![]() |
API 0.9.5
|
00001 /* 00002 * CPColor.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2008, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 00027 00028 var _redComponent = 0, 00029 _greenComponent = 1, 00030 _blueComponent = 2, 00031 _alphaCompnent = 3; 00032 00033 var _hueComponent = 0, 00034 _saturationComponent = 1, 00035 _brightnessComponent = 2; 00036 00037 var cachedBlackColor, 00038 cachedRedColor, 00039 cachedGreenColor, 00040 cachedBlueColor, 00041 cachedYellowColor, 00042 cachedGrayColor, 00043 cachedLightGrayColor, 00044 cachedDarkGrayColor, 00045 cachedWhiteColor, 00046 cachedBrownColor, 00047 cachedCyanColor, 00048 cachedMagentaColor, 00049 cachedOrangeColor, 00050 cachedPurpleColor, 00051 cachedShadowColor, 00052 cachedClearColor; 00053 00055 00059 CPColorPatternIsVertical = YES; 00060 00064 CPColorPatternIsHorizontal = NO; 00065 00085 function CPColorWithImages() 00086 { 00087 if (arguments.length < 3) 00088 { 00089 var slices = arguments[0], 00090 imageSlices = []; 00091 00092 for (var i = 0; i < slices.length; ++i) 00093 { 00094 var slice = slices[i]; 00095 00096 imageSlices.push(slice ? CPImageInBundle(slice[0], CGSizeMake(slice[1], slice[2]), slice[3]) : nil); 00097 } 00098 00099 if (imageSlices.length === 3) 00100 return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:arguments[1] || CPColorPatternIsHorizontal]]; 00101 else 00102 return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]]; 00103 } 00104 else if (arguments.length === 3 || arguments.length === 4) 00105 { 00106 return [CPColor colorWithPatternImage:CPImageInBundle(arguments[0], CGSizeMake(arguments[1], arguments[2]), arguments[3])]; 00107 } 00108 else 00109 { 00110 return nil; 00111 } 00112 } 00113 00123 @implementation CPColor : CPObject 00124 { 00125 CPArray _components; 00126 00127 CPImage _patternImage; 00128 CPString _cssString; 00129 } 00130 00144 + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha 00145 { 00146 return [[CPColor alloc] _initWithRGBA:[red, green, blue, alpha]]; 00147 } 00148 00164 + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha 00165 { 00166 return [self colorWithRed:red green:green blue:blue alpha:alpha]; 00167 } 00168 00169 00179 + (CPColor)colorWithWhite:(float)white alpha:(float)alpha 00180 { 00181 return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]]; 00182 } 00183 00195 + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha 00196 { 00197 return [self colorWithWhite:white alpha:alpha]; 00198 } 00199 00209 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness 00210 { 00211 return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0]; 00212 } 00213 00214 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha 00215 { 00216 if (saturation === 0.0) 00217 return [CPColor colorWithCalibratedWhite:brightness / 100.0 alpha:alpha]; 00218 00219 var f = hue % 60, 00220 p = (brightness * (100 - saturation)) / 10000, 00221 q = (brightness * (6000 - saturation * f)) / 600000, 00222 t = (brightness * (6000 - saturation * (60 -f))) / 600000, 00223 b = brightness / 100.0; 00224 00225 switch (FLOOR(hue / 60)) 00226 { 00227 case 0: return [CPColor colorWithCalibratedRed: b green: t blue: p alpha: alpha]; 00228 case 1: return [CPColor colorWithCalibratedRed: q green: b blue: p alpha: alpha]; 00229 case 2: return [CPColor colorWithCalibratedRed: p green: b blue: t alpha: alpha]; 00230 case 3: return [CPColor colorWithCalibratedRed: p green: q blue: b alpha: alpha]; 00231 case 4: return [CPColor colorWithCalibratedRed: t green: p blue: b alpha: alpha]; 00232 case 5: return [CPColor colorWithCalibratedRed: b green: p blue: q alpha: alpha]; 00233 } 00234 } 00235 00246 + (CPColor)colorWithHexString:(string)hex 00247 { 00248 var rgba = hexToRGB(hex); 00249 return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null; 00250 } 00251 00255 + (CPColor)blackColor 00256 { 00257 if (!cachedBlackColor) 00258 cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]]; 00259 00260 return cachedBlackColor; 00261 } 00262 00266 + (CPColor)blueColor 00267 { 00268 if (!cachedBlueColor) 00269 cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]]; 00270 00271 return cachedBlueColor; 00272 } 00273 00277 + (CPColor)darkGrayColor 00278 { 00279 if (!cachedDarkGrayColor) 00280 cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0]; 00281 00282 return cachedDarkGrayColor; 00283 } 00284 00288 + (CPColor)grayColor 00289 { 00290 if (!cachedGrayColor) 00291 cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]; 00292 00293 return cachedGrayColor; 00294 } 00295 00299 + (CPColor)greenColor 00300 { 00301 if (!cachedGreenColor) 00302 cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]]; 00303 00304 return cachedGreenColor; 00305 } 00306 00310 + (CPColor)lightGrayColor 00311 { 00312 if (!cachedLightGrayColor) 00313 cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0]; 00314 00315 return cachedLightGrayColor; 00316 } 00317 00321 + (CPColor)redColor 00322 { 00323 if (!cachedRedColor) 00324 cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]]; 00325 00326 return cachedRedColor; 00327 } 00328 00332 + (CPColor)whiteColor 00333 { 00334 if (!cachedWhiteColor) 00335 cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]]; 00336 00337 return cachedWhiteColor; 00338 } 00339 00343 + (CPColor)yellowColor 00344 { 00345 if (!cachedYellowColor) 00346 cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]]; 00347 00348 return cachedYellowColor; 00349 } 00350 00354 + (CPColor)brownColor 00355 { 00356 if (!cachedBrownColor) 00357 cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]]; 00358 00359 return cachedBrownColor; 00360 } 00361 00365 + (CPColor)cyanColor 00366 { 00367 if (!cachedCyanColor) 00368 cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]]; 00369 00370 return cachedCyanColor; 00371 } 00372 00376 + (CPColor)magentaColor 00377 { 00378 if (!cachedMagentaColor) 00379 cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]]; 00380 00381 return cachedMagentaColor; 00382 } 00383 00387 + (CPColor)orangeColor 00388 { 00389 if (!cachedOrangeColor) 00390 cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]]; 00391 00392 return cachedOrangeColor; 00393 } 00394 00398 + (CPColor)purpleColor 00399 { 00400 if (!cachedPurpleColor) 00401 cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]]; 00402 00403 return cachedPurpleColor; 00404 } 00405 00410 + (CPColor)shadowColor 00411 { 00412 if (!cachedShadowColor) 00413 cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]]; 00414 00415 return cachedShadowColor; 00416 } 00417 00422 + (CPColor)clearColor 00423 { 00424 if (!cachedClearColor) 00425 cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0]; 00426 00427 return cachedClearColor; 00428 } 00429 00430 + (CPColor)alternateSelectedControlColor 00431 { 00432 return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]; 00433 } 00434 00435 + (CPColor)secondarySelectedControlColor 00436 { 00437 return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]; 00438 } 00439 00445 + (CPColor)colorWithPatternImage:(CPImage)anImage 00446 { 00447 return [[CPColor alloc] _initWithPatternImage:anImage]; 00448 } 00449 00456 + (CPColor)colorWithCSSString:(CPString)aString 00457 { 00458 return [[CPColor alloc] _initWithCSSString: aString]; 00459 } 00460 00461 /* @ignore */ 00462 - (id)_initWithCSSString:(CPString)aString 00463 { 00464 if (aString.indexOf("rgb") == CPNotFound) 00465 return nil; 00466 00467 self = [super init]; 00468 00469 var startingIndex = aString.indexOf("("), 00470 parts = aString.substring(startingIndex + 1).split(','); 00471 00472 _components = [ 00473 parseInt(parts[0], 10) / 255.0, 00474 parseInt(parts[1], 10) / 255.0, 00475 parseInt(parts[2], 10) / 255.0, 00476 parts[3] ? parseFloat(parts[3], 10) : 1.0 00477 ]; 00478 00479 _cssString = aString; 00480 00481 return self; 00482 } 00483 00484 /* @ignore */ 00485 - (id)_initWithRGBA:(CPArray)components 00486 { 00487 self = [super init]; 00488 00489 if (self) 00490 { 00491 _components = components; 00492 00493 var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0; 00494 00495 _cssString = (hasAlpha ? "rgba(" : "rgb(") + 00496 parseInt(_components[0] * 255.0) + ", " + 00497 parseInt(_components[1] * 255.0) + ", " + 00498 parseInt(_components[2] * 255.0) + 00499 (hasAlpha ? (", " + _components[3]) : "") + ")"; 00500 } 00501 00502 return self; 00503 } 00504 00505 /* @ignore */ 00506 - (id)_initWithPatternImage:(CPImage)anImage 00507 { 00508 self = [super init]; 00509 00510 if (self) 00511 { 00512 _patternImage = anImage; 00513 _cssString = "url(\"" + [_patternImage filename] + "\")"; 00514 _components = [0.0, 0.0, 0.0, 1.0]; 00515 } 00516 00517 return self; 00518 } 00519 00523 - (CPImage)patternImage 00524 { 00525 return _patternImage; 00526 } 00527 00531 - (float)alphaComponent 00532 { 00533 return _components[3]; 00534 } 00535 00539 - (float)blueComponent 00540 { 00541 return _components[2]; 00542 } 00543 00547 - (float)greenComponent 00548 { 00549 return _components[1]; 00550 } 00551 00555 - (float)redComponent 00556 { 00557 return _components[0]; 00558 } 00559 00571 - (CPArray)components 00572 { 00573 return _components; 00574 } 00575 00583 - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent 00584 { 00585 var components = _components.slice(); 00586 00587 components[components.length - 1] = anAlphaComponent; 00588 00589 return [[[self class] alloc] _initWithRGBA:components]; 00590 } 00591 00602 - (CPArray)hsbComponents 00603 { 00604 var red = ROUND(_components[_redComponent] * 255.0), 00605 green = ROUND(_components[_greenComponent] * 255.0), 00606 blue = ROUND(_components[_blueComponent] * 255.0); 00607 00608 var max = MAX(red, green, blue), 00609 min = MIN(red, green, blue), 00610 delta = max - min; 00611 00612 var brightness = max / 255.0, 00613 saturation = (max != 0) ? delta / max : 0; 00614 00615 var hue; 00616 00617 if (saturation == 0) 00618 { 00619 hue = 0; 00620 } 00621 else 00622 { 00623 var rr = (max - red) / delta, 00624 gr = (max - green) / delta, 00625 br = (max - blue) / delta; 00626 00627 if (red == max) 00628 hue = br - gr; 00629 else if (green == max) 00630 hue = 2 + rr - br; 00631 else 00632 hue = 4 + gr - rr; 00633 00634 hue /= 6; 00635 if (hue < 0) 00636 hue++; 00637 } 00638 00639 return [ 00640 ROUND(hue * 360.0), 00641 ROUND(saturation * 100.0), 00642 ROUND(brightness * 100.0) 00643 ]; 00644 } 00645 00655 - (CPString)cssString 00656 { 00657 return _cssString; 00658 } 00659 00663 - (CPString)hexString 00664 { 00665 return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]); 00666 } 00667 00668 - (BOOL)isEqual:(CPColor)aColor 00669 { 00670 if (!aColor) 00671 return NO; 00672 00673 if (aColor === self) 00674 return YES; 00675 00676 if (![aColor isKindOfClass:CPColor]) 00677 return NO; 00678 00679 if (_patternImage || [aColor patternImage]) 00680 return [_patternImage isEqual:[aColor patternImage]]; 00681 00682 // We don't require the components to be equal beyond 8 bits since otherwise 00683 // simple rounding errors will make two colours which are exactly the same on 00684 // screen compare unequal. 00685 return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) && 00686 ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) && 00687 ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) && 00688 [self alphaComponent] == [aColor alphaComponent]; 00689 } 00690 00691 - (CPString)description 00692 { 00693 var description = [super description], 00694 patternImage = [self patternImage]; 00695 00696 if (!patternImage) 00697 return description + " " + [self cssString]; 00698 00699 description += " {\n"; 00700 00701 if ([patternImage isThreePartImage] || [patternImage isNinePartImage]) 00702 { 00703 var slices = [patternImage imageSlices]; 00704 00705 if ([patternImage isThreePartImage]) 00706 description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n"; 00707 00708 description += " patternImage (" + slices.length + " part): [\n"; 00709 00710 for (var i = 0; i < slices.length; ++i) 00711 { 00712 var imgDescription = [slices[i] description]; 00713 00714 description += imgDescription.replace(/^/mg, " ") + ",\n"; 00715 } 00716 00717 description = description.substr(0, description.length - 2) + "\n ]\n}"; 00718 } 00719 else 00720 description += [patternImage description].replace(/^/mg, " ") + "\n}"; 00721 00722 return description; 00723 } 00724 00725 @end 00726 00727 @implementation CPColor (CoreGraphicsExtensions) 00728 00732 - (void)set 00733 { 00734 [self setFill]; 00735 [self setStroke]; 00736 } 00737 00741 - (void)setFill 00742 { 00743 var ctx = [[CPGraphicsContext currentContext] graphicsPort]; 00744 CGContextSetFillColor(ctx, self); 00745 } 00746 00750 - (void)setStroke 00751 { 00752 var ctx = [[CPGraphicsContext currentContext] graphicsPort]; 00753 CGContextSetStrokeColor(ctx, self); 00754 } 00755 00756 @end 00757 00758 @implementation CPColor (Debugging) 00759 00760 + (CPColor)randomColor 00761 { 00762 return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0]; 00763 } 00764 00765 @end 00766 00768 var CPColorComponentsKey = @"CPColorComponentsKey", 00769 CPColorPatternImageKey = @"CPColorPatternImageKey"; 00771 00772 @implementation CPColor (CPCoding) 00773 00778 - (id)initWithCoder:(CPCoder)aCoder 00779 { 00780 if ([aCoder containsValueForKey:CPColorPatternImageKey]) 00781 return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]]; 00782 00783 return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]]; 00784 } 00785 00790 - (void)encodeWithCoder:(CPCoder)aCoder 00791 { 00792 if (_patternImage) 00793 [aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey]; 00794 else 00795 [aCoder encodeObject:_components forKey:CPColorComponentsKey]; 00796 } 00797 00798 @end 00799 00800 00802 var hexCharacters = "0123456789ABCDEF"; 00803 00804 /* 00805 Used for the CPColor +colorWithHexString: implementation. 00806 Returns an array of rgb components. 00807 */ 00808 var hexToRGB = function(hex) 00809 { 00810 if (hex.length == 3) 00811 hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); 00812 00813 if (hex.length != 6) 00814 return null; 00815 00816 hex = hex.toUpperCase(); 00817 00818 for (var i = 0; i < hex.length; i++) 00819 if (hexCharacters.indexOf(hex.charAt(i)) == -1) 00820 return null; 00821 00822 var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0, 00823 green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0, 00824 blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0; 00825 00826 return [red, green, blue, 1.0]; 00827 }; 00828 00829 var rgbToHex = function(r,g,b) 00830 { 00831 return byteToHex(r) + byteToHex(g) + byteToHex(b); 00832 }; 00833 00834 var byteToHex = function(n) 00835 { 00836 if (!n || isNaN(n)) 00837 return "00"; 00838 00839 n = FLOOR(MIN(255, MAX(0, 256 * n))); 00840 00841 return hexCharacters.charAt((n - n % 16) / 16) + 00842 hexCharacters.charAt(n % 16); 00843 }; 00844