diff --git a/examples/Blending.cpp b/examples/Blending.cpp index 4c18c76f..857d8208 100644 --- a/examples/Blending.cpp +++ b/examples/Blending.cpp @@ -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); diff --git a/inc/thorvg.h b/inc/thorvg.h index 8c5e3807..af7c9407 100644 --- a/inc/thorvg.h +++ b/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 }; diff --git a/src/common/tvgColor.cpp b/src/common/tvgColor.cpp index 7ef104a4..8ab24f4c 100644 --- a/src/common/tvgColor.cpp +++ b/src/common/tvgColor.cpp @@ -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); diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index b101889c..18eefcf4 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -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; } } diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h index 5c1e9ca6..de85cbb4 100644 --- a/src/renderer/sw_engine/tvgSwCommon.h +++ b/src/renderer/sw_engine/tvgSwCommon.h @@ -26,6 +26,7 @@ #include #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_ */ diff --git a/src/renderer/sw_engine/tvgSwRaster.cpp b/src/renderer/sw_engine/tvgSwRaster.cpp index 37b46349..55e57764 100644 --- a/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1769,4 +1769,40 @@ void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32 } } } -} \ No newline at end of file +} + + +//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 + } + } +} diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index 7e375b04..265fc59f 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -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; diff --git a/src/renderer/tvgPaint.cpp b/src/renderer/tvgPaint.cpp index a3d5c6b7..42ed7074 100644 --- a/src/renderer/tvgPaint.cpp +++ b/src/renderer/tvgPaint.cpp @@ -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; }