From 079667b97e176329da2480757570e8e8fc5b4a26 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Wed, 29 Sep 2021 09:17:47 +0100 Subject: [PATCH] add hsluv.h --- LICENSE.md | 1 + apps/samples/converter.ini | 2 +- apps/samples/game_loop.cpp | 85 +++++++ apps/samples/game_loop.ini | 5 + apps/samples/hello.ini | 2 +- desktop/os.header | 7 + desktop/prefix.h | 7 + shared/math.cpp | 8 + util/api_table.ini | 7 + util/designer2.cpp | 25 ++- util/hsluv.h | 449 +++++++++++++++++++++++++++++++++++++ 11 files changed, 585 insertions(+), 13 deletions(-) create mode 100644 apps/samples/game_loop.cpp create mode 100644 apps/samples/game_loop.ini create mode 100644 util/hsluv.h diff --git a/LICENSE.md b/LICENSE.md index 500b2c3..3ae1225 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -24,6 +24,7 @@ This project also include the work of other projects. All trademarks are registered trademarks of their respective owners. They licenses may be found in the following files: - util/nanosvg.h + - util/hsluv.h - shared/stb_image.h, shared/stb_sprintf.h, shared/stb_ds.h and util/stb_truetype.h - res/Fonts/Hack License.txt, res/Fonts/Inter License.txt - res/Icons/elementary Icons License.txt diff --git a/apps/samples/converter.ini b/apps/samples/converter.ini index 21f6aa7..5faf70a 100644 --- a/apps/samples/converter.ini +++ b/apps/samples/converter.ini @@ -2,4 +2,4 @@ name=Converter [build] -source=apps/converter.cpp +source=apps/samples/converter.cpp diff --git a/apps/samples/game_loop.cpp b/apps/samples/game_loop.cpp new file mode 100644 index 0000000..3f3ea12 --- /dev/null +++ b/apps/samples/game_loop.cpp @@ -0,0 +1,85 @@ +#include + +EsInstance *instance; +EsElement *canvas; + +float spriteX; +float spriteY; + +bool isLeftHeld; +bool isRightHeld; +bool isUpHeld; +bool isDownHeld; + +void GameRender(EsPainter *painter, EsRectangle bounds) { + // Fill with black. + EsDrawBlock(painter, bounds, 0xFF000000); + + // Draw the sprite. + EsRectangle spriteBounds = ES_RECT_4PD(spriteX, spriteY, 30, 30); + EsDrawRectangle(painter, EsRectangleTranslate(spriteBounds, bounds), 0xFFFF0000, 0xFF800000, ES_RECT_1(2)); +} + +void GameUpdate(float deltaMs) { + // Move the sprite if an arrow key is held. + float directionX = isLeftHeld ? -1 : isRightHeld ? 1 : 0; + spriteX += directionX * deltaMs * 0.25f; + float directionY = isUpHeld ? -1 : isDownHeld ? 1 : 0; + spriteY += directionY * deltaMs * 0.25f; +} + +int CanvasMessage(EsElement *, EsMessage *message) { + if (message->type == ES_MSG_PAINT) { + // Get the bounds we should draw on. + EsRectangle bounds = EsPainterBoundsInset(message->painter); + + // Render the game. + GameRender(message->painter, bounds); + } else if (message->type == ES_MSG_ANIMATE) { + // Keep sending animation messages. + message->animate.complete = false; + + // Update the game using the time delta (milliseconds) provided. + GameUpdate(message->animate.deltaMs); + + // Ask to repaint the canvas. + EsElementRepaint(canvas, nullptr); + } else if (message->type == ES_MSG_KEY_DOWN || message->type == ES_MSG_KEY_UP) { + // Track which keys are being held. + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ARROW) { + isLeftHeld = message->type == ES_MSG_KEY_DOWN; + } else if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ARROW) { + isRightHeld = message->type == ES_MSG_KEY_DOWN; + } else if (message->keyboard.scancode == ES_SCANCODE_UP_ARROW) { + isUpHeld = message->type == ES_MSG_KEY_DOWN; + } else if (message->keyboard.scancode == ES_SCANCODE_DOWN_ARROW) { + isDownHeld = message->type == ES_MSG_KEY_DOWN; + } + } + + return 0; +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + instance = EsInstanceCreate(message, "Game Loop"); + + // Create a canvas to draw on, and make it fill the window. + canvas = EsCustomElementCreate(instance->window, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE, ES_STYLE_PANEL_WINDOW_DIVIDER); + + // Set the message callback. + canvas->messageUser = CanvasMessage; + + // Send animation messages to the canvas. + EsElementStartAnimating(canvas); + + // Focus the canvas to receive keyboard messages. + EsElementFocus(canvas); + } + } +} diff --git a/apps/samples/game_loop.ini b/apps/samples/game_loop.ini new file mode 100644 index 0000000..1c28a0e --- /dev/null +++ b/apps/samples/game_loop.ini @@ -0,0 +1,5 @@ +[general] +name=Game Loop + +[build] +source=apps/samples/game_loop.cpp diff --git a/apps/samples/hello.ini b/apps/samples/hello.ini index 513220f..fa29a54 100644 --- a/apps/samples/hello.ini +++ b/apps/samples/hello.ini @@ -2,4 +2,4 @@ name=Hello [build] -source=apps/hello.c +source=apps/samples/hello.c diff --git a/desktop/os.header b/desktop/os.header index 455f959..ce7e78f 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -2221,13 +2221,17 @@ function bool EsUTF8IsValid(const char *input, ptrdiff_t bytes); // Does not che function int EsCRTabs(int n); function float EsCRTacosf(float x); function float EsCRTasinf(float x); +function double EsCRTatan2(double y, double x); function float EsCRTatan2f(float y, float x); function float EsCRTatanf(float x); function int EsCRTatoi(const char *string); function void *EsCRTbsearch(const void *key, const void *base, size_t num, size_t size, EsCRTComparisonCallback compar); function void *EsCRTcalloc(size_t num, size_t size); +function double EsCRTcbrt(double x); +function float EsCRTcbrtf(float x); function double EsCRTceil(double x); function float EsCRTceilf(float x); +function double EsCRTcos(double x); function float EsCRTcosf(float x); function double EsCRTexp(double x); function float EsCRTexp2f(float x); @@ -2235,6 +2239,7 @@ function double EsCRTfabs(double x); function float EsCRTfabsf(float x); function double EsCRTfloor(double x); function float EsCRTfloorf(float x); +function double EsCRTfmod(double x, double y); function float EsCRTfmodf(float x, float y); function void EsCRTfree(void *ptr); function char *EsCRTgetenv(const char *name); @@ -2252,10 +2257,12 @@ function int EsCRTmemcmp(const void *s1, const void *s2, size_t n); function void *EsCRTmemcpy(void *dest, const void *src, size_t n); function void *EsCRTmemmove(void *dest, const void *src, size_t n); function void *EsCRTmemset(void *s, int c, size_t n); +function double EsCRTpow(double x, double y); function float EsCRTpowf(float x, float y); function void EsCRTqsort(void *_base, size_t nmemb, size_t size, EsCRTComparisonCallback compar); function int EsCRTrand(); function void *EsCRTrealloc(void *ptr, size_t size); +function double EsCRTsin(double x); function float EsCRTsinf(float x); function int EsCRTsnprintf(char *buffer, size_t bufferSize, const char *format, ...); function int EsCRTsprintf(char *buffer, const char *format, ...); diff --git a/desktop/prefix.h b/desktop/prefix.h index 6adcf9f..863c7d4 100644 --- a/desktop/prefix.h +++ b/desktop/prefix.h @@ -326,13 +326,17 @@ extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writ #define acosf EsCRTacosf #define asinf EsCRTasinf #define assert EsCRTassert +#define atan2 EsCRTatan2 #define atan2f EsCRTatan2f #define atanf EsCRTatanf #define atoi EsCRTatoi #define bsearch EsCRTbsearch #define calloc EsCRTcalloc +#define cbrt EsCRTcbrt +#define cbrtf EsCRTcbrtf #define ceil EsCRTceil #define ceilf EsCRTceilf +#define cos EsCRTcos #define cosf EsCRTcosf #define exp EsCRTexp #define exp2f EsCRTexp2f @@ -340,6 +344,7 @@ extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writ #define fabsf EsCRTfabsf #define floor EsCRTfloor #define floorf EsCRTfloorf +#define fmod EsCRTfmod #define fmodf EsCRTfmodf #define free EsCRTfree #define getenv EsCRTgetenv @@ -355,10 +360,12 @@ extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writ #define memcpy EsCRTmemcpy #define memmove EsCRTmemmove #define memset EsCRTmemset +#define pow EsCRTpow #define powf EsCRTpowf #define qsort EsCRTqsort #define rand EsCRTrand #define realloc EsCRTrealloc +#define sin EsCRTsin #define sinf EsCRTsinf #define snprintf EsCRTsnprintf #define sprintf EsCRTsprintf diff --git a/shared/math.cpp b/shared/math.cpp index 5ddc321..21335ff 100644 --- a/shared/math.cpp +++ b/shared/math.cpp @@ -836,6 +836,14 @@ float EsCRTpowf(float x, float y) { return EsCRTexp2f(y * EsCRTlog2f(x)); } +double EsCRTcbrt(double x) { + return EsCRTpow(x, 1.0 / 3.0); +} + +float EsCRTcbrtf(float x) { + return EsCRTpowf(x, 1.0f / 3.0f); +} + double EsCRTexp(double x) { return EsCRTexp2(x * 1.4426950408889634073); } diff --git a/util/api_table.ini b/util/api_table.ini index 2581003..b25320f 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -363,6 +363,7 @@ EsWindowAddSizeAlternative=361 EsMenuAddCommandsFromToolbar=362 EsPanelStartMovementAnimation=363 EsElementStartTransition=364 +EsCRTatan2=365 EsFileWriteAllFromHandle=366 EsFileWriteAllGatherFromHandle=367 EsButtonSetIconFromBits=368 @@ -464,3 +465,9 @@ EsDialogShow=463 EsInstanceSetModified=464 EsFileMenuAddToToolbar=465 EsFileMenuCreate=466 +EsCRTcos=467 +EsCRTsin=468 +EsCRTfmod=469 +EsCRTpow=470 +EsCRTcbrt=471 +EsCRTcbrtf=472 diff --git a/util/designer2.cpp b/util/designer2.cpp index 1028941..3cc7f6e 100644 --- a/util/designer2.cpp +++ b/util/designer2.cpp @@ -5,6 +5,7 @@ #include #include #endif +#include "hsluv.h" // x86_64-w64-mingw32-gcc -O3 -o bin/designer2.exe -D UI_WINDOWS util/designer2.cpp -DUNICODE -lgdi32 -luser32 -lkernel32 -Wl,--subsystem,windows -fno-exceptions -fno-rtti @@ -16,12 +17,10 @@ // TODO Import and reorganize old theming data. // Additional features: -// TODO Better color modification space -- HSLuv. // TODO Selecting multiple objects. // TODO Resizing objects? // TODO Find object in graph by name. // TODO Prototyping display. (Multiple instances of each object can be placed, resized and interacted with). -// TODO Running on Essence: x86_64-essence-g++ -o root/designer2 -D UI_ESSENCE util/designer2.cpp ////////////////////////////////////////////////////////////// @@ -1123,8 +1122,10 @@ void InspectorPopulate() { InspectorAddLink(object, "Color:", "color"); } else if (object->type == OBJ_MOD_COLOR) { InspectorAddLink(object, "Base color:", "base"); - UILabelCreate(0, 0, "Brightness:", -1); + UILabelCreate(0, 0, "Brightness (%):", -1); InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "brightness", INSPECTOR_INTEGER_TEXTBOX); + UILabelCreate(0, 0, "Hue shift (deg):", -1); + InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "hueShift", INSPECTOR_INTEGER_TEXTBOX); } else if (object->type == OBJ_MOD_MULTIPLY) { InspectorAddLink(object, "Base integer:", "base"); UILabelCreate(0, 0, "Factor (%):", -1); @@ -1239,13 +1240,15 @@ uint32_t CanvasGetColorFromPaint(Object *object, int depth = 0) { uint32_t base = CanvasGetColorFromPaint(ObjectFind(property ? property->object : 0), depth + 1); uint32_t alpha = base & 0xFF000000; int32_t brightness = PropertyReadInt32(object, "brightness"); - float hue, saturation, value; - UIColorToHSV(base, &hue, &saturation, &value); - value += brightness / 100.0f; - if (value < 0) value = 0; - if (value > 1) value = 1; - UIColorToRGB(hue, saturation, value, &base); - return base | alpha; + int32_t hueShift = PropertyReadInt32(object, "hueShift"); + double hue, saturation, luminosity, red, green, blue; + rgb2hsluv(UI_COLOR_RED_F(base), UI_COLOR_GREEN_F(base), UI_COLOR_BLUE_F(base), &hue, &saturation, &luminosity); + luminosity += luminosity * brightness / 100.0f; + hue = fmod(hue + hueShift, 360.0); + if (luminosity < 0.0) luminosity = 0.0; + if (luminosity > 100.0) luminosity = 100.0; + hsluv2rgb(hue, saturation, luminosity, &red, &green, &blue); + return UI_COLOR_FROM_FLOAT(red, green, blue) | alpha; } else { return 0; } @@ -1530,7 +1533,7 @@ int main() { #else UIButtonCreate(0, 0, "Save", -1)->invoke = DocumentSave; #endif - UIButtonCreate(0, 0, "Add object", -1)->invoke = ObjectAddCommand; + UIButtonCreate(0, 0, "Add object...", -1)->invoke = ObjectAddCommand; UISpacerCreate(0, 0, 15, 0); labelMessage = UILabelCreate(0, UI_ELEMENT_H_FILL, 0, 0); UIParentPop(); diff --git a/util/hsluv.h b/util/hsluv.h new file mode 100644 index 0000000..8a8e732 --- /dev/null +++ b/util/hsluv.h @@ -0,0 +1,449 @@ +/* + * HSLuv-C: Human-friendly HSL + * + * + * + * Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation) + * Copyright (c) 2015 Roger Tallada (Obj-C implementation) + * Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +typedef struct Triplet_tag Triplet; +struct Triplet_tag { + double a; + double b; + double c; +}; + +/* for RGB */ +static const Triplet m[3] = { + { 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 }, + { -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 }, + { 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 } +}; + +/* for XYZ */ +static const Triplet m_inv[3] = { + { 0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751 }, + { 0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500 }, + { 0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086 } +}; + +static const double ref_u = 0.19783000664283680764; +static const double ref_v = 0.46831999493879100370; + +static const double kappa = 903.29629629629629629630; +static const double epsilon = 0.00885645167903563082; + + +typedef struct Bounds_tag Bounds; +struct Bounds_tag { + double a; + double b; +}; + + +static void +get_bounds(double l, Bounds bounds[6]) +{ + double tl = l + 16.0; + double sub1 = (tl * tl * tl) / 1560896.0; + double sub2 = (sub1 > epsilon ? sub1 : (l / kappa)); + int channel; + int t; + + for(channel = 0; channel < 3; channel++) { + double m1 = m[channel].a; + double m2 = m[channel].b; + double m3 = m[channel].c; + + for (t = 0; t < 2; t++) { + double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2; + double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l; + double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t; + + bounds[channel * 2 + t].a = top1 / bottom; + bounds[channel * 2 + t].b = top2 / bottom; + } + } +} + +static double +intersect_line_line(const Bounds* line1, const Bounds* line2) +{ + return (line1->b - line2->b) / (line2->a - line1->a); +} + +static double +dist_from_pole_squared(double x, double y) +{ + return x * x + y * y; +} + +static double +ray_length_until_intersect(double theta, const Bounds* line) +{ + return line->b / (sin(theta) - line->a * cos(theta)); +} + +static double +max_safe_chroma_for_l(double l) +{ + double min_len_squared = DBL_MAX; + Bounds bounds[6]; + int i; + + get_bounds(l, bounds); + for(i = 0; i < 6; i++) { + double m1 = bounds[i].a; + double b1 = bounds[i].b; + /* x where line intersects with perpendicular running though (0, 0) */ + Bounds line2 = { -1.0 / m1, 0.0 }; + double x = intersect_line_line(&bounds[i], &line2); + double distance = dist_from_pole_squared(x, b1 + x * m1); + + if(distance < min_len_squared) + min_len_squared = distance; + } + + return sqrt(min_len_squared); +} + +static double +max_chroma_for_lh(double l, double h) +{ + double min_len = DBL_MAX; + double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */ + Bounds bounds[6]; + int i; + + get_bounds(l, bounds); + for(i = 0; i < 6; i++) { + double len = ray_length_until_intersect(hrad, &bounds[i]); + + if(len >= 0 && len < min_len) + min_len = len; + } + return min_len; +} + +static double +dot_product(const Triplet* t1, const Triplet* t2) +{ + return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c); +} + +/* Used for rgb conversions */ +static double +from_linear(double c) +{ + if(c <= 0.0031308) + return 12.92 * c; + else + return 1.055 * pow(c, 1.0 / 2.4) - 0.055; +} + +static double +to_linear(double c) +{ + if (c > 0.04045) + return pow((c + 0.055) / 1.055, 2.4); + else + return c / 12.92; +} + +static void +xyz2rgb(Triplet* in_out) +{ + double r = from_linear(dot_product(&m[0], in_out)); + double g = from_linear(dot_product(&m[1], in_out)); + double b = from_linear(dot_product(&m[2], in_out)); + in_out->a = r; + in_out->b = g; + in_out->c = b; +} + +static void +rgb2xyz(Triplet* in_out) +{ + Triplet rgbl = { to_linear(in_out->a), to_linear(in_out->b), to_linear(in_out->c) }; + double x = dot_product(&m_inv[0], &rgbl); + double y = dot_product(&m_inv[1], &rgbl); + double z = dot_product(&m_inv[2], &rgbl); + in_out->a = x; + in_out->b = y; + in_out->c = z; +} + +/* https://en.wikipedia.org/wiki/CIELUV + * In these formulas, Yn refers to the reference white point. We are using + * illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is + * simplified accordingly. + */ +static double +y2l(double y) +{ + if(y <= epsilon) + return y * kappa; + else + return 116.0 * cbrt(y) - 16.0; +} + +static double +l2y(double l) +{ + if(l <= 8.0) { + return l / kappa; + } else { + double x = (l + 16.0) / 116.0; + return (x * x * x); + } +} + +static void +xyz2luv(Triplet* in_out) +{ + double var_u = (4.0 * in_out->a) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c)); + double var_v = (9.0 * in_out->b) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c)); + double l = y2l(in_out->b); + double u = 13.0 * l * (var_u - ref_u); + double v = 13.0 * l * (var_v - ref_v); + + in_out->a = l; + if(l < 0.00000001) { + in_out->b = 0.0; + in_out->c = 0.0; + } else { + in_out->b = u; + in_out->c = v; + } +} + +static void +luv2xyz(Triplet* in_out) +{ + if(in_out->a <= 0.00000001) { + /* Black will create a divide-by-zero error. */ + in_out->a = 0.0; + in_out->b = 0.0; + in_out->c = 0.0; + return; + } + + double var_u = in_out->b / (13.0 * in_out->a) + ref_u; + double var_v = in_out->c / (13.0 * in_out->a) + ref_v; + double y = l2y(in_out->a); + double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); + double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); + in_out->a = x; + in_out->b = y; + in_out->c = z; +} + +static void +luv2lch(Triplet* in_out) +{ + double l = in_out->a; + double u = in_out->b; + double v = in_out->c; + double h; + double c = sqrt(u * u + v * v); + + /* Grays: disambiguate hue */ + if(c < 0.00000001) { + h = 0; + } else { + h = atan2(v, u) * 57.29577951308232087680; /* (180 / pi) */ + if(h < 0.0) + h += 360.0; + } + + in_out->a = l; + in_out->b = c; + in_out->c = h; +} + +static void +lch2luv(Triplet* in_out) +{ + double hrad = in_out->c * 0.01745329251994329577; /* (pi / 180.0) */ + double u = cos(hrad) * in_out->b; + double v = sin(hrad) * in_out->b; + + in_out->b = u; + in_out->c = v; +} + +static void +hsluv2lch(Triplet* in_out) +{ + double h = in_out->a; + double s = in_out->b; + double l = in_out->c; + double c; + + /* White and black: disambiguate chroma */ + if(l > 99.9999999 || l < 0.00000001) + c = 0.0; + else + c = max_chroma_for_lh(l, h) / 100.0 * s; + + /* Grays: disambiguate hue */ + if (s < 0.00000001) + h = 0.0; + + in_out->a = l; + in_out->b = c; + in_out->c = h; +} + +static void +lch2hsluv(Triplet* in_out) +{ + double l = in_out->a; + double c = in_out->b; + double h = in_out->c; + double s; + + /* White and black: disambiguate saturation */ + if(l > 99.9999999 || l < 0.00000001) + s = 0.0; + else + s = c / max_chroma_for_lh(l, h) * 100.0; + + /* Grays: disambiguate hue */ + if (c < 0.00000001) + h = 0.0; + + in_out->a = h; + in_out->b = s; + in_out->c = l; +} + +static void +hpluv2lch(Triplet* in_out) +{ + double h = in_out->a; + double s = in_out->b; + double l = in_out->c; + double c; + + /* White and black: disambiguate chroma */ + if(l > 99.9999999 || l < 0.00000001) + c = 0.0; + else + c = max_safe_chroma_for_l(l) / 100.0 * s; + + /* Grays: disambiguate hue */ + if (s < 0.00000001) + h = 0.0; + + in_out->a = l; + in_out->b = c; + in_out->c = h; +} + +static void +lch2hpluv(Triplet* in_out) +{ + double l = in_out->a; + double c = in_out->b; + double h = in_out->c; + double s; + + /* White and black: disambiguate saturation */ + if (l > 99.9999999 || l < 0.00000001) + s = 0.0; + else + s = c / max_safe_chroma_for_l(l) * 100.0; + + /* Grays: disambiguate hue */ + if (c < 0.00000001) + h = 0.0; + + in_out->a = h; + in_out->b = s; + in_out->c = l; +} + + + +void +hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb) +{ + Triplet tmp = { h, s, l }; + + hsluv2lch(&tmp); + lch2luv(&tmp); + luv2xyz(&tmp); + xyz2rgb(&tmp); + + *pr = tmp.a; + *pg = tmp.b; + *pb = tmp.c; +} + +void +hpluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb) +{ + Triplet tmp = { h, s, l }; + + hpluv2lch(&tmp); + lch2luv(&tmp); + luv2xyz(&tmp); + xyz2rgb(&tmp); + + *pr = tmp.a; + *pg = tmp.b; + *pb = tmp.c; +} + +void +rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl) +{ + Triplet tmp = { r, g, b }; + + rgb2xyz(&tmp); + xyz2luv(&tmp); + luv2lch(&tmp); + lch2hsluv(&tmp); + + *ph = tmp.a; + *ps = tmp.b; + *pl = tmp.c; +} + +void +rgb2hpluv(double r, double g, double b, double* ph, double* ps, double* pl) +{ + Triplet tmp = { r, g, b }; + + rgb2xyz(&tmp); + xyz2luv(&tmp); + luv2lch(&tmp); + lch2hpluv(&tmp); + + *ph = tmp.a; + *ps = tmp.b; + *pl = tmp.c; +}