提交 575bd10b 编写于 作者: L libb

add GLPaint

Change-Id: I87f9cdfe1972d15d9d682c64b81671df87cf3777
上级 b2d305eb
此差异已折叠。
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Server View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ServerViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="291" y="381"/>
</scene>
</scenes>
</document>
//
// ANImageBitmapRep.h
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "OSCommonImage.h"
#import "BitmapScaleManipulator.h"
#import "BitmapCropManipulator.h"
#import "BitmapRotationManipulator.h"
#import "BitmapDrawManipulator.h"
#import "UIImage+ANImageBitmapRep.h"
typedef struct {
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
} BMPixel;
BMPixel BMPixelMake (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha);
#if TARGET_OS_IPHONE
UIColor * UIColorFromBMPixel (BMPixel pixel);
#elif TARGET_OS_MAC
NSColor * NSColorFromBMPixel (BMPixel pixel);
#endif
@interface ANImageBitmapRep : BitmapContextRep <BitmapScaleManipulator, BitmapCropManipulator, BitmapRotationManipulator, BitmapDrawManipulator, NSCopying> {
#if __has_feature(objc_arc) == 1
__strong NSArray * baseClasses;
#else
NSArray * baseClasses;
#endif
}
#if __has_feature(objc_arc) == 1
+ (ANImageBitmapRep *)imageBitmapRepWithCGSize:(CGSize)avgSize __attribute__((ns_returns_autoreleased));
+ (ANImageBitmapRep *)imageBitmapRepWithImage:(ANImageObj *)anImage __attribute__((ns_returns_autoreleased));
#else
+ (ANImageBitmapRep *)imageBitmapRepWithCGSize:(CGSize)avgSize;
+ (ANImageBitmapRep *)imageBitmapRepWithImage:(ANImageObj *)anImage;
#endif
/**
* Reverses the RGB values of all pixels in the bitmap. This causes
* an "inverted" effect.
*/
- (void)invertColors;
/**
* Scales the image down, then back up again. Use this to blur an image.
* @param quality A percentage from 0 to 1, 0 being horrible quality, 1 being
* perfect quality.
*/
- (void)setQuality:(CGFloat)quality;
/**
* Darken or brighten the image.
* @param brightness A percentage from 0 to 2. In this case, 0 is the darkest
* and 2 is the brightest. If this is 1, no change will be made.
*/
- (void)setBrightness:(CGFloat)brightness;
/**
* Returns a pixel at a given location.
* @param point The point from which a pixel will be taken. For all points
* in a BitmapContextRep, the x and y values start at 0 and end at
* width - 1 and height - 1 respectively.
* @return The pixel with values taken from the specified point.
*/
- (BMPixel)getPixelAtPoint:(BMPoint)point;
/**
* Sets a pixel at a specific location.
* @param pixel An RGBA pixel represented by an array of four floats.
* Each component is one float long, and goes from 0 to 1.
* In this case, 0 is black and 1 is white.
* @param point The location of the pixel to change. For all points
* in a BitmapContextRep, the x and y values start at 0 and end at
* width - 1 and height - 1 respectively.
*/
- (void)setPixel:(BMPixel)pixel atPoint:(BMPoint)point;
/**
* Creates a new UIImage or NSImage from the bitmap context.
*/
#if __has_feature(objc_arc) == 1
- (ANImageObj *)image __attribute__((ns_returns_autoreleased));
#else
- (ANImageObj *)image;
#endif
@end
//
// ANImageBitmapRep.m
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "ANImageBitmapRep.h"
BMPixel BMPixelMake (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) {
BMPixel pixel;
pixel.red = red;
pixel.green = green;
pixel.blue = blue;
pixel.alpha = alpha;
return pixel;
}
#if TARGET_OS_IPHONE
UIColor * UIColorFromBMPixel (BMPixel pixel) {
return [UIColor colorWithRed:pixel.red green:pixel.green blue:pixel.blue alpha:pixel.alpha];
}
#elif TARGET_OS_MAC
NSColor * NSColorFromBMPixel (BMPixel pixel) {
return [NSColor colorWithCalibratedRed:pixel.red green:pixel.green blue:pixel.blue alpha:pixel.alpha];
}
#endif
@interface ANImageBitmapRep (BaseClasses)
- (void)generateBaseClasses;
@end
@implementation ANImageBitmapRep
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (!baseClasses) [self generateBaseClasses];
for (int i = 0; i < [baseClasses count]; i++) {
BitmapContextManipulator * manip = [baseClasses objectAtIndex:i];
if ([manip respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:manip];
return;
}
}
[self doesNotRecognizeSelector:[anInvocation selector]];
}
#if __has_feature(objc_arc) == 1
+ (ANImageBitmapRep *)imageBitmapRepWithCGSize:(CGSize)avgSize {
return [[ANImageBitmapRep alloc] initWithSize:BMPointMake(round(avgSize.width), round(avgSize.height))];
}
+ (ANImageBitmapRep *)imageBitmapRepWithImage:(ANImageObj *)anImage {
return [[ANImageBitmapRep alloc] initWithImage:anImage];
}
#else
+ (ANImageBitmapRep *)imageBitmapRepWithCGSize:(CGSize)avgSize {
return [[[ANImageBitmapRep alloc] initWithSize:BMPointMake(round(avgSize.width), round(avgSize.height))] autorelease];
}
+ (ANImageBitmapRep *)imageBitmapRepWithImage:(ANImageObj *)anImage {
return [[[ANImageBitmapRep alloc] initWithImage:anImage] autorelease];
}
#endif
- (void)invertColors {
UInt8 pixel[4];
BMPoint size = [self bitmapSize];
for (long y = 0; y < size.y; y++) {
for (long x = 0; x < size.x; x++) {
[self getRawPixel:pixel atPoint:BMPointMake(x, y)];
pixel[0] = 255 - pixel[0];
pixel[1] = 255 - pixel[1];
pixel[2] = 255 - pixel[2];
[self setRawPixel:pixel atPoint:BMPointMake(x, y)];
}
}
}
- (void)setQuality:(CGFloat)quality {
NSAssert(quality >= 0 && quality <= 1, @"Quality must be between 0 and 1.");
if (quality == 1.0) return;
CGSize cSize = CGSizeMake((CGFloat)([self bitmapSize].x) * quality, (CGFloat)([self bitmapSize].y) * quality);
BMPoint oldSize = [self bitmapSize];
[self setSize:BMPointMake(round(cSize.width), round(cSize.height))];
[self setSize:oldSize];
}
- (void)setBrightness:(CGFloat)brightness {
NSAssert(brightness >= 0 && brightness <= 2, @"Brightness must be between 0 and 2.");
BMPoint size = [self bitmapSize];
for (long y = 0; y < size.y; y++) {
for (long x = 0; x < size.x; x++) {
BMPoint point = BMPointMake(x, y);
BMPixel pixel = [self getPixelAtPoint:point];
pixel.red *= brightness;
pixel.green *= brightness;
pixel.blue *= brightness;
if (pixel.red > 1) pixel.red = 1;
if (pixel.green > 1) pixel.green = 1;
if (pixel.blue > 1) pixel.blue = 1;
[self setPixel:pixel atPoint:point];
}
}
}
- (BMPixel)getPixelAtPoint:(BMPoint)point {
UInt8 rawPixel[4];
[self getRawPixel:rawPixel atPoint:point];
BMPixel pixel;
pixel.alpha = (CGFloat)(rawPixel[3]) / 255.0;
pixel.red = ((CGFloat)(rawPixel[0]) / 255.0) / pixel.alpha;
pixel.green = ((CGFloat)(rawPixel[1]) / 255.0) / pixel.alpha;
pixel.blue = ((CGFloat)(rawPixel[2]) / 255.0) / pixel.alpha;
return pixel;
}
- (void)setPixel:(BMPixel)pixel atPoint:(BMPoint)point {
NSAssert(pixel.red >= 0 && pixel.red <= 1, @"Pixel color must range from 0 to 1.");
NSAssert(pixel.green >= 0 && pixel.green <= 1, @"Pixel color must range from 0 to 1.");
NSAssert(pixel.blue >= 0 && pixel.blue <= 1, @"Pixel color must range from 0 to 1.");
NSAssert(pixel.alpha >= 0 && pixel.alpha <= 1, @"Pixel color must range from 0 to 1.");
UInt8 rawPixel[4];
rawPixel[0] = round(pixel.red * 255.0 * pixel.alpha);
rawPixel[1] = round(pixel.green * 255.0 * pixel.alpha);
rawPixel[2] = round(pixel.blue * 255.0 * pixel.alpha);
rawPixel[3] = round(pixel.alpha * 255.0);
[self setRawPixel:rawPixel atPoint:point];
}
- (ANImageObj *)image {
return ANImageFromCGImage([self CGImage]);
}
#if __has_feature(objc_arc) != 1
- (void)dealloc {
[baseClasses release];
[super dealloc];
}
#endif
#pragma mark Base Classes
- (void)generateBaseClasses {
BitmapCropManipulator * croppable = [[BitmapCropManipulator alloc] initWithContext:self];
BitmapScaleManipulator * scalable = [[BitmapScaleManipulator alloc] initWithContext:self];
BitmapRotationManipulator * rotatable = [[BitmapRotationManipulator alloc] initWithContext:self];
BitmapDrawManipulator * drawable = [[BitmapDrawManipulator alloc] initWithContext:self];
baseClasses = [[NSArray alloc] initWithObjects:croppable, scalable, rotatable, drawable, nil];
#if __has_feature(objc_arc) != 1
[rotatable release];
[scalable release];
[croppable release];
[drawable release];
#endif
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
BMPoint size = [self bitmapSize];
ANImageBitmapRep * rep = [[ANImageBitmapRep allocWithZone:zone] initWithSize:size];
CGContextRef newContext = [rep context];
CGContextDrawImage(newContext, CGRectMake(0, 0, size.x, size.y), [self CGImage]);
[rep setNeedsUpdate:YES];
return rep;
}
@end
//
// BitmapContextRep.h
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "OSCommonImage.h"
#import "CGImageContainer.h"
#import "CGContextCreator.h"
/**
* A structure that defines a point in bitmap space.
* This is similar to the CGPoint structure, but it
* does not use floating points, making it more accurate.
*/
typedef struct {
long x;
long y;
} BMPoint;
BMPoint BMPointMake (long x, long y);
BMPoint BMPointFromSize (CGSize size);
BMPoint BMPointFromPoint (CGPoint point);
/**
* BitmapContextRep is a concrete subclass of NSObject that provides a basic
* class for mutating image's bitmaps. This class is very barebones,
* and generally will not be enough for most image manipulation.
*/
@interface BitmapContextRep : NSObject {
CGContextRef context;
CGImageRef lastImage;
unsigned char * bitmapData;
BOOL needsUpdate;
}
/**
* Creates a bitmap context with pixels and dimensions from an image.
* @param image The image to wrap in a bitmap context.
*/
- (id)initWithImage:(ANImageObj *)image;
/**
* Creates a bitmap context with the information from a CGImageRef.
* @param image The image to use for initialization. The reference will
* not be consumed, so you will still need to CGImageRelease() this as expected.
*/
- (id)initWithCGImage:(CGImageRef)img;
/**
* Creates a blank bitmap context with specified dimensions.
* @param sizePoint The size to use for the new bitmap. The x value
* of this is used for the width, and the y value is used for height.
*/
- (id)initWithSize:(BMPoint)sizePoint;
/**
* Returns the bitmap context underlying the image.
*/
- (CGContextRef)context;
/**
* Replaces the current context with a new one.
* @param aContext The new bitmap context for the image which will be retained
* and released automatically by the BitmapContextRep.
*/
- (void)setContext:(CGContextRef)aContext;
/**
* Returns the current size of the bitmap.
*/
- (BMPoint)bitmapSize;
/**
* Tells the BitmapContext that a new image should be generated when
* one is requested because the internal context has been externally
* modified.
* @param needsUpdate This should almost always be YES. If this is no,
* a new CGImageRef will not be generated when one is requested.
*/
- (void)setNeedsUpdate:(BOOL)flag;
/**
* Returns by reference a 4-byte RGBA pixel at a certain point.
* @param rgba A pointer to a 4-byte or more pixel buffer.
* @param point The point from which a pixel will be read. For all
* points in a BitmapContextRep, the x and y values start at 0 and end
* at width - 1 and height - 1 respectively.
*/
- (void)getRawPixel:(UInt8 *)rgba atPoint:(BMPoint)point;
/**
* Sets a 4-byte ARGB pixel at a specified point.
* @param rgba The pixel buffer containing at least 4 bytes.
* @param point The point at which the pixel will be set. For all
* points in a BitmapContextRep, the x and y values start at 0 and end
* at width - 1 and height - 1 respectively.
* @discussion Since alpha is premultiplied, it is important to remember to multiply
* the alpha as a percentage by the RGB values. This means that a white pixel
* with 50% alpha would become rgba(128, 128, 128, 128).
*/
- (void)setRawPixel:(const UInt8 *)rgba atPoint:(BMPoint)point;
/**
* Returns an autoreleased CGImageRef of the current BitmapContext.
*/
- (CGImageRef)CGImage;
/**
* Returns a mutable RGBA data array. If you modify this, you should call
* [context setNeedsUpdate:YES] before calling [context CGImage] or similar.
*/
- (unsigned char *)bitmapData;
@end
@protocol BitmapContextRep
@optional
- (CGContextRef)context;
- (void)setContext:(CGContextRef)aContext;
- (BMPoint)bitmapSize;
- (void)setNeedsUpdate:(BOOL)flag;
- (void)getRawPixel:(UInt8 *)rgba atPoint:(BMPoint)point;
- (void)setRawPixel:(const UInt8 *)rgba atPoint:(BMPoint)point;
- (CGImageRef)CGImage;
@end
//
// BitmapContextRep.m
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "BitmapContextRep.h"
BMPoint BMPointMake (long x, long y) {
BMPoint p;
p.x = x;
p.y = y;
return p;
}
BMPoint BMPointFromSize (CGSize size) {
return BMPointMake(round(size.width), round(size.height));
}
BMPoint BMPointFromPoint (CGPoint point) {
return BMPointMake(round(point.x), round(point.y));
}
@implementation BitmapContextRep
- (id)initWithImage:(ANImageObj *)image {
if ((self = [super init])) {
CGImageRef img = CGImageFromANImage(image);
context = [CGContextCreator newARGBBitmapContextWithImage:img];
bitmapData = CGBitmapContextGetData(context);
lastImage = CGImageRetain(img);
}
return self;
}
- (id)initWithCGImage:(CGImageRef)img {
if ((self = [super init])) {
context = [CGContextCreator newARGBBitmapContextWithImage:img];
bitmapData = CGBitmapContextGetData(context);
lastImage = CGImageRetain(img);
}
return self;
}
- (id)initWithSize:(BMPoint)sizePoint {
if ((self = [super init])) {
if (sizePoint.x == 0 || sizePoint.y == 0) {
#if __has_feature(objc_arc)
return nil;
#else
[super dealloc];
return nil;
#endif
}
context = [CGContextCreator newARGBBitmapContextWithSize:CGSizeMake(sizePoint.x, sizePoint.y)];
bitmapData = CGBitmapContextGetData(context);
lastImage = CGBitmapContextCreateImage(context);
}
return self;
}
- (CGContextRef)context {
return context;
}
- (void)setContext:(CGContextRef)aContext {
if (context == aContext) return;
// free previous.
CGContextRelease(context);
free(bitmapData);
// create new.
context = CGContextRetain(aContext);
bitmapData = CGBitmapContextGetData(aContext);
[self setNeedsUpdate:YES];
}
- (BMPoint)bitmapSize {
BMPoint point;
point.x = (long)CGBitmapContextGetWidth(context);
point.y = (long)CGBitmapContextGetHeight(context);
return point;
}
- (void)setNeedsUpdate:(BOOL)flag {
needsUpdate = flag;
}
- (void)getRawPixel:(UInt8 *)rgba atPoint:(BMPoint)point {
size_t width = CGBitmapContextGetWidth(context);
NSAssert(point.x >= 0 && point.x < width, @"Point must be within bitmap.");
NSAssert(point.y >= 0 && point.y < CGBitmapContextGetHeight(context), @"Point must be within bitmap.");
unsigned char * argbData = &bitmapData[((point.y * width) + point.x) * 4];
rgba[0] = argbData[1]; // red
rgba[1] = argbData[2]; // green
rgba[2] = argbData[3]; // blue
rgba[3] = argbData[0]; // alpha
[self setNeedsUpdate:YES];
}
- (void)setRawPixel:(const UInt8 *)rgba atPoint:(BMPoint)point {
size_t width = CGBitmapContextGetWidth(context);
NSAssert(point.x >= 0 && point.x < width, @"Point must be within bitmap.");
NSAssert(point.y >= 0 && point.y < CGBitmapContextGetHeight(context), @"Point must be within bitmap.");
unsigned char * argbData = &bitmapData[((point.y * width) + point.x) * 4];
argbData[1] = rgba[0]; // red
argbData[2] = rgba[1]; // green
argbData[3] = rgba[2]; // blue
argbData[0] = rgba[3]; // alpha
[self setNeedsUpdate:YES];
}
- (CGImageRef)CGImage {
if (needsUpdate) {
CGImageRelease(lastImage);
lastImage = CGBitmapContextCreateImage(context);
needsUpdate = NO;
}
#if __has_feature(objc_arc) == 1
return (__bridge CGImageRef)CGImageReturnAutoreleased(lastImage);
#else
return (CGImageRef)[[CGImageContainer imageContainerWithImage:lastImage] image];
#endif
}
- (unsigned char *)bitmapData {
return bitmapData;
}
- (void)dealloc {
CGContextRelease(context);
free(bitmapData);
if (lastImage != NULL) {
CGImageRelease(lastImage);
}
#if __has_feature(objc_arc) != 1
[super dealloc];
#endif
}
@end
//
// NSImage+ANImageBitmapRep.h
// ImageBitmapRep
//
// Created by Alex Nichol on 10/23/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE != 1
#import <Cocoa/Cocoa.h>
@class ANImageBitmapRep;
@interface NSImage (ANImageBitmapRep)
#if __has_feature(objc_arc) == 1
+ (NSImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr __attribute__((ns_returns_autoreleased));
- (ANImageBitmapRep *)imageBitmapRep __attribute__((ns_returns_autoreleased));
- (NSImage *)imageByScalingToSize:(CGSize)sz __attribute__((ns_returns_autoreleased));
- (NSImage *)imageFittingFrame:(CGSize)sz __attribute__((ns_returns_autoreleased));
- (NSImage *)imageFillingFrame:(CGSize)sz __attribute__((ns_returns_autoreleased));
#else
+ (NSImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr;
- (ANImageBitmapRep *)imageBitmapRep;
- (NSImage *)imageByScalingToSize:(CGSize)sz;
- (NSImage *)imageFittingFrame:(CGSize)sz;
- (NSImage *)imageFillingFrame:(CGSize)sz;
#endif
@end
#endif
//
// NSImage+ANImageBitmapRep.m
// ImageBitmapRep
//
// Created by Alex Nichol on 10/23/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE != 1
#import "NSImage+ANImageBitmapRep.h"
#import "ANImageBitmapRep.h"
@implementation NSImage (ANImageBitmapRep)
+ (NSImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr {
return [ibr image];
}
- (ANImageBitmapRep *)imageBitmapRep {
#if __has_feature(objc_arc) == 1
return [[ANImageBitmapRep alloc] initWithImage:self];
#else
return [[[ANImageBitmapRep alloc] initWithImage:self] autorelease];
#endif
}
- (NSImage *)imageByScalingToSize:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSize:BMPointMake(round(sz.width), round(sz.height))];
NSImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
- (NSImage *)imageFittingFrame:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSizeFittingFrame:BMPointMake(round(sz.width), round(sz.height))];
NSImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
- (NSImage *)imageFillingFrame:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSizeFillingFrame:BMPointMake(round(sz.width), round(sz.height))];
NSImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
@end
#endif
//
// OSCommonImage.h
// ImageBitmapRep
//
// Created by Alex Nichol on 10/23/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#ifndef ImageBitmapRep_OSCommonImage_h
#define ImageBitmapRep_OSCommonImage_h
#import "TargetConditionals.h"
#import "CGImageContainer.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
typedef UIImage ANImageObj;
#elif TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
typedef NSImage ANImageObj;
#endif
CGImageRef CGImageFromANImage (ANImageObj * anImageObj);
ANImageObj * ANImageFromCGImage (CGImageRef imageRef);
#endif
//
// OSCommonImage.c
// ImageBitmapRep
//
// Created by Alex Nichol on 10/23/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#include "OSCommonImage.h"
CGImageRef CGImageFromANImage (ANImageObj * anImageObj) {
#if TARGET_OS_IPHONE
return [anImageObj CGImage];
#elif TARGET_OS_MAC
CGImageSourceRef source;
#if __has_feature(objc_arc) == 1
source = CGImageSourceCreateWithData((__bridge CFDataRef)[anImageObj TIFFRepresentation], NULL);
#else
source = CGImageSourceCreateWithData((CFDataRef)[anImageObj TIFFRepresentation], NULL);
#endif
CGImageRef maskRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
CFRelease(source);
#if __has_feature(objc_arc) == 1
CGImageRef autoreleased = (__bridge CGImageRef)CGImageReturnAutoreleased(maskRef);
CGImageRelease(maskRef);
return autoreleased;
#else
CGImageContainer * container = [CGImageContainer imageContainerWithImage:maskRef];
CGImageRelease(maskRef);
return [container image];
#endif
#endif
}
ANImageObj * ANImageFromCGImage (CGImageRef imageRef) {
#if TARGET_OS_IPHONE
return [UIImage imageWithCGImage:imageRef];
#elif TARGET_OS_MAC
NSImage * image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize];
#if __has_feature(objc_arc) == 1
return image;
#else
return [image autorelease];
#endif
#endif
}
//
// UIImage+ANImageBitmapRep.h
// ImageBitmapRep
//
// Created by Alex Nichol on 8/11/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
@class ANImageBitmapRep;
#import <UIKit/UIKit.h>
@interface UIImage (ANImageBitmapRep)
#if __has_feature(objc_arc) == 1
+ (UIImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr __attribute__((ns_returns_autoreleased));
- (ANImageBitmapRep *)imageBitmapRep __attribute__((ns_returns_autoreleased));
- (UIImage *)imageByScalingToSize:(CGSize)sz __attribute__((ns_returns_autoreleased));
- (UIImage *)imageFittingFrame:(CGSize)sz __attribute__((ns_returns_autoreleased));
- (UIImage *)imageFillingFrame:(CGSize)sz __attribute__((ns_returns_autoreleased));
#else
+ (UIImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr;
- (ANImageBitmapRep *)imageBitmapRep;
- (UIImage *)imageByScalingToSize:(CGSize)sz;
- (UIImage *)imageFittingFrame:(CGSize)sz;
- (UIImage *)imageFillingFrame:(CGSize)sz;
#endif
@end
#endif
//
// UIImage+ANImageBitmapRep.m
// ImageBitmapRep
//
// Created by Alex Nichol on 8/11/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import "UIImage+ANImageBitmapRep.h"
#import "ANImageBitmapRep.h"
@implementation UIImage (ANImageBitmapRep)
+ (UIImage *)imageFromImageBitmapRep:(ANImageBitmapRep *)ibr {
return [ibr image];
}
- (ANImageBitmapRep *)imageBitmapRep {
#if __has_feature(objc_arc) == 1
return [[ANImageBitmapRep alloc] initWithImage:self];
#else
return [[[ANImageBitmapRep alloc] initWithImage:self] autorelease];
#endif
}
- (UIImage *)imageByScalingToSize:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSize:BMPointMake(round(sz.width), round(sz.height))];
UIImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
- (UIImage *)imageFittingFrame:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSizeFittingFrame:BMPointMake(round(sz.width), round(sz.height))];
UIImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
- (UIImage *)imageFillingFrame:(CGSize)sz {
ANImageBitmapRep * imageBitmap = [[ANImageBitmapRep alloc] initWithImage:self];
[imageBitmap setSizeFillingFrame:BMPointMake(round(sz.width), round(sz.height))];
UIImage * scaled = [imageBitmap image];
#if __has_feature(objc_arc) != 1
[imageBitmap release];
#endif
return scaled;
}
@end
#endif
//
// CGContextCreator.h
// ImageBitmapRep
//
// Created by Alex Nichol on 7/4/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <CoreGraphics/CoreGraphics.h>
#elif TARGET_OS_MAC
#import <Quartz/Quartz.h>
#endif
/**
* This class has several static methods for creating bitmap contexts.
* These methods are pretty much only called when creating a new
* ANImageBitmapRep.
*/
@interface CGContextCreator : NSObject {
}
+ (CGContextRef)newARGBBitmapContextWithSize:(CGSize)size;
+ (CGContextRef)newARGBBitmapContextWithImage:(CGImageRef)image;
@end
//
// CGContextCreator.m
// ImageBitmapRep
//
// Created by Alex Nichol on 7/4/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "CGContextCreator.h"
@implementation CGContextCreator
- (id)init {
if ((self = [super init])) {
// Initialization code here.
}
return self;
}
+ (CGContextRef)newARGBBitmapContextWithSize:(CGSize)size {
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Get image width, height. We'll use the entire image.
size_t pixelsWide = round(size.width);
size_t pixelsHigh = round(size.height);
bitmapBytesPerRow = (int)(pixelsWide * 4);
bitmapByteCount = (int)(bitmapBytesPerRow * pixelsHigh);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) {
fprintf(stderr, "Error allocating color space\n");
return NULL;
}
// allocate
bitmapData = malloc(bitmapByteCount);
if (bitmapData == NULL) {
NSLog(@"Malloc failed which is too bad. I was hoping to use this memory.");
CGColorSpaceRelease(colorSpace);
// even though CGContextRef technically is not a pointer,
// it's typedef probably is and it is a scalar anyway.
return NULL;
}
// Create the bitmap context. We are
// setting up the image as an ARGB (0-255 per component)
// 4-byte per/pixel.
context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8,
bitmapBytesPerRow,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
if (context == NULL) {
free (bitmapData);
NSLog(@"Failed to create bitmap!");
}
CGContextClearRect(context, CGRectMake(0, 0, size.width, size.height));
CGColorSpaceRelease(colorSpace);
return context;
}
+ (CGContextRef)newARGBBitmapContextWithImage:(CGImageRef)image {
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Get image width, height. We'll use the entire image.
size_t pixelsWide = CGImageGetWidth(image);
size_t pixelsHigh = CGImageGetHeight(image);
bitmapBytesPerRow = (int)(pixelsWide * 4);
bitmapByteCount = (int)(bitmapBytesPerRow * pixelsHigh);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) {
fprintf(stderr, "Error allocating color space\n");
return NULL;
}
// allocate
bitmapData = malloc(bitmapByteCount);
if (bitmapData == NULL) {
NSLog(@"Malloc failed which is too bad. I was hoping to use this memory.");
CGColorSpaceRelease(colorSpace);
// even though CGContextRef technically is not a pointer,
// it's typedef probably is and it is a scalar anyway.
return NULL;
}
// Create the bitmap context. We are
// setting up the image as an ARGB (0-255 per component)
// 4-byte per/pixel.
context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
if (context == NULL) {
free (bitmapData);
NSLog(@"Failed to create bitmap!");
}
// draw the image on the context.
// CGContextTranslateCTM(context, 0, CGImageGetHeight(image));
// CGContextScaleCTM(context, 1.0, -1.0);
CGContextClearRect(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)));
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
CGColorSpaceRelease(colorSpace);
return context;
}
#if __has_feature(objc_arc) != 1
- (void)dealloc {
[super dealloc];
}
#endif
@end
//
// CGImageContainer.h
// ImageBitmapRep
//
// Created by Alex Nichol on 5/3/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <CoreGraphics/CoreGraphics.h>
#elif TARGET_OS_MAC
#import <Quartz/Quartz.h>
#endif
#if __has_feature(objc_arc) != 1
@interface CGImageContainer : NSObject {
CGImageRef image;
}
/**
* The image that this container encloses.
*/
@property (readonly) CGImageRef image;
/**
* Create a new image container with an image.
* @param anImage Will be retained and enclosed in this class.
* This object will be released when the CGImageContainer is
* deallocated. This can be nil.
* @return The new image container, or nil if anImage is nil.
*/
- (id)initWithImage:(CGImageRef)anImage;
/**
* Create a new image container with an image.
* @param anImage Will be retained and enclosed in this class.
* This object will be released when the CGImageContainer is
* deallocated. This can be nil.
* @return The new image container, or nil if anImage is nil.
* The image container returned will be autoreleased.
*/
+ (CGImageContainer *)imageContainerWithImage:(CGImageRef)anImage;
@end
#else
id CGImageReturnAutoreleased (CGImageRef original) __attribute__((ns_returns_autoreleased));
#endif
//
// CGImageContainer.m
// ImageBitmapRep
//
// Created by Alex Nichol on 5/3/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "CGImageContainer.h"
#if __has_feature(objc_arc) != 1
@implementation CGImageContainer
@synthesize image;
- (id)initWithImage:(CGImageRef)anImage {
if ((self = [super init])) {
image = CGImageRetain(anImage);
}
return self;
}
+ (CGImageContainer *)imageContainerWithImage:(CGImageRef)anImage {
CGImageContainer * container = [(CGImageContainer *)[CGImageContainer alloc] initWithImage:anImage];
return [container autorelease];
}
- (void)dealloc {
CGImageRelease(image);
[super dealloc];
}
@end
#else
__attribute__((ns_returns_autoreleased))
id CGImageReturnAutoreleased (CGImageRef original) {
// CGImageRetain(original);
return (__bridge id)original;
}
#endif
//
// BitmapContextManip.h
// ImageBitmapRep
//
// Created by Alex Nichol on 10/14/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BitmapContextRep.h"
@interface BitmapContextManipulator : NSObject <BitmapContextRep> {
#if __has_feature(objc_arc) == 1
__unsafe_unretained BitmapContextRep * bitmapContext;
#else
BitmapContextRep * bitmapContext;
#endif
}
#if __has_feature(objc_arc) == 1
@property (nonatomic, assign) BitmapContextRep * bitmapContext;
#else
@property (nonatomic, assign) BitmapContextRep * bitmapContext;
#endif
- (id)initWithContext:(BitmapContextRep *)aContext;
@end
//
// BitmapContextManip.m
// ImageBitmapRep
//
// Created by Alex Nichol on 10/14/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "BitmapContextManipulator.h"
@implementation BitmapContextManipulator
@synthesize bitmapContext;
- (id)initWithContext:(BitmapContextRep *)aContext {
if ((self = [super init])) {
self.bitmapContext = aContext;
}
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:bitmapContext];
}
#if __has_feature(objc_arc) != 1
- (void)dealloc {
self.bitmapContext = nil;
[super dealloc];
}
#endif
@end
//
// CroppableBitmapRep.h
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BitmapContextManipulator.h"
@protocol BitmapCropManipulator
@optional
- (void)cropFrame:(CGRect)frame;
- (void)cropTopFrame:(CGRect)frame;
- (void)cropTopEllipse:(CGRect)frame;
- (CGImageRef)croppedImageWithFrame:(CGRect)frame;
@end
@interface BitmapCropManipulator : BitmapContextManipulator {
}
/**
* Cuts a part of the bitmap out for a new bitmap.
* @param frame The rectangle from which a portion of the image will
* be cut.
* The coordinates for this start at (0,0).
* @discussion The coordinates for this method begin in the bottom
* left corner. For a coordinate system starting from the top
* left corner, use cropTopFrame: instead.
*/
- (void)cropFrame:(CGRect)frame;
/**
* Cuts a part of the bitmap out for a new bitmap.
* @param frame The rectangle from which a portion of the image will
* be cut.
* The coordinates for this start at (0,0).
* @discussion The coordinates for this method begin in the top
* left corner. For a coordinate system starting from the bottom
* left corner, use cropFrame: instead.
*/
- (void)cropTopFrame:(CGRect)frame;
/**
* Cuts an ellipse of the bitmap out for a new bitmap.
* @param frame The rectangle around the ellipse to be cut. The
* coordinates for this start at (0,0).
* @discussion The coordinates for this method begin in the top
* left corner. There is no alternative.
*/
- (void)cropTopEllipse:(CGRect)frame;
/**
* Creates a new CGImageRef by cutting out a portion of this one.
* This takes its behavoir from cropFrame.
* @return An autoreleased CGImageRef that has been cropped from this
* image.
*/
- (CGImageRef)croppedImageWithFrame:(CGRect)frame;
@end
//
// CroppableBitmapRep.m
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "BitmapCropManipulator.h"
@implementation BitmapCropManipulator
- (void)cropFrame:(CGRect)frame {
BMPoint size = [bitmapContext bitmapSize];
// It's kind of rude to prevent them from doing something kind of cool, so let's not.
// NSAssert(frame.origin.x >= 0 && frame.origin.x + frame.size.width <= size.x, @"Cropping frame must be within the bitmap.");
// NSAssert(frame.origin.y >= 0 && frame.origin.y + frame.size.height <= size.y, @"Cropping frame must be within the bitmap.");
CGContextRef newBitmap = [CGContextCreator newARGBBitmapContextWithSize:frame.size];
CGPoint offset = CGPointMake(-frame.origin.x, -frame.origin.y);
CGContextDrawImage(newBitmap, CGRectMake(offset.x, offset.y, size.x, size.y), [bitmapContext CGImage]);
[bitmapContext setContext:newBitmap];
CGContextRelease(newBitmap);
}
- (void)cropTopFrame:(CGRect)frame {
BMPoint size = [bitmapContext bitmapSize];
// It's kind of rude to prevent them from doing something kind of cool, so let's not.
// NSAssert(frame.origin.x >= 0 && frame.origin.x + frame.size.width <= size.x, @"Cropping frame must be within the bitmap.");
// NSAssert(frame.origin.y >= 0 && frame.origin.y + frame.size.height <= size.y, @"Cropping frame must be within the bitmap.");
CGContextRef newBitmap = [CGContextCreator newARGBBitmapContextWithSize:frame.size];
CGPoint offset = CGPointMake(-frame.origin.x, -(size.y - (frame.origin.y + frame.size.height)));
CGContextDrawImage(newBitmap, CGRectMake(offset.x, offset.y, size.x, size.y), [bitmapContext CGImage]);
[bitmapContext setContext:newBitmap];
CGContextRelease(newBitmap);
}
- (void)cropTopEllipse:(CGRect)frame {
frame.origin.x = round(frame.origin.x);
frame.origin.y = round(frame.origin.y);
frame.size.width = round(frame.size.width);
frame.size.height = round(frame.size.height);
BMPoint size = [bitmapContext bitmapSize];
// It's kind of rude to prevent them from doing something kind of cool, so let's not.
// NSAssert(frame.origin.x >= 0 && frame.origin.x + frame.size.width <= size.x, @"Cropping frame must be within the bitmap.");
// NSAssert(frame.origin.y >= 0 && frame.origin.y + frame.size.height <= size.y, @"Cropping frame must be within the bitmap.");
CGContextRef newBitmap = [CGContextCreator newARGBBitmapContextWithSize:frame.size];
CGPoint offset = CGPointMake(-frame.origin.x, -(size.y - (frame.origin.y + frame.size.height)));
CGContextSaveGState(newBitmap);
CGContextBeginPath(newBitmap);
CGContextAddEllipseInRect(newBitmap, CGRectMake(0, 0, frame.size.width, frame.size.height));
CGContextClip(newBitmap);
CGContextDrawImage(newBitmap, CGRectMake(offset.x, offset.y, size.x, size.y), [bitmapContext CGImage]);
CGContextRestoreGState(newBitmap);
[bitmapContext setContext:newBitmap];
CGContextRelease(newBitmap);
}
- (CGImageRef)croppedImageWithFrame:(CGRect)frame {
BMPoint size = [bitmapContext bitmapSize];
// It's kind of rude to prevent them from doing something kind of cool, so let's not.
// NSAssert(frame.origin.x >= 0 && frame.origin.x + frame.size.width <= size.x, @"Cropping frame must be within the bitmap.");
// NSAssert(frame.origin.y >= 0 && frame.origin.y + frame.size.height <= size.y, @"Cropping frame must be within the bitmap.");
CGContextRef newBitmap = [CGContextCreator newARGBBitmapContextWithSize:frame.size];
CGPoint offset = CGPointMake(-frame.origin.x, -frame.origin.y);
CGContextDrawImage(newBitmap, CGRectMake(offset.x, offset.y, size.x, size.y), [bitmapContext CGImage]);
CGImageRef image = CGBitmapContextCreateImage(newBitmap);
CGContextRelease(newBitmap);
#if __has_feature(objc_arc) == 1
CGImageRef retainedAutorelease = (__bridge CGImageRef)CGImageReturnAutoreleased(image);
CGImageRelease(image);
return retainedAutorelease;
#else
CGImageContainer * container = [CGImageContainer imageContainerWithImage:image];
CGImageRelease(image);
return [container image];
#endif
}
@end
//
// BitmapDrawManipulator.h
// FaceBlur
//
// Created by Alex Nichol on 7/1/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BitmapContextManipulator.h"
@protocol BitmapDrawManipulator
@optional
- (void)drawImage:(CGImageRef)image inRect:(CGRect)rect;
- (void)drawEllipseInFrame:(CGRect)frame color:(CGColorRef)color;
@end
@interface BitmapDrawManipulator : BitmapContextManipulator
/**
* Overlays an image on the existing bitmap.
* @param image The image to be overlayed.
* @param rect The frame in which the image will be drawn.
* The coordinates for this begin at the top-left hand
* corner of the view.
*/
- (void)drawImage:(CGImageRef)image inRect:(CGRect)rect;
/**
* Draws a colored ellipse in a given rectangle.
* @param frame The rectangle in which to draw the ellipse
* @param color The fill color for the ellipse. The coordinates
* for this begin at the top-left hand corner of the view.
*/
- (void)drawEllipseInFrame:(CGRect)frame color:(CGColorRef)color;
@end
//
// BitmapDrawManipulator.m
// FaceBlur
//
// Created by Alex Nichol on 7/1/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "BitmapDrawManipulator.h"
@implementation BitmapDrawManipulator
- (void)drawImage:(CGImageRef)image inRect:(CGRect)rect {
BMPoint size = [bitmapContext bitmapSize];
// It's kind of rude to prevent them from doing something kind of cool, so let's not.
// NSAssert(frame.origin.x >= 0 && frame.origin.x + frame.size.width <= size.x, @"Cropping frame must be within the bitmap.");
// NSAssert(frame.origin.y >= 0 && frame.origin.y + frame.size.height <= size.y, @"Cropping frame must be within the bitmap.");
CGPoint offset = CGPointMake(rect.origin.x, (size.y - (rect.origin.y + rect.size.height)));
CGContextRef context = [[self bitmapContext] context];
CGContextSaveGState(context);
CGContextDrawImage(context, CGRectMake(offset.x, offset.y, rect.size.width, rect.size.height), image);
CGContextRestoreGState(context);
[self.bitmapContext setNeedsUpdate:YES];
}
- (void)drawEllipseInFrame:(CGRect)frame color:(CGColorRef)color {
CGContextRef context = [[self bitmapContext] context];
CGContextSaveGState(context);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -[bitmapContext bitmapSize].y);
CGContextSetFillColorWithColor(context, color);
CGContextFillEllipseInRect(context, frame);
CGContextRestoreGState(context);
[self.bitmapContext setNeedsUpdate:YES];
}
@end
//
// RotatableBitmapRep.h
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BitmapContextManipulator.h"
@protocol BitmapRotationManipulator
@optional
- (void)rotate:(CGFloat)degrees;
- (CGImageRef)imageByRotating:(CGFloat)degrees;
@end
@interface BitmapRotationManipulator : BitmapContextManipulator {
}
/**
* Rotate the image bitmap around its center by a certain number of degrees.
* @param degrees The degrees from 0 to 360. This is not measured in radians.
* @discussion This will resize the image if needed.
*/
- (void)rotate:(CGFloat)degrees;
/**
* Create a new image by rotating this image bitmap around its center by a specified
* number of degrees.
* @param degrees The degrees (not in radians) by which the image should be rotated.
* @discussion This will resize the image if needed.
*/
- (CGImageRef)imageByRotating:(CGFloat)degrees;
@end
//
// RotatableBitmapRep.m
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "BitmapRotationManipulator.h"
#define DEGTORAD(x) (x * (M_PI / 180.0f))
static CGPoint locationForAngle (CGFloat angle, CGFloat hypotenuse) {
CGPoint p;
p.x = (CGFloat)cos((double)DEGTORAD(angle)) * hypotenuse;
p.y = (CGFloat)sin((double)DEGTORAD(angle)) * hypotenuse;
return p;
}
@implementation BitmapRotationManipulator
- (void)rotate:(CGFloat)degrees {
if (degrees == 0) return;
CGSize size = CGSizeMake([bitmapContext bitmapSize].x, [bitmapContext bitmapSize].y);
CGSize newSize = CGSizeZero;
/* Since the corners go off to the sides, we have to use the existing hypotenuse to calculate the new size
for the image. This is done using some basic trigonometry.
*/
CGFloat hypotenuse;
hypotenuse = (CGFloat)sqrt(pow((double)size.width / 2.0, 2.0) + pow((double)size.height / 2.0, 2.0));
CGPoint minP = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
CGPoint maxP = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
/* Find the angle for the corners. */
float firstAngle = (float)atan2((double)size.height / 2.0, (double)size.width / 2.0);
float secondAngle = (float)atan2((double)size.height / 2.0, (double)size.width / -2.0);
float thirdAngle = (float)atan2((double)size.height / -2.0, (double)size.width / -2.0);
float fourthAngle = (float)atan2((double)size.height / -2.0, (double)size.width / 2.0);
float angles[4] = {firstAngle, secondAngle, thirdAngle, fourthAngle};
/* Rotate the corners by the new degrees, finding out how outgoing
the corners will be. This will allow us to easily calculate
the new size of the image.
*/
for (int i = 0; i < 4; i++) {
// conver the angle to radians.
float deg = angles[i] * (float)(180.0f / M_PI);
CGPoint p1 = locationForAngle(deg + degrees, hypotenuse);
if (p1.x < minP.x) minP.x = p1.x;
if (p1.x > maxP.x) maxP.x = p1.x;
if (p1.y < minP.y) minP.y = p1.y;
if (p1.y > maxP.y) maxP.y = p1.y;
}
newSize.width = maxP.x - minP.x;
newSize.height = maxP.y - minP.y;
/* Figure out where the thing is going to go when rotated by the bottom left
corner. Use that information to translate it so that it rotates from the center.
*/
hypotenuse = (CGFloat)sqrt((pow(newSize.width / 2.0, 2) + pow(newSize.height / 2.0, 2)));
CGPoint newCenter;
float addAngle = (float)atan2((double)newSize.height / 2, (double)newSize.width / 2) * (float)(180.0f / M_PI);
newCenter.x = cos((float)DEGTORAD((degrees + addAngle))) * hypotenuse;
newCenter.y = sin((float)DEGTORAD((degrees + addAngle))) * hypotenuse;
CGPoint offsetCenter;
offsetCenter.x = (float)((float)newSize.width / 2.0f) - (float)newCenter.x;
offsetCenter.y = (float)((float)newSize.height / 2.0f) - (float)newCenter.y;
CGContextRef newContext = [CGContextCreator newARGBBitmapContextWithSize:newSize];
CGContextSaveGState(newContext);
CGContextTranslateCTM(newContext, (float)round((float)offsetCenter.x), (float)round((float)offsetCenter.y));
CGContextRotateCTM(newContext, (CGFloat)DEGTORAD(degrees));
CGRect drawRect;
drawRect.size = size;
drawRect.origin.x = (CGFloat)round((newSize.width / 2) - (size.width / 2));
drawRect.origin.y = (CGFloat)round((newSize.height / 2) - (size.height / 2));
CGContextDrawImage(newContext, drawRect, [bitmapContext CGImage]);
CGContextRestoreGState(newContext);
[bitmapContext setContext:newContext];
CGContextRelease(newContext);
}
- (CGImageRef)imageByRotating:(CGFloat)degrees {
if (degrees == 0) return [bitmapContext CGImage];
CGSize size = CGSizeMake([bitmapContext bitmapSize].x, [bitmapContext bitmapSize].y);
CGSize newSize = CGSizeZero;
/* Since the corners go off to the sides, we have to use the existing hypotenuse to calculate the new size
for the image. This is done using some basic trigonometry.
*/
CGFloat hypotenuse;
hypotenuse = (CGFloat)sqrt(pow((double)size.width / 2.0, 2.0) + pow((double)size.height / 2.0, 2.0));
CGPoint minP = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
CGPoint maxP = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
/* Find the angle for the corners. */
float firstAngle = (float)atan2((double)size.height / 2.0, (double)size.width / 2.0);
float secondAngle = (float)atan2((double)size.height / 2.0, (double)size.width / -2.0);
float thirdAngle = (float)atan2((double)size.height / -2.0, (double)size.width / -2.0);
float fourthAngle = (float)atan2((double)size.height / -2.0, (double)size.width / 2.0);
float angles[4] = {firstAngle, secondAngle, thirdAngle, fourthAngle};
/* Rotate the corners by the new degrees, finding out how outgoing
the corners will be. This will allow us to easily calculate
the new size of the image.
*/
for (int i = 0; i < 4; i++) {
// conver the angle to radians.
float deg = angles[i] * (float)(180.0f / M_PI);
CGPoint p1 = locationForAngle(deg + degrees, hypotenuse);
if (p1.x < minP.x) minP.x = p1.x;
if (p1.x > maxP.x) maxP.x = p1.x;
if (p1.y < minP.y) minP.y = p1.y;
if (p1.y > maxP.y) maxP.y = p1.y;
}
newSize.width = ceil(maxP.x - minP.x);
newSize.height = ceil(maxP.y - minP.y);
/* Figure out where the thing is going to go when rotated by the bottom left
corner. Use that information to translate it so that it rotates from the center.
*/
hypotenuse = (CGFloat)sqrt((pow(newSize.width / 2.0, 2) + pow(newSize.height / 2.0, 2)));
CGPoint newCenter;
float addAngle = (float)atan2((double)newSize.height / 2, (double)newSize.width / 2) * (float)(180.0f / M_PI);
newCenter.x = cos((float)DEGTORAD((degrees + addAngle))) * hypotenuse;
newCenter.y = sin((float)DEGTORAD((degrees + addAngle))) * hypotenuse;
CGPoint offsetCenter;
offsetCenter.x = (float)((float)newSize.width / 2.0f) - (float)newCenter.x;
offsetCenter.y = (float)((float)newSize.height / 2.0f) - (float)newCenter.y;
CGContextRef newContext = [CGContextCreator newARGBBitmapContextWithSize:newSize];
CGContextSaveGState(newContext);
CGContextTranslateCTM(newContext, (float)round((float)offsetCenter.x), (float)round((float)offsetCenter.y));
CGContextRotateCTM(newContext, (CGFloat)DEGTORAD(degrees));
CGRect drawRect;
drawRect.size = size;
drawRect.origin.x = (CGFloat)round((newSize.width / 2) - (size.width / 2));
drawRect.origin.y = (CGFloat)round((newSize.height / 2) - (size.height / 2));
CGContextDrawImage(newContext, drawRect, [bitmapContext CGImage]);
CGContextRestoreGState(newContext);
CGImageRef image = CGBitmapContextCreateImage(newContext);
void * buff = CGBitmapContextGetData(newContext);
CGContextRelease(newContext);
free(buff);
#if __has_feature(objc_arc) == 1
id retainedImage = CGImageReturnAutoreleased(image);
CGImageRelease(image);
return (__bridge CGImageRef)retainedImage;
#else
CGImageContainer * container = [CGImageContainer imageContainerWithImage:image];
CGImageRelease(image);
return [container image];
#endif
}
@end
//
// ScalableBitmapRep.h
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BitmapContextManipulator.h"
@protocol BitmapScaleManipulator <NSObject>
@optional
- (void)setSize:(BMPoint)aSize;
- (void)setSizeFittingFrame:(BMPoint)aSize;
- (void)setSizeFillingFrame:(BMPoint)aSize;
@end
@interface BitmapScaleManipulator : BitmapContextManipulator {
}
/**
* Stretches the bitmap context to a specified size.
* @param aSize The new size to make the bitmap.
* If this is the same as the current size, the bitmap
* will not be changed.
*/
- (void)setSize:(BMPoint)aSize;
/**
* Scales the image to fit a particular frame without stretching (bringing out of scale).
* @param aSize The size to which the image scaled.
* @discussion The actual image itself will most likely be smaller than the specified
* size, leaving transparent edges to make the image fit the exact size.
*/
- (void)setSizeFittingFrame:(BMPoint)aSize;
/**
* Scales the image to fill a particular frame without stretching.
* This will most likely cause the left and right or top and bottom
* edges of the image to be cut off.
* @param aSize The size that the image will be forced to fill.
*/
- (void)setSizeFillingFrame:(BMPoint)aSize;
@end
//
// ScalableBitmapRep.m
// ImageManip
//
// Created by Alex Nichol on 7/12/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "BitmapScaleManipulator.h"
@implementation BitmapScaleManipulator
- (void)setSize:(BMPoint)aSize {
CGContextRef newContext = [CGContextCreator newARGBBitmapContextWithSize:CGSizeMake(aSize.x, aSize.y)];
CGImageRef image = [bitmapContext CGImage];
CGContextDrawImage(newContext, CGRectMake(0, 0, aSize.x, aSize.y), image);
[bitmapContext setContext:newContext];
CGContextRelease(newContext);
}
- (void)setSizeFittingFrame:(BMPoint)aSize {
CGSize oldSize = CGSizeMake([bitmapContext bitmapSize].x, [bitmapContext bitmapSize].y);
CGSize newSize = CGSizeMake(aSize.x, aSize.y);
float wratio = newSize.width / oldSize.width;
float hratio = newSize.height / oldSize.height;
float scaleRatio;
if (wratio < hratio) {
scaleRatio = wratio;
} else {
scaleRatio = hratio;
}
scaleRatio = scaleRatio;
CGSize newContentSize = CGSizeMake(oldSize.width * scaleRatio, oldSize.height * scaleRatio);
CGImageRef image = [bitmapContext CGImage];
CGContextRef newContext = [CGContextCreator newARGBBitmapContextWithSize:CGSizeMake(aSize.x, aSize.y)];
CGContextDrawImage(newContext, CGRectMake(newSize.width / 2 - (newContentSize.width / 2),
newSize.height / 2 - (newContentSize.height / 2),
newContentSize.width, newContentSize.height), image);
[bitmapContext setContext:newContext];
CGContextRelease(newContext);
}
- (void)setSizeFillingFrame:(BMPoint)aSize {
CGSize oldSize = CGSizeMake([bitmapContext bitmapSize].x, [bitmapContext bitmapSize].y);
CGSize newSize = CGSizeMake(aSize.x, aSize.y);
float wratio = newSize.width / oldSize.width;
float hratio = newSize.height / oldSize.height;
float scaleRatio;
if (wratio > hratio) { // only difference from -setSizeFittingFrame:
scaleRatio = wratio;
} else {
scaleRatio = hratio;
}
scaleRatio = scaleRatio;
CGSize newContentSize = CGSizeMake(oldSize.width * scaleRatio, oldSize.height * scaleRatio);
CGImageRef image = [bitmapContext CGImage];
CGContextRef newContext = [CGContextCreator newARGBBitmapContextWithSize:CGSizeMake(aSize.x, aSize.y)];
CGContextDrawImage(newContext, CGRectMake(newSize.width / 2 - (newContentSize.width / 2),
newSize.height / 2 - (newContentSize.height / 2),
newContentSize.width, newContentSize.height), image);
[bitmapContext setContext:newContext];
CGContextRelease(newContext);
}
@end
/**
* BGRSLoupeLayer.h
* Copyright (c) 2011, Benjamin Guest.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* -Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* -Neither the name of Benjamin Guest nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@class RSColorPickerView;
@interface BGRSLoupeLayer : CALayer {
BOOL isReadyToDismiss;
BOOL isRunningInitialAnimation;
}
@property (nonatomic) CGPoint loupeCenter;
@property (nonatomic, weak) RSColorPickerView *colorPicker;
#pragma mark - Drawing
- (void)drawGridInContext:(CGContextRef)ctx;
#pragma mark - Animation
- (void)appearInColorPicker:(RSColorPickerView *)aColorPicker;
- (void)disappear;
- (void)disappearAnimated:(BOOL)anim;
@end
/**
* BGRSLoupeLayer.m
* Copyright (c) 2011, Benjamin Guest.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* -Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* -Neither the name of Benjamin Guest nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#import "BGRSLoupeLayer.h"
#import "RSColorPickerView.h"
@interface BGRSLoupeLayer ()
@property (nonatomic) struct CGPath *gridCirclePath;
- (void)drawGlintInContext:(CGContextRef)ctx;
- (UIImage *)loupeImage;
@end
@implementation BGRSLoupeLayer
@synthesize loupeCenter, colorPicker;
//设置中间放大器半径
const CGFloat LOUPE_SIZE = 55, SHADOW_SIZE = 6, RIM_THICKNESS = 3.0;
const int NUM_PIXELS = 5, NUM_SKIP = 15;
- (id)init {
self = [super init];
if (self) {
CGFloat size = LOUPE_SIZE+2*SHADOW_SIZE;
self.bounds = CGRectMake(-size/2,-size/2,size,size);
self.anchorPoint = CGPointMake(0.5, 1);
UIImage *loupeImage = [self loupeImage];
CALayer *loupeLayer = [CALayer layer];
loupeLayer.bounds = self.bounds;
loupeLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
loupeLayer.contents = (id)loupeImage.CGImage;
[self addSublayer:loupeLayer];
}
return self;
}
- (void)dealloc {
self.colorPicker = nil;
if (_gridCirclePath) CGPathRelease(_gridCirclePath);
}
- (struct CGPath *)gridCirclePath {
if (_gridCirclePath == NULL) {
CGMutablePathRef circlePath = CGPathCreateMutable();
const CGFloat radius = LOUPE_SIZE/2;
CGPathAddArc(circlePath, nil, 0, 0, radius-RIM_THICKNESS/2, 0, 2*M_PI, YES);
_gridCirclePath = circlePath;
}
return _gridCirclePath;
}
- (UIImage *)loupeImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat size = LOUPE_SIZE+2*SHADOW_SIZE;
CGContextTranslateCTM(ctx, size/2, size/2);
// Draw Shadow
CGContextSaveGState(ctx); // Save before shadow
UIBezierPath *inner = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, SHADOW_SIZE + 1, SHADOW_SIZE + 1)];
UIBezierPath *outer = [UIBezierPath bezierPathWithRect:self.bounds];
[outer appendPath:inner];
outer.usesEvenOddFillRule = YES;
[outer addClip];
CGSize shadowOffset = CGSizeMake(0,SHADOW_SIZE/2);
CGContextSetShadowWithColor(ctx, shadowOffset, SHADOW_SIZE/2, [UIColor blackColor].CGColor);
CGContextAddEllipseInRect(ctx, CGRectMake(-LOUPE_SIZE/2, -LOUPE_SIZE/2, LOUPE_SIZE, LOUPE_SIZE));
CGContextSetFillColorWithColor(ctx, [colorPicker selectionColor].CGColor);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx); // Restore context after shadow
// Create Cliping Area
CGContextSaveGState(ctx); // Save context for cliping
CGContextAddPath(ctx, self.gridCirclePath); // Clip gird drawing to inside of loupe
CGContextClip(ctx);
[self drawGlintInContext:ctx];
CGContextRestoreGState(ctx); // Restor from clip drawing
// Stroke Rim of Loupe
CGContextSetLineWidth(ctx, RIM_THICKNESS);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextAddPath(ctx, self.gridCirclePath);
CGContextStrokePath(ctx);
// Draw center of rim loupe
CGContextSetLineWidth(ctx, RIM_THICKNESS-1);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGContextAddPath(ctx, self.gridCirclePath);
CGContextStrokePath(ctx);
const CGFloat w = ceilf(LOUPE_SIZE/NUM_PIXELS);
// Draw Selection Square
CGFloat xyOffset = -(w+1)/2;
CGRect selectedRect = CGRectMake(xyOffset, xyOffset, w, w);
CGContextAddRect(ctx, selectedRect);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetLineWidth(ctx, 1.0);
CGContextStrokePath(ctx);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (void)drawInContext:(CGContextRef)ctx {
CGContextAddPath(ctx, self.gridCirclePath); // Clip gird drawing to inside of loupe
CGContextClip(ctx);
// Draw Opacity Background
NSInteger numCols = 6;
CGFloat loupeLength = LOUPE_SIZE;
CGFloat pixelLength = loupeLength / numCols;
UIColor *colorWhite = [UIColor whiteColor];
UIColor *colorGray = [UIColor grayColor];
UIColor *color1;
UIColor *color2;
UIColor *pixelColor;
for (int j = 0; j < numCols; j++){
color1 = (j % 2) ? colorWhite : colorGray;
color2 = (j % 2) ? colorGray : colorWhite;
for (int i = 0; i < numCols; i++){
CGRect pixelRect = CGRectMake((pixelLength * i) - (loupeLength / 2),
(pixelLength * j) - (loupeLength / 2),
pixelLength,
pixelLength);
pixelColor = (i % 2) ? color1 : color2;
CGContextSetFillColorWithColor(ctx, pixelColor.CGColor);
CGContextFillRect(ctx, pixelRect);
}
}
[self drawGridInContext:ctx];
}
- (void)drawGridInContext:(CGContextRef)ctx {
const CGFloat w = ceilf(LOUPE_SIZE/NUM_PIXELS);
CGPoint currentPoint = [colorPicker selection];
currentPoint.x -= NUM_PIXELS*NUM_SKIP/2;
currentPoint.y -= NUM_PIXELS*NUM_SKIP/2;
int i,j;
// Draw Pixelated Loupe
for (j=0; j<NUM_PIXELS; j++){
for (i=0; i<NUM_PIXELS; i++){
CGRect pixelRect = CGRectMake(w*i-LOUPE_SIZE/2, w*j-LOUPE_SIZE/2, w, w);
UIColor *pixelColor = [self.colorPicker colorAtPoint:currentPoint];
CGContextSetFillColorWithColor(ctx, pixelColor.CGColor);
CGContextFillRect(ctx, pixelRect);
currentPoint.x += NUM_SKIP;
}
currentPoint.x -= NUM_PIXELS*NUM_SKIP;
currentPoint.y += NUM_SKIP;
}
}
- (void)drawGlintInContext:(CGContextRef)ctx{
// Draw Top Glint
CGFloat radius = LOUPE_SIZE/2;
CGFloat glintRadius = 1.50*LOUPE_SIZE;
CGFloat drop = 0.25*LOUPE_SIZE;
CGFloat yOff = drop + glintRadius - radius;
// Calculations
CGFloat glintAngle1 = acosf((yOff*yOff + glintRadius*glintRadius - radius*radius)
/(2*yOff*glintRadius));
CGFloat glintAngle2 = asinf(glintRadius/radius * sinf(glintAngle1));
CGFloat glintEdgeHeight = -radius*sinf(glintAngle2-M_PI_2);
// Add bottom arc
CGContextAddArc(ctx, 0, yOff, glintRadius, -M_PI_2+glintAngle1, -M_PI_2-glintAngle1, YES);
// Add top arc
CGContextAddArc(ctx, 0, 0, radius, -M_PI_2-glintAngle2, -M_PI_2+glintAngle2, NO);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
//CGContextStrokePath(ctx);
//return;
CGContextClosePath(ctx);
CGContextSaveGState(ctx); // Save context for cliping
CGContextClip(ctx);
CGColorSpaceRef space = CGColorSpaceCreateDeviceGray();
NSArray *colors = @[(id)[UIColor colorWithWhite:1.0 alpha:0.65].CGColor,
(id)[UIColor colorWithWhite:1.0 alpha:0.15].CGColor];
CGGradientRef myGradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, NULL);
CGContextDrawLinearGradient(ctx, myGradient ,CGPointMake(0,-radius), CGPointMake(0,-glintEdgeHeight), 0);
CGGradientRelease(myGradient);
CGContextRestoreGState(ctx);
// Draw bottom glint
yOff = 0.40*LOUPE_SIZE;
radius = 0.40*LOUPE_SIZE;
CGPoint glintCenter = CGPointMake(0, yOff);
CGContextAddArc(ctx, 0, yOff, radius, 0, M_2_PI, YES);
CGContextSaveGState(ctx); // Save context for cliping
CGContextClip(ctx);
colors = @[(id)[UIColor colorWithWhite:1.0 alpha:0.5].CGColor,
(id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor];
myGradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, NULL);
CGContextDrawRadialGradient(ctx, myGradient, glintCenter, 0.0, glintCenter, radius, 0.0);
CGGradientRelease(myGradient);
CGContextRestoreGState(ctx);
// Release objects
CGColorSpaceRelease(space);
}
#pragma mark - Animation -
static NSString *const kAppearKey = @"cp_l_appear";
- (void)appearInColorPicker:(RSColorPickerView*)aColorPicker{
if (self.colorPicker != aColorPicker) {
self.colorPicker = aColorPicker;
}
[self removeAllAnimations];
self.transform = CATransform3DIdentity;
isReadyToDismiss = NO;
// Add Layer to color picker
[CATransaction setDisableActions:YES];
[self.colorPicker.layer addSublayer:self];
// Animate Arival
isRunningInitialAnimation = YES;
CAKeyframeAnimation *springEffect = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
springEffect.values = @[@(0.1), @(1.4), @(0.95), @(1)];
springEffect.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
springEffect.removedOnCompletion = NO;
springEffect.duration = 0.35f;
springEffect.delegate = self;
// Animate
[self addAnimation:springEffect forKey:kAppearKey];
}
/**
* Disapear removes the loupe view from the color picker by shrinking it down to zero
*/
static NSString *const kDisappearKey = @"cp_l_disappear";
- (void)disappear {
[self disappearAnimated:YES];
}
- (void)disappearAnimated:(BOOL)anim {
isReadyToDismiss = YES;
if (isRunningInitialAnimation) return;
if (!anim) {
[self removeFromSuperlayer];
return;
}
self.transform = CATransform3DMakeScale(0.01, 0.01, 1);
CABasicAnimation *disapear = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
disapear.fromValue = @(1);
disapear.duration = 0.1f;
disapear.delegate = self;
disapear.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
disapear.removedOnCompletion = NO;
[self addAnimation:disapear forKey:kDisappearKey];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (anim == [self animationForKey:kDisappearKey]){
if (!flag) return;
[self removeFromSuperlayer];
self.transform = CATransform3DIdentity;
} else if (anim == [self animationForKey:kAppearKey]) {
isRunningInitialAnimation = NO;
if (isReadyToDismiss) {
[self disappear];
}
}
}
@end
//
// RSBrightnessSlider.h
// RSColorPicker
//
// Created by Ryan Sullivan on 8/12/11.
//
#import <Foundation/Foundation.h>
#import "RSColorPickerView.h"
@interface RSBrightnessSlider : UISlider
@property (nonatomic) RSColorPickerView *colorPicker;
@end
//
// RSBrightnessSlider.m
// RSColorPicker
//
// Created by Ryan Sullivan on 8/12/11.
//
#import "RSBrightnessSlider.h"
#import "CGContextCreator.h"
/**
* Returns Image with hourglass looking slider that looks something like:
*
* 6 ______ 5
* \ /
* 7 \ / 4
* ->||<--- cWidth (Center Width)
* ||
* 8 / \ 3
* / \
* 1 ------ 2
*/
UIImage * RSHourGlassThumbImage(CGSize size, CGFloat cWidth){
//Set Size
CGFloat width = size.width;
CGFloat height = size.height;
//Setup Context
CGContextRef ctx = [CGContextCreator newARGBBitmapContextWithSize:size];
//Set Colors
CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
//Draw Slider, See Diagram above for point numbers
CGFloat yDist83 = sqrtf(3)/2*width;
CGFloat yDist74 = height - yDist83;
CGPoint addLines[] = {
CGPointMake(0, -1), //Point 1
CGPointMake(width, -1), //Point 2
CGPointMake(width/2+cWidth/2, yDist83), //Point 3
CGPointMake(width/2+cWidth/2, yDist74), //Point 4
CGPointMake(width, height+1), //Point 5
CGPointMake(0, height+1), //Point 6
CGPointMake(width/2-cWidth/2, yDist74), //Point 7
CGPointMake(width/2-cWidth/2, yDist83) //Point 8
};
//Fill Path
CGContextAddLines(ctx, addLines, sizeof(addLines)/sizeof(addLines[0]));
CGContextFillPath(ctx);
//Stroke Path
CGContextAddLines(ctx, addLines, sizeof(addLines)/sizeof(addLines[0]));
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
return image;
}
/**
* Returns image that looks like a square arrow loop, something like:
*
* +-----+
* | +-+ | ------------------------
* | | | | |
* ->| |<--- loopSize.width loopSize.height
* | | | | |
* | +-+ | ------------------------
* +-----+
*/
UIImage * RSArrowLoopThumbImage(CGSize size, CGSize loopSize){
//Setup Rects
CGRect outsideRect = CGRectMake(0, 0, size.width, size.height);
CGRect insideRect;
insideRect.size = loopSize;
insideRect.origin.x = (size.width - loopSize.width)/2;
insideRect.origin.y = (size.height - loopSize.height)/2;
//Setup Context
CGContextRef ctx = [CGContextCreator newARGBBitmapContextWithSize:size];
//Set Colors
CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGMutablePathRef loopPath = CGPathCreateMutable();
CGPathAddRect(loopPath, nil, outsideRect);
CGPathAddRect(loopPath, nil, insideRect);
//Fill Path
CGContextAddPath(ctx, loopPath);
CGContextEOFillPath(ctx);
//Stroke Path
CGContextAddRect(ctx, insideRect);
CGContextStrokePath(ctx);
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
//Memory
CGPathRelease(loopPath);
CGContextRelease(ctx);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
return image;
}
@implementation RSBrightnessSlider
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initRoutine];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initRoutine];
}
return self;
}
- (void)initRoutine {
self.minimumValue = 0.0;
self.maximumValue = 1.0;
self.continuous = YES;
self.enabled = YES;
self.userInteractionEnabled = YES;
[self addTarget:self action:@selector(myValueChanged:) forControlEvents:UIControlEventValueChanged];
}
- (void)myValueChanged:(id)notif {
[_colorPicker setBrightness:self.value];
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef space = CGColorSpaceCreateDeviceGray();
NSArray *colors = @[(id)[UIColor colorWithWhite:0 alpha:1].CGColor,
(id)[UIColor colorWithWhite:1 alpha:1].CGColor];
CGGradientRef myGradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, NULL);
CGContextDrawLinearGradient(ctx, myGradient, CGPointZero, CGPointMake(rect.size.width, 0), 0);
CGGradientRelease(myGradient);
CGColorSpaceRelease(space);
}
- (void)setColorPicker:(RSColorPickerView*)cp {
_colorPicker = cp;
if (!_colorPicker) { return; }
self.value = [_colorPicker brightness];
}
@end
//
// RSColorFunctions.h
// RSColorPicker
//
// Created by Ryan Sullivan on 3/12/13.
//
#import <UIKit/UIKit.h>
#import "ANImageBitmapRep.h"
BMPixel RSPixelFromHSV(CGFloat H, CGFloat S, CGFloat V);
void RSHSVFromPixel(BMPixel pixel, CGFloat *h, CGFloat *s, CGFloat *v);
// four floats will be placed into `components`
void RSGetComponentsForColor(CGFloat *components, UIColor *color);
UIImage * RSUIImageWithScale(UIImage *img, CGFloat scale);
UIImage * RSOpacityBackgroundImage(CGFloat length, CGFloat scale, UIColor *color);
UIColor * RSRandomColorOpaque(BOOL isOpaque);
//
// RSColorFunctions.m
// RSColorPicker
//
// Created by Ryan Sullivan on 3/12/13.
//
#import "RSColorFunctions.h"
BMPixel RSPixelFromHSV(CGFloat H, CGFloat S, CGFloat V)
{
if (S == 0) {
return BMPixelMake(V, V, V, 1.0);
}
if (H == 1) {
H = 0;
}
CGFloat var_h = H * 6.0;
// Verified `H` is never <0 so (int) is OK:
int var_i = (int)var_h;
CGFloat var_1 = V * (1.0 - S);
if (var_i == 0) {
CGFloat var_3 = V * (1.0 - S * (1.0 - (var_h - var_i)));
return BMPixelMake(V, var_3, var_1, 1.0);
} else if (var_i == 1) {
CGFloat var_2 = V * (1.0 - S * (var_h - var_i));
return BMPixelMake(var_2, V, var_1, 1.0);
} else if (var_i == 2) {
CGFloat var_3 = V * (1.0 - S * (1.0 - (var_h - var_i)));
return BMPixelMake(var_1, V, var_3, 1.0);
} else if (var_i == 3) {
CGFloat var_2 = V * (1.0 - S * (var_h - var_i));
return BMPixelMake(var_1, var_2, V, 1.0);
} else if (var_i == 4) {
CGFloat var_3 = V * (1.0 - S * (1.0 - (var_h - var_i)));
return BMPixelMake(var_3, var_1, V, 1.0);
}
CGFloat var_2 = V * (1.0 - S * (var_h - var_i));
return BMPixelMake(V, var_1, var_2, 1.0);
}
void RSHSVFromPixel(BMPixel pixel, CGFloat *h, CGFloat *s, CGFloat *v)
{
UIColor *color = [UIColor colorWithRed:pixel.red green:pixel.green blue:pixel.blue alpha:1];
[color getHue:h saturation:s brightness:v alpha:NULL];
}
void RSGetComponentsForColor(CGFloat *components, UIColor *color)
{
// First try to get the components the right way
if ([color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]) {
return;
} else if ([color getWhite:&components[0] alpha:&components[3]]) {
components[1] = components[0];
components[2] = components[0];
return;
}
// *Then* resort to this good old hack.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char resultingPixel[4] = {0};
CGContextRef context = CGBitmapContextCreate(&resultingPixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextRelease(context);
CGColorSpaceRelease(rgbColorSpace);
for (int component = 0; component < 4; component++) {
components[component] = resultingPixel[component] / 255.0;
}
if (components[3] > 0)
{
components[0] /= components[3];
components[1] /= components[3];
components[2] /= components[3];
}
}
UIImage * RSUIImageWithScale(UIImage *img, CGFloat scale)
{
return [UIImage imageWithCGImage:img.CGImage scale:scale orientation:UIImageOrientationUp];
}
/**
* Returns image that looks like a checkered background.
*/
UIImage * RSOpacityBackgroundImage(CGFloat length, CGFloat scale, UIColor *color) {
NSCAssert(scale > 0, @"Tried to create opacity background image with scale 0");
NSCAssert(length > 0, @"Tried to create opacity background image with length 0");
UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, length*0.5, length*0.5)];
UIBezierPath *rectangle2Path = [UIBezierPath bezierPathWithRect:CGRectMake(length*0.5, length*0.5, length*0.5, length*0.5)];
UIBezierPath *rectangle3Path = [UIBezierPath bezierPathWithRect:CGRectMake(0, length*0.5, length*0.5, length*0.5)];
UIBezierPath *rectangle4Path = [UIBezierPath bezierPathWithRect:CGRectMake(length*0.5, 0, length*0.5, length*0.5)];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(length, length), NO, scale);
[color setFill];
[rectanglePath fill];
[rectangle2Path fill];
[[UIColor whiteColor] setFill];
[rectangle3Path fill];
[rectangle4Path fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return RSUIImageWithScale(image, scale);
}
UIColor * RSRandomColorOpaque(BOOL isOpaque) {
/*
From https://gist.github.com/kylefox/1689973
***
Distributed under The MIT License:
http://opensource.org/licenses/mit-license.php
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Alpha modifications for RSColorPicker test project
*/
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
CGFloat alpha = 1;
if (!isOpaque) {
alpha = ( arc4random() % 128 / 256.0 ) + 0.5;
}
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
}
//
// RSColorPickerState.h
// RSColorPicker
//
// Created by Alex Nichol on 12/16/13.
//
#import <Foundation/Foundation.h>
#import "RSColorFunctions.h"
/**
* Represents the state of a color picker. This includes
* the position on the color picker (for a square picker) that
* is selected.
*
* Terms used:
* "size" - the diameter of the color picker
* "padding" - the amount of pixels on each side of the color picker
* reserved for padding
*/
@interface RSColorPickerState : NSObject {
CGPoint scaledRelativePoint; // H & S
CGFloat brightness; // V
CGFloat alpha; // A
}
@property (readonly) CGFloat hue, saturation, brightness, alpha;
/**
* Creates a state with a 1.0 alpha and 1.0 brightness that would arise
* by selecting `point` on a color picker of diameter `size` and padding `padding`.
*/
+ (RSColorPickerState *)stateForPoint:(CGPoint)point size:(CGFloat)size padding:(CGFloat)padding;
/**
* Create a state with a given color.
*/
- (id)initWithColor:(UIColor *)selectionColor;
/**
* Create a state given a point on the unit circle and brightness+alpha
*/
- (id)initWithScaledRelativePoint:(CGPoint)p brightness:(CGFloat)V alpha:(CGFloat)A;
/**
* Create a state given HSVA components.
*/
- (id)initWithHue:(CGFloat)H saturation:(CGFloat)S brightness:(CGFloat)V alpha:(CGFloat)A;
- (UIColor *)color;
/**
* Returns the position of this state on a color picker of size `size` and padding `padding`.
* Note: this point may be outside of the unit circle if a point outside the unit circle
* was picked to generate this state.
*/
- (CGPoint)selectionLocationWithSize:(CGFloat)size padding:(CGFloat)padding;
// This class is immutable, so these are helpful!
- (RSColorPickerState *)stateBySettingBrightness:(CGFloat)newBright;
- (RSColorPickerState *)stateBySettingAlpha:(CGFloat)newAlpha;
- (RSColorPickerState *)stateBySettingHue:(CGFloat)newHue;
- (RSColorPickerState *)stateBySettingSaturation:(CGFloat)newSaturation;
@end
//
// RSColorPickerState.m
// RSColorPicker
//
// Created by Alex Nichol on 12/16/13.
//
#import "RSColorPickerState.h"
static CGFloat _calculateHue(CGPoint point);
static CGFloat _calculateSaturation(CGPoint point);
static CGPoint _calculatePoint(CGFloat hue, CGFloat saturation);
@implementation RSColorPickerState
@synthesize brightness, alpha;
- (CGFloat)hue {
return _calculateHue(scaledRelativePoint);
}
- (CGFloat)saturation {
return _calculateSaturation(scaledRelativePoint);
}
- (UIColor *)color {
return [UIColor colorWithHue:self.hue saturation:self.saturation brightness:brightness alpha:alpha];
}
+ (RSColorPickerState *)stateForPoint:(CGPoint)point size:(CGFloat)size padding:(CGFloat)padding {
// calculate everything we need to know
CGPoint relativePoint = CGPointMake(point.x - (size / 2.0), (size / 2.0) - point.y);
CGPoint scaledRelativePoint = relativePoint;
scaledRelativePoint.x /= (size / 2.0) - padding;
scaledRelativePoint.y /= (size / 2.0) - padding;
return [[RSColorPickerState alloc] initWithScaledRelativePoint:scaledRelativePoint
brightness:1 alpha:1];
}
- (id)initWithColor:(UIColor *)_selectionColor {
if ((self = [super init])) {
CGFloat rgba[4];
RSGetComponentsForColor(rgba, _selectionColor);
UIColor * selectionColor = [UIColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]];
CGFloat hue, saturation;
[selectionColor getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
scaledRelativePoint = _calculatePoint(hue, saturation);
}
return self;
}
- (id)initWithScaledRelativePoint:(CGPoint)p brightness:(CGFloat)V alpha:(CGFloat)A {
if ((self = [super init])) {
scaledRelativePoint = p;
brightness = V;
alpha = A;
}
return self;
}
- (id)initWithHue:(CGFloat)H saturation:(CGFloat)S brightness:(CGFloat)V alpha:(CGFloat)A {
if ((self = [super init])) {
scaledRelativePoint = _calculatePoint(H, S);
brightness = V;
alpha = A;
}
return self;
}
- (CGPoint)selectionLocationWithSize:(CGFloat)size padding:(CGFloat)padding {
CGPoint unscaled = scaledRelativePoint;
unscaled.x *= (size / 2.0) - padding;
unscaled.y *= (size / 2.0) - padding;
return CGPointMake(unscaled.x + (size / 2.0), (size / 2.0) - unscaled.y);
}
#pragma mark - Modification
- (RSColorPickerState *)stateBySettingBrightness:(CGFloat)newBright {
return [[RSColorPickerState alloc] initWithScaledRelativePoint:scaledRelativePoint brightness:newBright alpha:alpha];
}
- (RSColorPickerState *)stateBySettingAlpha:(CGFloat)newAlpha {
return [[RSColorPickerState alloc] initWithScaledRelativePoint:scaledRelativePoint brightness:brightness alpha:newAlpha];
}
- (RSColorPickerState *)stateBySettingHue:(CGFloat)newHue {
CGPoint newPoint = _calculatePoint(newHue, self.saturation);
return [[RSColorPickerState alloc] initWithScaledRelativePoint:newPoint brightness:brightness alpha:alpha];
}
- (RSColorPickerState *)stateBySettingSaturation:(CGFloat)newSaturation {
CGPoint newPoint = _calculatePoint(self.hue, newSaturation);
return [[RSColorPickerState alloc] initWithScaledRelativePoint:newPoint brightness:brightness alpha:alpha];
}
#pragma mark - Debugging
- (NSString *)description {
NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: %p { ", NSStringFromClass([self class]), self];
[description appendFormat:@"scaledPoint:%@ ", NSStringFromCGPoint(scaledRelativePoint)];
[description appendFormat:@"brightness:%f ", brightness];
[description appendFormat:@"alpha:%f", alpha];
[description appendString:@"} >"];
return description;
}
@end
#pragma mark - Helper Functions
static CGFloat _calculateHue(CGPoint point) {
double angle = atan2(point.y, point.x);
if (angle < 0) angle += M_PI * 2;
return angle / (M_PI * 2);
}
static CGFloat _calculateSaturation(CGPoint point) {
CGFloat radius = sqrt(pow(point.x, 2) + pow(point.y, 2));
if (radius > 1) {
radius = 1;
}
return radius;
}
static CGPoint _calculatePoint(CGFloat hue, CGFloat saturation) {
// convert to HSV
CGFloat angle = hue * (2.0 * M_PI);
CGFloat pointX = cos(angle) * saturation;
CGFloat pointY = sin(angle) * saturation;
return CGPointMake(pointX, pointY);
}
//
// RSColorPickerView.h
// RSColorPicker
//
// Created by Ryan Sullivan on 8/12/11.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <Accelerate/Accelerate.h>
@class RSColorPickerView, BGRSLoupeLayer;
@protocol RSColorPickerViewDelegate <NSObject>
/**
* Called everytime the color picker's selection/color is changed.
* Don't do expensive operations here as it will slow down your app.
*/
- (void)colorPickerDidChangeSelection:(RSColorPickerView *)colorPicker;
@optional
- (void)colorPicker:(RSColorPickerView *)colorPicker touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)colorPicker:(RSColorPickerView *)colorPicker touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
@end
IB_DESIGNABLE
@interface RSColorPickerView : UIView
/**
* Specifies if the color picker should be drawn as a circle, or as a square.
*/
@property (nonatomic) IBInspectable BOOL cropToCircle;
/**
* The brightness of the current selection
*/
@property (nonatomic) CGFloat brightness;
/**
* The opacity of the current selection.
*/
@property (nonatomic) CGFloat opacity;
/**
* The selection color.
* This setter may modify `brightness` and `opacity` as necessary.
*/
@property (nonatomic) UIColor * selectionColor;
/**
* The delegate
*/
@property (nonatomic, weak) id <RSColorPickerViewDelegate> delegate;
/**
* The current point (in the color picker's bounds) of the selected color.
*/
@property (readwrite) CGPoint selection;
/**
* The distance around the edges of the color picker that is drawn for padding.
* Colors are cut-off before this distance so that the user can pick all colors.
*/
@property (readonly) CGFloat paddingDistance;
/**
* Specifies if the loupe should be drawn or not.
* Default: YES (show).
*/
@property (nonatomic) BOOL showLoupe;
/**
* The color at a given point in the color picker's bounds.
*/
- (UIColor *)colorAtPoint:(CGPoint)point;
/**
* Methods that create/cache data needed to create a color picker.
* These run async (except where noted) and can help the overall UX.
*/
+ (void)prepareForDiameter:(CGFloat)diameter;
+ (void)prepareForDiameter:(CGFloat)diameter padding:(CGFloat)padding;
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale;
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale padding:(CGFloat)padding;
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale padding:(CGFloat)padding inBackground:(BOOL)bg;
@end
//
// RSColorPickerView.m
// RSColorPicker
//
// Created by Ryan Sullivan on 8/12/11.
//
#import "ANImageBitmapRep.h"
#import "BGRSLoupeLayer.h"
#import "RSColorFunctions.h"
#import "RSColorPickerState.h"
#import "RSColorPickerView.h"
#import "RSGenerateOperation.h"
#import "RSSelectionLayer.h"
#define kSelectionViewSize 22
@interface RSColorPickerView () {
struct {
unsigned int bitmapNeedsUpdate:1;
} _colorPickerViewFlags;
RSColorPickerState * state;
}
@property (nonatomic) ANImageBitmapRep *rep;
/**
* A path which represents the shape of the color picker palette,
* padded by 1/2 the selectionViews's size.
*/
@property (nonatomic) UIBezierPath *activeAreaShape;
/**
* The layer which contains just the currently selected color
* within the -selectionLayer.
*/
@property (nonatomic) CALayer *selectionColorLayer;
/**
* Layer which shows the circular selection "target".
*/
@property (nonatomic) RSSelectionLayer *selectionLayer;
/**
* The layer which will ultimately contain the generated
* palette image.
*/
@property (nonatomic) CALayer *gradientLayer;
/**
* A black layer. As the brightness is lowered, the opacity
* of brightnessLayer is increased and thus this view becomes more
* visible.
*/
@property (nonatomic) CALayer *brightnessLayer;
/**
* A checkerboard pattern indicating opacity.
* As opacity is lowered, the alpha of this view becomes
* closer to 1.
*/
@property (nonatomic) CALayer *opacityLayer;
/**
* Layer that will contain the gradientLayer, brightnessLayer,
* opacityLayer.
*/
@property (nonatomic) CALayer *contentsLayer;
@property (nonatomic) BGRSLoupeLayer *loupeLayer;
/**
* Gets updated to the scale of the current UIWindow.
*/
@property (nonatomic) CGFloat scale;
- (void)initRoutine;
- (void)resizeOrRescale;
// Called to generate the _rep ivar and set it.
- (void)genBitmap;
// Called to generate the bezier paths
- (void)generateBezierPaths;
// Called to update the UI for the current state.
- (void)handleStateChanged;
// Called to handle a state change (optionally disabling CA Actions for loupe).
- (void)handleStateChangedDisableActions:(BOOL)disable;
// touch handling
- (CGPoint)validPointForTouch:(CGPoint)touchPoint;
- (RSColorPickerState *)stateForPoint:(CGPoint)point;
- (void)updateStateForTouchPoint:(CGPoint)point;
// metrics
- (CGFloat)paletteDiameter;
@end
@implementation RSColorPickerView
#pragma mark - Object Lifecycle -
- (id)initWithFrame:(CGRect)frame {
CGFloat square = fmin(frame.size.height, frame.size.width);
frame.size = CGSizeMake(square, square);
self = [super initWithFrame:frame];
if (self) {
[self initRoutine];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initRoutine];
}
return self;
}
- (void)initRoutine {
// Show or hide the loupe. Default: show.
self.showLoupe = YES;
self.opaque = YES;
self.backgroundColor = [UIColor clearColor];
_colorPickerViewFlags.bitmapNeedsUpdate = NO;
// the view used to select the colour
self.selectionLayer = [RSSelectionLayer layer];
self.selectionLayer.frame = CGRectMake(0.0, 0.0, kSelectionViewSize, kSelectionViewSize);
[self.selectionLayer setNeedsDisplay];
self.selectionColorLayer = [CALayer layer];
self.selectionColorLayer.cornerRadius = kSelectionViewSize / 2;
self.selectionColorLayer.frame = CGRectMake(0.0, 0.0, kSelectionViewSize, kSelectionViewSize);
self.brightnessLayer = [CALayer layer];
self.brightnessLayer.frame = self.bounds;
//原来的
// self.brightnessLayer.backgroundColor = [UIColor blackColor].CGColor;
//修改后的
self.brightnessLayer.backgroundColor = [UIColor colorWithWhite:.5f alpha:1.f].CGColor;
self.gradientLayer = [CALayer layer];
self.gradientLayer.frame = self.bounds;
self.opacityLayer = [CALayer layer];
self.contentsLayer = [CALayer layer];
self.contentsLayer.frame = self.bounds;
[self.contentsLayer addSublayer:self.gradientLayer];
[self.contentsLayer addSublayer:self.brightnessLayer];
[self.contentsLayer addSublayer:self.selectionColorLayer];
[self.contentsLayer addSublayer:self.opacityLayer];
[self.contentsLayer addSublayer:self.selectionLayer];
[self.layer addSublayer:self.contentsLayer];
[self handleStateChangedDisableActions:NO];
self.contentsLayer.masksToBounds = YES;
self.cropToCircle = NO;
self.selectionColor = [UIColor whiteColor];
}
- (void)resizeOrRescale {
if (!self.window || self.frame.size.width == 0 || self.frame.size.height == 0) {
self.scale = 0;
[self.loupeLayer disappearAnimated:NO];
return;
}
self.scale = self.window.screen.scale;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsScale = self.scale;
self.selectionLayer.contentsScale = self.scale;
self.selectionColorLayer.contentsScale = self.scale;
self.brightnessLayer.contentsScale = self.scale;
self.gradientLayer.contentsScale = self.scale;
self.opacityLayer.contentsScale = 1.0;//self.scale;
self.loupeLayer.contentsScale = self.scale;
self.contentsLayer.contentsScale = self.scale;
_colorPickerViewFlags.bitmapNeedsUpdate = YES;
self.contentsLayer.frame = self.bounds;
self.gradientLayer.frame = self.bounds;
self.brightnessLayer.frame = self.bounds;
self.opacityLayer.frame = self.bounds;
self.opacityLayer.backgroundColor = [[UIColor colorWithPatternImage:RSOpacityBackgroundImage(20, self.scale, [UIColor colorWithWhite:0.5 alpha:1.0])] CGColor];
[self genBitmap];
[self generateBezierPaths];
[self handleStateChanged];
[CATransaction commit];
}
- (void)didMoveToWindow {
[self resizeOrRescale];
}
- (void)setFrame:(CGRect)frame {
NSAssert(frame.size.width == frame.size.height, @"RSColorPickerView must be square.");
[super setFrame:frame];
[self resizeOrRescale];
}
#pragma mark - Business -
- (void)genBitmap {
if (!_colorPickerViewFlags.bitmapNeedsUpdate) return;
self.rep = [self.class bitmapForDiameter:self.gradientLayer.bounds.size.width scale:self.scale padding:self.paddingDistance shouldCache:YES];
_colorPickerViewFlags.bitmapNeedsUpdate = NO;
self.gradientLayer.contents = (id)[RSUIImageWithScale(self.rep.image, self.scale) CGImage];
}
- (void)generateBezierPaths {
[CATransaction begin];
[CATransaction setDisableActions:YES];
CGRect activeAreaFrame = CGRectInset(self.bounds, self.paddingDistance, self.paddingDistance);
if (self.cropToCircle) {
self.contentsLayer.cornerRadius = self.paletteDiameter / 2.0;
self.activeAreaShape = [UIBezierPath bezierPathWithOvalInRect:activeAreaFrame];
} else {
self.contentsLayer.cornerRadius = 0.0;
self.activeAreaShape = [UIBezierPath bezierPathWithRect:activeAreaFrame];
}
[CATransaction commit];
}
#pragma mark - Getters -
- (UIColor *)colorAtPoint:(CGPoint)point {
return [self stateForPoint:point].color;
}
- (CGFloat)brightness {
return state.brightness;
}
- (CGFloat)opacity {
return state.alpha;
}
- (UIColor *)selectionColor {
return state.color;
}
- (CGPoint)selection {
return [state selectionLocationWithSize:self.paletteDiameter padding:self.paddingDistance];
}
#pragma mark - Setters -
- (void)setSelection:(CGPoint)selection {
[self updateStateForTouchPoint:selection];
}
- (void)setBrightness:(CGFloat)bright {
state = [state stateBySettingBrightness:bright];
[self handleStateChanged];
}
- (void)setOpacity:(CGFloat)opacity {
state = [state stateBySettingAlpha:opacity];
[self handleStateChanged];
}
- (void)setCropToCircle:(BOOL)circle {
_cropToCircle = circle;
[self generateBezierPaths];
if (circle) {
// there's a chance the selection was outside the bounds
CGPoint point = [self validPointForTouch:[state selectionLocationWithSize:self.paletteDiameter
padding:self.paddingDistance]];
[self updateStateForTouchPoint:point];
} else {
[self handleStateChanged];
}
}
- (void)setSelectionColor:(UIColor *)selectionColor {
state = [[RSColorPickerState alloc] initWithColor:selectionColor];
[self handleStateChanged];
}
#pragma mark - Selection Updates -
- (void)handleStateChanged {
[self handleStateChangedDisableActions:YES];
}
- (void)handleStateChangedDisableActions:(BOOL)disable {
[CATransaction begin];
[CATransaction setDisableActions: disable];
// update positions
CGPoint selectionLocation = [state selectionLocationWithSize:self.paletteDiameter padding:self.paddingDistance];
self.selectionLayer.position = selectionLocation;
self.selectionColorLayer.position = selectionLocation;
//放大镜的位置
// LogRed(@"%@", NSStringFromCGPoint(selectionLocation));
CGPoint loupeLocation = selectionLocation;
//处于中间位置
if (selectionLocation.x >= 45.f && selectionLocation.x <= 105.f) {
//正上方
if (selectionLocation.y <= 60.f) {
//x不变
loupeLocation.y = selectionLocation.y + 100.f;
}
//正中央偏上
if (selectionLocation.y < 75.f && selectionLocation.y > 60.f) {
//x不变
loupeLocation.y = selectionLocation.y + 80.f;
}
//正中央偏下
if (selectionLocation.y > 75.f && selectionLocation.y < 120.f) {
//x不变
loupeLocation.y = selectionLocation.y - 20.f;
}
//正下方
if (selectionLocation.y >= 100.f) {
//x不变
loupeLocation.y = selectionLocation.y - 40.f;
}
}
//左侧部分
if (selectionLocation.x < 45.f) {
//左上方
if (selectionLocation.y <= 75.f) {
loupeLocation.x = selectionLocation.x + 70.f;
loupeLocation.y = selectionLocation.y + 80.f;
}
//左下方
if (selectionLocation.y >= 75.f) {
loupeLocation.x = selectionLocation.x + 70.f;
loupeLocation.y = selectionLocation.y - 25.f;
}
}
//右侧部分
if (selectionLocation.x > 105.f) {
//右上方
if (selectionLocation.y <= 75.f) {
loupeLocation.x = selectionLocation.x - 70.f;
loupeLocation.y = selectionLocation.y + 80.f;
}
//右下方
if (selectionLocation.y >= 75.f) {
loupeLocation.x = selectionLocation.x - 70.f;
loupeLocation.y = selectionLocation.y - 25.f;
}
}
self.loupeLayer.position = loupeLocation;
// Make loupeLayer sharp on screen
CGRect loupeFrame = self.loupeLayer.frame;
loupeFrame.origin = CGPointMake(round(loupeFrame.origin.x), round(loupeFrame.origin.y));
self.loupeLayer.frame = loupeFrame;
[self.loupeLayer setNeedsDisplay];
// set colors and opacities
self.selectionColorLayer.backgroundColor = [[self selectionColor] CGColor];
self.opacityLayer.opacity = 1 - self.opacity;
self.brightnessLayer.opacity = 1 - self.brightness;
[CATransaction commit];
// notify delegate
if ([self.delegate respondsToSelector:@selector(colorPickerDidChangeSelection:)]) {
[self.delegate colorPickerDidChangeSelection:self];
}
}
- (void)updateStateForTouchPoint:(CGPoint)point {
state = [self stateForPoint:[self validPointForTouch:point]];
[self handleStateChanged];
}
#pragma mark - Metrics -
- (CGFloat)paddingDistance {
return kSelectionViewSize / 2.0;
}
- (CGFloat)paletteDiameter {
return self.bounds.size.width;
}
#pragma mark - Touch Events -
- (CGPoint)validPointForTouch:(CGPoint)touchPoint {
if ([self.activeAreaShape containsPoint:touchPoint]) {
return touchPoint;
}
if (self.cropToCircle) {
// We compute the right point on the gradient border
CGPoint returnedPoint;
// TouchCircle is the circle which pass by the point 'touchPoint', of radius 'r'
// 'X' is the x coordinate of the touch in TouchCircle
CGFloat X = touchPoint.x - CGRectGetMidX(self.bounds);
// 'Y' is the y coordinate of the touch in TouchCircle
CGFloat Y = touchPoint.y - CGRectGetMidY(self.bounds);
CGFloat r = sqrt(pow(X, 2) + pow(Y, 2));
// alpha is the angle in radian of the touch on the unit circle
CGFloat alpha = acos( X / r );
if (touchPoint.y > CGRectGetMidX(self.bounds)) alpha = (2 * M_PI) - alpha;
// 'actual radius' is the distance between the center and the border of the gradient
CGFloat actualRadius = (self.paletteDiameter / 2.0) - self.paddingDistance;
returnedPoint.x = fabs(actualRadius) * cos(alpha);
returnedPoint.y = fabs(actualRadius) * sin(alpha);
// we offset the center of the circle, to get the coordinate from the right top left origin
returnedPoint.x = returnedPoint.x + CGRectGetMidX(self.bounds);
returnedPoint.y = CGRectGetMidY(self.bounds) - returnedPoint.y;
return returnedPoint;
} else {
CGPoint point = touchPoint;
if (point.x < self.paddingDistance) point.x = self.paddingDistance;
if (point.x > self.paletteDiameter - self.paddingDistance) {
point.x = self.paletteDiameter - self.paddingDistance;
}
if (point.y < self.paddingDistance) point.y = self.paddingDistance;
if (point.y > self.paletteDiameter - self.paddingDistance) {
point.y = self.paletteDiameter - self.paddingDistance;
}
return point;
}
}
- (RSColorPickerState *)stateForPoint:(CGPoint)point {
RSColorPickerState * newState = [RSColorPickerState stateForPoint:point
size:self.paletteDiameter
padding:self.paddingDistance];
newState = [[newState stateBySettingAlpha:self.opacity] stateBySettingBrightness:self.brightness];
return newState;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.showLoupe) {
// Lazily load loupeLayer, if user wants to display it.
if (!self.loupeLayer) {
self.loupeLayer = [BGRSLoupeLayer layer];
self.loupeLayer.contentsScale = self.scale;
}
[self.loupeLayer appearInColorPicker:self];
} else {
// Otherwise, byebye
[self.loupeLayer disappear];
}
CGPoint point = [touches.anyObject locationInView:self];
[self updateStateForTouchPoint:point];
if ([self.delegate respondsToSelector:@selector(colorPicker:touchesBegan:withEvent:)]) {
[self.delegate colorPicker:self touchesBegan:touches withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self];
[self updateStateForTouchPoint:point];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self];
[self updateStateForTouchPoint:point];
[self.loupeLayer disappear];
if ([self.delegate respondsToSelector:@selector(colorPicker:touchesEnded:withEvent:)]) {
[self.delegate colorPicker:self touchesEnded:touches withEvent:event];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.loupeLayer disappear];
}
#pragma mark - Class Methods -
static NSCache *generatedBitmaps;
static NSOperationQueue *generateQueue;
static dispatch_queue_t backgroundQueue;
+ (void)initialize {
generatedBitmaps = [NSCache new];
generateQueue = [NSOperationQueue new];
generateQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
backgroundQueue = dispatch_queue_create("com.github.rsully.rscolorpicker.background", DISPATCH_QUEUE_SERIAL);
}
#pragma mark Background Methods
+ (void)prepareForDiameter:(CGFloat)diameter {
[self prepareForDiameter:diameter padding:kSelectionViewSize/2.0];
}
+ (void)prepareForDiameter:(CGFloat)diameter padding:(CGFloat)padding {
[self prepareForDiameter:diameter scale:1.0 padding:padding];
}
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale {
[self prepareForDiameter:diameter scale:scale padding:kSelectionViewSize / 2.0];
}
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale padding:(CGFloat)padding {
[self prepareForDiameter:diameter scale:scale padding:padding inBackground:YES];
}
#pragma mark Prep Method
+ (void)prepareForDiameter:(CGFloat)diameter scale:(CGFloat)scale padding:(CGFloat)padding inBackground:(BOOL)bg {
void (*function)(dispatch_queue_t, dispatch_block_t) = bg ? dispatch_async : dispatch_sync;
function(backgroundQueue, ^{
[self bitmapForDiameter:diameter scale:scale padding:padding shouldCache:YES];
});
}
#pragma mark Generate Helper Method
+ (ANImageBitmapRep *)bitmapForDiameter:(CGFloat)diameter scale:(CGFloat)scale padding:(CGFloat)paddingDistance shouldCache:(BOOL)cache {
RSGenerateOperation *repOp = nil;
// Handle the scale here so the operation can just work with pixels directly
paddingDistance *= scale;
diameter *= scale;
if (diameter <= 0) return nil;
// Unique key for this size combo
NSString *dictionaryCacheKey = [NSString stringWithFormat:@"%.1f-%.1f", diameter, paddingDistance];
// Check cache
repOp = [generatedBitmaps objectForKey:dictionaryCacheKey];
if (repOp) {
if (!repOp.isFinished) {
[repOp waitUntilFinished];
}
return repOp.bitmap;
}
repOp = [[RSGenerateOperation alloc] initWithDiameter:diameter andPadding:paddingDistance];
if (cache) {
[generatedBitmaps setObject:repOp forKey:dictionaryCacheKey cost:diameter];
}
[generateQueue addOperation:repOp];
[repOp waitUntilFinished];
return repOp.bitmap;
}
@end
//
// GenerateOperation.h
// RSColorPicker
//
// Created by Ryan on 7/22/13.
//
#import <Foundation/Foundation.h>
#import <Accelerate/Accelerate.h>
@class ANImageBitmapRep;
@interface RSGenerateOperation : NSOperation
-(id)initWithDiameter:(CGFloat)diameter andPadding:(CGFloat)padding;
@property (readonly) CGFloat diameter;
@property (readonly) CGFloat padding;
@property ANImageBitmapRep *bitmap;
@end
//
// GenerateOperation.m
// RSColorPicker
//
// Created by Ryan on 7/22/13.
//
#import "RSGenerateOperation.h"
#import "ANImageBitmapRep.h"
#import "RSColorFunctions.h"
@implementation RSGenerateOperation
- (id)initWithDiameter:(CGFloat)diameter andPadding:(CGFloat)padding {
if ((self = [self init])) {
_diameter = diameter;
_padding = padding;
}
return self;
}
- (void)main {
BMPoint repSize = BMPointMake(_diameter, _diameter);
// Create fresh
ANImageBitmapRep *rep = [[ANImageBitmapRep alloc] initWithSize:repSize];
CGFloat radius = _diameter / 2.0;
CGFloat relRadius = radius - _padding;
CGFloat relX, relY;
int i, x, y;
int arrSize = powf(_diameter, 2);
size_t arrDataSize = sizeof(float) * arrSize;
// data
float *preComputeX = (float *)malloc(arrDataSize);
float *preComputeY = (float *)malloc(arrDataSize);
// output
float *atan2Vals = (float *)malloc(arrDataSize);
float *distVals = (float *)malloc(arrDataSize);
i = 0;
for (x = 0; x < _diameter; x++) {
relX = x - radius;
for (y = 0; y < _diameter; y++) {
relY = radius - y;
preComputeY[i] = relY;
preComputeX[i] = relX;
i++;
}
}
// Use Accelerate.framework to compute the distance and angle of every
// pixel from the center of the bitmap.
vvatan2f(atan2Vals, preComputeY, preComputeX, &arrSize);
vDSP_vdist(preComputeX, 1, preComputeY, 1, distVals, 1, arrSize);
// Compution done, free these
free(preComputeX);
free(preComputeY);
i = 0;
for (x = 0; x < _diameter; x++) {
for (y = 0; y < _diameter; y++) {
CGFloat r_distance = fmin(distVals[i], relRadius);
CGFloat angle = atan2Vals[i];
if (angle < 0.0) angle = (2.0 * M_PI) + angle;
CGFloat perc_angle = angle / (2.0 * M_PI);
BMPixel thisPixel = RSPixelFromHSV(perc_angle, r_distance/relRadius, 1); // full brightness
[rep setPixel:thisPixel atPoint:BMPointMake(x, y)];
i++;
}
}
// Bitmap generated, free these
free(atan2Vals);
free(distVals);
self.bitmap = rep;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return self.bitmap == nil;
}
- (BOOL)isFinished {
return !self.isExecuting;
}
@end
//
// RSOpacitySlider.h
// RSColorPicker
//
// Created by Jared Allen on 5/16/13.
// Copyright (c) 2013 Red Cactus LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RSColorPickerView.h"
@interface RSOpacitySlider : UISlider
@property (nonatomic) RSColorPickerView *colorPicker;
@end
//
// RSOpacitySlider.m
// RSColorPicker
//
// Created by Jared Allen on 5/16/13.
// Copyright (c) 2013 Red Cactus LLC. All rights reserved.
//
#import "RSOpacitySlider.h"
#import "RSColorFunctions.h"
@implementation RSOpacitySlider
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initRoutine];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initRoutine];
}
return self;
}
- (void)initRoutine {
self.minimumValue = 0.0;
self.maximumValue = 1.0;
self.continuous = YES;
self.enabled = YES;
self.userInteractionEnabled = YES;
[self addTarget:self action:@selector(myValueChanged:) forControlEvents:UIControlEventValueChanged];
}
- (void)didMoveToWindow {
if (!self.window) return;
UIImage *backgroundImage = RSOpacityBackgroundImage(16.f, self.window.screen.scale, [UIColor colorWithWhite:0.5 alpha:1.0]);
self.backgroundColor = [UIColor colorWithPatternImage:backgroundImage];
}
- (void)myValueChanged:(id)notif {
_colorPicker.opacity = self.value;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef space = CGColorSpaceCreateDeviceGray();
NSArray *colors = [[NSArray alloc] initWithObjects:
(id)[UIColor colorWithWhite:0 alpha:0].CGColor,
(id)[UIColor colorWithWhite:1 alpha:1].CGColor,nil];
CGGradientRef myGradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, NULL);
CGContextDrawLinearGradient(ctx, myGradient, CGPointZero, CGPointMake(rect.size.width, 0), 0);
CGGradientRelease(myGradient);
CGColorSpaceRelease(space);
}
- (void)setColorPicker:(RSColorPickerView *)cp {
_colorPicker = cp;
if (!_colorPicker) { return; }
self.value = [_colorPicker brightness];
}
@end
//
// RSSelectionView.h
// RSColorPicker
//
// Created by Ryan Sullivan on 3/12/13.
//
#import <UIKit/UIKit.h>
@interface RSSelectionLayer : CALayer
@end
//
// RSSelectionView.m
// RSColorPicker
//
// Created by Ryan Sullivan on 3/12/13.
//
#import "RSSelectionLayer.h"
@interface RSSelectionLayer ()
@property (nonatomic, strong) CGColorRef outerRingColor __attribute__((NSObject));
@property (nonatomic, strong) CGColorRef innerRingColor __attribute__((NSObject));
@end
@implementation RSSelectionLayer
- (void)drawInContext:(CGContextRef)ctx {
if (!self.outerRingColor || !self.innerRingColor) {
self.outerRingColor = [[UIColor colorWithWhite:1 alpha:0.4] CGColor];
self.innerRingColor = [[UIColor colorWithWhite:0 alpha:1] CGColor];
}
CGRect rect = self.bounds;
CGContextSetLineWidth(ctx, 3);
CGContextSetStrokeColorWithColor(ctx, self.outerRingColor);
CGContextStrokeEllipseInRect(ctx, CGRectInset(rect, 1.5, 1.5));
CGContextSetLineWidth(ctx, 2);
CGContextSetStrokeColorWithColor(ctx, self.innerRingColor);
CGContextStrokeEllipseInRect(ctx, CGRectInset(rect, 3, 3));
//新加的
// CGContextSetShouldAntialias(ctx, true);
}
@end
//
// ClientViewController.h
// GLPaint
//
// Created by 新界教育 on 16/4/5.
// Copyright © 2016年 roseonly. All rights reserved.
//
#import <UIKit/UIKit.h>
//导入网络框架
#import <AsyncNetwork/AsyncNetwork.h>
/** 需要遵循网络框架的协议*/
@interface ClientViewController : UIViewController <AsyncClientDelegate>
/** 客户端*/
@property (retain) AsyncClient *client; // client
/** bonjour发现服务类型*/
@property (retain) NSString *serviceType; // bonjour service type
@end
//
// ClientViewController.m
// GLPaint
//
// Created by 新界教育 on 16/4/5.
// Copyright © 2016年 roseonly. All rights reserved.
//
#import "ClientViewController.h"
//S
#import "RSColorPickerView.h"
#import "RSColorFunctions.h"
#import "PaintView.h"
#import "Masonry.h"
#import "RSBrightnessSlider.h"
#import "RSOpacitySlider.h"
#import "Line.h"
#import "RSColorPickerView.h"
#import "RSColorFunctions.h"
#import "PaintView.h"
//E
/** 宏定义*/
/** 左侧页边距*/
#define LEFT_MARGIN 20.f
/** 调色盘边长*/
#define PALLET_LENTH_OF_SIDE 150.f
@interface ClientViewController () <RSColorPickerViewDelegate>
/** 调色盘*/
@property (nonatomic, strong) RSColorPickerView *colorPicker;
/** 亮度*/
@property (nonatomic, strong) RSBrightnessSlider *brightnessSlider;
/** 不透明度*/
@property (nonatomic, strong) RSOpacitySlider *opacitySlider;
/** 线宽*/
@property (nonatomic, strong) UISlider *lineWidthSlider;
/** 调色面板视图*/
@property (nonatomic, strong) UIView *optionView;
/** 用于记录是否开启了调色面板*/
@property (nonatomic, assign, getter=isOpened) BOOL open;
/** 线段数组 ***/
@property (nonatomic, strong) NSMutableArray *lines;
/** 声明一个绘图面板属性*/
@property (nonatomic, strong) PaintView *paintView;
@end
@implementation ClientViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景色
self.view.backgroundColor = [UIColor lightGrayColor];
// 1. 配置选项视图
[self configOptionView];
// 2. 创建colorPiker
[self setupColorPicker];
// 3. 创建画板
[self setupPaintView];
// 4. 撤销
[self setupNavigationItem];
// 5. 添加手势
[self configGestureRecognizer];
// 6. 注册KVO
// 7. 配置网络服务
[self configAsyncNetwork];
}
- (void)configOptionView
{
self.optionView = [UIView new];
self.optionView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.optionView];
[self.optionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(TOP_LAYOUT_HEIGHT);
make.left.right.equalTo(self.view);
make.bottom.mas_equalTo(TOP_LAYOUT_HEIGHT+PALLET_LENTH_OF_SIDE);
}];
}
- (void)configGestureRecognizer
{
UILongPressGestureRecognizer *longPressGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPressGR.minimumPressDuration = .5f;
longPressGR.numberOfTouchesRequired = 2;
[_paintView addGestureRecognizer:longPressGR];
}
- (void)longPress:(UILongPressGestureRecognizer *)longPressGR
{
if (longPressGR.state == UIGestureRecognizerStateBegan) {
if (self.isOpened) {
[_paintView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view);
make.right.equalTo(self.view);
make.bottom.equalTo(self.view);
make.top.mas_equalTo(TOP_LAYOUT_HEIGHT);
}];
} else {
[_paintView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view);
make.right.equalTo(self.view);
make.bottom.equalTo(self.view).offset(PALLET_LENTH_OF_SIDE);
make.top.mas_equalTo(TOP_LAYOUT_HEIGHT+PALLET_LENTH_OF_SIDE);
}];
}
self.open = !self.open;
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
}
- (void)setupNavigationItem
{
// [[UIBarButtonItem appearance] setTintColor:[UIColor whiteColor]];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoMethod)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo target:self action:@selector(redoMethod)];
UIButton *titleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[titleBtn setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
[titleBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];
titleBtn.frame = CGRectMake(0, 0, 60, 44);
[titleBtn setTitle:@"保存到相册" forState:UIControlStateNormal];
[titleBtn addTarget:self action:@selector(saveImage) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.titleView = titleBtn;
}
#pragma mark - 保存图片到相册
// 监听点击
- (void)saveImage
{
// 获取照片
UIImage *paintImage = [_paintView paintImage];
// LogRed(@"%@", paintImage);
UIImageWriteToSavedPhotosAlbum(paintImage, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
}
- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
NSString *message = @"";
if (!error) {
message = @"成功保存到相册";
}else
{
message = [error description];
}
LogRed(@"message is %@",message);
}
#pragma mark - 监听undo
- (void)undoMethod
{
[_paintView undo];
}
- (void)redoMethod
{
[_paintView redo];
}
#pragma mark 配置调色盘colorPiker
- (void)setupColorPicker
{
self.colorPicker = [[RSColorPickerView alloc] initWithFrame:CGRectMake(0, 0, PALLET_LENTH_OF_SIDE, PALLET_LENTH_OF_SIDE)];
[self.colorPicker setSelectionColor:RSRandomColorOpaque(YES)];
[self.colorPicker setDelegate:self];
self.colorPicker.cropToCircle = YES;
[self.optionView addSubview:self.colorPicker];
//初始化亮度值
self.colorPicker.brightness = 1.f;
//初始化不透明度值
self.colorPicker.opacity = 1.f;
//设置初始化颜色为黑色,如果不设置该颜色为黑色,初始颜色为随机
//设置初始颜色为黑色后,亮度值将会改变为0
self.colorPicker.selectionColor = [UIColor blackColor];
// View that controls brightness
self.brightnessSlider = [[RSBrightnessSlider alloc] initWithFrame:CGRectMake(PALLET_LENTH_OF_SIDE + 20, 10, SCREEN_WIDTH - PALLET_LENTH_OF_SIDE - 40, 20)];
[self.brightnessSlider setColorPicker:self.colorPicker];
[self.optionView addSubview:self.brightnessSlider];
// View that controls opacity
self.opacitySlider = [[RSOpacitySlider alloc] initWithFrame:CGRectMake(PALLET_LENTH_OF_SIDE + 20, 10+20+20, SCREEN_WIDTH - PALLET_LENTH_OF_SIDE - 40, 20)];
[self.opacitySlider setColorPicker:self.colorPicker];
[self.optionView addSubview:self.opacitySlider];
self.lineWidthSlider = [[UISlider alloc] initWithFrame:CGRectMake(PALLET_LENTH_OF_SIDE + 20, 10+20+20+20+20, SCREEN_WIDTH - PALLET_LENTH_OF_SIDE - 40, 20)];
self.lineWidthSlider.maximumValue = 15.f;
self.lineWidthSlider.minimumValue = 1.f;
//初始化线宽值
self.lineWidthSlider.value = 4.f;
self.paintView.lineWidth = self.lineWidthSlider.value;
[self.optionView addSubview:self.lineWidthSlider];
[self.lineWidthSlider addTarget:self action:@selector(lineWidthChanged:) forControlEvents:UIControlEventValueChanged];
}
#pragma mark 线宽发生了改变
- (void)lineWidthChanged:(id)sender
{
if ([sender isKindOfClass:[UISlider class]]) {
self.lineWidthSlider = sender;
}
[self.paintView setLineWidth:self.lineWidthSlider.value];
}
#pragma mark 调色板上的颜色发生变化时触发的方法
- (void)colorPickerDidChangeSelection:(RSColorPickerView *)colorPicker
{
UIColor *color = [colorPicker selectionColor];
[self.paintView setPaintColor:color];
self.brightnessSlider.value = [colorPicker brightness];
self.opacitySlider.value = [colorPicker opacity];
}
#pragma mark 配置画板PaintView
- (void)setupPaintView
{
self.paintView = [PaintView paintView];
self.paintView.backgroundColor = [UIColor whiteColor];
// 设置线宽
self.paintView.lineWidth = self.lineWidthSlider.value;
[self.view addSubview:self.paintView];
// 约束
[self.paintView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view);
make.right.equalTo(self.view);
make.bottom.equalTo(self.view);
make.top.mas_equalTo(TOP_LAYOUT_HEIGHT);
}];
}
/////////////////////////////////////////////////////////////////////
///////////////////////////////网络部分///////////////////////////
/////////////////////////////////////////////////////////////////////
- (void)dealloc {
[self.client stop];
self.client.delegate = nil;
}
//配置网络框架
- (void)configAsyncNetwork
{
// 创建并配置客户端
self.client = [AsyncClient new];
self.client.serviceType = self.serviceType;
self.client.delegate = self;
// 开启客户端
[self.client start];
// 更新状态
[self updateStatus];
}
#pragma mark 私有方法-更新状态
- (void)updateStatus;
{
//显示连接的数量
LogRed(@"Conntected to %ld server(s)", self.client.connections.count);
}
//发送消息给全部连接的服务器
- (void)sendInput:(id)sender;
{
// ask the client to send the input string to all connected servers
[self.client sendObject:@"客户端发送的字符串,tag=1"];
// display log entry
NSString *string = [NSString stringWithFormat:@">> %@\n", @"测试字符串,tag=1"];
LogOrange(@"客户端发送%@", string);
//清除输入
// self.input.stringValue = @"";
}
#pragma mark - AsyncClientDelegate协议中的方法
- (void)client:(AsyncClient *)theClient didConnect:(AsyncConnection *)connection;
{
//显示已经连接到哪个服务器
NSString *string = [NSString stringWithFormat:@"[connected to %@]\n", connection.netService.name];
LogBlue(@"客户端发送%@", string);
// update the status
[self updateStatus];
}
- (void)client:(AsyncClient *)theClient didDisconnect:(AsyncConnection *)connection;
{
// display log entry
NSString *string = [NSString stringWithFormat:@"[disconnected from %@]\n", connection.netService.name];
LogBlue(@"客户端发送%@", string);
// update the status
[self updateStatus];
}
- (void)client:(AsyncClient *)theClient didReceiveCommand:(AsyncCommand)command object:(id)object connection:(AsyncConnection *)connection;
{
// display log entry
NSString *string = [NSString stringWithFormat:@"<< [%@] %@\n", connection.netService.name, object];
LogPurple(@"服务器端接收客户端%@", string);
}
- (void)client:(AsyncClient *)theClient didFailWithError:(NSError *)error;
{
//显示错误信息
LogRed(@"error] %@\n", error.localizedDescription);
}
@end
//
// ViewController.h
// GLPaint
//
// Created by jiaguanglei on 15/12/14.
// Copyright © 2015年 roseonly. All rights reserved.
//
#import <UIKit/UIKit.h>
//导入网络框架
#import <AsyncNetwork/AsyncNetwork.h>
/** 需要遵循网络框架的协议*/
@interface ServerViewController : UIViewController <AsyncServerDelegate>
/** 服务器端*/
@property (retain) AsyncServer *server; // the server
/** 服务器名称,用于bonjour发现*/
@property (retain) NSString *serviceName; // bonjour service name
/** bonjour发现服务类型*/
@property (retain) NSString *serviceType; // bonjour service type
/** 监听端口,0为自动设置*/
@property (assign) NSUInteger listenPort; // listening port (0: automatic)
@end
//
// AppDelegate.h
// GLPaint
//
// Created by jiaguanglei on 15/12/14.
// Copyright © 2015年 roseonly. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
//
// AppDelegate.m
// GLPaint
//
// Created by jiaguanglei on 15/12/14.
// Copyright © 2015年 roseonly. All rights reserved.
//
#import "AppDelegate.h"
//导入服务器端
#import "ServerViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//配置新服务器端
[self newServer:nil];
return YES;
}
//创建一个新的服务器端
- (void)newServer:(id)sender;
{
//服务器端ID(递增)
static NSUInteger serverId = 0;
static NSString *name = @"AsyncServer";
//创建并配置服务器端控制器
ServerViewController *serverViewController = [[ServerViewController alloc] init];
serverViewController.serviceType = @"_ClientServer._tcp";
serverViewController.serviceName = [NSString stringWithFormat:@"%@ %ld", name, ++serverId];
self.window = [[UIWindow alloc] initWithFrame:PP_SCREEN_RECT];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:serverViewController];
[self.window makeKeyAndVisible];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
//
// UIImage+Extention.h
// loveonly
//
// Created by jiaguanglei on 15/10/28.
// Copyright (c) 2015年 roseonly. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef void(^DownloadFinish)(void);
@interface UIImage (Extention)
/**
* 返回一张自由拉伸的图片
*/
+ (UIImage *)resizedImageWithName:(NSString *)name;
/**
* 压缩图片
*/
+(UIImage *)imageWithImageSimple:(UIImage *)image scaledToSize:(CGSize)newSize;
/*** *** *** *** *** *** 切图 *** *** *** *** *** *** *** *** *** ***/
#pragma mark - 切图
/**
* 屏幕截图
*
* @param view 需要截图的view
*
* @return 新的图片
*/
+ (UIImage *)captureWithView:(UIView *)view;
/**
* - 切圆形图
*
* @param borderW 边框宽度
* @param borderColor 边框颜色
* @param imageName 原始图片的名称
*
* @return 新的图片
*/
+ (UIImage *)clipARCImageWithBorderWidth:(CGFloat)borderW borderColor:(UIColor *)borderColor imageName:(NSString *)imageName;
/**
* 切方形图
**/
+ (UIImage *)clipRectImageWithBorderWidth:(CGFloat)borderW borderColor:(UIColor *)borderColor imageName:(NSString *)imageName;
/**
* 打水印
*
* @param bg 背景图片
* @param logo 水印
*
* @return 新的图片
*/
+ (UIImage *)blockImageWithBg:(NSString *)bg logo:(NSString *)logo;
// ************************* *************************
// ************************* TODO *******************************
// ************************* *************************
// 下载网络图片有默认图片
//- (void)downloadImageFromUrl:(NSString *)imageUrl placeHoder:(BOOL)placeHoder finish:(DownloadFinish)finish;
- (UIImage *)croppedImage:(CGRect)bounds;
- (UIImage *)thumbnailImage:(NSInteger)thumbnailSize
transparentBorder:(NSUInteger)borderSize
cornerRadius:(NSUInteger)cornerRadius
interpolationQuality:(CGInterpolationQuality)quality;
- (UIImage *)resizedImage:(CGSize)newSize
interpolationQuality:(CGInterpolationQuality)quality;
- (UIImage *)resizedImageWithContentMode:(UIViewContentMode)contentMode
bounds:(CGSize)bounds
interpolationQuality:(CGInterpolationQuality)quality;
- (UIImage *)resizedImage:(CGSize)newSize
transform:(CGAffineTransform)transform
drawTransposed:(BOOL)transpose
interpolationQuality:(CGInterpolationQuality)quality;
- (CGAffineTransform)transformForOrientation:(CGSize)newSize;
@end
//
// Common.h
// CaplessCoderPaint
//
// Created by crossmo/yangcun on 14/10/31.
// Copyright (c) 2014年 yangcun. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Common : NSObject
+ (BOOL)color:(UIColor *)color1
isEqualToColor:(UIColor *)color2
withTolerance:(CGFloat)tolerance;
@end
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
pod 'Masonry'
pod 'AsyncNetwork'
\ No newline at end of file
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册