Browse Source

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
pull/17/head
Yufei Huang 11 years ago
parent
commit
cea13f6866
  1. 4
      src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
  2. 80
      src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
  3. 40
      src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
  4. 1
      src/ImageProcessor/ImageProcessor.csproj
  5. 20
      tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs

4
src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs

@ -20,8 +20,8 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
/// <param name="imageBase">The image to quantize.</param> /// <param name="imageBase">The image to quantize.</param>
/// <returns> /// <returns>
/// A <see cref="T:byte[]"/> representing a quantized version of the image pixels. /// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels.
/// </returns> /// </returns>
byte[] Quantize(ImageBase imageBase); QuantizedImage Quantize(ImageBase imageBase);
} }
} }

80
src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs

@ -0,0 +1,80 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QuantizedImage.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides methods for allowing quantization of images pixels.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
/// <summary>
/// Represents a quantized image where the pixels indexed by a color palette.
/// </summary>
public class QuantizedImage
{
/// <summary>
/// Gets the width of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="T:QuantizedImage"/>.
/// </summary>
public Bgra[] Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="T:QuantizedImage"/>.
/// </summary>
public byte[] Pixels { get; }
/// <summary>
/// Initializes a new instance of <see cref="T:QuantizedImage"/>.
/// </summary>
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;
}
/// <summary>
/// Converts this quantized image to a normal image.
/// </summary>
/// <returns></returns>
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;
}
}
}

40
src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs

@ -11,6 +11,7 @@
namespace ImageProcessor.Formats namespace ImageProcessor.Formats
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
/// <summary> /// <summary>
/// Encapsulates methods to calculate the color palette of an image. /// Encapsulates methods to calculate the color palette of an image.
@ -45,22 +46,25 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// A <see cref="T:byte[]"/> representing a quantized version of the image pixels. /// A <see cref="T:byte[]"/> representing a quantized version of the image pixels.
/// </returns> /// </returns>
public byte[] Quantize(ImageBase imageBase) public QuantizedImage Quantize(ImageBase imageBase)
{ {
// Get the size of the source image // Get the size of the source image
int height = imageBase.Height; int height = imageBase.Height;
int width = imageBase.Width; int width = imageBase.Width;
ImageBase copy = new ImageFrame((ImageFrame)imageBase);
// Call the FirstPass function if not a single pass algorithm. // Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through // For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette. // 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);
} }
/// <summary> /// <summary>
@ -92,18 +96,34 @@ namespace ImageProcessor.Formats
/// <param name="height">The height in pixels of the image</param> /// <param name="height">The height in pixels of the image</param>
protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height) 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 // Convert the first pixel, so that I have values going into the loop
byte pixelValue = this.QuantizePixel(sourcePixel); Bgra previousPixel = source[0, 0];
byte pixelValue = QuantizePixel(previousPixel);
output[0] = pixelValue; output[0] = pixelValue;
for (int y = 0; y < height; y++) 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;
}
}
} }
/// <summary> /// <summary>

1
src/ImageProcessor/ImageProcessor.csproj

@ -52,6 +52,7 @@
<Compile Include="Formats\Bmp\BmpEncoder.cs" /> <Compile Include="Formats\Bmp\BmpEncoder.cs" />
<Compile Include="Formats\Gif\GifDecoderCore.cs" /> <Compile Include="Formats\Gif\GifDecoderCore.cs" />
<Compile Include="Formats\Gif\GifEncoder.cs" /> <Compile Include="Formats\Gif\GifEncoder.cs" />
<Compile Include="Formats\Gif\Quantizer\QuantizedImage.cs" />
<Compile Include="Formats\Gif\Quantizer\IQuantizer.cs" /> <Compile Include="Formats\Gif\Quantizer\IQuantizer.cs" />
<Compile Include="Formats\Gif\Quantizer\OctreeQuantizer.cs" /> <Compile Include="Formats\Gif\Quantizer\OctreeQuantizer.cs" />
<Compile Include="Formats\Gif\Quantizer\Quantizer.cs" /> <Compile Include="Formats\Gif\Quantizer\Quantizer.cs" />

20
tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs

@ -57,5 +57,25 @@
Trace.WriteLine(string.Format("{0} : {1}ms", filename, watch.ElapsedMilliseconds)); 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);
}
}
} }
} }
Loading…
Cancel
Save