// Copyright (c) 2010 LearnBoost #include "color.h" #include #include #include #include #include #include #include // Compatibility with Visual Studio versions prior to VS2015 #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf #endif /* * Parse integer value */ template static bool parse_integer(const char** pStr, parsed_t *pParsed) { parsed_t& c = *pParsed; const char*& str = *pStr; int8_t sign=1; c = 0; if (*str == '-') { sign=-1; ++str; } else if (*str == '+') ++str; if (*str >= '0' && *str <= '9') { do { c *= 10; c += *str++ - '0'; } while (*str >= '0' && *str <= '9'); } else { return false; } if (sign<0) c=-c; return true; } /* * Parse CSS value * Adapted from http://crackprogramming.blogspot.co.il/2012/10/implement-atof.html */ template static bool parse_css_number(const char** pStr, parsed_t *pParsed) { parsed_t &parsed = *pParsed; const char*& str = *pStr; const char* startStr = str; if (!str || !*str) return false; parsed_t integerPart = 0; parsed_t fractionPart = 0; int divisorForFraction = 1; int sign = 1; int exponent = 0; int digits = 0; bool inFraction = false; if (*str == '-') { ++str; sign = -1; } else if (*str == '+') ++str; while (*str != '\0') { if (*str >= '0' && *str <= '9') { if (digits>=std::numeric_limits::digits10) { if (!inFraction) return false; } else { ++digits; if (inFraction) { fractionPart = fractionPart*10 + (*str - '0'); divisorForFraction *= 10; } else { integerPart = integerPart*10 + (*str - '0'); } } } else if (*str == '.') { if (inFraction) break; else inFraction = true; } else if (*str == 'e') { ++str; if (!parse_integer(&str, &exponent)) return false; break; } else break; ++str; } if (str != startStr) { parsed = sign * (integerPart + fractionPart/divisorForFraction); for (;exponent>0;--exponent) parsed *= 10; for (;exponent<0;++exponent) parsed /= 10; return true; } return false; } /* * Clip value to the range [minValue, maxValue] */ template static T clip(T value, T minValue, T maxValue) { if (value > maxValue) value = maxValue; if (value < minValue) value = minValue; return value; } /* * Wrap value to the range [0, limit] */ template static T wrap_float(T value, T limit) { return fmod(fmod(value, limit) + limit, limit); } /* * Wrap value to the range [0, limit] - currently-unused integer version of wrap_float */ // template // static T wrap_int(T value, T limit) { // return (value % limit + limit) % limit; // } /* * Parse color channel value */ static bool parse_rgb_channel(const char** pStr, uint8_t *pChannel) { int channel; if (parse_integer(pStr, &channel)) { *pChannel = clip(channel, 0, 255); return true; } return false; } /* * Parse a value in degrees */ static bool parse_degrees(const char** pStr, float *pDegrees) { float degrees; if (parse_css_number(pStr, °rees)) { *pDegrees = wrap_float(degrees, 360.0f); return true; } return false; } /* * Parse and clip a percentage value. Returns a float in the range [0, 1]. */ static bool parse_clipped_percentage(const char** pStr, float *pFraction) { float percentage; bool result = parse_css_number(pStr,&percentage); const char*& str = *pStr; if (result) { if (*str == '%') { ++str; *pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f; return result; } } return false; } /* * Macros to help with parsing inside rgba_from_*_string */ #define WHITESPACE \ while (' ' == *str) ++str; #define WHITESPACE_OR_COMMA \ while (' ' == *str || ',' == *str) ++str; #define CHANNEL(NAME) \ if (!parse_rgb_channel(&str, &NAME)) \ return 0; \ #define HUE(NAME) \ if (!parse_degrees(&str, &NAME)) \ return 0; #define SATURATION(NAME) \ if (!parse_clipped_percentage(&str, &NAME)) \ return 0; #define LIGHTNESS(NAME) SATURATION(NAME) #define ALPHA(NAME) \ if (*str >= '1' && *str <= '9') { \ NAME = 1; \ } else { \ if ('0' == *str) { \ NAME = 0; \ ++str; \ } \ if ('.' == *str) { \ ++str; \ NAME = 0; \ float n = .1f; \ while (*str >= '0' && *str <= '9') { \ NAME += (*str++ - '0') * n; \ n *= .1f; \ } \ } \ } \ do {} while (0) // require trailing semicolon /* * Named colors. */ static const std::map named_colors = { { "transparent", 0xFFFFFF00} , { "aliceblue", 0xF0F8FFFF } , { "antiquewhite", 0xFAEBD7FF } , { "aqua", 0x00FFFFFF } , { "aquamarine", 0x7FFFD4FF } , { "azure", 0xF0FFFFFF } , { "beige", 0xF5F5DCFF } , { "bisque", 0xFFE4C4FF } , { "black", 0x000000FF } , { "blanchedalmond", 0xFFEBCDFF } , { "blue", 0x0000FFFF } , { "blueviolet", 0x8A2BE2FF } , { "brown", 0xA52A2AFF } , { "burlywood", 0xDEB887FF } , { "cadetblue", 0x5F9EA0FF } , { "chartreuse", 0x7FFF00FF } , { "chocolate", 0xD2691EFF } , { "coral", 0xFF7F50FF } , { "cornflowerblue", 0x6495EDFF } , { "cornsilk", 0xFFF8DCFF } , { "crimson", 0xDC143CFF } , { "cyan", 0x00FFFFFF } , { "darkblue", 0x00008BFF } , { "darkcyan", 0x008B8BFF } , { "darkgoldenrod", 0xB8860BFF } , { "darkgray", 0xA9A9A9FF } , { "darkgreen", 0x006400FF } , { "darkgrey", 0xA9A9A9FF } , { "darkkhaki", 0xBDB76BFF } , { "darkmagenta", 0x8B008BFF } , { "darkolivegreen", 0x556B2FFF } , { "darkorange", 0xFF8C00FF } , { "darkorchid", 0x9932CCFF } , { "darkred", 0x8B0000FF } , { "darksalmon", 0xE9967AFF } , { "darkseagreen", 0x8FBC8FFF } , { "darkslateblue", 0x483D8BFF } , { "darkslategray", 0x2F4F4FFF } , { "darkslategrey", 0x2F4F4FFF } , { "darkturquoise", 0x00CED1FF } , { "darkviolet", 0x9400D3FF } , { "deeppink", 0xFF1493FF } , { "deepskyblue", 0x00BFFFFF } , { "dimgray", 0x696969FF } , { "dimgrey", 0x696969FF } , { "dodgerblue", 0x1E90FFFF } , { "firebrick", 0xB22222FF } , { "floralwhite", 0xFFFAF0FF } , { "forestgreen", 0x228B22FF } , { "fuchsia", 0xFF00FFFF } , { "gainsboro", 0xDCDCDCFF } , { "ghostwhite", 0xF8F8FFFF } , { "gold", 0xFFD700FF } , { "goldenrod", 0xDAA520FF } , { "gray", 0x808080FF } , { "green", 0x008000FF } , { "greenyellow", 0xADFF2FFF } , { "grey", 0x808080FF } , { "honeydew", 0xF0FFF0FF } , { "hotpink", 0xFF69B4FF } , { "indianred", 0xCD5C5CFF } , { "indigo", 0x4B0082FF } , { "ivory", 0xFFFFF0FF } , { "khaki", 0xF0E68CFF } , { "lavender", 0xE6E6FAFF } , { "lavenderblush", 0xFFF0F5FF } , { "lawngreen", 0x7CFC00FF } , { "lemonchiffon", 0xFFFACDFF } , { "lightblue", 0xADD8E6FF } , { "lightcoral", 0xF08080FF } , { "lightcyan", 0xE0FFFFFF } , { "lightgoldenrodyellow", 0xFAFAD2FF } , { "lightgray", 0xD3D3D3FF } , { "lightgreen", 0x90EE90FF } , { "lightgrey", 0xD3D3D3FF } , { "lightpink", 0xFFB6C1FF } , { "lightsalmon", 0xFFA07AFF } , { "lightseagreen", 0x20B2AAFF } , { "lightskyblue", 0x87CEFAFF } , { "lightslategray", 0x778899FF } , { "lightslategrey", 0x778899FF } , { "lightsteelblue", 0xB0C4DEFF } , { "lightyellow", 0xFFFFE0FF } , { "lime", 0x00FF00FF } , { "limegreen", 0x32CD32FF } , { "linen", 0xFAF0E6FF } , { "magenta", 0xFF00FFFF } , { "maroon", 0x800000FF } , { "mediumaquamarine", 0x66CDAAFF } , { "mediumblue", 0x0000CDFF } , { "mediumorchid", 0xBA55D3FF } , { "mediumpurple", 0x9370DBFF } , { "mediumseagreen", 0x3CB371FF } , { "mediumslateblue", 0x7B68EEFF } , { "mediumspringgreen", 0x00FA9AFF } , { "mediumturquoise", 0x48D1CCFF } , { "mediumvioletred", 0xC71585FF } , { "midnightblue", 0x191970FF } , { "mintcream", 0xF5FFFAFF } , { "mistyrose", 0xFFE4E1FF } , { "moccasin", 0xFFE4B5FF } , { "navajowhite", 0xFFDEADFF } , { "navy", 0x000080FF } , { "oldlace", 0xFDF5E6FF } , { "olive", 0x808000FF } , { "olivedrab", 0x6B8E23FF } , { "orange", 0xFFA500FF } , { "orangered", 0xFF4500FF } , { "orchid", 0xDA70D6FF } , { "palegoldenrod", 0xEEE8AAFF } , { "palegreen", 0x98FB98FF } , { "paleturquoise", 0xAFEEEEFF } , { "palevioletred", 0xDB7093FF } , { "papayawhip", 0xFFEFD5FF } , { "peachpuff", 0xFFDAB9FF } , { "peru", 0xCD853FFF } , { "pink", 0xFFC0CBFF } , { "plum", 0xDDA0DDFF } , { "powderblue", 0xB0E0E6FF } , { "purple", 0x800080FF } , { "rebeccapurple", 0x663399FF } // Source: CSS Color Level 4 draft , { "red", 0xFF0000FF } , { "rosybrown", 0xBC8F8FFF } , { "royalblue", 0x4169E1FF } , { "saddlebrown", 0x8B4513FF } , { "salmon", 0xFA8072FF } , { "sandybrown", 0xF4A460FF } , { "seagreen", 0x2E8B57FF } , { "seashell", 0xFFF5EEFF } , { "sienna", 0xA0522DFF } , { "silver", 0xC0C0C0FF } , { "skyblue", 0x87CEEBFF } , { "slateblue", 0x6A5ACDFF } , { "slategray", 0x708090FF } , { "slategrey", 0x708090FF } , { "snow", 0xFFFAFAFF } , { "springgreen", 0x00FF7FFF } , { "steelblue", 0x4682B4FF } , { "tan", 0xD2B48CFF } , { "teal", 0x008080FF } , { "thistle", 0xD8BFD8FF } , { "tomato", 0xFF6347FF } , { "turquoise", 0x40E0D0FF } , { "violet", 0xEE82EEFF } , { "wheat", 0xF5DEB3FF } , { "white", 0xFFFFFFFF } , { "whitesmoke", 0xF5F5F5FF } , { "yellow", 0xFFFF00FF } , { "yellowgreen", 0x9ACD32FF } }; /* * Hex digit int val. */ static int h(char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return c - '0'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return (c - 'a') + 10; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return (c - 'A') + 10; } return 0; } /* * Return rgba_t from rgba. */ rgba_t rgba_create(uint32_t rgba) { rgba_t color; color.r = (double) (rgba >> 24) / 255; color.g = (double) (rgba >> 16 & 0xff) / 255; color.b = (double) (rgba >> 8 & 0xff) / 255; color.a = (double) (rgba & 0xff) / 255; return color; } /* * Return a string representation of the color. */ void rgba_to_string(rgba_t rgba, char *buf, size_t len) { if (1 == rgba.a) { snprintf(buf, len, "#%.2x%.2x%.2x", static_cast(round(rgba.r * 255)), static_cast(round(rgba.g * 255)), static_cast(round(rgba.b * 255))); } else { snprintf(buf, len, "rgba(%d, %d, %d, %.2f)", static_cast(round(rgba.r * 255)), static_cast(round(rgba.g * 255)), static_cast(round(rgba.b * 255)), rgba.a); } } /* * Return rgba from (r,g,b,a). */ static inline int32_t rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return r << 24 | g << 16 | b << 8 | a; } /* * Helper function used in rgba_from_hsla(). * Based on http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb */ static float hue_to_rgb(float t1, float t2, float hue) { if (hue < 0) hue += 6; if (hue >= 6) hue -= 6; if (hue < 1) return (t2 - t1) * hue + t1; else if (hue < 3) return t2; else if (hue < 4) return (t2 - t1) * (4 - hue) + t1; else return t1; } /* * Return rgba from (h,s,l,a). * Expects h values in the range [0, 360), and s, l, a in the range [0, 1]. * Adapted from http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb */ static inline int32_t rgba_from_hsla(float h_deg, float s, float l, float a) { uint8_t r, g, b; float h = (6 * h_deg) / 360.0f, m1, m2; if (l<=0.5) m2=l*(s+1); else m2=l+s-l*s; m1 = l*2 - m2; // Scale and round the RGB components r = (uint8_t)floor(hue_to_rgb(m1, m2, h + 2) * 255 + 0.5); g = (uint8_t)floor(hue_to_rgb(m1, m2, h ) * 255 + 0.5); b = (uint8_t)floor(hue_to_rgb(m1, m2, h - 2) * 255 + 0.5); return rgba_from_rgba(r, g, b, (uint8_t) (a * 255)); } /* * Return rgba from (h,s,l). * Expects h values in the range [0, 360), and s, l in the range [0, 1]. */ static inline int32_t rgba_from_hsl(float h_deg, float s, float l) { return rgba_from_hsla(h_deg, s, l, 1.0); } /* * Return rgba from (r,g,b). */ static int32_t rgba_from_rgb(uint8_t r, uint8_t g, uint8_t b) { return rgba_from_rgba(r, g, b, 255); } /* * Return rgba from #RRGGBBAA */ static int32_t rgba_from_hex8_string(const char *str) { return rgba_from_rgba( (h(str[0]) << 4) + h(str[1]), (h(str[2]) << 4) + h(str[3]), (h(str[4]) << 4) + h(str[5]), (h(str[6]) << 4) + h(str[7]) ); } /* * Return rgb from "#RRGGBB". */ static int32_t rgba_from_hex6_string(const char *str) { return rgba_from_rgb( (h(str[0]) << 4) + h(str[1]) , (h(str[2]) << 4) + h(str[3]) , (h(str[4]) << 4) + h(str[5]) ); } /* * Return rgba from #RGBA */ static int32_t rgba_from_hex4_string(const char *str) { return rgba_from_rgba( (h(str[0]) << 4) + h(str[0]), (h(str[1]) << 4) + h(str[1]), (h(str[2]) << 4) + h(str[2]), (h(str[3]) << 4) + h(str[3]) ); } /* * Return rgb from "#RGB" */ static int32_t rgba_from_hex3_string(const char *str) { return rgba_from_rgb( (h(str[0]) << 4) + h(str[0]) , (h(str[1]) << 4) + h(str[1]) , (h(str[2]) << 4) + h(str[2]) ); } /* * Return rgb from "rgb()" */ static int32_t rgba_from_rgb_string(const char *str, short *ok) { if (str == strstr(str, "rgb(")) { str += 4; WHITESPACE; uint8_t r = 0, g = 0, b = 0; CHANNEL(r); WHITESPACE_OR_COMMA; CHANNEL(g); WHITESPACE_OR_COMMA; CHANNEL(b); WHITESPACE; return *ok = 1, rgba_from_rgb(r, g, b); } return *ok = 0; } /* * Return rgb from "rgba()" */ static int32_t rgba_from_rgba_string(const char *str, short *ok) { if (str == strstr(str, "rgba(")) { str += 5; WHITESPACE; uint8_t r = 0, g = 0, b = 0; float a = 1.f; CHANNEL(r); WHITESPACE_OR_COMMA; CHANNEL(g); WHITESPACE_OR_COMMA; CHANNEL(b); WHITESPACE_OR_COMMA; ALPHA(a); WHITESPACE; return *ok = 1, rgba_from_rgba(r, g, b, (int) (a * 255)); } return *ok = 0; } /* * Return rgb from "hsla()" */ static int32_t rgba_from_hsla_string(const char *str, short *ok) { if (str == strstr(str, "hsla(")) { str += 5; WHITESPACE; float h_deg = 0; float s = 0, l = 0; float a = 0; HUE(h_deg); WHITESPACE_OR_COMMA; SATURATION(s); WHITESPACE_OR_COMMA; LIGHTNESS(l); WHITESPACE_OR_COMMA; ALPHA(a); WHITESPACE; return *ok = 1, rgba_from_hsla(h_deg, s, l, a); } return *ok = 0; } /* * Return rgb from "hsl()" */ static int32_t rgba_from_hsl_string(const char *str, short *ok) { if (str == strstr(str, "hsl(")) { str += 4; WHITESPACE; float h_deg = 0; float s = 0, l = 0; HUE(h_deg); WHITESPACE_OR_COMMA; SATURATION(s); WHITESPACE_OR_COMMA; LIGHTNESS(l); WHITESPACE; return *ok = 1, rgba_from_hsl(h_deg, s, l); } return *ok = 0; } /* * Return rgb from: * * - "#RGB" * - "#RGBA" * - "#RRGGBB" * - "#RRGGBBAA" * */ static int32_t rgba_from_hex_string(const char *str, short *ok) { size_t len = strlen(str); *ok = 1; switch (len) { case 8: return rgba_from_hex8_string(str); case 6: return rgba_from_hex6_string(str); case 4: return rgba_from_hex4_string(str); case 3: return rgba_from_hex3_string(str); } return *ok = 0; } /* * Return named color value. */ static int32_t rgba_from_name_string(const char *str, short *ok) { std::string lowered(str); std::transform(lowered.begin(), lowered.end(), lowered.begin(), tolower); auto color = named_colors.find(lowered); if (color != named_colors.end()) { return *ok = 1, color->second; } return *ok = 0; } /* * Return rgb from: * * - #RGB * - #RGBA * - #RRGGBB * - #RRGGBBAA * - rgb(r,g,b) * - rgba(r,g,b,a) * - hsl(h,s,l) * - hsla(h,s,l,a) * - name * */ int32_t rgba_from_string(const char *str, short *ok) { if ('#' == str[0]) return rgba_from_hex_string(++str, ok); if (str == strstr(str, "rgba")) return rgba_from_rgba_string(str, ok); if (str == strstr(str, "rgb")) return rgba_from_rgb_string(str, ok); if (str == strstr(str, "hsla")) return rgba_from_hsla_string(str, ok); if (str == strstr(str, "hsl")) return rgba_from_hsl_string(str, ok); return rgba_from_name_string(str, ok); } /* * Inspect the given rgba color. */ void rgba_inspect(int32_t rgba) { printf("rgba(%d,%d,%d,%d)\n" , rgba >> 24 & 0xff , rgba >> 16 & 0xff , rgba >> 8 & 0xff , rgba & 0xff ); }