From 2a72ef2d170aa400d415daf29dacb45d3e4cb94a Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:02:26 -0400 Subject: [PATCH 01/14] add outlineeffect2 --- Pinta.Effects/CoreEffectsExtension.cs | 1 + Pinta.Effects/Effects/OutlineEffect2.cs | 176 ++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 Pinta.Effects/Effects/OutlineEffect2.cs diff --git a/Pinta.Effects/CoreEffectsExtension.cs b/Pinta.Effects/CoreEffectsExtension.cs index 9e7a99f57e..935b32e41b 100644 --- a/Pinta.Effects/CoreEffectsExtension.cs +++ b/Pinta.Effects/CoreEffectsExtension.cs @@ -68,6 +68,7 @@ public void Initialize () PintaCore.Effects.RegisterEffect (new GaussianBlurEffect (services)); PintaCore.Effects.RegisterEffect (new GlowEffect (services)); PintaCore.Effects.RegisterEffect (new FeatherEffect (services)); + PintaCore.Effects.RegisterEffect (new OutlineEffect2 (services)); PintaCore.Effects.RegisterEffect (new InkSketchEffect (services)); PintaCore.Effects.RegisterEffect (new JuliaFractalEffect (services)); PintaCore.Effects.RegisterEffect (new MandelbrotFractalEffect (services)); diff --git a/Pinta.Effects/Effects/OutlineEffect2.cs b/Pinta.Effects/Effects/OutlineEffect2.cs new file mode 100644 index 0000000000..4147fb832d --- /dev/null +++ b/Pinta.Effects/Effects/OutlineEffect2.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Cairo; +using Pinta.Core; +using Pinta.Gui.Widgets; + +namespace Pinta.Effects; + +public sealed class OutlineEffect2 : BaseEffect +{ + + public override string Icon => Pinta.Resources.Icons.EffectsStylizeOutline; + + // Takes three passes, so must be multithreaded internally + public sealed override bool IsTileable => false; + + public override string Name => Translations.GetString ("Outline2"); + + public override bool IsConfigurable => true; + + public override string EffectMenuCategory => Translations.GetString ("Object"); + + public Outline2Data Data => (Outline2Data) EffectData!; // NRT - Set in constructor + + private readonly IChromeService chrome; + private readonly ISystemService system; + private readonly IPaletteService palette; + + public OutlineEffect2 (IServiceProvider services) + { + chrome = services.GetService (); + system = services.GetService (); + palette = services.GetService (); + EffectData = new Outline2Data (); + } + + public override void LaunchConfiguration () + => chrome.LaunchSimpleEffectDialog (this); + + protected override void Render (ImageSurface src, ImageSurface dest, RectangleI roi) + { + int top = roi.Top; + int bottom = roi.Bottom; + int left = roi.Left; + int right = roi.Right; + int srcHeight = src.Height; + int srcWidth = src.Width; + int radius = Data.Radius; + int threads = system.RenderThreads; + + ColorBgra primaryColor = palette.PrimaryColor.ToColorBgra (); + ColorBgra secondaryColor = palette.SecondaryColor.ToColorBgra (); + ConcurrentBag borderPixels = new ConcurrentBag (); + + // First pass + // Clean up dest, then collect all border pixels + Parallel.For (top, bottom + 1, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => { + var srcData = src.GetReadOnlyPixelData (); + var dstData = dest.GetPixelData (); + //var borderData = border.GetPixelData (); + + // reset dest to src + // Removing this causes preview to not update to lower radius levels + var srcRow = srcData.Slice (y * srcWidth, srcWidth); + var dstRow = dstData.Slice (y * srcWidth, srcWidth); + for (int x = left; x <= right; x++) { + dstRow[x].Bgra = srcRow[x].Bgra; + } + + // Produces different behaviour at radius == 0 and radius == 1 + // When radius == 0, only consider direct border pixels + // When radius == 1, consider border pixels on diagonal + Span pixels = stackalloc PointI[8]; + // Collect a list of pixels that surround the object (border pixels) + for (int x = left; x <= right; x++) { + PointI potentialBorderPixel = new (x, y); + if (Data.OutlineBorder && (x == 0 || x == srcWidth - 1 || y == 0 || y == srcHeight - 1)) { + borderPixels.Add (potentialBorderPixel); + } else if (src.GetColorBgra (srcData, srcWidth, potentialBorderPixel).A <= Data.Tolerance) { + // Test pixel above, below, left, & right + pixels[0] = new (x - 1, y); + pixels[1] = new (x + 1, y); + pixels[2] = new (x, y - 1); + pixels[3] = new (x, y + 1); + int pixelCount = 4; + if (radius == 1) { + // if radius == 1, also test pixels on diagonals + pixels[4] = new (x - 1, y - 1); + pixels[5] = new (x - 1, y + 1); + pixels[6] = new (x + 1, y - 1); + pixels[7] = new (x + 1, y + 1); + pixelCount = 8; + } + + for (int i = 0; i < pixelCount; i++) { + var px = pixels[i].X; + var py = pixels[i].Y; + if (px < 0 || px >= srcWidth || py < 0 || py >= srcHeight) + continue; + if (src.GetColorBgra (srcData, srcWidth, new PointI (px, py)).A > Data.Tolerance) { + borderPixels.Add (potentialBorderPixel); + // Remove comments below to draw border pixels + // You will also have to comment out the 2nd pass because it will overwrite this + //int pos = srcWidth * y + x; + //borderData[pos].Bgra = 0; + //borderData[pos].A = 255; + + break; + } + } + + + } + } + }); + + + // Second pass + // Generate outline, blend with dest + Parallel.For (top, bottom + 1, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => { + var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); + var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); + + if (radius == 0) + radius = 1; + + for (int x = left; x <= right; x++) { + byte highestAlpha = 0; + foreach (var borderPixel in relevantBorderPixels) { + if (borderPixel.X > x - radius && borderPixel.X < x + radius) { + var dx = borderPixel.X - x; + var dy = borderPixel.Y - y; + float distance = MathF.Sqrt (dx * dx + dy * dy); + if (distance <= radius) { + float mult = 1 - distance / radius; + if (mult <= 0) + continue; + byte alpha = (byte) (255 * mult); + if (alpha > highestAlpha) + highestAlpha = alpha; + } + } + } + + var color = primaryColor; + if(Data.ColorGradient) + color = ColorBgra.Blend (secondaryColor, primaryColor, highestAlpha); + if (Data.AlphaGradient && highestAlpha != 0) + highestAlpha = 255; + + var outlineColor = color.ToStraightAlpha ().NewAlpha (highestAlpha).ToPremultipliedAlpha (); + destRow[x] = ColorBgra.Blend (outlineColor, destRow[x], destRow[x].A); + } + }); + } + + public sealed class Outline2Data : EffectData + { + [Caption ("Radius"), MinimumValue (0), MaximumValue (100)] + public int Radius { get; set; } = 6; + + [Caption ("Tolerance"), MinimumValue (0), MaximumValue (255)] + public int Tolerance { get; set; } = 20; + + [Caption ("Alpha Gradient")] + public bool AlphaGradient { get; set; } = true; + + [Caption ("Color Gradient")] + public bool ColorGradient { get; set; } = true; + + [Caption ("Outline Border")] + public bool OutlineBorder { get; set; } = false; + } +} From 7f4a737eb3a9d0b5b42c62253bcc9893a9b9c1b2 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sat, 7 Sep 2024 23:01:42 -0400 Subject: [PATCH 02/14] Fixes and improvements to outlineeffect2 --- Pinta.Core/Effects/ColorBgra.cs | 21 ++++++++++++++++ Pinta.Effects/Effects/OutlineEffect2.cs | 32 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Pinta.Core/Effects/ColorBgra.cs b/Pinta.Core/Effects/ColorBgra.cs index cce963512e..70118015e9 100644 --- a/Pinta.Core/Effects/ColorBgra.cs +++ b/Pinta.Core/Effects/ColorBgra.cs @@ -207,6 +207,27 @@ public static ColorBgra FromUInt32 (uint bgra) return color; } + /// + /// Performs Alpha Blending between two colors. In practice, Alpha Blending two images produces the same result + /// as layering the top image on top of the bottom image. + /// + /// The color layered on top. Color must be in premultiplied form. See . + /// The color layered below. Color must be in premultiplied form. See . + /// Premultiplied form of the alpha-blend between colorTop and colorBottom. + public static ColorBgra AlphaBlend (ColorBgra colorTop, ColorBgra colorBottom) + { + if (colorTop.A == 255) + return colorTop; + if (colorTop.A == 0) + return colorBottom; + float cTopA = colorTop.A / 255f; + byte r = (byte) (colorTop.R + colorBottom.R * (1f - cTopA)); + byte g = (byte) (colorTop.G + colorBottom.G * (1f - cTopA)); + byte b = (byte) (colorTop.B + colorBottom.B * (1f - cTopA)); + byte a = (byte) (colorTop.A + colorBottom.A * (1f - cTopA)); + return FromBgra (b, g, r, a); + } + /// /// Smoothly blends between two colors. /// diff --git a/Pinta.Effects/Effects/OutlineEffect2.cs b/Pinta.Effects/Effects/OutlineEffect2.cs index 4147fb832d..9595f66879 100644 --- a/Pinta.Effects/Effects/OutlineEffect2.cs +++ b/Pinta.Effects/Effects/OutlineEffect2.cs @@ -58,16 +58,13 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI // Clean up dest, then collect all border pixels Parallel.For (top, bottom + 1, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => { var srcData = src.GetReadOnlyPixelData (); - var dstData = dest.GetPixelData (); - //var borderData = border.GetPixelData (); // reset dest to src // Removing this causes preview to not update to lower radius levels var srcRow = srcData.Slice (y * srcWidth, srcWidth); - var dstRow = dstData.Slice (y * srcWidth, srcWidth); - for (int x = left; x <= right; x++) { + var dstRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); + for (int x = left; x <= right; x++) dstRow[x].Bgra = srcRow[x].Bgra; - } // Produces different behaviour at radius == 0 and radius == 1 // When radius == 0, only consider direct border pixels @@ -118,7 +115,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI // Second pass - // Generate outline, blend with dest + // Generate outline and blend to dest Parallel.For (top, bottom + 1, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => { var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); @@ -128,7 +125,18 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI for (int x = left; x <= right; x++) { byte highestAlpha = 0; + + // optimization: no change if destination has max alpha already + if (destRow[x].A == 255) + continue; + + if (Data.FillObjectBackground && destRow[x].A >= Data.Tolerance) + highestAlpha = 255; + + // Grab nearest border pixel, and calculate outline alpha based off it foreach (var borderPixel in relevantBorderPixels) { + if (highestAlpha == 255) + break; if (borderPixel.X > x - radius && borderPixel.X < x + radius) { var dx = borderPixel.X - x; var dy = borderPixel.Y - y; @@ -144,14 +152,15 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI } } + // Handle color gradient / no alpha gradient option var color = primaryColor; - if(Data.ColorGradient) + if (Data.ColorGradient) color = ColorBgra.Blend (secondaryColor, primaryColor, highestAlpha); - if (Data.AlphaGradient && highestAlpha != 0) + if (!Data.AlphaGradient && highestAlpha != 0) highestAlpha = 255; - var outlineColor = color.ToStraightAlpha ().NewAlpha (highestAlpha).ToPremultipliedAlpha (); - destRow[x] = ColorBgra.Blend (outlineColor, destRow[x], destRow[x].A); + var outlineColor = color.NewAlpha (highestAlpha).ToPremultipliedAlpha (); + destRow[x] = ColorBgra.AlphaBlend (destRow[x], outlineColor); } }); } @@ -172,5 +181,8 @@ public sealed class Outline2Data : EffectData [Caption ("Outline Border")] public bool OutlineBorder { get; set; } = false; + + [Caption ("Fill Object Background")] + public bool FillObjectBackground { get; set; } = true; } } From 072bad292c134c017f476ef9409281d24aec9ec6 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:16:43 -0400 Subject: [PATCH 03/14] Add tests for outline2 --- tests/Pinta.Effects.Tests/EffectsTest.cs | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/Pinta.Effects.Tests/EffectsTest.cs b/tests/Pinta.Effects.Tests/EffectsTest.cs index 9bbaee4df6..80cacad326 100644 --- a/tests/Pinta.Effects.Tests/EffectsTest.cs +++ b/tests/Pinta.Effects.Tests/EffectsTest.cs @@ -634,10 +634,48 @@ public void AlignObject2 () effect.Data.Position = AlignPosition.Center; Utilities.TestEffect (effect, "alignobject2.png", source_image_name: "alignobjectinput.png"); } + [Test] public void AlignObject3 () { AlignObjectEffect effect = new (Utilities.CreateMockServices ()); effect.Data.Position = AlignPosition.BottomRight; Utilities.TestEffect (effect, "alignobject3.png", source_image_name: "alignobjectinput.png"); } + + [Test] + public void OutlineObject1 () + { + OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + effect.Data.Radius = 10; + effect.Data.Tolerance = 130; + effect.Data.AlphaGradient = true; + effect.Data.ColorGradient = false; + effect.Data.OutlineBorder = false; + effect.Data.FillObjectBackground = false; + Utilities.TestEffect (effect, "outline2_1.png", source_image_name: "outlineobjectinput.png"); + } + [Test] + public void OutlineObject2 () + { + OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + effect.Data.Radius = 10; + effect.Data.Tolerance = 20; + effect.Data.AlphaGradient = false; + effect.Data.ColorGradient = true; + effect.Data.OutlineBorder = true; + effect.Data.FillObjectBackground = false; + Utilities.TestEffect (effect, "outline2_2.png", source_image_name: "outlineobjectinput.png"); + } + [Test] + public void OutlineObject3 () + { + OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + effect.Data.Radius = 10; + effect.Data.Tolerance = 20; + effect.Data.AlphaGradient = true; + effect.Data.ColorGradient = true; + effect.Data.OutlineBorder = false; + effect.Data.FillObjectBackground = true; + Utilities.TestEffect (effect, "outline2_3.png", source_image_name: "outlineobjectinput.png"); + } } From bc0ab3b12c4535c5340963bd0ef37a2406a5e130 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:20:08 -0400 Subject: [PATCH 04/14] rename outline2 -> outlineobject --- Pinta.Effects/CoreEffectsExtension.cs | 2 +- .../{OutlineEffect2.cs => OutlineObjectEffect.cs} | 12 ++++++------ tests/Pinta.Effects.Tests/EffectsTest.cs | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) rename Pinta.Effects/Effects/{OutlineEffect2.cs => OutlineObjectEffect.cs} (95%) diff --git a/Pinta.Effects/CoreEffectsExtension.cs b/Pinta.Effects/CoreEffectsExtension.cs index 86edb5bde9..b4343ee608 100644 --- a/Pinta.Effects/CoreEffectsExtension.cs +++ b/Pinta.Effects/CoreEffectsExtension.cs @@ -69,7 +69,7 @@ public void Initialize () PintaCore.Effects.RegisterEffect (new GaussianBlurEffect (services)); PintaCore.Effects.RegisterEffect (new GlowEffect (services)); PintaCore.Effects.RegisterEffect (new FeatherEffect (services)); - PintaCore.Effects.RegisterEffect (new OutlineEffect2 (services)); + PintaCore.Effects.RegisterEffect (new OutlineObjectEffect (services)); PintaCore.Effects.RegisterEffect (new InkSketchEffect (services)); PintaCore.Effects.RegisterEffect (new JuliaFractalEffect (services)); PintaCore.Effects.RegisterEffect (new MandelbrotFractalEffect (services)); diff --git a/Pinta.Effects/Effects/OutlineEffect2.cs b/Pinta.Effects/Effects/OutlineObjectEffect.cs similarity index 95% rename from Pinta.Effects/Effects/OutlineEffect2.cs rename to Pinta.Effects/Effects/OutlineObjectEffect.cs index 9595f66879..80e662248f 100644 --- a/Pinta.Effects/Effects/OutlineEffect2.cs +++ b/Pinta.Effects/Effects/OutlineObjectEffect.cs @@ -8,7 +8,7 @@ namespace Pinta.Effects; -public sealed class OutlineEffect2 : BaseEffect +public sealed class OutlineObjectEffect : BaseEffect { public override string Icon => Pinta.Resources.Icons.EffectsStylizeOutline; @@ -16,24 +16,24 @@ public sealed class OutlineEffect2 : BaseEffect // Takes three passes, so must be multithreaded internally public sealed override bool IsTileable => false; - public override string Name => Translations.GetString ("Outline2"); + public override string Name => Translations.GetString ("Outline Object"); public override bool IsConfigurable => true; public override string EffectMenuCategory => Translations.GetString ("Object"); - public Outline2Data Data => (Outline2Data) EffectData!; // NRT - Set in constructor + public OutlineObjectData Data => (OutlineObjectData) EffectData!; // NRT - Set in constructor private readonly IChromeService chrome; private readonly ISystemService system; private readonly IPaletteService palette; - public OutlineEffect2 (IServiceProvider services) + public OutlineObjectEffect (IServiceProvider services) { chrome = services.GetService (); system = services.GetService (); palette = services.GetService (); - EffectData = new Outline2Data (); + EffectData = new OutlineObjectData (); } public override void LaunchConfiguration () @@ -165,7 +165,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI }); } - public sealed class Outline2Data : EffectData + public sealed class OutlineObjectData : EffectData { [Caption ("Radius"), MinimumValue (0), MaximumValue (100)] public int Radius { get; set; } = 6; diff --git a/tests/Pinta.Effects.Tests/EffectsTest.cs b/tests/Pinta.Effects.Tests/EffectsTest.cs index 80cacad326..5f365f7e25 100644 --- a/tests/Pinta.Effects.Tests/EffectsTest.cs +++ b/tests/Pinta.Effects.Tests/EffectsTest.cs @@ -645,37 +645,37 @@ public void AlignObject3 () [Test] public void OutlineObject1 () { - OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + OutlineObjectEffect effect = new (Utilities.CreateMockServices ()); effect.Data.Radius = 10; effect.Data.Tolerance = 130; effect.Data.AlphaGradient = true; effect.Data.ColorGradient = false; effect.Data.OutlineBorder = false; effect.Data.FillObjectBackground = false; - Utilities.TestEffect (effect, "outline2_1.png", source_image_name: "outlineobjectinput.png"); + Utilities.TestEffect (effect, "outlineobject1.png", source_image_name: "outlineobjectinput.png"); } [Test] public void OutlineObject2 () { - OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + OutlineObjectEffect effect = new (Utilities.CreateMockServices ()); effect.Data.Radius = 10; effect.Data.Tolerance = 20; effect.Data.AlphaGradient = false; effect.Data.ColorGradient = true; effect.Data.OutlineBorder = true; effect.Data.FillObjectBackground = false; - Utilities.TestEffect (effect, "outline2_2.png", source_image_name: "outlineobjectinput.png"); + Utilities.TestEffect (effect, "outlineobject2.png", source_image_name: "outlineobjectinput.png"); } [Test] public void OutlineObject3 () { - OutlineEffect2 effect = new (Utilities.CreateMockServices ()); + OutlineObjectEffect effect = new (Utilities.CreateMockServices ()); effect.Data.Radius = 10; effect.Data.Tolerance = 20; effect.Data.AlphaGradient = true; effect.Data.ColorGradient = true; effect.Data.OutlineBorder = false; effect.Data.FillObjectBackground = true; - Utilities.TestEffect (effect, "outline2_3.png", source_image_name: "outlineobjectinput.png"); + Utilities.TestEffect (effect, "outlineobject3.png", source_image_name: "outlineobjectinput.png"); } } From 1060e133b03f9cbb6040fdd49b81d19b33585b40 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:28:31 -0400 Subject: [PATCH 05/14] add comment --- Pinta.Effects/Effects/OutlineObjectEffect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Pinta.Effects/Effects/OutlineObjectEffect.cs b/Pinta.Effects/Effects/OutlineObjectEffect.cs index 80e662248f..3b389a09cf 100644 --- a/Pinta.Effects/Effects/OutlineObjectEffect.cs +++ b/Pinta.Effects/Effects/OutlineObjectEffect.cs @@ -13,7 +13,7 @@ public sealed class OutlineObjectEffect : BaseEffect public override string Icon => Pinta.Resources.Icons.EffectsStylizeOutline; - // Takes three passes, so must be multithreaded internally + // Takes two passes, so must be multithreaded internally public sealed override bool IsTileable => false; public override string Name => Translations.GetString ("Outline Object"); @@ -120,6 +120,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); + // otherwise produces nothing at radius == 0 if (radius == 0) radius = 1; From 9c0b1f5a5507ae24b1a7fc6850b7fc8a88b15346 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:52:55 -0400 Subject: [PATCH 06/14] Add test pngs --- .../Assets/outlineobject1.png | Bin 0 -> 2577 bytes .../Assets/outlineobject2.png | Bin 0 -> 4216 bytes .../Assets/outlineobject3.png | Bin 0 -> 3613 bytes .../Assets/outlineobject4.png | Bin 0 -> 1418 bytes .../Assets/outlineobject5.png | Bin 0 -> 1434 bytes .../Assets/outlineobjectinput.png | Bin 0 -> 1360 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobject1.png create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobject2.png create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobject3.png create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobject4.png create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobject5.png create mode 100644 tests/Pinta.Effects.Tests/Assets/outlineobjectinput.png diff --git a/tests/Pinta.Effects.Tests/Assets/outlineobject1.png b/tests/Pinta.Effects.Tests/Assets/outlineobject1.png new file mode 100644 index 0000000000000000000000000000000000000000..ab7d77bb8623776d7e90fde2791c65377bc5d80a GIT binary patch literal 2577 zcmd5;Ycv#E8{Q)_6g8P5avjP^7n2b}gNSjzgi|w>6OE<`xrD;ZlvHkETvE*(l$@fW za#!v#xim^eW;leyOu5y#4Q71fobRl4*0;XD-&)`M=iO_+?|$CrdG^|C?RZybTRB-} zSpa|>#?IOu0El=A0nqj0gbR6$7bnS3E6g#dIHI9`w*ZiNgR!B@-~Yk#nVB~TdiB0HdxS?*Y0qA`P*g5zT3VWGR&p4CQP#bg zdVKVXySu?`!j#z#5xEM<-pmNB^bYM)J>w5y@pjuNCG z_S+H%VxR?da3`>)f7}s5JMB_#sx=2&0Q;GgLuTWrH`3p&GnR*1Seh-37;6Ek@7$D; zTLAt#l-0uj8rlUL-43G+N3yDYF2Ly<%x@GTz~em%%#<5yM?S&8u`J{b(0L2wwyVjT zQ`Tw+SZ`=BNJAd7b;6i;+DrMm+J@GV0Qe5&aa@<3OGr!K@EIxaQ3(RhCjnr16i}2A zz#c?!y z{z18adxEI`6aDYW|3%wBiT$HY`LTFRV~2`luJ+-rP^Jt-H{M06Ni55L*yHL&8tTZV z-1et_>j`Xo8c~~N*e;6xkX=S_WD`)sw_GYMf#eV+yt}OQe!*m3DC0F7{}nf??J+x^ zJgVIK84uT)L?tI9ovk6ZNgzXUz=`^Zj+&e*Ao!mPFbysy(1{sx7G*|#>0a;_S$N!W zZ)6@S8Kc9nW2a@@P;>EwQ9`sZyDF*qy)GZ78ra2~EQmM2YdNAvW>V8DQ@c}=k zb$2thLMH%Iqi_Z1X$=w*wdomQg*gQSqlFkwAyecBbNoar?uIO`laL zVmVH9;>#@%eQJP`fg_FRPxeO1^6@c1{#k-di(wK*%(1#HM^i!yM&Wts#2iw;s%L6y z>R&S;UD~FOR0vKs$d`Ps7?R%qWV~LvIMljPY$!| zC}QR}V(;OlMnp<<S^z)(EtnmiAxbKvZ9tsaNdRvoxd*M=?&FXy8BZ$B{YVJ}~EL&@H3 zG*Qev*>at#s-QmV_9QOq0aMg#7f9z(Dyr^85u;>Xeg6 zq_F8i?c9f;u;RmfJA)}h^mklMd3u#W5GgfU&`VZ7JkxVC=a$WT-5psU+0J)89d*;- zdI>1JRH_|&PJ!Gvh>yO?m6Y^(;T71y^bsuiyjyPmRK$y5(FofKb7S7D!1^CA6{DIJ zCS&fr3oar2P8L1yS?)rI2T@hEWp6&|+`Tg+96k`wI61HPqVru2{3E~8jX8+VrT|C$ z$~FPxOUiK%_wm+?pM@i>tzRqa1dkZrq~+T9bTV2xzUh~H6$vtr#Dqgj>T{y!6v^AU z11d!#fsjxi72(%C-kdB0ZAc1ic`jNS@#i#WvieV%sVK%F!n7JzO+5RF_sI1#k;D;R zX!3O3;xgkcnN)Spbe(=;2ttwQkyeR{J+bH=UF5^>>wq)+@S9 ziHLraA!_Zzxkf?PL0q)1zfG^a9_4c6r1{}pGf-H+Kog{!)E}>ZBw`f2CL41r^5sre zRpoPHoXyR5OGX5WFE=<)WeJl*_X)vv7vu-jmR9GL2oH2S+*xL;oe0>(I91c%>w&)0 znKEDDPtp{`?>d7!slg8?)Sps>u ztuHNSP)WcSitXMloZ!TmF9>dDb=1fGtj9T0SVs{LYJoKRZ9;v?!;LAG zJ55H{)O%~mYMxctCu6>Of8y7wHncLt(Gny)SI|mbOmscu>m@^&dMRnUPy$b~I@fod z^H<-%PcREeVwf#9Y(y({NruB$%uA{}=P&$ffmp^aTl?bRRvj)?HmPH*#;V$`(Q!u4 zwQaoHUQz<6pSoGv>M?dO$0YlsBOw-zD>i;x` Zgz7_yo!wbA&Ue=sVr-nPi>-XF{R_{kVEO<6 literal 0 HcmV?d00001 diff --git a/tests/Pinta.Effects.Tests/Assets/outlineobject2.png b/tests/Pinta.Effects.Tests/Assets/outlineobject2.png new file mode 100644 index 0000000000000000000000000000000000000000..d507d7b6c9a1b74a0571aea8ba8822d041856bdb GIT binary patch literal 4216 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 literal 0 HcmV?d00001 diff --git a/tests/Pinta.Effects.Tests/Assets/outlineobjectinput.png b/tests/Pinta.Effects.Tests/Assets/outlineobjectinput.png new file mode 100644 index 0000000000000000000000000000000000000000..1952c0bfcf68f420a526dc0940ad0d8be28174a2 GIT binary patch literal 1360 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6%N?Bp530R%N1DIE+9tn8jH zjv*Cu-rhZ*eY;fV_{Z<+xxbPZv9~Mv9hC2Q;W)Et*StAv|8d9d;b>}-6A_f0XVcNd zzIVsvcTMcJUAGp9JpZ8d!tugGkevKUei>0b>ySG$6 zn{D#fj0tEvx!_xvmhFPGX4kKOFK281dgAA^DVKs?+D<-UnZJCBpX>7CKOfxX|4Imc zJh{+$_1~vYqd#YqRI4v#wtQY&{c&rs*K>Ev`F?ZHO+WqjUwF2<-ygaEMYYf7Y*${I z?{+sYfAMwe&EnBn>I}OJKC+v--VqY)Uiv-cfz!E45kW+YC( zDO&5>@eQ|a)tD~7o+5X_Z0^B?yS@8&Or86lIT)xi2dMJC<(uUjL<)mn@_u$;P}@+v z{;}czoCrPJ1UZfaGw%F0?q^BxdG~jwJClK0zh(OQ_V4%gPXP6PtUP0x>^ApY<0a`^ zehe9FO(gCFR5{<~WLP^jNT@-RlOeav_a$SlI>VNk;VFKe3^$B5mwvW<^h9G(d=>Zq zbL#_d7z6#Q`ADq3&}_F1qr*`a1}!y)07J%xE>4CJABF`UMF#SC@7|BjyuVaERX9%|r^)f9#mGZ-#JSuS;a$qRB&nEIm} zDC^wM=}`A4dNCQOGS29!FEDhrI=qm<$CE*Ag0kP#dk5?=uv&=wK{n&?8j{rH;xL70Z45pqA{%F@ zGZ`qRvnI{sIM6ajw4+i9BS=8{-t>ht8;P?di0_e_P^rkUd&B3f1Kdl?4{QMnnN{sI zwtSTGQr2MY(M!y?vaY=SCdSq*t(^4q(QiBZimOxeZ7Pp9-@jXN)VKNQmYSETyTptB z|9W5l``bfvf4TY>f8W>txbJ>gH3%4c-|pSIb=AAOyWj1|sb|MZukZc!=Wc(FzV4cy z!i)Pm551GUcdKe6$lnc`d$(^Z`zroDw)#oybbi}?d$(-3b>z8@Wb1#cF3qLYir@2p zeA^!EFY~wN^SbkOcEW;x4@XC@pK|YN8PkOZX8V0#UcO|WemA(vop;){_Wk?+{P~#7 zZvXq|WA<&^?rH5^{?>-`M#KDdcgv$+@@~Bm7ZqXq>&o2rxziU)_A$(w5dYs)X`P1c zVlmC<#XCRuyxz?S6x|qE<=|W7wew3GFob#~Z`o$-(e2ASdM>gTe~DWM4fhkg?^ literal 0 HcmV?d00001 From b1fe6624d779ce746dc6736d49934c3c6b62fb05 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:57:48 -0400 Subject: [PATCH 07/14] Add more outline object tests --- tests/Pinta.Effects.Tests/EffectsTest.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Pinta.Effects.Tests/EffectsTest.cs b/tests/Pinta.Effects.Tests/EffectsTest.cs index 5f365f7e25..81d55ccfcf 100644 --- a/tests/Pinta.Effects.Tests/EffectsTest.cs +++ b/tests/Pinta.Effects.Tests/EffectsTest.cs @@ -678,4 +678,28 @@ public void OutlineObject3 () effect.Data.FillObjectBackground = true; Utilities.TestEffect (effect, "outlineobject3.png", source_image_name: "outlineobjectinput.png"); } + [Test] + public void OutlineObject4 () + { + OutlineObjectEffect effect = new (Utilities.CreateMockServices ()); + effect.Data.Radius = 1; + effect.Data.Tolerance = 20; + effect.Data.AlphaGradient = false; + effect.Data.ColorGradient = false; + effect.Data.OutlineBorder = false; + effect.Data.FillObjectBackground = false; + Utilities.TestEffect (effect, "outlineobject4.png", source_image_name: "outlineobjectinput.png"); + } + [Test] + public void OutlineObject5 () + { + OutlineObjectEffect effect = new (Utilities.CreateMockServices ()); + effect.Data.Radius = 0; + effect.Data.Tolerance = 20; + effect.Data.AlphaGradient = false; + effect.Data.ColorGradient = false; + effect.Data.OutlineBorder = false; + effect.Data.FillObjectBackground = false; + Utilities.TestEffect (effect, "outlineobject5.png", source_image_name: "outlineobjectinput.png"); + } } From d38185ce75752ddf454ce83b99f6f4f075afcf1f Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:16:27 -0400 Subject: [PATCH 08/14] Fix and optimization --- Pinta.Effects/Effects/OutlineObjectEffect.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Pinta.Effects/Effects/OutlineObjectEffect.cs b/Pinta.Effects/Effects/OutlineObjectEffect.cs index 3b389a09cf..959395fb00 100644 --- a/Pinta.Effects/Effects/OutlineObjectEffect.cs +++ b/Pinta.Effects/Effects/OutlineObjectEffect.cs @@ -117,12 +117,11 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI // Second pass // Generate outline and blend to dest Parallel.For (top, bottom + 1, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => { - var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); - var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); - // otherwise produces nothing at radius == 0 if (radius == 0) radius = 1; + var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); + var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); for (int x = left; x <= right; x++) { byte highestAlpha = 0; @@ -136,8 +135,12 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI // Grab nearest border pixel, and calculate outline alpha based off it foreach (var borderPixel in relevantBorderPixels) { + if (borderPixel.X == x && borderPixel.Y == y) + highestAlpha = 255; + if (highestAlpha == 255) break; + if (borderPixel.X > x - radius && borderPixel.X < x + radius) { var dx = borderPixel.X - x; var dy = borderPixel.Y - y; From 97892f313231f8c7c0b476e80ed4177c44bb8e8d Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:47:32 -0400 Subject: [PATCH 09/14] Change Outline > OutlineEdge, change Feather name to Feather Object --- Pinta.Effects/CoreEffectsExtension.cs | 4 ++-- Pinta.Effects/Effects/FeatherEffect.cs | 2 +- .../{OutlineEffect.cs => OutlineEdgeEffect.cs} | 12 ++++++------ tests/Pinta.Effects.Tests/EffectsTest.cs | 4 ++-- tests/PintaBenchmarks/EffectsBenchmarks.cs | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename Pinta.Effects/Effects/{OutlineEffect.cs => OutlineEdgeEffect.cs} (91%) diff --git a/Pinta.Effects/CoreEffectsExtension.cs b/Pinta.Effects/CoreEffectsExtension.cs index b4343ee608..529d6299b8 100644 --- a/Pinta.Effects/CoreEffectsExtension.cs +++ b/Pinta.Effects/CoreEffectsExtension.cs @@ -76,7 +76,7 @@ public void Initialize () PintaCore.Effects.RegisterEffect (new MedianEffect (services)); PintaCore.Effects.RegisterEffect (new MotionBlurEffect (services)); PintaCore.Effects.RegisterEffect (new OilPaintingEffect (services)); - PintaCore.Effects.RegisterEffect (new OutlineEffect (services)); + PintaCore.Effects.RegisterEffect (new OutlineEdgeEffect (services)); PintaCore.Effects.RegisterEffect (new PencilSketchEffect (services)); PintaCore.Effects.RegisterEffect (new PixelateEffect (services)); PintaCore.Effects.RegisterEffect (new PolarInversionEffect (services)); @@ -127,7 +127,7 @@ public void Uninitialize () PintaCore.Effects.UnregisterInstanceOfEffect (typeof (MedianEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (MotionBlurEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (OilPaintingEffect)); - PintaCore.Effects.UnregisterInstanceOfEffect (typeof (OutlineEffect)); + PintaCore.Effects.UnregisterInstanceOfEffect (typeof (OutlineEdgeEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (PencilSketchEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (PixelateEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (PolarInversionEffect)); diff --git a/Pinta.Effects/Effects/FeatherEffect.cs b/Pinta.Effects/Effects/FeatherEffect.cs index 056a8e7f84..0e0111b4a5 100644 --- a/Pinta.Effects/Effects/FeatherEffect.cs +++ b/Pinta.Effects/Effects/FeatherEffect.cs @@ -16,7 +16,7 @@ public sealed class FeatherEffect : BaseEffect // Takes two passes, so must be multithreaded internally public sealed override bool IsTileable => false; - public override string Name => Translations.GetString ("Feather"); + public override string Name => Translations.GetString ("Feather Object"); public override bool IsConfigurable => true; diff --git a/Pinta.Effects/Effects/OutlineEffect.cs b/Pinta.Effects/Effects/OutlineEdgeEffect.cs similarity index 91% rename from Pinta.Effects/Effects/OutlineEffect.cs rename to Pinta.Effects/Effects/OutlineEdgeEffect.cs index 20020a833b..e585ef9e42 100644 --- a/Pinta.Effects/Effects/OutlineEffect.cs +++ b/Pinta.Effects/Effects/OutlineEdgeEffect.cs @@ -14,7 +14,7 @@ namespace Pinta.Effects; -public sealed class OutlineEffect : LocalHistogramEffect +public sealed class OutlineEdgeEffect : LocalHistogramEffect { private int thickness; private int intensity; @@ -23,20 +23,20 @@ public sealed class OutlineEffect : LocalHistogramEffect public sealed override bool IsTileable => true; - public override string Name => Translations.GetString ("Outline"); + public override string Name => Translations.GetString ("Outline Edge"); public override bool IsConfigurable => true; public override string EffectMenuCategory => Translations.GetString ("Stylize"); - public OutlineData Data => (OutlineData) EffectData!; // NRT - Set in constructor + public OutlineEdgeData Data => (OutlineEdgeData) EffectData!; // NRT - Set in constructor private readonly IChromeService chrome; - public OutlineEffect (IServiceProvider services) + public OutlineEdgeEffect (IServiceProvider services) { chrome = services.GetService (); - EffectData = new OutlineData (); + EffectData = new OutlineEdgeData (); } public override void LaunchConfiguration () @@ -131,7 +131,7 @@ public override void Render (ImageSurface src, ImageSurface dest, ReadOnlySpan Date: Sun, 15 Sep 2024 21:49:23 -0400 Subject: [PATCH 10/14] Changes to Outline Object --- Pinta.Effects/Effects/OutlineObjectEffect.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Pinta.Effects/Effects/OutlineObjectEffect.cs b/Pinta.Effects/Effects/OutlineObjectEffect.cs index 959395fb00..a6087fd07e 100644 --- a/Pinta.Effects/Effects/OutlineObjectEffect.cs +++ b/Pinta.Effects/Effects/OutlineObjectEffect.cs @@ -63,8 +63,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI // Removing this causes preview to not update to lower radius levels var srcRow = srcData.Slice (y * srcWidth, srcWidth); var dstRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); - for (int x = left; x <= right; x++) - dstRow[x].Bgra = srcRow[x].Bgra; + srcRow.CopyTo (dstRow); // Produces different behaviour at radius == 0 and radius == 1 // When radius == 0, only consider direct border pixels @@ -122,6 +121,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI radius = 1; var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > y - radius && borderPixel.Y < y + radius).ToArray (); var destRow = dest.GetPixelData ().Slice (y * srcWidth, srcWidth); + Span outlineRow = stackalloc ColorBgra[destRow.Length]; for (int x = left; x <= right; x++) { byte highestAlpha = 0; @@ -163,9 +163,11 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI if (!Data.AlphaGradient && highestAlpha != 0) highestAlpha = 255; - var outlineColor = color.NewAlpha (highestAlpha).ToPremultipliedAlpha (); - destRow[x] = ColorBgra.AlphaBlend (destRow[x], outlineColor); + outlineRow[x] = color.NewAlpha (highestAlpha).ToPremultipliedAlpha (); } + // Performs alpha blending + new UserBlendOps.NormalBlendOp().Apply (outlineRow, destRow); + outlineRow.CopyTo (destRow); }); } From 0ca05a7a58a35b834ed710db52e92cc3a897db69 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:52:37 -0400 Subject: [PATCH 11/14] Add outline object to uninitializer --- Pinta.Effects/CoreEffectsExtension.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Pinta.Effects/CoreEffectsExtension.cs b/Pinta.Effects/CoreEffectsExtension.cs index 529d6299b8..19626798c2 100644 --- a/Pinta.Effects/CoreEffectsExtension.cs +++ b/Pinta.Effects/CoreEffectsExtension.cs @@ -121,6 +121,7 @@ public void Uninitialize () PintaCore.Effects.UnregisterInstanceOfEffect (typeof (GaussianBlurEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (GlowEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (FeatherEffect)); + PintaCore.Effects.UnregisterInstanceOfEffect (typeof (OutlineObjectEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (InkSketchEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (JuliaFractalEffect)); PintaCore.Effects.UnregisterInstanceOfEffect (typeof (MandelbrotFractalEffect)); From 7213f32d97084740a1163049c265f86667f6a190 Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:52:42 -0400 Subject: [PATCH 12/14] Update tests --- .../Assets/outlineobject1.png | Bin 2577 -> 2557 bytes .../Assets/outlineobject2.png | Bin 4216 -> 4123 bytes .../Assets/outlineobject3.png | Bin 3613 -> 3520 bytes .../Assets/outlineobject4.png | Bin 1418 -> 1381 bytes .../Assets/outlineobject5.png | Bin 1434 -> 1351 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/Pinta.Effects.Tests/Assets/outlineobject1.png b/tests/Pinta.Effects.Tests/Assets/outlineobject1.png index ab7d77bb8623776d7e90fde2791c65377bc5d80a..adfc6617160d4253f0641089e88079b8e60c1e77 100644 GIT binary patch literal 2557 zcmcImc{J2}8~@GN#`L;eoMafILUScc6p?Lg{Rm@=$Wla-EF)x{vb|zl24!cYNQ`6{ zO?G8@ZJDeQk!`M+82eaW-S^)2zx&5~&U>G8KIeJPb3V`W{d~UX`^PsPYi7g;N5BC9 z-~%t|TLJ*&u!I0G?!$D=zr_78apEwbHS93LU~c~e0A32HuWKEgv-G*oN8AJ1zS5`6 z?pl0zw`|wnDp`PXn=9t@Z36?@%?_@gb~${%w3mzQ@c5S0J`}jL%P+2$rYCnIf`Z9O zmb8SQu_S>AYNgPaSYNMJU+Zz@PPeLw+!ZpbJ9t9DlakaP*pQXW^vKT)4>W`x3i(^& zOK|1~u7S~X0-?%+c&CG{AnHmZE&kFPs;$x%R)XH?TC}a$Hz!Vn&!$I=FV-*Qvi;q< zaTDy>uUm}-C64*Mc9}=y_35phm(2PgXQ7(n%?PxsmTzwFoLF{tKGNunsSzNqTk{mb z-~yLEtgAG2gfgY#b?oGz1x2UD#5^3p)o{>+{CHg(s!SKr8}=0x0Hlj5)Wuo0dI|xMQxFJNpkiMw9)^DyyxtD#1H$i= zJl1!oR&PS@n{n#+iQ}#$CKS$NhOt4M!0j%nPLV&$V|qY$JfM?X9w%S03{Oryl0pWAZQNfmMA-@66hHf)+e%;B>${7V$n)G2`rh~JU`9d84A;2+g}CNr8$ z`XTr6xmX}W2!j1dwYDU=!yf8#=$6-MM=^je_#I^|KjsGuFLQFE zgyD&`vI1trzy;`Uay92aAqnc0)#aqklqZ+_C(jX?h_4~#j~zX!sZK9vlc@GzK+dM0 z!x8QEp-(L~CwxbcA(My;!?JAEPZn-(+eQZO2B9$!hyXBafa2%w3L?6Bs)8XLOpVHF zrH6!s`T;(dlRxI(8Dg@d8QesK!|KDU(q^TI&g#OBjU2How7;wS5ZSdzTp6b0P#MF?UwDCjE6D@@AxuFtW5@K5l`RaX8 zH-&|aYfS0DNa{d(8o>$n;Lqu*F~4!MIwneO_}B8K_TKgKO{DD!;6x`O5fo&yHft4x z;R$oO2}x%k@k)=$cmi^>S=xFqVSS*xMwW3|i<7*x^!XsHF;dBEpR~D`f-_m}M6O^{ z+{P>dlI_O2!Yi<7@wk&oH!z2y1i|^@Z>`s9tYE;WD1*3lQz2(X#Gy>k7yxnluK`_)^0xJrlAsvBB4?%lL#0Rf1jXUdSN;m$9Y=aqoSX=OvcIa_ zmy=L5xUh|jirn1~-J2Qrf8m|Q&$YQfw7RupCagJ#tQ|nhB%28@)f7!RQ>hguv}_4x zBvGD9pLM7C%Dcd!F0`^yg;@!fv$j`}iF4=*jy=4;6*W84!ic%kca`Lu<4(AN@nni` z9ckPvc03pz9GR#KGN#uzPS&&Ii^?f0J`%6T;f|r{Hgmk>l1zvsgP!Re7F5gnYI|Bc zK#NzMy18rHxV_jMd4OXNToIqVlzs6)ZNEO7!@cXC1RE>=F>35 zR+($LoX`X|8GI)lx%Jp#GUUZ7(|UmGB;H_aF#rCAkSy4E%yHm3i!n`q|J>&sfu0bx zY(gVZRiCW&pSLp4KMD=HuEv=m-+88$!l>ev2}13q)syBULqH#I@dcv_&5T#9F75Xr zp*Rp!=bsHQ4`Ho7h}a_J4cV$~hghS$HR|i%q~*+ooplD53&s4Er~2jgpLUJC-^8 zLEYYX*xDfPqRl8axNhoUx>9q|+@u+b>aUv|15t()O9Ji=rrGRQbA545TVZ=@K_!Zw z;gwjJSD3KSr|UiWnh~;$KzUBtbSInE3;5CHXSUgy>q8%#qn9oi2aSM>W1c#RkvF`n zZ}n9DDK>7sVI!;LfjRgC0idYeXEQCxU4;mUedth zE+1nZ&9v1oYE`r_`i*Q^iyCp!ZADnvnMq$87aUG;S%SXX-H9$S*04v;=t;i)uD@SO zNrg(jC7MTs`(SQ~ABB@<#g^?VjIFUpOQY7*BID+?)hD>rXoG9Ay2bS8OP5FTBIJPA z>s>+^S4*_DZo^cmB8S3}LG~M=G!2!5egS~xIh$2;3&_y>FTqzra_qU1Dpw{7@_o$| zPH4rD;B8{^YisuT*;zf_&2!ezkma531Sw46)Zg+u`Ts1vw1+Y}^&!vLG}ZjaLeRiW JpNw&N_;26iO6OE<`xrD;ZlvHkETvE*(l$@fW za#!v#xim^eW;leyOu5y#4Q71fobRl4*0;XD-&)`M=iO_+?|$CrdG^|C?RZybTRB-} zSpa|>#?IOu0El=A0nqj0gbR6$7bnS3E6g#dIHI9`w*ZiNgR!B@-~Yk#nVB~TdiB0HdxS?*Y0qA`P*g5zT3VWGR&p4CQP#bg zdVKVXySu?`!j#z#5xEM<-pmNB^bYM)J>w5y@pjuNCG z_S+H%VxR?da3`>)f7}s5JMB_#sx=2&0Q;GgLuTWrH`3p&GnR*1Seh-37;6Ek@7$D; zTLAt#l-0uj8rlUL-43G+N3yDYF2Ly<%x@GTz~em%%#<5yM?S&8u`J{b(0L2wwyVjT zQ`Tw+SZ`=BNJAd7b;6i;+DrMm+J@GV0Qe5&aa@<3OGr!K@EIxaQ3(RhCjnr16i}2A zz#c?!y z{z18adxEI`6aDYW|3%wBiT$HY`LTFRV~2`luJ+-rP^Jt-H{M06Ni55L*yHL&8tTZV z-1et_>j`Xo8c~~N*e;6xkX=S_WD`)sw_GYMf#eV+yt}OQe!*m3DC0F7{}nf??J+x^ zJgVIK84uT)L?tI9ovk6ZNgzXUz=`^Zj+&e*Ao!mPFbysy(1{sx7G*|#>0a;_S$N!W zZ)6@S8Kc9nW2a@@P;>EwQ9`sZyDF*qy)GZ78ra2~EQmM2YdNAvW>V8DQ@c}=k zb$2thLMH%Iqi_Z1X$=w*wdomQg*gQSqlFkwAyecBbNoar?uIO`laL zVmVH9;>#@%eQJP`fg_FRPxeO1^6@c1{#k-di(wK*%(1#HM^i!yM&Wts#2iw;s%L6y z>R&S;UD~FOR0vKs$d`Ps7?R%qWV~LvIMljPY$!| zC}QR}V(;OlMnp<<S^z)(EtnmiAxbKvZ9tsaNdRvoxd*M=?&FXy8BZ$B{YVJ}~EL&@H3 zG*Qev*>at#s-QmV_9QOq0aMg#7f9z(Dyr^85u;>Xeg6 zq_F8i?c9f;u;RmfJA)}h^mklMd3u#W5GgfU&`VZ7JkxVC=a$WT-5psU+0J)89d*;- zdI>1JRH_|&PJ!Gvh>yO?m6Y^(;T71y^bsuiyjyPmRK$y5(FofKb7S7D!1^CA6{DIJ zCS&fr3oar2P8L1yS?)rI2T@hEWp6&|+`Tg+96k`wI61HPqVru2{3E~8jX8+VrT|C$ z$~FPxOUiK%_wm+?pM@i>tzRqa1dkZrq~+T9bTV2xzUh~H6$vtr#Dqgj>T{y!6v^AU z11d!#fsjxi72(%C-kdB0ZAc1ic`jNS@#i#WvieV%sVK%F!n7JzO+5RF_sI1#k;D;R zX!3O3;xgkcnN)Spbe(=;2ttwQkyeR{J+bH=UF5^>>wq)+@S9 ziHLraA!_Zzxkf?PL0q)1zfG^a9_4c6r1{}pGf-H+Kog{!)E}>ZBw`f2CL41r^5sre zRpoPHoXyR5OGX5WFE=<)WeJl*_X)vv7vu-jmR9GL2oH2S+*xL;oe0>(I91c%>w&)0 znKEDDPtp{`?>d7!slg8?)Sps>u ztuHNSP)WcSitXMloZ!TmF9>dDb=1fGtj9T0SVs{LYJoKRZ9;v?!;LAG zJ55H{)O%~mYMxctCu6>Of8y7wHncLt(Gny)SI|mbOmscu>m@^&dMRnUPy$b~I@fod z^H<-%PcREeVwf#9Y(y({NruB$%uA{}=P&$ffmp^aTl?bRRvj)?HmPH*#;V$`(Q!u4 zwQaoHUQz<6pSoGv>M?dO$0YlsBOw-zD>i;x` Zgz7_yo!wbA&Ue=sVr-nPi>-XF{R_{kVEO<6 diff --git a/tests/Pinta.Effects.Tests/Assets/outlineobject2.png b/tests/Pinta.Effects.Tests/Assets/outlineobject2.png index d507d7b6c9a1b74a0571aea8ba8822d041856bdb..953e91bb276cc24be2fe00af5216a23e1ce5760a 100644 GIT binary patch literal 4123 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 delta 1160 zcmV;31b6%83W^JmG;BdhL_t(|obBB|Xq;sn!13R6P12~CK@i0%YDGj5p$gK=AVgFg z1iN){aZ+#*M6|d#C>0!>baIz2wcTv(AV*8UK~Y4IVk>GV{}fZGCYR)KFjdr|clTcU z+^(x~=pKZ6^SJHgiN~Lju6INhw{-5vMmJR!h&q<#}G4ZIg4pIz`;+)jE^QEtBm6 z7&9=Sob$!fGr+hBz@Sn(S9$~(Hvt$>>U6#=Jp+uJ01W8yUhmV=Gr+hBz+lp)kFSDh zB9q(#7=M7)$|?0r&(L-PKx=g(&X=B{?F4|<$~pgBdWN`wi0EXiOE+$B4 zV?zKM5`ZD(h%=>U;4f)r=BLs#w4DG9Vc+udq0%$(mnTl1JX?B(wiAHCrIo&CohHe} z_4SAdpFGxp0MJ^w^Z-*p0H9(502LDesF(mi#gp>_8Ivvo2YvHv5Yt2ehT~mI2w>?Apj%T zIvQ}j^c>KX01Pj!^!t$`PM018nwlv+KwIrwUOsfR+kH7A<}ZEkvpv^e_x5x5J^Ib< z3%mO>Y3DyrGk?3DS8w@t=0__yC(@uA@#e6s53^0CFm8|G$ao{ot56OY_{ z^P4X{@YLGej`;yj`zKKt-BPdjsy*C{#T#9Yh&Z;mc*Ey=Z#wYG!R|9_b33jY#~GK5 z!vEchDP?v&uikcc<;=71e0JjgPk#I-*O~KDXfJq`{CM)=Uhj}S+>FI*E~m}6F65N1Ej>sr?)dtbFMhcDwR^W-nr#m^ z;6cDK$SLL0Gt}hF?uA>k3;$Sn8gM*Llpdfaf;&C}FcupJBI11MDJmkkVh;$)fR2e<4Mli>ty8UO$Q a+V6KcJ1Unaq99=a00001ljwV3&#qOGi(I@Nkl-MeFRWea} zfDX$M5y$%dy^-^dl+tABfku&Yo=+*Ai(c=<%-r0aJKgc!yLaZr#bIB6?rk$~k{8Mno(H z;gGf44jXZF`FOwoZbVF#9;9M_&I|@0S-;KoN)OO6`B=Zd*PXw(sinWeI2?Z|N&rT* zwA)W%DPnu+8ODJC4Da@%zvujG!-moWi~|7}fk%JFjQ|YCqd#Lw0EXl_C*w!}hUYow z8uQKU>^83iG$R0XR=Rm}Tj@F(x=sM-tdx=fG$a6Y*`uWg=sE$Q%bbzfkN~i1QcBlz z#F@p28>MIA-n_2Os>wNTOc77}kDay49+Mvf7%CW2&iQ)j8K7+fFszg=mL37xCICZ9 zz2524GeFw}U^r=JZtnZiLqOXElRg3%lbr$wU)?M{L)Qraoz;uDQhJ826976Z=lomg z8M;mY=&Xg51fU@S7>y6OSVMXX3j)xP0E{3Vgdja69A}~lMw?c8NlegD-dHt07m3pffy$OFdFX)#8?pksF;%x z0~LR;GR_8MEC|2~I2(|0ApoPheKz2&(t|)#0Rp5Ob$nG>&mb>Wx$J~b@kqm@@K zADN!sJUKD(dPGbe-LrM;$NOJ=ZGLjy)DVByy+agMZe}p}z#4AG#%m@YnV#PK!>-2; zym#>BH|8hTJ{jGds+xe^K-u&#;(HBl$x*QQVW(I?U)^Ia6UUPZN zuOEJQam#}*l^&!9lk*FI9Nd56^PMLzeYg(Q_7`hsL8qQo1Q2=!?+NDb{s7|KurXu|)vGm-X1q%nuOk)?{2d!yYqt$HVp(fB3d`?sYl(H+5*u>uS&Ij_tP_(V g1!0lE4;RM6UwGCG%2l`Hod5s;07*qoM6N<$f^eh&Qvd(} delta 1208 zcmV;p1V{VF3YrU$Gk*n9NklFFjNuc<|JodM~+Xyj9I13<(P!l!yc*Bq?f68bddbc6WC7 z=^>k{i+^VJeK*g%^L#$AmmOyJx9>jlotgI?-ZugO000000F%cAG=J`iRC<6JkV_BH zg0HTFd;Rz9T!Y{A`>oSGGFN(l4$F)Eew-N$PDIW}QcCls2bx6Ac|E0cHF~`Z%jeGJ zh=~2YUi|aev7>ow>*u|c4(6N>?8Wa+Dc#=6`FiU0Ea+>pBW5Jc<>juu>F6Sh5au@3BUvv`~BE{*-v39 z;&AC1W`O{V@9wj|_x#ggf9U~cfdGujvp=&)0LJ6lpP3{8V}J6VbNcK{4gxeK0ORwX zbIQEEynM)!fMx`M&Pt=vq0%#SodD2TDJ21DNC4=vAD14W>jZ!<^NGxc1b{u0Qo5ZZ zE^kGwm7axf=D0R{Cg*%0MLg|N}P%#03 ziU|NzOaP!_0ss{g0H~M%K*a=r&dMo`O3%=B0zhZ=B5szRq3Z;I&dND|QF?~16977E zGbI6NNB}0|0vA)Hx49_*4GF*ma>UirGw`J}H+Q4-3@=?L0OL!?S5{7!9)d6Z=KT3< z5#ho^4G6#lR!5FHO_SRj8>L6+JOQAya_IqPfRoVzDSrS|OaLb5tU$~R0ho}p0x?Sj zU^31M#7q%@2{|hevqS)(Vgdja69A}~06@hA04gQ`P%#169iIkdCJ4YT_%tB1LI5Ur z_tSu`OGW1iz%HzgMv)^fm!1Qfnkzj(N2TK{EBVdA;H8LIxckkEM}PFgzyAIgPyOpB zi-$*ZX@CFDJM}m6+QT2OUOVxR%NL%0`{PgU`P8t3ODpeQersvzf%&<)=OSX^?2`{Y z^y-VhdUkz&-@+L8{XbFIz2)KX&(?4=HeNINt)-<0K6w1WlYcrjcz%6;-+gWT(2`O3 zzE?4&^f&U_p=+yGfA_bGXMg$L=Qko^ZFx96Wq%DfW8*cK4}SXcyVnoi|Lf9&)L?#n z^FOCvyzuvY)qlnGN zBU|G2h`6==^?JnSPb0k$kzR<1yc7{{Y~RN2-P{f?EwB@%J>T}SG=1ys`L(5p^v8&J zG9v6m{BE}*;+=@ilXrq^?Y6;2ENhyhu=4>oO|09N*pSm_8h3D6CzHVhZy5m0hW`OG W&@Pcg&!3|J0000 Date: Sun, 15 Sep 2024 21:54:08 -0400 Subject: [PATCH 13/14] Remove unused blendalpha --- Pinta.Core/Effects/ColorBgra.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Pinta.Core/Effects/ColorBgra.cs b/Pinta.Core/Effects/ColorBgra.cs index 70118015e9..cce963512e 100644 --- a/Pinta.Core/Effects/ColorBgra.cs +++ b/Pinta.Core/Effects/ColorBgra.cs @@ -207,27 +207,6 @@ public static ColorBgra FromUInt32 (uint bgra) return color; } - /// - /// Performs Alpha Blending between two colors. In practice, Alpha Blending two images produces the same result - /// as layering the top image on top of the bottom image. - /// - /// The color layered on top. Color must be in premultiplied form. See . - /// The color layered below. Color must be in premultiplied form. See . - /// Premultiplied form of the alpha-blend between colorTop and colorBottom. - public static ColorBgra AlphaBlend (ColorBgra colorTop, ColorBgra colorBottom) - { - if (colorTop.A == 255) - return colorTop; - if (colorTop.A == 0) - return colorBottom; - float cTopA = colorTop.A / 255f; - byte r = (byte) (colorTop.R + colorBottom.R * (1f - cTopA)); - byte g = (byte) (colorTop.G + colorBottom.G * (1f - cTopA)); - byte b = (byte) (colorTop.B + colorBottom.B * (1f - cTopA)); - byte a = (byte) (colorTop.A + colorBottom.A * (1f - cTopA)); - return FromBgra (b, g, r, a); - } - /// /// Smoothly blends between two colors. /// From 579841225684fb9cb22d1168f1c679e0835940aa Mon Sep 17 00:00:00 2001 From: potatoes1286 <48143760+potatoes1286@users.noreply.github.com> Date: Sun, 15 Sep 2024 22:01:52 -0400 Subject: [PATCH 14/14] run dotnet format --- Pinta.Effects/Effects/OutlineObjectEffect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pinta.Effects/Effects/OutlineObjectEffect.cs b/Pinta.Effects/Effects/OutlineObjectEffect.cs index a6087fd07e..8a47e797ba 100644 --- a/Pinta.Effects/Effects/OutlineObjectEffect.cs +++ b/Pinta.Effects/Effects/OutlineObjectEffect.cs @@ -166,7 +166,7 @@ protected override void Render (ImageSurface src, ImageSurface dest, RectangleI outlineRow[x] = color.NewAlpha (highestAlpha).ToPremultipliedAlpha (); } // Performs alpha blending - new UserBlendOps.NormalBlendOp().Apply (outlineRow, destRow); + new UserBlendOps.NormalBlendOp ().Apply (outlineRow, destRow); outlineRow.CopyTo (destRow); }); }