Browse Source

Merge pull request #446 from SixLabors/js/premultiply

Premultiply to prevent transparent pixel bleed
pull/448/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
5fdd70ade0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  2. 4
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  3. 4
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  4. 4
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  5. 4
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  6. 4
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  7. 8
      src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
  8. 18
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  9. 15
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  10. 1
      tests/ImageSharp.Tests/TestImages.cs
  11. 2
      tests/Images/External
  12. BIN
      tests/Images/Input/Png/kaboom.png

28
src/ImageSharp/Common/Extensions/Vector4Extensions.cs

@ -12,6 +12,34 @@ namespace SixLabors.ImageSharp
/// </summary>
internal static class Vector4Extensions
{
/// <summary>
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Premultiply(this Vector4 source)
{
float w = source.W;
Vector4 premultiplied = source * w;
premultiplied.W = w;
return premultiplied;
}
/// <summary>
/// Reverses the result of premultiplying a vector via <see cref="Premultiply(Vector4)"/>.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 UnPremultiply(this Vector4 source)
{
float w = source.W;
Vector4 unpremultiplied = source / w;
unpremultiplied.W = w;
return unpremultiplied;
}
/// <summary>
/// Compresses a linear color signal to its sRGB equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>

4
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());
}
});

4
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());
}
});
}

4
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());
}
});

4
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());
}
});
}

4
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());
}
});
}

8
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();
}
/// <summary>
@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
result += firstPassPixels[x, index] * yw;
}
return result;
return result.UnPremultiply();
}
}
}

18
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<EdgeDetection> DetectEdgesFilters = new TheoryData<EdgeDetection>
{
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);
}
}
}

15
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider)
@ -89,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using (Image<TPixel> 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);

1
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";

2
tests/Images/External

@ -1 +1 @@
Subproject commit ddc4045926bb0331be8c1785d9adf5f76dc4e52e
Subproject commit 376605e05bb704d425b2d17bf5b310f5376da22e

BIN
tests/Images/Input/Png/kaboom.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Loading…
Cancel
Save