From 56b9d15017ac416f55ca748c5da5f470c379f7e4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Mar 2016 00:41:07 +1100 Subject: [PATCH] Quantizers now implement threshold. Former-commit-id: fb4a043db98f56ae1261cb3a7c3b1044798d5f16 Former-commit-id: 6abe27ce1f96183dbd6e1bcd76bb2af0654d21bd Former-commit-id: 9f742e5ca28a2adb62976f9b73fed8b7d773da16 --- .../Formats/Gif/GifEncoder.cs | 12 ++++- .../Quantizers/IQuantizer.cs | 5 +++ .../Quantizers/Octree/OctreeQuantizer.cs | 5 --- .../Quantizers/Octree/Quantizer.cs | 42 ++++++----------- .../Quantizers/Palette/PaletteQuantizer.cs | 21 ++++++--- .../Quantizers/Wu/WuQuantizer.cs | 45 ++++++++++++------- .../Processors/Formats/EncoderDecoderTests.cs | 2 +- 7 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs index 5fb3e4b8e9..4dd43f30f7 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs @@ -23,10 +23,15 @@ namespace ImageProcessorCore.Formats /// For gifs the value ranges from 1 to 256. public int Quality { get; set; } + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + /// /// The quantizer for reducing the color count. /// - public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); + public IQuantizer Quantizer { get; set; } /// public string Extension => "gif"; @@ -51,6 +56,11 @@ namespace ImageProcessorCore.Formats Image image = (Image)imageBase; + if (this.Quantizer == null) + { + this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; + } + // Write the header. // File Header signature and version. this.WriteString(stream, GifConstants.FileType); diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs index 68f7554dab..3196ebf9f1 100644 --- a/src/ImageProcessorCore/Quantizers/IQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -10,6 +10,11 @@ namespace ImageProcessorCore.Quantizers /// public interface IQuantizer { + /// + /// Gets or sets the transparency threshold. + /// + byte Threshold { get; set; } + /// /// Quantize an image and return the resulting output pixels. /// diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs index 1ef4cd541c..c0c900145b 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs @@ -36,11 +36,6 @@ namespace ImageProcessorCore.Quantizers { } - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - /// public override QuantizedImage Quantize(ImageBase image, int maxColors) { diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs index 3c66cc944c..0db212da57 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs @@ -7,6 +7,7 @@ namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; + using System.Threading.Tasks; /// /// Encapsulates methods to calculate the color palette of an image. @@ -39,6 +40,9 @@ namespace ImageProcessorCore.Quantizers /// public int TransparentIndex { get; protected set; } = -1; + /// + public byte Threshold { get; set; } + /// public virtual QuantizedImage Quantize(ImageBase image, int maxColors) { @@ -95,35 +99,17 @@ namespace ImageProcessorCore.Quantizers /// The height in pixels of the image protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height) { - int i = 0; - - // Convert the first pixel, so that I have values going into the loop. - // Implicit cast here from Color. - Bgra32 previousPixel = source[0, 0]; - byte pixelValue = this.QuantizePixel(previousPixel); - - output[0] = pixelValue; - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - Bgra32 sourcePixel = source[x, y]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (sourcePixel != previousPixel) + Parallel.For( + 0, + source.Height, + y => { - // Quantize the pixel - pixelValue = this.QuantizePixel(sourcePixel); - - // And setup the previous pointer - previousPixel = sourcePixel; - } - - output[i++] = pixelValue; - } - } + for (int x = 0; x < source.Width; x++) + { + Bgra32 sourcePixel = source[x, y]; + output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); + } + }); } /// diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs index 74f192834d..47f77a8777 100644 --- a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs @@ -35,9 +35,16 @@ namespace ImageProcessorCore.Quantizers public PaletteQuantizer(Color[] palette = null) : base(true) { - this.colors = palette == null - ? ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToArray() - : palette.Select(c => (Bgra32)c).ToArray(); + if (palette == null) + { + List safe = ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToList(); + safe.Insert(0, Bgra32.Empty); + this.colors = safe.ToArray(); + } + else + { + this.colors = palette.Select(c => (Bgra32)c).ToArray(); + } } /// @@ -61,8 +68,8 @@ namespace ImageProcessorCore.Quantizers else { // Not found - loop through the palette and find the nearest match. - // Firstly check the alpha value - if 0, lookup the transparent color - if (pixel.A == 0) + // Firstly check the alpha value - if less than the threshold, lookup the transparent color + if (!(pixel.A > this.Threshold)) { // Transparent. Lookup the first color with an alpha value of 0 for (int index = 0; index < this.colors.Length; index++) @@ -91,7 +98,7 @@ namespace ImageProcessorCore.Quantizers int redDistance = paletteColor.R - red; int greenDistance = paletteColor.G - green; int blueDistance = paletteColor.B - blue; - + int distance = (redDistance * redDistance) + (greenDistance * greenDistance) + (blueDistance * blueDistance); @@ -111,7 +118,7 @@ namespace ImageProcessorCore.Quantizers } // Now I have the color, pop it into the cache for next time - this.colorMap.Add(colorHash, colorIndex); + this.colorMap[colorHash] = colorIndex; } return colorIndex; diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index 9bb4e5f842..55e0bdcc99 100644 --- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -7,6 +7,7 @@ namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; + using System.Threading.Tasks; /// /// An implementation of Wu's color quantizer with alpha channel. @@ -110,6 +111,9 @@ namespace ImageProcessorCore.Quantizers this.tag = new byte[TableLength]; } + /// + public byte Threshold { get; set; } + /// public QuantizedImage Quantize(ImageBase image, int maxColors) { @@ -730,7 +734,7 @@ namespace ImageProcessorCore.Quantizers byte b = (byte)(Volume(cube[k], this.vmb) / weight); byte a = (byte)(Volume(cube[k], this.vma) / weight); - var color = new Bgra32(b, g, r, a); + Bgra32 color = new Bgra32(b, g, r, a); if (color == Bgra32.Empty) { @@ -746,22 +750,29 @@ namespace ImageProcessorCore.Quantizers } } - // TODO: Optimize here. - int i = 0; - for (int y = 0; y < image.Height; y++) - { - for (int x = 0; x < image.Width; x++) - { - Bgra32 color = image[x, y]; - int a = color.A >> (8 - IndexAlphaBits); - int r = color.R >> (8 - IndexBits); - int g = color.G >> (8 - IndexBits); - int b = color.B >> (8 - IndexBits); - - int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - pixels[i++] = this.tag[ind]; - } - } + Parallel.For( + 0, + image.Height, + y => + { + for (int x = 0; x < image.Width; x++) + { + Bgra32 color = image[x, y]; + int a = color.A >> (8 - IndexAlphaBits); + int r = color.R >> (8 - IndexBits); + int g = color.G >> (8 - IndexBits); + int b = color.B >> (8 - IndexBits); + + if (transparentIndex > -1 && color.A <= this.Threshold) + { + pixels[(y * image.Width) + x] = (byte)transparentIndex; + continue; + } + + int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + pixels[(y * image.Width) + x] = this.tag[ind]; + } + }); return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex); } diff --git a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs index c5eea6ba05..33df554423 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs @@ -62,7 +62,7 @@ using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}")) { - quantizedImage.ToImage().Save(output); + quantizedImage.ToImage().Save(output, image.CurrentImageFormat); } } }