Skip to content

Commit

Permalink
sw_engine: support for radial grad focal attribs
Browse files Browse the repository at this point in the history
Till now only the end circle values of the radial grad
were supported. Now also the start circle values are
interpreted - these are the focal point and the radius
of the start circle.
In the svg_loader support for the 'fr' attrib of the radial
grad is added.

@issue: thorvg#1558
  • Loading branch information
mgrudzinska committed Aug 14, 2023
1 parent 7a35ff4 commit 559504d
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 109 deletions.
44 changes: 34 additions & 10 deletions inc/thorvg.h
Original file line number Diff line number Diff line change
Expand Up @@ -757,28 +757,52 @@ class TVG_API RadialGradient final : public Fill
/**
* @brief Sets the radial gradient bounds.
*
* The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius.
* The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius @p r.
*
* @param[in] cx The horizontal coordinate of the center of the bounding circle.
* @param[in] cy The vertical coordinate of the center of the bounding circle.
* @param[in] radius The radius of the bounding circle.
* @param[in] r The radius of the bounding circle.
*
* @return Result::Success when succeed, Result::InvalidArguments in case the @p radius value is zero or less.
* @return Result::Success when succeed, Result::InvalidArguments in case the @p r value is zero or less.
*/
Result radial(float cx, float cy, float radius) noexcept;
Result radial(float cx, float cy, float r) noexcept;

/**
* @brief Gets the radial gradient bounds.
* @brief Sets the radial gradient attributes.
*
* The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius.
* The radial gradient is defined by the end circle with a center (@p cx, @p cy) and a radius @p r and
* the start circle with a center - focal point (@p fx, @p fy) and a radius @p fr.
* The gradient will be rendered in a way that aligns the 100% gradient stop with the edge of the end circle
* and 0% gradient stop with the edge of the start circle.
*
* @param[out] cx The horizontal coordinate of the center of the bounding circle.
* @param[out] cy The vertical coordinate of the center of the bounding circle.
* @param[out] radius The radius of the bounding circle.
* @param[in] cx The horizontal coordinate of the center of the end circle.
* @param[in] cy The vertical coordinate of the center of the end circle.
* @param[in] r The radius of the end circle.
* @param[in] fx The horizontal coordinate of the center of the start circle.
* @param[in] fy The vertical coordinate of the center of the start circle.
* @param[in] fr The radius of the start circle.
*
* @return Result::Success when succeed, Result::InvalidArguments in case the @p r or @p fr value is zero or less.
*
* @BETA_API
*/
Result radial(float cx, float cy, float r, float fx, float fy, float fr) noexcept;

/**
* @brief Gets the radial gradient attributes.
*
* @param[out] cx The horizontal coordinate of the center of the end circle.
* @param[out] cy The vertical coordinate of the center of the end circle.
* @param[out] r The radius of the end circle.
* @param[out] fx The horizontal coordinate of the center of the start circle.
* @param[out] fy The vertical coordinate of the center of the start circle.
* @param[out] fr The radius of the start circle.
*
* @return Result::Success when succeed.
*
* @see Result radial(float cx, float cy, float r, float fx, float fy, float fr) noexcept
*/
Result radial(float* cx, float* cy, float* radius) const noexcept;
Result radial(float* cx, float* cy, float* r, float* fx = nullptr, float* fy = nullptr, float* fr = nullptr) const noexcept;

/**
* @brief Creates a new RadialGradient object.
Expand Down
13 changes: 9 additions & 4 deletions src/lib/sw_engine/tvgSwCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,12 @@ struct SwFill
};

struct SwRadial {
float a11, a12, shiftX;
float a21, a22, shiftY;
float detSecDeriv;
float a;
float a11, a12, a13;
float a21, a22, a23;

float fx, fy, fr;
float dx, dy, dr;
float invA, a;
};

union {
Expand Down Expand Up @@ -558,6 +560,9 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver.
void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver.

SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRleData* rleRender(const SwBBox* bbox);
Expand Down
203 changes: 141 additions & 62 deletions src/lib/sw_engine/tvgSwFill.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,18 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr

bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
{
float radius, cx, cy;
if (radial->radial(&cx, &cy, &radius) != Result::Success) return false;
if (radius < FLT_EPSILON) return true;

float invR = 1.0f / radius;
fill->radial.shiftX = -cx;
fill->radial.shiftY = -cy;
fill->radial.a = radius;
float cx, cy, r, fx, fy, fr;
if (radial->radial(&cx, &cy, &r, &fx, &fy, &fr) != Result::Success) return false;
if (r < FLT_EPSILON) return true;

fill->radial.dr = r - fr;
fill->radial.dx = cx - fx;
fill->radial.dy = cy - fy;
fill->radial.fr = fr;
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;
if (fill->radial.a > FLT_EPSILON) fill->radial.invA = 1.0f / fill->radial.a;

auto gradTransform = radial->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
Expand All @@ -169,22 +173,17 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
Matrix invTransform;
if (!mathInverse(&gradTransform, &invTransform)) return false;

fill->radial.a11 = invTransform.e11 * invR;
fill->radial.a12 = invTransform.e12 * invR;
fill->radial.shiftX += invTransform.e13;
fill->radial.a21 = invTransform.e21 * invR;
fill->radial.a22 = invTransform.e22 * invR;
fill->radial.shiftY += invTransform.e23;
fill->radial.detSecDeriv = 2.0f * fill->radial.a11 * fill->radial.a11 + 2 * fill->radial.a21 * fill->radial.a21;

fill->radial.a *= sqrt(pow(invTransform.e11, 2) + pow(invTransform.e21, 2));
fill->radial.a11 = invTransform.e11;
fill->radial.a12 = invTransform.e12;
fill->radial.a13 = invTransform.e13;
fill->radial.a21 = invTransform.e21;
fill->radial.a22 = invTransform.e22;
fill->radial.a23 = invTransform.e23;
} else {
fill->radial.a11 = fill->radial.a22 = invR;
fill->radial.a12 = fill->radial.a21 = 0.0f;
fill->radial.detSecDeriv = 2.0f * invR * invR;
fill->radial.a11 = fill->radial.a22 = 1.0f;
fill->radial.a12 = fill->radial.a13 = 0.0f;
fill->radial.a21 = fill->radial.a23 = 0.0f;
}
fill->radial.shiftX *= invR;
fill->radial.shiftY *= invR;

return true;
}
Expand Down Expand Up @@ -228,82 +227,162 @@ static inline uint32_t _pixel(const SwFill* fill, float pos)
return fill->ctable[_clamp(fill, i)];
}

/*
* quadratic equation with the following coefficients (rx and ry defined in the _calculateCoefficients()):
* A = a // fill->radial.a
* B = 2 * (dr * fr + rx * dx + ry * dy)
* C = fr^2 - rx^2 - ry^2
* Derivatives are computed with respect to dx.
* This procedure aims to optimize and eliminate the need to calculate all values from the beginning
* for consecutive x values with a constant y. The Taylor series expansions are computed as long as
* its terms are non-zero.
*/
static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, float& b, float& delta_b, float& det, float& delta_det, float& delta_delta_det, float* ddd = nullptr, float* delta_ddd = nullptr, float* delta_delta_ddd = nullptr)
{
auto radial = &fill->radial;

auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;

b = (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy) * radial->invA;
delta_b = (radial->a11 * radial->dx + radial->a21 * radial->dy) * radial->invA;

auto rr = rx * rx + ry * ry;
auto delta_rr = 2.0f * (rx * radial->a11 + ry * radial->a21) * radial->invA;
auto delta_delta_rr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA;

det = b * b + (rr - radial->fr * radial->fr) * radial->invA;
delta_det = 2.0f * b * delta_b + delta_b * delta_b + delta_rr + delta_delta_rr;
delta_delta_det = 2.0f * delta_b * delta_b + delta_delta_rr;
}

/************************************************************************/
/* External Class Implementation */
/************************************************************************/

void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
float b, delta_b, det, delta_det, delta_delta_det;
_calculateCoefficients(fill, x, y, b, delta_b, det, delta_det, delta_delta_det);

// detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
auto detSecondDerivative = fill->radial.detSecDeriv;
// detFirstDerivative = d(det)/dx
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;
if (opacity == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, alpha(cmp));
det += delta_det;
delta_det += delta_delta_det;
b += delta_b;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, MULTIPLY(opacity, alpha(cmp)));
det += delta_det;
delta_det += delta_delta_det;
b += delta_b;
}
}
}


void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;

if (opacity == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, alpha(cmp));
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = opBlendNormal(_pixel(fill, x0), *dst, alpha(cmp));
rx += radial->a11;
ry += radial->a21;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, MULTIPLY(opacity, alpha(cmp)));
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = opBlendNormal(_pixel(fill, x0), *dst, MULTIPLY(opacity, alpha(cmp)));
rx += radial->a11;
ry += radial->a21;
}
}
}


void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;

// detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
auto detSecondDerivative = fill->radial.detSecDeriv;
// detFirstDerivative = d(det)/dx
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;

for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
*dst = op(_pixel(fill, sqrtf(det)), *dst, a);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
float b, delta_b, det, delta_det, delta_delta_det;
_calculateCoefficients(fill, x, y, b, delta_b, det, delta_det, delta_delta_det);

for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = op(_pixel(fill, sqrtf(det) - b), *dst, a);
det += delta_det;
delta_det += delta_delta_det;
b += delta_b;
}
}


void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = op(_pixel(fill, x0), *dst, a);
rx += radial->a11;
ry += radial->a21;
}
}

// detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
auto detSecondDerivative = fill->radial.detSecDeriv;
// detFirstDerivative = d(det)/dx
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;

void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
float b, delta_b, det, delta_det, delta_delta_det;
_calculateCoefficients(fill, x, y, b, delta_b, det, delta_det, delta_delta_det);

if (a == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255);
*dst = op2(tmp, *dst, 255);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
det += delta_det;
delta_det += delta_delta_det;
b += delta_b;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
det += delta_det;
delta_det += delta_delta_det;
b += delta_b;
}
}
}


void fillRadialEdgeCase(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;

if (a == 255) {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto tmp = op(_pixel(fill, x0), *dst, 255);
*dst = op2(tmp, *dst, 255);
rx += radial->a11;
ry += radial->a21;
}
} else {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto tmp = op(_pixel(fill, x0), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
rx += radial->a11;
ry += radial->a21;
}
}
}
Expand Down

0 comments on commit 559504d

Please sign in to comment.