diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
index 5af5db3cd..0c1572fe0 100644
--- a/src/ImageSharp/Common/Helpers/Numerics.cs
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -73,6 +73,30 @@ internal static class Numerics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo8(nint x) => x & 7;
+ ///
+ /// Calculates % 64
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Modulo64(int value) => value & 63;
+
+ ///
+ /// Calculates % 64
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint Modulo64(nint value) => value & 63;
+
+ ///
+ /// Calculates % 256
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Modulo256(int value) => value & 255;
+
+ ///
+ /// Calculates % 256
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint Modulo256(nint value) => value & 255;
+
///
/// Fast (x mod m) calculator, with the restriction that
/// should be power of 2.
@@ -1039,4 +1063,5 @@ internal static class Numerics
public static nuint Vector256Count(int length)
where TVector : struct
=> (uint)length / (uint)Vector256.Count;
+
}
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
index db383efd9..a9212874d 100644
--- a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
@@ -3,6 +3,8 @@
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@@ -85,7 +87,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
///
/// The stream where the bytes are being read
/// If the stream doesn't store a qoi image
- private void ProcessHeader(Stream stream)
+ private void ProcessHeader(BufferedReadStream stream)
{
Span magicBytes = stackalloc byte[4];
Span widthBytes = stackalloc byte[4];
@@ -141,7 +143,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
private static void ThrowInvalidImageContentException()
=> throw new InvalidImageContentException("The image is not a valid QOI image.");
- private void ProcessPixels(Stream stream, Buffer2D pixels)
+ private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
Rgba32[] previouslySeenPixels = new Rgba32[64];
@@ -151,26 +153,27 @@ internal class QoiDecoderCore : IImageDecoderInternals
// See https://github.com/phoboslab/qoi/issues/258
int pixelArrayPosition = GetArrayPosition(previousPixel);
previouslySeenPixels[pixelArrayPosition] = previousPixel;
+ byte operationByte;
+ Rgba32 readPixel = default;
+ Span pixelBytes = MemoryMarshal.CreateSpan(ref Unsafe.As(ref readPixel), 4);
+ TPixel pixel = default;
for (int i = 0; i < this.header.Height; i++)
{
for (int j = 0; j < this.header.Width; j++)
{
- byte operationByte = (byte)stream.ReadByte();
- byte[] pixelBytes;
- Rgba32 readPixel;
- TPixel pixel = default;
+ Span row = pixels.DangerousGetRowSpan(i);
+ operationByte = (byte)stream.ReadByte();
switch ((QoiChunk)operationByte)
{
// Reading one pixel with previous alpha intact
case QoiChunk.QoiOpRgb:
- pixelBytes = new byte[3];
- if (stream.Read(pixelBytes) < 3)
+ if (stream.Read(pixelBytes[..3]) < 3)
{
ThrowInvalidImageContentException();
}
- readPixel = previousPixel with { R = pixelBytes[0], G = pixelBytes[1], B = pixelBytes[2] };
+ readPixel.A = previousPixel.A;
pixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
@@ -178,13 +181,11 @@ internal class QoiDecoderCore : IImageDecoderInternals
// Reading one pixel with new alpha
case QoiChunk.QoiOpRgba:
- pixelBytes = new byte[4];
if (stream.Read(pixelBytes) < 4)
{
ThrowInvalidImageContentException();
}
- readPixel = new Rgba32(pixelBytes[0], pixelBytes[1], pixelBytes[2], pixelBytes[3]);
pixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
@@ -206,9 +207,9 @@ internal class QoiDecoderCore : IImageDecoderInternals
blueDifference = (byte)(operationByte & 0b00000011);
readPixel = previousPixel with
{
- R = (byte)((previousPixel.R + (redDifference - 2)) % 256),
- G = (byte)((previousPixel.G + (greenDifference - 2)) % 256),
- B = (byte)((previousPixel.B + (blueDifference - 2)) % 256)
+ R = (byte)Numerics.Modulo256(previousPixel.R + (redDifference - 2)),
+ G = (byte)Numerics.Modulo256(previousPixel.G + (greenDifference - 2)),
+ B = (byte)Numerics.Modulo256(previousPixel.B + (blueDifference - 2))
};
pixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
@@ -219,12 +220,12 @@ internal class QoiDecoderCore : IImageDecoderInternals
// depending on the green one
case QoiChunk.QoiOpLuma:
byte diffGreen = (byte)(operationByte & 0b00111111),
- currentGreen = (byte)((previousPixel.G + (diffGreen - 32)) % 256),
+ currentGreen = (byte)Numerics.Modulo256(previousPixel.G + (diffGreen - 32)),
nextByte = (byte)stream.ReadByte(),
diffRedDG = (byte)(nextByte >> 4),
diffBlueDG = (byte)(nextByte & 0b00001111),
- currentRed = (byte)((diffRedDG - 8 + (diffGreen - 32) + previousPixel.R) % 256),
- currentBlue = (byte)((diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B) % 256);
+ currentRed = (byte)Numerics.Modulo256(diffRedDG - 8 + (diffGreen - 32) + previousPixel.R),
+ currentBlue = (byte)Numerics.Modulo256(diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B);
readPixel = previousPixel with { R = currentRed, B = currentBlue, G = currentGreen };
pixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
@@ -233,7 +234,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
// Repeating the previous pixel 1..63 times
case QoiChunk.QoiOpRun:
- byte repetitions = (byte)(operationByte & 0b00111111);
+ int repetitions = operationByte & 0b00111111;
if (repetitions is 62 or 63)
{
ThrowInvalidImageContentException();
@@ -247,9 +248,10 @@ internal class QoiDecoderCore : IImageDecoderInternals
{
j = 0;
i++;
+ row = pixels.DangerousGetRowSpan(i);
}
- pixels[j, i] = pixel;
+ row[j] = pixel;
}
j--;
@@ -263,7 +265,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
break;
}
- pixels[j, i] = pixel;
+ row[j] = pixel;
previousPixel = readPixel;
}
}
@@ -283,5 +285,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
}
}
- private static int GetArrayPosition(Rgba32 pixel) => ((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)) % 64;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetArrayPosition(Rgba32 pixel)
+ => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
}
diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
index a7ec49b14..d1fe87ed0 100644
--- a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -63,6 +64,7 @@ public class QoiEncoderCore : IImageEncoderInternals
for (int j = 0; j < pixels.Width && i < pixels.Height; j++)
{
// We get the RGBA value from pixels
+ Span row = pixels.DangerousGetRowSpan(i);
TPixel currentPixel = pixels[j, i];
currentPixel.ToRgba32(ref currentRgba32);
@@ -87,14 +89,15 @@ public class QoiEncoderCore : IImageEncoderInternals
{
j = 0;
i++;
+ if (i == pixels.Height)
+ {
+ break;
+ }
+ row = pixels.DangerousGetRowSpan(i);
}
- if (i == pixels.Height)
- {
- break;
- }
- currentPixel = pixels[j, i];
+ currentPixel = row[j];
currentPixel.ToRgba32(ref currentRgba32);
}
while (currentRgba32.Equals(previousPixel) && repetitions < 62);
@@ -196,6 +199,7 @@ public class QoiEncoderCore : IImageEncoderInternals
stream.WriteByte(1);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetArrayPosition(Rgba32 pixel)
- => ((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)) % 64;
+ => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
}