@@ -1,7 +1,7 @@ /* - * Copyright (c) 2008-2022 Jonathan Schleifer + * Copyright (c) 2008-2024 Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in @@ -12,27 +12,95 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" + +#include #import "OFColor.h" +#import "OFConcreteColor.h" #import "OFOnce.h" +#import "OFString.h" +#import "OFTaggedPointerColor.h" + +@interface OFPlaceholderColor: OFColor +@end + +@interface OFConcreteColorSingleton: OFConcreteColor +@end + +static struct { + Class isa; +} placeholder; + +#ifdef OF_OBJFW_RUNTIME +static const float allowedImprecision = 0.0000001; +#endif + +@implementation OFPlaceholderColor +- (instancetype)initWithRed: (float)red + green: (float)green + blue: (float)blue + alpha: (float)alpha +{ +#ifdef OF_OBJFW_RUNTIME + uint8_t redInt = nearbyintf(red * 255); + uint8_t greenInt = nearbyintf(green * 255); + uint8_t blueInt = nearbyintf(blue * 255); + + if (fabsf(red * 255 - redInt) < allowedImprecision && + fabsf(green * 255 - greenInt) < allowedImprecision && + fabsf(blue * 255 - blueInt) < allowedImprecision && alpha == 1) { + id ret = [OFTaggedPointerColor colorWithRed: redInt + green: greenInt + blue: blueInt]; + + if (ret != nil) + return ret; + } +#endif + + return (id)[[OFConcreteColor alloc] initWithRed: red + green: green + blue: blue + alpha: alpha]; +} + +OF_SINGLETON_METHODS +@end -#import "OFInvalidArgumentException.h" +@implementation OFConcreteColorSingleton +OF_SINGLETON_METHODS +@end @implementation OFColor ++ (void)initialize +{ + if (self == [OFColor class]) + object_setClass((id)&placeholder, [OFPlaceholderColor class]); +} + ++ (instancetype)alloc +{ + if (self == [OFColor class]) + return (id)&placeholder; + + return [super alloc]; +} + #define PREDEFINED_COLOR(name, redValue, greenValue, blueValue) \ static OFColor *name##Color = nil; \ \ static void \ initPredefinedColor_##name(void) \ { \ - name##Color = [[OFColor alloc] initWithRed: redValue \ - green: greenValue \ - blue: blueValue \ - alpha: 1]; \ + name##Color = [[OFConcreteColorSingleton alloc] \ + initWithRed: redValue \ + green: greenValue \ + blue: blueValue \ + alpha: 1]; \ } \ \ + (OFColor *)name \ { \ static OFOnceControl onceControl = OFOnceControlInitValue; \ @@ -72,75 +140,78 @@ - (instancetype)initWithRed: (float)red green: (float)green blue: (float)blue alpha: (float)alpha { - self = [super init]; - - @try { - if (red < 0.0 || red > 1.0 || - green < 0.0 || green > 1.0 || - blue < 0.0 || blue > 1.0 || - alpha < 0.0 || alpha > 1.0) - @throw [OFInvalidArgumentException exception]; - - _red = red; - _green = green; - _blue = blue; - _alpha = alpha; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; + if ([self isMemberOfClass: [OFColor class]]) { + @try { + [self doesNotRecognizeSelector: _cmd]; + } @catch (id e) { + [self release]; + @throw e; + } + + abort(); + } + + return [super init]; } - (bool)isEqual: (id)object { OFColor *other; + float red, green, blue, alpha; + float otherRed, otherGreen, otherBlue, otherAlpha; if (object == self) return true; if (![object isKindOfClass: [OFColor class]]) return false; other = object; - - if (other->_red != _red) - return false; - if (other->_green != _green) - return false; - if (other->_blue != _blue) - return false; - if (other->_alpha != _alpha) + [self getRed: &red green: &green blue: &blue alpha: &alpha]; + [other getRed: &otherRed + green: &otherGreen + blue: &otherBlue + alpha: &otherAlpha]; + + if (otherRed != red) + return false; + if (otherGreen != green) + return false; + if (otherBlue != blue) + return false; + if (otherAlpha != alpha) return false; return true; } - (unsigned long)hash { + float red, green, blue, alpha; unsigned long hash; float tmp; + [self getRed: &red green: &green blue: &blue alpha: &alpha]; + OFHashInit(&hash); - tmp = OFToLittleEndianFloat(_red); + tmp = OFToLittleEndianFloat(red); + for (uint_fast8_t i = 0; i < sizeof(float); i++) + OFHashAddByte(&hash, ((char *)&tmp)[i]); + + tmp = OFToLittleEndianFloat(green); for (uint_fast8_t i = 0; i < sizeof(float); i++) OFHashAddByte(&hash, ((char *)&tmp)[i]); - tmp = OFToLittleEndianFloat(_green); + tmp = OFToLittleEndianFloat(blue); for (uint_fast8_t i = 0; i < sizeof(float); i++) OFHashAddByte(&hash, ((char *)&tmp)[i]); - tmp = OFToLittleEndianFloat(_blue); - for (uint_fast8_t i = 0; i < sizeof(float); i++) - OFHashAddByte(&hash, ((char *)&tmp)[i]); - - tmp = OFToLittleEndianFloat(_alpha); + tmp = OFToLittleEndianFloat(alpha); for (uint_fast8_t i = 0; i < sizeof(float); i++) OFHashAddByte(&hash, ((char *)&tmp)[i]); OFHashFinalize(&hash); @@ -150,13 +221,19 @@ - (void)getRed: (float *)red green: (float *)green blue: (float *)blue alpha: (float *)alpha { - *red = _red; - *green = _green; - *blue = _blue; + OF_UNRECOGNIZED_SELECTOR +} + +- (OFString *)description +{ + float red, green, blue, alpha; + + [self getRed: &red green: &green blue: &blue alpha: &alpha]; - if (alpha != NULL) - *alpha = _alpha; + return [OFString stringWithFormat: + @"<%@ red=%f green=%f blue=%f alpha=%f>", + self.class, red, green, blue, alpha]; } @end