/*
* 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 colorspace, 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:[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))]]; } /*! @deprecated in favor of colorWithRed:green:blue:alpha: Creates a color in the RGB colorspace, 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: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)colorWithCalibratedWhite:(float)white alpha:(float)alpha { return [self colorWithWhite:white alpha:alpha]; } /*! Creates a new color based on the given HSB components. Note: earlier versions of this method took a hue component as degrees between 0-360, and saturation and brightness components as percent between 0-100. This method has now been corrected to take all components in the 0-1 range as in Cocoa. @param hue the hue component (0.0-1.0) @param saturation the saturation component (0.0-1.0) @param brightness the brightness component (0.0-1.0) @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]; } /*! Calibrated colors are not supported in Cappuccino. This method has the same result as [CPColor colorWithHue:saturation:brightness:alpha:]. */ + (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha { return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha]; } /*! Creates a new color based on the given HSB components. Note: earlier versions of this method took a hue component as degrees between 0-360, and saturation and brightness components as percent between 0-100. This method has now been corrected to take all components in the 0-1 range as in Cocoa. @param hue the hue component (0.0-1.0) @param saturation the saturation component (0.0-1.0) @param brightness the brightness component (0.0-1.0) @param alpha the opacity component (0.0-1.0) @return the initialized color */ + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha { // Clamp values. hue = MAX(MIN(hue, 1.0), 0.0); saturation = MAX(MIN(saturation, 1.0), 0.0); brightness = MAX(MIN(brightness, 1.0), 0.0); if (saturation === 0.0) return [CPColor colorWithCalibratedWhite:brightness alpha:alpha]; var f = (hue * 360) % 60, p = (brightness * (1 - saturation)), q = (brightness * (60 - saturation * f)) / 60, t = (brightness * (60 - saturation * (60 - f))) / 60, b = brightness; switch (FLOOR(hue * 6)) { case 0: case 6: 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 { var rgba = hexToRGB(hex); return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null; } /*! Creates a color in the sRGB colorspace with the given components and alpha values. Values below 0.0 are treated as 0.0 and values above 1.0 are treated as 1.0. */ + (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha { // TODO If Cappuccino is ported to a colorspace aware platform, this color should be in // sRGBColorSpace. return [self colorWithRed:red green:green blue:blue alpha:alpha]; } /*! 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("("), 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] ? parseFloat(parts[3], 10) : 1.0 ]; // We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might // use it (issue #1413.) [self _initCSSStringFromComponents]; return self; } /* @ignore */ - (id)_initWithRGBA:(CPArray)components { self = [super init]; if (self) { _components = components; [self _initCSSStringFromComponents]; } return self; } - (void)_initCSSStringFromComponents { 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]) : "") + ")"; } /* @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 the receiver. This method is a placeholder that does nothing but may be implemented in the future. */ - (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName { return self; } /*! Returns an array with the HSB values for this color. The values are expressed as fractions between 0.0-1.0. 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, gr = (max - green) / delta, 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 [ hue, saturation, brightness ]; } /*! Returns the hue component, the H in HSB, of the receiver. */ - (float)hueComponent { return [self hsbComponents][0]; } /*! Returns the saturation component, the S in HSB, of the receiver. */ - (float)saturationComponent { return [self hsbComponents][1]; } /*! Returns the brightness component, the B in HSB, of the receiver. */ - (float)brightnessComponent { return [self hsbComponents][2]; } /*! 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;
if (![aColor isKindOfClass:CPColor])
return NO;
if (_patternImage || [aColor patternImage])
return [_patternImage isEqual:[aColor patternImage]];
// We don't require the components to be equal beyond 8 bits since otherwise
// simple rounding errors will make two colours which are exactly the same on
// screen compare unequal.
return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
[self alphaComponent] == [aColor alphaComponent];
}
- (CPString)description
{
var description = [super description],
patternImage = [self patternImage];
if (!patternImage)
return description + " " + [self cssString];
description += " {\n";
if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
{
var slices = [patternImage imageSlices];
if ([patternImage isThreePartImage])
description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
description += " patternImage (" + slices.length + " part): [\n";
for (var i = 0; i < slices.length; ++i)
{
var imgDescription = [slices[i] description] || "nil";
description += imgDescription.replace(/^/mg, " ") + ",\n";
}
description = description.substr(0, description.length - 2) + "\n ]\n}";
}
else
description += ([patternImage description] || "nil").replace(/^/mg, " ") + "\n}";
return description;
}
@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
/// @cond IGNORE
var CPColorComponentsKey = @"CPColorComponentsKey",
CPColorPatternImageKey = @"CPColorPatternImageKey";
/// @endcond
@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
/// @cond IGNORE
var hexCharacters = "0123456789ABCDEF";
/*
Used for the CPColor +colorWithHexString: implementation.
Returns 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,
green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
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);
};
/*!
To create a simple color with a pattern image:
CPColorWithImages(name, width, height{, bundle})
To create a color with a three-part/nine-part image using a pattern string:
CPColorWithImages(pattern, attributes{, bundle})