From 53ca3d5de1cfe0609fda1a08ea47378fef52fc14 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 17:22:36 +1100 Subject: [PATCH] Make QuantizePixel non-inheritable --- src/ImageSharp/Quantizers/IQuantizer.cs | 2 - .../Quantizers/Octree/OctreeQuantizer.cs | 94 +++++++++++++++++-- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 55 ++--------- .../Quantizers/Palette/PaletteQuantizer.cs | 67 ++++++++++++- 4 files changed, 158 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index 9ee307266..88f273e9b 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -5,8 +5,6 @@ namespace ImageSharp.Quantizers { - using System; - using ImageSharp.Dithering; /// diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index c047e1af4..2590f297e 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; + using System.Runtime.CompilerServices; /// /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. @@ -16,6 +17,11 @@ namespace ImageSharp.Quantizers public sealed class OctreeQuantizer : Quantizer where TColor : struct, IPixel { + /// + /// A lookup table for colors + /// + private readonly Dictionary colorMap = new Dictionary(); + /// /// The pixel buffer, used to reduce allocations. /// @@ -31,6 +37,11 @@ namespace ImageSharp.Quantizers /// private int colors; + /// + /// The reduced image palette + /// + private TColor[] palette; + /// /// Initializes a new instance of the class. /// @@ -52,6 +63,58 @@ namespace ImageSharp.Quantizers return base.Quantize(image, maxColors); } + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + { + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TColor sourcePixel = source[0, 0]; + TColor previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(sourcePixel); + TColor[] colorPalette = this.GetPalette(); + TColor transformedPixel = colorPalette[pixelValue]; + + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + 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 (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = colorPalette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } + } + /// /// Process the pixel in the first pass of the algorithm /// @@ -69,26 +132,39 @@ namespace ImageSharp.Quantizers } /// - /// Override this to process the pixel in the second pass of the algorithm + /// Retrieve the palette for the quantized image. /// - /// The pixel to quantize /// - /// The quantized value + /// The new color palette /// - protected override byte QuantizePixel(TColor pixel) + protected override TColor[] GetPalette() { - return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); + if (this.palette == null) + { + this.palette = this.octree.Palletize(Math.Max(this.colors, 1)); + } + + return this.palette; } /// - /// Retrieve the palette for the quantized image. + /// Process the pixel in the second pass of the algorithm /// + /// The pixel to quantize /// - /// The new color palette + /// The quantized value /// - protected override TColor[] GetPalette() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(TColor pixel) { - return this.octree.Palletize(Math.Max(this.colors, 1)); + if (this.Dither) + { + // The colors have changed so we need to use Euclidean distance caclulation to find the closest value. + // This palette can never be null here. + return this.GetClosestColor(pixel, this.palette, this.colorMap); + } + + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); } /// diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 8af638de3..0e6540d42 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -9,7 +9,6 @@ namespace ImageSharp.Quantizers using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; - using ImageSharp.Dithering; /// @@ -19,21 +18,11 @@ namespace ImageSharp.Quantizers public abstract class Quantizer : IDitheredQuantizer where TColor : struct, IPixel { - /// - /// A lookup table for colors - /// - private readonly Dictionary colorMap = new Dictionary(); - /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// private readonly bool singlePass; - /// - /// The reduced image palette - /// - private TColor[] palette; - /// /// Initializes a new instance of the class. /// @@ -65,6 +54,7 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; + TColor[] colorPalette; using (PixelAccessor pixels = image.Lock()) { @@ -76,8 +66,8 @@ namespace ImageSharp.Quantizers this.FirstPass(pixels, width, height); } - // Get the palette - this.palette = this.GetPalette(); + // Collect the palette. Octree requires this to be done before the second pass runs. + colorPalette = this.GetPalette(); if (this.Dither) { @@ -94,7 +84,7 @@ namespace ImageSharp.Quantizers } } - return new QuantizedImage(width, height, this.palette, quantizedPixels); + return new QuantizedImage(width, height, colorPalette, quantizedPixels); } /// @@ -124,25 +114,7 @@ namespace ImageSharp.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) - { - for (int y = 0; y < height; y++) - { - // And loop through each column - for (int x = 0; x < width; x++) - { - if (this.Dither) - { - // Apply the dithering matrix - TColor sourcePixel = source[x, y]; - TColor transformedPixel = this.palette[this.GetClosestColor(sourcePixel, this.palette, this.colorMap)]; - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); - } - - output[(y * source.Width) + x] = this.QuantizePixel(source[x, y]); - } - } - } + protected abstract void SecondPass(PixelAccessor source, byte[] output, int width, int height); /// /// Override this to process the pixel in the first pass of the algorithm @@ -157,16 +129,7 @@ namespace ImageSharp.Quantizers } /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - protected abstract byte QuantizePixel(TColor pixel); - - /// - /// Retrieve the palette for the quantized image + /// Retrieve the palette for the quantized image. Can be called more than once so make sure calls are cached. /// /// /// @@ -184,9 +147,9 @@ namespace ImageSharp.Quantizers protected byte GetClosestColor(TColor pixel, TColor[] colorPalette, Dictionary cache) { // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(pixel)) + if (cache.ContainsKey(pixel)) { - return this.colorMap[pixel]; + return cache[pixel]; } // Not found - loop through the palette and find the nearest match. @@ -215,7 +178,7 @@ namespace ImageSharp.Quantizers } // Now I have the index, pop it into the cache for next time - this.colorMap.Add(pixel, colorIndex); + cache.Add(pixel, colorIndex); return colorIndex; } diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index 19f10aabb..dedb5ca23 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; + using System.Numerics; + using System.Runtime.CompilerServices; /// /// Encapsulates methods to create a quantized image based upon the given palette. @@ -69,10 +71,56 @@ namespace ImageSharp.Quantizers return base.Quantize(image, maxColors); } - /// - protected override byte QuantizePixel(TColor pixel) + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) { - return this.GetClosestColor(pixel, this.colors, this.colorMap); + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TColor sourcePixel = source[0, 0]; + TColor previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(sourcePixel); + TColor[] colorPalette = this.GetPalette(); + TColor transformedPixel = colorPalette[pixelValue]; + + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + 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 (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = colorPalette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } } /// @@ -80,5 +128,18 @@ namespace ImageSharp.Quantizers { return this.colors; } + + /// + /// Process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(TColor pixel) + { + return this.GetClosestColor(pixel, this.GetPalette(), this.colorMap); + } } } \ No newline at end of file