Browse Source

Fix quantization transparent pixel allocation

af/merge-core
James Jackson-South 9 years ago
parent
commit
e14bf0889f
  1. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 122
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  3. 2
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  4. 95
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

10
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -154,18 +154,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
// Transparent pixels are much more likely to be found at the end of a palette // Transparent pixels are much more likely to be found at the end of a palette
int index = -1; int index = -1;
var trans = default(Rgba32);
for (int i = quantized.Palette.Length - 1; i >= 0; i--) for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{ {
quantized.Palette[i].ToXyzwBytes(this.buffer, 0); quantized.Palette[i].ToRgba32(ref trans);
if (this.buffer[3] > 0) if (trans.Equals(default(Rgba32)))
{
continue;
}
else
{ {
index = i; index = i;
break;
} }
} }

122
src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary> /// </summary>
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>(); private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>();
/// <summary>
/// The pixel buffer, used to reduce allocations.
/// </summary>
private readonly byte[] pixelBuffer = new byte[4];
/// <summary> /// <summary>
/// Stores the tree /// Stores the tree
/// </summary> /// </summary>
@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary> /// </summary>
private TPixel[] palette; private TPixel[] palette;
/// <summary>
/// The transparent index
/// </summary>
private byte transparentIndex;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
@ -73,7 +73,8 @@ namespace SixLabors.ImageSharp.Quantizers
// pass of the algorithm by avoiding transforming rows of identical color. // pass of the algorithm by avoiding transforming rows of identical color.
TPixel sourcePixel = source[0, 0]; TPixel sourcePixel = source[0, 0];
TPixel previousPixel = sourcePixel; TPixel previousPixel = sourcePixel;
byte pixelValue = this.QuantizePixel(sourcePixel); var rgba = default(Rgba32);
byte pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
TPixel[] colorPalette = this.GetPalette(); TPixel[] colorPalette = this.GetPalette();
TPixel transformedPixel = colorPalette[pixelValue]; TPixel transformedPixel = colorPalette[pixelValue];
@ -92,7 +93,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (!previousPixel.Equals(sourcePixel)) if (!previousPixel.Equals(sourcePixel))
{ {
// Quantize the pixel // Quantize the pixel
pixelValue = this.QuantizePixel(sourcePixel); pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
// And setup the previous pointer // And setup the previous pointer
previousPixel = sourcePixel; previousPixel = sourcePixel;
@ -118,24 +119,57 @@ namespace SixLabors.ImageSharp.Quantizers
protected override void InitialQuantizePixel(TPixel pixel) protected override void InitialQuantizePixel(TPixel pixel)
{ {
// Add the color to the Octree // Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer); var rgba = default(Rgba32);
this.octree.AddColor(pixel, ref rgba);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override TPixel[] GetPalette() protected override TPixel[] GetPalette()
{ {
return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); if (this.palette == null)
{
this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1));
this.transparentIndex = this.GetTransparentIndex();
}
return this.palette;
}
/// <summary>
/// Returns the index of the first instance of the transparent color in the palette.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte GetTransparentIndex()
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = this.colors;
var trans = default(Rgba32);
for (int i = this.palette.Length - 1; i >= 0; i--)
{
this.palette[i].ToRgba32(ref trans);
if (trans.Equals(default(Rgba32)))
{
index = i;
}
}
return (byte)index;
} }
/// <summary> /// <summary>
/// Process the pixel in the second pass of the algorithm /// Process the pixel in the second pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">The pixel to quantize</param>
/// <param name="rgba">The color to compare against</param>
/// <returns> /// <returns>
/// The quantized value /// The quantized value
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TPixel pixel) private byte QuantizePixel(TPixel pixel, ref Rgba32 rgba)
{ {
if (this.Dither) if (this.Dither)
{ {
@ -144,13 +178,13 @@ namespace SixLabors.ImageSharp.Quantizers
return this.GetClosestPixel(pixel, this.palette, this.colorMap); return this.GetClosestPixel(pixel, this.palette, this.colorMap);
} }
pixel.ToXyzwBytes(this.pixelBuffer, 0); pixel.ToRgba32(ref rgba);
if (this.pixelBuffer[3] == 0) if (rgba.Equals(default(Rgba32)))
{ {
return this.colors; return this.transparentIndex;
} }
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); return (byte)this.octree.GetPaletteIndex(pixel, ref rgba);
} }
/// <summary> /// <summary>
@ -233,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers
/// Add a given color value to the Octree /// Add a given color value to the Octree
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color.</param>
public void AddColor(TPixel pixel, byte[] buffer) public void AddColor(TPixel pixel, ref Rgba32 rgba)
{ {
// Check if this request is for the same color as the last // Check if this request is for the same color as the last
if (this.previousColor.Equals(pixel)) if (this.previousColor.Equals(pixel))
@ -244,18 +278,18 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.previousNode == null) if (this.previousNode == null)
{ {
this.previousColor = pixel; this.previousColor = pixel;
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba);
} }
else else
{ {
// Just update the previous node // Just update the previous node
this.previousNode.Increment(pixel, buffer); this.previousNode.Increment(pixel, ref rgba);
} }
} }
else else
{ {
this.previousColor = pixel; this.previousColor = pixel;
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba);
} }
} }
@ -287,13 +321,13 @@ namespace SixLabors.ImageSharp.Quantizers
/// Get the palette index for the passed color /// Get the palette index for the passed color
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
public int GetPaletteIndex(TPixel pixel, byte[] buffer) public int GetPaletteIndex(TPixel pixel, ref Rgba32 rgba)
{ {
return this.root.GetPaletteIndex(pixel, 0, buffer); return this.root.GetPaletteIndex(pixel, 0, ref rgba);
} }
/// <summary> /// <summary>
@ -415,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers
/// <summary> /// <summary>
/// Add a color into the tree /// Add a color into the tree
/// </summary> /// </summary>
/// <param name="pixel">The color</param> /// <param name="pixel">The pixel color</param>
/// <param name="colorBits">The number of significant color bits</param> /// <param name="colorBits">The number of significant color bits</param>
/// <param name="level">The level in the tree</param> /// <param name="level">The level in the tree</param>
/// <param name="octree">The tree to which this node belongs</param> /// <param name="octree">The tree to which this node belongs</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer) public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, ref Rgba32 rgba)
{ {
// Update the color information if this is a leaf // Update the color information if this is a leaf
if (this.leaf) if (this.leaf)
{ {
this.Increment(pixel, buffer); this.Increment(pixel, ref rgba);
// Setup the previous node // Setup the previous node
octree.TrackPrevious(this); octree.TrackPrevious(this);
@ -434,11 +468,11 @@ namespace SixLabors.ImageSharp.Quantizers
{ {
// Go to the next level down in the tree // Go to the next level down in the tree
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | int index = ((rgba.B & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) | ((rgba.G & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift); ((rgba.R & Mask[level]) >> shift);
OctreeNode child = this.children[index]; OctreeNode child = this.children[index];
@ -450,7 +484,7 @@ namespace SixLabors.ImageSharp.Quantizers
} }
// Add the color to the child node // Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree, buffer); child.AddColor(pixel, colorBits, level + 1, octree, ref rgba);
} }
} }
@ -524,26 +558,26 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="level">The level.</param> /// <param name="level">The level.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
/// <returns> /// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette. /// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns> /// </returns>
public int GetPaletteIndex(TPixel pixel, int level, byte[] buffer) public int GetPaletteIndex(TPixel pixel, int level, ref Rgba32 rgba)
{ {
int index = this.paletteIndex; int index = this.paletteIndex;
if (!this.leaf) if (!this.leaf)
{ {
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) | ((rgba.G & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift); ((rgba.R & Mask[level]) >> shift);
if (this.children[pixelIndex] != null) if (this.children[pixelIndex] != null)
{ {
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, buffer); index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, ref rgba);
} }
else else
{ {
@ -558,14 +592,14 @@ namespace SixLabors.ImageSharp.Quantizers
/// Increment the pixel count and add to the color information /// Increment the pixel count and add to the color information
/// </summary> /// </summary>
/// <param name="pixel">The pixel to add.</param> /// <param name="pixel">The pixel to add.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
public void Increment(TPixel pixel, byte[] buffer) public void Increment(TPixel pixel, ref Rgba32 rgba)
{ {
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
this.pixelCount++; this.pixelCount++;
this.red += buffer[0]; this.red += rgba.R;
this.green += buffer[1]; this.green += rgba.G;
this.blue += buffer[2]; this.blue += rgba.B;
} }
} }
} }

2
src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base
} }
/// <inheritdoc /> /// <inheritdoc />
public bool Dither { get; set; } = true; public bool Dither { get; set; } = false;
/// <inheritdoc /> /// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();

95
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -0,0 +1,95 @@
namespace SixLabors.ImageSharp.Tests
{
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Quantizers;
using Xunit;
public class QuantizedImageTests
{
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void PaletteQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new PaletteQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void OctreeQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new OctreeQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void WuQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new WuQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
var trans = default(Rgba32);
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
quantized.Palette[i].ToRgba32(ref trans);
if (trans.Equals(default(Rgba32)))
{
index = i;
}
}
return index;
}
}
}
Loading…
Cancel
Save