From 2ba0b471a9d6f85a846a2e5791259d427823b32c Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Tue, 10 Dec 2024 18:48:25 +0000 Subject: [PATCH] Refactored radial gradient code --- .../tests/gradients/transform-gradients.svg | 213 ++++++++++++++++++ .../tests/gradients/w3-pservers-grad-13-b.svg | 131 +++++++++++ src/svg/tests/test-svg.fluid | 14 +- src/vector/scene/scene_fill.cpp | 125 +++++----- 4 files changed, 406 insertions(+), 77 deletions(-) create mode 100644 src/svg/tests/gradients/transform-gradients.svg create mode 100644 src/svg/tests/gradients/w3-pservers-grad-13-b.svg diff --git a/src/svg/tests/gradients/transform-gradients.svg b/src/svg/tests/gradients/transform-gradients.svg new file mode 100644 index 00000000..1ae197af --- /dev/null +++ b/src/svg/tests/gradients/transform-gradients.svg @@ -0,0 +1,213 @@ + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svg/tests/gradients/w3-pservers-grad-13-b.svg b/src/svg/tests/gradients/w3-pservers-grad-13-b.svg new file mode 100644 index 00000000..34349f43 --- /dev/null +++ b/src/svg/tests/gradients/w3-pservers-grad-13-b.svg @@ -0,0 +1,131 @@ + + + + Radial gradient focal point + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/svg/tests/test-svg.fluid b/src/svg/tests/test-svg.fluid index 58737a34..7a70dd7e 100644 --- a/src/svg/tests/test-svg.fluid +++ b/src/svg/tests/test-svg.fluid @@ -67,7 +67,7 @@ function testSnake() hashTestSVG('patterns/snake.svg', 0xbb7d40f0) function testVStripes() hashTestSVG('patterns/vstripes.svg', 0x2f85dc21) end function testW3Gradients01() hashTestSVG('gradients/w3-pservers-grad-01-b.svg', 0xe637a6fa) end -function testW3Gradients02() hashTestSVG('gradients/w3-pservers-grad-02-b.svg', 0x69157174) end +function testW3Gradients02() hashTestSVG('gradients/w3-pservers-grad-02-b.svg', 0x2b80ff2f) end function testW3Gradients04() hashTestSVG('gradients/w3-pservers-grad-04-b.svg', 0x30e19fc3) end function testW3Gradients05() hashTestSVG('gradients/w3-pservers-grad-05-b.svg', 0x80b6a37d) end function testW3Gradients07() hashTestSVG('gradients/w3-pservers-grad-07-b.svg', 0xfb7be5fe) end @@ -75,9 +75,10 @@ function testW3Gradients08() hashTestSVG('gradients/w3-pservers-grad-08-b.svg', function testW3Gradients09() hashTestSVG('gradients/w3-pservers-grad-09-b.svg', 0xfd34d4f1) end function testW3Gradients10() hashTestSVG('gradients/w3-pservers-grad-10-b.svg', 0x8a32cb66) end function testW3Gradients11() hashTestSVG('gradients/w3-pservers-grad-11-b.svg', 0x4f4a58f3) end -function testW3Gradients12() hashTestSVG('gradients/w3-pservers-grad-12-b.svg', 0xd59b60ce) end -function testW3Gradients14() hashTestSVG('gradients/w3-pservers-grad-14-b.svg', 0xb26061f7) end -function testW3Gradients15() hashTestSVG('gradients/w3-pservers-grad-15-b.svg', 0x531aca96) end +function testW3Gradients12() hashTestSVG('gradients/w3-pservers-grad-12-b.svg', 0x2f3c750e) end +function testW3Gradients13() hashTestSVG('gradients/w3-pservers-grad-13-b.svg', 0x5dc91e94) end +function testW3Gradients14() hashTestSVG('gradients/w3-pservers-grad-14-b.svg', 0xd80b0bd2) end +function testW3Gradients15() hashTestSVG('gradients/w3-pservers-grad-15-b.svg', 0x1a612a1a) end function testW3Gradients22() hashTestSVG('gradients/w3-pservers-grad-22-b.svg', 0x5477019b) end function testCoarsePaper() hashTestSVG('filters/coarse_paper.svg', 0xc4158cf1) end @@ -137,10 +138,10 @@ function testContourGradient() hashTestSVG('misc/contour-gradient.svg', 0x4b8 function testBottleTree() hashTestSVG('images/bottletree.svg', 0xd7fb84ff) end function testButton() hashTestSVG('images/button.svg', 0x0e8f5a31) end -function testClock() hashTestSVG('images/clock.svg', 0xa2cf50e4) end +function testClock() hashTestSVG('images/clock.svg', 0x8b30aba4) end function testIceCube() hashTestSVG('images/icecube.svg', 0xc8dc1cd4) end function testTiger() hashTestSVG('images/tiger.svg', 0xa7b2c808) end -function testPod() hashTestSVG('images/pod.svg', 0xf51113c0) end +function testPod() hashTestSVG('images/pod.svg', 0xfd1904d6) end function testClip() hashTestSVG('masks/clip.svg', 0x209c16aa) end function testClipViewBox() hashTestSVG('masks/clip-viewbox.svg', 0xd66308b7) end @@ -264,6 +265,7 @@ function testW3TextB25() hashTestSVG('text/w3-text-ws-03-t.svg', -1) end 'testW3Gradients10', 'testW3Gradients11', 'testW3Gradients12', + 'testW3Gradients13', 'testW3Gradients14', 'testW3Gradients15', 'testW3Gradients22', diff --git a/src/vector/scene/scene_fill.cpp b/src/vector/scene/scene_fill.cpp index f3ce31af..83d5fb4e 100644 --- a/src/vector/scene/scene_fill.cpp +++ b/src/vector/scene/scene_fill.cpp @@ -122,8 +122,8 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun Path->approximation_scale(Transform.scale()); - auto render_gradient = [&](S SpreadMethod, double Span) { - typedef agg::span_gradient span_gradient_type; + auto render_gradient = [&](Method SpreadMethod, double Span) { + typedef agg::span_gradient span_gradient_type; typedef agg::renderer_scanline_aa renderer_gradient_type; span_gradient_type span_gradient(span_interpolator, SpreadMethod, *Table, 0, Span); @@ -208,56 +208,68 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun } else if (Gradient.Type IS VGT::RADIAL) { agg::point_d c, f; + + double radial_col_span = Gradient.Radius; + double focal_radius = Gradient.FocalRadius; + if (focal_radius <= 0) focal_radius = Gradient.Radius; - if ((Gradient.Flags & VGF::SCALED_CX) != VGF::NIL) c.x = x_offset + (c_width * Gradient.CenterX); - else c.x = x_offset + Gradient.CenterX; + if (Gradient.Units IS VUNIT::BOUNDING_BOX) { + // NOTE: In this mode we are stretching a 1x1 gradient square into the target path. - if ((Gradient.Flags & VGF::SCALED_CY) != VGF::NIL) c.y = y_offset + (c_height * Gradient.CenterY); - else c.y = y_offset + Gradient.CenterY; + c.x = Gradient.CenterX; + c.y = Gradient.CenterY; + if ((Gradient.Flags & (VGF::SCALED_FX|VGF::FIXED_FX)) != VGF::NIL) f.x = Gradient.FocalX; + else f.x = c.x; - if ((Gradient.Flags & VGF::SCALED_FX) != VGF::NIL) f.x = x_offset + (c_width * Gradient.FocalX); - else if ((Gradient.Flags & VGF::FIXED_FX) != VGF::NIL) f.x = x_offset + Gradient.FocalX; - else f.x = c.x; + if ((Gradient.Flags & (VGF::SCALED_FY|VGF::FIXED_FY)) != VGF::NIL) f.y = Gradient.FocalY; + else f.y = c.y; + + transform.translate(c); + transform.scale(c_width, c_height); + apply_transforms(Gradient, transform); + transform.translate(x_offset, y_offset); + transform *= Transform; + transform.invert(); - if ((Gradient.Flags & VGF::SCALED_FY) != VGF::NIL) f.y = y_offset + (c_height * Gradient.FocalY); - else if ((Gradient.Flags & VGF::FIXED_FY) != VGF::NIL) f.y = y_offset + Gradient.FocalY; - else f.y = c.y; + // Increase the gradient scale from 1.0 in order for AGG to draw a smooth gradient. - if ((c.x IS f.x) and (c.y IS f.y)) { - // Standard radial gradient, where the focal point is the same as the gradient center + radial_col_span *= MAX_SPAN; + transform.scale(MAX_SPAN); + focal_radius *= MAX_SPAN; + + c.x *= MAX_SPAN; + c.y *= MAX_SPAN; + f.x *= MAX_SPAN; + f.y *= MAX_SPAN; + } + else { + if ((Gradient.Flags & VGF::SCALED_CX) != VGF::NIL) c.x = x_offset + (c_width * Gradient.CenterX); + else c.x = x_offset + Gradient.CenterX; - double radial_col_span = Gradient.Radius; - if (Gradient.Units IS VUNIT::USERSPACE) { // Coordinates are relative to the viewport - if ((Gradient.Flags & VGF::SCALED_RADIUS) != VGF::NIL) { // Gradient is a ratio of the viewport's dimensions - radial_col_span = (ViewWidth + ViewHeight) * Gradient.Radius * 0.5; - } - } - else { // Coordinates are scaled to the bounding box - // Set radial_col_span to the wider of the width/height - if (c_height > c_width) { - radial_col_span = c_height * Gradient.Radius; - transform.scaleX(c_width / c_height); - } - else { - radial_col_span = c_width * Gradient.Radius; - transform.scaleY(c_height / c_width); - } - } + if ((Gradient.Flags & VGF::SCALED_CY) != VGF::NIL) c.y = y_offset + (c_height * Gradient.CenterY); + else c.y = y_offset + Gradient.CenterY; - constexpr double MIN_SPAN = 32; - if (radial_col_span < MIN_SPAN) { // Blending looks best if it meets a minimum span (radius) value. - transform.scale(radial_col_span * (1.0 / MIN_SPAN)); - radial_col_span = MIN_SPAN; - } - else if (radial_col_span > MAX_SPAN) { - transform.scale(radial_col_span * (1.0 / MAX_SPAN)); - radial_col_span = MAX_SPAN; - } + if ((Gradient.Flags & VGF::SCALED_FX) != VGF::NIL) f.x = x_offset + (c_width * Gradient.FocalX); + else if ((Gradient.Flags & VGF::FIXED_FX) != VGF::NIL) f.x = x_offset + Gradient.FocalX; + else f.x = c.x; + + if ((Gradient.Flags & VGF::SCALED_FY) != VGF::NIL) f.y = y_offset + (c_height * Gradient.FocalY); + else if ((Gradient.Flags & VGF::FIXED_FY) != VGF::NIL) f.y = y_offset + Gradient.FocalY; + else f.y = c.y; + if ((Gradient.Flags & VGF::SCALED_RADIUS) != VGF::NIL) { // Gradient is a ratio of the viewport's dimensions + radial_col_span = (ViewWidth + ViewHeight) * radial_col_span * 0.5; + focal_radius = (ViewWidth + ViewHeight) * focal_radius * 0.5; + } + transform.translate(c); apply_transforms(Gradient, transform); transform *= Transform; transform.invert(); + } + + if ((c.x IS f.x) and (c.y IS f.y)) { + // Standard radial gradient, where the focal point is the same as the gradient center agg::gradient_radial gradient_func; @@ -276,41 +288,12 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun // the SVG standard, the focal point had to be within the base radius. Later specifications allowed it to // be placed outside of that radius. - double radial_col_span = Gradient.Radius; - double focal_radius = Gradient.FocalRadius; - if (focal_radius <= 0) focal_radius = Gradient.Radius; - - if (Gradient.Units IS VUNIT::USERSPACE) { // Coordinates are relative to the viewport - if ((Gradient.Flags & VGF::SCALED_RADIUS) != VGF::NIL) { // Gradient is a ratio of the viewport's dimensions - radial_col_span = (ViewWidth + ViewHeight) * radial_col_span * 0.5; - focal_radius = (ViewWidth + ViewHeight) * focal_radius * 0.5; - } - } - else { // Coordinates are scaled to the bounding box - // Set radial_col_span to the wider of the width/height - if (c_height > c_width) { - radial_col_span = c_height * radial_col_span; - focal_radius = c_height * focal_radius; - transform.scaleX(c_width / c_height); - } - else { - radial_col_span = c_width * radial_col_span; - focal_radius = c_width * focal_radius; - transform.scaleY(c_height / c_width); - } - } - - // Changing the focal radius allows the client to increase or decrease the border to which the focal + // The FocalRadius allows the client to increase or decrease the border to which the focal // calculations are being made. If not supported by the underlying implementation (e.g. SVG) then - // it must match the base radius. + // FocalRadius must match the base radius. agg::gradient_radial_focus gradient_func(focal_radius, f.x - c.x, f.y - c.y); - transform.translate(c); - apply_transforms(Gradient, transform); - transform *= Transform; - transform.invert(); - if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { agg::gradient_reflect_adaptor spread_method(gradient_func); render_gradient(spread_method, radial_col_span);