diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 0c1b273f7..863fed359 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -453,6 +453,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle4(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span 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 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
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle8(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span 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 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
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle24(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span 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 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 data = bitfieldsBuffer.AsSpan();
+ Span bitfieldsBuffer = stackalloc byte[12];
+ stream.Read(bitfieldsBuffer);
+ Span 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 data = bitfieldsBuffer.AsSpan();
+ Span bitfieldsBuffer = stackalloc byte[16];
+ stream.Read(bitfieldsBuffer);
+ Span 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.");
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 6ff2723dd..55ad2c458 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
///
/// The temp buffer used to reduce allocations.
///
- private readonly byte[] buffer = new byte[16];
+ private ScratchBuffer buffer; // mutable struct, don't make readonly
///
/// The global color table.
@@ -249,13 +249,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// The containing image data.
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);
}
///
@@ -264,13 +264,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// The containing image data.
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
/// The containing image data.
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);
}
///
@@ -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 Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ }
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index f736da78d..c01cc78ef 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
///
private readonly Configuration configuration;
- ///
- /// A reusable buffer used to reduce allocations.
- ///
- private readonly byte[] buffer = new byte[20];
-
///
/// Whether to skip metadata during encode.
///
@@ -324,9 +319,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
- descriptor.WriteTo(this.buffer);
+ Span buffer = stackalloc byte[20];
+ descriptor.WriteTo(buffer);
- stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
+ stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
}
///
@@ -365,12 +361,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
return;
}
+ Span 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 commentSpan = comment.AsSpan();
@@ -437,22 +435,23 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
private void WriteExtension(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{
- IMemoryOwner? owner = null;
- Span extensionBuffer;
int extensionSize = extension.ContentLength;
if (extensionSize == 0)
{
return;
}
- else if (extensionSize > this.buffer.Length - 3)
+
+ IMemoryOwner? owner = null;
+ Span extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
+ if (extensionSize > 128)
{
owner = this.memoryAllocator.Allocate(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 buffer = stackalloc byte[20];
+ descriptor.WriteTo(buffer);
- stream.Write(this.buffer, 0, GifImageDescriptor.Size);
+ stream.Write(buffer, 0, GifImageDescriptor.Size);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs
new file mode 100644
index 000000000..4f7cf1ed6
--- /dev/null
+++ b/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)
+ {
+ }
+
+ ///
+ public override void ConvertToRgbInplace(in ComponentValues values)
+ {
+ ref Vector128 c0Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 c1Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 c2Base =
+ ref Unsafe.As>(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.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ // cb = cbVals[i] - 128F;
+ // cr = crVals[i] - 128F;
+ ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i);
+
+ Vector128 y = c0;
+ Vector128 cb = AdvSimd.Add(c1, chromaOffset);
+ Vector128 cr = AdvSimd.Add(c2, chromaOffset);
+
+ // r = y + (1.402F * cr);
+ // g = y - (0.344136F * cb) - (0.714136F * cr);
+ // b = y + (1.772F * cb);
+ Vector128 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
+ Vector128 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
+ Vector128 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;
+ }
+ }
+
+ ///
+ public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane)
+ {
+ ref Vector128 destY =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 destCb =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 destCr =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ ref Vector128 srcR =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane));
+ ref Vector128 srcG =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane));
+ ref Vector128 srcB =
+ ref Unsafe.As>(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.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ Vector128 r = Unsafe.Add(ref srcR, i);
+ Vector128 g = Unsafe.Add(ref srcG, i);
+ Vector128 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 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
+ Vector128 cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
+ Vector128 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;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs
new file mode 100644
index 000000000..285ba62cf
--- /dev/null
+++ b/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)
+ {
+ }
+
+ ///
+ public override void ConvertToRgbInplace(in ComponentValues values)
+ {
+ ref Vector128 c0Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 c1Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 c2Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector128 kBase =
+ ref Unsafe.As>(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.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 c0 = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i);
+ Vector128 y = c0;
+ Vector128 cb = AdvSimd.Add(c1, chromaOffset);
+ Vector128 cr = AdvSimd.Add(c2, chromaOffset);
+ Vector128 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 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
+ Vector128 g =
+ HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
+ Vector128 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;
+ }
+ }
+
+ ///
+ public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane)
+ {
+ // rgb -> cmyk
+ CmykArm64.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
+
+ // cmyk -> ycck
+ ref Vector128 destY =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 destCb =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 destCr =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ ref Vector128 srcR = ref destY;
+ ref Vector128 srcG = ref destCb;
+ ref Vector128 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.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ Vector128 r = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i));
+ Vector128 g = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i));
+ Vector128 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 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
+ Vector128 cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
+ Vector128 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;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
index 44b4ed4ec..041f6b057 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
+++ b/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);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs
index c9ee55cd7..cf2369b2c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs
@@ -62,7 +62,7 @@ internal readonly struct AdobeMarker : IEquatable
///
/// The byte array containing metadata to parse.
/// The marker to return.
- public static bool TryParse(byte[] bytes, out AdobeMarker marker)
+ public static bool TryParse(ReadOnlySpan bytes, out AdobeMarker marker)
{
if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker))
{
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
index 5ecf77961..02a346ff0 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
+++ b/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, as the values need to get updated.
private readonly byte[] fixedBin = { 113, 0, 0, 0 };
private readonly CancellationToken cancellationToken;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
index e13b26f9a..153dc8a03 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
@@ -69,7 +69,7 @@ internal readonly struct JFifMarker : IEquatable
///
/// The byte array containing metadata to parse.
/// The marker to return.
- public static bool TryParse(byte[] bytes, out JFifMarker marker)
+ public static bool TryParse(ReadOnlySpan bytes, out JFifMarker marker)
{
if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker))
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 45029f945..83a828caa 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -27,21 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
///
internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
{
- ///
- /// The only supported precision
- ///
- private readonly byte[] supportedPrecisions = { 8, 12 };
-
- ///
- /// The buffer used to temporarily store bytes read from the stream.
- ///
- private readonly byte[] temp = new byte[2 * 16 * 4];
-
- ///
- /// The buffer used to read markers from the stream.
- ///
- private readonly byte[] markerBuffer = new byte[2];
-
///
/// Whether the image has an EXIF marker.
///
@@ -139,6 +124,12 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.skipMetadata = options.GeneralOptions.SkipMetadata;
}
+ ///
+ /// Gets the only supported precisions
+ ///
+ // Refers to assembly's static data segment, no allocation occurs.
+ private static ReadOnlySpan SupportedPrecisions => new byte[] { 8, 12 };
+
///
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 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 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 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 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 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 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 blockDataSpan = resourceBlockData.AsSpan();
+ Span 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 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 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 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
///
/// The input stream.
/// The remaining bytes in the segment block.
- private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining)
+ /// Scratch buffer.
+ private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining, Span 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 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 from the stream advancing it by two bytes.
///
/// The input stream.
+ /// The scratch buffer used for reading from the stream.
/// The
[MethodImpl(InliningOptions.ShortMethod)]
- private ushort ReadUint16(BufferedReadStream stream)
+ private static ushort ReadUint16(BufferedReadStream stream, Span 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);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 1d06333e3..95f7fde32 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -25,11 +25,6 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
///
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
- ///
- /// A scratch buffer to reduce allocations.
- ///
- private readonly byte[] buffer = new byte[20];
-
private readonly JpegEncoder encoder;
///
@@ -67,6 +62,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
cancellationToken.ThrowIfCancellationRequested();
this.outputStream = stream;
+ Span 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 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
///
/// Write the start of image marker.
///
- private void WriteStartOfImage()
+ private void WriteStartOfImage(Span 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);
}
///
/// Writes the application header containing the JFIF identifier plus extra data.
///
/// The image metadata.
- private void WriteJfifApplicationHeader(ImageMetadata meta)
+ /// Temporary buffer.
+ private void WriteJfifApplicationHeader(ImageMetadata meta, Span 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 hResolution = this.buffer.AsSpan(12, 2);
- Span vResolution = this.buffer.AsSpan(14, 2);
+ Span hResolution = buffer.Slice(12, 2);
+ Span 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);
}
///
@@ -175,8 +172,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
///
/// The table configuration.
/// The scan encoder.
+ /// Temporary buffer.
/// is .
- private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
+ private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder, Span 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.
///
/// The color transform byte.
- private void WriteApp14Marker(byte colorTransform)
+ /// Temporary buffer.
+ private void WriteApp14Marker(byte colorTransform, Span 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));
}
///
/// Writes the EXIF profile.
///
/// The exif profile.
- private void WriteExifProfile(ExifProfile exifProfile)
+ /// Temporary buffer.
+ private void WriteExifProfile(ExifProfile exifProfile, Span 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.
///
/// The iptc metadata to write.
+ /// Temporary buffer.
///
/// Thrown if the IPTC profile size exceeds the limit of 65533 bytes.
///
- private void WriteIptcProfile(IptcProfile iptcProfile)
+ private void WriteIptcProfile(IptcProfile iptcProfile, Span 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.
///
/// The XMP metadata to write.
+ /// Temporary buffer.
///
/// Thrown if the XMP profile size exceeds the limit of 65533 bytes.
///
- private void WriteXmpProfile(XmpProfile xmpProfile)
+ private void WriteXmpProfile(XmpProfile xmpProfile, Span 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.
///
/// The length of the data the app1 marker contains.
- private void WriteApp1Header(int app1Length)
- => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
+ /// Temporary buffer.
+ private void WriteApp1Header(int app1Length, Span buffer)
+ => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1, buffer);
///
/// Writes a AppX header.
///
/// The length of the data the app marker contains.
/// The app marker to write.
- private void WriteAppHeader(int length, byte appMarker)
+ /// Temporary buffer.
+ private void WriteAppHeader(int length, byte appMarker, Span 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);
}
///
/// Writes the ICC profile.
///
/// The ICC profile to write.
+ /// Temporary buffer.
///
/// Thrown if any of the ICC profiles size exceeds the limit.
///
- private void WriteIccProfile(IccProfile iccProfile)
+ private void WriteIccProfile(IccProfile iccProfile, Span 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.
///
/// The image metadata.
- private void WriteProfiles(ImageMetadata metadata)
+ /// Temporary buffer.
+ private void WriteProfiles(ImageMetadata metadata, Span 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);
}
///
@@ -506,25 +513,26 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// The frame width.
/// The frame height.
/// The frame configuration.
- private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame)
+ /// Temporary buffer.
+ private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame, Span 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 bufferSpan = this.buffer.AsSpan(i3 + 6, 3);
+ Span 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);
}
///
/// Writes the StartOfScan marker.
///
/// The collecction of component configuration items.
- private void WriteStartOfScan(Span components)
+ /// Temporary buffer.
+ private void WriteStartOfScan(Span components, Span 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<<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);
}
///
/// Writes the EndOfImage marker.
///
- private void WriteEndOfImageMarker()
+ /// Temporary buffer.
+ private void WriteEndOfImageMarker(Span 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);
}
///
@@ -602,12 +612,14 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// The frame configuration.
/// The spectral converter.
/// The scan encoder.
+ /// Temporary buffer.
/// The cancellation token.
private void WriteHuffmanScans(
JpegFrame frame,
JpegFrameConfig frameConfig,
SpectralConverter spectralConverter,
HuffmanScanEncoder encoder,
+ Span buffer,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
@@ -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 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
///
/// The marker to write.
/// The marker length.
- private void WriteMarkerHeader(byte marker, int length)
+ /// Temporary buffer.
+ private void WriteMarkerHeader(byte marker, int length, Span 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);
}
///
@@ -668,15 +682,16 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// Quantization tables configs.
/// Optional quality value from the options.
/// Jpeg metadata instance.
- private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata)
+ /// Temporary buffer.
+ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata, Span 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 buffer = dataLen <= 256 ? stackalloc byte[dataLen] : new byte[dataLen];
int offset = 0;
Block8x8F workspaceBlock = default;
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 3ecc363fa..d1d29dca6 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Png;
///
internal sealed class PngDecoderCore : IImageDecoderInternals
{
- ///
- /// Reusable buffer.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// The general decoder options.
///
@@ -154,9 +149,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image image = null;
+ Span 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 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 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
///
/// Reads a chunk from the stream.
///
+ /// Temporary buffer.
/// The image format chunk.
///
/// The .
///
- private bool TryReadChunk(out PngChunk chunk)
+ private bool TryReadChunk(Span 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.
///
/// The .
- private void ValidateChunk(in PngChunk chunk)
+ /// Temporary buffer.
+ private void ValidateChunk(in PngChunk chunk, Span buffer)
{
- uint inputCrc = this.ReadChunkCrc();
+ uint inputCrc = this.ReadChunkCrc(buffer);
if (chunk.IsCritical)
{
@@ -1513,13 +1517,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
///
/// Reads the cycle redundancy chunk from the data.
///
+ /// Temporary buffer.
[MethodImpl(InliningOptions.ShortMethod)]
- private uint ReadChunkCrc()
+ private uint ReadChunkCrc(Span 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
///
/// Identifies the chunk type from the chunk.
///
+ /// Temporary buffer.
///
/// Thrown if the input stream is not valid.
///
[MethodImpl(InliningOptions.ShortMethod)]
- private PngChunkType ReadChunkType()
+ private PngChunkType ReadChunkType(Span 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
///
/// Attempts to read the length of the next chunk.
///
+ /// Temporary buffer.
/// The result length. If the return type is this parameter is passed uninitialized.
///
/// Whether the length was read.
///
[MethodImpl(InliningOptions.ShortMethod)]
- private bool TryReadChunkLength(out int result)
+ private bool TryReadChunkLength(Span 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;
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 5794da3d5..fb1d33277 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -38,15 +38,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
///
private readonly Configuration configuration;
- ///
- /// Reusable buffer for writing general data.
- ///
- private readonly byte[] buffer = new byte[8];
-
///
/// Reusable buffer for writing chunk data.
///
- private readonly byte[] chunkDataBuffer = new byte[16];
+ private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly
///
/// 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);
}
///
@@ -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);
}
///
@@ -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 alpha = this.chunkDataBuffer.AsSpan();
+ Span 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
/// The of the data to write.
private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length)
{
- BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
- BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
+ Span 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
}
///
@@ -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 Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ }
}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index e7dca00f7..26e057bff 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Tga;
///
internal sealed class TgaDecoderCore : IImageDecoderInternals
{
- ///
- /// A scratch buffer to reduce allocations.
- ///
- private readonly byte[] scratchBuffer = new byte[4];
-
///
/// General configuration options.
///
@@ -407,6 +402,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
bool invertX = InvertX(origin);
using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0);
Span rowSpan = row.GetSpan();
+ Span 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(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
+ color.FromLa16(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer)));
}
else
{
- color.FromBgra5551(Unsafe.As(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
+ color.FromBgra5551(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer)));
}
pixelSpan[x] = color;
@@ -484,6 +480,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
bool invertX = InvertX(origin);
if (invertX)
{
+ Span scratchBuffer = stackalloc byte[4];
TPixel color = default;
for (int y = 0; y < height; y++)
{
@@ -491,7 +488,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Span 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 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(BufferedReadStream stream, TPixel color, int x, Span pixelSpan)
+ private static void ReadBgr24Pixel(BufferedReadStream stream, TPixel color, int x, Span pixelSpan, Span scratchBuffer)
where TPixel : unmanaged, IPixel
{
- 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(ref MemoryMarshal.GetArrayDataReference(this.scratchBuffer)));
+ color.FromBgr24(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer)));
pixelSpan[x] = color;
}
@@ -715,10 +714,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ReadBgra32Pixel(BufferedReadStream stream, int x, TPixel color, Span pixelRow)
+ private void ReadBgra32Pixel(BufferedReadStream stream, int x, TPixel color, Span pixelRow, Span scratchBuffer)
where TPixel : unmanaged, IPixel
{
- 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 buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
- Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
+ Span 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");
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
index f468ab9ae..ad63bd356 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -22,11 +22,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
///
private readonly MemoryAllocator memoryAllocator;
- ///
- /// Reusable buffer for writing data.
- ///
- private readonly byte[] buffer = new byte[2];
-
///
/// The color depth, in number of bits per pixel.
///
@@ -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 buffer = stackalloc byte[2];
+ BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)bgra5551.PackedValue);
+ stream.WriteByte(buffer[0]);
+ stream.WriteByte(buffer[1]);
break;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
index 9007b3f5a..df37327c3 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
@@ -24,9 +24,9 @@ internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder
public override void Decode(ReadOnlySpan data, Buffer2D 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 buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@@ -37,8 +37,8 @@ internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder : TiffBaseColorDecoder : TiffBaseColorDecoder
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
- byte[] buffer = new byte[4];
+ Span buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@@ -38,18 +38,18 @@ internal class RgbFloat323232TiffColor : TiffBaseColorDecoder
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 : TiffBaseColorDecoder
{
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);
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
index 920f9fdc4..743502d56 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
@@ -27,7 +27,7 @@ internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
- byte[] buffer = new byte[4];
+ Span buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@@ -38,23 +38,23 @@ internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder
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 : TiffBaseColorDecoder
{
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);
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
index 78d557f30..f3207b2f4 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
@@ -26,7 +26,7 @@ internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@@ -37,8 +37,8 @@ internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder : TiffBaseColorDecoder
private readonly MemoryAllocator memoryAllocator;
- ///
- /// A scratch buffer to reduce allocations.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// The global configuration.
///
@@ -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 buffer = stackalloc byte[4];
+
+ long ifdMarker = WriteHeader(writer, buffer);
Image metadataImage = image;
foreach (ImageFrame 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.
///
/// The to write data to.
+ /// Scratch buffer with minimum size of 2.
///
/// The marker to write the first IFD offset.
///
- public static long WriteHeader(TiffStreamWriter writer)
+ public static long WriteHeader(TiffStreamWriter writer, Span buffer)
{
- writer.Write(ByteOrderMarker);
- writer.Write(TiffConstants.HeaderMagicNumber);
- return writer.PlaceMarker();
+ writer.Write(ByteOrderMarker, buffer);
+ writer.Write(TiffConstants.HeaderMagicNumber, buffer);
+ return writer.PlaceMarker(buffer);
}
///
@@ -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 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)
{
diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
index be32ca9ed..3c2ad6084 100644
--- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
@@ -10,13 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers;
///
internal sealed class TiffStreamWriter : IDisposable
{
- private static readonly byte[] PaddingBytes = new byte[4];
-
- ///
- /// A scratch buffer to reduce allocations.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// Initializes a new instance of the class.
///
@@ -41,11 +34,12 @@ internal sealed class TiffStreamWriter : IDisposable
///
/// Writes an empty four bytes to the stream, returning the offset to be written later.
///
+ /// Scratch buffer with minimum size of 4.
/// The offset to be written later.
- public long PlaceMarker()
+ public long PlaceMarker(Span 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.
///
/// The two-byte unsigned integer to write.
- public void Write(ushort value)
+ /// Scratch buffer with minimum size of 2.
+ public void Write(ushort value, Span 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));
}
///
/// Writes a four-byte unsigned integer to the current stream.
///
/// The four-byte unsigned integer to write.
- public void Write(uint value)
+ /// Scratch buffer with minimum size of 4.
+ public void Write(uint value, Span 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));
}
///
@@ -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 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
///
/// The offset returned when placing the marker
/// The four-byte unsigned integer to write.
- public void WriteMarker(long offset, uint value)
+ /// Scratch buffer.
+ public void WriteMarker(long offset, uint value, Span 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 buffer)
{
this.BaseStream.Seek(offset, SeekOrigin.Begin);
- this.Write(value);
+ this.Write(value, buffer);
}
///
diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
index 8da717545..659576cf1 100644
--- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
+++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
@@ -192,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes()
{
- System.Span dataSpan = this.Data!.Memory.Span;
+ Span dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index 02b1d0ab6..ab78d1860 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/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
///
/// A scratch buffer to reduce allocations.
///
- private readonly byte[] scratchBuffer = new byte[4];
+ private ScratchBuffer scratchBuffer; // mutable struct, don't make readonly
///
/// Initializes a new instance of the 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 buf = this.scratchBuffer.AsSpan(0, 4);
+ Span 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 dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
- Span buf = this.scratchBuffer.AsSpan(0, 4);
+ Span 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 buf = this.scratchBuffer.AsSpan(0, 4);
+ Span 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 buf = this.scratchBuffer.AsSpan(0, 4);
+ Span 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 Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ }
}
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
index 22bc195d6..9dc791239 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
@@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
///
internal class Vp8LBitWriter : BitWriterBase
{
- ///
- /// A scratch buffer to reduce allocations.
- ///
- private readonly byte[] scratchBuffer = new byte[8];
-
///
/// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed.
///
@@ -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 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 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;
diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
index 5ac330151..dd59ed209 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
+++ b/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
{
///
/// 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 imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols)
+ public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, Span 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 origHistograms, List histograms, ushort[] histogramSymbols)
+ private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, Span 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 histograms,
- ushort[] clusters,
+ Span clusters,
ushort[] clusterMappings,
Vp8LHistogram curCombo,
ushort[] binMap,
int numBins,
double combineCostFactor)
{
- var binInfo = new HistogramBinInfo[BinSize];
+ Span 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.
///
- private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols)
+ private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span 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 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 src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
- Span dst = mappings.AsSpan(mappingIndex);
+ int mappingIndex = mappings.IndexOf(bestIdx2);
+ Span src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1);
+ Span 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 input, List output, ushort[] symbols)
+ private static void HistogramRemap(List input, List output, Span symbols)
{
int inSize = input.Count;
int outSize = output.Count;
diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
index 18104331c..39ad967e3 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
+++ b/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 huffTree, HuffmanTreeCode huffCode)
{
int numSymbols = huffCode.NumSymbols;
bufRle.AsSpan().Clear();
@@ -159,7 +159,7 @@ internal static class HuffmanUtils
/// The size of the histogram.
/// The tree depth limit.
/// How many bits are used for the symbol.
- public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
+ public static void GenerateOptimalTree(Span tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
{
uint countMin;
int treeSizeOrig = 0;
@@ -177,7 +177,7 @@ internal static class HuffmanUtils
return;
}
- Span treePool = tree.AsSpan(treeSizeOrig);
+ Span 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 treeSlice = tree.AsSpan(0, treeSize);
+ Span 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 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 counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
+ Span 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 nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1];
+ Span 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.
///
- private static int NextTableBitSize(int[] count, int len, int rootBits)
+ private static int NextTableBitSize(ReadOnlySpan count, int len, int rootBits)
{
int left = 1 << (len - rootBits);
while (len < WebpConstants.MaxAllowedCodeLength)
diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
index 689c63f5b..2170eb198 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs
@@ -57,11 +57,13 @@ internal static unsafe class PredictorEncoder
Span 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 maxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]);
float bestDiff = MaxDiffCost;
int bestMode = 0;
- uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
+ Span 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;
}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index d678da602..1f7c7586e 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -23,7 +23,7 @@ internal class Vp8LEncoder : IDisposable
///
/// Scratch buffer to reduce allocations.
///
- 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 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 histogramSymbols = histogramImageXySize <= 64 ? stackalloc ushort[histogramImageXySize] : new ushort[histogramImageXySize];
+ Span 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
///
private void EncodePalette(bool lowEffort)
{
- Span tmpPalette = new uint[WebpConstants.MaxPaletteSize];
+ Span tmpPalette = stackalloc uint[WebpConstants.MaxPaletteSize];
int paletteSize = this.PaletteSize;
Span 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 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 huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode)
{
int count = 0;
- Span symbols = this.scratch.AsSpan(0, 2);
+ Span 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 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 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 entropyComp = stackalloc double[(int)HistoIx.HistoTotal];
+ Span 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 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();
}
+
+ ///
+ /// Scratch buffer to reduce allocations.
+ ///
+ private unsafe struct ScratchBuffer
+ {
+ private const int Size = 256;
+ private fixed int scratch[Size];
+
+ public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ }
}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
index 84ddd4b78..19ea42419 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
@@ -498,10 +498,7 @@ internal sealed class WebpLosslessDecoder
private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table)
{
bool simpleCode = this.bitReader.ReadBit();
- for (int i = 0; i < alphabetSize; i++)
- {
- codeLengths[i] = 0;
- }
+ codeLengths.AsSpan(0, alphabetSize).Clear();
if (simpleCode)
{
diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
index 76de2e8f4..e9eb1110b 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
+++ b/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 tmpLevels = new short[16];
+ Span tmpLevels = stackalloc short[16];
do
{
const int numBlocks = 1;
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
index b33ef57a6..7211f9376 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
@@ -374,7 +374,7 @@ internal class Vp8EncIterator
}
else
{
- byte[] modes = new byte[16]; // DC4
+ Span 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 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 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;
}
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index eefc4cd0a..f17d965e8 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/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 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 alphas)
{
int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments;
- int[] centers = new int[NumMbSegments];
+ Span centers = stackalloc int[NumMbSegments];
int weightedAverage = 0;
- int[] map = new int[WebpConstants.MaxAlpha + 1];
+ Span map = stackalloc int[WebpConstants.MaxAlpha + 1];
int n, k;
- int[] accum = new int[NumMbSegments];
- int[] distAccum = new int[NumMbSegments];
+ Span accum = stackalloc int[NumMbSegments];
+ Span 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 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 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 y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha)
+ private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, Span 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 alphas, out int bestUvAlpha)
{
it.SetIntra16Mode(0); // default: Intra16, DC_PRED
it.SetSkip(false); // not skipped.
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
index 4036fb284..2ace43d2d 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
+++ b/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];
-
///
/// Size of histogram used by CollectHistogram.
///
@@ -47,17 +41,20 @@ internal sealed class Vp8Histogram
public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock)
{
+ Span scratch = stackalloc int[16];
+ Span output = stackalloc short[16];
+ Span 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(this.output);
+ ref short outputRef = ref MemoryMarshal.GetReference(output);
Vector256 out0 = Unsafe.As>(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 distribution)
{
int maxValue = 0;
int lastNonZero = 1;
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
index 7384379da..177041506 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
@@ -15,10 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy;
///
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 ctxs = this.scratch.AsSpan(0, 16);
- Span levels = this.scratch.AsSpan(16, 16);
- Span absLevels = this.scratchUShort.AsSpan();
+ Span scratch = stackalloc byte[32];
+ Span ctxs = scratch.Slice(0, 16);
+ Span levels = scratch.Slice(16);
+ Span absLevels = stackalloc ushort[16];
// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs);
diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
index 96ed8903a..7952b15b4 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
@@ -34,16 +34,6 @@ internal sealed class WebpLossyDecoder
///
private readonly Configuration configuration;
- ///
- /// Scratch buffer to reduce allocations.
- ///
- private readonly int[] scratch = new int[16];
-
- ///
- /// Another scratch buffer to reduce allocations.
- ///
- private readonly byte[] scratchBytes = new byte[4];
-
///
/// Initializes a new instance of the class.
///
@@ -371,6 +361,9 @@ internal sealed class WebpLossyDecoder
}
}
+ Span scratch = stackalloc int[16];
+ Span 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 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
{
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index abaa85ef1..21337ce6c 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
///
internal class WebpAnimationDecoder : IDisposable
{
- ///
- /// Reusable buffer.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// Used for allocating memory during the decoding operations.
///
@@ -89,11 +84,12 @@ internal class WebpAnimationDecoder : IDisposable
this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount;
+ Span 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 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(alphaDataSize);
@@ -353,24 +350,26 @@ internal class WebpAnimationDecoder : IDisposable
/// Animation frame data.
private AnimationFrameData ReadFrameHeader(BufferedReadStream stream)
{
+ Span 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();
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index e4acdf311..a7ae474e4 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -18,7 +18,7 @@ internal static class WebpChunkParsingUtils
/// Reads the header of a lossy webp image.
///
/// Information about this webp image.
- public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
+ public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span 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.
///
/// Information about this image.
- public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features)
+ public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span 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.
///
/// Information about this webp image.
- public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features)
+ public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span buffer, WebpFeatures features)
{
uint fileSize = ReadChunkSize(stream, buffer);
@@ -253,7 +253,7 @@ internal static class WebpChunkParsingUtils
/// The stream to read from.
/// The buffer to store the read data into.
/// A unsigned 24 bit integer.
- public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer)
+ public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@@ -271,9 +271,11 @@ internal static class WebpChunkParsingUtils
/// The stream to read the data from.
/// Buffer to store the data read from the stream.
/// The chunk size in bytes.
- public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer)
+ public static uint ReadChunkSize(BufferedReadStream stream, Span 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
///
/// Thrown if the input stream is not valid.
///
- public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer)
+ public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span 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.
///
- 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 buffer)
{
long streamLength = stream.Length;
while (stream.Position < streamLength)
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index c158df44d..223e15a0e 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
///
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
- ///
- /// Reusable buffer.
- ///
- private readonly byte[] buffer = new byte[4];
-
///
/// General configuration options.
///
@@ -80,8 +75,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
try
{
ImageMetadata metadata = new();
+ Span 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
///
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.
///
/// The stream to decode from.
+ /// Temporary buffer.
/// The file size in bytes.
- private uint ReadImageHeader(BufferedReadStream stream)
+ private static uint ReadImageHeader(BufferedReadStream stream, Span 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 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
/// The chunk type.
/// The webp image features.
/// For identify, the alpha data should not be read.
+ /// Temporary buffer.
/// true, if its a alpha chunk.
private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
- bool ignoreAlpha)
+ bool ignoreAlpha,
+ Span 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
/// The stream to decode from.
/// The image metadata.
/// The webp features.
- private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
+ /// Temporary buffer.
+ private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span 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
///
/// The stream to decode from.
/// The image metadata.
- private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
+ /// Temporary buffer.
+ private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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
///
/// The stream to decode from.
/// The image metadata.
- private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
+ /// Temporary buffer.
+ private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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
///
/// The stream to decode from.
/// The image metadata.
- private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
+ /// Temporary buffer.
+ private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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
///
/// The stream to decode from.
/// The webp features.
- private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
+ /// Temporary buffer.
+ private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span 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);
}
///
@@ -412,9 +417,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// The stream to decode from.
/// The features.
/// if set to true, skips the chunk data.
- private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
+ /// Temporary buffer.
+ private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span 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.
///
/// The stream to decode from.
+ /// Temporary buffer.
///
/// Thrown if the input stream is not valid.
///
- private WebpChunkType ReadChunkType(BufferedReadStream stream)
+ private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span 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.
///
/// The stream to decode from.
+ /// Temporary buffer.
/// The chunk size in bytes.
/// Invalid data.
- private uint ReadChunkSize(BufferedReadStream stream)
+ private static uint ReadChunkSize(BufferedReadStream stream, Span 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;
}
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
index 885db3a5e..953ef74af 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
@@ -86,10 +86,6 @@ internal class ExifReader : BaseExifReader
///
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? 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 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 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 buffer = stackalloc byte[2];
+
+ return this.TryReadSpan(buffer)
+ ? this.ConvertToShort(buffer)
: default;
+ }
private long ConvertToInt64(ReadOnlySpan buffer)
{
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs
index 4320cb5e8..6811fc6f9 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs
@@ -45,12 +45,12 @@ internal sealed class ExifByteArray : ExifArrayValue
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];
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
index 5699f9bf3..3b5e43829 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
@@ -108,10 +108,10 @@ public sealed class IccProfile : IDeepCloneable
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 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
}
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));
}
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
index 800190d2d..87e1bf5aa 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
+++ b/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);
+ }
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
index 991d3b0d0..136182936 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
+++ b/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);
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index c71c70c33..ef9f48a89 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/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),
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs
index 73ce216d8..4646de7f8 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs
+++ b/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);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
index 790759785..872414730 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
+++ b/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);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
index f6a1257f4..9b26ab270 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
+++ b/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 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(