sw_engine: fix radial gradient when focal point is outside circle on ARM/Apple Silicon

sw_engine: radial gradient
[issues 2014: radial gradient](#2014)

Radial gradient results in a corrupted image when the focal point is outside the circle on Apple Silicon.
This happens because some compilers use FMA to optimize the a = dr² - dx² - dy² calculation,
which cause loss of precision.

We rely on temporary variables to prevent FMA.
We could also use compiler specific float contraction control pragmas to avoid this if this doesn't work in the future
This commit is contained in:
Lorcán Mc Donagh 2024-03-07 16:13:33 +01:00 committed by Hermet Park
parent 851f8eaabc
commit 612b52a3e3
3 changed files with 20 additions and 1 deletions

View file

@ -26,3 +26,4 @@ SergeyLebedkin <sergii@lottiefiles.com>
Jinny You <jinny@lottiefiles.com>
Nattu Adnan <nattu@reallynattu.com>
Gabor Kiss-Vamosi <kisvegabor@gmail.com>
Lorcán Mc Donagh <lorcan@lmdsp.com>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="800" height="590" version="1.1" id="https://github.com/thorvg/thorvg/issues/2014" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs>
<radialGradient cx="429" cy="3" fx="429" fy="-600" gradientUnits="userSpaceOnUse" id="b" r="600">
<stop offset="0" stop-color="red" id="stop10" />
<stop offset="1" stop-color="white" id="stop11" />
</radialGradient>
</defs>
<path d="M 800,200 H 0 V 590 H 800 V 0 Z" fill="url(#b)" id="svg_4" />
</svg>

After

Width:  |  Height:  |  Size: 575 B

View file

@ -201,7 +201,13 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
fill->radial.fy = cy + r * (fy - cy) / dist;
fill->radial.dx = cx - fill->radial.fx;
fill->radial.dy = cy - fill->radial.fy;
fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy;
// 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;