Skip to content

Commit

Permalink
Decreased nesting in FeatherEffect (#986)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lehonti committed Sep 21, 2024
1 parent 72719ab commit b810a6f
Showing 1 changed file with 90 additions and 64 deletions.
154 changes: 90 additions & 64 deletions Pinta.Effects/Effects/FeatherEffect.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Cairo;
Expand All @@ -10,7 +11,6 @@ namespace Pinta.Effects;

public sealed class FeatherEffect : BaseEffect
{

public override string Icon => Pinta.Resources.Icons.EffectsDefault;

// Takes two passes, so must be multithreaded internally
Expand Down Expand Up @@ -39,97 +39,123 @@ public override void LaunchConfiguration ()

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 src_height = src.Height;
int radius = Data.Radius;
int threads = system.RenderThreads;
int tolerance = Data.Tolerance;

ConcurrentBag<PointI> borderPixels = new ConcurrentBag<PointI> ();
ConcurrentBag<PointI> borderPixels = new ();
// Color in any pixel that the stencil says we need to fill
// First pass
// Clean up dest, then collect all border pixels
Parallel.For (top, bottom, new ParallelOptions { MaxDegreeOfParallelism = threads }, y => {
var src_data = src.GetReadOnlyPixelData ();
int src_width = src.Width;
var dst_data = dest.GetPixelData ();
// reset dest to src
// Removing this causes preview to not update to lower radius levels
var src_row = src_data.Slice (y * src_width, src_width);
var dst_row = dst_data.Slice (y * src_width, src_width);
for (int x = left; x <= right; x++) {
dst_row[x].Bgra = src_row[x].Bgra;
}
Parallel.For (
roi.Top,
roi.Bottom,
new ParallelOptions { MaxDegreeOfParallelism = threads },
y => {
var src_data = src.GetReadOnlyPixelData ();
int src_width = src.Width;
var dst_data = dest.GetPixelData ();
// reset dest to src
// Removing this causes preview to not update to lower radius levels
var src_row = src_data.Slice (y * src_width, src_width);
var dst_row = dst_data.Slice (y * src_width, src_width);
for (int x = roi.Left; x <= roi.Right; x++)
dst_row[x].Bgra = src_row[x].Bgra;
Span<PointI> pixels = stackalloc PointI[] { PointI.Zero, PointI.Zero, PointI.Zero, PointI.Zero };
// Collect a list of pixels that surround the object (border pixels)
for (int x = roi.Left; x <= roi.Right; x++) {
PointI potentialBorderPixel = new (x, y);
if (Data.FeatherCanvasEdge && (x == 0 || x == src_width - 1 || y == 0 || y == src_height - 1)) {
borderPixels.Add (potentialBorderPixel);
continue;
}
if (src.GetColorBgra (src_data, src_width, potentialBorderPixel).A > tolerance)
continue;
Span<PointI> pixels = stackalloc PointI[] { new (0, 0), new (0, 0), new (0, 0), new (0, 0) };
// 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.FeatherCanvasEdge && (x == 0 || x == src_width - 1 || y == 0 || y == src_height - 1)) {
borderPixels.Add (potentialBorderPixel);
} else if (src.GetColorBgra (src_data, src_width, 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);
foreach (var pixel in pixels) {
var px = pixel.X;
var py = pixel.Y;
if (px < 0 || px >= src_width || py < 0 || py >= src_height)
foreach (var p in pixels) {
if (p.X < 0 || p.X >= src_width || p.Y < 0 || p.Y >= src_height)
continue;
if (src.GetColorBgra (src_data, src_width, 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 feather pass because it will overwrite this
//int pos = src_width * y + x;
//dst_data[pos].Bgra = 0;
//dst_data[pos].A = 255;
break;
}
if (src.GetColorBgra (src_data, src_width, p).A <= tolerance)
continue;
borderPixels.Add (potentialBorderPixel);
// Remove comments below to draw border pixels
// You will also have to comment out the feather pass because it will overwrite this
//int pos = src_width * y + x;
//dst_data[pos].Bgra = 0;
//dst_data[pos].A = 255;
break;
}
}
}
});
);

// Second pass
// Feather pixels according to distance to border pixels
Parallel.For (top, bottom, new ParallelOptions { MaxDegreeOfParallelism = threads }, py => {
var src_data = src.GetReadOnlyPixelData ();
int src_width = src.Width;
var dst_data = dest.GetPixelData ();
var relevantBorderPixels = borderPixels.Where (borderPixel => borderPixel.Y > py - radius && borderPixel.Y < py + radius).ToArray ();
for (int px = left; px <= right; px++) {
int pixel_index = py * src_width + px;
byte lowestAlpha = dst_data[pixel_index].A;
// Can't feather further than alpha 0
if (lowestAlpha == 0)
continue;
foreach (var borderPixel in relevantBorderPixels) {
if (borderPixel.X > px - radius && borderPixel.X < px + radius) {
Parallel.For (
roi.Top,
roi.Bottom,
new ParallelOptions { MaxDegreeOfParallelism = threads },
py => {
var src_data = src.GetReadOnlyPixelData ();
int src_width = src.Width;
var dst_data = dest.GetPixelData ();
var relevantBorderPixels =
borderPixels
.Where (borderPixel => borderPixel.Y > py - radius && borderPixel.Y < py + radius)
.ToImmutableArray ();
for (int px = roi.Left; px <= roi.Right; px++) {
int pixel_index = py * src_width + px;
byte lowestAlpha = dst_data[pixel_index].A;
// Can't feather further than alpha 0
if (lowestAlpha == 0)
continue;
foreach (var borderPixel in relevantBorderPixels) {
if (borderPixel.X <= px - radius || borderPixel.X >= px + radius)
continue;
var dx = borderPixel.X - px;
var dy = borderPixel.Y - py;
float distance = MathF.Sqrt (dx * dx + dy * dy);
if (distance > radius)
continue;
// If within distance to border pixel
if (distance <= radius) {
float mult = distance / radius;
byte alpha = (byte) (src_data[pixel_index].A * mult);
if (alpha < lowestAlpha)
lowestAlpha = alpha;
}
float mult = distance / radius;
byte alpha = (byte) (src_data[pixel_index].A * mult);
if (alpha < lowestAlpha)
lowestAlpha = alpha;
}
if (lowestAlpha < dst_data[pixel_index].A)
dst_data[pixel_index].Bgra = src_data[pixel_index].ToStraightAlpha ().NewAlpha (lowestAlpha).ToPremultipliedAlpha ().Bgra;
}
if (lowestAlpha < dst_data[pixel_index].A)
dst_data[pixel_index].Bgra = src_data[pixel_index].ToStraightAlpha ().NewAlpha (lowestAlpha).ToPremultipliedAlpha ().Bgra;
}
});
);
}

public sealed class FeatherData : EffectData
Expand Down

0 comments on commit b810a6f

Please sign in to comment.