From e8f61570231cefdbace8ee714d66066be2ce80b9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Jan 2016 17:40:03 +1100 Subject: [PATCH] Better transparency handling Former-commit-id: a1d0f5d52b21aa76531f86ee63f75973311e1837 Former-commit-id: b3f3963a089a4c99ae8ceda8b4ad019b19e32ec2 Former-commit-id: eb5b4e013f56035cd961b03e213cea128ee5e88b --- src/ImageProcessor/Colors/Color.cs | 8 ++- src/ImageProcessor/Filters/BackgroundColor.cs | 13 +++- .../Filters/ColorMatrix/Polaroid.cs | 2 +- src/ImageProcessor/Filters/Glow.cs | 4 +- src/ImageProcessor/Filters/Vignette.cs | 13 +++- src/ImageProcessor/Formats/Bmp/BmpEncoder.cs | 19 ------ src/ImageProcessor/Formats/Gif/GifEncoder.cs | 16 +---- .../Formats/Gif/Quantizer/OctreeQuantizer.cs | 16 +---- src/ImageProcessor/Formats/IImageEncoder.cs | 5 -- src/ImageProcessor/Formats/Jpg/JpegEncoder.cs | 19 ------ src/ImageProcessor/Formats/Png/PngEncoder.cs | 19 ------ .../Samplers/ImageSampleExtensions.cs | 2 +- .../Processors/Filters/FilterTests.cs | 62 +++++++++---------- .../Processors/Samplers/SamplerTests.cs | 27 ++++---- 14 files changed, 79 insertions(+), 146 deletions(-) diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index f7c60860c..340151126 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -350,9 +350,13 @@ namespace ImageProcessor { amount = amount.Clamp(0f, 1f); + if (Math.Abs(from.A - 1) < Epsilon || Math.Abs(from.A) < Epsilon) + { + return from + (to - from) * amount; + } + // Premultiplied. - return from + (to - from) * amount; - //return (from * (1 - amount)) + to; + return from * (1 - amount) + to; } /// diff --git a/src/ImageProcessor/Filters/BackgroundColor.cs b/src/ImageProcessor/Filters/BackgroundColor.cs index 4b159b888..cfb9a66d6 100644 --- a/src/ImageProcessor/Filters/BackgroundColor.cs +++ b/src/ImageProcessor/Filters/BackgroundColor.cs @@ -5,6 +5,7 @@ namespace ImageProcessor.Filters { + using System; using System.Threading.Tasks; /// @@ -12,6 +13,11 @@ namespace ImageProcessor.Filters /// public class BackgroundColor : ParallelImageProcessor { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + /// /// Initializes a new instance of the class. /// @@ -46,11 +52,16 @@ namespace ImageProcessor.Filters { Color color = source[x, y]; - if (color.A < 1) + if (color.A < 1 && color.A > 0) { color = Color.Lerp(color, backgroundColor, .5f); } + if (Math.Abs(color.A) < Epsilon) + { + color = backgroundColor; + } + target[x, y] = color; } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs b/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs index d6c91ffda..26231b386 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs @@ -33,7 +33,7 @@ namespace ImageProcessor.Filters protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { new Vignette { Color = new Color(102 / 255f, 34 / 255f, 0) }.Apply(target, target, targetRectangle); - new Glow { Color = new Color(1, 153 / 255f, 102 / 255f) }.Apply(target, target, targetRectangle); + new Glow { Color = new Color(1, 153 / 255f, 102 / 255f, .7f) }.Apply(target, target, targetRectangle); } } } diff --git a/src/ImageProcessor/Filters/Glow.cs b/src/ImageProcessor/Filters/Glow.cs index afc8b392a..43ab28356 100644 --- a/src/ImageProcessor/Filters/Glow.cs +++ b/src/ImageProcessor/Filters/Glow.cs @@ -34,7 +34,7 @@ namespace ImageProcessor.Filters { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Color color = this.Color; + Color glowColor = this.Color; Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; @@ -49,7 +49,7 @@ namespace ImageProcessor.Filters { float distance = Vector2.Distance(centre, new Vector2(x, y)); Color sourceColor = target[x, y]; - target[x, y] = Color.Lerp(color, sourceColor, 4f * distance / maxDistance); + target[x, y] = Color.Lerp(sourceColor, glowColor, .334f * (1 - distance / maxDistance)); } }); } diff --git a/src/ImageProcessor/Filters/Vignette.cs b/src/ImageProcessor/Filters/Vignette.cs index c4cf02685..bae28f565 100644 --- a/src/ImageProcessor/Filters/Vignette.cs +++ b/src/ImageProcessor/Filters/Vignette.cs @@ -17,7 +17,7 @@ namespace ImageProcessor.Filters /// /// Gets or sets the vignette color to apply. /// - public Color Color { get; set; } = Color.Black; + public Color Color { get; set; } = new Color(0, 0, 0, 1); /// /// Gets or sets the the x-radius. @@ -34,7 +34,7 @@ namespace ImageProcessor.Filters { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Color color = this.Color; + Color vignetteColor = this.Color; Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; @@ -49,7 +49,14 @@ namespace ImageProcessor.Filters { float distance = Vector2.Distance(centre, new Vector2(x, y)); Color sourceColor = target[x, y]; - target[x, y] = Color.Lerp(sourceColor, color, .9f * distance / maxDistance); + //if (sourceColor.A > 0) + //{ + target[x, y] = Color.Lerp(sourceColor, vignetteColor, .9f * distance / maxDistance); + //} + //else + //{ + // target[x, y] = Color.Lerp(sourceColor, color, distance / maxDistance); + //} } }); } diff --git a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs index 04bf02dd0..ad80bbc76 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs @@ -14,11 +14,6 @@ namespace ImageProcessor.Formats /// The encoder can currently only write 24-bit rgb images to streams. public class BmpEncoder : IImageEncoder { - /// - /// The the transparency threshold. - /// - private int threshold = 128; - /// /// Gets or sets the quality of output for images. /// @@ -31,15 +26,6 @@ namespace ImageProcessor.Formats /// public string Extension => "bmp"; - /// - /// Gets or sets the transparency threshold. - /// - public int Threshold - { - get { return this.threshold; } - set { this.threshold = value.Clamp(0, 255); } - } - /// public bool IsSupportedFileExtension(string extension) { @@ -132,11 +118,6 @@ namespace ImageProcessor.Formats Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); - if (color.A < this.Threshold) - { - color = new Bgra32(0, 0, 0, 0); - } - writer.Write(color.B); writer.Write(color.G); writer.Write(color.R); diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs index 81735f55b..2d1654700 100644 --- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs @@ -15,11 +15,6 @@ namespace ImageProcessor.Formats /// public class GifEncoder : IImageEncoder { - /// - /// The the transparency threshold. - /// - private int threshold = 128; - /// /// Gets or sets the quality of output for images. /// @@ -32,15 +27,6 @@ namespace ImageProcessor.Formats /// public string MimeType => "image/gif"; - /// - /// Gets or sets the transparency threshold. - /// - public int Threshold - { - get { return this.threshold; } - set { this.threshold = value.Clamp(0, 255); } - } - /// public bool IsSupportedFileExtension(string extension) { @@ -138,7 +124,7 @@ namespace ImageProcessor.Formats private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth) { // Quantize the image returning a pallete. - IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth) { Threshold = this.threshold }; + IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth); QuantizedImage quantizedImage = quantizer.Quantize(image); // Grab the pallete and write it to the stream. diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs index 7954e1af7..79eaf26c2 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs @@ -24,11 +24,6 @@ namespace ImageProcessor.Formats /// private readonly int maxColors; - /// - /// The the transparency threshold. - /// - private int threshold = 128; - /// /// Initializes a new instance of the class. /// @@ -65,15 +60,6 @@ namespace ImageProcessor.Formats this.maxColors = maxColors; } - /// - /// Gets or sets the transparency threshold. - /// - public int Threshold - { - get { return this.threshold; } - set { this.threshold = value.Clamp(0, 255); } - } - /// /// Process the pixel in the first pass of the algorithm /// @@ -105,7 +91,7 @@ namespace ImageProcessor.Formats byte paletteIndex = (byte)this.maxColors; // Get the palette index if it's transparency meets criterea. - if (pixel.A >= this.Threshold) + if (pixel.A > 0) { paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); } diff --git a/src/ImageProcessor/Formats/IImageEncoder.cs b/src/ImageProcessor/Formats/IImageEncoder.cs index eb0df5561..bda8dbb42 100644 --- a/src/ImageProcessor/Formats/IImageEncoder.cs +++ b/src/ImageProcessor/Formats/IImageEncoder.cs @@ -22,11 +22,6 @@ namespace ImageProcessor.Formats /// int Quality { get; set; } - /// - /// Gets or sets the transparency threshold. - /// - int Threshold { get; set; } - /// /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. /// diff --git a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs index d794c9d6e..0517b422c 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs @@ -21,11 +21,6 @@ namespace ImageProcessor.Formats /// private int quality = 100; - /// - /// The the transparency threshold. - /// - private int threshold = 128; - /// /// Gets or sets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). @@ -37,15 +32,6 @@ namespace ImageProcessor.Formats set { this.quality = value.Clamp(1, 100); } } - /// - /// Gets or sets the transparency threshold. - /// - public int Threshold - { - get { return this.threshold; } - set { this.threshold = value.Clamp(0, 255); } - } - /// public string MimeType => "image/jpeg"; @@ -127,11 +113,6 @@ namespace ImageProcessor.Formats Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); - if (color.A < this.Threshold) - { - color = new Bgra32(0, 0, 0, 0); - } - samples[start] = color.R; samples[start + 1] = color.G; samples[start + 2] = color.B; diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs index 64e3ff7c9..e4501a956 100644 --- a/src/ImageProcessor/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs @@ -18,11 +18,6 @@ namespace ImageProcessor.Formats /// private const int MaxBlockSize = 0xFFFF; - /// - /// The the transparency threshold. - /// - private int threshold; - /// /// Initializes a new instance of the class. /// @@ -37,15 +32,6 @@ namespace ImageProcessor.Formats /// Png is a lossless format so this is not used in this encoder. public int Quality { get; set; } - /// - /// Gets or sets the transparency threshold. - /// - public int Threshold - { - get { return this.threshold; } - set { this.threshold = value.Clamp(0, 255); } - } - /// public string MimeType => "image/png"; @@ -342,11 +328,6 @@ namespace ImageProcessor.Formats Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); - if (color.A < this.Threshold) - { - color = new Bgra32(0, 0, 0, 0); - } - data[dataOffset] = color.R; data[dataOffset + 1] = color.G; data[dataOffset + 2] = color.B; diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs index 88ed3ef68..43f7e7dc8 100644 --- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs +++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs @@ -121,7 +121,7 @@ namespace ImageProcessor.Samplers /// The public static Image Rotate(this Image source, float degrees) { - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new RobidouxResampler()) { Angle = degrees }); + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new BicubicResampler()) { Angle = degrees }); } /// diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs index 72a0cae15..e4fe0704c 100644 --- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs @@ -13,39 +13,39 @@ namespace ImageProcessor.Tests { public static readonly TheoryData Filters = new TheoryData { - { "Brightness-50", new Brightness(50) }, - { "Brightness--50", new Brightness(-50) }, - { "Contrast-50", new Contrast(50) }, - { "Contrast--50", new Contrast(-50) }, - { "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))}, - { "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)}, - { "Saturation-50", new Saturation(50) }, - { "Saturation--50", new Saturation(-50) }, + //{ "Brightness-50", new Brightness(50) }, + //{ "Brightness--50", new Brightness(-50) }, + //{ "Contrast-50", new Contrast(50) }, + //{ "Contrast--50", new Contrast(-50) }, + //{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f,.5f))}, + //{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)}, + //{ "Saturation-50", new Saturation(50) }, + //{ "Saturation--50", new Saturation(-50) }, { "Alpha--50", new Alpha(50) }, - { "Invert", new Invert() }, - { "Sepia", new Sepia() }, - { "BlackWhite", new BlackWhite() }, - { "Lomograph", new Lomograph() }, + //{ "Invert", new Invert() }, + //{ "Sepia", new Sepia() }, + //{ "BlackWhite", new BlackWhite() }, + //{ "Lomograph", new Lomograph() }, { "Polaroid", new Polaroid() }, - { "Kodachrome", new Kodachrome() }, - { "GreyscaleBt709", new GreyscaleBt709() }, - { "GreyscaleBt601", new GreyscaleBt601() }, - { "Kayyali", new Kayyali() }, - { "Kirsch", new Kirsch() }, - { "Laplacian3X3", new Laplacian3X3() }, - { "Laplacian5X5", new Laplacian5X5() }, - { "LaplacianOfGaussian", new LaplacianOfGaussian() }, - { "Prewitt", new Prewitt() }, - { "RobertsCross", new RobertsCross() }, - { "Scharr", new Scharr() }, - { "Sobel", new Sobel {Greyscale = true} }, - { "Pixelate", new Pixelate(8) }, - { "GuassianBlur", new GuassianBlur(10) }, - { "GuassianSharpen", new GuassianSharpen(10) }, - { "Hue-180", new Hue(180) }, - { "Hue--180", new Hue(-180) }, - { "BoxBlur", new BoxBlur(10) }, - {"Vignette", new Vignette()} + //{ "Kodachrome", new Kodachrome() }, + //{ "GreyscaleBt709", new GreyscaleBt709() }, + //{ "GreyscaleBt601", new GreyscaleBt601() }, + //{ "Kayyali", new Kayyali() }, + //{ "Kirsch", new Kirsch() }, + //{ "Laplacian3X3", new Laplacian3X3() }, + //{ "Laplacian5X5", new Laplacian5X5() }, + //{ "LaplacianOfGaussian", new LaplacianOfGaussian() }, + //{ "Prewitt", new Prewitt() }, + //{ "RobertsCross", new RobertsCross() }, + //{ "Scharr", new Scharr() }, + //{ "Sobel", new Sobel {Greyscale = true} }, + //{ "Pixelate", new Pixelate(8) }, + //{ "GuassianBlur", new GuassianBlur(10) }, + //{ "GuassianSharpen", new GuassianSharpen(10) }, + //{ "Hue-180", new Hue(180) }, + //{ "Hue--180", new Hue(-180) }, + //{ "BoxBlur", new BoxBlur(10) }, + //{"Vignette", new Vignette()} }; [Theory] diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 8b045629d..dfc26728d 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -13,20 +13,20 @@ public static readonly TheoryData Samplers = new TheoryData { - //{ "Bicubic", new BicubicResampler() }, - //{ "Triangle", new TriangleResampler() }, - //{ "Box", new BoxResampler() }, - //{ "Lanczos3", new Lanczos3Resampler() }, - //{ "Lanczos5", new Lanczos5Resampler() }, - //{ "Lanczos8", new Lanczos8Resampler() }, - //{ "MitchellNetravali", new MitchellNetravaliResampler() }, + { "Bicubic", new BicubicResampler() }, + { "Triangle", new TriangleResampler() }, + { "Box", new BoxResampler() }, + { "Lanczos3", new Lanczos3Resampler() }, + { "Lanczos5", new Lanczos5Resampler() }, + { "Lanczos8", new Lanczos8Resampler() }, + { "MitchellNetravali", new MitchellNetravaliResampler() }, { "NearestNeighbor", new NearestNeighborResampler() }, - //{ "Hermite", new HermiteResampler() }, - //{ "Spline", new SplineResampler() }, - //{ "Robidoux", new RobidouxResampler() }, - //{ "RobidouxSharp", new RobidouxSharpResampler() }, - //{ "RobidouxSoft", new RobidouxSoftResampler() }, - //{ "Welch", new WelchResampler() } + { "Hermite", new HermiteResampler() }, + { "Spline", new SplineResampler() }, + { "Robidoux", new RobidouxResampler() }, + { "RobidouxSharp", new RobidouxSharpResampler() }, + { "RobidouxSoft", new RobidouxSoftResampler() }, + { "Welch", new WelchResampler() } }; public static readonly TheoryData RotateFlips = new TheoryData @@ -167,6 +167,7 @@ using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) { image.Rotate(45, sampler) + .BackgroundColor(Color.Aqua) .Save(output); }