Browse Source

Make QuantizePixel non-inheritable

pull/137/head
James Jackson-South 9 years ago
parent
commit
53ca3d5de1
  1. 2
      src/ImageSharp/Quantizers/IQuantizer.cs
  2. 94
      src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
  3. 55
      src/ImageSharp/Quantizers/Octree/Quantizer.cs
  4. 67
      src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs

2
src/ImageSharp/Quantizers/IQuantizer.cs

@ -5,8 +5,6 @@
namespace ImageSharp.Quantizers
{
using System;
using ImageSharp.Dithering;
/// <summary>

94
src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
/// <summary>
/// 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<TColor> : Quantizer<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// A lookup table for colors
/// </summary>
private readonly Dictionary<TColor, byte> colorMap = new Dictionary<TColor, byte>();
/// <summary>
/// The pixel buffer, used to reduce allocations.
/// </summary>
@ -31,6 +37,11 @@ namespace ImageSharp.Quantizers
/// </summary>
private int colors;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TColor}"/> class.
/// </summary>
@ -52,6 +63,58 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors);
}
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected override void SecondPass(PixelAccessor<TColor> 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;
}
}
}
/// <summary>
/// Process the pixel in the first pass of the algorithm
/// </summary>
@ -69,26 +132,39 @@ namespace ImageSharp.Quantizers
}
/// <summary>
/// Override this to process the pixel in the second pass of the algorithm
/// Retrieve the palette for the quantized image.
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// The new color palette
/// </returns>
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;
}
/// <summary>
/// Retrieve the palette for the quantized image.
/// Process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The new color palette
/// The quantized value
/// </returns>
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);
}
/// <summary>

55
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;
/// <summary>
@ -19,21 +18,11 @@ namespace ImageSharp.Quantizers
public abstract class Quantizer<TColor> : IDitheredQuantizer<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// A lookup table for colors
/// </summary>
private readonly Dictionary<TColor, byte> colorMap = new Dictionary<TColor, byte>();
/// <summary>
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
/// </summary>
private readonly bool singlePass;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="Quantizer{TColor}"/> class.
/// </summary>
@ -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<TColor> 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<TColor>(width, height, this.palette, quantizedPixels);
return new QuantizedImage<TColor>(width, height, colorPalette, quantizedPixels);
}
/// <summary>
@ -124,25 +114,7 @@ namespace ImageSharp.Quantizers
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected virtual void SecondPass(PixelAccessor<TColor> 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<TColor> source, byte[] output, int width, int height);
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
@ -157,16 +129,7 @@ namespace ImageSharp.Quantizers
}
/// <summary>
/// Override this to process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>
protected abstract byte QuantizePixel(TColor pixel);
/// <summary>
/// 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.
/// </summary>
/// <returns>
/// <see cref="T:TColor[]"/>
@ -184,9 +147,9 @@ namespace ImageSharp.Quantizers
protected byte GetClosestColor(TColor pixel, TColor[] colorPalette, Dictionary<TColor, byte> 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;
}

67
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;
/// <summary>
/// Encapsulates methods to create a quantized image based upon the given palette.
@ -69,10 +71,56 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors);
}
/// <inheritdoc/>
protected override byte QuantizePixel(TColor pixel)
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected override void SecondPass(PixelAccessor<TColor> 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;
}
}
}
/// <inheritdoc/>
@ -80,5 +128,18 @@ namespace ImageSharp.Quantizers
{
return this.colors;
}
/// <summary>
/// Process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TColor pixel)
{
return this.GetClosestColor(pixel, this.GetPalette(), this.colorMap);
}
}
}
Loading…
Cancel
Save