Skip to content

Commit 02212bd

Browse files
committed
Optimizing Decoder
Also adding Modulo64 and 256 to Numerics and optimizing a little bit Encoder
1 parent d671006 commit 02212bd

File tree

3 files changed

+60
-27
lines changed

3 files changed

+60
-27
lines changed

src/ImageSharp/Common/Helpers/Numerics.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ public static int LeastCommonMultiple(int a, int b)
7373
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7474
public static nint Modulo8(nint x) => x & 7;
7575

76+
/// <summary>
77+
/// Calculates <paramref name="x"/> % 64
78+
/// </summary>
79+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
80+
public static int Modulo64(int value) => value & 63;
81+
82+
/// <summary>
83+
/// Calculates <paramref name="x"/> % 64
84+
/// </summary>
85+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
86+
public static nint Modulo64(nint value) => value & 63;
87+
88+
/// <summary>
89+
/// Calculates <paramref name="x"/> % 256
90+
/// </summary>
91+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
92+
public static int Modulo256(int value) => value & 255;
93+
94+
/// <summary>
95+
/// Calculates <paramref name="x"/> % 256
96+
/// </summary>
97+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
98+
public static nint Modulo256(nint value) => value & 255;
99+
76100
/// <summary>
77101
/// Fast (x mod m) calculator, with the restriction that
78102
/// <paramref name="m"/> should be power of 2.
@@ -1039,4 +1063,5 @@ public static nuint Vector256Count<TVector>(this Span<float> span)
10391063
public static nuint Vector256Count<TVector>(int length)
10401064
where TVector : struct
10411065
=> (uint)length / (uint)Vector256<TVector>.Count;
1066+
10421067
}

src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System.Buffers.Binary;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
68
using SixLabors.ImageSharp.IO;
79
using SixLabors.ImageSharp.Memory;
810
using SixLabors.ImageSharp.Metadata;
@@ -85,7 +87,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
8587
/// </summary>
8688
/// <param name="stream">The stream where the bytes are being read</param>
8789
/// <exception cref="InvalidImageContentException">If the stream doesn't store a qoi image</exception>
88-
private void ProcessHeader(Stream stream)
90+
private void ProcessHeader(BufferedReadStream stream)
8991
{
9092
Span<byte> magicBytes = stackalloc byte[4];
9193
Span<byte> widthBytes = stackalloc byte[4];
@@ -141,7 +143,7 @@ private void ProcessHeader(Stream stream)
141143
private static void ThrowInvalidImageContentException()
142144
=> throw new InvalidImageContentException("The image is not a valid QOI image.");
143145

144-
private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
146+
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
145147
where TPixel : unmanaged, IPixel<TPixel>
146148
{
147149
Rgba32[] previouslySeenPixels = new Rgba32[64];
@@ -151,40 +153,39 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
151153
// See https://github.com/phoboslab/qoi/issues/258
152154
int pixelArrayPosition = GetArrayPosition(previousPixel);
153155
previouslySeenPixels[pixelArrayPosition] = previousPixel;
156+
byte operationByte;
157+
Rgba32 readPixel = default;
158+
Span<byte> pixelBytes = MemoryMarshal.CreateSpan(ref Unsafe.As<Rgba32, byte>(ref readPixel), 4);
159+
TPixel pixel = default;
154160

155161
for (int i = 0; i < this.header.Height; i++)
156162
{
157163
for (int j = 0; j < this.header.Width; j++)
158164
{
159-
byte operationByte = (byte)stream.ReadByte();
160-
byte[] pixelBytes;
161-
Rgba32 readPixel;
162-
TPixel pixel = default;
165+
Span<TPixel> row = pixels.DangerousGetRowSpan(i);
166+
operationByte = (byte)stream.ReadByte();
163167
switch ((QoiChunk)operationByte)
164168
{
165169
// Reading one pixel with previous alpha intact
166170
case QoiChunk.QoiOpRgb:
167-
pixelBytes = new byte[3];
168-
if (stream.Read(pixelBytes) < 3)
171+
if (stream.Read(pixelBytes[..3]) < 3)
169172
{
170173
ThrowInvalidImageContentException();
171174
}
172175

173-
readPixel = previousPixel with { R = pixelBytes[0], G = pixelBytes[1], B = pixelBytes[2] };
176+
readPixel.A = previousPixel.A;
174177
pixel.FromRgba32(readPixel);
175178
pixelArrayPosition = GetArrayPosition(readPixel);
176179
previouslySeenPixels[pixelArrayPosition] = readPixel;
177180
break;
178181

179182
// Reading one pixel with new alpha
180183
case QoiChunk.QoiOpRgba:
181-
pixelBytes = new byte[4];
182184
if (stream.Read(pixelBytes) < 4)
183185
{
184186
ThrowInvalidImageContentException();
185187
}
186188

187-
readPixel = new Rgba32(pixelBytes[0], pixelBytes[1], pixelBytes[2], pixelBytes[3]);
188189
pixel.FromRgba32(readPixel);
189190
pixelArrayPosition = GetArrayPosition(readPixel);
190191
previouslySeenPixels[pixelArrayPosition] = readPixel;
@@ -206,9 +207,9 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
206207
blueDifference = (byte)(operationByte & 0b00000011);
207208
readPixel = previousPixel with
208209
{
209-
R = (byte)((previousPixel.R + (redDifference - 2)) % 256),
210-
G = (byte)((previousPixel.G + (greenDifference - 2)) % 256),
211-
B = (byte)((previousPixel.B + (blueDifference - 2)) % 256)
210+
R = (byte)Numerics.Modulo256(previousPixel.R + (redDifference - 2)),
211+
G = (byte)Numerics.Modulo256(previousPixel.G + (greenDifference - 2)),
212+
B = (byte)Numerics.Modulo256(previousPixel.B + (blueDifference - 2))
212213
};
213214
pixel.FromRgba32(readPixel);
214215
pixelArrayPosition = GetArrayPosition(readPixel);
@@ -219,12 +220,12 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
219220
// depending on the green one
220221
case QoiChunk.QoiOpLuma:
221222
byte diffGreen = (byte)(operationByte & 0b00111111),
222-
currentGreen = (byte)((previousPixel.G + (diffGreen - 32)) % 256),
223+
currentGreen = (byte)Numerics.Modulo256(previousPixel.G + (diffGreen - 32)),
223224
nextByte = (byte)stream.ReadByte(),
224225
diffRedDG = (byte)(nextByte >> 4),
225226
diffBlueDG = (byte)(nextByte & 0b00001111),
226-
currentRed = (byte)((diffRedDG - 8 + (diffGreen - 32) + previousPixel.R) % 256),
227-
currentBlue = (byte)((diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B) % 256);
227+
currentRed = (byte)Numerics.Modulo256(diffRedDG - 8 + (diffGreen - 32) + previousPixel.R),
228+
currentBlue = (byte)Numerics.Modulo256(diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B);
228229
readPixel = previousPixel with { R = currentRed, B = currentBlue, G = currentGreen };
229230
pixel.FromRgba32(readPixel);
230231
pixelArrayPosition = GetArrayPosition(readPixel);
@@ -233,7 +234,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
233234

234235
// Repeating the previous pixel 1..63 times
235236
case QoiChunk.QoiOpRun:
236-
byte repetitions = (byte)(operationByte & 0b00111111);
237+
int repetitions = operationByte & 0b00111111;
237238
if (repetitions is 62 or 63)
238239
{
239240
ThrowInvalidImageContentException();
@@ -247,9 +248,10 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
247248
{
248249
j = 0;
249250
i++;
251+
row = pixels.DangerousGetRowSpan(i);
250252
}
251253

252-
pixels[j, i] = pixel;
254+
row[j] = pixel;
253255
}
254256

255257
j--;
@@ -263,7 +265,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
263265
break;
264266
}
265267

266-
pixels[j, i] = pixel;
268+
row[j] = pixel;
267269
previousPixel = readPixel;
268270
}
269271
}
@@ -283,5 +285,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
283285
}
284286
}
285287

286-
private static int GetArrayPosition(Rgba32 pixel) => ((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)) % 64;
288+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
289+
private static int GetArrayPosition(Rgba32 pixel)
290+
=> Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
287291
}

src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers.Binary;
5+
using System.Runtime.CompilerServices;
56
using SixLabors.ImageSharp.Memory;
67
using SixLabors.ImageSharp.PixelFormats;
78

@@ -63,6 +64,7 @@ private static void WritePixels<TPixel>(Image<TPixel> image, Stream stream)
6364
for (int j = 0; j < pixels.Width && i < pixels.Height; j++)
6465
{
6566
// We get the RGBA value from pixels
67+
Span<TPixel> row = pixels.DangerousGetRowSpan(i);
6668
TPixel currentPixel = pixels[j, i];
6769
currentPixel.ToRgba32(ref currentRgba32);
6870

@@ -87,14 +89,15 @@ private static void WritePixels<TPixel>(Image<TPixel> image, Stream stream)
8789
{
8890
j = 0;
8991
i++;
92+
if (i == pixels.Height)
93+
{
94+
break;
95+
}
96+
row = pixels.DangerousGetRowSpan(i);
9097
}
9198

92-
if (i == pixels.Height)
93-
{
94-
break;
95-
}
9699

97-
currentPixel = pixels[j, i];
100+
currentPixel = row[j];
98101
currentPixel.ToRgba32(ref currentRgba32);
99102
}
100103
while (currentRgba32.Equals(previousPixel) && repetitions < 62);
@@ -196,6 +199,7 @@ private static void WriteEndOfStream(Stream stream)
196199
stream.WriteByte(1);
197200
}
198201

202+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
199203
private static int GetArrayPosition(Rgba32 pixel)
200-
=> ((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)) % 64;
204+
=> Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
201205
}

0 commit comments

Comments
 (0)