Browse Source

Use Buffer2D throughout. Fix #1141

pull/1574/head
James Jackson-South 6 years ago
parent
commit
b656a9de35
  1. 15
      src/ImageSharp/Advanced/IPixelSource.cs
  2. 6
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  3. 49
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 3
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 24
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  6. 179
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  7. 25
      src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs
  8. 7
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  9. 4
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  10. 38
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

15
src/ImageSharp/Advanced/IPixelSource.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -6,6 +6,17 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced
{ {
/// <summary>
/// Encapsulates the basic properties and methods required to manipulate images.
/// </summary>
internal interface IPixelSource
{
/// <summary>
/// Gets the pixel buffer.
/// </summary>
Buffer2D<byte> PixelBuffer { get; }
}
/// <summary> /// <summary>
/// Encapsulates the basic properties and methods required to manipulate images. /// Encapsulates the basic properties and methods required to manipulate images.
/// </summary> /// </summary>
@ -18,4 +29,4 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary> /// </summary>
Buffer2D<TPixel> PixelBuffer { get; } Buffer2D<TPixel> PixelBuffer { get; }
} }
} }

6
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Restricts a <see cref="byte"/> to be within a specified range. /// Restricts a <see cref="byte"/> to be within a specified range.
/// </summary> /// </summary>
/// <param name="value">The The value to clamp.</param> /// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param> /// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param> /// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
/// <returns> /// <returns>
@ -137,4 +137,4 @@ namespace SixLabors.ImageSharp
return value; return value;
} }
} }
} }

49
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -353,7 +353,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadImageDescriptor(); this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null; IManagedByteBuffer localColorTable = null;
IManagedByteBuffer indices = null; Buffer2D<byte> indices = null;
try try
{ {
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
@ -364,11 +364,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream.Read(localColorTable.Array, 0, length); this.stream.Read(localColorTable.Array, 0, length);
} }
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean); indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan()); this.ReadFrameIndices(indices);
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan()); ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan());
this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor); this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
this.SkipBlock(); this.SkipBlock();
@ -383,16 +383,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Reads the frame indices marking the color to use for each pixel. /// Reads the frame indices marking the color to use for each pixel.
/// </summary> /// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param> /// <param name="indices">The 2D pixel buffer to write to.</param>
/// <param name="indices">The pixel array to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span<byte> indices) private void ReadFrameIndices(Buffer2D<byte> indices)
{ {
int dataSize = this.stream.ReadByte(); int dataSize = this.stream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream)) using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream);
{ lzwDecoder.DecodePixels(dataSize, indices);
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
}
} }
/// <summary> /// <summary>
@ -404,10 +401,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="indices">The indexed pixels.</param> /// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param> /// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param> /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor) private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
int imageWidth = this.logicalScreenDescriptor.Width; int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height; int imageHeight = this.logicalScreenDescriptor.Height;
@ -440,13 +436,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.RestoreToBackground(imageFrame); this.RestoreToBackground(imageFrame);
} }
int i = 0;
int interlacePass = 0; // The interlace pass int interlacePass = 0; // The interlace pass
int interlaceIncrement = 8; // The interlacing line increment int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line int interlaceY = 0; // The current interlaced line
int descriptorTop = descriptor.Top;
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) int descriptorBottom = descriptorTop + descriptor.Height;
int descriptorLeft = descriptor.Left;
int descriptorRight = descriptorLeft + descriptor.Width;
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{ {
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop));
// Check if this image is interlaced. // Check if this image is interlaced.
int writeY; // the target y offset to write to int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag) if (descriptor.InterlaceFlag)
@ -482,35 +485,29 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY));
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
if (!transFlag) if (!transFlag)
{ {
// #403 The left + width value can be larger than the image width // #403 The left + width value can be larger than the image width
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{ {
int index = Unsafe.Add(ref indicesRef, i); int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index]; Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb); pixel.FromRgb24(rgb);
i++;
} }
} }
else else
{ {
byte transIndex = this.graphicsControlExtension.TransparencyIndex; for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++)
{ {
int index = Unsafe.Add(ref indicesRef, i); int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft);
if (transIndex != index) if (transIndex != index)
{ {
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index]; Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb); pixel.FromRgb24(rgb);
} }
i++;
} }
} }
} }

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

@ -6,6 +6,7 @@ using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -471,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
encoder.Encode(image.GetPixelBufferSpan(), stream); encoder.Encode(((IPixelSource)image).PixelBuffer, stream);
} }
} }
} }

24
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -65,15 +65,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Decodes and decompresses all pixel indices from the stream. /// Decodes and decompresses all pixel indices from the stream.
/// </summary> /// </summary>
/// <param name="width">The width of the pixel index array.</param>
/// <param name="height">The height of the pixel index array.</param>
/// <param name="dataSize">Size of the data.</param> /// <param name="dataSize">Size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param> /// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(int width, int height, int dataSize, Span<byte> pixels) public void DecodePixels(int dataSize, Buffer2D<byte> pixels)
{ {
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
// The resulting index table length. // The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height; int length = width * height;
// Calculate the clear code. The value of the clear code is 2 ^ dataSize // Calculate the clear code. The value of the clear code is 2 ^ dataSize
@ -105,17 +105,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
ref byte pixelsRef = ref MemoryMarshal.GetReference(pixels);
for (code = 0; code < clearCode; code++) for (code = 0; code < clearCode; code++)
{ {
Unsafe.Add(ref suffixRef, code) = (byte)code; Unsafe.Add(ref suffixRef, code) = (byte)code;
} }
Span<byte> buffer = stackalloc byte[255]; Span<byte> buffer = stackalloc byte[byte.MaxValue];
int y = 0;
int x = 0;
int rowMax = width;
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y));
while (xyz < length) while (xyz < length)
{ {
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y));
rowMax = (y * width) + width;
}
if (top == 0) if (top == 0)
{ {
if (bits < codeSize) if (bits < codeSize)
@ -209,7 +220,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
top--; top--;
// Clear missing pixels // Clear missing pixels
Unsafe.Add(ref pixelsRef, xyz++) = (byte)Unsafe.Add(ref pixelStackRef, top); xyz++;
Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top);
} }
} }

179
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -41,13 +41,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private const int HashSize = 5003; private const int HashSize = 5003;
/// <summary>
/// The amount to shift each code.
/// </summary>
private const int HashShift = 4;
/// <summary> /// <summary>
/// Mask used when shifting pixel values /// Mask used when shifting pixel values
/// </summary> /// </summary>
private static readonly int[] Masks = private static readonly int[] Masks =
{ {
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0b0,
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF 0b1,
0b11,
0b111,
0b1111,
0b11111,
0b111111,
0b1111111,
0b11111111,
0b111111111,
0b1111111111,
0b11111111111,
0b111111111111,
0b1111111111111,
0b11111111111111,
0b111111111111111,
0b1111111111111111
}; };
/// <summary> /// <summary>
@ -80,16 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private readonly byte[] accumulators = new byte[256]; private readonly byte[] accumulators = new byte[256];
/// <summary>
/// For dynamic table sizing
/// </summary>
private readonly int hsize = HashSize;
/// <summary>
/// The current position within the pixelArray.
/// </summary>
private int position;
/// <summary> /// <summary>
/// Number of bits/code /// Number of bits/code
/// </summary> /// </summary>
@ -177,15 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Encodes and compresses the indexed pixels to the stream. /// Encodes and compresses the indexed pixels to the stream.
/// </summary> /// </summary>
/// <param name="indexedPixels">The span of indexed pixels.</param> /// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
public void Encode(ReadOnlySpan<byte> indexedPixels, Stream stream) public void Encode(Buffer2D<byte> indexedPixels, Stream stream)
{ {
// Write "initial code size" byte // Write "initial code size" byte
stream.WriteByte((byte)this.initialCodeSize); stream.WriteByte((byte)this.initialCodeSize);
this.position = 0;
// Compress and write the pixel data // Compress and write the pixel data
this.Compress(indexedPixels, this.initialCodeSize + 1, stream); this.Compress(indexedPixels, this.initialCodeSize + 1, stream);
@ -199,10 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="bitCount">The number of bits</param> /// <param name="bitCount">The number of bits</param>
/// <returns>See <see cref="int"/></returns> /// <returns>See <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetMaxcode(int bitCount) private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1;
{
return (1 << bitCount) - 1;
}
/// <summary> /// <summary>
/// Add a character to the end of the current packet, and if it is 254 characters, /// Add a character to the end of the current packet, and if it is 254 characters,
@ -239,25 +244,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Reset the code table. /// Reset the code table.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResetCodeTable() private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1);
{
this.hashTable.GetSpan().Fill(-1);
}
/// <summary> /// <summary>
/// Compress the packets to the stream. /// Compress the packets to the stream.
/// </summary> /// </summary>
/// <param name="indexedPixels">The span of indexed pixels.</param> /// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
/// <param name="initialBits">The initial bits.</param> /// <param name="initialBits">The initial bits.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
private void Compress(ReadOnlySpan<byte> indexedPixels, int initialBits, Stream stream) private void Compress(Buffer2D<byte> indexedPixels, int initialBits, Stream stream)
{ {
int fcode;
int c;
int ent;
int hsizeReg;
int hshift;
// Set up the globals: globalInitialBits - initial number of bits // Set up the globals: globalInitialBits - initial number of bits
this.globalInitialBits = initialBits; this.globalInitialBits = initialBits;
@ -265,92 +261,82 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.clearFlag = false; this.clearFlag = false;
this.bitCount = this.globalInitialBits; this.bitCount = this.globalInitialBits;
this.maxCode = GetMaxcode(this.bitCount); this.maxCode = GetMaxcode(this.bitCount);
this.clearCode = 1 << (initialBits - 1); this.clearCode = 1 << (initialBits - 1);
this.eofCode = this.clearCode + 1; this.eofCode = this.clearCode + 1;
this.freeEntry = this.clearCode + 2; this.freeEntry = this.clearCode + 2;
this.accumulatorCount = 0; // Clear packet
this.accumulatorCount = 0; // clear packet this.ResetCodeTable(); // Clear hash table
ent = this.NextPixel(indexedPixels);
// TODO: PERF: It looks like hshift could be calculated once statically.
hshift = 0;
for (fcode = this.hsize; fcode < 65536; fcode *= 2)
{
++hshift;
}
hshift = 8 - hshift; // set hash code range bound
hsizeReg = this.hsize;
this.ResetCodeTable(); // clear hash table
this.Output(this.clearCode, stream); this.Output(this.clearCode, stream);
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan());
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan());
while (this.position < indexedPixels.Length) int entry = indexedPixels[0, 0];
{
c = this.NextPixel(indexedPixels);
fcode = (c << MaxBits) + ent; for (int y = 0; y < indexedPixels.Height; y++)
int i = (c << hshift) ^ ent /* = 0 */; {
ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y));
int offsetX = y == 0 ? 1 : 0;
if (Unsafe.Add(ref hashTableRef, i) == fcode) for (int x = offsetX; x < indexedPixels.Width; x++)
{ {
ent = Unsafe.Add(ref codeTableRef, i); int code = Unsafe.Add(ref rowSpanRef, x);
continue; int freeCode = (code << MaxBits) + entry;
} int hashIndex = (code << HashShift) ^ entry;
// Non-empty slot if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode)
if (Unsafe.Add(ref hashTableRef, i) >= 0)
{
int disp = 1;
if (i != 0)
{ {
disp = hsizeReg - i; entry = Unsafe.Add(ref codeTableRef, hashIndex);
continue;
} }
do // Non-empty slot
if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0)
{ {
if ((i -= disp) < 0) int disp = 1;
if (hashIndex != 0)
{
disp = HashSize - hashIndex;
}
do
{ {
i += hsizeReg; if ((hashIndex -= disp) < 0)
{
hashIndex += HashSize;
}
if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode)
{
entry = Unsafe.Add(ref codeTableRef, hashIndex);
break;
}
} }
while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0);
if (Unsafe.Add(ref hashTableRef, i) == fcode) if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode)
{ {
ent = Unsafe.Add(ref codeTableRef, i); continue;
break;
} }
} }
while (Unsafe.Add(ref hashTableRef, i) >= 0);
if (Unsafe.Add(ref hashTableRef, i) == fcode) this.Output(entry, stream);
entry = code;
if (this.freeEntry < MaxMaxCode)
{ {
continue; Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable
Unsafe.Add(ref hashTableRef, hashIndex) = freeCode;
}
else
{
this.ClearBlock(stream);
} }
}
this.Output(ent, stream);
ent = c;
if (this.freeEntry < MaxMaxCode)
{
Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable
Unsafe.Add(ref hashTableRef, i) = fcode;
}
else
{
this.ClearBlock(stream);
} }
} }
// Put out the final code. // Output the final code.
this.Output(ent, stream); this.Output(entry, stream);
this.Output(this.eofCode, stream); this.Output(this.eofCode, stream);
} }
@ -366,19 +352,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.accumulatorCount = 0; this.accumulatorCount = 0;
} }
/// <summary>
/// Reads the next pixel from the image.
/// </summary>
/// <param name="indexedPixels">The span of indexed pixels.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int NextPixel(ReadOnlySpan<byte> indexedPixels)
{
return indexedPixels[this.position++] & 0xFF;
}
/// <summary> /// <summary>
/// Output the current code to the stream. /// Output the current code to the stream.
/// </summary> /// </summary>

25
src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -13,10 +14,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class IndexedImageFrame<TPixel> : IDisposable public sealed class IndexedImageFrame<TPixel> : IPixelSource, IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private IMemoryOwner<byte> pixelsOwner; private Buffer2D<byte> pixelBuffer;
private IMemoryOwner<TPixel> paletteOwner; private IMemoryOwner<TPixel> paletteOwner;
private bool isDisposed; private bool isDisposed;
@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.pixelsOwner = configuration.MemoryAllocator.AllocateManagedByteBuffer(width * height); this.pixelBuffer = configuration.MemoryAllocator.Allocate2D<byte>(width, height);
// Copy the palette over. We want the lifetime of this frame to be independant of any palette source. // Copy the palette over. We want the lifetime of this frame to be independant of any palette source.
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(palette.Length); this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(palette.Length);
@ -67,16 +68,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public ReadOnlyMemory<TPixel> Palette { get; } public ReadOnlyMemory<TPixel> Palette { get; }
/// <summary> /// <inheritdoc/>
/// Gets the pixels of this <see cref="IndexedImageFrame{TPixel}"/>. Buffer2D<byte> IPixelSource.PixelBuffer => this.pixelBuffer;
/// </summary>
/// <returns>The <see cref="ReadOnlySpan{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D<byte>
/// <summary> /// <summary>
/// Gets the representation of the pixels as a <see cref="ReadOnlySpan{T}"/> of contiguous memory /// Gets the representation of the pixels as a <see cref="ReadOnlySpan{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row. /// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary> /// </summary>
/// <param name="rowIndex">The row index in the pixel buffer.</param> /// <param name="rowIndex">The row index in the pixel buffer.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns> /// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
@ -87,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// <para> /// <para>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory /// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row. /// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </para> /// </para>
/// <para> /// <para>
/// Note: Values written to this span are not sanitized against the palette length. /// Note: Values written to this span are not sanitized against the palette length.
@ -98,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The pixel row as a <see cref="Span{T}"/>.</returns> /// <returns>The pixel row as a <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetWritablePixelRowSpanUnsafe(int rowIndex) public Span<byte> GetWritablePixelRowSpanUnsafe(int rowIndex)
=> this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width); => this.pixelBuffer.GetRowSpan(rowIndex);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -106,9 +103,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed) if (!this.isDisposed)
{ {
this.isDisposed = true; this.isDisposed = true;
this.pixelsOwner.Dispose(); this.pixelBuffer.Dispose();
this.paletteOwner.Dispose(); this.paletteOwner.Dispose();
this.pixelsOwner = null; this.pixelBuffer = null;
this.paletteOwner = null; this.paletteOwner = null;
} }
} }

7
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -68,21 +68,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelBufferSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
int width = this.bounds.Width;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> row = this.source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width; ReadOnlySpan<byte> quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
int i = rowStart + x - offsetX; row[x] = paletteSpan[quantizedRow[x - offsetX]];
row[x] = paletteSpan[quantizedPixelSpan[i]];
} }
} }
} }

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

@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests
using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]);
} }
} }
} }
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests
using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]);
} }
} }
} }

38
tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

@ -24,10 +24,11 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelBufferSpan().Length); Assert.Equal(1, result.Width);
Assert.Equal(1, result.Height);
Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelBufferSpan()[0]); Assert.Equal(0, result.GetPixelRowSpan(0)[0]);
} }
[Fact] [Fact]
@ -43,10 +44,11 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelBufferSpan().Length); Assert.Equal(1, result.Width);
Assert.Equal(1, result.Height);
Assert.Equal(default, result.Palette.Span[0]); Assert.Equal(default, result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelBufferSpan()[0]); Assert.Equal(0, result.GetPixelRowSpan(0)[0]);
} }
[Fact] [Fact]
@ -88,7 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(256, result.Palette.Length); Assert.Equal(256, result.Palette.Length);
Assert.Equal(256, result.GetPixelBufferSpan().Length); Assert.Equal(1, result.Width);
Assert.Equal(256, result.Height);
var actualImage = new Image<Rgba32>(1, 256); var actualImage = new Image<Rgba32>(1, 256);
@ -97,17 +100,18 @@ namespace SixLabors.ImageSharp.Tests.Quantization
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.GetPixelRowSpan(y); Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan(); ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelRowSpan(y);
int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++) for (int x = 0; x < actualImage.Width; x++)
{ {
int i = x + yy; row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])];
} }
} }
Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); for (int y = 0; y < image.Height; y++)
{
Assert.True(image.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y)));
}
} }
[Theory] [Theory]
@ -155,25 +159,27 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using (IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(256, result.GetPixelBufferSpan().Length); Assert.Equal(1, result.Width);
Assert.Equal(256, result.Height);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
int paletteCount = paletteSpan.Length - 1; int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.GetPixelRowSpan(y); Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan(); ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelRowSpan(y);
int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++) for (int x = 0; x < actualImage.Width; x++)
{ {
int i = x + yy; row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])];
} }
} }
} }
Assert.True(expectedImage.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); for (int y = 0; y < expectedImage.Height; y++)
{
Assert.True(expectedImage.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y)));
}
} }
} }
} }

Loading…
Cancel
Save