From 5ff9c08ba9bc5085289fc625e6bd37c8147fb832 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Sat, 27 Jul 2024 15:20:31 +0200 Subject: [PATCH] sw_engine: handle grad edge cases For a linear gradient defined by identical start and end points, and for a radial gradient with a radius of 0, the rendered shape should have the color of the last specified color stop. The documentation has been updated accordingly. @Issue: https://github.com/thorvg/thorvg/issues/2582 --- inc/thorvg.h | 5 +++- src/bindings/capi/thorvg_capi.h | 6 +++- src/renderer/sw_engine/tvgSwCommon.h | 6 ++-- src/renderer/sw_engine/tvgSwFill.cpp | 38 +++++++++++++++++------- src/renderer/sw_engine/tvgSwRaster.cpp | 16 ++++++++-- src/renderer/sw_engine/tvgSwRenderer.cpp | 4 +-- 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/inc/thorvg.h b/inc/thorvg.h index ee098590..ba2313b8 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -675,7 +675,8 @@ public: * @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds. * @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds. * - * @note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered. + * @note In case the first and the second points are equal, an object is filled with a single color using the last color specified in the colorStops(). + * @see Fill::colorStops() */ Result linear(float x1, float y1, float x2, float y2) noexcept; @@ -734,6 +735,8 @@ public: * @param[in] radius The radius of the bounding circle. * * @retval Result::InvalidArguments in case the @p radius value is zero or less. + * + * @note In case the @p radius is zero, an object is filled with a single color using the last color specified in the colorStops(). */ Result radial(float cx, float cy, float radius) noexcept; diff --git a/src/bindings/capi/thorvg_capi.h b/src/bindings/capi/thorvg_capi.h index db46ad41..dc3cab1c 100644 --- a/src/bindings/capi/thorvg_capi.h +++ b/src/bindings/capi/thorvg_capi.h @@ -1745,7 +1745,8 @@ TVG_API Tvg_Gradient* tvg_radial_gradient_new(void); * \return Tvg_Result enumeration. * \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Gradient pointer. * -* \note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered. +* \note In case the first and the second points are equal, an object is filled with a single color using the last color specified in the tvg_gradient_set_color_stops(). +* \see tvg_gradient_set_color_stops() */ TVG_API Tvg_Result tvg_linear_gradient_set(Tvg_Gradient* grad, float x1, float y1, float x2, float y2); @@ -1781,6 +1782,9 @@ TVG_API Tvg_Result tvg_linear_gradient_get(Tvg_Gradient* grad, float* x1, float* * * \return Tvg_Result enumeration. * \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Gradient pointer or the @p radius value less than zero. +* +* \note In case the @p radius is zero, an object is filled with a single color using the last color specified in the specified in the tvg_gradient_set_color_stops(). +* \see tvg_gradient_set_color_stops() */ TVG_API Tvg_Result tvg_radial_gradient_set(Tvg_Gradient* grad, float cx, float cy, float radius); diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h index b63a864e..24519a01 100644 --- a/src/renderer/sw_engine/tvgSwCommon.h +++ b/src/renderer/sw_engine/tvgSwCommon.h @@ -153,6 +153,7 @@ struct SwFill uint32_t* ctable; FillSpread spread; + bool solid = false; //solid color fill with the last color from colorStops bool translucent; }; @@ -525,6 +526,7 @@ void imageReset(SwImage* image); void imageFree(SwImage* image); bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable); +const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata); void fillReset(SwFill* fill); void fillFree(SwFill* fill); @@ -560,11 +562,11 @@ SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx); void mpoolRetDashOutline(SwMpool* mpool, unsigned idx); bool rasterCompositor(SwSurface* surface); -bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity); bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix& transform, const SwBBox& bbox, uint8_t opacity); bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); -bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity); bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h); void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len); diff --git a/src/renderer/sw_engine/tvgSwFill.cpp b/src/renderer/sw_engine/tvgSwFill.cpp index 57265fe0..631294ad 100644 --- a/src/renderer/sw_engine/tvgSwFill.cpp +++ b/src/renderer/sw_engine/tvgSwFill.cpp @@ -125,6 +125,8 @@ static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end) static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity) { + if (fill->solid) return true; + if (!fill->ctable) { fill->ctable = static_cast(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); if (!fill->ctable) return false; @@ -214,7 +216,12 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr fill->linear.dy = y2 - y1; auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; - if (len < FLOAT_EPSILON) return true; + if (len < FLOAT_EPSILON) { + if (mathZero(fill->linear.dx) && mathZero(fill->linear.dy)) { + fill->solid = true; + } + return true; + } fill->linear.dx /= len; fill->linear.dy /= len; @@ -254,7 +261,10 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& tr auto fy = P(radial)->fy; auto fr = P(radial)->fr; - if (r < FLOAT_EPSILON) return true; + if (mathZero(r)) { + fill->solid = true; + return true; + } fill->radial.dr = r - fr; fill->radial.dx = cx - fx; @@ -818,19 +828,26 @@ bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, fill->spread = fdata->spread(); - if (ctable) { - if (!_updateColorTable(fill, fdata, surface, opacity)) return false; - } - if (fdata->identifier() == TVG_CLASS_ID_LINEAR) { - return _prepareLinear(fill, static_cast(fdata), transform); + if (!_prepareLinear(fill, static_cast(fdata), transform)) return false; } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { - return _prepareRadial(fill, static_cast(fdata), transform); + if (!_prepareRadial(fill, static_cast(fdata), transform)) return false; } - //LOG: What type of gradient?! + if (ctable) return _updateColorTable(fill, fdata, surface, opacity); + return true; +} - return false; + +const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata) +{ + if (!fill->solid) return nullptr; + + const Fill::ColorStop* colors; + auto cnt = fdata->colorStops(&colors); + if (cnt == 0 || !colors) return nullptr; + + return colors + cnt - 1; } @@ -841,6 +858,7 @@ void fillReset(SwFill* fill) fill->ctable = nullptr; } fill->translucent = false; + fill->solid = false; } diff --git a/src/renderer/sw_engine/tvgSwRaster.cpp b/src/renderer/sw_engine/tvgSwRaster.cpp index 3db4e189..a64ca830 100644 --- a/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1912,10 +1912,16 @@ void rasterPremultiply(Surface* surface) } -bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) +bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity) { if (!shape->fill) return false; + if (auto color = fillFetchSolid(shape->fill, fdata)) { + auto a = MULTIPLY(color->a, opacity); + return a > 0 ? rasterShape(surface, shape, color->r, color->g, color->b, a) : true; + } + + auto id = fdata->identifier(); if (shape->fastTrack) { if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill); else if (id == TVG_CLASS_ID_RADIAL)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill); @@ -1927,10 +1933,16 @@ bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) } -bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id) +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity) { if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false; + if (auto color = fillFetchSolid(shape->stroke->fill, fdata)) { + auto a = MULTIPLY(color->a, opacity); + return a > 0 ? rasterStroke(surface, shape, color->r, color->g, color->b, a) : true; + } + + auto id = fdata->identifier(); if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index 5db2b541..2f9a2356 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -332,7 +332,7 @@ static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity) { uint8_t r, g, b, a; if (auto fill = task->rshape->fill) { - rasterGradientShape(surface, &task->shape, fill->identifier()); + rasterGradientShape(surface, &task->shape, fill, opacity); } else { task->rshape->fillColor(&r, &g, &b, &a); a = MULTIPLY(opacity, a); @@ -344,7 +344,7 @@ static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity { uint8_t r, g, b, a; if (auto strokeFill = task->rshape->strokeFill()) { - rasterGradientStroke(surface, &task->shape, strokeFill->identifier()); + rasterGradientStroke(surface, &task->shape, strokeFill, opacity); } else { if (task->rshape->strokeColor(&r, &g, &b, &a)) { a = MULTIPLY(opacity, a);