mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-23 14:48:24 +00:00
sw_engine: support hue, color, saturation, luminosity, hardmix blends
- Hue: Creates a result color with the luminance and saturation of the base color and the hue of the blend color. - Color: Creates a result color with the luminance of the base color and the hue and saturation of the blend color. This preserves the gray levels in the image and is useful for coloring monochrome images and for tinting color images. - Luminosity: reates a result color with the hue and saturation of the base color and the luminance of the blend color. This mode creates the inverse effect of Color mode. - Saturation: Creates a result color with the luminance and hue of the base color and the saturation of the blend color. Painting with this mode in an area with no (0) saturation (gray) causes no change. - HardMix: Adds the bottom & top. If the resulting sum for a channel is 255 or greater, it receives a value of 255; if less than 255, a value of 0. issue: https://github.com/thorvg/thorvg/issues/2701
This commit is contained in:
parent
16604a873a
commit
4768331b3b
8 changed files with 143 additions and 22 deletions
|
@ -162,12 +162,12 @@ struct UserExample : tvgexam::Example
|
|||
blender(canvas, "SoftLight", tvg::BlendMethod::SoftLight, 900.0f, 0.0f, data);
|
||||
blender(canvas, "Difference", tvg::BlendMethod::Difference, 900.0f, 150.0f, data);
|
||||
blender(canvas, "Exclusion", tvg::BlendMethod::Exclusion, 900.0f, 300.0f, data);
|
||||
blender(canvas, "Hue (Not Supported)", tvg::BlendMethod::Hue, 900.0f, 450.0f, data);
|
||||
blender(canvas, "Saturation (Not Supported)", tvg::BlendMethod::Saturation, 900.0f, 600.0f, data);
|
||||
blender(canvas, "Color (Not Supported)", tvg::BlendMethod::Color, 900.0f, 750.0f, data);
|
||||
blender(canvas, "Luminosity (Not Supported)", tvg::BlendMethod::Luminosity, 900.0f, 900.0f, data);
|
||||
blender(canvas, "Hue", tvg::BlendMethod::Hue, 900.0f, 450.0f, data);
|
||||
blender(canvas, "Saturation", tvg::BlendMethod::Saturation, 900.0f, 600.0f, data);
|
||||
blender(canvas, "Color", tvg::BlendMethod::Color, 900.0f, 750.0f, data);
|
||||
blender(canvas, "Luminosity", tvg::BlendMethod::Luminosity, 900.0f, 900.0f, data);
|
||||
blender(canvas, "Add", tvg::BlendMethod::Add, 900.0f, 1050.0f, data);
|
||||
blender(canvas, "HardMix (Not Supported)", tvg::BlendMethod::HardMix, 900.0f, 1200.0f, data);
|
||||
blender(canvas, "HardMix", tvg::BlendMethod::HardMix, 900.0f, 1200.0f, data);
|
||||
|
||||
free(data);
|
||||
|
||||
|
|
10
inc/thorvg.h
10
inc/thorvg.h
|
@ -209,12 +209,12 @@ enum class BlendMethod : uint8_t
|
|||
SoftLight, ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (255 - 2 * S) * (D * D) + (2 * S * D)
|
||||
Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S)
|
||||
Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. S + D - (2 * S * D)
|
||||
Hue, ///< Reserved. Not supported.
|
||||
Saturation, ///< Reserved. Not supported.
|
||||
Color, ///< Reserved. Not supported.
|
||||
Luminosity, ///< Reserved. Not supported.
|
||||
Hue, ///< Combine with HSL(Sh + Ds + Dl) then convert it to RGB.
|
||||
Saturation, ///< Combine with HSL(Dh + Ss + Dl) then convert it to RGB.
|
||||
Color, ///< Combine with HSL(Sh + Ss + Dl) then convert it to RGB.
|
||||
Luminosity, ///< Combine with HSL(Dh + Ds + Sl) then convert it to RGB.
|
||||
Add, ///< Simply adds pixel values of one layer with the other. (S + D)
|
||||
HardMix, ///< Reserved. Not supported.
|
||||
HardMix, ///< Adds S and D; result is 255 if the sum is greater than or equal to 255, otherwise 0.
|
||||
Composition = 255 ///< Used for intermediate composition. @since 1.0
|
||||
};
|
||||
|
||||
|
|
|
@ -27,11 +27,6 @@ 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;
|
||||
|
@ -53,6 +48,7 @@ void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b)
|
|||
auto vsf = v * sv * f;
|
||||
auto t = p + vsf;
|
||||
auto q = v - vsf;
|
||||
float tr, tg, tb;
|
||||
|
||||
switch (i) {
|
||||
case 0: tr = v; tg = t; tb = p; break;
|
||||
|
@ -61,7 +57,7 @@ void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b)
|
|||
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;
|
||||
default: tr = tg = tb = 0.0f; break;
|
||||
}
|
||||
r = (uint8_t)nearbyint(tr * 255.0f);
|
||||
g = (uint8_t)nearbyint(tg * 255.0f);
|
||||
|
|
|
@ -645,7 +645,7 @@ static bool _toColor(const char* str, uint8_t& r, uint8_t&g, uint8_t& b, char**
|
|||
hsl.l /= 100.0f;
|
||||
brightness = _skipSpace(brightness + 1, nullptr);
|
||||
if (brightness && brightness[0] == ')' && brightness[1] == '\0') {
|
||||
hsl2rgb(hsl.h, hsl.s, hsl.l, r, g, b);
|
||||
hsl2rgb(hsl.h, tvg::clamp(hsl.s, 0.0f, 1.0f), tvg::clamp(hsl.l, 0.0f, 1.0f), r, g, b);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <algorithm>
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgMath.h"
|
||||
#include "tvgColor.h"
|
||||
#include "tvgRender.h"
|
||||
|
||||
#define SW_CURVE_TYPE_POINT 0
|
||||
|
@ -564,6 +565,80 @@ static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d)
|
|||
return BLEND_PRE(JOIN(255, f(C1(s), o.r), f(C2(s), o.g), f(C3(s), o.b)), s, o.a);
|
||||
}
|
||||
|
||||
void rasterRGB2HSL(uint8_t r, uint8_t g, uint8_t b, float* h, float* s, float* l);
|
||||
|
||||
static inline uint32_t opBlendHue(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float sh, ds, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), &sh, 0, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, 0, &ds, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(sh, ds, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendSaturation(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float dh, ss, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), 0, &ss, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, &dh, 0, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(dh, ss, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendColor(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float sh, ss, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), &sh, &ss, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, 0, 0, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(sh, ss, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendLuminosity(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float dh, ds, sl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), 0, 0, &sl);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, &dh, &ds, 0);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(dh, ds, sl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendHardMix(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
auto f = [](uint8_t s, uint8_t d) {
|
||||
return (s + d >= 255) ? 255 : 0;
|
||||
};
|
||||
|
||||
return BLEND_PRE(JOIN(255, f(C1(s), o.r), f(C2(s), o.g), f(C3(s), o.b)), s, o.a);
|
||||
}
|
||||
|
||||
|
||||
int64_t mathMultiply(int64_t a, int64_t b);
|
||||
int64_t mathDivide(int64_t a, int64_t b);
|
||||
|
@ -679,4 +754,6 @@ bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct);
|
|||
void effectTritoneUpdate(RenderEffectTritone* effect);
|
||||
bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct);
|
||||
|
||||
|
||||
|
||||
#endif /* _TVG_SW_COMMON_H_ */
|
||||
|
|
|
@ -1769,4 +1769,40 @@ void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: can be moved in tvgColor
|
||||
void rasterRGB2HSL(uint8_t r, uint8_t g, uint8_t b, float* h, float* s, float* l)
|
||||
{
|
||||
auto rf = r / 255.0f;
|
||||
auto gf = g / 255.0f;
|
||||
auto bf = b / 255.0f;
|
||||
auto maxVal = std::max(std::max(rf, gf), bf);
|
||||
auto minVal = std::min(std::min(rf, gf), bf);
|
||||
auto delta = maxVal - minVal;
|
||||
|
||||
//lightness
|
||||
float t;
|
||||
if (l || s) {
|
||||
t = (maxVal + minVal) * 0.5f;
|
||||
if (l) *l = t;
|
||||
}
|
||||
|
||||
if (tvg::zero(delta)) {
|
||||
if (h) *h = 0.0f;
|
||||
if (s) *s = 0.0f;
|
||||
} else {
|
||||
//saturation
|
||||
if (s) {
|
||||
*s = (t < 0.5f) ? (delta / (maxVal + minVal)) : (delta / (2.0f - maxVal - minVal));
|
||||
}
|
||||
//hue
|
||||
if (h) {
|
||||
if (maxVal == rf) *h = (gf - bf) / delta + (gf < bf ? 6.0f : 0.0f);
|
||||
else if (maxVal == gf) *h = (bf - rf) / delta + 2.0f;
|
||||
else *h = (rf - gf) / delta + 4.0f;
|
||||
*h *= 60.0f; //directly convert to degrees
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -564,9 +564,24 @@ bool SwRenderer::blend(BlendMethod method)
|
|||
case BlendMethod::Exclusion:
|
||||
surface->blender = opBlendExclusion;
|
||||
break;
|
||||
case BlendMethod::Hue:
|
||||
surface->blender = opBlendHue;
|
||||
break;
|
||||
case BlendMethod::Saturation:
|
||||
surface->blender = opBlendSaturation;
|
||||
break;
|
||||
case BlendMethod::Color:
|
||||
surface->blender = opBlendColor;
|
||||
break;
|
||||
case BlendMethod::Luminosity:
|
||||
surface->blender = opBlendLuminosity;
|
||||
break;
|
||||
case BlendMethod::Add:
|
||||
surface->blender = opBlendAdd;
|
||||
break;
|
||||
case BlendMethod::HardMix:
|
||||
surface->blender = opBlendHardMix;
|
||||
break;
|
||||
default:
|
||||
TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method);
|
||||
surface->blender = nullptr;
|
||||
|
|
|
@ -429,10 +429,7 @@ uint8_t Paint::opacity() const noexcept
|
|||
|
||||
Result Paint::blend(BlendMethod method) noexcept
|
||||
{
|
||||
//TODO: Remove later
|
||||
if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport;
|
||||
|
||||
if (method == BlendMethod::Composition || method <= BlendMethod::HardMix) {
|
||||
if (method <= BlendMethod::HardMix || method == BlendMethod::Composition) {
|
||||
pImpl->blend(method);
|
||||
return Result::Success;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue