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.
using SixLabors.ImageSharp.Memory;
@ -6,6 +6,17 @@ using SixLabors.ImageSharp.PixelFormats;
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>
/// Encapsulates the basic properties and methods required to manipulate images.
/// </summary>
@ -18,4 +29,4 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
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.
using System;
@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Restricts a <see cref="byte"/> to be within a specified range.
/// </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="max">The maximum value. If value is greater than max, max will be returned.</param>
/// <returns>
@ -137,4 +137,4 @@ namespace SixLabors.ImageSharp
return value;
}
}
}
}

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

@ -353,7 +353,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null;
IManagedByteBuffer indices = null;
Buffer2D<byte> indices = null;
try
{
// 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);
}
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());
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
this.SkipBlock();
@ -383,16 +383,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Reads the frame indices marking the color to use for each pixel.
/// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <param name="indices">The pixel array to write to.</param>
/// <param name="indices">The 2D pixel buffer to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span<byte> indices)
private void ReadFrameIndices(Buffer2D<byte> indices)
{
int dataSize = this.stream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream))
{
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
}
using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream);
lzwDecoder.DecodePixels(dataSize, indices);
}
/// <summary>
@ -404,10 +401,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</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>
{
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
@ -440,13 +436,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.RestoreToBackground(imageFrame);
}
int i = 0;
int interlacePass = 0; // The interlace pass
int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
int descriptorTop = descriptor.Top;
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.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
@ -482,35 +485,29 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY));
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
if (!transFlag)
{
// #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);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
i++;
}
}
else
{
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
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);
if (transIndex != index)
{
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
}
i++;
}
}
}

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

@ -6,6 +6,7 @@ using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -471,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel<TPixel>
{
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>
/// Decodes and decompresses all pixel indices from the stream.
/// </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="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));
// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;
// 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 suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
ref byte pixelsRef = ref MemoryMarshal.GetReference(pixels);
for (code = 0; code < clearCode; 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)
{
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y));
rowMax = (y * width) + width;
}
if (top == 0)
{
if (bits < codeSize)
@ -209,7 +220,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
top--;
// 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.
using System;
@ -41,13 +41,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private const int HashSize = 5003;
/// <summary>
/// The amount to shift each code.
/// </summary>
private const int HashShift = 4;
/// <summary>
/// Mask used when shifting pixel values
/// </summary>
private static readonly int[] Masks =
{
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
0b0,
0b1,
0b11,
0b111,
0b1111,
0b11111,
0b111111,
0b1111111,
0b11111111,
0b111111111,
0b1111111111,
0b11111111111,
0b111111111111,
0b1111111111111,
0b11111111111111,
0b111111111111111,
0b1111111111111111
};
/// <summary>
@ -80,16 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
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>
/// Number of bits/code
/// </summary>
@ -177,15 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Encodes and compresses the indexed pixels to the stream.
/// </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>
public void Encode(ReadOnlySpan<byte> indexedPixels, Stream stream)
public void Encode(Buffer2D<byte> indexedPixels, Stream stream)
{
// Write "initial code size" byte
stream.WriteByte((byte)this.initialCodeSize);
this.position = 0;
// Compress and write the pixel data
this.Compress(indexedPixels, this.initialCodeSize + 1, stream);
@ -199,10 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="bitCount">The number of bits</param>
/// <returns>See <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetMaxcode(int bitCount)
{
return (1 << bitCount) - 1;
}
private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1;
/// <summary>
/// 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.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResetCodeTable()
{
this.hashTable.GetSpan().Fill(-1);
}
private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1);
/// <summary>
/// Compress the packets to the stream.
/// </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="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
this.globalInitialBits = initialBits;
@ -265,92 +261,82 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.clearFlag = false;
this.bitCount = this.globalInitialBits;
this.maxCode = GetMaxcode(this.bitCount);
this.clearCode = 1 << (initialBits - 1);
this.eofCode = this.clearCode + 1;
this.freeEntry = this.clearCode + 2;
this.accumulatorCount = 0; // Clear packet
this.accumulatorCount = 0; // clear packet
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.ResetCodeTable(); // Clear hash table
this.Output(this.clearCode, stream);
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan());
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan());
while (this.position < indexedPixels.Length)
{
c = this.NextPixel(indexedPixels);
int entry = indexedPixels[0, 0];
fcode = (c << MaxBits) + ent;
int i = (c << hshift) ^ ent /* = 0 */;
for (int y = 0; y < indexedPixels.Height; y++)
{
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);
continue;
}
int code = Unsafe.Add(ref rowSpanRef, x);
int freeCode = (code << MaxBits) + entry;
int hashIndex = (code << HashShift) ^ entry;
// Non-empty slot
if (Unsafe.Add(ref hashTableRef, i) >= 0)
{
int disp = 1;
if (i != 0)
if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode)
{
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);
break;
continue;
}
}
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.
this.Output(ent, stream);
// Output the final code.
this.Output(entry, stream);
this.Output(this.eofCode, stream);
}
@ -366,19 +352,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
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>
/// Output the current code to the stream.
/// </summary>

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

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
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.
/// </summary>
/// <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>
{
private IMemoryOwner<byte> pixelsOwner;
private Buffer2D<byte> pixelBuffer;
private IMemoryOwner<TPixel> paletteOwner;
private bool isDisposed;
@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration;
this.Width = width;
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.
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(palette.Length);
@ -67,16 +68,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public ReadOnlyMemory<TPixel> Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="IndexedImageFrame{TPixel}"/>.
/// </summary>
/// <returns>The <see cref="ReadOnlySpan{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D<byte>
/// <inheritdoc/>
Buffer2D<byte> IPixelSource.PixelBuffer => this.pixelBuffer;
/// <summary>
/// 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>
/// <param name="rowIndex">The row index in the pixel buffer.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
@ -87,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// <para>
/// 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>
/// 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>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetWritablePixelRowSpanUnsafe(int rowIndex)
=> this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width);
=> this.pixelBuffer.GetRowSpan(rowIndex);
/// <inheritdoc/>
public void Dispose()
@ -106,9 +103,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed)
{
this.isDisposed = true;
this.pixelsOwner.Dispose();
this.pixelBuffer.Dispose();
this.paletteOwner.Dispose();
this.pixelsOwner = null;
this.pixelBuffer = 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)]
public void Invoke(in RowInterval rows)
{
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelBufferSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
int width = this.bounds.Width;
for (int y = rows.Min; y < rows.Max; 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++)
{
int i = rowStart + x - offsetX;
row[x] = paletteSpan[quantizedPixelSpan[i]];
row[x] = paletteSpan[quantizedRow[x - offsetX]];
}
}
}

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

@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests
using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
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()))
{
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());
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(0, result.GetPixelBufferSpan()[0]);
Assert.Equal(0, result.GetPixelRowSpan(0)[0]);
}
[Fact]
@ -43,10 +44,11 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
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(0, result.GetPixelBufferSpan()[0]);
Assert.Equal(0, result.GetPixelRowSpan(0)[0]);
}
[Fact]
@ -88,7 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
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);
@ -97,17 +100,18 @@ namespace SixLabors.ImageSharp.Tests.Quantization
for (int y = 0; y < actualImage.Height; y++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width;
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelRowSpan(y);
for (int x = 0; x < actualImage.Width; x++)
{
int i = x + yy;
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])];
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
}
}
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]
@ -155,25 +159,27 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using (IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
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;
int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width;
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelRowSpan(y);
for (int x = 0; x < actualImage.Width; x++)
{
int i = x + yy;
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])];
row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])];
}
}
}
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