mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-25 09:21:55 +00:00
renderer: improve radial gradient handling
This change ensures at the api level that if the focal point lies outside the end circle, it is projected onto the edge of the end circle. Additionally, if the start circle does not fully fit inside the end circle (after possible repositioning), its radius is reduced accordingly. The modification aligns with the SVG 1.1 standard (for fr = 0). Cases with fr > 0 are not covered by the SVG 1.1, and edge cases have been handled here to avoid numerical issues. Note: This update replaces previous behavior where gl handled the SVG 2.0 standard.
This commit is contained in:
parent
22742863f4
commit
30662afba7
7 changed files with 51 additions and 28 deletions
|
@ -886,6 +886,8 @@ public:
|
|||
* @retval Result::InvalidArguments in case the radius @p r or @p fr value is negative.
|
||||
*
|
||||
* @note In case the radius @p r is zero, an object is filled with a single color using the last color specified in the colorStops().
|
||||
* @note In case the focal point (@p fx and @p fy) lies outside the end circle, it is projected onto the edge of the end circle.
|
||||
* @note If the start circle doesn't fully fit inside the end circle (after possible repositioning), the @p fr is reduced accordingly.
|
||||
* @note By manipulating the position and size of the focal point, a wide range of visual effects can be achieved, such as directing
|
||||
* the gradient focus towards a specific edge or enhancing the depth and complexity of shading patterns.
|
||||
* If a focal effect is not desired, simply align the focal point (@p fx and @p fy) with the center of the end circle (@p cx and @p cy)
|
||||
|
|
|
@ -1663,6 +1663,8 @@ TVG_API Tvg_Result tvg_linear_gradient_get(Tvg_Gradient* grad, float* x1, float*
|
|||
* @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Gradient pointer or the radius @p r or @p fr value is negative.
|
||||
*
|
||||
* @note In case the radius @p r 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().
|
||||
* @note In case the focal point (@p fx and @p fy) lies outside the end circle, it is projected onto the edge of the end circle.
|
||||
* @note If the start circle doesn't fully fit inside the end circle (after possible repositioning), the @p fr is reduced accordingly.
|
||||
* @note By manipulating the position and size of the focal point, a wide range of visual effects can be achieved, such as directing
|
||||
* the gradient focus towards a specific edge or enhancing the depth and complexity of shading patterns.
|
||||
* If a focal effect is not desired, simply align the focal point (@p fx and @p fy) with the center of the end circle (@p cx and @p cy)
|
||||
|
|
|
@ -519,7 +519,8 @@ Fill* LottieGradient::fill(float frameNo, uint8_t opacity, Tween& tween, LottieE
|
|||
if (tvg::zero(progress)) {
|
||||
static_cast<RadialGradient*>(fill)->radial(s.x, s.y, r, s.x, s.y, 0.0f);
|
||||
} else {
|
||||
if (tvg::equal(progress, 1.0f)) progress = 0.99f;
|
||||
//TODO: apply if SVG2.0 std is applied to the radial gradient in the engines
|
||||
//progress = tvg::clamp(progress, -0.99f, 0.99f);
|
||||
auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x));
|
||||
auto angle = deg2rad((startAngle + this->angle(frameNo, tween, exps)));
|
||||
auto fx = s.x + cos(angle) * progress * r;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "tvgGlRenderTask.h"
|
||||
#include "tvgGlProgram.h"
|
||||
#include "tvgGlShaderSrc.h"
|
||||
#include "tvgFill.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
|
@ -412,6 +413,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
|
|||
|
||||
float x, y, r, fx, fy, fr;
|
||||
radialFill->radial(&x, &y, &r, &fx, &fy, &fr);
|
||||
CONST_RADIAL(radialFill)->correct(fx, fy, fr);
|
||||
|
||||
gradientBlock.centerPos[0] = fx;
|
||||
gradientBlock.centerPos[1] = fy;
|
||||
|
|
|
@ -245,11 +245,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& pT
|
|||
{
|
||||
float cx, cy, r, fx, fy, fr;
|
||||
radial->radial(&cx, &cy, &r, &fx, &fy, &fr);
|
||||
|
||||
if (tvg::zero(r)) {
|
||||
fill->solid = true;
|
||||
return true;
|
||||
}
|
||||
if ((fill->solid = !CONST_RADIAL(radial)->correct(fx, fy, fr))) return true;
|
||||
|
||||
fill->radial.dr = r - fr;
|
||||
fill->radial.dx = cx - fx;
|
||||
|
@ -258,28 +254,9 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& pT
|
|||
fill->radial.fx = fx;
|
||||
fill->radial.fy = fy;
|
||||
fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy;
|
||||
|
||||
//This condition fulfills the SVG 1.1 std:
|
||||
//the focal point, if outside the end circle, is moved to be on the end circle
|
||||
//See: the SVG 2 std requirements: https://www.w3.org/TR/SVG2/pservers.html#RadialGradientNotes
|
||||
constexpr float precision = 0.01f;
|
||||
if (fill->radial.a <= precision) {
|
||||
auto dist = sqrtf(fill->radial.dx * fill->radial.dx + fill->radial.dy * fill->radial.dy);
|
||||
//retract focal point slightly from edge to avoid numerical errors:
|
||||
fill->radial.fx = cx + r * (1.0f - precision) * (fx - cx) / dist;
|
||||
fill->radial.fy = cy + r * (1.0f - precision) * (fy - cy) / dist;
|
||||
fill->radial.dx = cx - fill->radial.fx;
|
||||
fill->radial.dy = cy - fill->radial.fy;
|
||||
// Prevent loss of precision on Apple Silicon when dr=dy and dx=0 due to FMA
|
||||
// https://github.com/thorvg/thorvg/issues/2014
|
||||
auto dr2 = fill->radial.dr * fill->radial.dr;
|
||||
auto dx2 = fill->radial.dx * fill->radial.dx;
|
||||
auto dy2 = fill->radial.dy * fill->radial.dy;
|
||||
|
||||
fill->radial.a = dr2 - dx2 - dy2;
|
||||
}
|
||||
|
||||
if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a;
|
||||
if (fill->radial.a < precision) fill->radial.a = precision;
|
||||
fill->radial.invA = 1.0f / fill->radial.a;
|
||||
|
||||
const auto& transform = pTransform * radial->transform();
|
||||
|
||||
|
|
|
@ -130,6 +130,42 @@ struct RadialGradientImpl : RadialGradient
|
|||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
//TODO: remove this logic once SVG 2.0 is adopted by sw and wg engines (gl already supports it); lottie-specific handling will then be delegated entirely to the loader
|
||||
//clamp focal point and shrink start circle if needed to avoid invalid gradient setup
|
||||
bool correct(float& fx, float& fy, float& fr) const
|
||||
{
|
||||
constexpr float precision = 0.01f;
|
||||
|
||||
//a solid fill case. It can be handled by engine.
|
||||
if (this->r < precision) return false;
|
||||
|
||||
fx = this->fx;
|
||||
fy = this->fy;
|
||||
fr = this->fr;
|
||||
|
||||
auto dx = this->cx - this->fx;
|
||||
auto dy = this->cy - this->fy;
|
||||
auto dist = sqrtf(dx * dx + dy * dy);
|
||||
|
||||
//move the focal point to the edge (just inside) if it's outside the end circle
|
||||
if (this->r - dist < precision) {
|
||||
//handle special case: small radius and small distance -> shift focal point to avoid div-by-zero
|
||||
if (dist < precision) dist = dx = precision;
|
||||
auto scale = this->r * (1.0f - precision) / dist;
|
||||
fx = this->cx - dx * scale;
|
||||
fy = this->cy - dy * scale;
|
||||
dx = this->cx - fx;
|
||||
dy = this->cy - fy;
|
||||
dist = sqrtf(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
//if the start circle doesn't fit entirely within the end circle, shrink it (with epsilon margin)
|
||||
auto maxFr = (this->r - dist) * (1.0f - precision);
|
||||
if (this->fr > maxFr) fr = std::max(0.0f, maxFr);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "tvgWgShaderTypes.h"
|
||||
#include <cassert>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgFill.h"
|
||||
|
||||
//************************************************************************
|
||||
// WgShaderTypeMat4x4f
|
||||
|
@ -139,8 +140,10 @@ void WgShaderTypeGradSettings::update(const Fill* fill)
|
|||
// update gradient base points
|
||||
if (fill->type() == Type::LinearGradient)
|
||||
((LinearGradient*)fill)->linear(&coords.vec[0], &coords.vec[1], &coords.vec[2], &coords.vec[3]);
|
||||
else if (fill->type() == Type::RadialGradient)
|
||||
else if (fill->type() == Type::RadialGradient) {
|
||||
((RadialGradient*)fill)->radial(&coords.vec[0], &coords.vec[1], &coords.vec[2], &focal.vec[0], &focal.vec[1], &focal.vec[2]);
|
||||
CONST_RADIAL(fill)->correct(focal.vec[0], focal.vec[1], focal.vec[2]);
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************************************
|
||||
|
|
Loading…
Add table
Reference in a new issue