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
This commit is contained in:
Mira Grudzinska 2024-07-27 15:20:31 +02:00 committed by Hermet Park
parent a5f69a9bbc
commit 5ff9c08ba9
6 changed files with 57 additions and 18 deletions

View file

@ -675,7 +675,8 @@ public:
* @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds. * @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. * @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; 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. * @param[in] radius The radius of the bounding circle.
* *
* @retval Result::InvalidArguments in case the @p radius value is zero or less. * @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; Result radial(float cx, float cy, float radius) noexcept;

View file

@ -1745,7 +1745,8 @@ TVG_API Tvg_Gradient* tvg_radial_gradient_new(void);
* \return Tvg_Result enumeration. * \return Tvg_Result enumeration.
* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Gradient pointer. * \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); 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. * \return Tvg_Result enumeration.
* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Gradient pointer or the @p radius value less than zero. * \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); TVG_API Tvg_Result tvg_radial_gradient_set(Tvg_Gradient* grad, float cx, float cy, float radius);

View file

@ -153,6 +153,7 @@ struct SwFill
uint32_t* ctable; uint32_t* ctable;
FillSpread spread; FillSpread spread;
bool solid = false; //solid color fill with the last color from colorStops
bool translucent; bool translucent;
}; };
@ -525,6 +526,7 @@ void imageReset(SwImage* image);
void imageFree(SwImage* image); void imageFree(SwImage* image);
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable); 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 fillReset(SwFill* fill);
void fillFree(SwFill* fill); void fillFree(SwFill* fill);
@ -560,11 +562,11 @@ SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx);
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx); void mpoolRetDashOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface); 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 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 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 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); 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 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); void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);

View file

@ -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) static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{ {
if (fill->solid) return true;
if (!fill->ctable) { if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
if (!fill->ctable) return false; if (!fill->ctable) return false;
@ -214,7 +216,12 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr
fill->linear.dy = y2 - y1; fill->linear.dy = y2 - y1;
auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; 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.dx /= len;
fill->linear.dy /= 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 fy = P(radial)->fy;
auto fr = P(radial)->fr; 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.dr = r - fr;
fill->radial.dx = cx - fx; fill->radial.dx = cx - fx;
@ -818,19 +828,26 @@ bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform,
fill->spread = fdata->spread(); fill->spread = fdata->spread();
if (ctable) {
if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
}
if (fdata->identifier() == TVG_CLASS_ID_LINEAR) { if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform); if (!_prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform)) return false;
} else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform); if (!_prepareRadial(fill, static_cast<const RadialGradient*>(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->ctable = nullptr;
} }
fill->translucent = false; fill->translucent = false;
fill->solid = false;
} }

View file

@ -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 (!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 (shape->fastTrack) {
if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill); 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); 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 (!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); 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); else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill);

View file

@ -332,7 +332,7 @@ static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{ {
uint8_t r, g, b, a; uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) { if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill->identifier()); rasterGradientShape(surface, &task->shape, fill, opacity);
} else { } else {
task->rshape->fillColor(&r, &g, &b, &a); task->rshape->fillColor(&r, &g, &b, &a);
a = MULTIPLY(opacity, 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; uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) { if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill->identifier()); rasterGradientStroke(surface, &task->shape, strokeFill, opacity);
} else { } else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) { if (task->rshape->strokeColor(&r, &g, &b, &a)) {
a = MULTIPLY(opacity, a); a = MULTIPLY(opacity, a);