diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
index bdc1ca27e..face314ae 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
@@ -20,8 +20,8 @@ namespace ImageProcessor.Formats
///
/// The image to quantize.
///
- /// A representing a quantized version of the image pixels.
+ /// A representing a quantized version of the image pixels.
///
- byte[] Quantize(ImageBase imageBase);
+ QuantizedImage Quantize(ImageBase imageBase);
}
}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
new file mode 100644
index 000000000..a463057a0
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
@@ -0,0 +1,80 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Provides methods for allowing quantization of images pixels.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System;
+
+ ///
+ /// Represents a quantized image where the pixels indexed by a color palette.
+ ///
+ public class QuantizedImage
+ {
+ ///
+ /// Gets the width of this .
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height of this .
+ ///
+ public int Height { get; }
+
+ ///
+ /// Gets the color palette of this .
+ ///
+ public Bgra[] Palette { get; }
+
+ ///
+ /// Gets the pixels of this .
+ ///
+ public byte[] Pixels { get; }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels)
+ {
+ if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width));
+ if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height));
+ if (palette == null) throw new ArgumentNullException(nameof(palette));
+ if (pixels == null) throw new ArgumentNullException(nameof(pixels));
+ if (pixels.Length != width * height) throw new ArgumentException("Pixel array size must be width * height", nameof(pixels));
+
+ this.Width = width;
+ this.Height = height;
+ this.Palette = palette;
+ this.Pixels = pixels;
+ }
+
+ ///
+ /// Converts this quantized image to a normal image.
+ ///
+ ///
+ public Image ToImage()
+ {
+ Image image = new Image();
+ int pixelCount = Pixels.Length;
+ byte[] bgraPixels = new byte[pixelCount * 4];
+
+ for (int i = 0; i < pixelCount; i++)
+ {
+ Bgra color = Palette[Pixels[i]];
+ bgraPixels[i + 0] = color.B;
+ bgraPixels[i + 1] = color.G;
+ bgraPixels[i + 2] = color.R;
+ bgraPixels[i + 3] = color.A;
+ }
+
+ image.SetPixels(Width, Height, bgraPixels);
+ return image;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
index 38f16bb88..797068790 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
@@ -11,6 +11,7 @@
namespace ImageProcessor.Formats
{
using System.Collections.Generic;
+ using System.Linq;
///
/// Encapsulates methods to calculate the color palette of an image.
@@ -45,22 +46,25 @@ namespace ImageProcessor.Formats
///
/// A representing a quantized version of the image pixels.
///
- public byte[] Quantize(ImageBase imageBase)
+ public QuantizedImage Quantize(ImageBase imageBase)
{
// Get the size of the source image
int height = imageBase.Height;
int width = imageBase.Width;
- ImageBase copy = new ImageFrame((ImageFrame)imageBase);
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
- if (!this.singlePass)
+ if (!singlePass)
{
- this.FirstPass(copy, width, height);
+ FirstPass(imageBase, width, height);
}
- throw new System.NotImplementedException();
+ byte[] quantizedPixels = new byte[width * height];
+
+ SecondPass(imageBase, quantizedPixels, width, height);
+
+ return new QuantizedImage(width, height, GetPalette().ToArray(), quantizedPixels);
}
///
@@ -92,18 +96,34 @@ namespace ImageProcessor.Formats
/// The height in pixels of the image
protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height)
{
- Bgra sourcePixel = source[0, 0];
+ int i = 0;
- // And convert the first pixel, so that I have values going into the loop
- byte pixelValue = this.QuantizePixel(sourcePixel);
+ // Convert the first pixel, so that I have values going into the loop
+ Bgra previousPixel = source[0, 0];
+ byte pixelValue = QuantizePixel(previousPixel);
output[0] = pixelValue;
for (int y = 0; y < height; y++)
{
- // TODO: Translate this from the old method.
- }
+ for (int x = 0; x < width; x++)
+ {
+ Bgra 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)
+ {
+ // Quantize the pixel
+ pixelValue = QuantizePixel(sourcePixel);
+
+ // And setup the previous pointer
+ previousPixel = sourcePixel;
+ }
+
+ output[i++] = pixelValue;
+ }
+ }
}
///
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index b840766ae..9fb2db2dd 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -52,6 +52,7 @@
+
diff --git a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
index 7cb38d1c3..5d561094c 100644
--- a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
@@ -57,5 +57,25 @@
Trace.WriteLine(string.Format("{0} : {1}ms", filename, watch.ElapsedMilliseconds));
}
+
+ [Theory]
+ [InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
+ public void QuantizedImageShouldPreserveMaximumColorPrecision(string filename)
+ {
+ if (!Directory.Exists("Quantized"))
+ {
+ Directory.CreateDirectory("Quantized");
+ }
+
+ Image image = new Image(File.OpenRead(filename));
+ IQuantizer quantizer = new OctreeQuantizer();
+ QuantizedImage quantizedImage = quantizer.Quantize(image);
+
+ using (FileStream output = File.OpenWrite($"Quantized/{ Path.GetFileName(filename) }"))
+ {
+ IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(Path.GetExtension(filename)));
+ encoder.Encode(quantizedImage.ToImage(), output);
+ }
+ }
}
}
\ No newline at end of file