33
44using System . Buffers . Binary ;
55using System . Diagnostics . CodeAnalysis ;
6+ using System . Runtime . CompilerServices ;
7+ using System . Runtime . InteropServices ;
68using SixLabors . ImageSharp . IO ;
79using SixLabors . ImageSharp . Memory ;
810using 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}
0 commit comments