Line data Source code
1 0 : /*
2 :
3 : OOColor.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "OOColor.h"
26 : #import "OOCollectionExtractors.h"
27 : #import "OOMaths.h"
28 :
29 :
30 : @implementation OOColor
31 :
32 : // Set methods are internal, because OOColor is immutable (as seen from outside).
33 0 : - (void) setRed:(float)r green:(float)g blue:(float)b alpha:(float)a
34 : {
35 : rgba[0] = r;
36 : rgba[1] = g;
37 : rgba[2] = b;
38 : rgba[3] = a;
39 : }
40 :
41 :
42 0 : - (void) setHue:(float)h saturation:(float)s brightness:(float)b alpha:(float)a
43 : {
44 : rgba[3] = a;
45 : if (s == 0.0f)
46 : {
47 : rgba[0] = rgba[1] = rgba[2] = b;
48 : return;
49 : }
50 : float f, p, q, t;
51 : int i;
52 : h = fmod(h, 360.0f);
53 : if (h < 0.0) h += 360.0f;
54 : h /= 60.0f;
55 :
56 : i = floor(h);
57 : f = h - i;
58 : p = b * (1.0f - s);
59 : q = b * (1.0f - (s * f));
60 : t = b * (1.0f - (s * (1.0f - f)));
61 :
62 : switch (i)
63 : {
64 : case 0:
65 : rgba[0] = b; rgba[1] = t; rgba[2] = p; break;
66 : case 1:
67 : rgba[0] = q; rgba[1] = b; rgba[2] = p; break;
68 : case 2:
69 : rgba[0] = p; rgba[1] = b; rgba[2] = t; break;
70 : case 3:
71 : rgba[0] = p; rgba[1] = q; rgba[2] = b; break;
72 : case 4:
73 : rgba[0] = t; rgba[1] = p; rgba[2] = b; break;
74 : case 5:
75 : rgba[0] = b; rgba[1] = p; rgba[2] = q; break;
76 : }
77 : }
78 :
79 :
80 0 : - (id) copyWithZone:(NSZone *)zone
81 : {
82 : // Copy is implemented as retain since OOColor is immutable.
83 : return [self retain];
84 : }
85 :
86 :
87 : + (OOColor *) colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
88 : {
89 : OOColor* result = [[OOColor alloc] init];
90 : [result setHue:360.0f * hue saturation:saturation brightness:brightness alpha:alpha];
91 : return [result autorelease];
92 : }
93 :
94 :
95 : + (OOColor *) colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
96 : {
97 : OOColor* result = [[OOColor alloc] init];
98 : [result setRed:red green:green blue:blue alpha:alpha];
99 : return [result autorelease];
100 : }
101 :
102 :
103 : + (OOColor *) colorWithWhite:(float)white alpha:(float)alpha
104 : {
105 : return [OOColor colorWithRed:white green:white blue:white alpha:alpha];
106 : }
107 :
108 :
109 : + (OOColor *) colorWithRGBAComponents:(OORGBAComponents)components
110 : {
111 : return [self colorWithRed:components.r
112 : green:components.g
113 : blue:components.b
114 : alpha:components.a];
115 : }
116 :
117 :
118 : + (OOColor *) colorWithHSBAComponents:(OOHSBAComponents)components
119 : {
120 : return [self colorWithHue:components.h / 360.0f
121 : saturation:components.s
122 : brightness:components.b
123 : alpha:components.a];
124 : }
125 :
126 :
127 : + (OOColor *) colorWithDescription:(id)description
128 : {
129 : return [self colorWithDescription:description saturationFactor:1.0f];
130 : }
131 :
132 :
133 : + (OOColor *) colorWithDescription:(id)description saturationFactor:(float)factor
134 : {
135 : NSDictionary *dict = nil;
136 : OOColor *result = nil;
137 :
138 : if (description == nil) return nil;
139 :
140 : if ([description isKindOfClass:[OOColor class]])
141 : {
142 : result = [[description copy] autorelease];
143 : }
144 : else if ([description isKindOfClass:[NSString class]])
145 : {
146 : if ([description hasSuffix:@"Color"])
147 : {
148 : // +fooColor selector
149 : SEL selector = NSSelectorFromString(description);
150 : if ([self respondsToSelector:selector]) result = [self performSelector:selector];
151 : }
152 : else
153 : {
154 : // Some other string
155 : result = [self colorFromString:description];
156 : }
157 : }
158 : else if ([description isKindOfClass:[NSArray class]])
159 : {
160 : result = [self colorFromString:[description componentsJoinedByString:@" "]];
161 : }
162 : else if ([description isKindOfClass:[NSDictionary class]])
163 : {
164 : dict = description; // Workaround for gnu-gcc's more agressive "multiple methods named..." warnings.
165 :
166 : if ([dict objectForKey:@"hue"] != nil)
167 : {
168 : // Treat as HSB(A) dictionary
169 : float h = [dict oo_floatForKey:@"hue"];
170 : float s = [dict oo_floatForKey:@"saturation" defaultValue:1.0f];
171 : float b = [dict oo_floatForKey:@"brightness" defaultValue:-1.0f];
172 : if (b < 0.0f) b = [dict oo_floatForKey:@"value" defaultValue:1.0f];
173 : float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f];
174 : if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f];
175 :
176 : // Not "result =", because we handle the saturation scaling here to allow oversaturation.
177 : return [OOColor colorWithHue:h / 360.0f saturation:s * factor brightness:b alpha:a];
178 : }
179 : else
180 : {
181 : // Treat as RGB(A) dictionary
182 : float r = [dict oo_floatForKey:@"red"];
183 : float g = [dict oo_floatForKey:@"green"];
184 : float b = [dict oo_floatForKey:@"blue"];
185 : float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f];
186 : if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f];
187 :
188 : result = [OOColor colorWithRed:r green:g blue:b alpha:a];
189 : }
190 : }
191 :
192 : if (factor != 1.0f && result != nil)
193 : {
194 : float h, s, b, a;
195 : [result getHue:&h saturation:&s brightness:&b alpha:&a];
196 : h *= 1.0 / 360.0f; // See note in header.
197 : s *= factor;
198 : result = [self colorWithHue:h saturation:s brightness:b alpha:a];
199 : }
200 :
201 : return result;
202 : }
203 :
204 :
205 : + (OOColor *) brightColorWithDescription:(id)description
206 : {
207 : OOColor *color = [OOColor colorWithDescription:description];
208 : if (color == nil || 0.5f <= [color brightnessComponent]) return color;
209 :
210 : return [OOColor colorWithHue:[color hueComponent] / 360.0f saturation:[color saturationComponent] brightness:0.5f alpha:1.0f];
211 : }
212 :
213 :
214 : + (OOColor *) colorFromString:(NSString*) colorFloatString
215 : {
216 : float rgbaValue[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
217 : NSScanner *scanner = [NSScanner scannerWithString:colorFloatString];
218 : float factor = 1.0f;
219 : int i;
220 :
221 : for (i = 0; i != 4; ++i)
222 : {
223 : if (![scanner scanFloat:&rgbaValue[i]])
224 : {
225 : // Less than three floats or non-float, can't parse -> quit
226 : if (i < 3) return nil;
227 :
228 : // If we get here, we only got three components. Make sure alpha is at correct scale:
229 : rgbaValue[3] /= factor;
230 : }
231 : if (1.0f < rgbaValue[i]) factor = 1.0f / 255.0f;
232 : }
233 :
234 : return [OOColor colorWithRed:rgbaValue[0] * factor green:rgbaValue[1] * factor blue:rgbaValue[2] * factor alpha:rgbaValue[3] * factor];
235 : }
236 :
237 :
238 : + (OOColor *) blackColor // 0.0 white
239 : {
240 : return [OOColor colorWithWhite:0.0f alpha:1.0f];
241 : }
242 :
243 :
244 : + (OOColor *) darkGrayColor // 0.333 white
245 : {
246 : return [OOColor colorWithWhite:1.0f/3.0f alpha:1.0f];
247 : }
248 :
249 :
250 : + (OOColor *) lightGrayColor // 0.667 white
251 : {
252 : return [OOColor colorWithWhite:2.0f/3.0f alpha:1.0f];
253 : }
254 :
255 :
256 : + (OOColor *) whiteColor // 1.0 white
257 : {
258 : return [OOColor colorWithWhite:1.0f alpha:1.0f];
259 : }
260 :
261 :
262 : + (OOColor *) grayColor // 0.5 white
263 : {
264 : return [OOColor colorWithWhite:0.5f alpha:1.0f];
265 : }
266 :
267 :
268 : + (OOColor *) redColor // 1.0, 0.0, 0.0 RGB
269 : {
270 : return [OOColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f];
271 : }
272 :
273 :
274 : + (OOColor *) greenColor // 0.0, 1.0, 0.0 RGB
275 : {
276 : return [OOColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f];
277 : }
278 :
279 :
280 : + (OOColor *) blueColor // 0.0, 0.0, 1.0 RGB
281 : {
282 : return [OOColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f];
283 : }
284 :
285 :
286 : + (OOColor *) cyanColor // 0.0, 1.0, 1.0 RGB
287 : {
288 : return [OOColor colorWithRed:0.0f green:1.0f blue:1.0f alpha:1.0f];
289 : }
290 :
291 :
292 : + (OOColor *) yellowColor // 1.0, 1.0, 0.0 RGB
293 : {
294 : return [OOColor colorWithRed:1.0f green:1.0f blue:0.0f alpha:1.0f];
295 : }
296 :
297 :
298 : + (OOColor *) magentaColor // 1.0, 0.0, 1.0 RGB
299 : {
300 : return [OOColor colorWithRed:1.0f green:0.0f blue:1.0f alpha:1.0f];
301 : }
302 :
303 :
304 : + (OOColor *) orangeColor // 1.0, 0.5, 0.0 RGB
305 : {
306 : return [OOColor colorWithRed:1.0f green:0.5f blue:0.0f alpha:1.0f];
307 : }
308 :
309 :
310 : + (OOColor *) purpleColor // 0.5, 0.0, 0.5 RGB
311 : {
312 : return [OOColor colorWithRed:0.5f green:0.0f blue:0.5f alpha:1.0f];
313 : }
314 :
315 :
316 : + (OOColor *)brownColor // 0.6, 0.4, 0.2 RGB
317 : {
318 : return [OOColor colorWithRed:0.6f green:0.4f blue:0.2f alpha:1.0f];
319 : }
320 :
321 :
322 : + (OOColor *) clearColor // 0.0 white, 0.0 alpha
323 : {
324 : return [OOColor colorWithWhite:0.0f alpha:0.0f];
325 : }
326 :
327 :
328 : - (OOColor *) blendedColorWithFraction:(float)fraction ofColor:(OOColor *)color
329 : {
330 : float rgba1[4];
331 : [color getRed:&rgba1[0] green:&rgba1[1] blue:&rgba1[2] alpha:&rgba1[3]];
332 :
333 : OOColor *result = [[OOColor alloc] init];
334 : [result setRed:OOLerp(rgba[0], rgba1[0], fraction)
335 : green:OOLerp(rgba[1], rgba1[1], fraction)
336 : blue:OOLerp(rgba[2], rgba1[2], fraction)
337 : alpha:OOLerp(rgba[3], rgba1[3], fraction)];
338 :
339 : return [result autorelease];
340 : }
341 :
342 :
343 0 : - (NSString *) descriptionComponents
344 : {
345 : return [NSString stringWithFormat:@"%g, %g, %g, %g", rgba[0], rgba[1], rgba[2], rgba[3]];
346 : }
347 :
348 :
349 : // Get the red, green, or blue components.
350 : - (float) redComponent
351 : {
352 : return rgba[0];
353 : }
354 :
355 :
356 : - (float) greenComponent
357 : {
358 : return rgba[1];
359 : }
360 :
361 :
362 : - (float) blueComponent
363 : {
364 : return rgba[2];
365 : }
366 :
367 :
368 : - (void) getRed:(float *)red green:(float *)green blue:(float *)blue alpha:(float *)alpha
369 : {
370 : NSParameterAssert(red != NULL && green != NULL && blue != NULL && alpha != NULL);
371 :
372 : *red = rgba[0];
373 : *green = rgba[1];
374 : *blue = rgba[2];
375 : *alpha = rgba[3];
376 : }
377 :
378 :
379 : - (OORGBAComponents) rgbaComponents
380 : {
381 : OORGBAComponents c = { rgba[0], rgba[1], rgba[2], rgba[3] };
382 : return c;
383 : }
384 :
385 :
386 : - (BOOL) isBlack
387 : {
388 : return rgba[0] == 0.0f && rgba[1] == 0.0f && rgba[2] == 0.0f;
389 : }
390 :
391 :
392 : - (BOOL) isWhite
393 : {
394 : return rgba[0] == 1.0f && rgba[1] == 1.0f && rgba[2] == 1.0f && rgba[3] == 1.0f;
395 : }
396 :
397 :
398 : // Get the components as hue, saturation, or brightness.
399 : - (float) hueComponent
400 : {
401 : float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
402 : float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]);
403 : float delta = maxrgb - minrgb + 0.0001f;
404 : float fRed = rgba[0], fGreen = rgba[1], fBlue = rgba[2];
405 : float hue = 0.0f;
406 : if (maxrgb == fRed && fGreen >= fBlue)
407 : {
408 : hue = 60.0f * (fGreen - fBlue) / delta;
409 : }
410 : else if (maxrgb == fRed && fGreen < fBlue)
411 : {
412 : hue = 60.0f * (fGreen - fBlue) / delta + 360.0f;
413 : }
414 : else if (maxrgb == fGreen)
415 : {
416 : hue = 60.0f * (fBlue - fRed) / delta + 120.0f;
417 : }
418 : else if (maxrgb == fBlue)
419 : {
420 : hue = 60.0f * (fRed - fGreen) / delta + 240.0f;
421 : }
422 : return hue;
423 : }
424 :
425 : - (float) saturationComponent
426 : {
427 : float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
428 : float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]);
429 : return maxrgb == 0.0f ? 0.0f : (1.0f - (minrgb / maxrgb));
430 : }
431 :
432 : - (float) brightnessComponent
433 : {
434 : float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]);
435 : return maxrgb;
436 : }
437 :
438 : - (void) getHue:(float *)hue saturation:(float *)saturation brightness:(float *)brightness alpha:(float *)alpha
439 : {
440 : NSParameterAssert(hue != NULL && saturation != NULL && brightness != NULL && alpha != NULL);
441 :
442 : *alpha = rgba[3];
443 :
444 : float fRed = rgba[0], fGreen = rgba[1], fBlue = rgba[2];
445 : float maxrgb = fmax(fRed, fmax(fGreen, fBlue));
446 : float minrgb = fmin(fRed, fmin(fGreen, fBlue));
447 : float delta = maxrgb - minrgb + 0.0001f;
448 : float h = 0.0f;
449 : if (maxrgb == fRed && fGreen >= fBlue)
450 : {
451 : h = 60.0f * (fGreen - fBlue) / delta;
452 : }
453 : else if (maxrgb == fRed && fGreen < fBlue)
454 : {
455 : h = 60.0f * (fGreen - fBlue) / delta + 360.0f;
456 : }
457 : else if (maxrgb == fGreen)
458 : {
459 : h = 60.0f * (fBlue - fRed) / delta + 120.0f;
460 : }
461 : else if (maxrgb == fBlue)
462 : {
463 : h = 60.0f * (fRed - fGreen) / delta + 240.0f;
464 : }
465 :
466 : float s = (maxrgb == 0.0f) ? 0.0f : (1.0f - (minrgb / maxrgb));
467 :
468 : *hue = h;
469 : *saturation = s;
470 : *brightness = maxrgb;
471 : }
472 :
473 :
474 : - (OOHSBAComponents) hsbaComponents
475 : {
476 : OOHSBAComponents c;
477 : [self getHue:&c.h
478 : saturation:&c.s
479 : brightness:&c.b
480 : alpha:&c.a];
481 : return c;
482 : }
483 :
484 :
485 : // Get the alpha component.
486 : - (float) alphaComponent
487 : {
488 : return rgba[3];
489 : }
490 :
491 :
492 : - (OOColor *) premultipliedColor
493 : {
494 : if (rgba[3] == 1.0f) return [[self retain] autorelease];
495 : return [OOColor colorWithRed:rgba[0] * rgba[3]
496 : green:rgba[1] * rgba[3]
497 : blue:rgba[2] * rgba[3]
498 : alpha:1.0f];
499 : }
500 :
501 :
502 : - (OOColor *) colorWithBrightnessFactor:(float)factor
503 : {
504 : return [OOColor colorWithRed:OOClamp_0_1_f(rgba[0] * factor)
505 : green:OOClamp_0_1_f(rgba[1] * factor)
506 : blue:OOClamp_0_1_f(rgba[2] * factor)
507 : alpha:rgba[3]];
508 : }
509 :
510 :
511 : - (NSArray *) normalizedArray
512 : {
513 : float r, g, b, a;
514 : [self getRed:&r green:&g blue:&b alpha:&a];
515 : return [NSArray arrayWithObjects:
516 : [NSNumber numberWithFloat:r],
517 : [NSNumber numberWithFloat:g],
518 : [NSNumber numberWithFloat:b],
519 : [NSNumber numberWithFloat:a],
520 : nil];
521 : }
522 :
523 :
524 : - (NSString *) rgbaDescription
525 : {
526 : return OORGBAComponentsDescription([self rgbaComponents]);
527 : }
528 :
529 :
530 : - (NSString *) hsbaDescription
531 : {
532 : return OOHSBAComponentsDescription([self hsbaComponents]);
533 : }
534 :
535 : @end
536 :
537 :
538 0 : NSString *OORGBAComponentsDescription(OORGBAComponents components)
539 : {
540 : return [NSString stringWithFormat:@"{%.3g, %.3g, %.3g, %.3g}", components.r, components.g, components.b, components.a];
541 : }
542 :
543 :
544 0 : NSString *OOHSBAComponentsDescription(OOHSBAComponents components)
545 : {
546 : return [NSString stringWithFormat:@"{%i, %.3g, %.3g, %.3g}", (int)components.h, components.s, components.b, components.a];
547 : }
|