From 1a14c57889e480a94c5395067c2e8bcdb8a16440 Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Mon, 20 Jan 2025 11:30:02 +0000 Subject: [PATCH 1/2] [Vector] Changed contour gradient scaling; Fixed supershape boundary calculations; Fixed PNG palette saving --- examples/gradients.fluid | 31 ++-- src/picture/picture.cpp | 8 +- src/svg/gradients.cpp | 2 +- src/svg/tests/gradients/contour-gradient.svg | 18 ++- src/svg/tests/test-svg.fluid | 2 +- .../agg/include/agg_span_gradient_contour.h | 82 +++++------ src/vector/defs/gradient.cpp | 22 +-- src/vector/scene/scene_fill.cpp | 78 +++++----- src/vector/vector.h | 17 ++- src/vector/vectors/spiral.cpp | 53 ++++--- src/vector/vectors/supershape.cpp | 135 ++++++++++-------- 11 files changed, 240 insertions(+), 208 deletions(-) diff --git a/examples/gradients.fluid b/examples/gradients.fluid index 7d1cc16bb..5078cffa6 100644 --- a/examples/gradients.fluid +++ b/examples/gradients.fluid @@ -37,43 +37,45 @@ function selectGradient(Text) elseif Text == 'Contour' then gradient.type = 'contour' gradient.x1 = 0 - gradient.x2 = 512 + gradient.x2 = 2.0 uiEvent = function(ev) - local x = math.abs(ev.x - (gradientVP.width * 0.5)) - local y = math.abs(ev.y - (gradientVP.height * 0.5)) + local x = math.abs(ev.x - (gradientVP.width * 0.5)) / gradientVP.width * 2 + local y = math.abs(ev.y - (gradientVP.height * 0.5)) / gradientVP.height * 2 + if x > y then - gradient.x1 = x + gradient.x1 = x * gradient.x2 else - gradient.x1 = y + gradient.x1 = y * gradient.x2 end end elseif Text == 'Contour Low-Res' then gradient.type = 'contour' gradient.x1 = 0 - gradient.x2 = LORES_COUNT + gradient.x2 = LORES_COUNT/256 uiEvent = function(ev) local xscale = math.abs(ev.x - (gradientVP.width * 0.5)) / gradientVP.width * 2 local yscale = math.abs(ev.y - (gradientVP.height * 0.5)) / gradientVP.height * 2 if xscale > yscale then - gradient.x1 = xscale * LORES_COUNT + gradient.x1 = xscale * LORES_COUNT/256 else - gradient.x1 = yscale * LORES_COUNT + gradient.x1 = yscale * LORES_COUNT/256 end - gradient.x2 = LORES_COUNT + gradient.x2 = LORES_COUNT/256 end elseif Text == 'Contour Shape' then contourShape.visibility = 'visible' gradient.type = 'contour' rect.visibility = 'hidden' - gradient.x1 = 211 - gradient.x2 = 256 + gradient.x1 = 0.9 + gradient.x2 = 1.0 uiEvent = function(ev) local xscale = math.abs(ev.x - (gradientVP.width * 0.5)) / gradientVP.width * 2 local yscale = math.abs(ev.y - (gradientVP.height * 0.5)) / gradientVP.height * 2 + if xscale > yscale then - gradient.x1 = xscale*256 + gradient.x1 = xscale else - gradient.x1 = yscale*256 + gradient.x1 = yscale end end elseif Text == 'Linear' then @@ -83,7 +85,8 @@ function selectGradient(Text) gradient.cY = (ev.y / gradientVP.height * 100) .. '%' gradient.x2 = (ev.x / gradientVP.width * 100) .. '%' gradient.y2 = (ev.y / gradientVP.height * 100) .. '%' - end gradient.x1 = '50%' + end + gradient.x1 = '50%' gradient.y1 = '50%' gradient.x2 = '100%' gradient.y2 = '100%' diff --git a/src/picture/picture.cpp b/src/picture/picture.cpp index 597aaf7b1..4369d1197 100644 --- a/src/picture/picture.cpp +++ b/src/picture/picture.cpp @@ -612,7 +612,13 @@ static ERR PICTURE_SaveImage(extPicture *Self, struct acSaveImage *Args) } else { png_set_IHDR(write_ptr, info_ptr, bmp->Width, bmp->Height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - png_set_PLTE(write_ptr, info_ptr, (png_colorp)bmp->Palette->Col, bmp->AmtColours); + + std::vector p(bmp->Palette->AmtColours); + for (LONG i=0; i < bmp->Palette->AmtColours; i++) { + p[i] = { bmp->Palette->Col[i].Red, bmp->Palette->Col[i].Green, bmp->Palette->Col[i].Blue }; + } + + png_set_PLTE(write_ptr, info_ptr, p.data(), bmp->AmtColours); } // On Intel CPU's the pixel format is BGR diff --git a/src/svg/gradients.cpp b/src/svg/gradients.cpp index e106af9da..a06d39809 100644 --- a/src/svg/gradients.cpp +++ b/src/svg/gradients.cpp @@ -367,7 +367,7 @@ void svgState::parse_contourgradient(const XMLTag &Tag, objVectorGradient *Gradi auto &val = Tag.Attribs[a].Value; if (val.empty()) continue; - log.trace("Processing contour gradient attribute %s = %s", Tag.Attribs[a].Name, val); + log.trace("Processing contour gradient attribute %s = %s", Tag.Attribs[a].Name, val); auto attrib = strihash(Tag.Attribs[a].Name); switch(attrib) { diff --git a/src/svg/tests/gradients/contour-gradient.svg b/src/svg/tests/gradients/contour-gradient.svg index e23874c7c..5dc59ccb7 100644 --- a/src/svg/tests/gradients/contour-gradient.svg +++ b/src/svg/tests/gradients/contour-gradient.svg @@ -9,23 +9,31 @@ + + + - + - + - - - + + + + + diff --git a/src/svg/tests/test-svg.fluid b/src/svg/tests/test-svg.fluid index 101f319fb..3f4f9bf6c 100644 --- a/src/svg/tests/test-svg.fluid +++ b/src/svg/tests/test-svg.fluid @@ -241,7 +241,7 @@ function testW3TransformAttr05() hashTestSVG('transforms/w3-coords-transformattr function testCSS() hashTestSVG('misc/css.svg', 0x586302af) end function testBrushStrokes() hashTestSVG('misc/brush-strokes.svg', 0x21e65d43) end -function testContourGradient() hashTestSVG('gradients/contour-gradient.svg', 0x5726053f) end +function testContourGradient() hashTestSVG('gradients/contour-gradient.svg', 0xab4dcff7) end function testBottleTree() hashTestSVG('images/bottletree.svg', 0xd7fb84ff) end function testButton() hashTestSVG('images/button.svg', 0x0e8f5a31) end diff --git a/src/vector/agg/include/agg_span_gradient_contour.h b/src/vector/agg/include/agg_span_gradient_contour.h index 24eeb2bf4..7614db8b1 100644 --- a/src/vector/agg/include/agg_span_gradient_contour.h +++ b/src/vector/agg/include/agg_span_gradient_contour.h @@ -26,22 +26,22 @@ #include "agg_rasterizer_outline.h" #include "agg_span_gradient.h" -#define infinity 1E20 +constexpr double infinity = 1E20; namespace agg { class gradient_contour { private: std::vector m_buffer; - int m_width; - int m_height; - int m_frame; - double m_d1; - double m_d2; + int m_width, m_height; + double m_d1; // Ranges from 0 to 254 + double m_d2; // Ranges from 0.001 to 1.0 public: - gradient_contour() : m_buffer(NULL), m_width(0), m_height(0), m_frame(10), m_d1(0), m_d2(100) { } - gradient_contour(double d1, double d2) : m_buffer(NULL), m_width(0), m_height(0), m_frame(10), m_d1(d1), m_d2(d2) { } + gradient_contour() : m_width(0), m_height(0), m_d1(0), m_d2(1.0) { } + gradient_contour(double d1, double d2) : m_width(0), m_height(0), m_d2(d2) { + m_d1 = (d1 > 254) ? 254 : d1; + } int8u* contour_create(path_storage &ps); @@ -51,19 +51,14 @@ namespace agg void d1(double d) { m_d1 = d; } void d2(double d) { m_d2 = d; } - void frame(int f) { m_frame = f; } - int frame() { return m_frame; } - int calculate(int x, int y, int d) const { if (!m_buffer.empty()) { - int px = x >> agg::gradient_subpixel_shift; - int py = y >> agg::gradient_subpixel_shift; + int px = (x >> agg::gradient_subpixel_shift) % m_width; + int py = (y >> agg::gradient_subpixel_shift) % m_height; - px %= m_width; if (px < 0) px += m_width; - py %= m_height; if (py < 0) py += m_height; - return iround(m_buffer[py * m_width + px ] * (m_d2 / 256) + m_d1) << gradient_subpixel_shift; + return iround((m_buffer[(py * m_width) + px] * m_d2) + m_d1) << gradient_subpixel_shift; } else return 0; } @@ -71,18 +66,16 @@ namespace agg static constexpr int square(int x) { return x * x; } - // DT algorithm by: Pedro Felzenszwalb + // Distance transform algorithm by: Pedro Felzenszwalb void dt(std::vector &spanf, std::vector &spang, std::vector &spanr, std::vector &spann, int length) { - int k = 0; - double s; - spann[0] = 0; - spang[0] = double(-infinity); - spang[1] = double(+infinity); + spang[0] = -infinity; + spang[1] = +infinity; + int k = 0; for (int q = 1; q <= length - 1; q++) { - s = ((spanf[q ] + square(q)) - (spanf[spann[k]] + square(spann[k]))) / (2 * q - 2 * spann[k]); + double s = ((spanf[q ] + square(q)) - (spanf[spann[k]] + square(spann[k]))) / (2 * q - 2 * spann[k]); while (s <= spang[k ]) { k--; @@ -90,34 +83,32 @@ namespace agg } k++; - spann[k ] = q; - spang[k ] = s; - spang[k + 1 ] = double(+infinity); + spann[k] = q; + spang[k] = s; + spang[k + 1] = +infinity; } - k = 0; + int j = 0; for (int q = 0; q <= length - 1; q++) { - while (spang[k + 1] < q) k++; - spanr[q] = square(q - spann[k]) + spanf[spann[k]]; + while (spang[j + 1] < q) j++; + spanr[q] = square(q - spann[j]) + spanf[spann[j]]; } } - // DT algorithm by: Pedro Felzenszwalb + // Distance transform algorithm by: Pedro Felzenszwalb int8u* gradient_contour::contour_create(path_storage &ps) { // I. Render Black And White NonAA Stroke of the Path // Path Bounding Box + Some Frame Space Around [configurable] agg::conv_curve conv(ps); double x1, y1, x2, y2; - if (agg::bounding_rect_single(conv, 0, &x1, &y1, &x2, &y2)) { // Create BW Rendering Surface - int width = int(ceil(x2 - x1)) + m_frame * 2 + 1; - int height = int(ceil(y2 - y1)) + m_frame * 2 + 1; - auto size = width * height; + auto width = int(ceil(x2 - x1)) + 1; + auto height = int(ceil(y2 - y1)) + 1; - m_buffer.resize(size); - memset(m_buffer.data(), 255, size); + m_buffer.resize(width * height); + std::fill(m_buffer.begin(), m_buffer.end(), 255); // Setup VG Engine & Render agg::rendering_buffer rb; @@ -129,7 +120,7 @@ namespace agg agg::rasterizer_outline>> ras(prim); agg::trans_affine mtx; - mtx *= agg::trans_affine_translation(-x1 + m_frame, -y1 + m_frame); + mtx.translate(-x1, -y1); agg::conv_transform> trans(conv, mtx); @@ -143,7 +134,7 @@ namespace agg for (int y=0, l=0; y < height; y++) { for (int x=0; x < width; x++, l++) { if (m_buffer[l] == 0) image[l] = 0.0; - else image[l] = double(infinity); + else image[l] = infinity; } } @@ -160,16 +151,12 @@ namespace agg // Transform along columns for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - spanf[y] = image[y * width + x]; - } + for (int y = 0; y < height; y++) spanf[y] = image[y * width + x]; // DT of 1d dt(spanf, spang, spanr, spann, height); - for (int y=0; y < height; y++) { - image[y * width + x] = spanr[y]; - } + for (int y=0; y < height; y++) image[y * width + x] = spanr[y]; } // Transform along rows @@ -195,14 +182,11 @@ namespace agg } // III. Convert To Grayscale - if (min == max) { - auto size = width * height; - memset(m_buffer.data(), 0, size); - } + if (min == max) m_buffer.clear(); else { double scale = 255.0 / (max - min); for (int y=0, l=0; y < height; y++) { - for (int x=0; x < width; x++ ,l++) m_buffer[l] = int8u(int((image[l] - min) * scale)); + for (int x=0; x < width; x++, l++) m_buffer[l] = int8u(int((image[l] - min) * scale)); } } diff --git a/src/vector/defs/gradient.cpp b/src/vector/defs/gradient.cpp index 793384e21..839cc44b9 100644 --- a/src/vector/defs/gradient.cpp +++ b/src/vector/defs/gradient.cpp @@ -198,7 +198,7 @@ static ERR VECTORGRADIENT_NewObject(extVectorGradient *Self) Self->CenterY = 0.5; Self->Radius = 0.5; Self->X1 = 0; - Self->X2 = 100; // For an effective contoured gradient, this needs to default to 100 + Self->X2 = 100.0/256.0; // For an effective contoured gradient, this needs to default to 100 Self->Flags |= VGF::SCALED_CX|VGF::SCALED_CY|VGF::SCALED_RADIUS; return ERR::Okay; } @@ -669,10 +669,12 @@ references it. The alternative is `USERSPACE`, which positions the gradient sca -FIELD- X1: Initial X coordinate for the gradient. -The `(X1, Y1)` field values define the starting coordinate for mapping linear gradients. Other gradient types ignore -these values. The gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. +For linear gradients, the `(X1, Y1)` field values define the starting coordinate for mapping linear gradients. The +gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. Coordinate values can be expressed as units that are +scaled to the target space. -Coordinate values can be expressed as percentages that are scaled to the target space. +For contour gradients, `X1` is used as the floor for the gradient colour values and `X2` acts as a multiplier. +`X1` has a range of `0 < X1 < X2` and `X2` has a range of `.01 < X2 < 4`. *********************************************************************************************************************/ @@ -695,10 +697,12 @@ static ERR VECTORGRADIENT_SET_X1(extVectorGradient *Self, Unit &Value) -FIELD- X2: Final X coordinate for the gradient. -The `(X2, Y2)` field values define the end coordinate for mapping linear gradients. Other gradient types ignore -these values. The gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. +For linear gradients, the `(X1, Y1)` field values define the starting coordinate for mapping linear gradients. The +gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. Coordinate values can be expressed as units that are +scaled to the target space. -Coordinate values can be expressed as percentages that are scaled to the target space. +For contour gradients, `X1` is used as the floor for the gradient colour values and `X2` acts as a multiplier. +`X1` has a range of `0 < X1 < X2` and `X2` has a range of `.01 < X2 < 4`. *********************************************************************************************************************/ @@ -724,7 +728,7 @@ Y1: Initial Y coordinate for the gradient. The `(X1, Y1)` field values define the starting coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. -Coordinate values can be expressed as percentages that are scaled to the target space. +Coordinate values can also be expressed as units that are scaled to the target space. *********************************************************************************************************************/ @@ -750,7 +754,7 @@ Y2: Final Y coordinate for the gradient. The `(X2, Y2)` field values define the end coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from `(X1, Y1)` to `(X2, Y2)`. -Coordinate values can be expressed as percentages that are scaled to the target space. +Coordinate values can also be expressed as units that are scaled to the target space. -END- *********************************************************************************************************************/ diff --git a/src/vector/scene/scene_fill.cpp b/src/vector/scene/scene_fill.cpp index 35f536d32..a0366a2ca 100644 --- a/src/vector/scene/scene_fill.cpp +++ b/src/vector/scene/scene_fill.cpp @@ -125,11 +125,11 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun Path->approximation_scale(Transform.scale()); - auto render_gradient = [&](Method SpreadMethod, double Span) { + auto render_gradient = [&](Method SpreadMethod, double SpanA, double SpanB) { 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); + span_gradient_type span_gradient(span_interpolator, SpreadMethod, *Table, SpanA, SpanB); renderer_gradient_type solidrender_gradient(RenderBase, span_allocator, span_gradient); if (State.mClipStack->empty()) { @@ -201,17 +201,17 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { agg::gradient_reflect_adaptor spread_method(gradient_func); - render_gradient(spread_method, span); + render_gradient(spread_method, 0, span); } else if (Gradient.SpreadMethod IS VSPREAD::REPEAT) { agg::gradient_repeat_adaptor spread_method(gradient_func); - render_gradient(spread_method, span); + render_gradient(spread_method, 0, span); } else if (Gradient.SpreadMethod IS VSPREAD::CLIP) { agg::gradient_clip_adaptor spread_method(gradient_func); - render_gradient(spread_method, span); + render_gradient(spread_method, 0, span); } - else render_gradient(gradient_func, span); + else render_gradient(gradient_func, 0, span); } else if (Gradient.Type IS VGT::RADIAL) { agg::point_d c, f; @@ -282,17 +282,17 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { agg::gradient_reflect_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::REPEAT) { agg::gradient_repeat_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::CLIP) { agg::gradient_clip_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } - else render_gradient(gradient_func, radial_col_span); + else render_gradient(gradient_func, 0, radial_col_span); } else { // Radial gradient with a displaced focal point (uses agg::gradient_radial_focus). @@ -317,17 +317,17 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { agg::gradient_reflect_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::REPEAT) { agg::gradient_repeat_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::CLIP) { agg::gradient_clip_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } - else render_gradient(gradient_func, radial_col_span); + else render_gradient(gradient_func, 0, radial_col_span); } } else if (Gradient.Type IS VGT::DIAMOND) { @@ -369,17 +369,17 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { agg::gradient_reflect_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::REPEAT) { agg::gradient_repeat_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } else if (Gradient.SpreadMethod IS VSPREAD::CLIP) { agg::gradient_clip_adaptor spread_method(gradient_func); - render_gradient(spread_method, radial_col_span); + render_gradient(spread_method, 0, radial_col_span); } - else render_gradient(gradient_func, radial_col_span); + else render_gradient(gradient_func, 0, radial_col_span); } else if (Gradient.Type IS VGT::CONIC) { agg::point_d c; @@ -417,37 +417,43 @@ static void fill_gradient(VectorState &State, const TClipRectangle &Boun transform *= Transform; transform.invert(); - render_gradient(gradient_func, radial_col_span); + render_gradient(gradient_func, 0, radial_col_span); } else if (Gradient.Type IS VGT::CONTOUR) { - auto x2 = std::clamp(Gradient.X2, 3.0, 1024.0); + // TODO: The creation of the contour gradient is expensive, but it can be cached as long as the + // path hasn't been modified. + + auto x2 = std::clamp(Gradient.X2, 0.01, 4.0); auto x1 = std::clamp(Gradient.X1, 0.0, x2); agg::gradient_contour gradient_func; - gradient_func.frame(0); // This value offsets the gradient from the path, e.g. 10 adds an x,y offset of (10,10) - gradient_func.d1(x1); // x1 and x2 alter the sampling rate and initial offset of the gradient colours - gradient_func.d2(x2); + gradient_func.d1(x1 * 256.0); // d1 is added to the DT base values + gradient_func.d2(x2); // d2 is a multiplier of the base DT value gradient_func.contour_create(*Path); - transform.translate(Bounds.left + Bounds.width(), Bounds.top + Bounds.height()); + transform.translate(Bounds.left, Bounds.top); apply_transforms(Gradient, transform); transform *= Transform; transform.invert(); + + // Regarding the repeatable spread methods, bear in mind that the nature of the contour gradient + // means that it is always masked by the target path. To achieve repetition, the span must be + // reduced by dividing by the number of desired steps. E.g. x2 * 0.5 results in the contour being + // drawn twice. - typedef agg::span_gradient span_gradient_type; - typedef agg::renderer_scanline_aa renderer_gradient_type; - span_gradient_type span_gradient(span_interpolator, gradient_func, *Table, x1, x2); - renderer_gradient_type solidrender_gradient(RenderBase, span_allocator, span_gradient); - - if (State.mClipStack->empty()) { - agg::scanline_u8 scanline; - agg::render_scanlines(Raster, scanline, solidrender_gradient); + if (Gradient.SpreadMethod IS VSPREAD::REFLECT) { + agg::gradient_reflect_adaptor spread_method(gradient_func); + render_gradient(spread_method, x1 * 256.0, x2 * 256.0); } - else { // Masked gradient - agg::alpha_mask_gray8 alpha_mask(State.mClipStack->top().m_renderer); - agg::scanline_u8_am masked_scanline(alpha_mask); - agg::render_scanlines(Raster, masked_scanline, solidrender_gradient); + else if (Gradient.SpreadMethod IS VSPREAD::REPEAT) { + agg::gradient_repeat_adaptor spread_method(gradient_func); + render_gradient(spread_method, x1 * 256.0, x2 * 256.0); + } + else if (Gradient.SpreadMethod IS VSPREAD::CLIP) { + agg::gradient_clip_adaptor spread_method(gradient_func); + render_gradient(spread_method, x1 * 256.0, x2 * 256.0); } + else render_gradient(gradient_func, x1 * 256.0, x2 * 256.0); } } diff --git a/src/vector/vector.h b/src/vector/vector.h index 6fe0f61db..1f2317031 100644 --- a/src/vector/vector.h +++ b/src/vector/vector.h @@ -951,17 +951,24 @@ inline static void save_bitmap(std::string Name, UBYTE *Data, LONG Width, LONG H fl::Width(Width), fl::Height(Height), fl::BitsPerPixel(BPP), - fl::Flags(PCF::FORCE_ALPHA_32|PCF::NEW), + fl::Flags((BPP IS 32 ? PCF::FORCE_ALPHA_32 : PCF::NIL)|PCF::NEW), fl::Path(path) }; if (pic.ok()) { - const LONG byte_width = Width * pic->Bitmap->BytesPerPixel; - UBYTE *out = pic->Bitmap->Data; + auto &bmp = pic->Bitmap; + if (BPP IS 8) { + for (ULONG i=0; i < bmp->Palette->AmtColours; i++) { + bmp->Palette->Col[i] = { .Red = UBYTE(i), .Green = UBYTE(i), .Blue = UBYTE(i), .Alpha = 255 }; + } + } + + const LONG byte_width = Width * bmp->BytesPerPixel; + UBYTE *out = bmp->Data; for (LONG y=0; y < Height; y++) { copymem(Data, out, byte_width); - out += pic->Bitmap->LineWidth; - Data += Width * pic->Bitmap->BytesPerPixel; + out += bmp->LineWidth; + Data += Width * bmp->BytesPerPixel; } pic->saveImage(NULL); } diff --git a/src/vector/vectors/spiral.cpp b/src/vector/vectors/spiral.cpp index 7481d47e6..37c7bac87 100644 --- a/src/vector/vectors/spiral.cpp +++ b/src/vector/vectors/spiral.cpp @@ -16,12 +16,12 @@ class extVectorSpiral : public extVector { static constexpr CSTRING CLASS_NAME = "VectorSpiral"; using create = pf::Create; - DOUBLE Spacing; - DOUBLE Offset; - DOUBLE Radius; - DOUBLE CX, CY; - DOUBLE Step; - DOUBLE LoopLimit; + double Spacing; + double Offset; + double Radius; + double CX, CY; + double Step; + double LoopLimit; DMF Dimensions; }; @@ -29,26 +29,23 @@ class extVectorSpiral : public extVector { static void generate_spiral(extVectorSpiral *Vector, agg::path_storage &Path) { - const DOUBLE cx = dmf::hasScaledCenterX(Vector->Dimensions) ? Vector->CX * get_parent_width(Vector) : Vector->CX; - const DOUBLE cy = dmf::hasScaledCenterY(Vector->Dimensions) ? Vector->CY * get_parent_height(Vector) : Vector->CY; + const double cx = dmf::hasScaledCenterX(Vector->Dimensions) ? Vector->CX * get_parent_width(Vector) : Vector->CX; + const double cy = dmf::hasScaledCenterY(Vector->Dimensions) ? Vector->CY * get_parent_height(Vector) : Vector->CY; - DOUBLE min_x = DBL_MAX, max_x = -DBL_MAX, min_y = DBL_MAX, max_y = -DBL_MAX; - DOUBLE angle = 0; - DOUBLE radius = Vector->Offset; - DOUBLE limit = Vector->LoopLimit * 360.0; - DOUBLE max_radius = Vector->Radius ? Vector->Radius : DBL_MAX; - DOUBLE lx = -DBL_MAX, ly = -DBL_MAX; - DOUBLE step = Vector->Step; - - if (step > 180) step = 180; - else if (step < 0.1) step = 0.1; + double min_x = DBL_MAX, max_x = -DBL_MAX, min_y = DBL_MAX, max_y = -DBL_MAX; + double angle = 0; + double radius = Vector->Offset; + double limit = Vector->LoopLimit * 360.0; + double max_radius = Vector->Radius ? Vector->Radius : DBL_MAX; + double lx = -DBL_MAX, ly = -DBL_MAX; + double step = std::clamp(Vector->Step, 0.1, 180.0); if ((max_radius IS DBL_MAX) and (limit <= 0.01)) limit = 360; else if (limit < 0.001) limit = DBL_MAX; // Ignore the loop limit in favour of radius limit for (int v=0; (v < MAX_SPIRAL_VERTICES) and (angle < limit) and (radius < max_radius); v++) { - DOUBLE x = radius * cos(angle * DEG2RAD); - DOUBLE y = radius * sin(angle * DEG2RAD); + double x = radius * cos(angle * DEG2RAD); + double y = radius * sin(angle * DEG2RAD); x += cx; y += cy; @@ -145,13 +142,13 @@ If the LoopLimit is not set, the #Radius will take precedence. *********************************************************************************************************************/ -static ERR SPIRAL_GET_LoopLimit(extVectorSpiral *Self, DOUBLE *Value) +static ERR SPIRAL_GET_LoopLimit(extVectorSpiral *Self, double *Value) { *Value = Self->LoopLimit; return ERR::Okay; } -static ERR SPIRAL_SET_LoopLimit(extVectorSpiral *Self, DOUBLE Value) +static ERR SPIRAL_SET_LoopLimit(extVectorSpiral *Self, double Value) { if (Value >= 0) { Self->LoopLimit = Value; @@ -172,13 +169,13 @@ If Spacing is undeclared, the spiral expands at an incremental rate of `Step * 0 *********************************************************************************************************************/ -static ERR SPIRAL_GET_Spacing(extVectorSpiral *Self, DOUBLE *Value) +static ERR SPIRAL_GET_Spacing(extVectorSpiral *Self, double *Value) { *Value = Self->Spacing; return ERR::Okay; } -static ERR SPIRAL_SET_Spacing(extVectorSpiral *Self, DOUBLE Value) +static ERR SPIRAL_SET_Spacing(extVectorSpiral *Self, double Value) { if (Value >= 0.0) { Self->Spacing = Value; @@ -218,13 +215,13 @@ Offset is set to zero. *********************************************************************************************************************/ -static ERR SPIRAL_GET_Offset(extVectorSpiral *Self, DOUBLE *Value) +static ERR SPIRAL_GET_Offset(extVectorSpiral *Self, double *Value) { *Value = Self->Offset; return ERR::Okay; } -static ERR SPIRAL_SET_Offset(extVectorSpiral *Self, DOUBLE Value) +static ERR SPIRAL_SET_Offset(extVectorSpiral *Self, double Value) { if (Value >= 0.0) { Self->Offset = Value; @@ -294,13 +291,13 @@ is `1.0`. Using larger values will create a spiral with jagged corners due to t *********************************************************************************************************************/ -static ERR SPIRAL_GET_Step(extVectorSpiral *Self, DOUBLE *Value) +static ERR SPIRAL_GET_Step(extVectorSpiral *Self, double *Value) { *Value = Self->Step; return ERR::Okay; } -static ERR SPIRAL_SET_Step(extVectorSpiral *Self, DOUBLE Value) +static ERR SPIRAL_SET_Step(extVectorSpiral *Self, double Value) { if (Value != 0.0) { Self->Step = Value; diff --git a/src/vector/vectors/supershape.cpp b/src/vector/vectors/supershape.cpp index c176e615e..817026514 100644 --- a/src/vector/vectors/supershape.cpp +++ b/src/vector/vectors/supershape.cpp @@ -21,14 +21,14 @@ class extVectorShape : public extVector { static constexpr CSTRING CLASS_NAME = "VectorShape"; using create = pf::Create; - DOUBLE Radius; - DOUBLE CX, CY; - DOUBLE M, N1, N2, N3, A, B, Phi; + double Radius; + double CX, CY; + double M, N1, N2, N3, A, B, Phi; LONG Vertices; - DMF Dimensions; LONG Spiral; LONG Repeat; - UBYTE Close; + DMF Dimensions; + bool Close; UBYTE Mod; }; @@ -36,7 +36,7 @@ class extVectorShape : public extVector { static void generate_supershape(extVectorShape *Vector, agg::path_storage &Path) { - DOUBLE cx = Vector->CX, cy = Vector->CY; + double cx = Vector->CX, cy = Vector->CY; agg::path_storage path_buffer, *target; if (Path.empty()) target = &Path; @@ -45,31 +45,32 @@ static void generate_supershape(extVectorShape *Vector, agg::path_storage &Path) if (dmf::hasScaledCenterX(Vector->Dimensions)) cx *= get_parent_width(Vector); if (dmf::hasScaledCenterY(Vector->Dimensions)) cy *= get_parent_height(Vector); - const DOUBLE scale = Vector->Radius; - DOUBLE rescale = 0; - DOUBLE tscale = Vector->Transform.scale(); + const double scale = Vector->Radius; + double rescale = 0; + double tscale = Vector->Transform.scale(); - DOUBLE vertices = Vector->Vertices; + double vertices = Vector->Vertices; if (vertices IS DEFAULT_VERTICES) { if (Vector->Spiral > 1) vertices *= 2; } - const DOUBLE m = Vector->M; - const DOUBLE n1 = Vector->N1; - const DOUBLE n2 = Vector->N2; - const DOUBLE n3 = Vector->N3; - DOUBLE phi_a; - if (Vector->Spiral > 1) phi_a = (agg::pi * Vector->Phi * DOUBLE(Vector->Spiral)) / vertices; + const double m = Vector->M; + const double n1 = Vector->N1; + const double n2 = Vector->N2; + const double n3 = Vector->N3; + double phi_a; + if (Vector->Spiral > 1) phi_a = (agg::pi * Vector->Phi * double(Vector->Spiral)) / vertices; else phi_a = (agg::pi * Vector->Phi) / vertices; - const DOUBLE a = 1.0 / Vector->A; - const DOUBLE b = 1.0 / Vector->B; + const double a = 1.0 / Vector->A; + const double b = 1.0 / Vector->B; + double min_x = DBL_MAX, max_x = -DBL_MAX, min_y = DBL_MAX, max_y = -DBL_MAX; LONG lx = 0x7fffffff, ly = 0x7fffffff; - for (DOUBLE i=0; i < vertices; i++) { - const DOUBLE phi = phi_a * i; - const DOUBLE t1 = pow(std::abs(a * cos(m * phi * 0.25)), n2); - const DOUBLE t2 = pow(std::abs(b * sin(m * phi * 0.25)), n3); - DOUBLE r = 1.0 / pow(t1 + t2, 1.0/n1); + for (double i=0; i < vertices; i++) { + const double phi = phi_a * i; + const double t1 = pow(std::abs(a * cos(m * phi * 0.25)), n2); + const double t2 = pow(std::abs(b * sin(m * phi * 0.25)), n3); + double r = 1.0 / pow(t1 + t2, 1.0/n1); // These additional transforms can help in building a greater library of shapes. @@ -78,14 +79,14 @@ static void generate_supershape(extVectorShape *Vector, agg::path_storage &Path) case 2: r = log(r); break; case 3: r = atan(r); break; case 4: r = exp(1.0 / r); break; - case 5: r = 1+fastPow(cos(r), 2); break; + case 5: r = 1 + fastPow(cos(r), 2); break; case 6: r = fastPow(sin(r),2); break; - case 7: r = 1+fastPow(sin(r), 2); break; + case 7: r = 1 + fastPow(sin(r), 2); break; case 8: r = fastPow(cos(r),2); break; } - DOUBLE x = r * cos(phi); - DOUBLE y = r * sin(phi); + double x = r * cos(phi); + double y = r * sin(phi); x *= scale * tscale; y *= scale * tscale; @@ -103,40 +104,56 @@ static void generate_supershape(extVectorShape *Vector, agg::path_storage &Path) if (i == 0.0) target->move_to(x, y); // Plot the vertex else target->line_to(x, y); + + if (Vector->Spiral <= 1) { // Boundary management for non-spirals + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + } } if (Vector->Spiral > 1) { - DOUBLE total = target->total_vertices(); - for (DOUBLE i=0; i < total; i++) { - DOUBLE x, y; + double total = target->total_vertices(); + for (double i=0; i < total; i++) { + double x, y; target->vertex(i, &x, &y); x = x * (i / total); y = y * (i / total); target->modify_vertex(i, x, y); + + // Boundary management + + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; } } - else if (Vector->Repeat > 1) { - target->close_polygon(); // Repeated paths are always closed. - - agg::path_storage clone(*target); - - for (LONG i=0; i < Vector->Repeat-1; i++) { - agg::trans_affine transform; - transform.scale(DOUBLE(i+1) / DOUBLE(Vector->Repeat)); - agg::conv_transform scaled_path(clone, transform); - target->concat_path(scaled_path); + else { + if (Vector->Repeat > 1) { + target->close_polygon(); // Repeated paths are always closed. + + agg::path_storage clone(*target); + + for (LONG i=0; i < Vector->Repeat-1; i++) { + agg::trans_affine transform; + transform.scale(double(i+1) / double(Vector->Repeat)); + agg::conv_transform scaled_path(clone, transform); + target->concat_path(scaled_path); + } } + else if (Vector->Close) target->close_polygon(); } - else if (Vector->Close) target->close_polygon(); - + agg::trans_affine transform; if (rescale != scale) transform.scale(scale / rescale); transform.translate(cx, cy); target->transform(transform); if (&Path != target) Path.concat_path(*target); - - Vector->Bounds = { cx - scale, cy - scale, cx + scale, cy + scale }; + + Vector->Bounds = { min_x + cx, min_y + cy, max_x + cx, max_y + cy }; } //******************************************************************************************************************** @@ -152,7 +169,7 @@ static ERR SUPER_NewObject(extVectorShape *Self) Self->B = 1; Self->Phi = 2; Self->Vertices = DEFAULT_VERTICES; - Self->Close = TRUE; + Self->Close = true; Self->GeneratePath = (void (*)(extVector *, agg::path_storage &))&generate_supershape; return ERR::Okay; } @@ -165,13 +182,13 @@ This field sets the Superformula's 'A' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_A(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_A(extVectorShape *Self, double *Value) { *Value = Self->A; return ERR::Okay; } -static ERR SUPER_SET_A(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_A(extVectorShape *Self, double Value) { Self->A = Value; reset_path(Self); @@ -186,13 +203,13 @@ This field sets the Superformula's 'B' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_B(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_B(extVectorShape *Self, double *Value) { *Value = Self->B; return ERR::Okay; } -static ERR SUPER_SET_B(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_B(extVectorShape *Self, double Value) { Self->B = Value; reset_path(Self); @@ -305,13 +322,13 @@ This field sets the Superformula's 'M' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_M(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_M(extVectorShape *Self, double *Value) { *Value = Self->M; return ERR::Okay; } -static ERR SUPER_SET_M(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_M(extVectorShape *Self, double Value) { Self->M = Value; reset_path(Self); @@ -361,13 +378,13 @@ This field sets the Superformula's 'N1' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_N1(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_N1(extVectorShape *Self, double *Value) { *Value = Self->N1; return ERR::Okay; } -static ERR SUPER_SET_N1(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_N1(extVectorShape *Self, double Value) { Self->N1 = Value; reset_path(Self); @@ -382,13 +399,13 @@ This field sets the Superformula's 'N2' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_N2(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_N2(extVectorShape *Self, double *Value) { *Value = Self->N2; return ERR::Okay; } -static ERR SUPER_SET_N2(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_N2(extVectorShape *Self, double Value) { Self->N2 = Value; reset_path(Self); @@ -403,13 +420,13 @@ This field sets the Superformula's 'N3' parameter value. *********************************************************************************************************************/ -static ERR SUPER_GET_N3(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_N3(extVectorShape *Self, double *Value) { *Value = Self->N3; return ERR::Okay; } -static ERR SUPER_SET_N3(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_N3(extVectorShape *Self, double Value) { Self->N3 = Value; reset_path(Self); @@ -427,13 +444,13 @@ that the Phi value is increased in increments of 2 until the desired effect is a *********************************************************************************************************************/ -static ERR SUPER_GET_Phi(extVectorShape *Self, DOUBLE *Value) +static ERR SUPER_GET_Phi(extVectorShape *Self, double *Value) { *Value = Self->Phi; return ERR::Okay; } -static ERR SUPER_SET_Phi(extVectorShape *Self, DOUBLE Value) +static ERR SUPER_SET_Phi(extVectorShape *Self, double Value) { if (Value >= 2.0) { Self->Phi = Value; From fc3e9a95467e1641ba3696aed65c17a1662fae69 Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Mon, 20 Jan 2025 11:30:24 +0000 Subject: [PATCH 2/2] Updated documentation --- docs/xml/modules/classes/vectorgradient.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/xml/modules/classes/vectorgradient.xml b/docs/xml/modules/classes/vectorgradient.xml index ff8bb2fa0..5d7be28f3 100644 --- a/docs/xml/modules/classes/vectorgradient.xml +++ b/docs/xml/modules/classes/vectorgradient.xml @@ -234,8 +234,8 @@ Get/Set DOUBLE -

The (X1, Y1) field values define the starting coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from (X1, Y1) to (X2, Y2).

-

Coordinate values can be expressed as percentages that are scaled to the target space.

+

For linear gradients, the (X1, Y1) field values define the starting coordinate for mapping linear gradients. The gradient will be drawn from (X1, Y1) to (X2, Y2). Coordinate values can be expressed as units that are scaled to the target space.

+

For contour gradients, X1 is used as the floor for the gradient colour values and X2 acts as a multiplier. X1 has a range of 0 < X1 < X2 and X2 has a range of .01 < X2 < 4.

@@ -245,8 +245,8 @@ Get/Set DOUBLE -

The (X2, Y2) field values define the end coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from (X1, Y1) to (X2, Y2).

-

Coordinate values can be expressed as percentages that are scaled to the target space.

+

For linear gradients, the (X1, Y1) field values define the starting coordinate for mapping linear gradients. The gradient will be drawn from (X1, Y1) to (X2, Y2). Coordinate values can be expressed as units that are scaled to the target space.

+

For contour gradients, X1 is used as the floor for the gradient colour values and X2 acts as a multiplier. X1 has a range of 0 < X1 < X2 and X2 has a range of .01 < X2 < 4.

@@ -257,7 +257,7 @@ DOUBLE

The (X1, Y1) field values define the starting coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from (X1, Y1) to (X2, Y2).

-

Coordinate values can be expressed as percentages that are scaled to the target space.

+

Coordinate values can also be expressed as units that are scaled to the target space.

@@ -268,7 +268,7 @@ DOUBLE

The (X2, Y2) field values define the end coordinate for mapping linear gradients. Other gradient types ignore these values. The gradient will be drawn from (X1, Y1) to (X2, Y2).

-

Coordinate values can be expressed as percentages that are scaled to the target space.

+

Coordinate values can also be expressed as units that are scaled to the target space.