diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index a07928b04..07a70ad96 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer();
+ ///
+ /// Gets or sets the color table mode: Global or local.
+ ///
+ public GifColorTableMode ColorTableMode { get; set; }
+
///
public void Encode(Image image, Stream stream)
where TPixel : struct, IPixel
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index f84b13f5f..baed04260 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -19,6 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
internal sealed class GifEncoderCore
{
+ ///
+ /// Used for allocating memory during procesing operations.
+ ///
private readonly MemoryAllocator memoryAllocator;
///
@@ -27,15 +30,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
private readonly byte[] buffer = new byte[20];
///
- /// Gets the text encoding used to write comments.
+ /// The text encoding used to write comments.
///
private readonly Encoding textEncoding;
///
- /// Gets or sets the quantizer used to generate the color palette.
+ /// The quantizer used to generate the color palette.
///
private readonly IQuantizer quantizer;
+ ///
+ /// The color table mode: Global or local.
+ ///
+ private readonly GifColorTableMode colorTableMode;
+
///
/// A flag indicating whether to ingore the metadata when writing the image.
///
@@ -56,6 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.memoryAllocator = memoryAllocator;
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer;
+ this.colorTableMode = options.ColorTableMode;
this.ignoreMetadata = options.IgnoreMetadata;
}
@@ -72,28 +81,80 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(stream, nameof(stream));
// Quantize the image returning a palette.
- QuantizedFrame quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
+ QuantizedFrame quantized =
+ this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
// Get the number of bits.
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
- int index = this.GetTransparentIndex(quantized);
-
// Write the header.
this.WriteHeader(stream);
- // Write the LSD. We'll use local color tables for now.
- this.WriteLogicalScreenDescriptor(image, stream, index);
+ // Write the LSD.
+ int index = this.GetTransparentIndex(quantized);
+ bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global);
+ this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream);
+
+ if (useGlobalTable)
+ {
+ this.WriteColorTable(quantized, stream);
+ }
- // Write the first frame.
+ // Write the comments.
this.WriteComments(image.MetaData, stream);
- // Write additional frames.
+ // Write application extension to allow additional frames.
if (image.Frames.Count > 1)
{
this.WriteApplicationExtension(stream, image.MetaData.RepeatCount);
}
+ if (useGlobalTable)
+ {
+ this.EncodeGlobal(image, quantized, index, stream);
+ }
+ else
+ {
+ this.EncodeLocal(image, quantized, stream);
+ }
+
+ // Clean up.
+ quantized?.Dispose();
+ quantized = null;
+
+ // TODO: Write extension etc
+ stream.WriteByte(GifConstants.EndIntroducer);
+ }
+
+ private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ var palleteQuantizer = new PaletteQuantizer(this.quantizer.Diffuser);
+
+ for (int i = 0; i < image.Frames.Count; i++)
+ {
+ ImageFrame frame = image.Frames[i];
+
+ this.WriteGraphicalControlExtension(frame.MetaData, transparencyIndex, stream);
+ this.WriteImageDescriptor(frame, false, stream);
+
+ if (i == 0)
+ {
+ this.WriteImageData(quantized, stream);
+ }
+ else
+ {
+ using (QuantizedFrame paletteQuantized = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
+ {
+ this.WriteImageData(paletteQuantized, stream);
+ }
+ }
+ }
+ }
+
+ private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream)
+ where TPixel : struct, IPixel
+ {
foreach (ImageFrame frame in image.Frames)
{
if (quantized == null)
@@ -101,16 +162,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
}
- this.WriteGraphicalControlExtension(frame.MetaData, stream, this.GetTransparentIndex(quantized));
- this.WriteImageDescriptor(frame, stream);
+ this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream);
+ this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);
+ quantized?.Dispose();
quantized = null; // So next frame can regenerate it
}
-
- // TODO: Write extension etc
- stream.WriteByte(GifConstants.EndIntroducer);
}
///
@@ -159,12 +218,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The pixel format.
/// The image to encode.
- /// The stream to write to.
/// The transparency index to set the default background index to.
- private void WriteLogicalScreenDescriptor(Image image, Stream stream, int transparencyIndex)
+ /// Whether to use a global or local color table.
+ /// The stream to write to.
+ private void WriteLogicalScreenDescriptor(Image image, int transparencyIndex, bool useGlobalTable, Stream stream)
where TPixel : struct, IPixel
{
- byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(false, this.bitDepth - 1, false, this.bitDepth - 1);
+ byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1);
var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width,
@@ -243,9 +303,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Writes the graphics control extension to the stream.
///
/// The metadata of the image or frame.
- /// The stream to write to.
/// The index of the color in the color palette to make transparent.
- private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, Stream stream, int transparencyIndex)
+ /// The stream to write to.
+ private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, int transparencyIndex, Stream stream)
{
byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metaData.DisposalMethod,
@@ -253,8 +313,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
var extension = new GifGraphicControlExtension(
packed: packedValue,
- transparencyIndex: unchecked((byte)transparencyIndex),
- delayTime: (ushort)metaData.FrameDelay);
+ delayTime: (ushort)metaData.FrameDelay,
+ transparencyIndex: unchecked((byte)transparencyIndex));
this.WriteExtension(extension, stream);
}
@@ -281,15 +341,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The pixel format.
/// The to be encoded.
+ /// Whether to use the global color table.
/// The stream to write to.
- private void WriteImageDescriptor(ImageFrame image, Stream stream)
+ private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream)
where TPixel : struct, IPixel
{
byte packedValue = GifImageDescriptor.GetPackedValue(
- localColorTableFlag: true,
+ localColorTableFlag: hasColorTable,
interfaceFlag: false,
sortFlag: false,
- localColorTableSize: (byte)this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
+ localColorTableSize: (byte)(this.bitDepth - 1)); // Note: we subtract 1 from the colorTableSize writing
var descriptor = new GifImageDescriptor(
left: 0,
@@ -342,9 +403,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteImageData(QuantizedFrame image, Stream stream)
where TPixel : struct, IPixel
{
- using (var encoder = new LzwEncoder(this.memoryAllocator, image.Pixels, (byte)this.bitDepth))
+ using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))
{
- encoder.Encode(stream);
+ encoder.Encode(image.GetPixelSpan(), stream);
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
index 44dd19db6..30e476e7e 100644
--- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
@@ -25,5 +25,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the quantizer used to generate the color palette.
///
IQuantizer Quantizer { get; }
+
+ ///
+ /// Gets the color table mode: Global or local.
+ ///
+ GifColorTableMode ColorTableMode { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
index de9de5e15..2ec569781 100644
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
@@ -58,11 +58,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private const int MaxMaxCode = 1 << MaxBits;
- ///
- /// The working pixel array.
- ///
- private readonly byte[] pixelArray;
-
///
/// The initial code size.
///
@@ -83,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private readonly byte[] accumulators = new byte[256];
+ ///
+ /// For dynamic table sizing
+ ///
+ private readonly int hsize = HashSize;
+
///
/// The current position within the pixelArray.
///
@@ -98,11 +98,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private int maxCode;
- ///
- /// For dynamic table sizing
- ///
- private int hsize = HashSize;
-
///
/// First unused entry
///
@@ -169,13 +164,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Initializes a new instance of the class.
///
/// The to use for buffer allocations.
- /// The array of indexed pixels.
/// The color depth in bits.
- public LzwEncoder(MemoryAllocator memoryAllocator, byte[] indexedPixels, int colorDepth)
+ public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth)
{
- this.pixelArray = indexedPixels;
this.initialCodeSize = Math.Max(2, colorDepth);
-
this.hashTable = memoryAllocator.Allocate(HashSize, true);
this.codeTable = memoryAllocator.Allocate(HashSize, true);
}
@@ -183,8 +175,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Encodes and compresses the indexed pixels to the stream.
///
+ /// The span of indexed pixels.
/// The stream to write to.
- public void Encode(Stream stream)
+ public void Encode(Span indexedPixels, Stream stream)
{
// Write "initial code size" byte
stream.WriteByte((byte)this.initialCodeSize);
@@ -192,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.position = 0;
// Compress and write the pixel data
- this.Compress(this.initialCodeSize + 1, stream);
+ this.Compress(indexedPixels, this.initialCodeSize + 1, stream);
// Write block terminator
stream.WriteByte(GifConstants.Terminator);
@@ -252,9 +245,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Compress the packets to the stream.
///
+ /// The span of indexed pixels.
/// The initial bits.
/// The stream to write to.
- private void Compress(int intialBits, Stream stream)
+ private void Compress(Span indexedPixels, int intialBits, Stream stream)
{
int fcode;
int c;
@@ -276,7 +270,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.accumulatorCount = 0; // clear packet
- ent = this.NextPixel();
+ ent = this.NextPixel(indexedPixels);
// TODO: PERF: It looks likt hshift could be calculated once statically.
hshift = 0;
@@ -296,9 +290,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan());
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan());
- while (this.position < this.pixelArray.Length)
+ while (this.position < indexedPixels.Length)
{
- c = this.NextPixel();
+ c = this.NextPixel(indexedPixels);
fcode = (c << MaxBits) + ent;
int i = (c << hshift) ^ ent /* = 0 */;
@@ -373,13 +367,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Reads the next pixel from the image.
///
+ /// The span of indexed pixels.
///
/// The
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int NextPixel()
+ private int NextPixel(Span indexedPixels)
{
- return this.pixelArray[this.position++] & 0xff;
+ return indexedPixels[this.position++] & 0xff;
}
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 45e8669d6..1b3e84b85 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -86,11 +86,6 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private readonly bool writeGamma;
- ///
- /// Contains the raw pixel data from an indexed image.
- ///
- private byte[] palettePixelData;
-
///
/// The image width.
///
@@ -188,11 +183,12 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
+ ReadOnlySpan quantizedPixelsSpan = default;
if (this.pngColorType == PngColorType.Palette)
{
// Create quantized frame returning the palette and set the bit depth.
quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
- this.palettePixelData = quantized.Pixels;
+ quantizedPixelsSpan = quantized.GetPixelSpan();
byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
@@ -233,9 +229,11 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
- this.WriteDataChunks(image.Frames.RootFrame, stream);
+ this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
+
+ quantized?.Dispose();
}
///
@@ -384,9 +382,10 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The row span.
+ /// The span of quantized pixels. Can be null.
/// The row.
/// The
- private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, int row)
+ private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, ReadOnlySpan quantizedPixelsSpan, int row)
where TPixel : struct, IPixel
{
switch (this.pngColorType)
@@ -394,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Palette:
int stride = this.rawScanline.Length();
- this.palettePixelData.AsSpan(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
+ quantizedPixelsSpan.Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
break;
case PngColorType.Grayscale:
@@ -555,10 +554,11 @@ namespace SixLabors.ImageSharp.Formats.Png
{
Span colorTableSpan = colorTable.GetSpan();
Span alphaTableSpan = alphaTable.GetSpan();
+ Span quantizedSpan = quantized.GetPixelSpan();
for (byte i = 0; i < pixelCount; i++)
{
- if (quantized.Pixels.Contains(i))
+ if (quantizedSpan.IndexOf(i) > -1)
{
int offset = i * 3;
palette[i].ToRgba32(ref rgba);
@@ -571,10 +571,10 @@ namespace SixLabors.ImageSharp.Formats.Png
if (alpha > this.threshold)
{
- alpha = 255;
+ alpha = byte.MaxValue;
}
- anyAlpha = anyAlpha || alpha < 255;
+ anyAlpha = anyAlpha || alpha < byte.MaxValue;
alphaTableSpan[i] = alpha;
}
}
@@ -635,8 +635,9 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The image.
+ /// The span of quantized pixel data. Can be null.
/// The stream.
- private void WriteDataChunks(ImageFrame pixels, Stream stream)
+ private void WriteDataChunks(ImageFrame pixels, ReadOnlySpan quantizedPixelsSpan, Stream stream)
where TPixel : struct, IPixel
{
this.bytesPerScanline = this.width * this.bytesPerPixel;
@@ -688,7 +689,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
for (int y = 0; y < this.height; y++)
{
- IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), y);
+ IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantizedPixelsSpan, y);
deflateStream.Write(r.Array, 0, resultLength);
IManagedByteBuffer temp = this.rawScanline;
diff --git a/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs
index bf0d80b07..0c21f6e5e 100644
--- a/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs
+++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs
@@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
///
/// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
/// only call the methods.
- /// If two passes are required, the code will also call
+ /// If two passes are required, the code will also call
/// and then 'QuantizeImage'.
///
protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass)
@@ -58,7 +58,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
// Get the size of the source image
int height = image.Height;
int width = image.Width;
- byte[] quantizedPixels = new byte[width * height];
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
@@ -69,22 +68,22 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
// Collect the palette. Required before the second pass runs.
- TPixel[] colorPalette = this.GetPalette();
+ var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, this.GetPalette());
if (this.Dither)
{
// We clone the image as we don't want to alter the original.
using (ImageFrame clone = image.Clone())
{
- this.SecondPass(clone, quantizedPixels, width, height);
+ this.SecondPass(clone, quantizedFrame.GetPixelSpan(), width, height);
}
}
else
{
- this.SecondPass(image, quantizedPixels, width, height);
+ this.SecondPass(image, quantizedFrame.GetPixelSpan(), width, height);
}
- return new QuantizedFrame(width, height, colorPalette, quantizedPixels);
+ return quantizedFrame;
}
///
@@ -104,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// The output pixel array
/// The width in pixels of the image
/// The height in pixels of the image
- protected abstract void SecondPass(ImageFrame source, byte[] output, int width, int height);
+ protected abstract void SecondPass(ImageFrame source, Span output, int width, int height);
///
/// Retrieve the palette for the quantized image.
@@ -131,7 +130,13 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
return cache[pixel];
}
- // Not found - loop through the palette and find the nearest match.
+ return this.GetClosestPixelSlow(pixel, colorPalette, cache);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private byte GetClosestPixelSlow(TPixel pixel, TPixel[] colorPalette, Dictionary cache)
+ {
+ // Loop through the palette and find the nearest match.
byte colorIndex = 0;
float leastDistance = int.MaxValue;
var vector = pixel.ToVector4();
diff --git a/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs
index e32022254..99519031d 100644
--- a/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs
@@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
///
- protected override void SecondPass(ImageFrame source, byte[] output, int width, int height)
+ protected override void SecondPass(ImageFrame source, Span 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.
@@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
{
this.palette[i].ToRgba32(ref trans);
- if (trans.Equals(default(Rgba32)))
+ if (trans.Equals(default))
{
index = i;
}
@@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
pixel.ToRgba32(ref rgba);
- if (rgba.Equals(default(Rgba32)))
+ if (rgba.Equals(default))
{
return this.transparentIndex;
}
@@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
this.Leaves = 0;
this.reducibleNodes = new OctreeNode[9];
this.root = new OctreeNode(0, this.maxColorBits, this);
- this.previousColor = default(TPixel);
+ this.previousColor = default;
this.previousNode = null;
}
@@ -476,9 +476,9 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
int shift = 7 - level;
pixel.ToRgba32(ref rgba);
- int index = ((rgba.B & Mask[level]) >> (shift - 2)) |
- ((rgba.G & Mask[level]) >> (shift - 1)) |
- ((rgba.R & Mask[level]) >> shift);
+ int index = ((rgba.B & Mask[level]) >> (shift - 2))
+ | ((rgba.G & Mask[level]) >> (shift - 1))
+ | ((rgba.R & Mask[level]) >> shift);
OctreeNode child = this.children[index];
@@ -551,10 +551,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
// Loop through children looking for leaves
for (int i = 0; i < 8; i++)
{
- if (this.children[i] != null)
- {
- this.children[i].ConstructPalette(palette, ref index);
- }
+ this.children[i]?.ConstructPalette(palette, ref index);
}
}
}
@@ -577,9 +574,9 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
int shift = 7 - level;
pixel.ToRgba32(ref rgba);
- int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) |
- ((rgba.G & Mask[level]) >> (shift - 1)) |
- ((rgba.R & Mask[level]) >> shift);
+ int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2))
+ | ((rgba.G & Mask[level]) >> (shift - 1))
+ | ((rgba.R & Mask[level]) >> shift);
if (this.children[pixelIndex] != null)
{
diff --git a/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs
index 34cb7eb16..14f4b1c39 100644
--- a/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs
@@ -24,22 +24,23 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
private readonly Dictionary colorMap = new Dictionary();
///
- /// List of all colors in the palette
+ /// List of all colors in the palette.
///
private readonly TPixel[] colors;
///
/// Initializes a new instance of the class.
///
- /// The palette quantizer
- public PaletteFrameQuantizer(PaletteQuantizer quantizer)
+ /// The palette quantizer.
+ /// An array of all colors in the palette.
+ public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors)
: base(quantizer, true)
{
- this.colors = quantizer.GetPalette();
+ this.colors = colors;
}
///
- protected override void SecondPass(ImageFrame source, byte[] output, int width, int height)
+ protected override void SecondPass(ImageFrame source, Span 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.
@@ -88,10 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected override TPixel[] GetPalette()
- {
- return this.colors;
- }
+ protected override TPixel[] GetPalette() => this.colors;
///
/// Process the pixel in the second pass of the algorithm
@@ -101,9 +99,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// The quantized value
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private byte QuantizePixel(TPixel pixel)
- {
- return this.GetClosestPixel(pixel, this.GetPalette(), this.colorMap);
- }
+ private byte QuantizePixel(TPixel pixel) => this.GetClosestPixel(pixel, this.GetPalette(), this.colorMap);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs
index 78c4bfbf8..154263959 100644
--- a/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs
@@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
///
- protected override void SecondPass(ImageFrame source, byte[] output, int width, int height)
+ protected override void SecondPass(ImageFrame source, Span 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.
@@ -464,6 +464,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
///
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
///
+ /// The memory allocator used for allocating buffers.
private void Get3DMoments(MemoryAllocator memoryAllocator)
{
Span vwtSpan = this.vwt.GetSpan();
diff --git a/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs
index 8f790dfc9..85cc8334f 100644
--- a/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs
+++ b/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Dithering;
using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion;
@@ -46,19 +47,20 @@ namespace SixLabors.ImageSharp.Processing.Quantization
///
public IErrorDiffuser Diffuser { get; }
+ ///
+ public IFrameQuantizer CreateFrameQuantizer()
+ where TPixel : struct, IPixel
+ => this.CreateFrameQuantizer(() => NamedColors.WebSafePalette);
+
///
/// Gets the palette to use to quantize the image.
///
/// The pixel format.
- /// The
- public virtual TPixel[] GetPalette()
- where TPixel : struct, IPixel
- => NamedColors.WebSafePalette;
-
- ///
- public IFrameQuantizer CreateFrameQuantizer()
+ /// The method to return the palette.
+ /// The
+ public virtual IFrameQuantizer CreateFrameQuantizer(Func paletteFunction)
where TPixel : struct, IPixel
- => new PaletteFrameQuantizer(this);
+ => new PaletteFrameQuantizer(this, paletteFunction.Invoke());
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
}
diff --git a/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs b/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs
index 951e47127..5b20805b0 100644
--- a/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs
+++ b/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs
@@ -36,22 +36,24 @@ namespace SixLabors.ImageSharp.Processing.Quantization.Processors
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
IFrameQuantizer executor = this.Quantizer.CreateFrameQuantizer();
- QuantizedFrame quantized = executor.QuantizeFrame(source);
- int paletteCount = quantized.Palette.Length - 1;
-
- // Not parallel to remove "quantized" closure allocation.
- // We can operate directly on the source here as we've already read it to get the
- // quantized result
- for (int y = 0; y < source.Height; y++)
+ using (QuantizedFrame quantized = executor.QuantizeFrame(source))
{
- Span row = source.GetPixelRowSpan(y);
- int yy = y * source.Width;
+ int paletteCount = quantized.Palette.Length - 1;
- for (int x = 0; x < source.Width; x++)
+ // Not parallel to remove "quantized" closure allocation.
+ // We can operate directly on the source here as we've already read it to get the
+ // quantized result
+ for (int y = 0; y < source.Height; y++)
{
- int i = x + yy;
- TPixel color = quantized.Palette[Math.Min(paletteCount, quantized.Pixels[i])];
- row[x] = color;
+ Span row = source.GetPixelRowSpan(y);
+ ReadOnlySpan quantizedPixelSpan = quantized.GetPixelSpan();
+ int yy = y * source.Width;
+
+ for (int x = 0; x < source.Width; x++)
+ {
+ int i = x + yy;
+ row[x] = quantized.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])];
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Quantization/QuantizedFrame{TPixel}.cs
index ac87e1c7c..6699c76f4 100644
--- a/src/ImageSharp/Processing/Quantization/QuantizedFrame{TPixel}.cs
+++ b/src/ImageSharp/Processing/Quantization/QuantizedFrame{TPixel}.cs
@@ -3,39 +3,36 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+// TODO: Consider pooling the TPixel palette also. For Rgba48+ this would end up on th LOH if 256 colors.
namespace SixLabors.ImageSharp.Processing.Quantization
{
///
/// Represents a quantized image frame where the pixels indexed by a color palette.
///
/// The pixel format.
- public class QuantizedFrame
+ public class QuantizedFrame : IDisposable
where TPixel : struct, IPixel
{
+ private IBuffer pixels;
+
///
/// Initializes a new instance of the class.
///
+ /// Used to allocated memory for image processing operations.
/// The image width.
/// The image height.
/// The color palette.
- /// The quantized pixels.
- public QuantizedFrame(int width, int height, TPixel[] palette, byte[] pixels)
+ public QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, TPixel[] palette)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- Guard.NotNull(palette, nameof(palette));
- Guard.NotNull(pixels, nameof(pixels));
-
- if (pixels.Length != width * height)
- {
- throw new ArgumentException($"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels));
- }
this.Width = width;
this.Height = height;
this.Palette = palette;
- this.Pixels = pixels;
+ this.pixels = memoryAllocator.AllocateCleanManagedByteBuffer(width * height);
}
///
@@ -51,11 +48,20 @@ namespace SixLabors.ImageSharp.Processing.Quantization
///
/// Gets the color palette of this .
///
- public TPixel[] Palette { get; }
+ public TPixel[] Palette { get; private set; }
///
/// Gets the pixels of this .
///
- public byte[] Pixels { get; }
+ /// The
+ public Span GetPixelSpan() => this.pixels.GetSpan();
+
+ ///
+ public void Dispose()
+ {
+ this.pixels?.Dispose();
+ this.pixels = null;
+ this.Palette = null;
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
index 91ba160ab..91b331639 100644
--- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
+++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests
QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
- Assert.Equal(index, quantized.Pixels[0]);
+ Assert.Equal(index, quantized.GetPixelSpan()[0]);
}
}
}
@@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests
QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
- Assert.Equal(index, quantized.Pixels[0]);
+ Assert.Equal(index, quantized.GetPixelSpan()[0]);
}
}
}
@@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests
QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
- Assert.Equal(index, quantized.Pixels[0]);
+ Assert.Equal(index, quantized.GetPixelSpan()[0]);
}
}
}