common: consolidate color-related functions

- Introduced RGB, RGBA, and HSL structures
- Migrated hsl2Rgb() from SVG loader to common module

This is a refactoring for upcoming hsl color functionlaities.
This commit is contained in:
Hermet Park 2025-07-21 15:24:02 +09:00
parent 930de44359
commit c3d1e6e519
6 changed files with 153 additions and 112 deletions

View file

@ -1,10 +1,12 @@
source_file = [
'tvgArray.h',
'tvgColor.h',
'tvgCompressor.h',
'tvgInlist.h',
'tvgLock.h',
'tvgMath.h',
'tvgStr.h',
'tvgColor.cpp',
'tvgCompressor.cpp',
'tvgMath.cpp',
'tvgStr.cpp'

71
src/common/tvgColor.cpp Normal file
View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2025 the ThorVG project. All rights reserved.
* 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 "tvgMath.h"
#include "tvgColor.h"
namespace tvg
{
void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b)
{
s = tvg::clamp(s, 0.0f, 1.0f);
l = tvg::clamp(l, 0.0f, 1.0f);
auto tr = 0.0f, tg = 0.0f, tb = 0.0f;
if (tvg::zero(s)) {
r = g = b = (uint8_t)nearbyint(l * 255.0f);
return;
}
if (tvg::equal(h, 360.0f)) {
h = 0.0f;
} else {
h = fmod(h, 360.0f);
if (h < 0.0f) h += 360.0f;
h /= 60.0f;
}
auto v = (l <= 0.5f) ? (l * (1.0f + s)) : (l + s - (l * s));
auto p = l + l - v;
auto sv = tvg::zero(v) ? 0.0f : (v - p) / v;
auto i = static_cast<uint8_t>(h);
auto f = h - i;
auto vsf = v * sv * f;
auto t = p + vsf;
auto q = v - vsf;
switch (i) {
case 0: tr = v; tg = t; tb = p; break;
case 1: tr = q; tg = v; tb = p; break;
case 2: tr = p; tg = v; tb = t; break;
case 3: tr = p; tg = q; tb = v; break;
case 4: tr = t; tg = p; tb = v; break;
case 5: tr = v; tg = p; tb = q; break;
default: break;
}
r = (uint8_t)nearbyint(tr * 255.0f);
g = (uint8_t)nearbyint(tg * 255.0f);
b = (uint8_t)nearbyint(tb * 255.0f);
}
}

49
src/common/tvgColor.h Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 the ThorVG project. All rights reserved.
* 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.
*/
#ifndef _TVG_COLOR_H_
#define _TVG_COLOR_H_
#include "tvgCommon.h"
namespace tvg
{
struct RGB
{
uint8_t r, g, b;
};
struct RGBA
{
uint8_t r, g, b, a;
};
struct HSL
{
float h, s, l;
};
void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b);
}
#endif //_TVG_COLOR_H_

View file

@ -23,6 +23,7 @@
#include <fstream>
#include "tvgStr.h"
#include "tvgMath.h"
#include "tvgColor.h"
#include "tvgLoader.h"
#include "tvgXmlParser.h"
#include "tvgSvgLoader.h"
@ -573,85 +574,7 @@ static constexpr struct
};
static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* red, uint8_t* green, uint8_t* blue)
{
auto r = 0.0f, g = 0.0f, b = 0.0f;
auto i = 0;
while (hue < 0) hue += 360.0f;
hue = fmod(hue, 360.0f);
saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f;
brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f;
if (tvg::zero(saturation)) r = g = b = brightness;
else {
if (tvg::equal(hue, 360.0)) hue = 0.0f;
hue /= 60.0f;
auto v = (brightness <= 0.5f) ? (brightness * (1.0f + saturation)) : (brightness + saturation - (brightness * saturation));
auto p = brightness + brightness - v;
float sv;
if (!tvg::zero(v)) sv = (v - p) / v;
else sv = 0.0f;
i = static_cast<uint8_t>(hue);
auto f = hue - i;
auto vsf = v * sv * f;
auto t = p + vsf;
auto q = v - vsf;
switch (i) {
case 0: {
r = v;
g = t;
b = p;
break;
}
case 1: {
r = q;
g = v;
b = p;
break;
}
case 2: {
r = p;
g = v;
b = t;
break;
}
case 3: {
r = p;
g = q;
b = v;
break;
}
case 4: {
r = t;
g = p;
b = v;
break;
}
case 5: {
r = v;
g = p;
b = q;
break;
}
}
}
*red = (uint8_t)nearbyint(r * 255.0f);
*green = (uint8_t)nearbyint(g * 255.0f);
*blue = (uint8_t)nearbyint(b * 255.0f);
return true;
}
static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
static bool _toColor(const char* str, uint8_t& r, uint8_t&g, uint8_t& b, char** ref)
{
auto len = strlen(str);
@ -661,13 +584,13 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
char tmp[3] = { '\0', '\0', '\0' };
tmp[0] = str[1];
tmp[1] = str[1];
*r = strtol(tmp, nullptr, 16);
r = strtol(tmp, nullptr, 16);
tmp[0] = str[2];
tmp[1] = str[2];
*g = strtol(tmp, nullptr, 16);
g = strtol(tmp, nullptr, 16);
tmp[0] = str[3];
tmp[1] = str[3];
*b = strtol(tmp, nullptr, 16);
b = strtol(tmp, nullptr, 16);
}
return true;
} else if (len == 7 && str[0] == '#') {
@ -675,13 +598,13 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
char tmp[3] = { '\0', '\0', '\0' };
tmp[0] = str[1];
tmp[1] = str[2];
*r = strtol(tmp, nullptr, 16);
r = strtol(tmp, nullptr, 16);
tmp[0] = str[3];
tmp[1] = str[4];
*g = strtol(tmp, nullptr, 16);
g = strtol(tmp, nullptr, 16);
tmp[0] = str[5];
tmp[1] = str[6];
*b = strtol(tmp, nullptr, 16);
b = strtol(tmp, nullptr, 16);
}
return true;
} else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') {
@ -692,9 +615,9 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
if (green && *green == ',') {
auto tb = _parseColor(green + 1, &blue);
if (blue && blue[0] == ')' && blue[1] == '\0') {
*r = tr;
*g = tg;
*b = tb;
r = tr;
g = tg;
b = tb;
}
}
}
@ -704,25 +627,26 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
*ref = _idFromUrl((const char*)(str + 3));
return true;
} else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') {
float th, ts, tb;
tvg::HSL hsl;
const char* content = _skipSpace(str + 4, nullptr);
const char* hue = nullptr;
if (_parseNumber(&content, &hue, &th) && hue) {
if (_parseNumber(&content, &hue, &hsl.h) && hue) {
const char* saturation = nullptr;
hue = _skipSpace(hue, nullptr);
hue = (char*)_skipComma(hue);
hue = _skipSpace(hue, nullptr);
if (_parseNumber(&hue, &saturation, &ts) && saturation && *saturation == '%') {
if (_parseNumber(&hue, &saturation, &hsl.s) && saturation && *saturation == '%') {
const char* brightness = nullptr;
ts /= 100.0f;
hsl.s /= 100.0f;
saturation = _skipSpace(saturation + 1, nullptr);
saturation = (char*)_skipComma(saturation);
saturation = _skipSpace(saturation, nullptr);
if (_parseNumber(&saturation, &brightness, &tb) && brightness && *brightness == '%') {
tb /= 100.0f;
if (_parseNumber(&saturation, &brightness, &hsl.l) && brightness && *brightness == '%') {
hsl.l /= 100.0f;
brightness = _skipSpace(brightness + 1, nullptr);
if (brightness && brightness[0] == ')' && brightness[1] == '\0') {
return _hslToRgb(th, ts, tb, r, g, b);
hsl2rgb(hsl.h, hsl.s, hsl.l, r, g, b);
return true;
}
}
}
@ -731,9 +655,9 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
//Handle named color
for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
if (!strcasecmp(colors[i].name, str)) {
*r = (((uint8_t*)(&(colors[i].value)))[2]);
*g = (((uint8_t*)(&(colors[i].value)))[1]);
*b = (((uint8_t*)(&(colors[i].value)))[0]);
r = ((uint8_t*)(&(colors[i].value)))[2];
g = ((uint8_t*)(&(colors[i].value)))[1];
b = ((uint8_t*)(&(colors[i].value)))[0];
return true;
}
}
@ -964,14 +888,14 @@ static void _handlePaintAttr(SvgPaint* paint, const char* value)
paint->none = false;
return;
}
if (_toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url)) paint->none = false;
if (_toColor(value, paint->color.r, paint->color.g, paint->color.b, &paint->url)) paint->none = false;
}
static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
{
SvgStyleProperty* style = node->style;
if (_toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr)) {
if (_toColor(value, style->color.r, style->color.g, style->color.b, nullptr)) {
style->curColorSet = true;
}
}
@ -2710,7 +2634,7 @@ static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
stop->g = latestColor->g;
stop->b = latestColor->b;
}
} else if (_toColor(value, &stop->r, &stop->g, &stop->b, nullptr)) {
} else if (_toColor(value, stop->r, stop->g, stop->b, nullptr)) {
loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopColor);
}
} else {
@ -2740,7 +2664,7 @@ static bool _attrParseStops(void* data, const char* key, const char* value)
stop->b = latestColor->b;
}
} else if (!(loader->svgParse->flags & SvgStopStyleFlags::StopColor)) {
_toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
_toColor(value, stop->r, stop->g, stop->b, nullptr);
}
} else if (STR_AS(key, "style")) {
xmlParseW3CAttribute(value, strlen(value), _attrParseStopsStyle, data);

View file

@ -26,6 +26,9 @@
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgInlist.h"
#include "tvgColor.h"
using SvgColor = tvg::RGB;
struct Box
{
@ -428,11 +431,6 @@ struct SvgComposite
bool applying; //flag for checking circular dependency.
};
struct SvgColor
{
uint8_t r, g, b;
};
struct SvgPaint
{
SvgStyleGradient* gradient;

View file

@ -28,11 +28,13 @@
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgLock.h"
#include "tvgColor.h"
namespace tvg
{
using RenderData = void*;
using RenderColor = tvg::RGBA;
using pixel_t = uint32_t;
#define DASH_PATTERN_THRESHOLD 0.001f
@ -81,11 +83,6 @@ struct RenderSurface
}
};
struct RenderColor
{
uint8_t r, g, b, a;
};
struct RenderCompositor
{
MaskMethod method;