|
|
@ -7,31 +7,233 @@ |
|
|
|
|
|
|
|
#include "color.h" |
|
|
|
#include <stdlib.h> |
|
|
|
#include <cmath> |
|
|
|
#include <limits> |
|
|
|
|
|
|
|
/*
|
|
|
|
* Consume whitespace. |
|
|
|
* Parse integer value |
|
|
|
*/ |
|
|
|
|
|
|
|
#define WHITESPACE \ |
|
|
|
while (' ' == *str) ++str; |
|
|
|
template <typename parsed_t> |
|
|
|
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 <number> value |
|
|
|
* Adapted from http://crackprogramming.blogspot.co.il/2012/10/implement-atof.html
|
|
|
|
*/ |
|
|
|
|
|
|
|
template <typename parsed_t> |
|
|
|
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<parsed_t>::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 <typename T> |
|
|
|
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 <typename T> |
|
|
|
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 <typename T>
|
|
|
|
// 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) \ |
|
|
|
c = 0; \ |
|
|
|
if (*str >= '0' && *str <= '9') { \ |
|
|
|
do { \ |
|
|
|
c *= 10; \ |
|
|
|
c += *str++ - '0'; \ |
|
|
|
} while (*str >= '0' && *str <= '9'); \ |
|
|
|
} else { \ |
|
|
|
return 0; \ |
|
|
|
} \ |
|
|
|
if (c > 255) c = 255; \ |
|
|
|
NAME = c; \ |
|
|
|
while (' ' == *str || ',' == *str) str++; |
|
|
|
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) ++str; \ |
|
|
|
if ('.' == *str) { \ |
|
|
|
++str; \ |
|
|
|
float n = .1f; \ |
|
|
|
while (*str >= '0' && *str <= '9') { \ |
|
|
|
NAME += (*str++ - '0') * n; \ |
|
|
|
n *= .1f; \ |
|
|
|
} \ |
|
|
|
} \ |
|
|
|
} \ |
|
|
|
do {} while (0) // require trailing semicolon
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Named colors. |
|
|
@ -161,6 +363,7 @@ static struct named_color { |
|
|
|
, { "plum", 0xDDA0DDFF } |
|
|
|
, { "powderblue", 0xB0E0E6FF } |
|
|
|
, { "purple", 0x800080FF } |
|
|
|
, { "rebeccapurple", 0x663399FF } // Source: CSS Color Level 4 draft
|
|
|
|
, { "red", 0xFF0000FF } |
|
|
|
, { "rosybrown", 0xBC8F8FFF } |
|
|
|
, { "royalblue", 0x4169E1FF } |
|
|
@ -275,6 +478,64 @@ rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { |
|
|
|
| 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). |
|
|
|
*/ |
|
|
@ -320,10 +581,12 @@ rgba_from_rgb_string(const char *str, short *ok) { |
|
|
|
str += 4; |
|
|
|
WHITESPACE; |
|
|
|
uint8_t r = 0, g = 0, b = 0; |
|
|
|
int c; |
|
|
|
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; |
|
|
@ -339,29 +602,68 @@ rgba_from_rgba_string(const char *str, short *ok) { |
|
|
|
str += 5; |
|
|
|
WHITESPACE; |
|
|
|
uint8_t r = 0, g = 0, b = 0; |
|
|
|
int c; |
|
|
|
float a = 0; |
|
|
|
CHANNEL(r); |
|
|
|
WHITESPACE_OR_COMMA; |
|
|
|
CHANNEL(g); |
|
|
|
WHITESPACE_OR_COMMA; |
|
|
|
CHANNEL(b); |
|
|
|
if (*str >= '1' && *str <= '9') { |
|
|
|
a = 1; |
|
|
|
} else { |
|
|
|
if ('0' == *str) ++str; |
|
|
|
if ('.' == *str) { |
|
|
|
++str; |
|
|
|
float n = .1f; |
|
|
|
while (*str >= '0' && *str <= '9') { |
|
|
|
a += (*str++ - '0') * n; |
|
|
|
n *= .1f; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
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: |
|
|
|
* |
|
|
@ -401,6 +703,8 @@ rgba_from_name_string(const char *str, short *ok) { |
|
|
|
* - #RRGGBB |
|
|
|
* - rgb(r,g,b) |
|
|
|
* - rgba(r,g,b,a) |
|
|
|
* - hsl(h,s,l) |
|
|
|
* - hsla(h,s,l,a) |
|
|
|
* - name |
|
|
|
* |
|
|
|
*/ |
|
|
@ -413,6 +717,10 @@ rgba_from_string(const char *str, short *ok) { |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|