API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPColor.j
Go to the documentation of this file.
1 /*
2  * CPColor.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
27 
28 var _redComponent = 0,
29  _greenComponent = 1,
30  _blueComponent = 2,
31  _alphaCompnent = 3;
32 
33 var _hueComponent = 0,
34  _saturationComponent = 1,
35  _brightnessComponent = 2;
36 
37 var cachedBlackColor,
38  cachedRedColor,
39  cachedGreenColor,
40  cachedBlueColor,
41  cachedYellowColor,
42  cachedGrayColor,
43  cachedLightGrayColor,
44  cachedDarkGrayColor,
45  cachedWhiteColor,
46  cachedBrownColor,
47  cachedCyanColor,
48  cachedMagentaColor,
49  cachedOrangeColor,
50  cachedPurpleColor,
51  cachedShadowColor,
52  cachedClearColor;
53 
55 
60 
65 
86 {
87  if (arguments.length < 3)
88  {
89  var slices = arguments[0],
90  imageSlices = [];
91 
92  for (var i = 0; i < slices.length; ++i)
93  {
94  var slice = slices[i];
95 
96  imageSlices.push(slice ? CPImageInBundle(slice[0], CGSizeMake(slice[1], slice[2]), slice[3]) : nil);
97  }
98 
99  if (imageSlices.length === 3)
100  return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:arguments[1] || CPColorPatternIsHorizontal]];
101  else
102  return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]];
103  }
104  else if (arguments.length === 3 || arguments.length === 4)
105  {
106  return [CPColor colorWithPatternImage:CPImageInBundle(arguments[0], CGSizeMake(arguments[1], arguments[2]), arguments[3])];
107  }
108  else
109  {
110  return nil;
111  }
112 }
113 
123 @implementation CPColor : CPObject
124 {
125  CPArray _components;
126 
127  CPImage _patternImage;
128  CPString _cssString;
129 }
130 
144 + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
145 {
146  return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
147 }
148 
164 + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
165 {
166  return [self colorWithRed:red green:green blue:blue alpha:alpha];
167 }
168 
169 
179 + (CPColor)colorWithWhite:(float)white alpha:(float)alpha
180 {
181  return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
182 }
183 
195 + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
196 {
197  return [self colorWithWhite:white alpha:alpha];
198 }
199 
209 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
210 {
211  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
212 }
213 
214 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
215 {
216  if (saturation === 0.0)
217  return [CPColor colorWithCalibratedWhite:brightness / 100.0 alpha:alpha];
218 
219  var f = hue % 60,
220  p = (brightness * (100 - saturation)) / 10000,
221  q = (brightness * (6000 - saturation * f)) / 600000,
222  t = (brightness * (6000 - saturation * (60 -f))) / 600000,
223  b = brightness / 100.0;
224 
225  switch (FLOOR(hue / 60))
226  {
227  case 0: return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
228  case 1: return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
229  case 2: return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
230  case 3: return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
231  case 4: return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
232  case 5: return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
233  }
234 }
235 
246 + (CPColor)colorWithHexString:(string)hex
247 {
248  var rgba = hexToRGB(hex);
249  return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
250 }
251 
256 + (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
257 {
258  // TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
259  // sRGBColorSpace.
260  return [self colorWithRed:red green:green blue:blue alpha:alpha];
261 }
262 
266 + (CPColor)blackColor
267 {
268  if (!cachedBlackColor)
269  cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
270 
271  return cachedBlackColor;
272 }
273 
277 + (CPColor)blueColor
278 {
279  if (!cachedBlueColor)
280  cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
281 
282  return cachedBlueColor;
283 }
284 
288 + (CPColor)darkGrayColor
289 {
290  if (!cachedDarkGrayColor)
291  cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
292 
293  return cachedDarkGrayColor;
294 }
295 
299 + (CPColor)grayColor
300 {
301  if (!cachedGrayColor)
302  cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
303 
304  return cachedGrayColor;
305 }
306 
310 + (CPColor)greenColor
311 {
312  if (!cachedGreenColor)
313  cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
314 
315  return cachedGreenColor;
316 }
317 
321 + (CPColor)lightGrayColor
322 {
323  if (!cachedLightGrayColor)
324  cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
325 
326  return cachedLightGrayColor;
327 }
328 
332 + (CPColor)redColor
333 {
334  if (!cachedRedColor)
335  cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
336 
337  return cachedRedColor;
338 }
339 
343 + (CPColor)whiteColor
344 {
345  if (!cachedWhiteColor)
346  cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
347 
348  return cachedWhiteColor;
349 }
350 
354 + (CPColor)yellowColor
355 {
356  if (!cachedYellowColor)
357  cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
358 
359  return cachedYellowColor;
360 }
361 
365 + (CPColor)brownColor
366 {
367  if (!cachedBrownColor)
368  cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
369 
370  return cachedBrownColor;
371 }
372 
376 + (CPColor)cyanColor
377 {
378  if (!cachedCyanColor)
379  cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
380 
381  return cachedCyanColor;
382 }
383 
387 + (CPColor)magentaColor
388 {
389  if (!cachedMagentaColor)
390  cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
391 
392  return cachedMagentaColor;
393 }
394 
398 + (CPColor)orangeColor
399 {
400  if (!cachedOrangeColor)
401  cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
402 
403  return cachedOrangeColor;
404 }
405 
409 + (CPColor)purpleColor
410 {
411  if (!cachedPurpleColor)
412  cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
413 
414  return cachedPurpleColor;
415 }
416 
421 + (CPColor)shadowColor
422 {
423  if (!cachedShadowColor)
424  cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
425 
426  return cachedShadowColor;
427 }
428 
433 + (CPColor)clearColor
434 {
435  if (!cachedClearColor)
436  cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
437 
438  return cachedClearColor;
439 }
440 
441 + (CPColor)alternateSelectedControlColor
442 {
443  return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
444 }
445 
446 + (CPColor)secondarySelectedControlColor
447 {
448  return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
449 }
450 
456 + (CPColor)colorWithPatternImage:(CPImage)anImage
457 {
458  return [[CPColor alloc] _initWithPatternImage:anImage];
459 }
460 
467 + (CPColor)colorWithCSSString:(CPString)aString
468 {
469  return [[CPColor alloc] _initWithCSSString: aString];
470 }
471 
472 /* @ignore */
473 - (id)_initWithCSSString:(CPString)aString
474 {
475  if (aString.indexOf("rgb") == CPNotFound)
476  return nil;
477 
478  self = [super init];
479 
480  var startingIndex = aString.indexOf("("),
481  parts = aString.substring(startingIndex + 1).split(',');
482 
483  _components = [
484  parseInt(parts[0], 10) / 255.0,
485  parseInt(parts[1], 10) / 255.0,
486  parseInt(parts[2], 10) / 255.0,
487  parts[3] ? parseFloat(parts[3], 10) : 1.0
488  ];
489 
490  // We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
491  // use it (issue #1413.)
492  [self _initCSSStringFromComponents];
493 
494  return self;
495 }
496 
497 /* @ignore */
498 - (id)_initWithRGBA:(CPArray)components
499 {
500  self = [super init];
501 
502  if (self)
503  {
504  _components = components;
505 
506  [self _initCSSStringFromComponents];
507  }
508 
509  return self;
510 }
511 
512 - (void)_initCSSStringFromComponents
513 {
514  var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
515 
516  _cssString = (hasAlpha ? "rgba(" : "rgb(") +
517  parseInt(_components[0] * 255.0) + ", " +
518  parseInt(_components[1] * 255.0) + ", " +
519  parseInt(_components[2] * 255.0) +
520  (hasAlpha ? (", " + _components[3]) : "") + ")";
521 }
522 
523 /* @ignore */
524 - (id)_initWithPatternImage:(CPImage)anImage
525 {
526  self = [super init];
527 
528  if (self)
529  {
530  _patternImage = anImage;
531  _cssString = "url(\"" + [_patternImage filename] + "\")";
532  _components = [0.0, 0.0, 0.0, 1.0];
533  }
534 
535  return self;
536 }
537 
541 - (CPImage)patternImage
542 {
543  return _patternImage;
544 }
545 
549 - (float)alphaComponent
550 {
551  return _components[3];
552 }
553 
557 - (float)blueComponent
558 {
559  return _components[2];
560 }
561 
565 - (float)greenComponent
566 {
567  return _components[1];
568 }
569 
573 - (float)redComponent
574 {
575  return _components[0];
576 }
577 
589 - (CPArray)components
590 {
591  return _components;
592 }
593 
601 - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
602 {
603  var components = _components.slice();
604 
605  components[components.length - 1] = anAlphaComponent;
606 
607  return [[[self class] alloc] _initWithRGBA:components];
608 }
609 
613 - (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
614 {
615  return self;
616 }
617 
628 - (CPArray)hsbComponents
629 {
630  var red = ROUND(_components[_redComponent] * 255.0),
631  green = ROUND(_components[_greenComponent] * 255.0),
632  blue = ROUND(_components[_blueComponent] * 255.0);
633 
634  var max = MAX(red, green, blue),
635  min = MIN(red, green, blue),
636  delta = max - min;
637 
638  var brightness = max / 255.0,
639  saturation = (max != 0) ? delta / max : 0;
640 
641  var hue;
642 
643  if (saturation == 0)
644  {
645  hue = 0;
646  }
647  else
648  {
649  var rr = (max - red) / delta,
650  gr = (max - green) / delta,
651  br = (max - blue) / delta;
652 
653  if (red == max)
654  hue = br - gr;
655  else if (green == max)
656  hue = 2 + rr - br;
657  else
658  hue = 4 + gr - rr;
659 
660  hue /= 6;
661  if (hue < 0)
662  hue++;
663  }
664 
665  return [
666  ROUND(hue * 360.0),
667  ROUND(saturation * 100.0),
668  ROUND(brightness * 100.0)
669  ];
670 }
671 
681 - (CPString)cssString
682 {
683  return _cssString;
684 }
685 
689 - (CPString)hexString
690 {
691  return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
692 }
693 
694 - (BOOL)isEqual:(CPColor)aColor
695 {
696  if (!aColor)
697  return NO;
698 
699  if (aColor === self)
700  return YES;
701 
702  if (![aColor isKindOfClass:CPColor])
703  return NO;
704 
705  if (_patternImage || [aColor patternImage])
706  return [_patternImage isEqual:[aColor patternImage]];
707 
708  // We don't require the components to be equal beyond 8 bits since otherwise
709  // simple rounding errors will make two colours which are exactly the same on
710  // screen compare unequal.
711  return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
712  ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
713  ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
714  [self alphaComponent] == [aColor alphaComponent];
715 }
716 
717 - (CPString)description
718 {
719  var description = [super description],
720  patternImage = [self patternImage];
721 
722  if (!patternImage)
723  return description + " " + [self cssString];
724 
725  description += " {\n";
726 
727  if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
728  {
729  var slices = [patternImage imageSlices];
730 
731  if ([patternImage isThreePartImage])
732  description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
733 
734  description += " patternImage (" + slices.length + " part): [\n";
735 
736  for (var i = 0; i < slices.length; ++i)
737  {
738  var imgDescription = [slices[i] description];
739 
740  description += imgDescription.replace(/^/mg, " ") + ",\n";
741  }
742 
743  description = description.substr(0, description.length - 2) + "\n ]\n}";
744  }
745  else
746  description += [patternImage description].replace(/^/mg, " ") + "\n}";
747 
748  return description;
749 }
750 
751 @end
752 
754 
758 - (void)set
759 {
760  [self setFill];
761  [self setStroke];
762 }
763 
767 - (void)setFill
768 {
770  CGContextSetFillColor(ctx, self);
771 }
772 
776 - (void)setStroke
777 {
779  CGContextSetStrokeColor(ctx, self);
780 }
781 
782 @end
783 
784 @implementation CPColor (Debugging)
785 
786 + (CPColor)randomColor
787 {
788  return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
789 }
790 
791 @end
792 
794 var CPColorComponentsKey = @"CPColorComponentsKey",
795  CPColorPatternImageKey = @"CPColorPatternImageKey";
797 
798 @implementation CPColor (CPCoding)
799 
804 - (id)initWithCoder:(CPCoder)aCoder
805 {
806  if ([aCoder containsValueForKey:CPColorPatternImageKey])
807  return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
808 
809  return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
810 }
811 
816 - (void)encodeWithCoder:(CPCoder)aCoder
817 {
818  if (_patternImage)
819  [aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
820  else
821  [aCoder encodeObject:_components forKey:CPColorComponentsKey];
822 }
823 
824 @end
825 
826 
828 var hexCharacters = "0123456789ABCDEF";
829 
830 /*
831  Used for the CPColor +colorWithHexString: implementation.
832  Returns an array of rgb components.
833 */
834 var hexToRGB = function(hex)
835 {
836  if (hex.length == 3)
837  hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
838 
839  if (hex.length != 6)
840  return null;
841 
842  hex = hex.toUpperCase();
843 
844  for (var i = 0; i < hex.length; i++)
845  if (hexCharacters.indexOf(hex.charAt(i)) == -1)
846  return null;
847 
848  var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
849  green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
850  blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
851 
852  return [red, green, blue, 1.0];
853 };
854 
855 var rgbToHex = function(r,g,b)
856 {
857  return byteToHex(r) + byteToHex(g) + byteToHex(b);
858 };
859 
860 var byteToHex = function(n)
861 {
862  if (!n || isNaN(n))
863  return "00";
864 
865  n = FLOOR(MIN(255, MAX(0, 256 * n)));
866 
867  return hexCharacters.charAt((n - n % 16) / 16) +
868  hexCharacters.charAt(n % 16);
869 };
870