From cea13f6866fef8f5ce6ded2a4a08ecf0e0409a8a Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Mon, 4 May 2015 11:00:43 +0800 Subject: [PATCH] Implement the interface for the basic quantizer. The resulting image is not correct with this commit but we have a starting point to troubleshoot the quantizer. Former-commit-id: 9a02fda23c4ee617948798ca10df40f415469291 Former-commit-id: 2a73febf146145751efde9460f3de24086422727 Former-commit-id: 98a3c0689c9d9a7cb6ca0badf9ef4ebec4e36cac --- .../Formats/Gif/Quantizer/IQuantizer.cs | 4 +- .../Formats/Gif/Quantizer/QuantizedImage.cs | 80 +++++++++++++++++++ .../Formats/Gif/Quantizer/Quantizer.cs | 40 +++++++--- src/ImageProcessor/ImageProcessor.csproj | 1 + .../Formats/EncoderDecoderTests.cs | 20 +++++ 5 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs 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