oolite/src/Core/OOColor.m

548 lines
13 KiB
Objective-C

/*
OOColor.m
Oolite
Copyright (C) 2004-2013 Giles C Williams and contributors
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
*/
#import "OOColor.h"
#import "OOCollectionExtractors.h"
#import "OOMaths.h"
@implementation OOColor
// Set methods are internal, because OOColor is immutable (as seen from outside).
- (void) setRed:(float)r green:(float)g blue:(float)b alpha:(float)a
{
rgba[0] = r;
rgba[1] = g;
rgba[2] = b;
rgba[3] = a;
}
- (void) setHue:(float)h saturation:(float)s brightness:(float)b alpha:(float)a
{
rgba[3] = a;
if (s == 0.0f)
{
rgba[0] = rgba[1] = rgba[2] = b;
return;
}
float f, p, q, t;
int i;
h = fmod(h, 360.0f);
if (h < 0.0) h += 360.0f;
h /= 60.0f;
i = floor(h);
f = h - i;
p = b * (1.0f - s);
q = b * (1.0f - (s * f));
t = b * (1.0f - (s * (1.0f - f)));
switch (i)
{
case 0:
rgba[0] = b; rgba[1] = t; rgba[2] = p; break;
case 1:
rgba[0] = q; rgba[1] = b; rgba[2] = p; break;
case 2:
rgba[0] = p; rgba[1] = b; rgba[2] = t; break;
case 3:
rgba[0] = p; rgba[1] = q; rgba[2] = b; break;
case 4:
rgba[0] = t; rgba[1] = p; rgba[2] = b; break;
case 5:
rgba[0] = b; rgba[1] = p; rgba[2] = q; break;
}
}
- (id) copyWithZone:(NSZone *)zone
{
// Copy is implemented as retain since OOColor is immutable.
return [self retain];
}
+ (OOColor *) colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
{
OOColor* result = [[OOColor alloc] init];
[result setHue:360.0f * hue saturation:saturation brightness:brightness alpha:alpha];
return [result autorelease];
}
+ (OOColor *) colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
{
OOColor* result = [[OOColor alloc] init];
[result setRed:red green:green blue:blue alpha:alpha];
return [result autorelease];
}
+ (OOColor *) colorWithWhite:(float)white alpha:(float)alpha
{
return [OOColor colorWithRed:white green:white blue:white alpha:alpha];
}
+ (OOColor *) colorWithRGBAComponents:(OORGBAComponents)components
{
return [self colorWithRed:components.r
green:components.g
blue:components.b
alpha:components.a];
}
+ (OOColor *) colorWithHSBAComponents:(OOHSBAComponents)components
{
return [self colorWithHue:components.h / 360.0f
saturation:components.s
brightness:components.b
alpha:components.a];
}
+ (OOColor *) colorWithDescription:(id)description
{
return [self colorWithDescription:description saturationFactor:1.0f];
}
+ (OOColor *) colorWithDescription:(id)description saturationFactor:(float)factor
{
NSDictionary *dict = nil;
OOColor *result = nil;
if (description == nil) return nil;
if ([description isKindOfClass:[OOColor class]])
{
result = [[description copy] autorelease];
}
else if ([description isKindOfClass:[NSString class]])
{
if ([description hasSuffix:@"Color"])
{
// +fooColor selector
SEL selector = NSSelectorFromString(description);
if ([self respondsToSelector:selector]) result = [self performSelector:selector];
}
else
{
// Some other string
result = [self colorFromString:description];
}
}
else if ([description isKindOfClass:[NSArray class]])
{
result = [self colorFromString:[description componentsJoinedByString:@" "]];
}
else if ([description isKindOfClass:[NSDictionary class]])
{
dict = description; // Workaround for gnu-gcc's more agressive "multiple methods named..." warnings.
if ([dict objectForKey:@"hue"] != nil)
{
// Treat as HSB(A) dictionary
float h = [dict oo_floatForKey:@"hue"];
float s = [dict oo_floatForKey:@"saturation" defaultValue:1.0f];
float b = [dict oo_floatForKey:@"brightness" defaultValue:-1.0f];
if (b < 0.0f) b = [dict oo_floatForKey:@"value" defaultValue:1.0f];
float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f];
if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f];
// Not "result =", because we handle the saturation scaling here to allow oversaturation.
return [OOColor colorWithHue:h / 360.0f saturation:s * factor brightness:b alpha:a];
}
else
{
// Treat as RGB(A) dictionary
float r = [dict oo_floatForKey:@"red"];
float g = [dict oo_floatForKey:@"green"];
float b = [dict oo_floatForKey:@"blue"];
float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f];
if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f];
result = [OOColor colorWithRed:r green:g blue:b alpha:a];
}
}
if (factor != 1.0f && result != nil)
{
float h, s, b, a;
[result getHue:&h saturation:&s brightness:&b alpha:&a];
h *= 1.0 / 360.0f; // See note in header.
s *= factor;
result = [self colorWithHue:h saturation:s brightness:b alpha:a];
}
return result;
}
+ (OOColor *) brightColorWithDescription:(id)description
{
OOColor *color = [OOColor colorWithDescription:description];
if (color == nil || 0.5f <= [color brightnessComponent]) return color;
return [OOColor colorWithHue:[color hueComponent] / 360.0f saturation:[color saturationComponent] brightness:0.5f alpha:1.0f];
}
+ (OOColor *) colorFromString:(NSString*) colorFloatString
{
float rgbaValue[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
NSScanner *scanner = [NSScanner scannerWithString:colorFloatString];
float factor = 1.0f;
int i;
for (i = 0; i != 4; ++i)
{
if (![scanner scanFloat:&rgbaValue[i]])
{
// Less than three floats or non-float, can't parse -> quit
if (i < 3) return nil;
// If we get here, we only got three components. Make sure alpha is at correct scale:
rgbaValue[3] /= factor;
}
if (1.0f < rgbaValue[i]) factor = 1.0f / 255.0f;
}
return [OOColor colorWithRed:rgbaValue[0] * factor green:rgbaValue[1] * factor blue:rgbaValue[2] * factor alpha:rgbaValue[3] * factor];
}
+ (OOColor *) blackColor // 0.0 white
{
return [OOColor colorWithWhite:0.0f alpha:1.0f];
}
+ (OOColor *) darkGrayColor // 0.333 white
{
return [OOColor colorWithWhite:1.0f/3.0f alpha:1.0f];
}
+ (OOColor *) lightGrayColor // 0.667 white
{
return [OOColor colorWithWhite:2.0f/3.0f alpha:1.0f];
}
+ (OOColor *) whiteColor // 1.0 white
{
return [OOColor colorWithWhite:1.0f alpha:1.0f];
}
+ (OOColor *) grayColor // 0.5 white
{
return [OOColor colorWithWhite:0.5f alpha:1.0f];
}
+ (OOColor *) redColor // 1.0, 0.0, 0.0 RGB
{
return [OOColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f];
}
+ (OOColor *) greenColor // 0.0, 1.0, 0.0 RGB
{
return [OOColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f];
}
+ (OOColor *) blueColor // 0.0, 0.0, 1.0 RGB
{
return [OOColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f];
}
+ (OOColor *) cyanColor // 0.0, 1.0, 1.0 RGB
{
return [OOColor colorWithRed:0.0f green:1.0f blue:1.0f alpha:1.0f];
}
+ (OOColor *) yellowColor // 1.0, 1.0, 0.0 RGB
{
return [OOColor colorWithRed:1.0f green:1.0f blue:0.0f alpha:1.0f];
}
+ (OOColor *) magentaColor // 1.0, 0.0, 1.0 RGB
{
return [OOColor colorWithRed:1.0f green:0.0f blue:1.0f alpha:1.0f];
}
+ (OOColor *) orangeColor // 1.0, 0.5, 0.0 RGB
{
return [OOColor colorWithRed:1.0f green:0.5f blue:0.0f alpha:1.0f];
}
+ (OOColor *) purpleColor // 0.5, 0.0, 0.5 RGB
{
return [OOColor colorWithRed:0.5f green:0.0f blue:0.5f alpha:1.0f];
}
+ (OOColor *)brownColor // 0.6, 0.4, 0.2 RGB
{
return [OOColor colorWithRed:0.6f green:0.4f blue:0.2f alpha:1.0f];
}
+ (OOColor *) clearColor // 0.0 white, 0.0 alpha
{
return [OOColor colorWithWhite:0.0f alpha:0.0f];
}
- (OOColor *) blendedColorWithFraction:(float)fraction ofColor:(OOColor *)color
{
float rgba1[4];
[color getRed:&rgba1[0] green:&rgba1[1] blue:&rgba1[2] alpha:&rgba1[3]];
OOColor *result = [[OOColor alloc] init];
[result setRed:OOLerp(rgba[0], rgba1[0], fraction)
green:OOLerp(rgba[1], rgba1[1], fraction)
blue:OOLerp(rgba[2], rgba1[2], fraction)
alpha:OOLerp(rgba[3], rgba1[3], fraction)];
return [result autorelease];
}
- (NSString *) descriptionComponents
{
return [NSString stringWithFormat:@"%g, %g, %g, %g", rgba[0], rgba[1], rgba[2], rgba[3]];
}
// Get the red, green, or blue components.
- (float) redComponent
{
return rgba[0];
}
- (float) greenComponent
{
return rgba[1];
}
- (float) blueComponent
{
return rgba[2];
}
- (void) getRed:(float *)red green:(float *)green blue:(float *)blue alpha:(float *)alpha
{
NSParameterAssert(red != NULL && green != NULL && blue != NULL && alpha != NULL);
*red = rgba[0];
*green = rgba[1];
*blue = rgba[2];
*alpha = rgba[3];
}
- (OORGBAComponents) rgbaComponents
{
OORGBAComponents c = { rgba[0], rgba[1], rgba[2], rgba[3] };
return c;
}
- (BOOL) isBlack
{
return rgba[0] == 0.0f && rgba[1] == 0.0f && rgba[2] == 0.0f;
}
- (BOOL) isWhite
{
return rgba[0] == 1.0f && rgba[1] == 1.0f && rgba[2] == 1.0f && rgba[3] == 1.0f;
}
// Get the components as hue, saturation, or brightness.
- (float) hueComponent
{
float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]);
float delta = maxrgb - minrgb;
float fRed = rgba[0], fGreen = rgba[1], fBlue = rgba[2];
float hue = 0.0f;
if (maxrgb == fRed && fGreen >= fBlue)
{
hue = 60.0f * (fGreen - fBlue) / delta;
}
else if (maxrgb == fRed && fGreen < fBlue)
{
hue = 60.0f * (fGreen - fBlue) / delta + 360.0f;
}
else if (maxrgb == fGreen)
{
hue = 60.0f * (fBlue - fRed) / delta + 120.0f;
}
else if (maxrgb == fBlue)
{
hue = 60.0f * (fRed - fGreen) / delta + 240.0f;
}
return hue;
}
- (float) saturationComponent
{
float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]);
return maxrgb == 0.0f ? 0.0f : (1.0f - (minrgb / maxrgb));
}
- (float) brightnessComponent
{
float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
return maxrgb;
}
- (void) getHue:(float *)hue saturation:(float *)saturation brightness:(float *)brightness alpha:(float *)alpha
{
NSParameterAssert(hue != NULL && saturation != NULL && brightness != NULL && alpha != NULL);
*alpha = rgba[3];
float fRed = rgba[0], fGreen = rgba[1], fBlue = rgba[2];
float maxrgb = fmax(fRed, fmax(fGreen, fBlue));
float minrgb = fmin(fRed, fmin(fGreen, fBlue));
float delta = maxrgb - minrgb;
float h = 0.0f;
if (maxrgb == fRed && fGreen >= fBlue)
{
h = 60.0f * (fGreen - fBlue) / delta;
}
else if (maxrgb == fRed && fGreen < fBlue)
{
h = 60.0f * (fGreen - fBlue) / delta + 360.0f;
}
else if (maxrgb == fGreen)
{
h = 60.0f * (fBlue - fRed) / delta + 120.0f;
}
else if (maxrgb == fBlue)
{
h = 60.0f * (fRed - fGreen) / delta + 240.0f;
}
float s = (maxrgb == 0.0f) ? 0.0f : (1.0f - (minrgb / maxrgb));
*hue = h;
*saturation = s;
*brightness = maxrgb;
}
- (OOHSBAComponents) hsbaComponents
{
OOHSBAComponents c;
[self getHue:&c.h
saturation:&c.s
brightness:&c.b
alpha:&c.a];
return c;
}
// Get the alpha component.
- (float) alphaComponent
{
return rgba[3];
}
- (OOColor *) premultipliedColor
{
if (rgba[3] == 1.0f) return [[self retain] autorelease];
return [OOColor colorWithRed:rgba[0] * rgba[3]
green:rgba[1] * rgba[3]
blue:rgba[2] * rgba[3]
alpha:1.0f];
}
- (OOColor *) colorWithBrightnessFactor:(float)factor
{
return [OOColor colorWithRed:OOClamp_0_1_f(rgba[0] * factor)
green:OOClamp_0_1_f(rgba[1] * factor)
blue:OOClamp_0_1_f(rgba[2] * factor)
alpha:rgba[3]];
}
- (NSArray *) normalizedArray
{
float r, g, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:r],
[NSNumber numberWithFloat:g],
[NSNumber numberWithFloat:b],
[NSNumber numberWithFloat:a],
nil];
}
- (NSString *) rgbaDescription
{
return OORGBAComponentsDescription([self rgbaComponents]);
}
- (NSString *) hsbaDescription
{
return OOHSBAComponentsDescription([self hsbaComponents]);
}
@end
NSString *OORGBAComponentsDescription(OORGBAComponents components)
{
return [NSString stringWithFormat:@"{%.3g, %.3g, %.3g, %.3g}", components.r, components.g, components.b, components.a];
}
NSString *OOHSBAComponentsDescription(OOHSBAComponents components)
{
return [NSString stringWithFormat:@"{%i, %.3g, %.3g, %.3g}", (int)components.h, components.s, components.b, components.a];
}