ColorUtils.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // ColorUtils.m
  3. //
  4. // Version 1.1.3
  5. //
  6. // Created by Nick Lockwood on 19/11/2011.
  7. // Copyright (c) 2011 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib License
  10. // Get the latest version from here:
  11. //
  12. // https://github.com/nicklockwood/ColorUtils
  13. //
  14. // This software is provided 'as-is', without any express or implied
  15. // warranty. In no event will the authors be held liable for any damages
  16. // arising from the use of this software.
  17. //
  18. // Permission is granted to anyone to use this software for any purpose,
  19. // including commercial applications, and to alter it and redistribute it
  20. // freely, subject to the following restrictions:
  21. //
  22. // 1. The origin of this software must not be misrepresented; you must not
  23. // claim that you wrote the original software. If you use this software
  24. // in a product, an acknowledgment in the product documentation would be
  25. // appreciated but is not required.
  26. //
  27. // 2. Altered source versions must be plainly marked as such, and must not be
  28. // misrepresented as being the original software.
  29. //
  30. // 3. This notice may not be removed or altered from any source distribution.
  31. //
  32. #import "ColorUtils.h"
  33. #pragma GCC diagnostic ignored "-Wformat-non-iso"
  34. #pragma GCC diagnostic ignored "-Wconversion"
  35. #pragma GCC diagnostic ignored "-Wgnu"
  36. #import <Availability.h>
  37. #if !__has_feature(objc_arc)
  38. #error This class requires automatic reference counting
  39. #endif
  40. @implementation UIColor (ColorUtils)
  41. + (dispatch_queue_t)sharedDispatchQueue
  42. {
  43. static dispatch_queue_t queue;
  44. static dispatch_once_t onceToken;
  45. dispatch_once(&onceToken, ^{
  46. queue = dispatch_queue_create("com.charcoaldesign.ColorUtils", DISPATCH_QUEUE_SERIAL);
  47. });
  48. return queue;
  49. }
  50. + (NSMutableDictionary *)standardColors
  51. {
  52. static NSMutableDictionary *colors = nil;
  53. static dispatch_once_t onceToken;
  54. dispatch_once(&onceToken, ^{
  55. colors = [@{@"black": [self blackColor], // 0.0 white
  56. @"darkgray": [self darkGrayColor], // 0.333 white
  57. @"lightgray": [self lightGrayColor], // 0.667 white
  58. @"white": [self whiteColor], // 1.0 white
  59. @"gray": [self grayColor], // 0.5 white
  60. @"red": [self redColor], // 1.0, 0.0, 0.0 RGB
  61. @"green": [self greenColor], // 0.0, 1.0, 0.0 RGB
  62. @"blue": [self blueColor], // 0.0, 0.0, 1.0 RGB
  63. @"cyan": [self cyanColor], // 0.0, 1.0, 1.0 RGB
  64. @"yellow": [self yellowColor], // 1.0, 1.0, 0.0 RGB
  65. @"magenta": [self magentaColor], // 1.0, 0.0, 1.0 RGB
  66. @"orange": [self orangeColor], // 1.0, 0.5, 0.0 RGB
  67. @"purple": [self purpleColor], // 0.5, 0.0, 0.5 RGB
  68. @"brown": [self brownColor], // 0.6, 0.4, 0.2 RGB
  69. @"clear": [self clearColor]} mutableCopy];
  70. });
  71. return colors;
  72. }
  73. - (void)getRGBAComponents:(CGFloat[4])rgba
  74. {
  75. CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
  76. const CGFloat *components = CGColorGetComponents(self.CGColor);
  77. switch (model)
  78. {
  79. case kCGColorSpaceModelMonochrome:
  80. {
  81. rgba[0] = components[0];
  82. rgba[1] = components[0];
  83. rgba[2] = components[0];
  84. rgba[3] = components[1];
  85. break;
  86. }
  87. case kCGColorSpaceModelRGB:
  88. {
  89. rgba[0] = components[0];
  90. rgba[1] = components[1];
  91. rgba[2] = components[2];
  92. rgba[3] = components[3];
  93. break;
  94. }
  95. case kCGColorSpaceModelCMYK:
  96. case kCGColorSpaceModelDeviceN:
  97. case kCGColorSpaceModelIndexed:
  98. case kCGColorSpaceModelLab:
  99. case kCGColorSpaceModelPattern:
  100. case kCGColorSpaceModelUnknown:
  101. {
  102. #ifdef DEBUG
  103. //unsupported format
  104. NSLog(@"Unsupported color model: %i", model);
  105. #endif
  106. rgba[0] = 0.0f;
  107. rgba[1] = 0.0f;
  108. rgba[2] = 0.0f;
  109. rgba[3] = 1.0f;
  110. break;
  111. }
  112. }
  113. }
  114. + (void)registerColor:(UIColor *)color forName:(NSString *)name
  115. {
  116. name = [name lowercaseString];
  117. dispatch_sync([self sharedDispatchQueue], ^{
  118. #ifdef DEBUG
  119. //don't allow re-registration
  120. NSAssert([self standardColors][name] == nil || [[self standardColors][name] isEquivalentToColor:color],
  121. @"Cannot re-register the color '%@' as this is already assigned", name);
  122. #endif
  123. [self standardColors][[name lowercaseString]] = color;
  124. });
  125. }
  126. + (instancetype)colorWithString:(NSString *)string
  127. {
  128. //convert to lowercase
  129. string = [string lowercaseString];
  130. //try standard colors first
  131. __block UIColor *color = nil;
  132. dispatch_sync([self sharedDispatchQueue], ^{
  133. color = [self standardColors][string];
  134. });
  135. if (color)
  136. {
  137. return color;
  138. }
  139. //create new instance
  140. return [[self alloc] initWithString:string useLookup:NO];
  141. }
  142. + (instancetype)colorWithRGBValue:(uint32_t)rgb
  143. {
  144. return [[self alloc] initWithRGBValue:rgb];
  145. }
  146. + (instancetype)colorWithRGBAValue:(uint32_t)rgba
  147. {
  148. return [[self alloc] initWithRGBAValue:rgba];
  149. }
  150. - (instancetype)initWithString:(NSString *)string
  151. {
  152. return [self initWithString:string useLookup:YES];
  153. }
  154. - (instancetype)initWithString:(NSString *)string useLookup:(BOOL)lookup
  155. {
  156. //convert to lowercase
  157. string = [string lowercaseString];
  158. if (lookup)
  159. {
  160. //try standard colors
  161. __block UIColor *color = nil;
  162. dispatch_sync([[self class] sharedDispatchQueue], ^{
  163. color = [[self class] standardColors][string];
  164. });
  165. if (color)
  166. {
  167. return ((self = color));
  168. }
  169. }
  170. //try hex
  171. string = [string stringByReplacingOccurrencesOfString:@"#" withString:@""];
  172. string = [string stringByReplacingOccurrencesOfString:@"0x" withString:@""];
  173. switch ([string length])
  174. {
  175. case 0:
  176. {
  177. string = @"00000000";
  178. break;
  179. }
  180. case 3:
  181. {
  182. NSString *red = [string substringWithRange:NSMakeRange(0, 1)];
  183. NSString *green = [string substringWithRange:NSMakeRange(1, 1)];
  184. NSString *blue = [string substringWithRange:NSMakeRange(2, 1)];
  185. string = [NSString stringWithFormat:@"%1$@%1$@%2$@%2$@%3$@%3$@ff", red, green, blue];
  186. break;
  187. }
  188. case 6:
  189. {
  190. string = [string stringByAppendingString:@"ff"];
  191. break;
  192. }
  193. case 8:
  194. {
  195. //do nothing
  196. break;
  197. }
  198. default:
  199. {
  200. #ifdef DEBUG
  201. //unsupported format
  202. NSLog(@"Unsupported color string format: %@", string);
  203. #endif
  204. return nil;
  205. }
  206. }
  207. uint32_t rgba;
  208. NSScanner *scanner = [NSScanner scannerWithString:string];
  209. [scanner scanHexInt:&rgba];
  210. return [self initWithRGBAValue:rgba];
  211. }
  212. - (instancetype)initWithRGBValue:(uint32_t)rgb
  213. {
  214. CGFloat red = ((rgb & 0xFF0000) >> 16) / 255.0f;
  215. CGFloat green = ((rgb & 0x00FF00) >> 8) / 255.0f;
  216. CGFloat blue = (rgb & 0x0000FF) / 255.0f;
  217. return [self initWithRed:red green:green blue:blue alpha:1.0f];
  218. }
  219. - (instancetype)initWithRGBAValue:(uint32_t)rgba
  220. {
  221. CGFloat red = ((rgba & 0xFF000000) >> 24) / 255.0f;
  222. CGFloat green = ((rgba & 0x00FF0000) >> 16) / 255.0f;
  223. CGFloat blue = ((rgba & 0x0000FF00) >> 8) / 255.0f;
  224. CGFloat alpha = (rgba & 0x000000FF) / 255.0f;
  225. return [self initWithRed:red green:green blue:blue alpha:alpha];
  226. }
  227. - (uint32_t)RGBValue
  228. {
  229. CGFloat rgba[4];
  230. [self getRGBAComponents:rgba];
  231. uint32_t red = rgba[0]*255;
  232. uint32_t green = rgba[1]*255;
  233. uint32_t blue = rgba[2]*255;
  234. return (red << 16) + (green << 8) + blue;
  235. }
  236. - (uint32_t)RGBAValue
  237. {
  238. CGFloat rgba[4];
  239. [self getRGBAComponents:rgba];
  240. uint8_t red = rgba[0]*255;
  241. uint8_t green = rgba[1]*255;
  242. uint8_t blue = rgba[2]*255;
  243. uint8_t alpha = rgba[3]*255;
  244. return (red << 24) + (green << 16) + (blue << 8) + alpha;
  245. }
  246. - (NSString *)stringValue
  247. {
  248. //try standard colors
  249. NSUInteger index = [[[[self class] standardColors] allValues] indexOfObject:self];
  250. if (index != NSNotFound)
  251. {
  252. return [[[self class] standardColors] allKeys][index];
  253. }
  254. //convert to hex
  255. if (self.alpha < 1.0f)
  256. {
  257. //include alpha component
  258. return [NSString stringWithFormat:@"#%.8x", self.RGBAValue];
  259. }
  260. else
  261. {
  262. //don't include alpha component
  263. return [NSString stringWithFormat:@"#%.6x", self.RGBValue];
  264. }
  265. }
  266. - (CGFloat)red
  267. {
  268. CGFloat rgba[4];
  269. [self getRGBAComponents:rgba];
  270. return rgba[0];
  271. }
  272. - (CGFloat)green
  273. {
  274. CGFloat rgba[4];
  275. [self getRGBAComponents:rgba];
  276. return rgba[1];
  277. }
  278. - (CGFloat)blue
  279. {
  280. CGFloat rgba[4];
  281. [self getRGBAComponents:rgba];
  282. return rgba[2];
  283. }
  284. - (CGFloat)alpha
  285. {
  286. return CGColorGetAlpha(self.CGColor);
  287. }
  288. - (BOOL)isMonochromeOrRGB
  289. {
  290. CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
  291. return model == kCGColorSpaceModelMonochrome || model == kCGColorSpaceModelRGB;
  292. }
  293. - (BOOL)isEquivalent:(id)object
  294. {
  295. if ([object isKindOfClass:[self class]])
  296. {
  297. return [self isEquivalentToColor:object];
  298. }
  299. return NO;
  300. }
  301. - (BOOL)isEquivalentToColor:(UIColor *)color
  302. {
  303. if ([self isMonochromeOrRGB] && [color isMonochromeOrRGB])
  304. {
  305. return self.RGBAValue == color.RGBAValue;
  306. }
  307. return [self isEqual:color];
  308. }
  309. - (instancetype)colorWithBrightness:(CGFloat)brightness
  310. {
  311. brightness = MAX(brightness, 0.0f);
  312. CGFloat rgba[4];
  313. [self getRGBAComponents:rgba];
  314. return [[self class] colorWithRed:rgba[0] * brightness
  315. green:rgba[1] * brightness
  316. blue:rgba[2] * brightness
  317. alpha:rgba[3]];
  318. }
  319. - (instancetype)colorBlendedWithColor:(UIColor *)color factor:(CGFloat)factor
  320. {
  321. factor = MIN(MAX(factor, 0.0f), 1.0f);
  322. CGFloat fromRGBA[4], toRGBA[4];
  323. [self getRGBAComponents:fromRGBA];
  324. [color getRGBAComponents:toRGBA];
  325. return [[self class] colorWithRed:fromRGBA[0] + (toRGBA[0] - fromRGBA[0]) * factor
  326. green:fromRGBA[1] + (toRGBA[1] - fromRGBA[1]) * factor
  327. blue:fromRGBA[2] + (toRGBA[2] - fromRGBA[2]) * factor
  328. alpha:fromRGBA[3] + (toRGBA[3] - fromRGBA[3]) * factor];
  329. }
  330. @end