PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/SimpleDrawing/DrawingLib/3rd Party/ColorPickerClasses/RSColorPickerView.m

https://bitbucket.org/kyamaguchi/simpledrawing
Objective C | 384 lines | 273 code | 76 blank | 35 comment | 70 complexity | 823daafa7838519afdb693a5e115e001 MD5 | raw file
  1. //
  2. // RSColorPickerView.m
  3. // RSColorPicker
  4. //
  5. // Created by Ryan Sullivan on 8/12/11.
  6. // Copyright 2011 Freelance Web Developer. All rights reserved.
  7. //
  8. #import "RSColorPickerView.h"
  9. #import "BGRSLoupeLayer.h"
  10. // point-related macros
  11. #define INNER_P(x) (x < 0 ? ceil(x) : floor(x))
  12. #define IS_INSIDE(p) (round(p.x) >= 0 && round(p.x) < self.frame.size.width && round(p.y) >= 0 && round(p.y) < self.frame.size.height)
  13. #define MY_MIN3(x,y,z) MIN(x,MIN(y,z))
  14. #define MY_MAX3(x,y,z) MAX(x,MAX(y,z))
  15. // Concept-code from http://www.easyrgb.com/index.php?X=MATH&H=21#text21
  16. BMPixel pixelFromHSV(CGFloat H, CGFloat S, CGFloat V) {
  17. if (S == 0) {
  18. return BMPixelMake(V, V, V, 1.0);
  19. }
  20. CGFloat var_h = H * 6.0;
  21. if (var_h == 6.0) {
  22. var_h = 0.0;
  23. }
  24. CGFloat var_i = floor(var_h);
  25. CGFloat var_1 = V * (1.0 - S);
  26. CGFloat var_2 = V * (1.0 - S * (var_h - var_i));
  27. CGFloat var_3 = V * (1.0 - S * (1.0 - (var_h - var_i)));
  28. if (var_i == 0) {
  29. return BMPixelMake(V, var_3, var_1, 1.0);
  30. } else if (var_i == 1) {
  31. return BMPixelMake(var_2, V, var_1, 1.0);
  32. } else if (var_i == 2) {
  33. return BMPixelMake(var_1, V, var_3, 1.0);
  34. } else if (var_i == 3) {
  35. return BMPixelMake(var_1, var_2, V, 1.0);
  36. } else if (var_i == 4) {
  37. return BMPixelMake(var_3, var_1, V, 1.0);
  38. }
  39. return BMPixelMake(V, var_1, var_2, 1.0);
  40. }
  41. void HSVFromPixel(BMPixel pixel, CGFloat* h, CGFloat* s, CGFloat* v) {
  42. CGFloat rgb_min, rgb_max;
  43. CGFloat hsv_hue, hsv_val, hsv_sat;
  44. rgb_min = MY_MIN3(pixel.red, pixel.green, pixel.blue);
  45. rgb_max = MY_MAX3(pixel.red, pixel.green, pixel.blue);
  46. if (rgb_max == rgb_min) {
  47. hsv_hue = 0;
  48. } else if (rgb_max == pixel.red) {
  49. hsv_hue = 60.0f * ((pixel.green - pixel.blue) / (rgb_max - rgb_min));
  50. hsv_hue = fmodf(hsv_hue, 360.0f);
  51. } else if (rgb_max == pixel.green) {
  52. hsv_hue = 60.0f * ((pixel.blue - pixel.red) / (rgb_max - rgb_min)) + 120.0f;
  53. } else if (rgb_max == pixel.blue) {
  54. hsv_hue = 60.0f * ((pixel.red - pixel.green) / (rgb_max - rgb_min)) + 240.0f;
  55. }
  56. hsv_val = rgb_max;
  57. if (rgb_max == 0) {
  58. hsv_sat = 0;
  59. } else {
  60. hsv_sat = 1.0 - (rgb_min / rgb_max);
  61. }
  62. *h = hsv_hue;
  63. *s = hsv_sat;
  64. *v = hsv_val;
  65. }
  66. @interface RSColorPickerView (Private)
  67. -(void)initRoutine;
  68. -(void)updateSelectionLocation;
  69. -(CGPoint)validPointForTouch:(CGPoint)touchPoint;
  70. @end
  71. @implementation RSColorPickerView
  72. @synthesize brightness, cropToCircle, delegate;
  73. - (id)initWithFrame:(CGRect)frame {
  74. CGFloat sqr = fmin(frame.size.height, frame.size.width);
  75. frame.size = CGSizeMake(sqr, sqr);
  76. self = [super initWithFrame:frame];
  77. if (self) {
  78. [self initRoutine];
  79. }
  80. return self;
  81. }
  82. - (id)initWithCoder:(NSCoder *)aDecoder {
  83. self = [super initWithCoder:aDecoder];
  84. if (self) {
  85. [self initRoutine];
  86. }
  87. return self;
  88. }
  89. -(void)initRoutine {
  90. CGRect frame = self.frame;
  91. CGFloat sqr = fmin(frame.size.height, frame.size.width);
  92. cropToCircle = YES;
  93. badTouch = NO;
  94. bitmapNeedsUpdate = YES;
  95. selection = CGPointMake(sqr/2, sqr/2);
  96. selectionView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 18.0, 18.0)];
  97. selectionView.backgroundColor = [UIColor clearColor];
  98. selectionView.layer.borderWidth = 2.0;
  99. selectionView.layer.borderColor = [UIColor colorWithWhite:0.1 alpha:1.0].CGColor;
  100. selectionView.layer.cornerRadius = 9.0;
  101. [self updateSelectionLocation];
  102. [self addSubview:selectionView];
  103. self.brightness = 1.0;
  104. rep = [[ANImageBitmapRep alloc] initWithSize:BMPointFromSize(frame.size)];
  105. }
  106. -(void)setBrightness:(CGFloat)bright {
  107. brightness = bright;
  108. [self setNeedsDisplay];
  109. [delegate colorPickerDidChangeSelection:self];
  110. }
  111. -(void)setCropToCircle:(BOOL)circle {
  112. if (circle == cropToCircle) { return; }
  113. cropToCircle = circle;
  114. bitmapNeedsUpdate = YES;
  115. [self setNeedsDisplay];
  116. }
  117. -(void)genBitmap {
  118. if (!bitmapNeedsUpdate) return;
  119. CGFloat radius = (rep.bitmapSize.x / 2.0);
  120. CGFloat relX = 0.0;
  121. CGFloat relY = 0.0;
  122. for (int x = 0; x < rep.bitmapSize.x; x++) {
  123. relX = x - radius;
  124. for (int y = 0; y < rep.bitmapSize.y; y++) {
  125. relY = radius - y;
  126. CGFloat r_distance = sqrt((relX * relX)+(relY * relY));
  127. r_distance = fmin(r_distance, radius);
  128. CGFloat angle = atan2(relY, relX);
  129. if (angle < 0.0) { angle = (2.0 * M_PI)+angle; }
  130. CGFloat perc_angle = angle / (2.0 * M_PI);
  131. BMPixel thisPixel = pixelFromHSV(perc_angle, r_distance/radius, 1.0);
  132. [rep setPixel:thisPixel atPoint:BMPointMake(x, y)];
  133. }
  134. }
  135. bitmapNeedsUpdate = NO;
  136. }
  137. // Only override drawRect: if you perform custom drawing.
  138. // An empty implementation adversely affects performance during animation.
  139. - (void)drawRect:(CGRect)rect
  140. {
  141. [self genBitmap];
  142. CGContextRef ctx = UIGraphicsGetCurrentContext();
  143. if (cropToCircle) {
  144. CGContextAddEllipseInRect(ctx, self.bounds);
  145. CGContextClip(ctx);
  146. }
  147. CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
  148. CGContextFillRect(ctx, self.bounds);
  149. [[rep image] drawInRect:self.bounds blendMode:kCGBlendModeNormal alpha:brightness];
  150. }
  151. -(UIColor*)selectionColor {
  152. [self genBitmap];
  153. BMPixel pixel = [rep getPixelAtPoint:BMPointFromPoint(selection)];
  154. pixel.red *= brightness;
  155. pixel.green *= brightness;
  156. pixel.blue *= brightness;
  157. return UIColorFromBMPixel(pixel);
  158. }
  159. -(CGPoint)selection {
  160. return selection;
  161. }
  162. -(void)setSelectionColor:(UIColor *)selectionColor {
  163. const float* comps = CGColorGetComponents(selectionColor.CGColor);
  164. BMPixel pixel = BMPixelMake(comps[0], comps[1], comps[2], 1);
  165. // convert to HSV
  166. CGFloat h, s, v;
  167. HSVFromPixel(pixel, &h, &s, &v);
  168. // extract the original point
  169. CGFloat radius = (rep.bitmapSize.x / 2.0);
  170. CGFloat angle = h * (M_PI / 180);
  171. CGFloat centerDistance = s * radius;
  172. CGFloat pointX = cos(angle) * centerDistance + radius;
  173. CGFloat pointY = radius - sin(angle) * centerDistance;
  174. selection = CGPointMake(pointX, pointY);
  175. [self updateSelectionLocation];
  176. [self setBrightness:v];
  177. }
  178. /**
  179. * Hue saturation and briteness of the selected point
  180. * @Reference: Taken from ars/uicolor-utilities
  181. * http://github.com/ars/uicolor-utilities
  182. */
  183. -(void)selectionToHue:(CGFloat *)pH saturation:(CGFloat *)pS brightness:(CGFloat *)pV{
  184. //Get red green and blue from selection
  185. BMPixel pixel = [rep getPixelAtPoint:BMPointFromPoint(selection)];
  186. CGFloat r = pixel.red, b = pixel.blue, g = pixel.green;
  187. CGFloat h,s,v;
  188. // From Foley and Van Dam
  189. CGFloat max = MAX(r, MAX(g, b));
  190. CGFloat min = MIN(r, MIN(g, b));
  191. // Brightness
  192. v = max;
  193. // Saturation
  194. s = (max != 0.0f) ? ((max - min) / max) : 0.0f;
  195. if (s == 0.0f) {
  196. // No saturation, so undefined hue
  197. h = 0.0f;
  198. } else {
  199. // Determine hue
  200. CGFloat rc = (max - r) / (max - min); // Distance of color from red
  201. CGFloat gc = (max - g) / (max - min); // Distance of color from green
  202. CGFloat bc = (max - b) / (max - min); // Distance of color from blue
  203. if (r == max) h = bc - gc; // resulting color between yellow and magenta
  204. else if (g == max) h = 2 + rc - bc; // resulting color between cyan and yellow
  205. else /* if (b == max) */ h = 4 + gc - rc; // resulting color between magenta and cyan
  206. h *= 60.0f; // Convert to degrees
  207. if (h < 0.0f) h += 360.0f; // Make non-negative
  208. h /= 360.0f; // Convert to decimal
  209. }
  210. if (pH) *pH = h;
  211. if (pS) *pS = s;
  212. if (pV) *pV = v;
  213. }
  214. -(UIColor*)colorAtPoint:(CGPoint)point {
  215. if (IS_INSIDE(point)){
  216. return UIColorFromBMPixel([rep getPixelAtPoint:BMPointFromPoint(point)]);
  217. }
  218. return self.backgroundColor;
  219. }
  220. -(CGPoint)validPointForTouch:(CGPoint)touchPoint {
  221. if (!cropToCircle) {
  222. //Constrain point to inside of bounds
  223. touchPoint.x = MIN(CGRectGetMaxX(self.bounds)-1, touchPoint.x);
  224. touchPoint.x = MAX(CGRectGetMinX(self.bounds), touchPoint.x);
  225. touchPoint.y = MIN(CGRectGetMaxY(self.bounds)-1, touchPoint.y);
  226. touchPoint.y = MAX(CGRectGetMinY(self.bounds), touchPoint.y);
  227. return touchPoint;
  228. }
  229. // BMPixel pixel = BMPixelMake(0.0, 0.0, 0.0, 0.0);
  230. // if (IS_INSIDE(touchPoint)) {
  231. // pixel = [rep getPixelAtPoint:BMPointFromPoint(touchPoint)];
  232. // }
  233. //
  234. // if (pixel.alpha > 0.0) {
  235. // return touchPoint;
  236. // }
  237. // the point is invalid, so we will put it in a valid location.
  238. CGFloat radius = (self.frame.size.width / 2.0);
  239. CGFloat relX = touchPoint.x - radius;
  240. CGFloat relY = radius - touchPoint.y;
  241. CGFloat angle = atan2(relY, relX);
  242. CGFloat r_distance = sqrt((relX * relX)+(relY * relY));
  243. if (r_distance <= radius) return touchPoint;
  244. if (angle < 0) { angle = (2.0 * M_PI) + angle; }
  245. relX = INNER_P(cos(angle) * radius);
  246. relY = INNER_P(sin(angle) * radius);
  247. while (relX >= radius) { relX -= 1; }
  248. while (relX <= -radius) { relX += 1; }
  249. while (relY >= radius) { relY -= 1; }
  250. while (relY <= -radius) { relY += 1; }
  251. return CGPointMake(round(relX + radius), round(radius - relY));
  252. }
  253. -(void)updateSelectionLocation {
  254. selectionView.center = selection;
  255. [CATransaction setDisableActions:YES];
  256. loupeLayer.position = selection;
  257. [loupeLayer setNeedsDisplay];
  258. }
  259. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  260. //Lazily load loupeLayer
  261. if (!loupeLayer) {
  262. loupeLayer = [[BGRSLoupeLayer layer] retain];
  263. }
  264. CGPoint point = [[touches anyObject] locationInView:self];
  265. CGPoint circlePoint = [self validPointForTouch:point];
  266. BMPixel checker = [rep getPixelAtPoint:BMPointFromPoint(point)];
  267. if (!(checker.alpha > 0.0)) {
  268. badTouch = YES;
  269. return;
  270. }
  271. badTouch = NO;
  272. BMPixel pixel = [rep getPixelAtPoint:BMPointFromPoint(circlePoint)];
  273. NSAssert(pixel.alpha >= 0.0, @"-validPointForTouch: returned invalid point.");
  274. selection = circlePoint;
  275. [delegate colorPickerDidChangeSelection:self];
  276. [loupeLayer appearInColorPicker:self];
  277. [self updateSelectionLocation];
  278. }
  279. -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  280. if (badTouch) return;
  281. CGPoint point = [[touches anyObject] locationInView:self];
  282. CGPoint circlePoint = [self validPointForTouch:point];
  283. BMPixel pixel = [rep getPixelAtPoint:BMPointFromPoint(circlePoint)];
  284. NSAssert(pixel.alpha >= 0.0, @"-validPointForTouch: returned invalid point.");
  285. selection = circlePoint;
  286. [delegate colorPickerDidChangeSelection:self];
  287. [self updateSelectionLocation];
  288. }
  289. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  290. if (badTouch) return;
  291. CGPoint point = [[touches anyObject] locationInView:self];
  292. CGPoint circlePoint = [self validPointForTouch:point];
  293. BMPixel pixel = [rep getPixelAtPoint:BMPointFromPoint(circlePoint)];
  294. NSAssert(pixel.alpha >= 0.0, @"-validPointForTouch: returned invalid point.");
  295. selection = circlePoint;
  296. [delegate colorPickerDidChangeSelection:self];
  297. [self updateSelectionLocation];
  298. [loupeLayer disapear];
  299. }
  300. - (void)dealloc
  301. {
  302. [rep release];
  303. [selectionView release];
  304. [loupeLayer release];
  305. loupeLayer = nil;
  306. [super dealloc];
  307. }
  308. @end