Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
b2a28d1f3c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  2. 170
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 69
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  4. 23
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  6. 155
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  7. 14
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  8. 157
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  9. 92
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  10. 4
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  11. 12
      src/ImageSharp/Memory/Buffer2D{T}.cs
  12. 8
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  13. 30
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  14. 21
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  15. 4
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  16. 23
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  17. 76
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  18. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  19. 6
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  20. 28
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  21. 241
      tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs
  22. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  23. 2
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  24. 8
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  25. 14
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  26. 2
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

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

@ -72,12 +72,6 @@ namespace SixLabors.ImageSharp
}
}
public static void Read(this Stream stream, IManagedByteBuffer buffer)
=> stream.Read(buffer.Array, 0, buffer.Length());
public static void Write(this Stream stream, IManagedByteBuffer buffer)
=> stream.Write(buffer.Array, 0, buffer.Length());
#if !SUPPORTS_SPAN_STREAM
// This is a port of the CoreFX implementation and is MIT Licensed:
// https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742

170
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -928,20 +928,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 3);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding))
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
}
@ -957,20 +956,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
}
@ -987,87 +985,85 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
using (IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
using IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width);
Span<byte> rowSpan = row.GetSpan();
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
this.stream.Read(rowSpan);
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
bgraRowSpan,
width);
if (hasAlpha)
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
return;
break;
}
}
// Slow path. We need to set each alpha component value to fully opaque.
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
return;
}
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
bgraRowSpan,
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
}
}

69
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -257,7 +257,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary>
/// Writes the 32bit color palette to the stream.
@ -268,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -294,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
int width = pixels.Width;
int rowBytesWithoutPadding = width * 3;
using (IManagedByteBuffer row = this.AllocateRow(width, 3))
using IMemoryOwner<byte> row = this.AllocateRow(width, 3);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
width);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
width);
stream.Write(rowSpan);
}
}
@ -320,20 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
int width = pixels.Width;
int rowBytesWithoutPadding = width * 2;
using (IManagedByteBuffer row = this.AllocateRow(width, 2))
using IMemoryOwner<byte> row = this.AllocateRow(width, 2);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
stream.Write(rowSpan);
}
}

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

@ -7,7 +7,6 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The pixel sampling strategy for global quantization.
/// </summary>
private IPixelSamplingStrategy pixelSamplingStrategy;
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The palette quantizer can reuse the same pixel map across multiple frames
// since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default;
bool pixelMapHasValue = false;
PaletteQuantizer<TPixel> paletteFrameQuantizer = default;
bool quantizerInitialized = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
@ -166,22 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
if (!pixelMapHasValue)
if (!quantizerInitialized)
{
pixelMapHasValue = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
quantizerInitialized = true;
paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette);
}
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap, true);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
if (pixelMapHasValue)
{
pixelMap.Dispose();
}
paletteFrameQuantizer.Dispose();
}
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
@ -310,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
ratio = (byte)(((1 / vr) * 64) - 15);
ratio = (byte)((1 / vr * 64) - 15);
}
}
}
@ -354,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
for (var i = 0; i < metadata.Comments.Count; i++)
for (int i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;

8
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -5,8 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// A compiled look-up table representation of a huffmanSpec.
/// Each value maps to a uint32 of which the 8 most significant bits hold the
/// codeword size in bits and the 24 least significant bits hold the codeword.
/// Each value maps to a int32 of which the 24 most significant bits hold the
/// codeword in bits and the 8 least significant bits hold the codeword size.
/// The maximum codeword size is 16 bits.
/// </summary>
internal readonly struct HuffmanLut
@ -51,10 +51,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
for (int i = 0; i < spec.Count.Length; i++)
{
int bits = (i + 1) << 24;
int len = i + 1;
for (int j = 0; j < spec.Count[i]; j++)
{
this.Values[spec.Values[k]] = bits | code;
this.Values[spec.Values[k]] = len | (code << 8);
code++;
k++;
}

155
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -3,6 +3,10 @@
using System.IO;
using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class HuffmanScanEncoder
{
/// <summary>
/// Compiled huffman tree to encode given values.
/// </summary>
/// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks>
private HuffmanLut[] huffmanTables;
/// <summary>
/// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count).
/// </summary>
@ -64,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
@ -122,6 +134,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
@ -187,6 +201,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
@ -243,16 +259,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig);
int dc = (int)refTemp2[0];
// Emit the DC delta.
this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC);
int dc = (int)refTemp2[0];
this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC);
// Emit the AC components.
int h = (2 * (int)index) + 1;
int runLength = 0;
int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
for (int zig = 1; zig < Block8x8F.Size; zig++)
int runLength = 0;
int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2);
for (int zig = 1; zig <= lastValuableIndex; zig++)
{
int ac = (int)refTemp2[zig];
@ -264,18 +280,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
while (runLength > 15)
{
this.EmitHuff(h, 0xf0);
this.EmitHuff(acHuffTable, 0xf0);
runLength -= 16;
}
this.EmitHuffRLE(h, runLength, ac);
this.EmitHuffRLE(acHuffTable, runLength, ac);
runLength = 0;
}
}
if (runLength > 0)
// if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
// this can be done for any number of trailing zeros, even when all 63 ac values are zero
// (Block8x8F.Size - 1) == 63 - last index of the mcu elements
if (lastValuableIndex != Block8x8F.Size - 1)
{
this.EmitHuff(h, 0x00);
this.EmitHuff(acHuffTable, 0x00);
}
return dc;
@ -306,6 +325,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
byte b = (byte)(bits >> 24);
this.emitBuffer[this.emitLen++] = b;
// Adding stuff byte
// This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte)
// Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker
if (b == byte.MaxValue)
{
this.emitBuffer[this.emitLen++] = byte.MinValue;
@ -334,23 +357,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Emits the given value with the given Huffman encoder.
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="table">Compiled Huffman spec values.</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int index, int value)
private void EmitHuff(int[] table, int value)
{
int x = HuffmanLut.TheHuffmanLut[index].Values[value];
this.Emit(x & ((1 << 24) - 1), x >> 24);
int x = table[value];
this.Emit(x >> 8, x & 0xff);
}
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitDirectCurrentTerm(int[] table, int value)
{
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
int bt = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(table, bt);
if (bt > 0)
{
this.Emit(b & ((1 << bt) - 1), bt);
}
}
/// <summary>
/// Emits a run of runLength copies of value encoded with the given Huffman encoder.
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="table">Compiled Huffman spec values.</param>
/// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int index, int runLength, int value)
private void EmitHuffRLE(int[] table, int runLength, int value)
{
int a = value;
int b = value;
@ -362,11 +405,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int bt = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(index, (runLength << 4) | bt);
if (bt > 0)
{
this.Emit(b & ((1 << bt) - 1), bt);
}
this.EmitHuff(table, (runLength << 4) | bt);
this.Emit(b & ((1 << bt) - 1), bt);
}
/// <summary>
@ -380,11 +420,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
if (padBitsCount != 0)
{
this.Emit((1 << padBitsCount) - 1, padBitsCount);
}
// flush remaining bytes
if (this.emitLen != 0)
{
this.target.Write(this.emitBuffer, 0, this.emitLen);
}
}
@ -393,11 +428,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding.
/// </summary>
/// <remarks>
/// This method returns 0 for input value 0. This is done specificaly for huffman encoding
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding.
/// </remarks>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetHuffmanEncodingLength(uint value)
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetHuffmanEncodingLength(uint value)
{
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS
@ -423,5 +458,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
return Numerics.Log2(value << 1);
#endif
}
/// <summary>
/// Returns index of the last non-zero element in given mcu block.
/// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support.
/// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop.
/// This method is guaranteed to return either -1 or 0 if all elements are zero.
/// </summary>
/// <remarks>
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding.
/// </remarks>
/// <param name="mcu">Mcu block.</param>
/// <returns>Index of the last non-zero element.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetLastValuableElementIndex(ref Block8x8F mcu)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
Vector256<int> zero8 = Vector256<int>.Zero;
ref Vector256<float> mcuStride = ref mcu.V0;
for (int i = 7; i >= 0; i--)
{
int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte());
// we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros
if (areEqual != equalityMask)
{
// last index in the stride, we go from the end to the start of the stride
int startIndex = i * 8;
int index = startIndex + 7;
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu);
while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
// this implementation will return -1 if all ac components are zero and dc are zero
return index;
}
}
return -1;
}
else
#endif
{
int index = Block8x8F.Size - 1;
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu);
while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
// this implementation will return 0 if all ac components and dc are zero
return index;
}
}
}
}

14
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -24,9 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
// Luminance AC.
new HuffmanSpec(
new byte[]
{
@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
}),
// Chrominance DC.
new HuffmanSpec(
new byte[]
{
@ -132,4 +136,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.Values = values;
}
}
}
}

157
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -373,22 +373,21 @@ namespace SixLabors.ImageSharp.Formats.Tga
return;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
{
bool invertY = InvertY(origin);
if (invertY)
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadL8Row(width, pixels, row, y);
}
this.ReadL8Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadL8Row(width, pixels, row, y);
}
this.ReadL8Row(width, pixels, rowSpan, y);
}
}
}
@ -406,58 +405,57 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
TPixel color = default;
bool invertX = InvertX(origin);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0);
Span<byte> rowSpan = row.GetSpan();
if (invertX)
{
for (int x = width - 1; x >= 0; x--)
{
this.currentStream.Read(this.scratchBuffer, 0, 2);
if (!this.hasAlpha)
{
this.scratchBuffer[1] |= 1 << 7;
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref this.scratchBuffer[0]));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref this.scratchBuffer[0]));
}
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
pixelSpan[x] = color;
}
}
else
if (invertX)
{
for (int x = width - 1; x >= 0; x--)
{
this.currentStream.Read(row);
Span<byte> rowSpan = row.GetSpan();
this.currentStream.Read(this.scratchBuffer, 0, 2);
if (!this.hasAlpha)
{
// We need to set the alpha component value to fully opaque.
for (int x = 1; x < rowSpan.Length; x += 2)
{
rowSpan[x] |= 1 << 7;
}
this.scratchBuffer[1] |= 1 << 7;
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width);
color.FromLa16(Unsafe.As<byte, La16>(ref this.scratchBuffer[0]));
}
else
{
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width);
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref this.scratchBuffer[0]));
}
pixelSpan[x] = color;
}
}
else
{
this.currentStream.Read(rowSpan);
if (!this.hasAlpha)
{
// We need to set the alpha component value to fully opaque.
for (int x = 1; x < rowSpan.Length; x += 2)
{
rowSpan[x] |= 1 << 7;
}
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width);
}
else
{
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width);
}
}
}
}
@ -490,23 +488,22 @@ namespace SixLabors.ImageSharp.Formats.Tga
return;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
{
bool invertY = InvertY(origin);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
if (invertY)
{
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgr24Row(width, pixels, row, y);
}
this.ReadBgr24Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadBgr24Row(width, pixels, row, y);
}
this.ReadBgr24Row(width, pixels, rowSpan, y);
}
}
}
@ -526,21 +523,21 @@ namespace SixLabors.ImageSharp.Formats.Tga
bool invertX = InvertX(origin);
if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX)
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0);
Span<byte> rowSpan = row.GetSpan();
if (InvertY(origin))
{
if (InvertY(origin))
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgra32Row(width, pixels, row, y);
}
this.ReadBgra32Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadBgra32Row(width, pixels, row, y);
}
this.ReadBgra32Row(width, pixels, rowSpan, y);
}
}
@ -652,12 +649,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -679,12 +676,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -698,12 +695,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

92
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
@ -258,7 +259,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
return equalPixelCount;
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
/// <summary>
/// Writes the 8bit pixels uncompressed to the stream.
@ -269,18 +271,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 1);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -293,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 2);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -317,18 +319,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 3);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -341,18 +343,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}

4
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Memory
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary>
internal static unsafe void CopyColumns<T>(
internal static unsafe void DangerousCopyColumns<T>(
this Buffer2D<T> buffer,
int sourceIndex,
int destIndex,
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory
int dOffset = destIndex * elementSize;
long count = columnCount * elementSize;
Span<byte> span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span);
Span<byte> span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span);
fixed (byte* ptr = span)
{

12
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -168,10 +168,10 @@ namespace SixLabors.ImageSharp.Memory
/// Thrown when the backing group is discontiguous.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
internal Span<T> GetSingleSpan()
internal Span<T> DangerousGetSingleSpan()
{
// TODO: If we need a public version of this method, we need to cache the non-fast Memory<T> of this.MemoryGroup
return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow();
return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow();
}
/// <summary>
@ -183,10 +183,10 @@ namespace SixLabors.ImageSharp.Memory
/// Thrown when the backing group is discontiguous.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
internal Memory<T> GetSingleMemory()
internal Memory<T> DangerousGetSingleMemory()
{
// TODO: If we need a public version of this method, we need to cache the non-fast Memory<T> of this.MemoryGroup
return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow();
return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow();
}
/// <summary>
@ -203,10 +203,10 @@ namespace SixLabors.ImageSharp.Memory
private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetSingleMemorySlow() => this.FastMemoryGroup.Single();
private Memory<T> DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single();
[MethodImpl(InliningOptions.ColdPath)]
private Span<T> GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span;
private Span<T> DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span;
[MethodImpl(InliningOptions.ColdPath)]
private ref T GetElementSlow(int x, int y)

8
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -67,21 +67,21 @@ namespace SixLabors.ImageSharp.Memory
}
/// <summary>
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea).
/// Allocates padded buffers. Generally used by encoder/decoders.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
/// <param name="width">Pixel count in the row</param>
/// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB.</param>
/// <param name="paddingInBytes">The padding.</param>
/// <returns>A <see cref="IManagedByteBuffer"/>.</returns>
internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
/// <returns>A <see cref="IMemoryOwner{Byte}"/>.</returns>
internal static IMemoryOwner<byte> AllocatePaddedPixelRowBuffer(
this MemoryAllocator memoryAllocator,
int width,
int pixelSizeInBytes,
int paddingInBytes)
{
int length = (width * pixelSizeInBytes) + paddingInBytes;
return memoryAllocator.AllocateManagedByteBuffer(length);
return memoryAllocator.Allocate<byte>(length);
}
/// <summary>

30
src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -18,20 +18,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// This class is not threadsafe and should not be accessed in parallel.
/// Doing so will result in non-idempotent results.
/// </para>
internal readonly struct EuclideanPixelMap<TPixel> : IDisposable
internal sealed class EuclideanPixelMap<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Rgba32[] rgbaPalette;
private Rgba32[] rgbaPalette;
private readonly ColorDistanceCache cache;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="palette">The color palette to map from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{
this.configuration = configuration;
this.Palette = palette;
this.rgbaPalette = new Rgba32[palette.Length];
this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
@ -46,6 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
[MethodImpl(InliningOptions.ShortMethod)]
private set;
}
/// <summary>
@ -72,6 +76,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return index;
}
/// <summary>
/// Clears the map, resetting it to use the given palette.
/// </summary>
/// <param name="palette">The color palette to map from.</param>
public void Clear(ReadOnlyMemory<TPixel> palette)
{
this.Palette = palette;
this.rgbaPalette = new Rgba32[palette.Length];
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette);
this.cache.Clear();
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
{
@ -177,6 +193,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return match > -1;
}
/// <summary>
/// Clears the cache resetting each entry to empty.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear() => this.table.GetSpan().Fill(-1);
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
=> (r << ((IndexBits << 1) + IndexAlphaBits))

21
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@ -48,7 +47,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree = new Octree(this.bitDepth);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false;
@ -112,15 +110,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.Palletize(paletteSpan, max, ref paletteIndex);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
// When called multiple times by QuantizerUtilities.BuildPalette
// this prevents memory churn caused by reallocation.
if (this.pixelMap is null)
{
this.pixelMap.Dispose();
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
}
else
{
this.pixelMap.Clear(result);
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
this.palette = result;
}
@ -153,9 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed)
{
this.isDisposed = true;
this.paletteOwner.Dispose();
this.paletteOwner?.Dispose();
this.paletteOwner = null;
this.pixelMap.Dispose();
this.pixelMap?.Dispose();
this.pixelMap = null;
}
}

4
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -58,9 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var palette = new TPixel[length];
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteQuantizer<TPixel>(configuration, options, pixelMap, false);
return new PaletteQuantizer<TPixel>(configuration, options, palette);
}
}
}

23
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -16,32 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly bool leaveMap;
private EuclideanPixelMap<TPixel> pixelMap;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="pixelMap">The pixel map for looking up color matches from a predefined palette.</param>
/// <param name="leaveMap">
/// <see langword="true"/> to leave the pixel map undisposed after disposing the <see cref="PaletteQuantizer{TPixel}"/> object; otherwise, <see langword="false"/>.
/// </param>
/// <param name="palette">The palette to use.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteQuantizer(
Configuration configuration,
QuantizerOptions options,
EuclideanPixelMap<TPixel> pixelMap,
bool leaveMap)
public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> palette)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
this.pixelMap = pixelMap;
this.leaveMap = leaveMap;
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
}
/// <inheritdoc/>
@ -72,10 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
public void Dispose()
{
if (!this.leaveMap)
{
this.pixelMap.Dispose();
}
this.pixelMap?.Dispose();
this.pixelMap = null;
}
}
}

76
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private int maxColors;
private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@ -97,7 +96,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.colorCube = new Box[this.maxColors];
this.isDisposed = false;
this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null);
}
@ -147,15 +145,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
if (this.isDithering)
{
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
// When called multiple times by QuantizerUtilities.BuildPalette
// this prevents memory churn caused by reallocation.
if (this.pixelMap is null)
{
this.pixelMap.Dispose();
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
}
else
{
this.pixelMap.Clear(result);
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
}
this.palette = result;
@ -201,7 +200,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = null;
this.tagsOwner = null;
this.paletteOwner = null;
this.pixelMap.Dispose();
this.pixelMap?.Dispose();
this.pixelMap = null;
}
}
@ -215,16 +215,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The index.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
{
return (r << ((IndexBits * 2) + IndexAlphaBits))
+ (r << (IndexBits + IndexAlphaBits + 1))
+ (g << (IndexBits + IndexAlphaBits))
+ (r << (IndexBits * 2))
+ (r << (IndexBits + 1))
+ (g << IndexBits)
+ ((r + g + b) << IndexAlphaBits)
+ r + g + b + a;
}
=> (r << ((IndexBits * 2) + IndexAlphaBits))
+ (r << (IndexBits + IndexAlphaBits + 1))
+ (g << (IndexBits + IndexAlphaBits))
+ (r << (IndexBits * 2))
+ (r << (IndexBits + 1))
+ (g << IndexBits)
+ ((r + g + b) << IndexAlphaBits)
+ r + g + b + a;
/// <summary>
/// Computes sum over a box of any given statistic.
@ -233,24 +231,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="moments">The moment.</param>
/// <returns>The result.</returns>
private static Moment Volume(ref Box cube, ReadOnlySpan<Moment> moments)
{
return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
}
=> moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
/// <summary>
/// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction).
@ -835,7 +831,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public int Volume;
/// <inheritdoc/>
public readonly override bool Equals(object obj)
public override readonly bool Equals(object obj)
=> obj is Box box
&& this.Equals(box);
@ -852,7 +848,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
&& this.Volume == other.Volume;
/// <inheritdoc/>
public readonly override int GetHashCode()
public override readonly int GetHashCode()
{
HashCode hash = default;
hash.Add(this.RMin);

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.DestinationLength = destinationLength;
this.MaxDiameter = (radius * 2) + 1;
this.data = memoryAllocator.Allocate2D<float>(this.MaxDiameter, bufferHeight, AllocationOptions.Clean);
this.pinHandle = this.data.GetSingleMemory().Pin();
this.pinHandle = this.data.DangerousGetSingleMemory().Pin();
this.kernels = new ResizeKernel[destinationLength];
this.tempValues = new double[this.MaxDiameter];
}

6
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Span<Vector4> tempColSpan = this.tempColumnBuffer.GetSpan();
// When creating transposedFirstPassBuffer, we made sure it's contiguous:
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
for (int y = rowInterval.Min; y < rowInterval.Max; y++)
{
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Copy previous bottom band to the new top:
// (rows <--> columns, because the buffer is transposed)
this.transposedFirstPassBuffer.CopyColumns(
this.transposedFirstPassBuffer.DangerousCopyColumns(
this.workerHeight - this.windowBandHeight,
0,
this.windowBandHeight);
@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private void CalculateFirstPassValues(RowInterval calculationInterval)
{
Span<Vector4> tempRowSpan = this.tempRowBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
{

28
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -114,21 +114,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT [AttachedDebugger]
[Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
| Method | Quality | Mean | Error | StdDev | Ratio | RatioSD |
|---------------------------- |-------- |---------:|---------:|---------:|------:|--------:|
| 'System.Drawing Jpeg 4:2:0' | 75 | 30.60 ms | 0.496 ms | 0.464 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg 4:2:0' | 75 | 29.86 ms | 0.350 ms | 0.311 ms | 0.98 | 0.02 |
| 'ImageSharp Jpeg 4:4:4' | 75 | 45.36 ms | 0.899 ms | 1.036 ms | 1.48 | 0.05 |
| | | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 90 | 34.05 ms | 0.669 ms | 0.687 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg 4:2:0' | 90 | 37.26 ms | 0.706 ms | 0.660 ms | 1.10 | 0.03 |
| 'ImageSharp Jpeg 4:4:4' | 90 | 52.54 ms | 0.579 ms | 0.514 ms | 1.55 | 0.04 |
| | | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 100 | 39.36 ms | 0.267 ms | 0.237 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg 4:2:0' | 100 | 42.44 ms | 0.410 ms | 0.383 ms | 1.08 | 0.01 |
| 'ImageSharp Jpeg 4:4:4' | 100 | 70.88 ms | 0.508 ms | 0.450 ms | 1.80 | 0.02 |
| Method | Quality | Mean | Error | StdDev | Ratio |
|---------------------------- |-------- |---------:|---------:|---------:|------:|
| 'System.Drawing Jpeg 4:2:0' | 75 | 29.41 ms | 0.108 ms | 0.096 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 75 | 26.30 ms | 0.131 ms | 0.109 ms | 0.89 |
| 'ImageSharp Jpeg 4:4:4' | 75 | 36.70 ms | 0.303 ms | 0.269 ms | 1.25 |
| | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 90 | 32.67 ms | 0.226 ms | 0.211 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 90 | 33.56 ms | 0.237 ms | 0.222 ms | 1.03 |
| 'ImageSharp Jpeg 4:4:4' | 90 | 44.82 ms | 0.250 ms | 0.234 ms | 1.37 |
| | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 100 | 39.06 ms | 0.233 ms | 0.218 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 100 | 40.23 ms | 0.225 ms | 0.277 ms | 1.03 |
| 'ImageSharp Jpeg 4:4:4' | 100 | 63.35 ms | 0.486 ms | 0.431 ms | 1.62 |
*/

241
tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs

@ -0,0 +1,241 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public class HuffmanScanEncoderTests
{
private ITestOutputHelper Output { get; }
public HuffmanScanEncoderTests(ITestOutputHelper output)
{
this.Output = output;
}
private static int GetHuffmanEncodingLength_Reference(uint number)
{
int bits = 0;
if (number > 32767)
{
number >>= 16;
bits += 16;
}
if (number > 127)
{
number >>= 8;
bits += 8;
}
if (number > 7)
{
number >>= 4;
bits += 4;
}
if (number > 1)
{
number >>= 2;
bits += 2;
}
if (number > 0)
{
bits++;
}
return bits;
}
[Fact]
public void GetHuffmanEncodingLength_Zero()
{
int expected = 0;
int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void GetHuffmanEncodingLength_Random(int seed)
{
int maxNumber = 1 << 16;
var rng = new Random(seed);
for (int i = 0; i < 1000; i++)
{
uint number = (uint)rng.Next(0, maxNumber);
int expected = GetHuffmanEncodingLength_Reference(number);
int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number);
Assert.Equal(expected, actual);
}
}
[Fact]
public void GetLastValuableElementIndex_AllZero()
{
static void RunTest()
{
Block8x8F data = default;
int expectedLessThan = 1;
int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data);
Assert.True(actual < expectedLessThan);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Fact]
public void GetLastValuableElementIndex_AllNonZero()
{
static void RunTest()
{
Block8x8F data = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
data[i] = 10;
}
int expected = Block8x8F.Size - 1;
int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data);
Assert.Equal(expected, actual);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void GetLastValuableElementIndex_RandomFilledSingle(int seed)
{
static void RunTest(string seedSerialized)
{
int seed = FeatureTestRunner.Deserialize<int>(seedSerialized);
var rng = new Random(seed);
for (int i = 0; i < 1000; i++)
{
Block8x8F data = default;
int setIndex = rng.Next(1, Block8x8F.Size);
data[setIndex] = rng.Next();
int expected = setIndex;
int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data);
Assert.Equal(expected, actual);
}
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void GetLastValuableElementIndex_RandomFilledPartially(int seed)
{
static void RunTest(string seedSerialized)
{
int seed = FeatureTestRunner.Deserialize<int>(seedSerialized);
var rng = new Random(seed);
for (int i = 0; i < 1000; i++)
{
Block8x8F data = default;
int lastIndex = rng.Next(1, Block8x8F.Size);
int fillValue = rng.Next();
for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++)
{
data[dataIndex] = fillValue;
}
int expected = lastIndex;
int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data);
Assert.Equal(expected, actual);
}
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void GetLastValuableElementIndex_RandomFilledFragmented(int seed)
{
static void RunTest(string seedSerialized)
{
int seed = FeatureTestRunner.Deserialize<int>(seedSerialized);
var rng = new Random(seed);
for (int i = 0; i < 1000; i++)
{
Block8x8F data = default;
int fillValue = rng.Next();
// first filled chunk
int lastIndex1 = rng.Next(1, Block8x8F.Size / 2);
for (int dataIndex = 0; dataIndex <= lastIndex1; dataIndex++)
{
data[dataIndex] = fillValue;
}
// second filled chunk, there might be a spot with zero(s) between first and second chunk
int lastIndex2 = rng.Next(lastIndex1 + 1, Block8x8F.Size);
for (int dataIndex = 0; dataIndex <= lastIndex2; dataIndex++)
{
data[dataIndex] = fillValue;
}
int expected = lastIndex2;
int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data);
Assert.Equal(expected, actual);
}
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"Component{i}: {diff}");
averageDifference += diff.average;
totalDifference += diff.total;
tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length;
tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length;
}
averageDifference /= componentCount;

2
tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs

@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
in operation);
// Assert:
TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan());
TestImageExtensions.CompareBuffers(expected.DangerousGetSingleSpan(), actual.DangerousGetSingleSpan());
}
}

8
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
{
Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory());
Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory());
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
{
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory());
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory());
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory<Bgra32>(byteMemory, bmp.Width, bmp.Height))
{
Span<Bgra32> pixelSpan = pixelMemory.Span;
Span<Bgra32> imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span;
Span<Bgra32> imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span;
// We can't compare the two Memory<T> instances directly as they wrap different memory managers.
// To check that the underlying data matches, we can just manually check their lenth, and the
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory<Bgra32>(p, bmp.Width, bmp.Height))
{
Span<Bgra32> pixelSpan = pixelMemory.Span;
Span<Bgra32> imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span;
Span<Bgra32> imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span;
Assert.Equal(pixelSpan.Length, imageSpan.Length);
Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));

14
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height);
Assert.Equal(0, buffer.FastMemoryGroup.TotalLength);
Assert.Equal(0, buffer.GetSingleSpan().Length);
Assert.Equal(0, buffer.DangerousGetSingleSpan().Length);
}
}
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
using (Buffer2D<int> buffer = this.MemoryAllocator.Allocate2D<int>(42, 42, AllocationOptions.Clean))
{
Span<int> span = buffer.GetSingleSpan();
Span<int> span = buffer.DangerousGetSingleSpan();
for (int j = 0; j < span.Length; j++)
{
Assert.Equal(0, span[j]);
@ -249,9 +249,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(width, height))
{
rnd.RandomFill(b.GetSingleSpan(), 0, 1);
rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1);
b.CopyColumns(startIndex, destIndex, columnCount);
b.DangerousCopyColumns(startIndex, destIndex, columnCount);
for (int y = 0; y < b.Height; y++)
{
@ -271,10 +271,10 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(100, 100))
{
rnd.RandomFill(b.GetSingleSpan(), 0, 1);
rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1);
b.CopyColumns(0, 50, 22);
b.CopyColumns(0, 50, 22);
b.DangerousCopyColumns(0, 50, 22);
b.DangerousCopyColumns(0, 50, 22);
for (int y = 0; y < b.Height; y++)
{

2
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -672,7 +672,7 @@ namespace SixLabors.ImageSharp.Tests
var image = new Image<Rgba32>(buffer.Width, buffer.Height);
Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<Rgba32> pixels));
Span<float> bufferSpan = buffer.GetSingleSpan();
Span<float> bufferSpan = buffer.DangerousGetSingleSpan();
for (int i = 0; i < bufferSpan.Length; i++)
{

Loading…
Cancel
Save