Browse Source

Merge branch 'main' into fixed-improvements

pull/2418/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
df4ae9e6dc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 30
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  3. 32
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 122
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs
  5. 133
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs
  6. 14
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs
  8. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
  9. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  10. 146
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  11. 289
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  12. 57
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  13. 51
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  14. 43
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  15. 12
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  16. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  17. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  18. 30
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  19. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  20. 38
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  21. 44
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  22. 2
      src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
  23. 23
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  24. 15
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  25. 32
      src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
  26. 26
      src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
  27. 16
      src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
  28. 58
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  29. 5
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  30. 2
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  31. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
  32. 20
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  33. 25
      src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
  34. 11
      src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
  35. 27
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  36. 35
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  37. 26
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  38. 104
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  39. 33
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  40. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs
  41. 14
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
  42. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
  43. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
  44. 237
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  45. 4
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs
  46. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  47. 15
      tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

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

@ -453,6 +453,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> scratchBuffer = stackalloc byte[128];
Span<byte> cmd = stackalloc byte[2];
int count = 0;
@ -491,9 +492,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
int max = cmd[1];
int bytesToRead = (int)(((uint)max + 1) / 2);
byte[] run = new byte[bytesToRead];
Span<byte> run = bytesToRead <= 128 ? scratchBuffer.Slice(0, bytesToRead) : new byte[bytesToRead];
stream.Read(run, 0, run.Length);
stream.Read(run);
int idx = 0;
for (int i = 0; i < max; i++)
@ -559,6 +560,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> scratchBuffer = stackalloc byte[128];
Span<byte> cmd = stackalloc byte[2];
int count = 0;
@ -596,13 +598,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
byte[] run = new byte[length];
Span<byte> run = length <= 128 ? scratchBuffer.Slice(0, length) : new byte[length];
stream.Read(run, 0, run.Length);
stream.Read(run);
run.AsSpan().CopyTo(buffer[count..]);
run.CopyTo(buffer[count..]);
count += run.Length;
count += length;
// Absolute mode data is aligned to two-byte word-boundary.
int padding = length & 1;
@ -639,6 +641,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle24(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> scratchBuffer = stackalloc byte[128];
Span<byte> cmd = stackalloc byte[2];
int uncompressedPixels = 0;
@ -675,17 +678,18 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// If the second byte > 2, we are in 'absolute mode'.
// Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
int length3 = length * 3;
byte[] run = new byte[length * 3];
Span<byte> run = length3 <= 128 ? scratchBuffer.Slice(0, length3) : new byte[length3];
stream.Read(run, 0, run.Length);
stream.Read(run);
run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]);
run.CopyTo(buffer[(uncompressedPixels * 3)..]);
uncompressedPixels += length;
// Absolute mode data is aligned to two-byte word-boundary.
int padding = run.Length & 1;
int padding = length3 & 1;
stream.Skip(padding);
@ -1286,18 +1290,18 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// color masks for each color channel follow the info header.
if (this.infoHeader.Compression == BmpCompression.BitFields)
{
byte[] bitfieldsBuffer = new byte[12];
stream.Read(bitfieldsBuffer, 0, 12);
Span<byte> data = bitfieldsBuffer.AsSpan();
Span<byte> bitfieldsBuffer = stackalloc byte[12];
stream.Read(bitfieldsBuffer);
Span<byte> data = bitfieldsBuffer;
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
}
else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS)
{
byte[] bitfieldsBuffer = new byte[16];
stream.Read(bitfieldsBuffer, 0, 16);
Span<byte> data = bitfieldsBuffer.AsSpan();
Span<byte> bitfieldsBuffer = stackalloc byte[16];
stream.Read(bitfieldsBuffer);
Span<byte> data = bitfieldsBuffer;
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
@ -1470,7 +1474,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
if ((stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes)
{
BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");

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

@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// The temp buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[16];
private ScratchBuffer buffer; // mutable struct, don't make readonly
/// <summary>
/// The global color table.
@ -249,13 +249,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadGraphicalControlExtension(BufferedReadStream stream)
{
int bytesRead = stream.Read(this.buffer, 0, 6);
int bytesRead = stream.Read(this.buffer.Span, 0, 6);
if (bytesRead != 6)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
}
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span);
}
/// <summary>
@ -264,13 +264,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadImageDescriptor(BufferedReadStream stream)
{
int bytesRead = stream.Read(this.buffer, 0, 9);
int bytesRead = stream.Read(this.buffer.Span, 0, 9);
if (bytesRead != 9)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
}
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
{
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
@ -283,13 +283,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
{
int bytesRead = stream.Read(this.buffer, 0, 7);
int bytesRead = stream.Read(this.buffer.Span, 0, 7);
if (bytesRead != 7)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
}
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span);
}
/// <summary>
@ -306,8 +306,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
long position = stream.Position;
if (appLength == GifConstants.ApplicationBlockSize)
{
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.skipMetadata)
{
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
@ -331,8 +331,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
stream.Skip(1); // Skip the terminator.
return;
}
@ -762,4 +762,12 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
}
}
private unsafe struct ScratchBuffer
{
private const int Size = 16;
private fixed byte scratch[Size];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
}
}

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

@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// A reusable buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary>
/// Whether to skip metadata during encode.
/// </summary>
@ -324,9 +319,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
descriptor.WriteTo(this.buffer);
Span<byte> buffer = stackalloc byte[20];
descriptor.WriteTo(buffer);
stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
}
/// <summary>
@ -365,12 +361,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
return;
}
Span<byte> buffer = stackalloc byte[2];
for (int i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.CommentLabel;
stream.Write(this.buffer, 0, 2);
buffer[1] = GifConstants.CommentLabel;
buffer[0] = GifConstants.ExtensionIntroducer;
stream.Write(buffer);
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan<char> commentSpan = comment.AsSpan();
@ -437,22 +435,23 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer;
int extensionSize = extension.ContentLength;
if (extensionSize == 0)
{
return;
}
else if (extensionSize > this.buffer.Length - 3)
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
if (extensionSize > 128)
{
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);
extensionBuffer = owner.GetSpan();
}
else
{
extensionBuffer = this.buffer;
extensionBuffer = stackalloc byte[extensionSize + 3];
}
extensionBuffer[0] = GifConstants.ExtensionIntroducer;
@ -489,9 +488,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
height: (ushort)image.Height,
packed: packedValue);
descriptor.WriteTo(this.buffer);
Span<byte> buffer = stackalloc byte[20];
descriptor.WriteTo(buffer);
stream.Write(this.buffer, 0, GifImageDescriptor.Size);
stream.Write(buffer, 0, GifImageDescriptor.Size);
}
/// <summary>

122
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs

@ -0,0 +1,122 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrArm : JpegColorConverterArm
{
public YCbCrArm(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
var chromaOffset = Vector128.Create(-this.HalfValue);
var scale = Vector128.Create(1 / this.MaximumValue);
var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector128<float> y = c0;
Vector128<float> cb = AdvSimd.Add(c1, chromaOffset);
Vector128<float> cr = AdvSimd.Add(c2, chromaOffset);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector128<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector128<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = AdvSimd.Multiply(AdvSimd.RoundToNearest(r), scale);
g = AdvSimd.Multiply(AdvSimd.RoundToNearest(g), scale);
b = AdvSimd.Multiply(AdvSimd.RoundToNearest(b), scale);
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destCb =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destCr =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> srcR =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcG =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcB =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
var chromaOffset = Vector128.Create(this.HalfValue);
var f0299 = Vector128.Create(0.299f);
var f0587 = Vector128.Create(0.587f);
var f0114 = Vector128.Create(0.114f);
var fn0168736 = Vector128.Create(-0.168736f);
var fn0331264 = Vector128.Create(-0.331264f);
var fn0418688 = Vector128.Create(-0.418688f);
var fn0081312F = Vector128.Create(-0.081312F);
var f05 = Vector128.Create(0.5f);
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
Vector128<float> r = Unsafe.Add(ref srcR, i);
Vector128<float> g = Unsafe.Add(ref srcG, i);
Vector128<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector128<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
Vector128<float> cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
Vector128<float> cr = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(fn0081312F, b), fn0418688, g), f05, r));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

133
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs

@ -0,0 +1,133 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKArm64 : JpegColorConverterArm64
{
public YccKArm64(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> kBase =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var chromaOffset = Vector128.Create(-this.HalfValue);
var scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector128.Create(this.MaximumValue);
var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector128<float> y = c0;
Vector128<float> cb = AdvSimd.Add(c1, chromaOffset);
Vector128<float> cr = AdvSimd.Add(c2, chromaOffset);
Vector128<float> scaledK = AdvSimd.Multiply(Unsafe.Add(ref kBase, i), scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector128<float> g =
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector128<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(r));
g = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(g));
b = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(b));
r = AdvSimd.Multiply(r, scaledK);
g = AdvSimd.Multiply(g, scaledK);
b = AdvSimd.Multiply(b, scaledK);
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykArm64.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destCb =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destCr =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> srcR = ref destY;
ref Vector128<float> srcG = ref destCb;
ref Vector128<float> srcB = ref destCr;
// Used for the color conversion
var maxSampleValue = Vector128.Create(this.MaximumValue);
var chromaOffset = Vector128.Create(this.HalfValue);
var f0299 = Vector128.Create(0.299f);
var f0587 = Vector128.Create(0.587f);
var f0114 = Vector128.Create(0.114f);
var fn0168736 = Vector128.Create(-0.168736f);
var fn0331264 = Vector128.Create(-0.331264f);
var fn0418688 = Vector128.Create(-0.418688f);
var fn0081312F = Vector128.Create(-0.081312F);
var f05 = Vector128.Create(0.5f);
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
Vector128<float> r = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i));
Vector128<float> g = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i));
Vector128<float> b = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i));
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector128<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
Vector128<float> cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
Vector128<float> cr = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(fn0081312F, b), fn0418688, g), f05, r));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}

14
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -138,6 +138,11 @@ internal abstract partial class JpegColorConverterBase
return new YCbCrAvx(precision);
}
if (JpegColorConverterArm.IsSupported)
{
return new YCbCrArm(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new YCbCrVector(precision);
@ -157,6 +162,11 @@ internal abstract partial class JpegColorConverterBase
return new YccKAvx(precision);
}
if (JpegColorConverterArm64.IsSupported)
{
return new YccKArm64(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new YccKVector(precision);
@ -231,10 +241,10 @@ internal abstract partial class JpegColorConverterBase
if (JpegColorConverterVector.IsSupported)
{
return new RgbScalar(precision);
return new RgbVector(precision);
}
return new GrayscaleScalar(precision);
return new RgbScalar(precision);
}
/// <summary>

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs

@ -62,7 +62,7 @@ internal readonly struct AdobeMarker : IEquatable<AdobeMarker>
/// </summary>
/// <param name="bytes">The byte array containing metadata to parse.</param>
/// <param name="marker">The marker to return.</param>
public static bool TryParse(byte[] bytes, out AdobeMarker marker)
public static bool TryParse(ReadOnlySpan<byte> bytes, out AdobeMarker marker)
{
if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker))
{

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

@ -53,6 +53,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
private ArithmeticDecodingTable[] acDecodingTables;
// Don't make this a ReadOnlySpan<byte>, as the values need to get updated.
private readonly byte[] fixedBin = { 113, 0, 0, 0 };
private readonly CancellationToken cancellationToken;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs

@ -69,7 +69,7 @@ internal readonly struct JFifMarker : IEquatable<JFifMarker>
/// </summary>
/// <param name="bytes">The byte array containing metadata to parse.</param>
/// <param name="marker">The marker to return.</param>
public static bool TryParse(byte[] bytes, out JFifMarker marker)
public static bool TryParse(ReadOnlySpan<byte> bytes, out JFifMarker marker)
{
if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker))
{

146
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -27,21 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
/// </summary>
internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
{
/// <summary>
/// The only supported precision
/// </summary>
private readonly byte[] supportedPrecisions = { 8, 12 };
/// <summary>
/// The buffer used to temporarily store bytes read from the stream.
/// </summary>
private readonly byte[] temp = new byte[2 * 16 * 4];
/// <summary>
/// The buffer used to read markers from the stream.
/// </summary>
private readonly byte[] markerBuffer = new byte[2];
/// <summary>
/// Whether the image has an EXIF marker.
/// </summary>
@ -139,6 +124,12 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.skipMetadata = options.GeneralOptions.SkipMetadata;
}
/// <summary>
/// Gets the only supported precisions
/// </summary>
// Refers to assembly's static data segment, no allocation occurs.
private static ReadOnlySpan<byte> SupportedPrecisions => new byte[] { 8, 12 };
/// <inheritdoc />
public DecoderOptions Options { get; }
@ -257,24 +248,26 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
using MemoryStream ms = new(tableBytes);
using BufferedReadStream stream = new(this.configuration, ms);
Span<byte> markerBuffer = stackalloc byte[2];
// Check for the Start Of Image marker.
int bytesRead = stream.Read(this.markerBuffer, 0, 2);
JpegFileMarker fileMarker = new(this.markerBuffer[1], 0);
int bytesRead = stream.Read(markerBuffer);
JpegFileMarker fileMarker = new(markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
// Read next marker.
bytesRead = stream.Read(this.markerBuffer, 0, 2);
fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2);
bytesRead = stream.Read(markerBuffer);
fileMarker = new JpegFileMarker(markerBuffer[1], (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
// Get the marker length.
int markerContentByteSize = this.ReadUint16(stream) - 2;
int markerContentByteSize = ReadUint16(stream, markerBuffer) - 2;
// Check whether the stream actually has enough bytes to read
// markerContentByteSize is always positive so we cast
@ -297,7 +290,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize, markerBuffer);
break;
case JpegConstants.Markers.EOI:
return;
@ -305,13 +298,13 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
// Read next marker.
bytesRead = stream.Read(this.markerBuffer, 0, 2);
bytesRead = stream.Read(markerBuffer);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
fileMarker = new JpegFileMarker(markerBuffer[1], 0);
}
}
@ -329,9 +322,11 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.Metadata = new ImageMetadata();
Span<byte> markerBuffer = stackalloc byte[2];
// Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2);
JpegFileMarker fileMarker = new(this.markerBuffer[1], 0);
stream.Read(markerBuffer);
JpegFileMarker fileMarker = new(markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
@ -349,7 +344,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
if (!fileMarker.Invalid)
{
// Get the marker length.
int markerContentByteSize = this.ReadUint16(stream) - 2;
int markerContentByteSize = ReadUint16(stream, markerBuffer) - 2;
// Check whether stream actually has enough bytes to read
// markerContentByteSize is always positive so we cast
@ -446,7 +441,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
else
{
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize, markerBuffer);
}
break;
@ -755,8 +750,10 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
return;
}
stream.Read(this.temp, 0, JFifMarker.Length);
if (!JFifMarker.TryParse(this.temp, out this.jFif))
Span<byte> temp = stackalloc byte[2 * 16 * 4];
stream.Read(temp, 0, JFifMarker.Length);
if (!JFifMarker.TryParse(temp, out this.jFif))
{
JpegThrowHelper.ThrowNotSupportedException("Unknown App0 Marker - Expected JFIF.");
}
@ -796,11 +793,13 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
}
Span<byte> temp = stackalloc byte[2 * 16 * 4];
// XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes.
stream.Read(this.temp, 0, exifMarkerLength);
stream.Read(temp, 0, exifMarkerLength);
remaining -= exifMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker))
if (ProfileResolver.IsProfile(temp, ProfileResolver.ExifMarker))
{
this.hasExif = true;
byte[] profile = new byte[remaining];
@ -819,7 +818,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
remaining = 0;
}
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker[..exifMarkerLength]))
if (ProfileResolver.IsProfile(temp, ProfileResolver.XmpMarker[..exifMarkerLength]))
{
const int remainingXmpMarkerBytes = xmpMarkerLength - exifMarkerLength;
if (remaining < remainingXmpMarkerBytes || this.skipMetadata)
@ -829,9 +828,9 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
return;
}
stream.Read(this.temp, exifMarkerLength, remainingXmpMarkerBytes);
stream.Read(temp, exifMarkerLength, remainingXmpMarkerBytes);
remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
if (ProfileResolver.IsProfile(temp, ProfileResolver.XmpMarker))
{
this.hasXmp = true;
byte[] profile = new byte[remaining];
@ -870,8 +869,8 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
return;
}
byte[] identifier = new byte[icclength];
stream.Read(identifier, 0, icclength);
Span<byte> identifier = stackalloc byte[icclength];
stream.Read(identifier);
remaining -= icclength; // We have read it by this point
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
@ -911,13 +910,13 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
return;
}
stream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length);
Span<byte> temp = stackalloc byte[2 * 16 * 4];
stream.Read(temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length);
remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
if (ProfileResolver.IsProfile(temp, ProfileResolver.AdobePhotoshopApp13Marker))
{
byte[] resourceBlockData = new byte[remaining];
stream.Read(resourceBlockData, 0, remaining);
Span<byte> blockDataSpan = resourceBlockData.AsSpan();
Span<byte> blockDataSpan = remaining <= 128 ? stackalloc byte[remaining] : new byte[remaining];
stream.Read(blockDataSpan);
while (blockDataSpan.Length > 12)
{
@ -1047,10 +1046,12 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
return;
}
stream.Read(this.temp, 0, markerLength);
Span<byte> temp = stackalloc byte[2 * 16 * 4];
stream.Read(temp, 0, markerLength);
remaining -= markerLength;
if (AdobeMarker.TryParse(this.temp, out this.adobe))
if (AdobeMarker.TryParse(temp, out this.adobe))
{
this.hasAdobeMarker = true;
}
@ -1072,6 +1073,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{
JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance);
Span<byte> temp = stackalloc byte[2 * 16 * 4];
while (remaining > 0)
{
@ -1102,13 +1104,13 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
}
stream.Read(this.temp, 0, 64);
stream.Read(temp, 0, 64);
remaining -= 64;
// Parsing quantization table & saving it in natural order
for (int j = 0; j < 64; j++)
{
table[ZigZag.ZigZagOrder[j]] = this.temp[j];
table[ZigZag.ZigZagOrder[j]] = temp[j];
}
break;
@ -1121,13 +1123,13 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
}
stream.Read(this.temp, 0, 128);
stream.Read(temp, 0, 128);
remaining -= 128;
// Parsing quantization table & saving it in natural order
for (int j = 0; j < 64; j++)
{
table[ZigZag.ZigZagOrder[j]] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1];
table[ZigZag.ZigZagOrder[j]] = (temp[2 * j] << 8) | temp[(2 * j) + 1];
}
break;
@ -1174,28 +1176,30 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
Span<byte> temp = stackalloc byte[2 * 16 * 4];
// Read initial marker definitions.
const int length = 6;
int bytesRead = stream.Read(this.temp, 0, length);
int bytesRead = stream.Read(temp, 0, length);
if (bytesRead != length)
{
JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data.");
}
// 1 byte: Bits/sample precision.
byte precision = this.temp[0];
byte precision = temp[0];
// Validate: only 8-bit and 12-bit precisions are supported.
if (Array.IndexOf(this.supportedPrecisions, precision) == -1)
if (SupportedPrecisions.IndexOf(precision) < 0)
{
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported.");
}
// 2 byte: Height
int frameHeight = (this.temp[1] << 8) | this.temp[2];
int frameHeight = (temp[1] << 8) | temp[2];
// 2 byte: Width
int frameWidth = (this.temp[3] << 8) | this.temp[4];
int frameWidth = (temp[3] << 8) | temp[4];
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that).
if (frameHeight == 0 || frameWidth == 0)
@ -1204,7 +1208,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
// 1 byte: Number of components.
byte componentCount = this.temp[5];
byte componentCount = temp[5];
// Validate: componentCount more than 4 can lead to a buffer overflow during stream
// reading so we must limit it to 4.
@ -1227,7 +1231,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
// components*3 bytes: component data
stream.Read(this.temp, 0, remaining);
stream.Read(temp, 0, remaining);
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[componentCount];
@ -1240,10 +1244,10 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
for (int i = 0; i < this.Frame.Components.Length; i++)
{
// 1 byte: component identifier
byte componentId = this.temp[index];
byte componentId = temp[index];
// 1 byte: component sampling factors
byte hv = this.temp[index + 1];
byte hv = temp[index + 1];
int h = (hv >> 4) & 15;
int v = hv & 15;
@ -1270,7 +1274,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
// 1 byte: quantization table destination selector
byte quantTableIndex = this.temp[index + 2];
byte quantTableIndex = temp[index + 2];
// Validate: 0-3 range
if (quantTableIndex > 3)
@ -1379,7 +1383,8 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining)
/// <param name="markerBuffer">Scratch buffer.</param>
private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining, Span<byte> markerBuffer)
{
if (remaining != 2)
{
@ -1388,7 +1393,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
// Save the reset interval, because it can come before or after the SOF marker.
// If the reset interval comes after the SOF marker, the scanDecoder has not been created.
this.resetInterval = this.ReadUint16(stream);
this.resetInterval = ReadUint16(stream, markerBuffer);
if (this.scanDecoder != null)
{
@ -1425,14 +1430,16 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
}
Span<byte> temp = stackalloc byte[2 * 16 * 4];
// selectorsCount*2 bytes: component index + huffman tables indices
stream.Read(this.temp, 0, selectorsBytes);
stream.Read(temp, 0, selectorsBytes);
this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount;
for (int i = 0; i < selectorsBytes; i += 2)
{
// 1 byte: Component id
int componentSelectorId = this.temp[i];
int componentSelectorId = temp[i];
int componentIndex = -1;
for (int j = 0; j < this.Frame.ComponentIds.Length; j++)
@ -1459,7 +1466,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
// 1 byte: Huffman table selectors.
// 4 bits - dc
// 4 bits - ac
int tableSpec = this.temp[i + 1];
int tableSpec = temp[i + 1];
int dcTableIndex = tableSpec >> 4;
int acTableIndex = tableSpec & 15;
@ -1475,17 +1482,17 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
// 3 bytes: Progressive scan decoding data.
int bytesRead = stream.Read(this.temp, 0, 3);
int bytesRead = stream.Read(temp, 0, 3);
if (bytesRead != 3)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data");
}
this.scanDecoder.SpectralStart = this.temp[0];
this.scanDecoder.SpectralStart = temp[0];
this.scanDecoder.SpectralEnd = this.temp[1];
this.scanDecoder.SpectralEnd = temp[1];
int successiveApproximation = this.temp[2];
int successiveApproximation = temp[2];
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
@ -1501,16 +1508,17 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="markerBuffer">The scratch buffer used for reading from the stream.</param>
/// <returns>The <see cref="ushort"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
private ushort ReadUint16(BufferedReadStream stream)
private static ushort ReadUint16(BufferedReadStream stream, Span<byte> markerBuffer)
{
int bytesRead = stream.Read(this.markerBuffer, 0, 2);
int bytesRead = stream.Read(markerBuffer, 0, 2);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort.");
}
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
return BinaryPrimitives.ReadUInt16BigEndian(markerBuffer);
}
}

289
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -25,11 +25,6 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// </summary>
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[20];
private readonly JpegEncoder encoder;
/// <summary>
@ -67,6 +62,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
cancellationToken.ThrowIfCancellationRequested();
this.outputStream = stream;
Span<byte> buffer = stackalloc byte[20];
ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
@ -76,39 +72,39 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
using JpegFrame frame = new(image, frameConfig, interleaved);
// Write the Start Of Image marker.
this.WriteStartOfImage();
this.WriteStartOfImage(buffer);
// Write APP0 marker
if (frameConfig.AdobeColorTransformMarkerFlag is null)
{
this.WriteJfifApplicationHeader(metadata);
this.WriteJfifApplicationHeader(metadata, buffer);
}
// Write APP14 marker with adobe color extension
else
{
this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value);
this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value, buffer);
}
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
this.WriteProfiles(metadata, buffer);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, frameConfig);
this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer);
// Write the Huffman tables.
HuffmanScanEncoder scanEncoder = new(frame.BlocksPerMcu, stream);
this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder);
this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder, buffer);
// Write the quantization tables.
this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata);
this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata, buffer);
// Write scans with actual pixel data
using SpectralConverter<TPixel> spectralConverter = new(frame, image, this.QuantizationTables);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken);
// Write the End Of Image marker.
this.WriteEndOfImageMarker();
this.WriteEndOfImageMarker(buffer);
stream.Flush();
}
@ -116,58 +112,59 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <summary>
/// Write the start of image marker.
/// </summary>
private void WriteStartOfImage()
private void WriteStartOfImage(Span<byte> buffer)
{
// Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI;
buffer[1] = JpegConstants.Markers.SOI;
buffer[0] = JpegConstants.Markers.XFF;
this.outputStream.Write(this.buffer, 0, 2);
this.outputStream.Write(buffer, 0, 2);
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="meta">The image metadata.</param>
private void WriteJfifApplicationHeader(ImageMetadata meta)
/// <param name="buffer">Temporary buffer.</param>
private void WriteJfifApplicationHeader(ImageMetadata meta, Span<byte> buffer)
{
// Write the JFIF headers
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[2] = 0x00;
this.buffer[3] = 0x10;
this.buffer[4] = 0x4a; // J
this.buffer[5] = 0x46; // F
this.buffer[6] = 0x49; // I
this.buffer[7] = 0x46; // F
this.buffer[8] = 0x00; // = "JFIF",'\0'
this.buffer[9] = 0x01; // versionhi
this.buffer[10] = 0x01; // versionlo
// Write the JFIF headers (highest index first to avoid additional bound checks)
buffer[10] = 0x01; // versionlo
buffer[0] = JpegConstants.Markers.XFF;
buffer[1] = JpegConstants.Markers.APP0; // Application Marker
buffer[2] = 0x00;
buffer[3] = 0x10;
buffer[4] = 0x4a; // J
buffer[5] = 0x46; // F
buffer[6] = 0x49; // I
buffer[7] = 0x46; // F
buffer[8] = 0x00; // = "JFIF",'\0'
buffer[9] = 0x01; // versionhi
// Resolution. Big Endian
Span<byte> hResolution = this.buffer.AsSpan(12, 2);
Span<byte> vResolution = this.buffer.AsSpan(14, 2);
Span<byte> hResolution = buffer.Slice(12, 2);
Span<byte> vResolution = buffer.Slice(14, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{
// Scale down to PPI
this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
}
else
{
// We can simply pass the value.
this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits
buffer[11] = (byte)meta.ResolutionUnits; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
}
// No thumbnail
this.buffer[16] = 0x00; // Thumbnail width
this.buffer[17] = 0x00; // Thumbnail height
buffer[17] = 0x00; // Thumbnail height
buffer[16] = 0x00; // Thumbnail width
this.outputStream.Write(this.buffer, 0, 18);
this.outputStream.Write(buffer, 0, 18);
}
/// <summary>
@ -175,8 +172,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// </summary>
/// <param name="tableConfigs">The table configuration.</param>
/// <param name="scanEncoder">The scan encoder.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ArgumentNullException"><paramref name="tableConfigs"/> is <see langword="null"/>.</exception>
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder, Span<byte> buffer)
{
if (tableConfigs is null)
{
@ -190,7 +188,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length;
}
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen, buffer);
for (int i = 0; i < tableConfigs.Length; i++)
{
JpegHuffmanTableConfig tableConfig = tableConfigs[i];
@ -208,37 +206,39 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Writes the APP14 marker to indicate the image is in RGB color space.
/// </summary>
/// <param name="colorTransform">The color transform byte.</param>
private void WriteApp14Marker(byte colorTransform)
/// <param name="buffer">Temporary buffer.</param>
private void WriteApp14Marker(byte colorTransform, Span<byte> buffer)
{
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length);
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length, buffer);
// Identifier: ASCII "Adobe".
this.buffer[0] = 0x41;
this.buffer[1] = 0x64;
this.buffer[2] = 0x6F;
this.buffer[3] = 0x62;
this.buffer[4] = 0x65;
// Identifier: ASCII "Adobe" (highest index first to avoid additional bound checks).
buffer[4] = 0x65;
buffer[0] = 0x41;
buffer[1] = 0x64;
buffer[2] = 0x6F;
buffer[3] = 0x62;
// Version, currently 100.
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(5, 2), 100);
BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(5, 2), 100);
// Flags0
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(7, 2), 0);
BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(7, 2), 0);
// Flags1
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0);
BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(9, 2), 0);
// Color transform byte
this.buffer[11] = colorTransform;
buffer[11] = colorTransform;
this.outputStream.Write(this.buffer.AsSpan(0, 12));
this.outputStream.Write(buffer.Slice(0, 12));
}
/// <summary>
/// Writes the EXIF profile.
/// </summary>
/// <param name="exifProfile">The exif profile.</param>
private void WriteExifProfile(ExifProfile exifProfile)
/// <param name="buffer">Temporary buffer.</param>
private void WriteExifProfile(ExifProfile exifProfile, Span<byte> buffer)
{
if (exifProfile is null || exifProfile.Values.Count == 0)
{
@ -262,7 +262,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
int app1Length = bytesToWrite + 2;
// Write the app marker, EXIF marker, and data
this.WriteApp1Header(app1Length);
this.WriteApp1Header(app1Length, buffer);
this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker);
this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength);
remaining -= bytesToWrite;
@ -273,7 +273,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining;
app1Length = bytesToWrite + 2 + exifMarkerLength;
this.WriteApp1Header(app1Length);
this.WriteApp1Header(app1Length, buffer);
// Write Exif00 marker
this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker);
@ -289,10 +289,11 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Writes the IPTC metadata.
/// </summary>
/// <param name="iptcProfile">The iptc metadata to write.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the IPTC profile size exceeds the limit of 65533 bytes.
/// </exception>
private void WriteIptcProfile(IptcProfile iptcProfile)
private void WriteIptcProfile(IptcProfile iptcProfile, Span<byte> buffer)
{
const int maxBytes = 65533;
if (iptcProfile is null || !iptcProfile.Values.Any())
@ -316,14 +317,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length +
Components.Decoder.ProfileResolver.AdobeIptcMarker.Length +
2 + 4 + data.Length;
this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13);
this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13, buffer);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker);
this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even)
this.outputStream.WriteByte(0);
BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length);
this.outputStream.Write(this.buffer, 0, 4);
BinaryPrimitives.WriteInt32BigEndian(buffer, data.Length);
this.outputStream.Write(buffer, 0, 4);
this.outputStream.Write(data, 0, data.Length);
}
@ -331,10 +332,11 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Writes the XMP metadata.
/// </summary>
/// <param name="xmpProfile">The XMP metadata to write.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the XMP profile size exceeds the limit of 65533 bytes.
/// </exception>
private void WriteXmpProfile(XmpProfile xmpProfile)
private void WriteXmpProfile(XmpProfile xmpProfile, Span<byte> buffer)
{
if (xmpProfile is null)
{
@ -367,7 +369,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
dataLength -= length;
int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length;
this.WriteApp1Header(app1Length);
this.WriteApp1Header(app1Length, buffer);
this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker);
this.outputStream.Write(data, offset, length);
@ -379,32 +381,35 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Writes the App1 header.
/// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains.</param>
private void WriteApp1Header(int app1Length)
=> this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
/// <param name="buffer">Temporary buffer.</param>
private void WriteApp1Header(int app1Length, Span<byte> buffer)
=> this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1, buffer);
/// <summary>
/// Writes a AppX header.
/// </summary>
/// <param name="length">The length of the data the app marker contains.</param>
/// <param name="appMarker">The app marker to write.</param>
private void WriteAppHeader(int length, byte appMarker)
/// <param name="buffer">Temporary buffer.</param>
private void WriteAppHeader(int length, byte appMarker, Span<byte> buffer)
{
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = appMarker;
this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF);
buffer[0] = JpegConstants.Markers.XFF;
buffer[1] = appMarker;
buffer[2] = (byte)((length >> 8) & 0xFF);
buffer[3] = (byte)(length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
this.outputStream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes the ICC profile.
/// </summary>
/// <param name="iccProfile">The ICC profile to write.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if any of the ICC profiles size exceeds the limit.
/// </exception>
private void WriteIccProfile(IccProfile iccProfile)
private void WriteIccProfile(IccProfile iccProfile, Span<byte> buffer)
{
if (iccProfile is null)
{
@ -446,30 +451,31 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
dataLength -= length;
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
buffer[0] = JpegConstants.Markers.XFF;
buffer[1] = JpegConstants.Markers.APP2; // Application Marker
int markerLength = length + 16;
this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
this.buffer[3] = (byte)(markerLength & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
this.buffer[0] = (byte)'I';
this.buffer[1] = (byte)'C';
this.buffer[2] = (byte)'C';
this.buffer[3] = (byte)'_';
this.buffer[4] = (byte)'P';
this.buffer[5] = (byte)'R';
this.buffer[6] = (byte)'O';
this.buffer[7] = (byte)'F';
this.buffer[8] = (byte)'I';
this.buffer[9] = (byte)'L';
this.buffer[10] = (byte)'E';
this.buffer[11] = 0x00;
this.buffer[12] = (byte)current; // The position within the collection.
this.buffer[13] = (byte)count; // The total number of profiles.
this.outputStream.Write(this.buffer, 0, iccOverheadLength);
buffer[2] = (byte)((markerLength >> 8) & 0xFF);
buffer[3] = (byte)(markerLength & 0xFF);
this.outputStream.Write(buffer, 0, 4);
// We write the highest index first, to have only one bound check.
buffer[13] = (byte)count; // The total number of profiles.
buffer[12] = (byte)current; // The position within the collection.
buffer[11] = 0x00;
buffer[0] = (byte)'I';
buffer[1] = (byte)'C';
buffer[2] = (byte)'C';
buffer[3] = (byte)'_';
buffer[4] = (byte)'P';
buffer[5] = (byte)'R';
buffer[6] = (byte)'O';
buffer[7] = (byte)'F';
buffer[8] = (byte)'I';
buffer[9] = (byte)'L';
buffer[10] = (byte)'E';
this.outputStream.Write(buffer, 0, iccOverheadLength);
this.outputStream.Write(data, offset, length);
current++;
@ -481,7 +487,8 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Writes the metadata profiles to the image.
/// </summary>
/// <param name="metadata">The image metadata.</param>
private void WriteProfiles(ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer)
{
if (metadata is null)
{
@ -494,10 +501,10 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
// - APP2 ICC
// - APP13 IPTC
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile);
this.WriteXmpProfile(metadata.XmpProfile);
this.WriteIccProfile(metadata.IccProfile);
this.WriteIptcProfile(metadata.IptcProfile);
this.WriteExifProfile(metadata.ExifProfile, buffer);
this.WriteXmpProfile(metadata.XmpProfile, buffer);
this.WriteIccProfile(metadata.IccProfile, buffer);
this.WriteIptcProfile(metadata.IptcProfile, buffer);
}
/// <summary>
@ -506,25 +513,26 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <param name="width">The frame width.</param>
/// <param name="height">The frame height.</param>
/// <param name="frame">The frame configuration.</param>
private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame)
/// <param name="buffer">Temporary buffer.</param>
private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame, Span<byte> buffer)
{
JpegComponentConfig[] components = frame.Components;
// Length (high byte, low byte), 8 + components * 3.
int markerlen = 8 + (3 * components.Length);
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen);
this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
this.buffer[1] = (byte)(height >> 8);
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[3] = (byte)(width >> 8);
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[5] = (byte)components.Length;
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen, buffer);
buffer[5] = (byte)components.Length;
buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
buffer[1] = (byte)(height >> 8);
buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
buffer[3] = (byte)(width >> 8);
buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
// Components data
for (int i = 0; i < components.Length; i++)
{
int i3 = 3 * i;
Span<byte> bufferSpan = this.buffer.AsSpan(i3 + 6, 3);
Span<byte> bufferSpan = buffer.Slice(i3 + 6, 3);
// Quantization table selector
bufferSpan[2] = (byte)components[i].QuantizatioTableIndex;
@ -538,14 +546,15 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
bufferSpan[0] = components[i].Id;
}
this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9);
this.outputStream.Write(buffer, 0, (3 * (components.Length - 1)) + 9);
}
/// <summary>
/// Writes the StartOfScan marker.
/// </summary>
/// <param name="components">The collecction of component configuration items.</param>
private void WriteStartOfScan(Span<JpegComponentConfig> components)
/// <param name="buffer">Temporary buffer.</param>
private void WriteStartOfScan(Span<JpegComponentConfig> components, Span<byte> buffer)
{
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
@ -556,14 +565,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
// should be 0x00, 0x3f, 0x00&lt;&lt;4 | 0x00.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOS;
buffer[1] = JpegConstants.Markers.SOS;
buffer[0] = JpegConstants.Markers.XFF;
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
int sosSize = 6 + (2 * components.Length);
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
this.buffer[4] = (byte)components.Length; // Number of components in a scan
buffer[4] = (byte)components.Length; // Number of components in a scan
buffer[3] = (byte)sosSize;
buffer[2] = 0x00;
// Components data
for (int i = 0; i < components.Length; i++)
@ -571,27 +580,28 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
int i2 = 2 * i;
// Id
this.buffer[i2 + 5] = components[i].Id;
buffer[i2 + 5] = components[i].Id;
// Table selectors
int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector;
this.buffer[i2 + 6] = (byte)tableSelectors;
buffer[i2 + 6] = (byte)tableSelectors;
}
this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.
this.buffer[sosSize] = 0x3f; // Se - End of spectral selection.
this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(this.buffer, 0, sosSize + 2);
buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.
buffer[sosSize] = 0x3f; // Se - End of spectral selection.
buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(buffer, 0, sosSize + 2);
}
/// <summary>
/// Writes the EndOfImage marker.
/// </summary>
private void WriteEndOfImageMarker()
/// <param name="buffer">Temporary buffer.</param>
private void WriteEndOfImageMarker(Span<byte> buffer)
{
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.EOI;
this.outputStream.Write(this.buffer, 0, 2);
buffer[1] = JpegConstants.Markers.EOI;
buffer[0] = JpegConstants.Markers.XFF;
this.outputStream.Write(buffer, 0, 2);
}
/// <summary>
@ -602,12 +612,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <param name="frameConfig">The frame configuration.</param>
/// <param name="spectralConverter">The spectral converter.</param>
/// <param name="encoder">The scan encoder.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void WriteHuffmanScans<TPixel>(
JpegFrame frame,
JpegFrameConfig frameConfig,
SpectralConverter<TPixel> spectralConverter,
HuffmanScanEncoder encoder,
Span<byte> buffer,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -615,14 +627,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
this.WriteStartOfScan(frameConfig.Components, buffer);
encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken);
}
else if (frame.Interleaved)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
this.WriteStartOfScan(frameConfig.Components, buffer);
encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
@ -633,7 +645,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
Span<JpegComponentConfig> components = frameConfig.Components;
for (int i = 0; i < frame.Components.Length; i++)
{
this.WriteStartOfScan(components.Slice(i, 1));
this.WriteStartOfScan(components.Slice(i, 1), buffer);
encoder.EncodeScanBaseline(frame.Components[i], cancellationToken);
}
}
@ -644,14 +656,16 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// </summary>
/// <param name="marker">The marker to write.</param>
/// <param name="length">The marker length.</param>
private void WriteMarkerHeader(byte marker, int length)
/// <param name="buffer">Temporary buffer.</param>
private void WriteMarkerHeader(byte marker, int length, Span<byte> buffer)
{
// Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = marker;
this.buffer[2] = (byte)(length >> 8);
this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4);
buffer[3] = (byte)(length & 0xff);
buffer[2] = (byte)(length >> 8);
buffer[1] = marker;
buffer[0] = JpegConstants.Markers.XFF;
this.outputStream.Write(buffer, 0, 4);
}
/// <summary>
@ -668,15 +682,16 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <param name="configs">Quantization tables configs.</param>
/// <param name="optionsQuality">Optional quality value from the options.</param>
/// <param name="metadata">Jpeg metadata instance.</param>
private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata)
/// <param name="tmpBuffer">Temporary buffer.</param>
private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata, Span<byte> tmpBuffer)
{
int dataLen = configs.Length * (1 + Block8x8.Size);
// Marker + quantization table lengths.
int markerlen = 2 + dataLen;
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen, tmpBuffer);
byte[] buffer = new byte[dataLen];
Span<byte> buffer = dataLen <= 256 ? stackalloc byte[dataLen] : new byte[dataLen];
int offset = 0;
Block8x8F workspaceBlock = default;

57
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// </summary>
internal sealed class PngDecoderCore : IImageDecoderInternals
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The general decoder options.
/// </summary>
@ -154,9 +149,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image<TPixel> image = null;
Span<byte> buffer = stackalloc byte[20];
try
{
while (this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(buffer, out PngChunk chunk))
{
try
{
@ -252,10 +249,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ImageMetadata metadata = new();
PngMetadata pngMetadata = metadata.GetPngMetadata();
this.currentStream = stream;
Span<byte> buffer = stackalloc byte[20];
this.currentStream.Skip(8);
try
{
while (this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(buffer, out PngChunk chunk))
{
try
{
@ -1401,9 +1401,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return 0;
}
this.currentStream.Read(this.buffer, 0, 4);
Span<byte> buffer = stackalloc byte[20];
this.currentStream.Read(buffer, 0, 4);
if (this.TryReadChunk(out PngChunk chunk))
if (this.TryReadChunk(buffer, out PngChunk chunk))
{
if (chunk.Type == PngChunkType.Data)
{
@ -1420,11 +1422,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads a chunk from the stream.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="chunk">The image format chunk.</param>
/// <returns>
/// The <see cref="PngChunk"/>.
/// </returns>
private bool TryReadChunk(out PngChunk chunk)
private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
{
if (this.nextChunk != null)
{
@ -1435,7 +1438,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return true;
}
if (!this.TryReadChunkLength(out int length))
if (!this.TryReadChunkLength(buffer, out int length))
{
chunk = default;
@ -1446,7 +1449,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
{
// Not a valid chunk so try again until we reach a known chunk.
if (!this.TryReadChunkLength(out length))
if (!this.TryReadChunkLength(buffer, out length))
{
chunk = default;
@ -1454,7 +1457,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
}
PngChunkType type = this.ReadChunkType();
PngChunkType type = this.ReadChunkType(buffer);
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
// We can skip all other chunk data in the stream for better performance.
@ -1471,7 +1474,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
type: type,
data: this.ReadChunkData(length));
this.ValidateChunk(chunk);
this.ValidateChunk(chunk, buffer);
// Restore the stream position for IDAT chunks, because it will be decoded later and
// was only read to verifying the CRC is correct.
@ -1487,9 +1490,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// Validates the png chunk.
/// </summary>
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk)
/// <param name="buffer">Temporary buffer.</param>
private void ValidateChunk(in PngChunk chunk, Span<byte> buffer)
{
uint inputCrc = this.ReadChunkCrc();
uint inputCrc = this.ReadChunkCrc(buffer);
if (chunk.IsCritical)
{
@ -1513,13 +1517,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the cycle redundancy chunk from the data.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private uint ReadChunkCrc()
private uint ReadChunkCrc(Span<byte> buffer)
{
uint crc = 0;
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
crc = BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
return crc;
@ -1554,15 +1559,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
private PngChunkType ReadChunkType()
private PngChunkType ReadChunkType(Span<byte> buffer)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
PngThrowHelper.ThrowInvalidChunkType();
@ -1574,16 +1580,17 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <summary>
/// Attempts to read the length of the next chunk.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="result">The result length. If the return type is <see langword="false"/> this parameter is passed uninitialized.</param>
/// <returns>
/// Whether the length was read.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
private bool TryReadChunkLength(out int result)
private bool TryReadChunkLength(Span<byte> buffer, out int result)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (this.currentStream.Read(buffer, 0, 4) == 4)
{
result = BinaryPrimitives.ReadInt32BigEndian(this.buffer);
result = BinaryPrimitives.ReadInt32BigEndian(buffer);
return true;
}

51
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -38,15 +38,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Reusable buffer for writing general data.
/// </summary>
private readonly byte[] buffer = new byte[8];
/// <summary>
/// Reusable buffer for writing chunk data.
/// </summary>
private readonly byte[] chunkDataBuffer = new byte[16];
private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly
/// <summary>
/// The encoder with options
@ -576,9 +571,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
filterMethod: 0,
interlaceMethod: this.interlaceMode);
header.WriteTo(this.chunkDataBuffer);
header.WriteTo(this.chunkDataBuffer.Span);
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size);
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer.Span, 0, PngHeader.Size);
}
/// <summary>
@ -652,9 +647,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer.Span);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer.Span, 0, PhysicalChunkData.Size);
}
/// <summary>
@ -880,9 +875,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// 4-byte unsigned integer of gamma * 100,000.
uint gammaValue = (uint)(this.gamma * 100_000F);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span.Slice(0, 4), gammaValue);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4);
}
}
@ -899,7 +894,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
Span<byte> alpha = this.chunkDataBuffer.AsSpan();
Span<byte> alpha = this.chunkDataBuffer.Span;
if (pngMetadata.ColorType == PngColorType.Rgb)
{
if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit)
@ -909,7 +904,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
}
else if (pngMetadata.TransparentRgb24.HasValue)
{
@ -918,7 +913,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
alpha[1] = rgb.R;
alpha[3] = rgb.G;
alpha[5] = rgb.B;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
}
}
else if (pngMetadata.ColorType == PngColorType.Grayscale)
@ -926,13 +921,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
if (pngMetadata.TransparentL16.HasValue && this.use16Bit)
{
BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
}
else if (pngMetadata.TransparentL8.HasValue)
{
alpha.Clear();
alpha[1] = pngMetadata.TransparentL8.Value.PackedValue;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
}
}
}
@ -1173,12 +1168,14 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
{
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
Span<byte> buffer = stackalloc byte[8];
BinaryPrimitives.WriteInt32BigEndian(buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)type);
stream.Write(this.buffer, 0, 8);
stream.Write(buffer);
uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer
uint crc = Crc32.Calculate(buffer.Slice(4)); // Write the type buffer
if (data.Length > 0 && length > 0)
{
@ -1187,9 +1184,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
crc = Crc32.Calculate(crc, data.Slice(offset, length));
}
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
BinaryPrimitives.WriteUInt32BigEndian(buffer, crc);
stream.Write(this.buffer, 0, 4); // write the crc
stream.Write(buffer, 0, 4); // write the crc
}
/// <summary>
@ -1412,4 +1409,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
_ => PngBitDepth.Bit8
};
private unsafe struct ScratchBuffer
{
private const int Size = 16;
private fixed byte scratch[Size];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
}
}

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

@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Tga;
/// </summary>
internal sealed class TgaDecoderCore : IImageDecoderInternals
{
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
/// <summary>
/// General configuration options.
/// </summary>
@ -407,6 +402,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
bool invertX = InvertX(origin);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0);
Span<byte> rowSpan = row.GetSpan();
Span<byte> scratchBuffer = stackalloc byte[2];
for (int y = 0; y < height; y++)
{
@ -417,7 +413,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
int bytesRead = stream.Read(this.scratchBuffer, 0, 2);
int bytesRead = stream.Read(scratchBuffer);
if (bytesRead != 2)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -425,16 +421,16 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (!this.hasAlpha)
{
this.scratchBuffer[1] |= 1 << 7;
scratchBuffer[1] |= 1 << 7;
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
color.FromLa16(Unsafe.As<byte, La16>(ref MemoryMarshal.GetReference(scratchBuffer)));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref MemoryMarshal.GetReference(scratchBuffer)));
}
pixelSpan[x] = color;
@ -484,6 +480,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
bool invertX = InvertX(origin);
if (invertX)
{
Span<byte> scratchBuffer = stackalloc byte[4];
TPixel color = default;
for (int y = 0; y < height; y++)
{
@ -491,7 +488,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgr24Pixel(stream, color, x, pixelSpan);
ReadBgr24Pixel(stream, color, x, pixelSpan, scratchBuffer);
}
}
@ -558,6 +555,8 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
return;
}
Span<byte> scratchBuffer = stackalloc byte[4];
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
@ -566,14 +565,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgra32Pixel(stream, x, color, pixelRow);
this.ReadBgra32Pixel(stream, x, color, pixelRow, scratchBuffer);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadBgra32Pixel(stream, x, color, pixelRow);
this.ReadBgra32Pixel(stream, x, color, pixelRow, scratchBuffer);
}
}
}
@ -687,16 +686,16 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgr24Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan)
private static void ReadBgr24Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan, Span<byte> scratchBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = stream.Read(this.scratchBuffer, 0, 3);
int bytesRead = stream.Read(scratchBuffer, 0, 3);
if (bytesRead != 3)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel");
}
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref MemoryMarshal.GetReference(scratchBuffer)));
pixelSpan[x] = color;
}
@ -715,10 +714,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Pixel<TPixel>(BufferedReadStream stream, int x, TPixel color, Span<TPixel> pixelRow)
private void ReadBgra32Pixel<TPixel>(BufferedReadStream stream, int x, TPixel color, Span<TPixel> pixelRow, Span<byte> scratchBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = stream.Read(this.scratchBuffer, 0, 4);
int bytesRead = stream.Read(scratchBuffer, 0, 4);
if (bytesRead != 4)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel");
@ -726,8 +725,8 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Guard.NotNull(this.tgaMetadata);
byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha));
byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : scratchBuffer[3];
color.FromBgra32(new Bgra32(scratchBuffer[2], scratchBuffer[1], scratchBuffer[0], alpha));
pixelRow[x] = color;
}
@ -814,7 +813,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void UncompressRle(BufferedReadStream stream, int width, int height, Span<byte> buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
Span<byte> pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
Span<byte> pixel = stackalloc byte[bytesPerPixel];
int totalPixels = width * height;
while (uncompressedPixels < totalPixels)
{
@ -825,7 +824,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (highBit == 1)
{
int runLength = runLengthByte & 127;
int bytesRead = stream.Read(pixel, 0, bytesPerPixel);
int bytesRead = stream.Read(pixel);
if (bytesRead != bytesPerPixel)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
@ -845,7 +844,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
int bufferIdx = uncompressedPixels * bytesPerPixel;
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
{
int bytesRead = stream.Read(pixel, 0, bytesPerPixel);
int bytesRead = stream.Read(pixel);
if (bytesRead != bytesPerPixel)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");

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

@ -22,11 +22,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Reusable buffer for writing data.
/// </summary>
private readonly byte[] buffer = new byte[2];
/// <summary>
/// The color depth, in number of bits per pixel.
/// </summary>
@ -221,9 +216,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
case TgaBitsPerPixel.Pixel16:
Bgra5551 bgra5551 = new(color.ToVector4());
BinaryPrimitives.WriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
stream.WriteByte(this.buffer[0]);
stream.WriteByte(this.buffer[1]);
Span<byte> buffer = stackalloc byte[2];
BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)bgra5551.PackedValue);
stream.WriteByte(buffer[0]);
stream.WriteByte(buffer[1]);
break;

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -24,9 +24,9 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
TPixel color = default;
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@ -37,8 +37,8 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float intensity = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float intensity = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@ -50,8 +50,7 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float intensity = BitConverter.ToSingle(buffer, 0);
float intensity = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

23
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -27,7 +27,7 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@ -38,18 +38,18 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float r = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float r = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float g = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float g = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float b = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float b = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
@ -61,16 +61,13 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float r = BitConverter.ToSingle(buffer, 0);
float r = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float g = BitConverter.ToSingle(buffer, 0);
float g = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float b = BitConverter.ToSingle(buffer, 0);
float b = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);

30
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -27,7 +27,7 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@ -38,23 +38,23 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float r = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float r = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float g = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float g = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float b = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float b = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float a = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float a = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, a);
@ -66,20 +66,16 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float r = BitConverter.ToSingle(buffer, 0);
float r = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float g = BitConverter.ToSingle(buffer, 0);
float g = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float b = BitConverter.ToSingle(buffer, 0);
float b = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float a = BitConverter.ToSingle(buffer, 0);
float a = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, a);

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -26,7 +26,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@ -37,8 +37,8 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float intensity = 1.0f - BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@ -50,8 +50,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

38
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -29,11 +29,6 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The global configuration.
/// </summary>
@ -157,7 +152,9 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
using TiffStreamWriter writer = new(stream);
long ifdMarker = WriteHeader(writer);
Span<byte> buffer = stackalloc byte[4];
long ifdMarker = WriteHeader(writer, buffer);
Image<TPixel> metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
@ -171,7 +168,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
long currentOffset = writer.BaseStream.Position;
foreach ((long, uint) marker in this.frameMarkers)
{
writer.WriteMarkerFast(marker.Item1, marker.Item2);
writer.WriteMarkerFast(marker.Item1, marker.Item2, buffer);
}
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
@ -181,14 +178,15 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// Writes the TIFF file header.
/// </summary>
/// <param name="writer">The <see cref="TiffStreamWriter" /> to write data to.</param>
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
/// <returns>
/// The marker to write the first IFD offset.
/// </returns>
public static long WriteHeader(TiffStreamWriter writer)
public static long WriteHeader(TiffStreamWriter writer, Span<byte> buffer)
{
writer.Write(ByteOrderMarker);
writer.Write(TiffConstants.HeaderMagicNumber);
return writer.PlaceMarker();
writer.Write(ByteOrderMarker, buffer);
writer.Write(TiffConstants.HeaderMagicNumber, buffer);
return writer.PlaceMarker(buffer);
}
/// <summary>
@ -307,20 +305,22 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
writer.Write((ushort)entries.Count);
Span<byte> buffer = stackalloc byte[4];
writer.Write((ushort)entries.Count, buffer);
foreach (IExifValue entry in entries)
{
writer.Write((ushort)entry.Tag);
writer.Write((ushort)entry.DataType);
writer.Write(ExifWriter.GetNumberOfComponents(entry));
writer.Write((ushort)entry.Tag, buffer);
writer.Write((ushort)entry.DataType, buffer);
writer.Write(ExifWriter.GetNumberOfComponents(entry), buffer);
uint length = ExifWriter.GetLength(entry);
if (length <= 4)
{
int sz = ExifWriter.WriteValue(entry, this.buffer, 0);
int sz = ExifWriter.WriteValue(entry, buffer, 0);
DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written");
writer.WritePadded(this.buffer.AsSpan(0, sz));
writer.WritePadded(buffer.Slice(0, sz));
}
else
{
@ -328,12 +328,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
int sz = ExifWriter.WriteValue(entry, raw, 0);
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
largeDataBlocks.Add(raw);
writer.Write(dataOffset);
writer.Write(dataOffset, buffer);
dataOffset += (uint)(raw.Length + (raw.Length % 2));
}
}
long nextIfdMarker = writer.PlaceMarker();
long nextIfdMarker = writer.PlaceMarker(buffer);
foreach (byte[] dataBlock in largeDataBlocks)
{

44
src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs

@ -10,13 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers;
/// </summary>
internal sealed class TiffStreamWriter : IDisposable
{
private static readonly byte[] PaddingBytes = new byte[4];
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Initializes a new instance of the <see cref="TiffStreamWriter"/> class.
/// </summary>
@ -41,11 +34,12 @@ internal sealed class TiffStreamWriter : IDisposable
/// <summary>
/// Writes an empty four bytes to the stream, returning the offset to be written later.
/// </summary>
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
/// <returns>The offset to be written later.</returns>
public long PlaceMarker()
public long PlaceMarker(Span<byte> buffer)
{
long offset = this.BaseStream.Position;
this.Write(0u);
this.Write(0u, buffer);
return offset;
}
@ -71,36 +65,38 @@ internal sealed class TiffStreamWriter : IDisposable
/// Writes a two-byte unsigned integer to the current stream.
/// </summary>
/// <param name="value">The two-byte unsigned integer to write.</param>
public void Write(ushort value)
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
public void Write(ushort value, Span<byte> buffer)
{
if (IsLittleEndian)
{
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value);
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
}
else
{
BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value);
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
}
this.BaseStream.Write(this.buffer.AsSpan(0, 2));
this.BaseStream.Write(buffer.Slice(0, 2));
}
/// <summary>
/// Writes a four-byte unsigned integer to the current stream.
/// </summary>
/// <param name="value">The four-byte unsigned integer to write.</param>
public void Write(uint value)
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
public void Write(uint value, Span<byte> buffer)
{
if (IsLittleEndian)
{
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
}
else
{
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value);
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
}
this.BaseStream.Write(this.buffer.AsSpan(0, 4));
this.BaseStream.Write(buffer.Slice(0, 4));
}
/// <summary>
@ -113,7 +109,10 @@ internal sealed class TiffStreamWriter : IDisposable
if (value.Length % 4 != 0)
{
this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4));
// No allocation occurs, refers directly to assembly's data segment.
ReadOnlySpan<byte> paddingBytes = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
paddingBytes = paddingBytes[..(4 - (value.Length % 4))];
this.BaseStream.Write(paddingBytes);
}
}
@ -122,18 +121,19 @@ internal sealed class TiffStreamWriter : IDisposable
/// </summary>
/// <param name="offset">The offset returned when placing the marker</param>
/// <param name="value">The four-byte unsigned integer to write.</param>
public void WriteMarker(long offset, uint value)
/// <param name="buffer">Scratch buffer.</param>
public void WriteMarker(long offset, uint value, Span<byte> buffer)
{
long back = this.BaseStream.Position;
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.Write(value, buffer);
this.BaseStream.Seek(back, SeekOrigin.Begin);
}
public void WriteMarkerFast(long offset, uint value)
public void WriteMarkerFast(long offset, uint value, Span<byte> buffer)
{
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.Write(value, buffer);
}
/// <summary>

2
src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

@ -192,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes()
{
System.Span<byte> dataSpan = this.Data!.Memory.Span;
Span<byte> dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;

23
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@ -23,7 +24,7 @@ internal abstract class BitWriterBase
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
private ScratchBuffer scratchBuffer; // mutable struct, don't make readonly
/// <summary>
/// Initializes a new instance of the <see cref="BitWriterBase"/> class.
@ -90,8 +91,8 @@ internal abstract class BitWriterBase
protected void WriteRiffHeader(Stream stream, uint riffSize)
{
stream.Write(WebpConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize);
stream.Write(this.scratchBuffer.AsSpan(0, 4));
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
stream.Write(this.scratchBuffer.Span.Slice(0, 4));
stream.Write(WebpConstants.WebpHeader);
}
@ -128,7 +129,7 @@ internal abstract class BitWriterBase
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
uint size = (uint)metadataBytes.Length;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@ -151,7 +152,7 @@ internal abstract class BitWriterBase
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@ -182,7 +183,7 @@ internal abstract class BitWriterBase
{
uint size = (uint)iccProfileBytes.Length;
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@ -245,7 +246,7 @@ internal abstract class BitWriterBase
flags |= 32;
}
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
@ -256,4 +257,12 @@ internal abstract class BitWriterBase
BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
stream.Write(buf[..3]);
}
private unsafe struct ScratchBuffer
{
private const int Size = 4;
private fixed byte scratch[Size];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
}
}

15
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
/// </summary>
internal class Vp8LBitWriter : BitWriterBase
{
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBuffer = new byte[8];
/// <summary>
/// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed.
/// </summary>
@ -194,8 +189,9 @@ internal class Vp8LBitWriter : BitWriterBase
stream.Write(WebpConstants.Vp8LMagicBytes);
// Write Vp8 Header.
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size);
stream.Write(this.scratchBuffer.AsSpan(0, 4));
Span<byte> scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size);
stream.Write(scratchBuffer.Slice(0, 4));
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
// Write the encoded bytes of the image to the stream.
@ -228,8 +224,9 @@ internal class Vp8LBitWriter : BitWriterBase
this.BitWriterResize(extraSize);
}
BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits);
this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
Span<byte> scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits);
scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
this.cur += WriterBytes;
this.bits >>= WriterBits;

32
src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs

@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
internal class HistogramEncoder
internal static class HistogramEncoder
{
/// <summary>
/// Number of partitions for the three dominant (literal, red and blue) symbol costs.
@ -27,7 +27,7 @@ internal class HistogramEncoder
private const ushort InvalidHistogramSymbol = ushort.MaxValue;
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols)
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, Span<ushort> histogramSymbols)
{
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1;
int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1;
@ -148,7 +148,7 @@ internal class HistogramEncoder
}
}
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols)
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, Span<ushort> histogramSymbols)
{
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
@ -171,20 +171,28 @@ internal class HistogramEncoder
}
}
int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol);
int numUsed = 0;
foreach (ushort h in histogramSymbols)
{
if (h != InvalidHistogramSymbol)
{
numUsed++;
}
}
return numUsed;
}
private static void HistogramCombineEntropyBin(
List<Vp8LHistogram> histograms,
ushort[] clusters,
Span<ushort> clusters,
ushort[] clusterMappings,
Vp8LHistogram curCombo,
ushort[] binMap,
int numBins,
double combineCostFactor)
{
var binInfo = new HistogramBinInfo[BinSize];
Span<HistogramBinInfo> binInfo = stackalloc HistogramBinInfo[BinSize];
for (int idx = 0; idx < numBins; idx++)
{
binInfo[idx].First = -1;
@ -258,7 +266,7 @@ internal class HistogramEncoder
/// Given a Histogram set, the mapping of clusters 'clusterMapping' and the
/// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values.
/// </summary>
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols)
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span<ushort> symbols)
{
bool doContinue = true;
@ -331,7 +339,7 @@ internal class HistogramEncoder
int maxSize = 9;
// Fill the initial mapping.
int[] mappings = new int[histograms.Count];
Span<int> mappings = histograms.Count <= 64 ? stackalloc int[histograms.Count] : new int[histograms.Count];
for (int j = 0, iter = 0; iter < histograms.Count; iter++)
{
if (histograms[iter] == null)
@ -388,9 +396,9 @@ internal class HistogramEncoder
int bestIdx1 = histoPriorityList[0].Idx1;
int bestIdx2 = histoPriorityList[0].Idx2;
int mappingIndex = Array.IndexOf(mappings, bestIdx2);
Span<int> src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> dst = mappings.AsSpan(mappingIndex);
int mappingIndex = mappings.IndexOf(bestIdx2);
Span<int> src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> dst = mappings.Slice(mappingIndex);
src.CopyTo(dst);
// Merge the histograms and remove bestIdx2 from the list.
@ -528,7 +536,7 @@ internal class HistogramEncoder
}
}
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, ushort[] symbols)
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, Span<ushort> symbols)
{
int inSize = input.Count;
int outSize = output.Count;

26
src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs

@ -25,7 +25,7 @@ internal static class HuffmanUtils
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf
};
public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode)
public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, Span<HuffmanTree> huffTree, HuffmanTreeCode huffCode)
{
int numSymbols = huffCode.NumSymbols;
bufRle.AsSpan().Clear();
@ -159,7 +159,7 @@ internal static class HuffmanUtils
/// <param name="histogramSize">The size of the histogram.</param>
/// <param name="treeDepthLimit">The tree depth limit.</param>
/// <param name="bitDepths">How many bits are used for the symbol.</param>
public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
public static void GenerateOptimalTree(Span<HuffmanTree> tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
{
uint countMin;
int treeSizeOrig = 0;
@ -177,7 +177,7 @@ internal static class HuffmanUtils
return;
}
Span<HuffmanTree> treePool = tree.AsSpan(treeSizeOrig);
Span<HuffmanTree> treePool = tree.Slice(treeSizeOrig);
// For block sizes with less than 64k symbols we never need to do a
// second iteration of this loop.
@ -202,14 +202,8 @@ internal static class HuffmanUtils
}
// Build the Huffman tree.
#if NET5_0_OR_GREATER
Span<HuffmanTree> treeSlice = tree.AsSpan(0, treeSize);
Span<HuffmanTree> treeSlice = tree.Slice(0, treeSize);
treeSlice.Sort(HuffmanTree.Compare);
#else
HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray();
Array.Sort(treeCopy, HuffmanTree.Compare);
treeCopy.AsSpan().CopyTo(tree);
#endif
if (treeSize > 1)
{
@ -312,12 +306,12 @@ internal static class HuffmanUtils
DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
// sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
int[] sorted = new int[codeLengthsSize];
Span<int> sorted = codeLengthsSize <= 64 ? stackalloc int[codeLengthsSize] : new int[codeLengthsSize];
int totalSize = 1 << rootBits; // total size root table + 2nd level table.
int len; // current code length.
int symbol; // symbol index in original or sorted table.
int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
Span<int> counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
Span<int> offsets = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
@ -544,8 +538,8 @@ internal static class HuffmanUtils
private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree)
{
// 0 bit-depth means that the symbol does not exist.
uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1];
int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1];
Span<uint> nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1];
Span<int> depthCount = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1];
int len = tree.NumSymbols;
for (int i = 0; i < len; i++)
@ -603,7 +597,7 @@ internal static class HuffmanUtils
/// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
/// len is the code length of the next processed symbol.
/// </summary>
private static int NextTableBitSize(int[] count, int len, int rootBits)
private static int NextTableBitSize(ReadOnlySpan<int> count, int len, int rootBits)
{
int left = 1 << (len - rootBits);
while (len < WebpConstants.MaxAllowedCodeLength)

16
src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs

@ -57,11 +57,13 @@ internal static unsafe class PredictorEncoder
Span<short> scratch = stackalloc short[8];
// TODO: Can we optimize this?
int[][] histo = new int[4][];
for (int i = 0; i < 4; i++)
int[][] histo =
{
histo[i] = new int[256];
}
new int[256],
new int[256],
new int[256],
new int[256]
};
if (lowEffort)
{
@ -233,7 +235,7 @@ internal static unsafe class PredictorEncoder
Span<byte> maxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow[(width + 1)..]);
float bestDiff = MaxDiffCost;
int bestMode = 0;
uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
Span<uint> residuals = stackalloc uint[1 << WebpConstants.MaxTransformBits]; // 256 bytes
for (int i = 0; i < 4; i++)
{
histoArgb[i].AsSpan().Clear();
@ -299,9 +301,7 @@ internal static unsafe class PredictorEncoder
if (curDiff < bestDiff)
{
int[][] tmp = histoArgb;
histoArgb = bestHisto;
bestHisto = tmp;
(bestHisto, histoArgb) = (histoArgb, bestHisto);
bestDiff = curDiff;
bestMode = mode;
}

58
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -23,7 +23,7 @@ internal class Vp8LEncoder : IDisposable
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[256];
private ScratchBuffer scratch; // mutable struct, don't make readonly
private readonly int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] };
@ -549,12 +549,8 @@ internal class Vp8LEncoder : IDisposable
// bgra data with transformations applied.
Span<uint> bgra = this.EncodedData.GetSpan();
int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits);
ushort[] histogramSymbols = new ushort[histogramImageXySize];
HuffmanTree[] huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes];
for (int i = 0; i < huffTree.Length; i++)
{
huffTree[i] = default;
}
Span<ushort> histogramSymbols = histogramImageXySize <= 64 ? stackalloc ushort[histogramImageXySize] : new ushort[histogramImageXySize];
Span<HuffmanTree> huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes];
if (useCache)
{
@ -607,10 +603,6 @@ internal class Vp8LEncoder : IDisposable
int histogramImageSize = histogramImage.Count;
int bitArraySize = 5 * histogramImageSize;
HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[bitArraySize];
for (int i = 0; i < huffmanCodes.Length; i++)
{
huffmanCodes[i] = default;
}
GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes);
@ -702,7 +694,7 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
private void EncodePalette(bool lowEffort)
{
Span<uint> tmpPalette = new uint[WebpConstants.MaxPaletteSize];
Span<uint> tmpPalette = stackalloc uint[WebpConstants.MaxPaletteSize];
int paletteSize = this.PaletteSize;
Span<uint> palette = this.Palette.Memory.Span;
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
@ -763,7 +755,7 @@ internal class Vp8LEncoder : IDisposable
int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits);
int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits);
PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch);
PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch.Span);
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2);
@ -778,16 +770,7 @@ internal class Vp8LEncoder : IDisposable
ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol.
HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[5];
for (int i = 0; i < huffmanCodes.Length; i++)
{
huffmanCodes[i] = default;
}
HuffmanTree[] huffTree = new HuffmanTree[3UL * WebpConstants.CodeLengthCodes];
for (int i = 0; i < huffTree.Length; i++)
{
huffTree[i] = default;
}
Span<HuffmanTree> huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes];
// Calculate backward references from the image pixels.
hashChain.Fill(bgra, quality, width, height, lowEffort);
@ -847,10 +830,10 @@ internal class Vp8LEncoder : IDisposable
this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes);
}
private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
private void StoreHuffmanCode(Span<HuffmanTree> huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
{
int count = 0;
Span<int> symbols = this.scratch.AsSpan(0, 2);
Span<int> symbols = this.scratch.Span.Slice(0, 2);
symbols.Clear();
const int maxBits = 8;
const int maxSymbol = 1 << maxBits;
@ -901,7 +884,7 @@ internal class Vp8LEncoder : IDisposable
}
}
private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree)
private void StoreFullHuffmanCode(Span<HuffmanTree> huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree)
{
int i;
byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes];
@ -1013,7 +996,7 @@ internal class Vp8LEncoder : IDisposable
}
}
private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes)
private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, Span<ushort> histogramSymbols, HuffmanTreeCode[] huffmanCodes)
{
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1;
int tileMask = histoBits == 0 ? 0 : -(1 << histoBits);
@ -1143,8 +1126,8 @@ internal class Vp8LEncoder : IDisposable
prevRow = currentRow;
}
double[] entropyComp = new double[(int)HistoIx.HistoTotal];
double[] entropy = new double[(int)EntropyIx.NumEntropyIx];
Span<double> entropyComp = stackalloc double[(int)HistoIx.HistoTotal];
Span<double> entropy = stackalloc double[(int)EntropyIx.NumEntropyIx];
int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen;
// Let's add one zero to the predicted histograms. The zeros are removed
@ -1647,11 +1630,7 @@ internal class Vp8LEncoder : IDisposable
// Create Huffman trees.
bool[] bufRle = new bool[maxNumSymbols];
HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols];
for (int i = 0; i < huffTree.Length; i++)
{
huffTree[i] = default;
}
Span<HuffmanTree> huffTree = stackalloc HuffmanTree[3 * maxNumSymbols];
for (int i = 0; i < histogramImage.Count; i++)
{
@ -1849,4 +1828,15 @@ internal class Vp8LEncoder : IDisposable
this.TransformData.Dispose();
this.HashChain.Dispose();
}
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private unsafe struct ScratchBuffer
{
private const int Size = 256;
private fixed int scratch[Size];
public Span<int> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
}
}

5
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -498,10 +498,7 @@ internal sealed class WebpLosslessDecoder
private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span<HuffmanCode> table)
{
bool simpleCode = this.bitReader.ReadBit();
for (int i = 0; i < alphabetSize; i++)
{
codeLengths[i] = 0;
}
codeLengths.AsSpan(0, alphabetSize).Clear();
if (simpleCode)
{

2
src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs

@ -121,7 +121,7 @@ internal static unsafe class QuantEnc
var rdi4 = new Vp8ModeScore();
var rdTmp = new Vp8ModeScore();
var res = new Vp8Residual();
Span<short> tmpLevels = new short[16];
Span<short> tmpLevels = stackalloc short[16];
do
{
const int numBlocks = 1;

8
src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs

@ -374,7 +374,7 @@ internal class Vp8EncIterator
}
else
{
byte[] modes = new byte[16]; // DC4
Span<byte> modes = stackalloc byte[16]; // DC4
this.SetIntra4Mode(modes);
}
@ -407,7 +407,7 @@ internal class Vp8EncIterator
public int MbAnalyzeBestIntra4Mode(int bestAlpha)
{
byte[] modes = new byte[16];
Span<byte> modes = stackalloc byte[16];
const int maxMode = MaxIntra4Mode;
Vp8Histogram totalHisto = new();
int curHisto = 0;
@ -494,13 +494,13 @@ internal class Vp8EncIterator
this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16;
}
public void SetIntra4Mode(byte[] modes)
public void SetIntra4Mode(ReadOnlySpan<byte> modes)
{
int modesIdx = 0;
int predIdx = this.PredIdx;
for (int y = 4; y > 0; y--)
{
modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx));
modes.Slice(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx));
predIdx += this.predsWidth;
modesIdx += 4;
}

20
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -329,7 +329,7 @@ internal class Vp8Encoder : IDisposable
int uvStride = (yStride + 1) >> 1;
Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh);
int[] alphas = new int[WebpConstants.MaxAlpha + 1];
Span<int> alphas = stackalloc int[WebpConstants.MaxAlpha + 1];
this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha);
int totalMb = this.Mbw * this.Mbw;
this.alpha /= totalMb;
@ -625,15 +625,15 @@ internal class Vp8Encoder : IDisposable
}
// Simplified k-Means, to assign Nb segments based on alpha-histogram.
private void AssignSegments(int[] alphas)
private void AssignSegments(ReadOnlySpan<int> alphas)
{
int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments;
int[] centers = new int[NumMbSegments];
Span<int> centers = stackalloc int[NumMbSegments];
int weightedAverage = 0;
int[] map = new int[WebpConstants.MaxAlpha + 1];
Span<int> map = stackalloc int[WebpConstants.MaxAlpha + 1];
int n, k;
int[] accum = new int[NumMbSegments];
int[] distAccum = new int[NumMbSegments];
Span<int> accum = stackalloc int[NumMbSegments];
Span<int> distAccum = stackalloc int[NumMbSegments];
// Bracket the input.
for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n)
@ -719,7 +719,7 @@ internal class Vp8Encoder : IDisposable
this.SetSegmentAlphas(centers, weightedAverage);
}
private void SetSegmentAlphas(int[] centers, int mid)
private void SetSegmentAlphas(ReadOnlySpan<int> centers, int mid)
{
int nb = this.SegmentHeader.NumSegments;
Vp8SegmentInfo[] dqm = this.SegmentInfos;
@ -840,7 +840,7 @@ internal class Vp8Encoder : IDisposable
private void SetSegmentProbas()
{
int[] p = new int[NumMbSegments];
Span<int> p = stackalloc int[NumMbSegments];
int n;
for (n = 0; n < this.Mbw * this.Mbh; ++n)
@ -931,7 +931,7 @@ internal class Vp8Encoder : IDisposable
}
}
private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span<byte> y, Span<byte> u, Span<byte> v, int yStride, int uvStride, int[] alphas, out int uvAlpha)
private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span<byte> y, Span<byte> u, Span<byte> v, int yStride, int uvStride, Span<int> alphas, out int uvAlpha)
{
int alpha = 0;
uvAlpha = 0;
@ -952,7 +952,7 @@ internal class Vp8Encoder : IDisposable
return alpha;
}
private int MbAnalyze(Vp8EncIterator it, int[] alphas, out int bestUvAlpha)
private int MbAnalyze(Vp8EncIterator it, Span<int> alphas, out int bestUvAlpha)
{
it.SetIntra16Mode(0); // default: Intra16, DC_PRED
it.SetSkip(false); // not skipped.

25
src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs

@ -10,12 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy;
internal sealed class Vp8Histogram
{
private readonly int[] scratch = new int[16];
private readonly short[] output = new short[16];
private readonly int[] distribution = new int[MaxCoeffThresh + 1];
/// <summary>
/// Size of histogram used by CollectHistogram.
/// </summary>
@ -47,17 +41,20 @@ internal sealed class Vp8Histogram
public void CollectHistogram(Span<byte> reference, Span<byte> pred, int startBlock, int endBlock)
{
Span<int> scratch = stackalloc int[16];
Span<short> output = stackalloc short[16];
Span<int> distribution = stackalloc int[MaxCoeffThresh + 1];
int j;
this.distribution.AsSpan().Clear();
for (j = startBlock; j < endBlock; j++)
{
Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], this.output, this.scratch);
Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], output, scratch);
// Convert coefficients to bin.
if (Avx2.IsSupported)
{
// Load.
ref short outputRef = ref MemoryMarshal.GetReference<short>(this.output);
ref short outputRef = ref MemoryMarshal.GetReference(output);
Vector256<byte> out0 = Unsafe.As<short, Vector256<byte>>(ref outputRef);
// v = abs(out) >> 3
@ -73,21 +70,21 @@ internal sealed class Vp8Histogram
// Convert coefficients to bin.
for (int k = 0; k < 16; ++k)
{
++this.distribution[this.output[k]];
++distribution[output[k]];
}
}
else
{
for (int k = 0; k < 16; ++k)
{
int v = Math.Abs(this.output[k]) >> 3;
int v = Math.Abs(output[k]) >> 3;
int clippedValue = ClipMax(v, MaxCoeffThresh);
++this.distribution[clippedValue];
++distribution[clippedValue];
}
}
}
this.SetHistogramData(this.distribution);
this.SetHistogramData(distribution);
}
public void Merge(Vp8Histogram other)
@ -103,7 +100,7 @@ internal sealed class Vp8Histogram
}
}
private void SetHistogramData(int[] distribution)
private void SetHistogramData(ReadOnlySpan<int> distribution)
{
int maxValue = 0;
int lastNonZero = 1;

11
src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

@ -15,10 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy;
/// </summary>
internal class Vp8Residual
{
private readonly byte[] scratch = new byte[32];
private readonly ushort[] scratchUShort = new ushort[16];
public int First { get; set; }
public int Last { get; set; }
@ -162,9 +158,10 @@ internal class Vp8Residual
if (Avx2.IsSupported)
{
Span<byte> ctxs = this.scratch.AsSpan(0, 16);
Span<byte> levels = this.scratch.AsSpan(16, 16);
Span<ushort> absLevels = this.scratchUShort.AsSpan();
Span<byte> scratch = stackalloc byte[32];
Span<byte> ctxs = scratch.Slice(0, 16);
Span<byte> levels = scratch.Slice(16);
Span<ushort> absLevels = stackalloc ushort[16];
// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference<short>(this.Coeffs);

27
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -34,16 +34,6 @@ internal sealed class WebpLossyDecoder
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Scratch buffer to reduce allocations.
/// </summary>
private readonly int[] scratch = new int[16];
/// <summary>
/// Another scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] scratchBytes = new byte[4];
/// <summary>
/// Initializes a new instance of the <see cref="WebpLossyDecoder"/> class.
/// </summary>
@ -371,6 +361,9 @@ internal sealed class WebpLossyDecoder
}
}
Span<int> scratch = stackalloc int[16];
Span<byte> scratchBytes = stackalloc byte[4];
// Reconstruct one row.
for (int mbx = 0; mbx < dec.MbWidth; mbx++)
{
@ -448,7 +441,7 @@ internal sealed class WebpLossyDecoder
LossyUtils.TM4(dst, yuv, offset);
break;
case 2:
LossyUtils.VE4(dst, yuv, offset, this.scratchBytes);
LossyUtils.VE4(dst, yuv, offset, scratchBytes);
break;
case 3:
LossyUtils.HE4(dst, yuv, offset);
@ -473,7 +466,7 @@ internal sealed class WebpLossyDecoder
break;
}
DoTransform(bits, coeffs.AsSpan(n * 16), dst, this.scratch);
DoTransform(bits, coeffs.AsSpan(n * 16), dst, scratch);
}
}
else
@ -508,7 +501,7 @@ internal sealed class WebpLossyDecoder
{
for (int n = 0; n < 16; ++n, bits <<= 2)
{
DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], this.scratch);
DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], scratch);
}
}
}
@ -547,8 +540,8 @@ internal sealed class WebpLossyDecoder
break;
}
DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, this.scratch);
DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, this.scratch);
DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, scratch);
DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, scratch);
// Stash away top samples for next block.
if (mby < dec.MbHeight - 1)
@ -875,14 +868,14 @@ internal sealed class WebpLossyDecoder
else
{
// Parse DC
short[] dc = new short[16];
Span<short> dc = stackalloc short[16];
int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs);
int nz = GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc);
mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0);
if (nz > 1)
{
// More than just the DC -> perform the full transform.
LossyUtils.TransformWht(dc, dst, this.scratch);
LossyUtils.TransformWht(dc, dst, stackalloc int[16]);
}
else
{

35
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal class WebpAnimationDecoder : IDisposable
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Used for allocating memory during the decoding operations.
/// </summary>
@ -89,11 +84,12 @@ internal class WebpAnimationDecoder : IDisposable
this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount;
Span<byte> buffer = stackalloc byte[4];
uint frameCount = 0;
int remainingBytes = (int)completeDataSize;
while (remainingBytes > 0)
{
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
remainingBytes -= 4;
switch (chunkType)
{
@ -103,7 +99,7 @@ internal class WebpAnimationDecoder : IDisposable
break;
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer);
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, buffer);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
@ -134,15 +130,16 @@ internal class WebpAnimationDecoder : IDisposable
{
AnimationFrameData frameData = this.ReadFrameHeader(stream);
long streamStartPosition = stream.Position;
Span<byte> buffer = stackalloc byte[4];
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
bool hasAlpha = false;
byte alphaChunkHeader = 0;
if (chunkType is WebpChunkType.Alpha)
{
alphaChunkHeader = this.ReadAlphaData(stream);
hasAlpha = true;
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
}
WebpImageInfo? webpInfo = null;
@ -150,12 +147,12 @@ internal class WebpAnimationDecoder : IDisposable
switch (chunkType)
{
case WebpChunkType.Vp8:
webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
features.Alpha = hasAlpha;
features.AlphaChunkHeader = alphaChunkHeader;
break;
case WebpChunkType.Vp8L:
webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected chunk type, should be VP8 or VP8L");
@ -226,7 +223,7 @@ internal class WebpAnimationDecoder : IDisposable
{
this.alphaData?.Dispose();
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, stackalloc byte[4]);
int alphaDataSize = (int)(alphaChunkSize - 1);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
@ -353,24 +350,26 @@ internal class WebpAnimationDecoder : IDisposable
/// <returns>Animation frame data.</returns>
private AnimationFrameData ReadFrameHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[4];
AnimationFrameData data = new()
{
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer),
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
// 3 bytes for the X coordinate of the upper left corner of the frame.
X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer),
X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer),
// 3 bytes for the Y coordinate of the upper left corner of the frame.
Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer),
Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer),
// Frame width Minus One.
Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1,
Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
// Frame height Minus One.
Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1,
Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
// Frame duration.
Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer)
Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer)
};
byte flags = (byte)stream.ReadByte();

26
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -18,7 +18,7 @@ internal static class WebpChunkParsingUtils
/// Reads the header of a lossy webp image.
/// </summary>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
// VP8 data size (not including this 4 bytes).
int bytesRead = stream.Read(buffer, 0, 4);
@ -77,7 +77,7 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes");
}
if (!buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
if (!buffer.Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
{
WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
}
@ -91,7 +91,7 @@ internal static class WebpChunkParsingUtils
uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
uint width = tmp & 0x3fff;
sbyte xScale = (sbyte)(tmp >> 6);
tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2));
tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2));
uint height = tmp & 0x3fff;
sbyte yScale = (sbyte)(tmp >> 6);
remaining -= 7;
@ -140,7 +140,7 @@ internal static class WebpChunkParsingUtils
/// Reads the header of a lossless webp image.
/// </summary>
/// <returns>Information about this image.</returns>
public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
// VP8 data size.
uint imageDataSize = ReadChunkSize(stream, buffer);
@ -195,7 +195,7 @@ internal static class WebpChunkParsingUtils
/// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow.
/// </summary>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features)
public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
uint fileSize = ReadChunkSize(stream, buffer);
@ -253,7 +253,7 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the read data into.</param>
/// <returns>A unsigned 24 bit integer.</returns>
public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer)
public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@ -271,9 +271,11 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read the data from.</param>
/// <param name="buffer">Buffer to store the data read from the stream.</param>
/// <returns>The chunk size in bytes.</returns>
public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer)
public static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 4) == 4)
DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length");
if (stream.Read(buffer) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
@ -290,9 +292,11 @@ internal static class WebpChunkParsingUtils
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer)
public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 4) == 4)
DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length");
if (stream.Read(buffer) == 4)
{
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
return chunkType;
@ -306,7 +310,7 @@ internal static class WebpChunkParsingUtils
/// If there are more such chunks, readers MAY ignore all except the first one.
/// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
/// </summary>
public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, byte[] buffer)
public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, Span<byte> buffer)
{
long streamLength = stream.Length;
while (stream.Position < streamLength)

104
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// General configuration options.
/// </summary>
@ -80,8 +75,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
try
{
ImageMetadata metadata = new();
Span<byte> buffer = stackalloc byte[4];
uint fileSize = this.ReadImageHeader(stream);
uint fileSize = ReadImageHeader(stream, buffer);
using (this.webImageInfo = this.ReadVp8Info(stream, metadata))
{
@ -112,7 +108,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
// There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null)
{
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features);
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features, buffer);
}
return image;
@ -128,7 +124,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeader(stream);
ReadImageHeader(stream, stackalloc byte[4]);
ImageMetadata metadata = new();
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
@ -144,8 +140,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// Reads and skips over the image header.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <returns>The file size in bytes.</returns>
private uint ReadImageHeader(BufferedReadStream stream)
private static uint ReadImageHeader(BufferedReadStream stream, Span<byte> buffer)
{
// Skip FourCC header, we already know its a RIFF file at this point.
stream.Skip(4);
@ -153,7 +150,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
// Read file size.
// The size of the file in bytes starting at offset 8.
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer);
// Skip 'WEBP' from the header.
stream.Skip(4);
@ -172,35 +169,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
Span<byte> buffer = stackalloc byte[4];
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
case WebpChunkType.Vp8L:
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
case WebpChunkType.Vp8X:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features);
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features);
while (stream.Position < stream.Length)
{
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
if (chunkType == WebpChunkType.Vp8)
{
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
}
else if (chunkType == WebpChunkType.Vp8L)
{
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
}
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha);
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer);
if (isAnimationChunk)
{
return webpInfos;
@ -209,7 +207,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
else
{
// Ignore unknown chunks.
uint chunkSize = this.ReadChunkSize(stream);
uint chunkSize = ReadChunkSize(stream, buffer);
stream.Skip((int)chunkSize);
}
}
@ -230,34 +228,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
bool ignoreAlpha)
bool ignoreAlpha,
Span<byte> buffer)
{
switch (chunkType)
{
case WebpChunkType.Iccp:
this.ReadIccProfile(stream, metadata);
this.ReadIccProfile(stream, metadata, buffer);
break;
case WebpChunkType.Exif:
this.ReadExifProfile(stream, metadata);
this.ReadExifProfile(stream, metadata, buffer);
break;
case WebpChunkType.Xmp:
this.ReadXmpProfile(stream, metadata);
this.ReadXmpProfile(stream, metadata, buffer);
break;
case WebpChunkType.AnimationParameter:
this.ReadAnimationParameters(stream, features);
ReadAnimationParameters(stream, features, buffer);
return true;
case WebpChunkType.Alpha:
this.ReadAlphaData(stream, features, ignoreAlpha);
this.ReadAlphaData(stream, features, ignoreAlpha, buffer);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -273,7 +273,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="features">The webp features.</param>
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
/// <param name="buffer">Temporary buffer.</param>
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span<byte> buffer)
{
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{
@ -284,19 +285,19 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
while (stream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType(stream);
WebpChunkType chunkType = ReadChunkType(stream, buffer);
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
{
this.ReadExifProfile(stream, metadata);
this.ReadExifProfile(stream, metadata, buffer);
}
else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null)
{
this.ReadXmpProfile(stream, metadata);
this.ReadXmpProfile(stream, metadata, buffer);
}
else
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize(stream);
uint chunkLength = ReadChunkSize(stream, buffer);
stream.Skip((int)chunkLength);
}
}
@ -307,9 +308,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint exifChunkSize = this.ReadChunkSize(stream);
uint exifChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)exifChunkSize);
@ -333,9 +335,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint xmpChunkSize = this.ReadChunkSize(stream);
uint xmpChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)xmpChunkSize);
@ -359,9 +362,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
/// <param name="buffer">Temporary buffer.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint iccpChunkSize = this.ReadChunkSize(stream);
uint iccpChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)iccpChunkSize);
@ -388,22 +392,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
/// <param name="buffer">Temporary buffer.</param>
private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span<byte> buffer)
{
features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer);
byte blue = (byte)stream.ReadByte();
byte green = (byte)stream.ReadByte();
byte red = (byte)stream.ReadByte();
byte alpha = (byte)stream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
int bytesRead = stream.Read(this.buffer, 0, 2);
int bytesRead = stream.Read(buffer, 0, 2);
if (bytesRead != 2)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
}
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer);
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
}
/// <summary>
@ -412,9 +417,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
/// <param name="buffer">Temporary buffer.</param>
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span<byte> buffer)
{
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer);
if (ignoreAlpha)
{
stream.Skip((int)alphaChunkSize);
@ -436,14 +442,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private WebpChunkType ReadChunkType(BufferedReadStream stream)
private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(buffer, 0, 4) == 4)
{
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
throw new ImageFormatException("Invalid Webp data.");
@ -454,13 +461,14 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// so the chunk size will be increased by 1 in those cases.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <returns>The chunk size in bytes.</returns>
/// <exception cref="ImageFormatException">Invalid data.</exception>
private uint ReadChunkSize(BufferedReadStream stream)
private static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(buffer, 0, 4) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
}

33
src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

@ -86,10 +86,6 @@ internal class ExifReader : BaseExifReader
/// </summary>
internal abstract class BaseExifReader
{
private readonly byte[] buf8 = new byte[8];
private readonly byte[] buf4 = new byte[4];
private readonly byte[] buf2 = new byte[2];
private readonly MemoryAllocator? allocator;
private readonly Stream data;
private List<ExifTag>? invalidTags;
@ -528,20 +524,33 @@ internal abstract class BaseExifReader
return read == length;
}
protected ulong ReadUInt64() =>
this.TryReadSpan(this.buf8)
? this.ConvertToUInt64(this.buf8)
protected ulong ReadUInt64()
{
Span<byte> buffer = stackalloc byte[8];
return this.TryReadSpan(buffer)
? this.ConvertToUInt64(buffer)
: default;
}
// Known as Long in Exif Specification.
protected uint ReadUInt32() =>
this.TryReadSpan(this.buf4)
? this.ConvertToUInt32(this.buf4)
protected uint ReadUInt32()
{
Span<byte> buffer = stackalloc byte[4];
return this.TryReadSpan(buffer)
? this.ConvertToUInt32(buffer)
: default;
}
protected ushort ReadUInt16() => this.TryReadSpan(this.buf2)
? this.ConvertToShort(this.buf2)
protected ushort ReadUInt16()
{
Span<byte> buffer = stackalloc byte[2];
return this.TryReadSpan(buffer)
? this.ConvertToShort(buffer)
: default;
}
private long ConvertToInt64(ReadOnlySpan<byte> buffer)
{

4
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs

@ -45,12 +45,12 @@ internal sealed class ExifByteArray : ExifArrayValue<byte>
private bool TrySetSignedIntArray(int[] intArrayValue)
{
if (Array.FindIndex(intArrayValue, x => x < byte.MinValue || x > byte.MaxValue) > -1)
if (Array.FindIndex(intArrayValue, x => (uint)x > byte.MaxValue) >= 0)
{
return false;
}
var value = new byte[intArrayValue.Length];
byte[] value = new byte[intArrayValue.Length];
for (int i = 0; i < intArrayValue.Length; i++)
{
int s = intArrayValue[i];

14
src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

@ -108,10 +108,10 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
const int profileIdPos = 84;
// need to copy some values because they need to be zero for the hashing
byte[] temp = new byte[24];
Buffer.BlockCopy(data, profileFlagPos, temp, 0, 4);
Buffer.BlockCopy(data, renderingIntentPos, temp, 4, 4);
Buffer.BlockCopy(data, profileIdPos, temp, 8, 16);
Span<byte> temp = stackalloc byte[24];
data.AsSpan(profileFlagPos, 4).CopyTo(temp);
data.AsSpan(renderingIntentPos, 4).CopyTo(temp.Slice(4));
data.AsSpan(profileIdPos, 16).CopyTo(temp.Slice(8));
try
{
@ -131,9 +131,9 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
}
finally
{
Buffer.BlockCopy(temp, 0, data, profileFlagPos, 4);
Buffer.BlockCopy(temp, 4, data, renderingIntentPos, 4);
Buffer.BlockCopy(temp, 8, data, profileIdPos, 16);
temp.Slice(0, 4).CopyTo(data.AsSpan(profileFlagPos));
temp.Slice(4, 4).CopyTo(data.AsSpan(renderingIntentPos));
temp.Slice(8, 16).CopyTo(data.AsSpan(profileIdPos));
}
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs

@ -37,4 +37,12 @@ public class YCbCrColorConversion : ColorConversionBenchmark
new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values);
}
[Benchmark]
public void SimdVectorArm()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.YCbCrArm(8).ConvertToRgbInplace(values);
}
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs

@ -37,4 +37,12 @@ public class YccKColorConverter : ColorConversionBenchmark
new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values);
}
[Benchmark]
public void SimdVectorArm64()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.YccKArm64(8).ConvertToRgbInplace(values);
}
}

237
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -69,6 +71,171 @@ public class JpegColorConverterTests
Assert.Equal(precision, converter.Precision);
}
[Fact]
public void GetConverterReturnsCorrectConverterWithRgbColorSpace()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.RgbScalar);
if (Avx.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.RgbAvx);
}
else if (Sse2.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.RgbVector);
}
else if (AdvSimd.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.RgbArm);
}
// act
JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8);
Type actualType = converter.GetType();
// assert
Assert.Equal(expectedType, actualType);
}
}
[Fact]
public void GetConverterReturnsCorrectConverterWithGrayScaleColorSpace()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.GrayscaleScalar);
if (Avx.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.GrayscaleAvx);
}
else if (Sse2.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.GrayScaleVector);
}
else if (AdvSimd.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.GrayscaleArm);
}
// act
JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8);
Type actualType = converter.GetType();
// assert
Assert.Equal(expectedType, actualType);
}
}
[Fact]
public void GetConverterReturnsCorrectConverterWithCmykColorSpace()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.CmykScalar);
if (Avx.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.CmykAvx);
}
else if (Sse2.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.CmykVector);
}
else if (AdvSimd.Arm64.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.CmykArm64);
}
// act
JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8);
Type actualType = converter.GetType();
// assert
Assert.Equal(expectedType, actualType);
}
}
[Fact]
public void GetConverterReturnsCorrectConverterWithYCbCrColorSpace()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.YCbCrScalar);
if (Avx.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrAvx);
}
else if (Sse2.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrVector);
}
else if (AdvSimd.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YCbCrArm);
}
// act
JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8);
Type actualType = converter.GetType();
// assert
Assert.Equal(expectedType, actualType);
}
}
[Fact]
public void GetConverterReturnsCorrectConverterWithYcckColorSpace()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
// arrange
Type expectedType = typeof(JpegColorConverterBase.YccKScalar);
if (Avx.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YccKAvx);
}
else if (Sse2.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YccKVector);
}
else if (AdvSimd.Arm64.IsSupported)
{
expectedType = typeof(JpegColorConverterBase.YccKArm64);
}
// act
JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8);
Type actualType = converter.GetType();
// assert
Assert.Equal(expectedType, actualType);
}
}
[Theory]
[InlineData(JpegColorSpace.Grayscale, 1)]
[InlineData(JpegColorSpace.Ycck, 4)]
@ -242,7 +409,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.YCbCrAvx(8),
this.TestConversionToRgb(
new JpegColorConverterBase.YCbCrAvx(8),
3,
seed,
new JpegColorConverterBase.YCbCrScalar(8));
@ -250,7 +418,25 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToYCbCrAvx2(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.YCbCrAvx(8),
this.TestConversionFromRgb(
new JpegColorConverterBase.YCbCrAvx(8),
3,
seed,
new JpegColorConverterBase.YCbCrScalar(8),
precísion: 2);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrArm(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.YCbCrArm(8),
3,
seed,
new JpegColorConverterBase.YCbCrScalar(8));
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToYCbCrArm(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.YCbCrArm(8),
3,
seed,
new JpegColorConverterBase.YCbCrScalar(8),
@ -259,7 +445,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.CmykAvx(8),
this.TestConversionToRgb(
new JpegColorConverterBase.CmykAvx(8),
4,
seed,
new JpegColorConverterBase.CmykScalar(8));
@ -267,7 +454,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToCmykAvx2(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.CmykAvx(8),
this.TestConversionFromRgb(
new JpegColorConverterBase.CmykAvx(8),
4,
seed,
new JpegColorConverterBase.CmykScalar(8),
@ -276,7 +464,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykArm(int seed) =>
this.TestConversionToRgb( new JpegColorConverterBase.CmykArm64(8),
this.TestConversionToRgb(
new JpegColorConverterBase.CmykArm64(8),
4,
seed,
new JpegColorConverterBase.CmykScalar(8));
@ -284,7 +473,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToCmykArm(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.CmykArm64(8),
this.TestConversionFromRgb(
new JpegColorConverterBase.CmykArm64(8),
4,
seed,
new JpegColorConverterBase.CmykScalar(8),
@ -293,7 +483,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleAvx(8),
this.TestConversionToRgb(
new JpegColorConverterBase.GrayscaleAvx(8),
1,
seed,
new JpegColorConverterBase.GrayscaleScalar(8));
@ -301,7 +492,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToGrayscaleAvx2(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.GrayscaleAvx(8),
this.TestConversionFromRgb(
new JpegColorConverterBase.GrayscaleAvx(8),
1,
seed,
new JpegColorConverterBase.GrayscaleScalar(8),
@ -327,7 +519,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.RgbAvx(8),
this.TestConversionToRgb(
new JpegColorConverterBase.RgbAvx(8),
3,
seed,
new JpegColorConverterBase.RgbScalar(8));
@ -335,7 +528,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbArm(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.RgbArm(8),
this.TestConversionToRgb(
new JpegColorConverterBase.RgbArm(8),
3,
seed,
new JpegColorConverterBase.RgbScalar(8));
@ -343,7 +537,8 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) =>
this.TestConversionToRgb( new JpegColorConverterBase.YccKAvx(8),
this.TestConversionToRgb(
new JpegColorConverterBase.YccKAvx(8),
4,
seed,
new JpegColorConverterBase.YccKScalar(8));
@ -351,7 +546,25 @@ public class JpegColorConverterTests
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToYccKAvx2(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.YccKAvx(8),
this.TestConversionFromRgb(
new JpegColorConverterBase.YccKAvx(8),
4,
seed,
new JpegColorConverterBase.YccKScalar(8),
precísion: 4);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKArm64(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.YccKArm64(8),
4,
seed,
new JpegColorConverterBase.YccKScalar(8));
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToYccKArm64(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.YccKArm64(8),
4,
seed,
new JpegColorConverterBase.YccKScalar(8),

4
tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs

@ -211,8 +211,8 @@ public class BigTiffMetadataTests
foreach (IExifValue entry in values)
{
writer.Write((ushort)entry.Tag);
writer.Write((ushort)entry.DataType);
writer.Write((ushort)entry.Tag, buffer);
writer.Write((ushort)entry.DataType, buffer);
WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry));
uint length = ExifWriter.GetLength(entry);

4
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

@ -19,7 +19,7 @@ public class TiffEncoderHeaderTests
using (TiffStreamWriter writer = new(stream))
{
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);
}
Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray());
@ -32,7 +32,7 @@ public class TiffEncoderHeaderTests
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
using TiffStreamWriter writer = new(stream);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);
Assert.Equal(4, firstIfdMarker);
}
}

15
tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

@ -53,7 +53,7 @@ public class TiffWriterTests
{
using var stream = new MemoryStream();
using var writer = new TiffStreamWriter(stream);
writer.Write(1234);
writer.Write(1234, stackalloc byte[2]);
Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray());
}
@ -63,7 +63,7 @@ public class TiffWriterTests
{
using var stream = new MemoryStream();
using var writer = new TiffStreamWriter(stream);
writer.Write(12345678U);
writer.Write(12345678U, stackalloc byte[4]);
Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray());
}
@ -89,16 +89,17 @@ public class TiffWriterTests
public void WriteMarker_WritesToPlacedPosition()
{
using var stream = new MemoryStream();
Span<byte> buffer = stackalloc byte[4];
using (var writer = new TiffStreamWriter(stream))
{
writer.Write(0x11111111);
long marker = writer.PlaceMarker();
writer.Write(0x33333333);
writer.Write(0x11111111, buffer);
long marker = writer.PlaceMarker(buffer);
writer.Write(0x33333333, buffer);
writer.WriteMarker(marker, 0x12345678);
writer.WriteMarker(marker, 0x12345678, buffer);
writer.Write(0x44444444);
writer.Write(0x44444444, buffer);
}
Assert.Equal(

Loading…
Cancel
Save