3
3
4
4
using System . Buffers . Binary ;
5
5
using System . Diagnostics . CodeAnalysis ;
6
+ using System . Runtime . CompilerServices ;
7
+ using System . Runtime . InteropServices ;
6
8
using SixLabors . ImageSharp . IO ;
7
9
using SixLabors . ImageSharp . Memory ;
8
10
using SixLabors . ImageSharp . Metadata ;
@@ -85,7 +87,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
85
87
/// </summary>
86
88
/// <param name="stream">The stream where the bytes are being read</param>
87
89
/// <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 )
89
91
{
90
92
Span < byte > magicBytes = stackalloc byte [ 4 ] ;
91
93
Span < byte > widthBytes = stackalloc byte [ 4 ] ;
@@ -141,7 +143,7 @@ private void ProcessHeader(Stream stream)
141
143
private static void ThrowInvalidImageContentException ( )
142
144
=> throw new InvalidImageContentException ( "The image is not a valid QOI image." ) ;
143
145
144
- private void ProcessPixels < TPixel > ( Stream stream , Buffer2D < TPixel > pixels )
146
+ private void ProcessPixels < TPixel > ( BufferedReadStream stream , Buffer2D < TPixel > pixels )
145
147
where TPixel : unmanaged, IPixel < TPixel >
146
148
{
147
149
Rgba32 [ ] previouslySeenPixels = new Rgba32 [ 64 ] ;
@@ -151,40 +153,39 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
151
153
// See https://github.com/phoboslab/qoi/issues/258
152
154
int pixelArrayPosition = GetArrayPosition ( previousPixel ) ;
153
155
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 ;
154
160
155
161
for ( int i = 0 ; i < this . header . Height ; i ++ )
156
162
{
157
163
for ( int j = 0 ; j < this . header . Width ; j ++ )
158
164
{
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 ( ) ;
163
167
switch ( ( QoiChunk ) operationByte )
164
168
{
165
169
// Reading one pixel with previous alpha intact
166
170
case QoiChunk . QoiOpRgb :
167
- pixelBytes = new byte [ 3 ] ;
168
- if ( stream . Read ( pixelBytes ) < 3 )
171
+ if ( stream . Read ( pixelBytes [ ..3 ] ) < 3 )
169
172
{
170
173
ThrowInvalidImageContentException ( ) ;
171
174
}
172
175
173
- readPixel = previousPixel with { R = pixelBytes [ 0 ] , G = pixelBytes [ 1 ] , B = pixelBytes [ 2 ] } ;
176
+ readPixel . A = previousPixel . A ;
174
177
pixel . FromRgba32 ( readPixel ) ;
175
178
pixelArrayPosition = GetArrayPosition ( readPixel ) ;
176
179
previouslySeenPixels [ pixelArrayPosition ] = readPixel ;
177
180
break ;
178
181
179
182
// Reading one pixel with new alpha
180
183
case QoiChunk . QoiOpRgba :
181
- pixelBytes = new byte [ 4 ] ;
182
184
if ( stream . Read ( pixelBytes ) < 4 )
183
185
{
184
186
ThrowInvalidImageContentException ( ) ;
185
187
}
186
188
187
- readPixel = new Rgba32 ( pixelBytes [ 0 ] , pixelBytes [ 1 ] , pixelBytes [ 2 ] , pixelBytes [ 3 ] ) ;
188
189
pixel . FromRgba32 ( readPixel ) ;
189
190
pixelArrayPosition = GetArrayPosition ( readPixel ) ;
190
191
previouslySeenPixels [ pixelArrayPosition ] = readPixel ;
@@ -206,9 +207,9 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
206
207
blueDifference = ( byte ) ( operationByte & 0b00000011 ) ;
207
208
readPixel = previousPixel with
208
209
{
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 ) )
212
213
} ;
213
214
pixel . FromRgba32 ( readPixel ) ;
214
215
pixelArrayPosition = GetArrayPosition ( readPixel ) ;
@@ -219,12 +220,12 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
219
220
// depending on the green one
220
221
case QoiChunk . QoiOpLuma :
221
222
byte diffGreen = ( byte ) ( operationByte & 0b00111111 ) ,
222
- currentGreen = ( byte ) ( ( previousPixel . G + ( diffGreen - 32 ) ) % 256 ) ,
223
+ currentGreen = ( byte ) Numerics . Modulo256 ( previousPixel . G + ( diffGreen - 32 ) ) ,
223
224
nextByte = ( byte ) stream . ReadByte ( ) ,
224
225
diffRedDG = ( byte ) ( nextByte >> 4 ) ,
225
226
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 ) ;
228
229
readPixel = previousPixel with { R = currentRed , B = currentBlue , G = currentGreen } ;
229
230
pixel . FromRgba32 ( readPixel ) ;
230
231
pixelArrayPosition = GetArrayPosition ( readPixel ) ;
@@ -233,7 +234,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
233
234
234
235
// Repeating the previous pixel 1..63 times
235
236
case QoiChunk . QoiOpRun :
236
- byte repetitions = ( byte ) ( operationByte & 0b00111111 ) ;
237
+ int repetitions = operationByte & 0b00111111 ;
237
238
if ( repetitions is 62 or 63 )
238
239
{
239
240
ThrowInvalidImageContentException ( ) ;
@@ -247,9 +248,10 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
247
248
{
248
249
j = 0 ;
249
250
i ++ ;
251
+ row = pixels . DangerousGetRowSpan ( i ) ;
250
252
}
251
253
252
- pixels [ j , i ] = pixel ;
254
+ row [ j ] = pixel ;
253
255
}
254
256
255
257
j -- ;
@@ -263,7 +265,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
263
265
break ;
264
266
}
265
267
266
- pixels [ j , i ] = pixel ;
268
+ row [ j ] = pixel ;
267
269
previousPixel = readPixel ;
268
270
}
269
271
}
@@ -283,5 +285,7 @@ private void ProcessPixels<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
283
285
}
284
286
}
285
287
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 ) ) ;
287
291
}
0 commit comments