Jump to content

VeLC

Members
  • Posts

    17
  • Joined

  • Last visited

  • Days Won

    6

Posts posted by VeLC

  1. Resizes and draws line/s of text to a specified width

     

    Screenshot-SlabText.png.3d237468834101222f5e723372ad275b.png

    Found in Effects > Text Formations

     

    Versions:

    1.0(20220513) - Initial Release

    1.1(20220519) - Add 'Convert to all caps' and 'Set spacing from baseline' options. Prevent crash when selected font does not contain glyph/s of typed letters.

     

    C# Code Snippet (Modified from CodeLab generated code)

    License: LPGL-3.0

     

    Spoiler
            protected override void OnRender(Rectangle[] rois, int startIndex, int length)
            {
                if (length == 0 || IsCancelRequested) return;
                Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds);
            }
    
            #region UICode
            private MultiLineTextboxControl text = ""; // [1000] 
            private CheckboxControl allCaps = false; // Convert to all caps
            private FontFamily fontFamily = new("Arial"); // 
            private CheckboxControl bold = false; // Bold
            private CheckboxControl italic = false; // Italic
            private IntSliderControl targetWidth = 150; // [1,1000] Width
            private IntSliderControl spacing = 15; // [0,100] Spacing
            private CheckboxControl baseline = false; // Set spacing from baseline
            private PanSliderControl center = Pair.Create(0.000, 0.000); // Center
            #endregion
    
            private void Render(Surface dst, Surface src, Rectangle rect)
            {
                dst.CopySurface(src, rect.Location, rect);
                if (text == string.Empty || EnvironmentParameters.PrimaryColor.A == 0) return;
    
                using Graphics g = new RenderArgs(dst).Graphics;
                using Region gClipRegion = new(rect);
    
                g.Clip = gClipRegion;
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.TextRenderingHint = TextRenderingHint.AntiAlias;
    
                float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width);
                float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height);
                g.TranslateTransform(centerX, centerY);
    
                StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries;
                string[] lines = text.Split(new string[] { "\r\n", "\r", "\n" }, options);
    
                Pair<Bitmap, Rectangle>[] pairs = new Pair<Bitmap, Rectangle>[lines.Length];
                int totalHeight = 0;
    
                try
                {
                    for (int i = 0; i < lines.Length; ++i)
                    {
                        string line = allCaps ? lines[i].ToUpperInvariant() : lines[i];
                        pairs[i] = MakeBitmap(g, line);
                        if (IsCancelRequested) return;
                        totalHeight += pairs[i].Second.Height;
                    }
                    totalHeight += (lines.Length - 1) * spacing;
    
                    g.TranslateTransform(targetWidth / -2f, totalHeight / -2f);
    
                    foreach (Pair<Bitmap, Rectangle> pair in pairs)
                    {
                        if (IsCancelRequested) return;
                        g.DrawImageUnscaled(pair.First, -pair.Second.Left, -pair.Second.Top);
                        g.TranslateTransform(0, pair.Second.Height + spacing);
                    }
                }
                finally
                {
                    foreach (Pair<Bitmap, Rectangle> pair in pairs)
                    {
                        pair.First?.Dispose();
                    }
                }
            }
    
            private Pair<Bitmap, Rectangle> MakeBitmap(Graphics g, string line)
            {
                FontStyle fontStyle = FontStyle.Regular;
                if (bold) fontStyle |= FontStyle.Bold;
                if (italic) fontStyle |= FontStyle.Italic;
    
                using Brush brush = new SolidBrush(EnvironmentParameters.PrimaryColor);
    
                StringFormat format = new(StringFormat.GenericDefault);
                format.FormatFlags |= StringFormatFlags.NoFontFallback;
    
                Font refFont = new(fontFamily, 10, fontStyle, GraphicsUnit.Pixel);
                float refWidth = g.MeasureString(line, refFont, short.MaxValue, format).Width;
    
                while (!IsCancelRequested)
                {
                    Font newFont = new(fontFamily, refFont.Size * targetWidth / refWidth, fontStyle, GraphicsUnit.Pixel);
                    SizeF newSize = g.MeasureString(line, newFont, short.MaxValue, format);
    
                    int width = (int)Math.Ceiling(newSize.Width);
                    int height = (int)Math.Ceiling(newSize.Height);
    
                    Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
    
                    using Graphics bmpG = Graphics.FromImage(bmp);
                    bmpG.SmoothingMode = SmoothingMode.AntiAlias;
                    bmpG.TextRenderingHint = TextRenderingHint.AntiAlias;
                    bmpG.Clear(Color.Transparent);
                    bmpG.DrawString(line, newFont, brush, 0, 0, format);
    
                    Rectangle boundingBox = GetBoundingBox(bmp);
    
                    if (boundingBox.Width == targetWidth)
                    {
                        if (baseline)
                        {
                            int bottom = GetBaseline(g, newFont, brush, format);
                            boundingBox = Rectangle.FromLTRB(boundingBox.Left, boundingBox.Top, boundingBox.Right, bottom);
                        }
                        newFont.Dispose();
                        refFont.Dispose();
                        return new(bmp, boundingBox);
                    }
                    else
                    {
                        bmp.Dispose();
                        refFont.Dispose();
                        refFont = newFont;
                        refWidth = boundingBox.Width;
                    }
                }
                refFont.Dispose();
                return default;
            }
    
            private static int GetBaseline(Graphics g, Font font, Brush brush, StringFormat format)
            {
                SizeF size = g.MeasureString("Z", font, short.MaxValue, format);
                int width = (int)Math.Ceiling(size.Width);
                int height = (int)Math.Ceiling(size.Height);
    
                using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
                using Graphics bmpG = Graphics.FromImage(bmp);
                bmpG.SmoothingMode = SmoothingMode.AntiAlias;
                bmpG.TextRenderingHint = TextRenderingHint.AntiAlias;
                bmpG.Clear(Color.Transparent);
                bmpG.DrawString("Z", font, brush, 0, 0, format);
    
                return GetBoundingBox(bmp).Bottom;
            }
    
            private static Rectangle GetBoundingBox(Bitmap bmp)
            {
                Rectangle rect = new(0, 0, bmp.Width, bmp.Height);
                BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    
                byte[] bytes = new byte[bmpData.Stride * bmpData.Height];
                Marshal.Copy(bmpData.Scan0, bytes, 0, bytes.Length);
    
                int bottom = GetBottom(bytes, bmpData);
                int left = GetLeft(bytes, bmpData);
                int right = GetRight(bytes, bmpData);
                int top = GetTop(bytes, bmpData);
                bmp.UnlockBits(bmpData);
    
                return Rectangle.FromLTRB(left, top, right, bottom);
            }
    
            private static int GetBottom(byte[] bytes, BitmapData data)
            {
                for (int row = data.Height - 1; row >= 0; --row)
                {
                    for (int col = 0; col < data.Width; ++col)
                    {
                        int alpha = bytes[row * data.Stride + col * 4 + 3];
                        if (alpha > 0) return row;
                    }
                }
                return data.Height - 1;
            }
    
            private static int GetLeft(byte[] bytes, BitmapData data)
            {
                for (int col = 0; col < data.Width; ++col)
                {
                    for (int row = 0; row < data.Height; ++row)
                    {
                        int alpha = bytes[row * data.Stride + col * 4 + 3];
                        if (alpha > 0) return col;
                    }
                }
                return 0;
            }
    
            private static int GetRight(byte[] bytes, BitmapData data)
            {
                for (int col = data.Width - 1; col >= 0; --col)
                {
                    for (int row = 0; row < data.Height; ++row)
                    {
                        int alpha = bytes[row * data.Stride + col * 4 + 3];
                        if (alpha > 0) return col;
                    }
                }
                return data.Width - 1;
            }
    
            private static int GetTop(byte[] bytes, BitmapData data)
            {
                for (int row = 0; row < data.Height; ++row)
                {
                    for (int col = 0; col < data.Width; ++col)
                    {
                        int alpha = bytes[row * data.Stride + col * 4 + 3];
                        if (alpha > 0) return row;
                    }
                }
                return 0;
            }

     

     

    SlabText.zip

    • Like 6
    • Upvote 2
  2. Draws text along an Archimedean spiral path

     

    Screenshot-SpiralText.png.12e6f18bb881782266026d6bc7379145.png

    Found in Effects > Text Formations. The menu name is "Spir@l Text" to differentiate with dpy's SpiralText

     

    Versions:

    1.0(20220506) - Initial Release

    1.1(20220507) - Add Loop Spacing and Initial Point Offset options

    1.2(20220507) - Code Update and slightly shorter UI

     

    CodeLab Code

    License: LPGL-3.0

     

    Spoiler
    // Name: Spir@l Text
    // Submenu: Text Formations
    // Author: Louie Velarde
    // Title: Spiral Text
    // Version: 1.2
    // Desc: Draws text along an Archimedean spiral path
    // Keywords: spiral text
    // URL: https://forums.getpaint.net/topic/119979-spiral-text/
    // Help: https://forums.getpaint.net/topic/119979-spiral-text/
    // Force Single Render Call
    #region UICode
    TextboxControl text = ""; // [18000]
    IntSliderControl repeat = 1; // [1,1800] Repeat
    FontFamily fontFamily = new FontFamily("Arial"); //
    IntSliderControl fontSize = 12; // [8,288]
    CheckboxControl bold = false; // Bold
    PanSliderControl center = Pair.Create(0.000, 0.000); // Center
    ListBoxControl spiralType = 0; // |Clockwise Inwards|Clockwise Outwards|Counterclockwise Inwards|Counterclockwise Outwards
    AngleControl rotation = 0; // [0,360]
    CheckboxControl useFontSize = true; // Calculate spacing based on font size
    IntSliderControl charSpacing = 0; // [0, 100] Character Spacing
    DoubleSliderControl lineSpacing = 1; // [0.001, 10] {useFontSize} Line Spacing
    IntSliderControl lineHeight = 25; // [1, 100] {!useFontSize} Loop Spacing
    ListBoxControl offsetType = 0; // |Initial Loop Number | Initial Point Offset
    IntSliderControl offset = 2; // [0,100]
    #endregion
    
    bool rendered;
    
    void PreRender(Surface dst, Surface src)
    {
        rendered = false;
    }
    
    void Render(Surface dst, Surface src, Rectangle rect)
    {
        if (rendered) return;
        rendered = true;
    
        rect = EnvironmentParameters.SelectionBounds;
        dst.CopySurface(src, rect.Location, rect);
    
        if (text == string.Empty) return;
    
        StringBuilder sb = new StringBuilder(text);
        for (int i = 2; i <= repeat; ++i)
        {
            if (IsCancelRequested) return;
            sb.Append(text);
        }
    
        using (Graphics g = new RenderArgs(dst).Graphics)
        using (Region gClipRegion = new Region(rect))
        using (Font font = new Font(fontFamily, fontSize, bold ? FontStyle.Bold : FontStyle.Regular))
        using (Brush brush = new SolidBrush(EnvironmentParameters.PrimaryColor))
        {
            g.Clip = gClipRegion;
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.TextRenderingHint = TextRenderingHint.AntiAlias;
            
            float centerX = (float) (rect.Left + (center.First + 1) / 2 * rect.Width);
            float centerY = (float) (rect.Top + (center.Second + 1) / 2 * rect.Height);
    
            g.TranslateTransform(centerX, centerY);
    
            StringFormat format = new StringFormat(StringFormat.GenericTypographic);
            format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
            format.FormatFlags |= StringFormatFlags.NoFontFallback;
            
            double totalHeights = 0;
            SizeF[] sizes = new SizeF[sb.Length];
    
            for (int i = 0; i < sb.Length; ++i)
            {
                if (IsCancelRequested) return;
                sizes[i] = g.MeasureString(sb[i].ToString(), font, short.MaxValue, format);
                totalHeights += sizes[i].Height;
            }
            double avgHeight = totalHeights / sb.Length;
            double b = (useFontSize ? avgHeight * lineSpacing : lineHeight) / Math.PI / 2;
    
            double len = offsetType == 0
                ? b * Math.Pow(Math.PI * 2 * offset, 2) / 2
                : offset * offset / b / 2;
            double radian = Math.Sqrt(2 * len / b);
    
            if (spiralType < 2)
            {
                g.RotateTransform((float) (90 - rotation));
            }
            else
            {
                g.RotateTransform((float) (-90 - rotation));
            }
    
            if ((spiralType & 1) == 0)
            {
                int s = spiralType == 0 ? -1 : 1;
                double radius = b * radian * s;
                double toDeg = 180 / Math.PI * s;
    
                int startIndex = sb.Length - 1;
                g.DrawString(sb[startIndex].ToString(), font, brush, sizes[startIndex].Width / -2, (float) (radius - avgHeight / 2), format);
    
                for (int i = startIndex - 1; i >=0; --i)
                {
                    if (IsCancelRequested) return;
                    len += sizes[i + 1].Width / 2 + charSpacing + sizes[i].Width / 2;
    
                    double newRadian = Math.Sqrt(2 * len / b);
                    g.RotateTransform((float) ((newRadian - radian) * toDeg));
    
                    radius = b * newRadian * s;
                    g.DrawString(sb[i].ToString(), font, brush, sizes[i].Width / -2, (float) (radius - avgHeight / 2), format);
    
                    radian = newRadian;
                }
            }
            else
            {
                int s = spiralType == 1 ? -1 : 1;
                double radius = b * radian * s;
                double toDeg = 180 / Math.PI * s * -1;
    
                g.DrawString(sb[0].ToString(), font, brush, sizes[0].Width / -2, (float) (radius - avgHeight / 2), format);
    
                for (int i = 1; i < sb.Length; ++i)
                {
                    if (IsCancelRequested) return;
                    len += sizes[i - 1].Width / 2 + charSpacing + sizes[i].Width / 2;
    
                    double newRadian = Math.Sqrt(2 * len / b);
                    g.RotateTransform((float) ((newRadian - radian) * toDeg));
    
                    radius = b * newRadian * s;
                    g.DrawString(sb[i].ToString(), font, brush, sizes[i].Width / -2, (float) (radius - avgHeight / 2), format);
    
                    radian = newRadian;
                }
            }
        }
    }

     

     

     

    SpiralText.zip

    • Like 3
    • Upvote 3
    • You're a Smart Cookie! 1
  3. Draws dots along the path of an Archimedean spiral

     

    Screenshot-ArchimedeanDots.png.c55a39cdf03cc4dc44f2a565dd6838c8.png

    Found in Effects > Render

     

    Versions:

    1.0(20220502) - Initial Release

    1.1(20220504) - Add Initial Loop Number / Initial Point Offset option. Add Center on Top option to change the order on which the dots are drawn and/or colored.

     

    C# Code Snippet (Modified from CodeLab generated code)

    License: LPGL-3.0

     

    Spoiler
            protected override void OnRender(Rectangle[] rois, int startIndex, int length)
            {
                if (length == 0 || IsCancelRequested) return;
                Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds);
            }
    
            #region UICode
            private DoubleSliderControl loops = 5; // [2,100] Loops
            private IntSliderControl spacing = 25; // [1,100] Loop Spacing
            private ListBoxControl stepType = 1; // |Point Alignment|Point Spacing
            private DoubleSliderControl step = 10; // [1,360]
            private ListBoxControl offsetType = 1; // |Initial Loop Number|Initial Point Offset
            private IntSliderControl offset = 0; // [0,100]
            private IntSliderControl size = 5; // [1,100] Dot Size && Color
            private ListBoxControl color = 0; // |Primary Color|Secondary Color|Raindow 1|Rainbow 2
            private IntSliderControl startHue = 0; // [0,360,1]
            private IntSliderControl transparency = 255; // [0,255,4]
            private CheckboxControl centerOnTop = true; // Center on Top
            private CheckboxControl clockwise = true; // Clockwise
            private PanSliderControl center = Pair.Create(0.000, 0.000); // Center
            private AngleControl rotation = 0; // [0,360] Rotation
            #endregion
    
            private List<Vector3> cachedPoints;
            private State cachedState;
    
            private void Render(Surface dst, Surface src, Rectangle rect)
            {
                dst.CopySurface(src, rect.Location, rect);
    
                State state = new(this);
                List<Vector3> points = state.Equals(cachedState) ? cachedPoints : stepType == 0 ? GenerateAlignedPoints() : GenerateEquidistantPoints();
                if (IsCancelRequested) return;
    
                List<Vector3> ordered;
                if (centerOnTop || color < 2)
                {
                    ordered = points;
                }
                else
                {
                    ordered = new(points);
                    ordered.Reverse();
                }
    
                using Graphics g = new RenderArgs(dst).Graphics;
                using Region gClipRegion = new(rect);
                g.Clip = gClipRegion;
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.TextRenderingHint = TextRenderingHint.AntiAlias;
    
                float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width);
                float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height);
    
                g.TranslateTransform(centerX, centerY);
                g.RotateTransform((float)-rotation);
    
                using SolidBrush brush = new(Color.Transparent);
    
                if (color == 0)
                {
                    brush.Color = EnvironmentParameters.PrimaryColor;
                }
                else if (color == 1)
                {
                    brush.Color = EnvironmentParameters.SecondaryColor;
                }
                HsvColor hsv = new(startHue == 0 ? 360 : startHue - 1, 100, 100);
    
                float halfSize = size / 2f;
    
                foreach (Vector3 v in ordered)
                {
                    if (IsCancelRequested) return;
    
                    if (color == 2)
                    {
                        hsv.Hue = (hsv.Hue + 1) % 361;
                        ColorBgra bgra = hsv.ToColor();
                        bgra.A = (byte)transparency;
                        brush.Color = bgra;
                    }
                    else if (color == 3)
                    {
                        hsv.Hue = (startHue + (int)Math.Round(v.Z, MidpointRounding.ToEven)) % 361;
                        ColorBgra bgra = hsv.ToColor();
                        bgra.A = (byte)transparency;
                        brush.Color = bgra;
                    }
                    g.FillEllipse(brush, v.X - halfSize, v.Y - halfSize, size, size);
                }
                cachedPoints = points;
                cachedState = state;
            }
    
            private List<Vector3> GenerateAlignedPoints()
            {
                double a = offsetType == 1 ? offset : offset * spacing;
                double b = spacing / 360.0;
                double toRad = Math.PI / (clockwise ? 180 : -180);
    
                List<Vector3> points = new();
                for (double degree = loops * 360; degree >= 0; degree -= step)
                {
                    if (IsCancelRequested) return null;
    
                    double radius = a + b * degree;
                    (double sin, double cos) = Math.SinCos(degree * toRad);
                    float x = (float)(radius * cos);
                    float y = (float)(radius * sin);
                    points.Add(new Vector3(x, y, (float)degree));
                }
                return points;
            }
    
            private List<Vector3> GenerateEquidistantPoints()
            {
                double a = offsetType == 1 ? offset : offset * spacing;
                double b = spacing / Math.PI / 2;
    
                List<Vector3> points = new();
                for (double radian = loops * Math.PI * 2; radian >= 0;)
                {
                    if (IsCancelRequested) return null;
    
                    double radius = a + b * radian;
                    (double sin, double cos) = Math.SinCos(clockwise ? radian : -radian);
                    float x = (float)(radius * cos);
                    float y = (float)(radius * sin);
                    float z = (float)(radian * 180 / Math.PI);
                    points.Add(new Vector3(x, y, z));
    
                    radian -= step / radius;
                }
                return points;
            }
    
            internal class State
            {
                private readonly double loops;
                private readonly int spacing;
                private readonly byte stepType;
                private readonly double step;
                private readonly byte offsetType;
                private readonly int offset;
                private readonly bool clockwise;
    
                public State(ArchimedeanDotsEffectPlugin plugin)
                {
                    loops = plugin.loops;
                    spacing = plugin.spacing;
                    stepType = plugin.stepType;
                    step = plugin.step;
                    offsetType = plugin.offsetType;
                    offset = plugin.offset;
                    clockwise = plugin.clockwise;
                }
    
                public override bool Equals(object obj)
                {
                    return obj is State state &&
                           loops == state.loops &&
                           spacing == state.spacing &&
                           stepType == state.stepType &&
                           step == state.step &&
                           offsetType == state.offsetType &&
                           offset == state.offset &&
                           clockwise == state.clockwise;
                }
    
                public override int GetHashCode()
                {
                    return HashCode.Combine(loops, spacing, stepType, step, offsetType, offset, clockwise);
                }
            }
        }

     

     

     

    ArchimedeanDots.zip

    • Like 4
    • Upvote 1
  4. Draws an Archimedean spiral

     

    Screenshot-ArchimedeanSpiral.png.3bed7a6df11cda279a050be28a6ef0be.png

    Found in Effects > Render

     

    Versions:

    1.0(20220502) - Initial Release

    1.1(20220503) - Add option for Initial Loop Number / larger Initial Point Offset increments

    1.2(20220506) - Fix Initial Point Offset "typo"

     

    C# Code Snippet (Modified from CodeLab generated code)

    License: LPGL-3.0

     

    Spoiler
            protected override void OnRender(Rectangle[] rois, int startIndex, int length)
            {
                if (length == 0 || IsCancelRequested)
                {
                    return;
                }
    
                using CancellationTokenSource cts = new();
                using System.Timers.Timer timer = new(20);
                timer.AutoReset = true;
                timer.Elapsed += (s, e) =>
                {
                    if (IsCancelRequested)
                    {
                        cts.Cancel();
                    }
                };
                timer.Start();
    
                try
                {
                    Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds, cts.Token);
                }
                catch (OperationCanceledException)
                {
                }
                timer.Stop();
            }
    
            #region UICode
            private DoubleSliderControl loops = 5; // [2,1000] Loops
            private IntSliderControl spacing = 25; // [1,100] Loop Spacing
            private DoubleSliderControl step = 10; // [1,360] Point Alignment
            private ListBoxControl offsetType = 1; // |Initial Loop Number|Initial Point Offset
            private IntSliderControl offset = 0; // [0,100]
            private DoubleSliderControl tension = 0.5; // [0,1] Line Curvature
            private IntSliderControl width = 1; // [1,100] Line Width
            private CheckboxControl clockwise = true; // Clockwise
            private PanSliderControl center = Pair.Create(0.000, 0.000); // Center
            private AngleControl rotation = 0; // [0,360] Rotation
            #endregion
    
            private PointF[] cachedPoints;
            private State cachedState;
    
            private void Render(Surface dst, Surface src, Rectangle rect, CancellationToken token)
            {
                dst.CopySurface(src, rect.Location, rect);
    
                using Graphics g = new RenderArgs(dst).Graphics;
                using Region gClipRegion = new(rect);
    
                g.Clip = gClipRegion;
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.TextRenderingHint = TextRenderingHint.AntiAlias;
    
                float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width);
                float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height);
    
                g.TranslateTransform(centerX, centerY);
                g.RotateTransform((float)-rotation);
    
                if (IsCancelRequested) return;
    
                PointF[] points;
                State state = new(this);
    
                if (state.Equals(cachedState))
                {
                    points = cachedPoints;
                }
                else
                {
                    double a = offsetType == 1 ? offset : offset * spacing;
                    double b = spacing / 360.0;
                    double toRad = Math.PI / (clockwise ? 180 : -180);
    
                    points = Enumerable
                        .Range(0, (int)(loops * 360 / step + 1))
                        .AsParallel()
                        .AsOrdered()
                        .WithCancellation(token)
                        .Select(i =>
                        {
                            double theta = i * step;
                            double r = a + b * theta;
                            double rad = theta * toRad;
    
                            (double sin, double cos) = Math.SinCos(rad);
                            float x = (float)(r * cos);
                            float y = (float)(r * sin);
                            return new PointF(x, y);
                        })
                        .ToArray();
                }
    
                using Pen pen = new(EnvironmentParameters.PrimaryColor, width);
                pen.StartCap = LineCap.Round;
                pen.EndCap = LineCap.Round;
                g.DrawCurve(pen, points, (float)tension);
    
                cachedPoints = points;
                cachedState = state;
            }
    
            internal class State
            {
                private readonly double loops;
                private readonly int spacing;
                private readonly double step;
                private readonly byte offsetType;
                private readonly int offset;
                private readonly bool clockwise;
    
                public State(ArchimedeanSpiralEffectPlugin plugin)
                {
                    loops = plugin.loops;
                    spacing = plugin.spacing;
                    step = plugin.step;
                    offsetType = plugin.offsetType;
                    offset = plugin.offset;
                    clockwise = plugin.clockwise;
                }
    
                public override bool Equals(object obj)
                {
                    return obj is State state &&
                           loops == state.loops &&
                           spacing == state.spacing &&
                           step == state.step &&
                           offsetType == state.offsetType &&
                           offset == state.offset &&
                           clockwise == state.clockwise;
                }
    
                public override int GetHashCode()
                {
                    return HashCode.Combine(loops, spacing, step, offsetType, offset, clockwise);
                }
            }
        }

     

     

     

     

    ArchimedeanSpiral.zip

    • Like 5
    • Upvote 1
×
×
  • Create New...