mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
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:
parent
a5f69a9bbc
commit
5ff9c08ba9
6 changed files with 57 additions and 18 deletions
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue