diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
index 259ae4b0f..8ba0cbdd0 100644
--- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
+++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
@@ -39,7 +39,7 @@
All
-
+
diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs
index 1f5f4cdb1..eba11b5c5 100644
--- a/src/ImageSharp.Drawing/Text/DrawText.cs
+++ b/src/ImageSharp.Drawing/Text/DrawText.cs
@@ -177,13 +177,14 @@ namespace ImageSharp
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,
- TabWidth = options.TabWidth
+ TabWidth = options.TabWidth,
+ WrappingWidth = options.WrapTextWidth
};
- renderer.RenderText(text, style, dpi);
+ renderer.RenderText(text, style);
System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths;
diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
index b58a40b34..6b09f2395 100644
--- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
+++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
@@ -41,6 +41,11 @@ namespace ImageSharp.Drawing
///
public bool UseImageResolution;
+ ///
+ /// If greater than zero determine the width at which text should wrap.
+ ///
+ public float WrapTextWidth;
+
///
/// Initializes a new instance of the struct.
///
@@ -52,6 +57,7 @@ namespace ImageSharp.Drawing
this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16;
this.UseImageResolution = false;
+ this.WrapTextWidth = 0;
}
///
diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
index cde146f1e..d6ab8eb64 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
@@ -71,8 +71,19 @@ namespace ImageSharp.Dithering
public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel
{
- // Assign the transformed pixel to the array.
- pixels[x, y] = transformed;
+ this.Dither(pixels, source, transformed, x, y, width, height, true);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
+ where TColor : struct, IPixel
+ {
+ if (replacePixel)
+ {
+ // Assign the transformed pixel to the array.
+ pixels[x, y] = transformed;
+ }
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
index 18079b1fb..66ec3d515 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
@@ -25,5 +25,23 @@ namespace ImageSharp.Dithering
/// The pixel format.
void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel;
+
+ ///
+ /// Transforms the image applying the dither matrix. This method alters the input pixels array
+ ///
+ /// The pixel accessor
+ /// The source pixel
+ /// The transformed pixel
+ /// The column index.
+ /// The row index.
+ /// The image width.
+ /// The image height.
+ ///
+ /// 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.
+ ///
+ /// The pixel format.
+ void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
+ where TColor : struct, IPixel;
}
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 552673179..c11fc94df 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Formats
{
using System;
using System.Buffers;
- using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -487,7 +486,7 @@ namespace ImageSharp.Formats
if (this.quantizer == null)
{
- this.quantizer = new OctreeQuantizer();
+ this.quantizer = new WuQuantizer();
}
// 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.
TColor[] palette = quantized.Palette;
- int pixelCount = palette.Length;
- List transparentPixels = new List();
+ byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength);
+ byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount);
byte[] bytes = ArrayPool.Shared.Rent(4);
-
+ bool anyAlpha = false;
try
{
- for (int i = 0; i < pixelCount; i++)
+ for (byte i = 0; i < pixelCount; i++)
{
- int offset = i * 3;
- palette[i].ToXyzwBytes(bytes, 0);
+ if (quantized.Pixels.Contains(i))
+ {
+ int offset = i * 3;
+ palette[i].ToXyzwBytes(bytes, 0);
- int alpha = bytes[3];
+ byte alpha = bytes[3];
- colorTable[offset] = bytes[0];
- colorTable[offset + 1] = bytes[1];
- colorTable[offset + 2] = bytes[2];
+ colorTable[offset] = bytes[0];
+ colorTable[offset + 1] = bytes[1];
+ colorTable[offset + 2] = bytes[2];
- if (alpha < 255 && 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))
+ if (alpha > this.options.Threshold)
{
- transparentPixels.Add((byte)i);
+ alpha = 255;
}
+
+ anyAlpha = anyAlpha || alpha < 255;
+ alphaTable[i] = alpha;
}
}
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
+
+ // Write the transparency data
+ if (anyAlpha)
+ {
+ this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount);
+ }
}
finally
{
ArrayPool.Shared.Return(colorTable);
+ ArrayPool.Shared.Return(alphaTable);
ArrayPool.Shared.Return(bytes);
}
- // Write the transparency data
- if (transparentPixels.Any())
- {
- this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
- }
-
return quantized;
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
index 2891f1974..90175c6d6 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
@@ -60,7 +60,7 @@ namespace ImageSharp.Formats
///
/// Gets or sets the transparency threshold.
///
- public byte Threshold { get; set; } = 0;
+ public byte Threshold { get; set; } = 255;
///
/// Gets or sets a value indicating whether this instance should write
diff --git a/src/ImageSharp/Quantizers/Wu/Box.cs b/src/ImageSharp/Quantizers/Box.cs
similarity index 96%
rename from src/ImageSharp/Quantizers/Wu/Box.cs
rename to src/ImageSharp/Quantizers/Box.cs
index e715c7839..7f2a32087 100644
--- a/src/ImageSharp/Quantizers/Wu/Box.cs
+++ b/src/ImageSharp/Quantizers/Box.cs
@@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers
{
///
/// Represents a box color cube.
+ /// TODO: This should be a struct for performance
///
internal sealed class Box
{
diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/OctreeQuantizer.cs
similarity index 89%
rename from src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
rename to src/ImageSharp/Quantizers/OctreeQuantizer.cs
index 2590f297e..df52ee7f9 100644
--- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageSharp/Quantizers/OctreeQuantizer.cs
@@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers
public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
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);
}
- ///
- /// Execute a second pass through the bitmap
- ///
- /// The source image.
- /// The output pixel array
- /// The width in pixels of the image
- /// The height in pixels of the image
+ ///
protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height)
{
// 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)
{
// 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;
@@ -115,36 +109,17 @@ namespace ImageSharp.Quantizers
}
}
- ///
- /// Process the pixel in the first pass of the algorithm
- ///
- ///
- /// The pixel to quantize
- ///
- ///
- /// This function need only be overridden if your quantize algorithm needs two passes,
- /// such as an Octree quantizer.
- ///
+ ///
protected override void InitialQuantizePixel(TColor pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer);
}
- ///
- /// Retrieve the palette for the quantized image.
- ///
- ///
- /// The new color palette
- ///
+ ///
protected override TColor[] GetPalette()
{
- if (this.palette == null)
- {
- this.palette = this.octree.Palletize(Math.Max(this.colors, 1));
- }
-
- return this.palette;
+ return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1)));
}
///
@@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers
///
/// The
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBitsNeededForColorDepth(int colorCount)
{
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
///
// 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 };
///
/// The root of the Octree
@@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers
///
private int blue;
- ///
- /// Alpha component
- ///
- private int alpha;
-
///
/// The index of this node in the palette
///
@@ -406,7 +377,7 @@ namespace ImageSharp.Quantizers
// Construct the new node
this.leaf = level == colorBits;
- this.red = this.green = this.blue = this.alpha = 0;
+ this.red = this.green = this.blue = 0;
this.pixelCount = 0;
// If a leaf, increment the leaf count
@@ -454,10 +425,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
- int index = ((buffer[3] & Mask[0]) >> (shift - 3)) |
- ((buffer[2] & Mask[level + 1]) >> (shift - 2)) |
- ((buffer[1] & Mask[level + 1]) >> (shift - 1)) |
- ((buffer[0] & Mask[level + 1]) >> shift);
+ int index = ((buffer[2] & Mask[level]) >> (shift - 2)) |
+ ((buffer[1] & Mask[level]) >> (shift - 1)) |
+ ((buffer[0] & Mask[level]) >> shift);
OctreeNode child = this.children[index];
@@ -479,7 +449,7 @@ namespace ImageSharp.Quantizers
/// The number of leaves removed
public int Reduce()
{
- this.red = this.green = this.blue = this.alpha = 0;
+ this.red = this.green = this.blue = 0;
int childNodes = 0;
// 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.green += this.children[index].green;
this.blue += this.children[index].blue;
- this.alpha += this.children[index].alpha;
this.pixelCount += this.children[index].pixelCount;
++childNodes;
this.children[index] = null;
@@ -517,11 +486,10 @@ namespace ImageSharp.Quantizers
byte r = (this.red / this.pixelCount).ToByte();
byte g = (this.green / 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
TColor pixel = default(TColor);
- pixel.PackFromBytes(r, g, b, a);
+ pixel.PackFromBytes(r, g, b, 255);
palette[index] = pixel;
// Consume the next palette index
@@ -558,10 +526,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
- int pixelIndex = ((buffer[3] & Mask[0]) >> (shift - 3)) |
- ((buffer[2] & Mask[level + 1]) >> (shift - 2)) |
- ((buffer[1] & Mask[level + 1]) >> (shift - 1)) |
- ((buffer[0] & Mask[level + 1]) >> shift);
+ int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) |
+ ((buffer[1] & Mask[level]) >> (shift - 1)) |
+ ((buffer[0] & Mask[level]) >> shift);
if (this.children[pixelIndex] != null)
{
@@ -588,9 +555,8 @@ namespace ImageSharp.Quantizers
this.red += buffer[0];
this.green += buffer[1];
this.blue += buffer[2];
- this.alpha += buffer[3];
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/PaletteQuantizer.cs
similarity index 91%
rename from src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs
rename to src/ImageSharp/Quantizers/PaletteQuantizer.cs
index dedb5ca23..f039fe0c5 100644
--- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs
+++ b/src/ImageSharp/Quantizers/PaletteQuantizer.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Collections.Generic;
- using System.Numerics;
using System.Runtime.CompilerServices;
///
@@ -71,13 +70,7 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors);
}
- ///
- /// Execute a second pass through the bitmap
- ///
- /// The source image.
- /// The output pixel array
- /// The width in pixels of the image
- /// The height in pixels of the image
+ ///
protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height)
{
// 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)
{
// 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;
diff --git a/src/ImageSharp/Quantizers/Options/Quantization.cs b/src/ImageSharp/Quantizers/Quantization.cs
similarity index 81%
rename from src/ImageSharp/Quantizers/Options/Quantization.cs
rename to src/ImageSharp/Quantizers/Quantization.cs
index 428c8e801..039404384 100644
--- a/src/ImageSharp/Quantizers/Options/Quantization.cs
+++ b/src/ImageSharp/Quantizers/Quantization.cs
@@ -12,17 +12,20 @@ namespace ImageSharp
{
///
/// An adaptive Octree quantizer. Fast with good quality.
+ /// The quantizer only supports a single alpha value.
///
Octree,
///
/// Xiaolin Wu's Color Quantizer which generates high quality output.
+ /// The quantizer supports multiple alpha values.
///
Wu,
///
/// Palette based, Uses the collection of web-safe colors by default.
+ /// The quantizer supports multiple alpha values.
///
Palette
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Quantizer.cs
similarity index 90%
rename from src/ImageSharp/Quantizers/Octree/Quantizer.cs
rename to src/ImageSharp/Quantizers/Quantizer.cs
index ccf8da3df..bb856ccc5 100644
--- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageSharp/Quantizers/Quantizer.cs
@@ -66,22 +66,9 @@ namespace ImageSharp.Quantizers
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();
-
- if (this.Dither)
- {
- // We clone the image as we don't want to alter the original.
- using (Image clone = new Image(image))
- using (PixelAccessor clonedPixels = clone.Lock())
- {
- this.SecondPass(clonedPixels, quantizedPixels, width, height);
- }
- }
- else
- {
- this.SecondPass(pixels, quantizedPixels, width, height);
- }
+ this.SecondPass(pixels, quantizedPixels, width, height);
}
return new QuantizedImage(width, height, colorPalette, quantizedPixels);
diff --git a/src/ImageSharp/Quantizers/WuArrayPool.cs b/src/ImageSharp/Quantizers/WuArrayPool.cs
new file mode 100644
index 000000000..5e4956f01
--- /dev/null
+++ b/src/ImageSharp/Quantizers/WuArrayPool.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Quantizers
+{
+ using System.Buffers;
+
+ ///
+ /// Provides array pooling for the .
+ /// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces.
+ ///
+ internal static class WuArrayPool
+ {
+ ///
+ /// The long array pool.
+ ///
+ public static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25);
+
+ ///
+ /// The float array pool.
+ ///
+ public static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5);
+
+ ///
+ /// The byte array pool.
+ ///
+ public static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5);
+
+ ///
+ /// The table length. Matches the calculated value in
+ ///
+ private const int TableLength = 2471625;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/WuQuantizer.cs
similarity index 70%
rename from src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
rename to src/ImageSharp/Quantizers/WuQuantizer.cs
index d632cdd73..c1c81d0ac 100644
--- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
+++ b/src/ImageSharp/Quantizers/WuQuantizer.cs
@@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Buffers;
+ using System.Collections.Generic;
using System.Numerics;
- using System.Threading.Tasks;
+ using System.Runtime.CompilerServices;
///
/// An implementation of Wu's color quantizer with alpha channel.
@@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers
///
///
/// The pixel format.
- public sealed class WuQuantizer : IQuantizer
+ public class WuQuantizer : Quantizer
where TColor : struct, IPixel
{
///
@@ -59,103 +60,238 @@ namespace ImageSharp.Quantizers
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
///
- /// The long array pool.
- ///
- private static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25);
-
- ///
- /// The double array pool.
+ /// A buffer for storing pixels
///
- private static readonly ArrayPool DoublePool = ArrayPool.Create(TableLength, 5);
+ private readonly byte[] rgbaBuffer = new byte[4];
///
- /// The byte array pool.
+ /// A lookup table for colors
///
- private static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5);
+ private readonly Dictionary colorMap = new Dictionary();
///
/// Moment of P(c).
///
- private readonly long[] vwt;
+ private long[] vwt;
///
/// Moment of r*P(c).
///
- private readonly long[] vmr;
+ private long[] vmr;
///
/// Moment of g*P(c).
///
- private readonly long[] vmg;
+ private long[] vmg;
///
/// Moment of b*P(c).
///
- private readonly long[] vmb;
+ private long[] vmb;
///
/// Moment of a*P(c).
///
- private readonly long[] vma;
+ private long[] vma;
///
/// Moment of c^2*P(c).
///
- private readonly double[] m2;
+ private float[] m2;
///
/// Color space tag.
///
- private readonly byte[] tag;
+ private byte[] tag;
///
- /// A buffer for storing pixels
+ /// Maximum allowed color depth
///
- private readonly byte[] rgbaBuffer = new byte[4];
+ private int colors;
+
+ ///
+ /// The reduced image palette
+ ///
+ private TColor[] palette;
+
+ ///
+ /// The color cube representing the image palette
+ ///
+ private Box[] colorCube;
///
/// Initializes a new instance of the class.
///
+ ///
+ /// 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.
+ ///
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);
}
///
- public QuantizedImage Quantize(ImageBase image, int maxColors)
+ public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
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);
+ }
+ }
+
+ ///
+ 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;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ protected override void FirstPass(PixelAccessor 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 imagePixels = image.Lock())
+ ///
+ protected override void SecondPass(PixelAccessor 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);
- this.Get3DMoments();
+ // And loop through each column
+ 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;
- this.BuildCube(out cube, ref colorCount);
+ if (this.Dither)
+ {
+ // 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;
+ }
}
}
///
- /// Gets an index.
+ /// Gets the index index of the given color in the palette.
///
/// The red value.
/// The green value.
/// The blue value.
/// The alpha value.
/// The index.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPaletteIndex(int r, int g, int b, int a)
{
return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1))
@@ -169,7 +305,7 @@ namespace ImageSharp.Quantizers
/// The cube.
/// The moment.
/// The result.
- 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)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
@@ -310,55 +446,6 @@ namespace ImageSharp.Quantizers
}
}
- ///
- /// Clears the tables.
- ///
- 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);
- }
-
- ///
- /// Builds a 3-D color histogram of counts, r/g/b, c^2.
- ///
- /// The pixel accessor.
- private void Build3DHistogram(PixelAccessor 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);
- }
- }
- }
-
///
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
///
@@ -369,14 +456,14 @@ namespace ImageSharp.Quantizers
long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
- double[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ float[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] area = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount);
- double[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount);
+ float[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount);
try
{
@@ -405,7 +492,7 @@ namespace ImageSharp.Quantizers
long lineG = 0;
long lineB = 0;
long lineA = 0;
- double line2 = 0;
+ float line2 = 0;
for (int a = 1; a < IndexAlphaCount; a++)
{
@@ -454,14 +541,14 @@ namespace ImageSharp.Quantizers
ArrayPool.Shared.Return(volumeG);
ArrayPool.Shared.Return(volumeB);
ArrayPool.Shared.Return(volumeA);
- ArrayPool.Shared.Return(volume2);
+ ArrayPool.Shared.Return(volume2);
ArrayPool.Shared.Return(area);
ArrayPool.Shared.Return(areaR);
ArrayPool.Shared.Return(areaG);
ArrayPool.Shared.Return(areaB);
ArrayPool.Shared.Return(areaA);
- ArrayPool.Shared.Return(area2);
+ ArrayPool.Shared.Return(area2);
}
}
@@ -469,15 +556,15 @@ namespace ImageSharp.Quantizers
/// Computes the weighted variance of a box cube.
///
/// The cube.
- /// The .
- private double Variance(Box cube)
+ /// The .
+ private float Variance(Box cube)
{
- double dr = Volume(cube, this.vmr);
- double dg = Volume(cube, this.vmg);
- double db = Volume(cube, this.vmb);
- double da = Volume(cube, this.vma);
+ float dr = Volume(cube, this.vmr);
+ float dg = Volume(cube, this.vmg);
+ float db = Volume(cube, this.vmb);
+ 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.A0)]
- this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
@@ -515,8 +602,8 @@ namespace ImageSharp.Quantizers
/// The whole blue.
/// The whole alpha.
/// The whole weight.
- /// The .
- private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW)
+ /// The .
+ 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 baseG = Bottom(cube, direction, this.vmg);
@@ -524,20 +611,20 @@ namespace ImageSharp.Quantizers
long baseA = Bottom(cube, direction, this.vma);
long baseW = Bottom(cube, direction, this.vwt);
- double max = 0.0;
+ float max = 0F;
cut = -1;
for (int i = first; i < last; i++)
{
- double halfR = baseR + Top(cube, direction, i, this.vmr);
- double halfG = baseG + Top(cube, direction, i, this.vmg);
- double halfB = baseB + Top(cube, direction, i, this.vmb);
- double halfA = baseA + Top(cube, direction, i, this.vma);
- double halfW = baseW + Top(cube, direction, i, this.vwt);
+ float halfR = baseR + Top(cube, direction, i, this.vmr);
+ float halfG = baseG + Top(cube, direction, i, this.vmg);
+ float halfB = baseB + Top(cube, direction, i, this.vmb);
+ float halfA = baseA + Top(cube, direction, i, this.vma);
+ 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;
}
@@ -550,7 +637,7 @@ namespace ImageSharp.Quantizers
halfA = wholeA - halfA;
halfW = wholeW - halfW;
- if (Math.Abs(halfW) < Constants.Epsilon)
+ if (MathF.Abs(halfW) < Constants.Epsilon)
{
continue;
}
@@ -575,21 +662,16 @@ namespace ImageSharp.Quantizers
/// Returns a value indicating whether the box has been split.
private bool Cut(Box set1, Box set2)
{
- double wholeR = Volume(set1, this.vmr);
- double wholeG = Volume(set1, this.vmg);
- double wholeB = Volume(set1, this.vmb);
- double wholeA = Volume(set1, this.vma);
- double wholeW = Volume(set1, this.vwt);
-
- int cutr;
- int cutg;
- int cutb;
- int cuta;
-
- 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);
+ float wholeR = Volume(set1, this.vmr);
+ float wholeG = Volume(set1, this.vmg);
+ float wholeB = Volume(set1, this.vmb);
+ float wholeA = Volume(set1, this.vma);
+ float wholeW = Volume(set1, this.vwt);
+
+ float maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
int dir;
@@ -686,40 +768,38 @@ namespace ImageSharp.Quantizers
///
/// Builds the cube.
///
- /// The cube.
- /// The color count.
- private void BuildCube(out Box[] cube, ref int colorCount)
+ private void BuildCube()
{
- cube = new Box[colorCount];
- double[] vv = new double[colorCount];
+ this.colorCube = new Box[this.colors];
+ 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;
- cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1;
- cube[0].A1 = IndexAlphaCount - 1;
+ this.colorCube[0].R0 = this.colorCube[0].G0 = this.colorCube[0].B0 = this.colorCube[0].A0 = 0;
+ this.colorCube[0].R1 = this.colorCube[0].G1 = this.colorCube[0].B1 = IndexCount - 1;
+ this.colorCube[0].A1 = IndexAlphaCount - 1;
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[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0;
+ vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(this.colorCube[next]) : 0F;
+ vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(this.colorCube[i]) : 0F;
}
else
{
- vv[next] = 0.0;
+ vv[next] = 0F;
i--;
}
next = 0;
- double temp = vv[0];
+ float temp = vv[0];
for (int k = 1; k <= i; k++)
{
if (vv[k] > temp)
@@ -731,79 +811,38 @@ namespace ImageSharp.Quantizers
if (temp <= 0.0)
{
- colorCount = i + 1;
+ this.colors = i + 1;
break;
}
}
}
///
- /// Generates the quantized result.
+ /// Process the pixel in the second pass of the algorithm
///
- /// The image pixels.
- /// The color count.
- /// The cube.
- /// The result.
- private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube)
+ /// The pixel to quantize
+ ///
+ /// The quantized value
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte QuantizePixel(TColor pixel)
{
- TColor[] pallette = new TColor[colorCount];
- byte[] pixels = new byte[imagePixels.Width * imagePixels.Height];
- int width = imagePixels.Width;
- int height = imagePixels.Height;
-
- for (int k = 0; k < colorCount; k++)
+ if (this.Dither)
{
- this.Mark(cube[k], (byte)k);
-
- double weight = Volume(cube[k], this.vwt);
-
- 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;
- }
+ // The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
+ // This palette can never be null here.
+ return this.GetClosestColor(pixel, this.palette, this.colorMap);
}
- Parallel.For(
- 0,
- height,
- imagePixels.ParallelOptions,
- y =>
- {
- byte[] rgba = ArrayPool.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.Shared.Return(rgba);
- });
+ // Expected order r->g->b->a
+ pixel.ToXyzwBytes(this.rgbaBuffer, 0);
- // Cleanup
- LongPool.Return(this.vwt);
- LongPool.Return(this.vmr);
- LongPool.Return(this.vmg);
- LongPool.Return(this.vmb);
- LongPool.Return(this.vma);
- DoublePool.Return(this.m2);
- BytePool.Return(this.tag);
+ int r = this.rgbaBuffer[0] >> (8 - IndexBits);
+ int g = this.rgbaBuffer[1] >> (8 - IndexBits);
+ int b = this.rgbaBuffer[2] >> (8 - IndexBits);
+ int a = this.rgbaBuffer[3] >> (8 - IndexAlphaBits);
- return new QuantizedImage(width, height, pallette, pixels);
+ return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 765ff3a42..1fa26063d 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/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.Progressive.Bad.BadEOF), // Perf: Enable for local testing only
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.ChunkLength1), // 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.Pd), // Perf: Enable for local testing only
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.Giphy) // Perf: Enable for local testing only
};
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
index 882f903d6..a7453f77c 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
@@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Formats.Png
using ImageSharp.Formats;
using System.Linq;
using ImageSharp.IO;
+ using System.Numerics;
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(TestImageProvider provider)
+ // where TColor : struct, IPixel
+ //{
+ // // does saving a file then repoening mean both files are identical???
+ // using (Image 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 pixels1 = img1.Lock())
+ // using (PixelAccessor 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]
[WithTestPatternImages(300, 300, PixelTypes.All)]
public void Resize(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index e9d658887..5be1240ef 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -103,6 +103,7 @@ namespace ImageSharp.Tests
public const string Rings = "Gif/rings.gif";
public const string Giphy = "Gif/giphy.gif";
public const string Cheers = "Gif/cheers.gif";
+ public const string Trans = "Gif/trans.gif";
}
}
}
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif
new file mode 100644
index 000000000..6a7577fa1
--- /dev/null
+++ b/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