Browse Source

Merge remote-tracking branch 'origin/master' into drop-pointers

af/merge-core
Anton Firszov 9 years ago
parent
commit
05369202c6
  1. 2
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  2. 7
      src/ImageSharp.Drawing/Text/DrawText.cs
  3. 6
      src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
  4. 15
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
  5. 18
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  6. 49
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  7. 2
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  8. 1
      src/ImageSharp/Quantizers/Box.cs
  9. 72
      src/ImageSharp/Quantizers/OctreeQuantizer.cs
  10. 11
      src/ImageSharp/Quantizers/PaletteQuantizer.cs
  11. 5
      src/ImageSharp/Quantizers/Quantization.cs
  12. 17
      src/ImageSharp/Quantizers/Quantizer.cs
  13. 36
      src/ImageSharp/Quantizers/WuArrayPool.cs
  14. 441
      src/ImageSharp/Quantizers/WuQuantizer.cs
  15. 3
      tests/ImageSharp.Tests/FileTestBase.cs
  16. 44
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  17. 1
      tests/ImageSharp.Tests/TestImages.cs
  18. 3
      tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif

2
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -39,7 +39,7 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001"> <PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0002" /> <PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0005" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0010" /> <PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0010" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

7
src/ImageSharp.Drawing/Text/DrawText.cs

@ -177,13 +177,14 @@ namespace ImageSharp
dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution);
} }
FontSpan style = new FontSpan(font) FontSpan style = new FontSpan(font, dpi)
{ {
ApplyKerning = options.ApplyKerning, ApplyKerning = options.ApplyKerning,
TabWidth = options.TabWidth TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth
}; };
renderer.RenderText(text, style, dpi); renderer.RenderText(text, style);
System.Collections.Generic.IEnumerable<SixLabors.Shapes.IPath> shapesToDraw = glyphBuilder.Paths; System.Collections.Generic.IEnumerable<SixLabors.Shapes.IPath> shapesToDraw = glyphBuilder.Paths;

6
src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs

@ -41,6 +41,11 @@ namespace ImageSharp.Drawing
/// </summary> /// </summary>
public bool UseImageResolution; public bool UseImageResolution;
/// <summary>
/// If greater than zero determine the width at which text should wrap.
/// </summary>
public float WrapTextWidth;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct. /// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct.
/// </summary> /// </summary>
@ -52,6 +57,7 @@ namespace ImageSharp.Drawing
this.TabWidth = 4; this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16; this.AntialiasSubpixelDepth = 16;
this.UseImageResolution = false; this.UseImageResolution = false;
this.WrapTextWidth = 0;
} }
/// <summary> /// <summary>

15
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs

@ -71,8 +71,19 @@ namespace ImageSharp.Dithering
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height) public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
// Assign the transformed pixel to the array. this.Dither(pixels, source, transformed, x, y, width, height, true);
pixels[x, y] = transformed; }
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
where TColor : struct, IPixel<TColor>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
}
// Calculate the error // Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4(); Vector4 error = source.ToVector4() - transformed.ToVector4();

18
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -25,5 +25,23 @@ namespace ImageSharp.Dithering
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height) void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel<TColor>; where TColor : struct, IPixel<TColor>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="replacePixel">
/// Whether to replace the pixel at the given coordinates with the transformed value.
/// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
/// </param>
/// <typeparam name="TColor">The pixel format.</typeparam>
void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
where TColor : struct, IPixel<TColor>;
} }
} }

49
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -7,7 +7,6 @@ namespace ImageSharp.Formats
{ {
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -487,7 +486,7 @@ namespace ImageSharp.Formats
if (this.quantizer == null) if (this.quantizer == null)
{ {
this.quantizer = new OctreeQuantizer<TColor>(); this.quantizer = new WuQuantizer<TColor>();
} }
// Quantize the image returning a palette. This boxing is icky. // Quantize the image returning a palette. This boxing is icky.
@ -495,52 +494,54 @@ namespace ImageSharp.Formats
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
TColor[] palette = quantized.Palette; TColor[] palette = quantized.Palette;
int pixelCount = palette.Length; byte pixelCount = palette.Length.ToByte();
List<byte> transparentPixels = new List<byte>();
// Get max colors for bit depth. // Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength); byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength);
byte[] alphaTable = ArrayPool<byte>.Shared.Rent(pixelCount);
byte[] bytes = ArrayPool<byte>.Shared.Rent(4); byte[] bytes = ArrayPool<byte>.Shared.Rent(4);
bool anyAlpha = false;
try try
{ {
for (int i = 0; i < pixelCount; i++) for (byte i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; if (quantized.Pixels.Contains(i))
palette[i].ToXyzwBytes(bytes, 0); {
int offset = i * 3;
palette[i].ToXyzwBytes(bytes, 0);
int alpha = bytes[3]; byte alpha = bytes[3];
colorTable[offset] = bytes[0]; colorTable[offset] = bytes[0];
colorTable[offset + 1] = bytes[1]; colorTable[offset + 1] = bytes[1];
colorTable[offset + 2] = bytes[2]; colorTable[offset + 2] = bytes[2];
if (alpha < 255 && alpha <= this.options.Threshold) if (alpha > this.options.Threshold)
{
// Ensure the index is actually being used in our array.
// I'd like to find a faster way of doing this.
if (quantized.Pixels.Contains((byte)i))
{ {
transparentPixels.Add((byte)i); alpha = 255;
} }
anyAlpha = anyAlpha || alpha < 255;
alphaTable[i] = alpha;
} }
} }
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
// Write the transparency data
if (anyAlpha)
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount);
}
} }
finally finally
{ {
ArrayPool<byte>.Shared.Return(colorTable); ArrayPool<byte>.Shared.Return(colorTable);
ArrayPool<byte>.Shared.Return(alphaTable);
ArrayPool<byte>.Shared.Return(bytes); ArrayPool<byte>.Shared.Return(bytes);
} }
// Write the transparency data
if (transparentPixels.Any())
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
}
return quantized; return quantized;
} }

2
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -60,7 +60,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Gets or sets the transparency threshold. /// Gets or sets the transparency threshold.
/// </summary> /// </summary>
public byte Threshold { get; set; } = 0; public byte Threshold { get; set; } = 255;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance should write /// Gets or sets a value indicating whether this instance should write

1
src/ImageSharp/Quantizers/Wu/Box.cs → src/ImageSharp/Quantizers/Box.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers
{ {
/// <summary> /// <summary>
/// Represents a box color cube. /// Represents a box color cube.
/// TODO: This should be a struct for performance
/// </summary> /// </summary>
internal sealed class Box internal sealed class Box
{ {

72
src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs → src/ImageSharp/Quantizers/OctreeQuantizer.cs

@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers
public override QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors) public override QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors)
{ {
this.colors = maxColors.Clamp(1, 255); this.colors = maxColors.Clamp(1, 255);
this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
return base.Quantize(image, maxColors); return base.Quantize(image, this.colors);
} }
/// <summary> /// <inheritdoc/>
/// 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) 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 // Load up the values for the first pixel. We can use these to speed up the second
@ -107,7 +101,7 @@ namespace ImageSharp.Quantizers
if (this.Dither) if (this.Dither)
{ {
// Apply the dithering matrix. We have to reapply the value now as the original has changed. // 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); this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false);
} }
output[(y * source.Width) + x] = pixelValue; output[(y * source.Width) + x] = pixelValue;
@ -115,36 +109,17 @@ namespace ImageSharp.Quantizers
} }
} }
/// <summary> /// <inheritdoc/>
/// Process the pixel in the first pass of the algorithm
/// </summary>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks>
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
/// </remarks>
protected override void InitialQuantizePixel(TColor pixel) protected override void InitialQuantizePixel(TColor pixel)
{ {
// Add the color to the Octree // Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer); this.octree.AddColor(pixel, this.pixelBuffer);
} }
/// <summary> /// <inheritdoc/>
/// Retrieve the palette for the quantized image.
/// </summary>
/// <returns>
/// The new color palette
/// </returns>
protected override TColor[] GetPalette() protected override TColor[] GetPalette()
{ {
if (this.palette == null) return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1)));
{
this.palette = this.octree.Palletize(Math.Max(this.colors, 1));
}
return this.palette;
} }
/// <summary> /// <summary>
@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers
/// <returns> /// <returns>
/// The <see cref="int"/> /// The <see cref="int"/>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBitsNeededForColorDepth(int colorCount) private int GetBitsNeededForColorDepth(int colorCount)
{ {
return (int)Math.Ceiling(Math.Log(colorCount, 2)); return (int)Math.Ceiling(Math.Log(colorCount, 2));
@ -189,7 +165,7 @@ namespace ImageSharp.Quantizers
/// Mask used when getting the appropriate pixels for a given node /// Mask used when getting the appropriate pixels for a given node
/// </summary> /// </summary>
// ReSharper disable once StaticMemberInGenericType // ReSharper disable once StaticMemberInGenericType
private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary> /// <summary>
/// The root of the Octree /// The root of the Octree
@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers
/// </summary> /// </summary>
private int blue; private int blue;
/// <summary>
/// Alpha component
/// </summary>
private int alpha;
/// <summary> /// <summary>
/// The index of this node in the palette /// The index of this node in the palette
/// </summary> /// </summary>
@ -406,7 +377,7 @@ namespace ImageSharp.Quantizers
// Construct the new node // Construct the new node
this.leaf = level == colorBits; this.leaf = level == colorBits;
this.red = this.green = this.blue = this.alpha = 0; this.red = this.green = this.blue = 0;
this.pixelCount = 0; this.pixelCount = 0;
// If a leaf, increment the leaf count // If a leaf, increment the leaf count
@ -454,10 +425,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToXyzwBytes(buffer, 0);
int index = ((buffer[3] & Mask[0]) >> (shift - 3)) | int index = ((buffer[2] & Mask[level]) >> (shift - 2)) |
((buffer[2] & Mask[level + 1]) >> (shift - 2)) | ((buffer[1] & Mask[level]) >> (shift - 1)) |
((buffer[1] & Mask[level + 1]) >> (shift - 1)) | ((buffer[0] & Mask[level]) >> shift);
((buffer[0] & Mask[level + 1]) >> shift);
OctreeNode child = this.children[index]; OctreeNode child = this.children[index];
@ -479,7 +449,7 @@ namespace ImageSharp.Quantizers
/// <returns>The number of leaves removed</returns> /// <returns>The number of leaves removed</returns>
public int Reduce() public int Reduce()
{ {
this.red = this.green = this.blue = this.alpha = 0; this.red = this.green = this.blue = 0;
int childNodes = 0; int childNodes = 0;
// Loop through all children and add their information to this node // Loop through all children and add their information to this node
@ -490,7 +460,6 @@ namespace ImageSharp.Quantizers
this.red += this.children[index].red; this.red += this.children[index].red;
this.green += this.children[index].green; this.green += this.children[index].green;
this.blue += this.children[index].blue; this.blue += this.children[index].blue;
this.alpha += this.children[index].alpha;
this.pixelCount += this.children[index].pixelCount; this.pixelCount += this.children[index].pixelCount;
++childNodes; ++childNodes;
this.children[index] = null; this.children[index] = null;
@ -517,11 +486,10 @@ namespace ImageSharp.Quantizers
byte r = (this.red / this.pixelCount).ToByte(); byte r = (this.red / this.pixelCount).ToByte();
byte g = (this.green / this.pixelCount).ToByte(); byte g = (this.green / this.pixelCount).ToByte();
byte b = (this.blue / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte();
byte a = (this.alpha / this.pixelCount).ToByte();
// And set the color of the palette entry // And set the color of the palette entry
TColor pixel = default(TColor); TColor pixel = default(TColor);
pixel.PackFromBytes(r, g, b, a); pixel.PackFromBytes(r, g, b, 255);
palette[index] = pixel; palette[index] = pixel;
// Consume the next palette index // Consume the next palette index
@ -558,10 +526,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToXyzwBytes(buffer, 0);
int pixelIndex = ((buffer[3] & Mask[0]) >> (shift - 3)) | int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) |
((buffer[2] & Mask[level + 1]) >> (shift - 2)) | ((buffer[1] & Mask[level]) >> (shift - 1)) |
((buffer[1] & Mask[level + 1]) >> (shift - 1)) | ((buffer[0] & Mask[level]) >> shift);
((buffer[0] & Mask[level + 1]) >> shift);
if (this.children[pixelIndex] != null) if (this.children[pixelIndex] != null)
{ {
@ -588,9 +555,8 @@ namespace ImageSharp.Quantizers
this.red += buffer[0]; this.red += buffer[0];
this.green += buffer[1]; this.green += buffer[1];
this.blue += buffer[2]; this.blue += buffer[2];
this.alpha += buffer[3];
} }
} }
} }
} }
} }

11
src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs → src/ImageSharp/Quantizers/PaletteQuantizer.cs

@ -7,7 +7,6 @@ namespace ImageSharp.Quantizers
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
@ -71,13 +70,7 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors); return base.Quantize(image, maxColors);
} }
/// <summary> /// <inheritdoc/>
/// 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) 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 // Load up the values for the first pixel. We can use these to speed up the second
@ -115,7 +108,7 @@ namespace ImageSharp.Quantizers
if (this.Dither) if (this.Dither)
{ {
// Apply the dithering matrix. We have to reapply the value now as the original has changed. // 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); this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false);
} }
output[(y * source.Width) + x] = pixelValue; output[(y * source.Width) + x] = pixelValue;

5
src/ImageSharp/Quantizers/Options/Quantization.cs → src/ImageSharp/Quantizers/Quantization.cs

@ -12,17 +12,20 @@ namespace ImageSharp
{ {
/// <summary> /// <summary>
/// An adaptive Octree quantizer. Fast with good quality. /// An adaptive Octree quantizer. Fast with good quality.
/// The quantizer only supports a single alpha value.
/// </summary> /// </summary>
Octree, Octree,
/// <summary> /// <summary>
/// Xiaolin Wu's Color Quantizer which generates high quality output. /// Xiaolin Wu's Color Quantizer which generates high quality output.
/// The quantizer supports multiple alpha values.
/// </summary> /// </summary>
Wu, Wu,
/// <summary> /// <summary>
/// Palette based, Uses the collection of web-safe colors by default. /// Palette based, Uses the collection of web-safe colors by default.
/// The quantizer supports multiple alpha values.
/// </summary> /// </summary>
Palette Palette
} }
} }

17
src/ImageSharp/Quantizers/Octree/Quantizer.cs → src/ImageSharp/Quantizers/Quantizer.cs

@ -66,22 +66,9 @@ namespace ImageSharp.Quantizers
this.FirstPass(pixels, width, height); this.FirstPass(pixels, width, height);
} }
// Collect the palette. Octree requires this to be done before the second pass runs. // Collect the palette. Required before the second pass runs.
colorPalette = this.GetPalette(); colorPalette = this.GetPalette();
this.SecondPass(pixels, quantizedPixels, width, height);
if (this.Dither)
{
// We clone the image as we don't want to alter the original.
using (Image<TColor> clone = new Image<TColor>(image))
using (PixelAccessor<TColor> clonedPixels = clone.Lock())
{
this.SecondPass(clonedPixels, quantizedPixels, width, height);
}
}
else
{
this.SecondPass(pixels, quantizedPixels, width, height);
}
} }
return new QuantizedImage<TColor>(width, height, colorPalette, quantizedPixels); return new QuantizedImage<TColor>(width, height, colorPalette, quantizedPixels);

36
src/ImageSharp/Quantizers/WuArrayPool.cs

@ -0,0 +1,36 @@
// <copyright file="WuArrayPool.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Quantizers
{
using System.Buffers;
/// <summary>
/// Provides array pooling for the <see cref="WuQuantizer{TColor}"/>.
/// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces.
/// </summary>
internal static class WuArrayPool
{
/// <summary>
/// The long array pool.
/// </summary>
public static readonly ArrayPool<long> LongPool = ArrayPool<long>.Create(TableLength, 25);
/// <summary>
/// The float array pool.
/// </summary>
public static readonly ArrayPool<float> FloatPool = ArrayPool<float>.Create(TableLength, 5);
/// <summary>
/// The byte array pool.
/// </summary>
public static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(TableLength, 5);
/// <summary>
/// The table length. Matches the calculated value in <see cref="WuQuantizer{TColor}"/>
/// </summary>
private const int TableLength = 2471625;
}
}

441
src/ImageSharp/Quantizers/Wu/WuQuantizer.cs → src/ImageSharp/Quantizers/WuQuantizer.cs

@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers
{ {
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// An implementation of Wu's color quantizer with alpha channel. /// An implementation of Wu's color quantizer with alpha channel.
@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public sealed class WuQuantizer<TColor> : IQuantizer<TColor> public class WuQuantizer<TColor> : Quantizer<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
/// <summary> /// <summary>
@ -59,103 +60,238 @@ namespace ImageSharp.Quantizers
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary> /// <summary>
/// The long array pool. /// A buffer for storing pixels
/// </summary>
private static readonly ArrayPool<long> LongPool = ArrayPool<long>.Create(TableLength, 25);
/// <summary>
/// The double array pool.
/// </summary> /// </summary>
private static readonly ArrayPool<double> DoublePool = ArrayPool<double>.Create(TableLength, 5); private readonly byte[] rgbaBuffer = new byte[4];
/// <summary> /// <summary>
/// The byte array pool. /// A lookup table for colors
/// </summary> /// </summary>
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(TableLength, 5); private readonly Dictionary<TColor, byte> colorMap = new Dictionary<TColor, byte>();
/// <summary> /// <summary>
/// Moment of <c>P(c)</c>. /// Moment of <c>P(c)</c>.
/// </summary> /// </summary>
private readonly long[] vwt; private long[] vwt;
/// <summary> /// <summary>
/// Moment of <c>r*P(c)</c>. /// Moment of <c>r*P(c)</c>.
/// </summary> /// </summary>
private readonly long[] vmr; private long[] vmr;
/// <summary> /// <summary>
/// Moment of <c>g*P(c)</c>. /// Moment of <c>g*P(c)</c>.
/// </summary> /// </summary>
private readonly long[] vmg; private long[] vmg;
/// <summary> /// <summary>
/// Moment of <c>b*P(c)</c>. /// Moment of <c>b*P(c)</c>.
/// </summary> /// </summary>
private readonly long[] vmb; private long[] vmb;
/// <summary> /// <summary>
/// Moment of <c>a*P(c)</c>. /// Moment of <c>a*P(c)</c>.
/// </summary> /// </summary>
private readonly long[] vma; private long[] vma;
/// <summary> /// <summary>
/// Moment of <c>c^2*P(c)</c>. /// Moment of <c>c^2*P(c)</c>.
/// </summary> /// </summary>
private readonly double[] m2; private float[] m2;
/// <summary> /// <summary>
/// Color space tag. /// Color space tag.
/// </summary> /// </summary>
private readonly byte[] tag; private byte[] tag;
/// <summary> /// <summary>
/// A buffer for storing pixels /// Maximum allowed color depth
/// </summary> /// </summary>
private readonly byte[] rgbaBuffer = new byte[4]; private int colors;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor[] palette;
/// <summary>
/// The color cube representing the image palette
/// </summary>
private Box[] colorCube;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer{TColor}"/> class. /// Initializes a new instance of the <see cref="WuQuantizer{TColor}"/> class.
/// </summary> /// </summary>
/// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
public WuQuantizer() public WuQuantizer()
: base(false)
{ {
this.vwt = LongPool.Rent(TableLength);
this.vmr = LongPool.Rent(TableLength);
this.vmg = LongPool.Rent(TableLength);
this.vmb = LongPool.Rent(TableLength);
this.vma = LongPool.Rent(TableLength);
this.m2 = DoublePool.Rent(TableLength);
this.tag = BytePool.Rent(TableLength);
} }
/// <inheritdoc/> /// <inheritdoc/>
public QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors) public override QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors)
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
int colorCount = maxColors.Clamp(1, 256); this.colors = maxColors.Clamp(1, 256);
try
{
this.vwt = WuArrayPool.LongPool.Rent(TableLength);
this.vmr = WuArrayPool.LongPool.Rent(TableLength);
this.vmg = WuArrayPool.LongPool.Rent(TableLength);
this.vmb = WuArrayPool.LongPool.Rent(TableLength);
this.vma = WuArrayPool.LongPool.Rent(TableLength);
this.m2 = WuArrayPool.FloatPool.Rent(TableLength);
this.tag = WuArrayPool.BytePool.Rent(TableLength);
return base.Quantize(image, this.colors);
}
finally
{
WuArrayPool.LongPool.Return(this.vwt, true);
WuArrayPool.LongPool.Return(this.vmr, true);
WuArrayPool.LongPool.Return(this.vmg, true);
WuArrayPool.LongPool.Return(this.vmb, true);
WuArrayPool.LongPool.Return(this.vma, true);
WuArrayPool.FloatPool.Return(this.m2, true);
WuArrayPool.BytePool.Return(this.tag, true);
}
}
/// <inheritdoc/>
protected override TColor[] GetPalette()
{
if (this.palette == null)
{
this.palette = new TColor[this.colors];
for (int k = 0; k < this.colors; k++)
{
this.Mark(this.colorCube[k], (byte)k);
float weight = Volume(this.colorCube[k], this.vwt);
if (MathF.Abs(weight) > Constants.Epsilon)
{
float r = Volume(this.colorCube[k], this.vmr) / weight;
float g = Volume(this.colorCube[k], this.vmg) / weight;
float b = Volume(this.colorCube[k], this.vmb) / weight;
float a = Volume(this.colorCube[k], this.vma) / weight;
TColor color = default(TColor);
color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
this.palette[k] = color;
}
}
}
return this.palette;
}
/// <inheritdoc/>
protected override void InitialQuantizePixel(TColor pixel)
{
// Add the color to a 3-D color histogram.
// Colors are expected in r->g->b->a format
pixel.ToXyzwBytes(this.rgbaBuffer, 0);
byte r = this.rgbaBuffer[0];
byte g = this.rgbaBuffer[1];
byte b = this.rgbaBuffer[2];
byte a = this.rgbaBuffer[3];
int inr = r >> (8 - IndexBits);
int ing = g >> (8 - IndexBits);
int inb = b >> (8 - IndexBits);
int ina = a >> (8 - IndexAlphaBits);
int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1);
this.vwt[ind]++;
this.vmr[ind] += r;
this.vmg[ind] += g;
this.vmb[ind] += b;
this.vma[ind] += a;
this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
}
/// <inheritdoc/>
protected override void FirstPass(PixelAccessor<TColor> source, int width, int height)
{
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
// And loop through each column
for (int x = 0; x < width; x++)
{
// Now I have the pixel, call the FirstPassQuantize function...
this.InitialQuantizePixel(source[x, y]);
}
}
this.Clear(); this.Get3DMoments();
this.BuildCube();
}
using (PixelAccessor<TColor> imagePixels = image.Lock()) /// <inheritdoc/>
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++)
{ {
this.Build3DHistogram(imagePixels); // And loop through each column
this.Get3DMoments(); 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];
}
}
Box[] cube; if (this.Dither)
this.BuildCube(out cube, ref colorCount); {
// 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, false);
}
return this.GenerateResult(imagePixels, colorCount, cube); output[(y * source.Width) + x] = pixelValue;
}
} }
} }
/// <summary> /// <summary>
/// Gets an index. /// Gets the index index of the given color in the palette.
/// </summary> /// </summary>
/// <param name="r">The red value.</param> /// <param name="r">The red value.</param>
/// <param name="g">The green value.</param> /// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param> /// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param> /// <param name="a">The alpha value.</param>
/// <returns>The index.</returns> /// <returns>The index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPaletteIndex(int r, int g, int b, int a) private static int GetPaletteIndex(int r, int g, int b, int a)
{ {
return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1)) return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1))
@ -169,7 +305,7 @@ namespace ImageSharp.Quantizers
/// <param name="cube">The cube.</param> /// <param name="cube">The cube.</param>
/// <param name="moment">The moment.</param> /// <param name="moment">The moment.</param>
/// <returns>The result.</returns> /// <returns>The result.</returns>
private static double Volume(Box cube, long[] moment) private static float Volume(Box cube, long[] moment)
{ {
return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
@ -310,55 +446,6 @@ namespace ImageSharp.Quantizers
} }
} }
/// <summary>
/// Clears the tables.
/// </summary>
private void Clear()
{
Array.Clear(this.vwt, 0, TableLength);
Array.Clear(this.vmr, 0, TableLength);
Array.Clear(this.vmg, 0, TableLength);
Array.Clear(this.vmb, 0, TableLength);
Array.Clear(this.vma, 0, TableLength);
Array.Clear(this.m2, 0, TableLength);
Array.Clear(this.tag, 0, TableLength);
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="pixels">The pixel accessor.</param>
private void Build3DHistogram(PixelAccessor<TColor> pixels)
{
for (int y = 0; y < pixels.Height; y++)
{
for (int x = 0; x < pixels.Width; x++)
{
// Colors are expected in r->g->b->a format
pixels[x, y].ToXyzwBytes(this.rgbaBuffer, 0);
byte r = this.rgbaBuffer[0];
byte g = this.rgbaBuffer[1];
byte b = this.rgbaBuffer[2];
byte a = this.rgbaBuffer[3];
int inr = r >> (8 - IndexBits);
int ing = g >> (8 - IndexBits);
int inb = b >> (8 - IndexBits);
int ina = a >> (8 - IndexAlphaBits);
int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1);
this.vwt[ind]++;
this.vmr[ind] += r;
this.vmg[ind] += g;
this.vmb[ind] += b;
this.vma[ind] += a;
this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
}
}
}
/// <summary> /// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary> /// </summary>
@ -369,14 +456,14 @@ namespace ImageSharp.Quantizers
long[] volumeG = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount); long[] volumeG = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeB = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount); long[] volumeB = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeA = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount); long[] volumeA = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
double[] volume2 = ArrayPool<double>.Shared.Rent(IndexCount * IndexAlphaCount); float[] volume2 = ArrayPool<float>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] area = ArrayPool<long>.Shared.Rent(IndexAlphaCount); long[] area = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaR = ArrayPool<long>.Shared.Rent(IndexAlphaCount); long[] areaR = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaG = ArrayPool<long>.Shared.Rent(IndexAlphaCount); long[] areaG = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaB = ArrayPool<long>.Shared.Rent(IndexAlphaCount); long[] areaB = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaA = ArrayPool<long>.Shared.Rent(IndexAlphaCount); long[] areaA = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
double[] area2 = ArrayPool<double>.Shared.Rent(IndexAlphaCount); float[] area2 = ArrayPool<float>.Shared.Rent(IndexAlphaCount);
try try
{ {
@ -405,7 +492,7 @@ namespace ImageSharp.Quantizers
long lineG = 0; long lineG = 0;
long lineB = 0; long lineB = 0;
long lineA = 0; long lineA = 0;
double line2 = 0; float line2 = 0;
for (int a = 1; a < IndexAlphaCount; a++) for (int a = 1; a < IndexAlphaCount; a++)
{ {
@ -454,14 +541,14 @@ namespace ImageSharp.Quantizers
ArrayPool<long>.Shared.Return(volumeG); ArrayPool<long>.Shared.Return(volumeG);
ArrayPool<long>.Shared.Return(volumeB); ArrayPool<long>.Shared.Return(volumeB);
ArrayPool<long>.Shared.Return(volumeA); ArrayPool<long>.Shared.Return(volumeA);
ArrayPool<double>.Shared.Return(volume2); ArrayPool<float>.Shared.Return(volume2);
ArrayPool<long>.Shared.Return(area); ArrayPool<long>.Shared.Return(area);
ArrayPool<long>.Shared.Return(areaR); ArrayPool<long>.Shared.Return(areaR);
ArrayPool<long>.Shared.Return(areaG); ArrayPool<long>.Shared.Return(areaG);
ArrayPool<long>.Shared.Return(areaB); ArrayPool<long>.Shared.Return(areaB);
ArrayPool<long>.Shared.Return(areaA); ArrayPool<long>.Shared.Return(areaA);
ArrayPool<double>.Shared.Return(area2); ArrayPool<float>.Shared.Return(area2);
} }
} }
@ -469,15 +556,15 @@ namespace ImageSharp.Quantizers
/// Computes the weighted variance of a box cube. /// Computes the weighted variance of a box cube.
/// </summary> /// </summary>
/// <param name="cube">The cube.</param> /// <param name="cube">The cube.</param>
/// <returns>The <see cref="double"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private double Variance(Box cube) private float Variance(Box cube)
{ {
double dr = Volume(cube, this.vmr); float dr = Volume(cube, this.vmr);
double dg = Volume(cube, this.vmg); float dg = Volume(cube, this.vmg);
double db = Volume(cube, this.vmb); float db = Volume(cube, this.vmb);
double da = Volume(cube, this.vma); float da = Volume(cube, this.vma);
double xx = float xx =
this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
@ -515,8 +602,8 @@ namespace ImageSharp.Quantizers
/// <param name="wholeB">The whole blue.</param> /// <param name="wholeB">The whole blue.</param>
/// <param name="wholeA">The whole alpha.</param> /// <param name="wholeA">The whole alpha.</param>
/// <param name="wholeW">The whole weight.</param> /// <param name="wholeW">The whole weight.</param>
/// <returns>The <see cref="double"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) private float Maximize(Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
{ {
long baseR = Bottom(cube, direction, this.vmr); long baseR = Bottom(cube, direction, this.vmr);
long baseG = Bottom(cube, direction, this.vmg); long baseG = Bottom(cube, direction, this.vmg);
@ -524,20 +611,20 @@ namespace ImageSharp.Quantizers
long baseA = Bottom(cube, direction, this.vma); long baseA = Bottom(cube, direction, this.vma);
long baseW = Bottom(cube, direction, this.vwt); long baseW = Bottom(cube, direction, this.vwt);
double max = 0.0; float max = 0F;
cut = -1; cut = -1;
for (int i = first; i < last; i++) for (int i = first; i < last; i++)
{ {
double halfR = baseR + Top(cube, direction, i, this.vmr); float halfR = baseR + Top(cube, direction, i, this.vmr);
double halfG = baseG + Top(cube, direction, i, this.vmg); float halfG = baseG + Top(cube, direction, i, this.vmg);
double halfB = baseB + Top(cube, direction, i, this.vmb); float halfB = baseB + Top(cube, direction, i, this.vmb);
double halfA = baseA + Top(cube, direction, i, this.vma); float halfA = baseA + Top(cube, direction, i, this.vma);
double halfW = baseW + Top(cube, direction, i, this.vwt); float halfW = baseW + Top(cube, direction, i, this.vwt);
double temp; float temp;
if (Math.Abs(halfW) < Constants.Epsilon) if (MathF.Abs(halfW) < Constants.Epsilon)
{ {
continue; continue;
} }
@ -550,7 +637,7 @@ namespace ImageSharp.Quantizers
halfA = wholeA - halfA; halfA = wholeA - halfA;
halfW = wholeW - halfW; halfW = wholeW - halfW;
if (Math.Abs(halfW) < Constants.Epsilon) if (MathF.Abs(halfW) < Constants.Epsilon)
{ {
continue; continue;
} }
@ -575,21 +662,16 @@ namespace ImageSharp.Quantizers
/// <returns>Returns a value indicating whether the box has been split.</returns> /// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut(Box set1, Box set2) private bool Cut(Box set1, Box set2)
{ {
double wholeR = Volume(set1, this.vmr); float wholeR = Volume(set1, this.vmr);
double wholeG = Volume(set1, this.vmg); float wholeG = Volume(set1, this.vmg);
double wholeB = Volume(set1, this.vmb); float wholeB = Volume(set1, this.vmb);
double wholeA = Volume(set1, this.vma); float wholeA = Volume(set1, this.vma);
double wholeW = Volume(set1, this.vwt); float wholeW = Volume(set1, this.vwt);
int cutr; float maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
int cutg; float maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
int cutb; float maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
int cuta; float maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
int dir; int dir;
@ -686,40 +768,38 @@ namespace ImageSharp.Quantizers
/// <summary> /// <summary>
/// Builds the cube. /// Builds the cube.
/// </summary> /// </summary>
/// <param name="cube">The cube.</param> private void BuildCube()
/// <param name="colorCount">The color count.</param>
private void BuildCube(out Box[] cube, ref int colorCount)
{ {
cube = new Box[colorCount]; this.colorCube = new Box[this.colors];
double[] vv = new double[colorCount]; float[] vv = new float[this.colors];
for (int i = 0; i < colorCount; i++) for (int i = 0; i < this.colors; i++)
{ {
cube[i] = new Box(); this.colorCube[i] = new Box();
} }
cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; this.colorCube[0].R0 = this.colorCube[0].G0 = this.colorCube[0].B0 = this.colorCube[0].A0 = 0;
cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; this.colorCube[0].R1 = this.colorCube[0].G1 = this.colorCube[0].B1 = IndexCount - 1;
cube[0].A1 = IndexAlphaCount - 1; this.colorCube[0].A1 = IndexAlphaCount - 1;
int next = 0; int next = 0;
for (int i = 1; i < colorCount; i++) for (int i = 1; i < this.colors; i++)
{ {
if (this.Cut(cube[next], cube[i])) if (this.Cut(this.colorCube[next], this.colorCube[i]))
{ {
vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(this.colorCube[next]) : 0F;
vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(this.colorCube[i]) : 0F;
} }
else else
{ {
vv[next] = 0.0; vv[next] = 0F;
i--; i--;
} }
next = 0; next = 0;
double temp = vv[0]; float temp = vv[0];
for (int k = 1; k <= i; k++) for (int k = 1; k <= i; k++)
{ {
if (vv[k] > temp) if (vv[k] > temp)
@ -731,79 +811,38 @@ namespace ImageSharp.Quantizers
if (temp <= 0.0) if (temp <= 0.0)
{ {
colorCount = i + 1; this.colors = i + 1;
break; break;
} }
} }
} }
/// <summary> /// <summary>
/// Generates the quantized result. /// Process the pixel in the second pass of the algorithm
/// </summary> /// </summary>
/// <param name="imagePixels">The image pixels.</param> /// <param name="pixel">The pixel to quantize</param>
/// <param name="colorCount">The color count.</param> /// <returns>
/// <param name="cube">The cube.</param> /// The quantized value
/// <returns>The result.</returns> /// </returns>
private QuantizedImage<TColor> GenerateResult(PixelAccessor<TColor> imagePixels, int colorCount, Box[] cube) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TColor pixel)
{ {
TColor[] pallette = new TColor[colorCount]; if (this.Dither)
byte[] pixels = new byte[imagePixels.Width * imagePixels.Height];
int width = imagePixels.Width;
int height = imagePixels.Height;
for (int k = 0; k < colorCount; k++)
{ {
this.Mark(cube[k], (byte)k); // The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here.
double weight = Volume(cube[k], this.vwt); return this.GetClosestColor(pixel, this.palette, this.colorMap);
if (Math.Abs(weight) > Constants.Epsilon)
{
float r = (float)(Volume(cube[k], this.vmr) / weight);
float g = (float)(Volume(cube[k], this.vmg) / weight);
float b = (float)(Volume(cube[k], this.vmb) / weight);
float a = (float)(Volume(cube[k], this.vma) / weight);
TColor color = default(TColor);
color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
pallette[k] = color;
}
} }
Parallel.For( // Expected order r->g->b->a
0, pixel.ToXyzwBytes(this.rgbaBuffer, 0);
height,
imagePixels.ParallelOptions,
y =>
{
byte[] rgba = ArrayPool<byte>.Shared.Rent(4);
for (int x = 0; x < width; x++)
{
// Expected order r->g->b->a
imagePixels[x, y].ToXyzwBytes(rgba, 0);
int r = rgba[0] >> (8 - IndexBits);
int g = rgba[1] >> (8 - IndexBits);
int b = rgba[2] >> (8 - IndexBits);
int a = rgba[3] >> (8 - IndexAlphaBits);
int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
pixels[(y * width) + x] = this.tag[ind];
}
ArrayPool<byte>.Shared.Return(rgba);
});
// Cleanup int r = this.rgbaBuffer[0] >> (8 - IndexBits);
LongPool.Return(this.vwt); int g = this.rgbaBuffer[1] >> (8 - IndexBits);
LongPool.Return(this.vmr); int b = this.rgbaBuffer[2] >> (8 - IndexBits);
LongPool.Return(this.vmg); int a = this.rgbaBuffer[3] >> (8 - IndexAlphaBits);
LongPool.Return(this.vmb);
LongPool.Return(this.vma);
DoublePool.Return(this.m2);
BytePool.Return(this.tag);
return new QuantizedImage<TColor>(width, height, pallette, pixels); return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
} }
} }
} }

3
tests/ImageSharp.Tests/FileTestBase.cs

@ -28,7 +28,7 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only
TestFile.Create(TestImages.Bmp.Car), TestFile.Create(TestImages.Bmp.Car),
// TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash), TestFile.Create(TestImages.Png.Splash),
// TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only
@ -46,6 +46,7 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only
TestFile.Create(TestImages.Gif.Rings), TestFile.Create(TestImages.Gif.Rings),
// TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only
}; };

44
tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Formats.Png
using ImageSharp.Formats; using ImageSharp.Formats;
using System.Linq; using System.Linq;
using ImageSharp.IO; using ImageSharp.IO;
using System.Numerics;
public class PngSmokeTests public class PngSmokeTests
{ {
@ -58,6 +59,49 @@ namespace ImageSharp.Tests.Formats.Png
} }
} }
// JJS: Commented out for now since the test does not take into lossy nature of indexing.
//[Theory]
//[WithTestPatternImages(100, 100, PixelTypes.Color)]
//public void CanSaveIndexedPngTwice<TColor>(TestImageProvider<TColor> provider)
// where TColor : struct, IPixel<TColor>
//{
// // does saving a file then repoening mean both files are identical???
// using (Image<TColor> source = provider.GetImage())
// using (MemoryStream ms = new MemoryStream())
// {
// source.MetaData.Quality = 256;
// source.Save(ms, new PngEncoder(), new PngEncoderOptions {
// Threshold = 200
// });
// ms.Position = 0;
// using (Image img1 = Image.Load(ms, new PngDecoder()))
// {
// using (MemoryStream ms2 = new MemoryStream())
// {
// img1.Save(ms2, new PngEncoder(), new PngEncoderOptions
// {
// Threshold = 200
// });
// ms2.Position = 0;
// using (Image img2 = Image.Load(ms2, new PngDecoder()))
// {
// using (PixelAccessor<Color> pixels1 = img1.Lock())
// using (PixelAccessor<Color> pixels2 = img2.Lock())
// {
// for (int y = 0; y < img1.Height; y++)
// {
// for (int x = 0; x < img1.Width; x++)
// {
// Assert.Equal(pixels1[x, y], pixels2[x, y]);
// }
// }
// }
// }
// }
// }
// }
//}
[Theory] [Theory]
[WithTestPatternImages(300, 300, PixelTypes.All)] [WithTestPatternImages(300, 300, PixelTypes.All)]
public void Resize<TColor>(TestImageProvider<TColor> provider) public void Resize<TColor>(TestImageProvider<TColor> provider)

1
tests/ImageSharp.Tests/TestImages.cs

@ -103,6 +103,7 @@ namespace ImageSharp.Tests
public const string Rings = "Gif/rings.gif"; public const string Rings = "Gif/rings.gif";
public const string Giphy = "Gif/giphy.gif"; public const string Giphy = "Gif/giphy.gif";
public const string Cheers = "Gif/cheers.gif"; public const string Cheers = "Gif/cheers.gif";
public const string Trans = "Gif/trans.gif";
} }
} }
} }

3
tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99b20b417a63c30f62fa69a00fa0a76c2cb9848988e5def1b886460a129197f7
size 13707
Loading…
Cancel
Save