From 47e4f66d9e02f2efdc712d536ae5c1bf60b92fbd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 25 Nov 2015 18:51:37 +1100 Subject: [PATCH] Better transparency handling in encoders Former-commit-id: 4ac66346af234c105628b588a84a4cb746c3e6ad Former-commit-id: 3c249d1fb3e2860e3df77b1cb8e30bd60fda1db4 Former-commit-id: fb192a7e2fdaedf1b75173c8283b44642614d81d --- src/ImageProcessor/Colors/Color.cs | 10 +++----- src/ImageProcessor/Filters/BackgroundColor.cs | 10 +++++--- src/ImageProcessor/Formats/Bmp/BmpEncoder.cs | 23 +++++++++++++++++-- src/ImageProcessor/Formats/Gif/GifEncoder.cs | 16 ++++++++++++- .../Formats/Gif/Quantizer/OctreeQuantizer.cs | 11 ++++++++- src/ImageProcessor/Formats/IImageEncoder.cs | 5 ++++ src/ImageProcessor/Formats/Jpg/JpegEncoder.cs | 21 ++++++++++++++++- src/ImageProcessor/Formats/Png/PngEncoder.cs | 19 +++++++++++++++ src/ImageProcessor/Samplers/Resampler.cs | 14 +++++------ .../Processors/Samplers/SamplerTests.cs | 2 +- 10 files changed, 108 insertions(+), 23 deletions(-) diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index 0f4d26ce1..a1ca1817f 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -473,13 +473,9 @@ namespace ImageProcessor public static Color Lerp(Color from, Color to, float amount) { amount = amount.Clamp(0f, 1f); - - if (to.A < 1) - { - return (from * (1 - amount)) + to; - } - - return (from * (1 - amount)) + (to * amount); + + // Premultiplied. + return (from * (1 - amount)) + to; } /// diff --git a/src/ImageProcessor/Filters/BackgroundColor.cs b/src/ImageProcessor/Filters/BackgroundColor.cs index 58c33db7e..e1e49d142 100644 --- a/src/ImageProcessor/Filters/BackgroundColor.cs +++ b/src/ImageProcessor/Filters/BackgroundColor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,7 +13,7 @@ namespace ImageProcessor.Filters public class BackgroundColor : ParallelImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The to set the background color to. public BackgroundColor(Color color) @@ -46,7 +46,11 @@ namespace ImageProcessor.Filters { Color color = source[x, y]; - if (color.A < 1) + if (color == Color.Empty) + { + color = backgroundColor; + } + else if (color.A < 1) { color = Color.Lerp(color, backgroundColor, .5f); } diff --git a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs index 2cb80ef7e..d227ec1d1 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs @@ -14,6 +14,11 @@ 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. /// @@ -26,6 +31,15 @@ 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) { @@ -77,7 +91,7 @@ namespace ImageProcessor.Formats WriteInfo(writer, infoHeader); - WriteImage(writer, image); + this.WriteImage(writer, image); writer.Flush(); } @@ -91,7 +105,7 @@ namespace ImageProcessor.Formats /// /// The containing pixel data. /// - private static void WriteImage(BinaryWriter writer, ImageBase image) + private void WriteImage(BinaryWriter writer, ImageBase image) { // TODO: Add more compression formats. int amount = (image.Width * 3) % 4; @@ -119,6 +133,11 @@ namespace ImageProcessor.Formats // Implicit cast to Bgra32 handles premultiplication conversion. Bgra32 color = 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 5ea513dad..d50182448 100644 --- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs @@ -14,6 +14,11 @@ namespace ImageProcessor.Formats /// public class GifEncoder : IImageEncoder { + /// + /// The the transparency threshold. + /// + private int threshold = 128; + /// /// Gets or sets the quality of output for images. /// @@ -25,6 +30,15 @@ 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) @@ -123,7 +137,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); + IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth) {Threshold = this.threshold}; 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 b7a04aab2..7954e1af7 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs @@ -24,6 +24,11 @@ namespace ImageProcessor.Formats /// private readonly int maxColors; + /// + /// The the transparency threshold. + /// + private int threshold = 128; + /// /// Initializes a new instance of the class. /// @@ -63,7 +68,11 @@ namespace ImageProcessor.Formats /// /// Gets or sets the transparency threshold. /// - public byte Threshold { get; set; } = 128; + public int Threshold + { + get { return this.threshold; } + set { this.threshold = value.Clamp(0, 255); } + } /// /// Process the pixel in the first pass of the algorithm diff --git a/src/ImageProcessor/Formats/IImageEncoder.cs b/src/ImageProcessor/Formats/IImageEncoder.cs index bda8dbb42..eb0df5561 100644 --- a/src/ImageProcessor/Formats/IImageEncoder.cs +++ b/src/ImageProcessor/Formats/IImageEncoder.cs @@ -22,6 +22,11 @@ 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 93c4a9de3..30fd6574c 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs @@ -17,10 +17,15 @@ namespace ImageProcessor.Formats public class JpegEncoder : IImageEncoder { /// - /// The jpeg quality. + /// The quality. /// 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). @@ -32,6 +37,15 @@ 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"; @@ -114,6 +128,11 @@ namespace ImageProcessor.Formats // Implicit cast to Bgra32 handles premultiplication conversion. Bgra32 color = 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 00ba4b0a3..0b9edb44e 100644 --- a/src/ImageProcessor/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs @@ -18,6 +18,11 @@ namespace ImageProcessor.Formats /// private const int MaxBlockSize = 0xFFFF; + /// + /// The the transparency threshold. + /// + private int threshold; + /// /// Initializes a new instance of the class. /// @@ -32,6 +37,15 @@ 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"; @@ -329,6 +343,11 @@ namespace ImageProcessor.Formats // Implicit cast to Bgra32 handles premultiplication conversion. Bgra32 color = 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/Resampler.cs b/src/ImageProcessor/Samplers/Resampler.cs index 0f932b2af..a98ac835e 100644 --- a/src/ImageProcessor/Samplers/Resampler.cs +++ b/src/ImageProcessor/Samplers/Resampler.cs @@ -157,15 +157,15 @@ namespace ImageProcessor.Samplers // Restrict alpha values in an attempt to prevent bleed. // This is a baaaaaaad hack!!! - if (destination.A <= 0.03) - { - destination = Color.Empty; - } - else - { + //if (destination.A <= 0.03) + //{ + // destination = Color.Empty; + //} + //else + //{ destination = Color.Compand(destination); destination.A = (float)Math.Round(destination.A, 2); - } + //} target[x, y] = destination; } diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 355230843..e171341f8 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -43,7 +43,7 @@ namespace ImageProcessor.Tests { Stopwatch watch = Stopwatch.StartNew(); Image image = new Image(stream); - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + ".jpg"; + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); using (FileStream output = File.OpenWrite($"TestOutput/Resized/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output);