/*
* CPColor.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import
It also provides some class helper methods that returns instances of commonly used colors.
*/ @implementation CPColor : CPObject { CPArray _components; CPImage _patternImage; CPString _cssString; } /*! Creates a color in the RGB color space, with an alpha value. Each component should be between the range of 0.0 to 1.0. For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent. @param red the red component of the color @param green the green component of the color @param blue the blue component of the color @param alpha the alpha component @return a color initialized to the values specified */ + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha { return [[CPColor alloc] _initWithRGBA:[red, green, blue, alpha]]; } /*! @deprecated in favor of colorWithRed:green:blue:alpha: Creates a color in the RGB color space, with an alpha value. Each component should be between the range of 0.0 to 1.0. For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent. @param red the red component of the color @param green the green component of the color @param blue the blue component of the color @param alpha the alpha component @return a color initialized to the values specified */ + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha { return [self colorWithRed:red green:green blue:blue alpha:alpha]; } /*! Creates a new color object with \c white for the RGB components. For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent. @param white a float between 0.0 and 1.0 @param alpha the alpha component between 0.0 and 1.0 @return a color initialized to the values specified */ + (CPColor)colorWithWhite:(float)white alpha:(float)alpha { return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]]; } /*! @deprecated in favor of colorWithWhite:apha: Creates a new color object with \c white for the RGB components. For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent. @param white a float between 0.0 and 1.0 @param alpha the alpha component between 0.0 and 1.0 @return a color initialized to the values specified */ + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha { return [self colorWithWhite:white alpha:alpha]; } /*! Creates a new color in HSB space. @param hue the hue value @param saturation the saturation value @param brightness the brightness value @return the initialized color */ + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness { return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0]; } + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha { if (saturation === 0.0) return [CPColor colorWithCalibratedWhite:brightness / 100.0 alpha:alpha]; var f = hue % 60, p = (brightness * (100 - saturation)) / 10000, q = (brightness * (6000 - saturation * f)) / 600000, t = (brightness * (6000 - saturation * (60 -f))) / 600000, b = brightness / 100.0; switch (FLOOR(hue / 60)) { case 0: return [CPColor colorWithCalibratedRed: b green: t blue: p alpha: alpha]; case 1: return [CPColor colorWithCalibratedRed: q green: b blue: p alpha: alpha]; case 2: return [CPColor colorWithCalibratedRed: p green: b blue: t alpha: alpha]; case 3: return [CPColor colorWithCalibratedRed: p green: q blue: b alpha: alpha]; case 4: return [CPColor colorWithCalibratedRed: t green: p blue: b alpha: alpha]; case 5: return [CPColor colorWithCalibratedRed: b green: p blue: q alpha: alpha]; } } /*! Creates an RGB color from a hexadecimal string. For example, the a string of "FFFFFF" would return a white CPColor. "FF0000" would return a pure red, "00FF00" would return a pure blue, and "0000FF" would return a pure green. @param hex a 6 character long string of hex @return an initialized RGB color */ + (CPColor)colorWithHexString:(string)hex { return [[CPColor alloc] _initWithRGBA: hexToRGB(hex)]; } /*! Returns a black color object. (RGBA=[0.0, 0.0, 0.0, 1.0]) */ + (CPColor)blackColor { if (!cachedBlackColor) cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]]; return cachedBlackColor; } /*! Returns a blue color object. (RGBA=[0.0, 0.0, 1.0, 1.0]) */ + (CPColor)blueColor { if (!cachedBlueColor) cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]]; return cachedBlueColor; } /*! Returns a dark gray color object. (RGBA=[0.33 ,0.33, 0.33, 1.0]) */ + (CPColor)darkGrayColor { if (!cachedDarkGrayColor) cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0]; return cachedDarkGrayColor; } /*! Returns a gray color object. (RGBA=[0.5, 0.5, 0.5, 1.0]) */ + (CPColor)grayColor { if (!cachedGrayColor) cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]; return cachedGrayColor; } /*! Returns a green color object. (RGBA=[0.0, 1.0, 0.0, 1.0]) */ + (CPColor)greenColor { if (!cachedGreenColor) cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]]; return cachedGreenColor; } /*! Returns a light gray color object (RGBA=[0.66, 0.66, 0.66, 1.0]) */ + (CPColor)lightGrayColor { if (!cachedLightGrayColor) cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0]; return cachedLightGrayColor; } /*! Returns a red color object (RGBA=[1.0, 0.0, 0.0, 1.0]) */ + (CPColor)redColor { if (!cachedRedColor) cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]]; return cachedRedColor; } /*! Returns a white color object (RGBA=[1.0, 1.0, 1.0, 1.0]) */ + (CPColor)whiteColor { if (!cachedWhiteColor) cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]]; return cachedWhiteColor; } /*! Returns a yellow color object (RGBA=[1.0, 1.0, 0.0, 1.0]) */ + (CPColor)yellowColor { if (!cachedYellowColor) cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]]; return cachedYellowColor; } /*! Returns a brown color object (RGBA=[0.6, 0.4, 0.2, 1.0]) */ + (CPColor)brownColor { if (!cachedBrownColor) cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]]; return cachedBrownColor; } /*! Returns a cyan color object (RGBA=[0.0, 1.0, 1.0, 1.0]) */ + (CPColor)cyanColor { if (!cachedCyanColor) cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]]; return cachedCyanColor; } /*! Returns a magenta color object (RGBA=[1.0, 0.0, 1.0, 1.0]) */ + (CPColor)magentaColor { if (!cachedMagentaColor) cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]]; return cachedMagentaColor; } /*! Returns a orange color object (RGBA=[1.0, 0.5, 0.0, 1.0]) */ + (CPColor)orangeColor { if (!cachedOrangeColor) cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]]; return cachedOrangeColor; } /*! Returns a purple color object (RGBA=[0.5, 0.0, 0.5, 1.0]) */ + (CPColor)purpleColor { if (!cachedPurpleColor) cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]]; return cachedPurpleColor; } /*! Returns a shadow looking color (RGBA=[0.0, 0.0, 0.0, 0.33]) */ + (CPColor)shadowColor { if (!cachedShadowColor) cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]]; return cachedShadowColor; } /*! Returns a clear color (RGBA=[0.0, 0.0, 0.0, 0.0]) */ + (CPColor)clearColor { if (!cachedClearColor) cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0]; return cachedClearColor; } + (CPColor)alternateSelectedControlColor { return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]; } + (CPColor)secondarySelectedControlColor { return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]; } /*! Creates a color using a tile pattern with \c anImage @param the image to tile @return a tiled image color object */ + (CPColor)colorWithPatternImage:(CPImage)anImage { return [[CPColor alloc] _initWithPatternImage:anImage]; } /*! Creates a CPColor from a valid CSS RGB string. Example, "rgb(32,64,129)". @param aString a CSS color string @return a color initialized to the value in the css string */ + (CPColor)colorWithCSSString:(CPString)aString { return [[CPColor alloc] _initWithCSSString: aString]; } /* @ignore */ - (id)_initWithCSSString:(CPString)aString { if (aString.indexOf("rgb") == CPNotFound) return nil; self = [super init]; var startingIndex = aString.indexOf("("); var parts = aString.substring(startingIndex+1).split(','); _components = [ parseInt(parts[0], 10) / 255.0, parseInt(parts[1], 10) / 255.0, parseInt(parts[2], 10) / 255.0, parts[3] ? parseInt(parts[3], 10) / 255.0 : 1.0 ]; _cssString = aString; return self; } /* @ignore */ - (id)_initWithRGBA:(CPArray)components { self = [super init]; if (self) { _components = components; var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0; _cssString = (hasAlpha ? "rgba(" : "rgb(") + parseInt(_components[0] * 255.0) + ", " + parseInt(_components[1] * 255.0) + ", " + parseInt(_components[2] * 255.0) + (hasAlpha ? (", " + _components[3]) : "") + ")"; } return self; } /* @ignore */ - (id)_initWithPatternImage:(CPImage)anImage { self = [super init]; if (self) { _patternImage = anImage; _cssString = "url(\"" + [_patternImage filename] + "\")"; _components = [0.0, 0.0, 0.0, 1.0]; } return self; } /*! Returns the image being used as the pattern for the tile in this color. */ - (CPImage)patternImage { return _patternImage; } /*! Returns the alpha component of this color. */ - (float)alphaComponent { return _components[3]; } /*! Returns the blue component of this color. */ - (float)blueComponent { return _components[2]; } /*! Returns the green component of this color. */ - (float)greenComponent { return _components[1]; } /*! Return the red component of this color. */ - (float)redComponent { return _components[0]; } /*! Returns the RGBA components of this color in an array. The index values are ordered as:Index Component 0 Red 1 Green 2 Blue 3 Alpha*/ - (CPArray)components { return _components; } /*! Returns a new color with the same RGB as the receiver but a new alpha component. @param anAlphaComponent the alpha component for the new color @return a new color object */ - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent { var components = _components.slice(); components[components.length - 1] = anAlphaComponent; return [[[self class] alloc] _initWithRGBA:components]; } /*! Returns an array with the HSB values for this color. The index values are ordered as:
Index Component 0 Hue 1 Saturation 2 Brightness*/ - (CPArray)hsbComponents { var red = ROUND(_components[_redComponent] * 255.0), green = ROUND(_components[_greenComponent] * 255.0), blue = ROUND(_components[_blueComponent] * 255.0); var max = MAX(red, green, blue), min = MIN(red, green, blue), delta = max - min; var brightness = max / 255.0, saturation = (max != 0) ? delta / max : 0; var hue; if (saturation == 0) { hue = 0; } else { var rr = (max - red) / delta; var gr = (max - green) / delta; var br = (max - blue) / delta; if (red == max) hue = br - gr; else if (green == max) hue = 2 + rr - br; else hue = 4 + gr - rr; hue /= 6; if (hue < 0) hue++; } return [ ROUND(hue * 360.0), ROUND(saturation * 100.0), ROUND(brightness * 100.0) ]; } /*! Returns the CSS representation of this color. The color will be in one of the following forms:
rgb(22,44,88)
rgba(22,44,88,0.5) // if there is an alpha
url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
*/
- (CPString)cssString
{
return _cssString;
}
/*!
Returns a 6 character long hex string of this color.
*/
- (CPString)hexString
{
return rgbToHex([self redComponent], [self greenComponent], [self blueComponent])
}
- (BOOL)isEqual:(CPColor)aColor
{
if (!aColor)
return NO;
if (aColor === self)
return YES;
return [aColor isKindOfClass:CPColor] && [aColor cssString] === [self cssString];
}
- (CPString)description
{
return [super description]+" "+[self cssString];
}
@end
@implementation CPColor (CoreGraphicsExtensions)
/*!
Set's the receiver to be the fill and stroke color in the current graphics context
*/
- (void)set
{
[self setFill];
[self setStroke];
}
/*!
Set's the receiver to be the fill color in the current graphics context
*/
- (void)setFill
{
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
CGContextSetFillColor(ctx, self);
}
/*!
Set's the receiver to be the stroke color in the current graphics context
*/
- (void)setStroke
{
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
CGContextSetStrokeColor(ctx, self);
}
@end
@implementation CPColor (Debugging)
+ (CPColor)randomColor
{
return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
}
@end
var CPColorComponentsKey = @"CPColorComponentsKey",
CPColorPatternImageKey = @"CPColorPatternImageKey";
@implementation CPColor (CPCoding)
/*!
Initializes this color from the data archived in a coder.
@param aCoder the coder from which the color will be loaded
*/
- (id)initWithCoder:(CPCoder)aCoder
{
if ([aCoder containsValueForKey:CPColorPatternImageKey])
return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
}
/*!
Archives this color into a coder.
@param aCoder the coder into which the color will be archived.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
if (_patternImage)
[aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
else
[aCoder encodeObject:_components forKey:CPColorComponentsKey];
}
@end
var hexCharacters = "0123456789ABCDEF";
/*!
Used for the CPColor \c +colorWithHexString: implementation
@ignore
@class CPColor
@return an array of rgb components
*/
var hexToRGB = function(hex)
{
if (hex.length == 3)
hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
if (hex.length != 6)
return null;
hex = hex.toUpperCase();
for (var i = 0; i < hex.length; i++)
if (hexCharacters.indexOf(hex.charAt(i)) == -1)
return null;
var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0;
var green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0;
var blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
return [red, green, blue, 1.0];
};
var rgbToHex = function(r,g,b)
{
return byteToHex(r) + byteToHex(g) + byteToHex(b);
};
var byteToHex = function(n)
{
if (!n || isNaN(n))
return "00";
n = FLOOR(MIN(255, MAX(0, 256 * n)));
return hexCharacters.charAt((n - n % 16) / 16) +
hexCharacters.charAt(n % 16);
};