API 0.9.5
AppKit/CPColor.j
Go to the documentation of this file.
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 
 All Classes Files Functions Variables Defines