diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs index 7cb193e82..8133ebb38 100644 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs @@ -12,6 +12,34 @@ namespace SixLabors.ImageSharp /// internal static class Vector4Extensions { + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Premultiply(this Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + /// + /// Reverses the result of premultiplying a vector via . + /// + /// The to premultiply + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 UnPremultiply(this Vector4 source) + { + float w = source.W; + Vector4 unpremultiplied = source / w; + unpremultiplied.W = w; + return unpremultiplied; + } + /// /// Compresses a linear color signal to its sRGB equivalent. /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index b85432ac5..cf9b7be9c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - var currentColor = sourceOffsetRow[offsetX].ToVector4(); + Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); if (fy < kernelXHeight) { @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors float blue = MathF.Sqrt((bX * bX) + (bY * bY)); ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 362fa5c50..57f434ee0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -113,13 +113,13 @@ namespace SixLabors.ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - var currentColor = row[offsetX].ToVector4(); + Vector4 currentColor = row[offsetX].ToVector4().Premultiply(); destination += kernel[fy, fx] * currentColor; } } ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(destination); + pixel.PackFromVector4(destination.UnPremultiply()); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index c0d3fdcfe..96db9a4ce 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - var currentColor = sourceOffsetRow[offsetX].ToVector4(); + Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 07c247d73..9d7056b67 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - var mupltiplied = new Vector4(new Vector3(vector.X, vector.Y, vector.Z) * vector.W, vector.W); + Vector4 mupltiplied = vector.Premultiply(); sum += mupltiplied * xWeight * yWeight; } } @@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Processing.Processors ref TPixel dest = ref destRow[x]; // Reverse the premultiplication - dest.PackFromVector4(new Vector4(new Vector3(sum.X, sum.Y, sum.Z) / sum.W, sum.W)); + dest.PackFromVector4(sum.UnPremultiply()); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index f53585093..463d3717d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -199,7 +199,7 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - var mupltiplied = new Vector4(new Vector3(vector.X, vector.Y, vector.Z) * vector.W, vector.W); + Vector4 mupltiplied = vector.Premultiply(); sum += mupltiplied * xWeight * yWeight; } } @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Processing.Processors ref TPixel dest = ref destRow[x]; // Reverse the premultiplication - dest.PackFromVector4(new Vector4(new Vector3(sum.X, sum.Y, sum.Z) / sum.W, sum.W)); + dest.PackFromVector4(sum.UnPremultiply()); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs index d23ee5a06..b0a530514 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { float weight = Unsafe.Add(ref horizontalValues, i); Vector4 v = Unsafe.Add(ref vecPtr, i); - result += v * weight; + result += v.Premultiply() * weight; } return result; @@ -114,10 +114,10 @@ namespace SixLabors.ImageSharp.Processing.Processors { float weight = Unsafe.Add(ref horizontalValues, i); Vector4 v = Unsafe.Add(ref vecPtr, i); - result += v.Expand() * weight; + result += v.Premultiply().Expand() * weight; } - return result; + return result.UnPremultiply(); } /// @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors result += firstPassPixels[x, index] * yw; } - return result; + return result.UnPremultiply(); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index c2d891638..323842556 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.Primitives; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public class DetectEdgesTest : FileTestBase { public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; - + public static readonly TheoryData DetectEdgesFilters = new TheoryData { EdgeDetection.Kayyali, @@ -39,9 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { image.Mutate(x => x.DetectEdges(detector)); image.DebugSave(provider, detector.ToString()); - - // TODO: Enable once we have updated the images - // image.CompareToReferenceOutput(provider, detector.ToString()); + image.CompareToReferenceOutput(provider, detector.ToString()); } } @@ -54,9 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { image.Mutate(x => x.DetectEdges()); image.DebugSave(provider); - - // TODO: Enable once we have updated the images - // image.CompareToReferenceOutput(provider); + image.CompareToReferenceOutput(provider); } } @@ -83,12 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.DetectEdges(bounds)); image.DebugSave(provider); - - // TODO: Enable once we have updated the images - //image.CompareToReferenceOutput(provider); - - // TODO: We don't need this any longer after switching to ReferenceImages - //ImageComparer.Tolerant().EnsureProcessorChangesAreConstrained(source, image, bounds); + image.CompareToReferenceOutput(provider); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 92f1af58e..8370a802c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -82,6 +82,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType)] + public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + } + [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) @@ -89,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, true)); + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.NearestNeighbor)); // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( image.DebugSave(provider, extension: Extensions.Gif); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c542fa880..864c96332 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; public const string SnakeGame = "Png/SnakeGame.png"; public const string Icon = "Png/icon.png"; + public const string Kaboom = "Png/kaboom.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/External b/tests/Images/External index ddc404592..376605e05 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit ddc4045926bb0331be8c1785d9adf5f76dc4e52e +Subproject commit 376605e05bb704d425b2d17bf5b310f5376da22e diff --git a/tests/Images/Input/Png/kaboom.png b/tests/Images/Input/Png/kaboom.png new file mode 100644 index 000000000..c2abdf465 Binary files /dev/null and b/tests/Images/Input/Png/kaboom.png differ