33
44using System ;
55using System . Runtime . CompilerServices ;
6- using System . Runtime . InteropServices ;
76using SixLabors . ImageSharp . Advanced ;
87using SixLabors . ImageSharp . PixelFormats ;
98using SixLabors . ImageSharp . Processing . Processors . Quantization ;
@@ -145,24 +144,27 @@ in Unsafe.AsRef(this),
145144 in ditherOperation ) ;
146145 }
147146
147+ // Spread assumes an even colorspace distribution and precision.
148+ // TODO: Cubed root is currently used to represent 3 color channels
149+ // but we should introduce something to PixelTypeInfo.
150+ // https://bisqwit.iki.fi/story/howto/dither/jy/
151+ // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
152+ internal static int CalculatePaletteSpread ( int colors )
153+ => ( int ) ( 255 / Math . Max ( 1 , Math . Pow ( colors , 1.0 / 3 ) - 1 ) ) ;
154+
148155 [ MethodImpl ( InliningOptions . ShortMethod ) ]
149156 internal TPixel Dither < TPixel > (
150157 TPixel source ,
151158 int x ,
152159 int y ,
153- int bitDepth ,
160+ int spread ,
154161 float scale )
155162 where TPixel : unmanaged, IPixel < TPixel >
156163 {
157- Rgba32 rgba = default ;
164+ Unsafe . SkipInit ( out Rgba32 rgba ) ;
158165 source . ToRgba32 ( ref rgba ) ;
159- Rgba32 attempt ;
166+ Unsafe . SkipInit ( out Rgba32 attempt ) ;
160167
161- // Spread assumes an even colorspace distribution and precision.
162- // Calculated as 0-255/component count. 256 / bitDepth
163- // https://bisqwit.iki.fi/story/howto/dither/jy/
164- // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
165- int spread = 256 / bitDepth ;
166168 float factor = spread * this . thresholdMatrix [ y % this . modulusY , x % this . modulusX ] * scale ;
167169
168170 attempt . R = ( byte ) Numerics . Clamp ( rgba . R + factor , byte . MinValue , byte . MaxValue ) ;
@@ -203,7 +205,7 @@ public override int GetHashCode()
203205 private readonly ImageFrame < TPixel > source ;
204206 private readonly IndexedImageFrame < TPixel > destination ;
205207 private readonly Rectangle bounds ;
206- private readonly int bitDepth ;
208+ private readonly int spread ;
207209
208210 [ MethodImpl ( InliningOptions . ShortMethod ) ]
209211 public QuantizeDitherRowOperation (
@@ -218,23 +220,24 @@ public QuantizeDitherRowOperation(
218220 this . source = source ;
219221 this . destination = destination ;
220222 this . bounds = bounds ;
221- this . bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( destination . Palette . Length ) ;
223+ this . spread = CalculatePaletteSpread ( destination . Palette . Length ) ;
222224 }
223225
224226 [ MethodImpl ( InliningOptions . ShortMethod ) ]
225227 public void Invoke ( int y )
226228 {
227- int offsetY = this . bounds . Top ;
228- int offsetX = this . bounds . Left ;
229+ ref TFrameQuantizer quantizer = ref Unsafe . AsRef ( this . quantizer ) ;
230+ int spread = this . spread ;
229231 float scale = this . quantizer . Options . DitherScale ;
230232
231- ref TPixel sourceRowRef = ref MemoryMarshal . GetReference ( this . source . GetPixelRowSpan ( y ) ) ;
232- ref byte destinationRowRef = ref MemoryMarshal . GetReference ( this . destination . GetWritablePixelRowSpanUnsafe ( y - offsetY ) ) ;
233+ ReadOnlySpan < TPixel > sourceRow = this . source . GetPixelRowSpan ( y ) . Slice ( this . bounds . X , this . bounds . Width ) ;
234+ Span < byte > destRow =
235+ this . destination . GetWritablePixelRowSpanUnsafe ( y - this . bounds . Y ) . Slice ( 0 , sourceRow . Length ) ;
233236
234- for ( int x = this . bounds . Left ; x < this . bounds . Right ; x ++ )
237+ for ( int x = 0 ; x < sourceRow . Length ; x ++ )
235238 {
236- TPixel dithered = this . dither . Dither ( Unsafe . Add ( ref sourceRowRef , x ) , x , y , this . bitDepth , scale ) ;
237- Unsafe . Add ( ref destinationRowRef , x - offsetX ) = Unsafe . AsRef ( this . quantizer ) . GetQuantizedColor ( dithered , out TPixel _ ) ;
239+ TPixel dithered = this . dither . Dither ( sourceRow [ x ] , x , y , spread , scale ) ;
240+ destRow [ x ] = quantizer . GetQuantizedColor ( dithered , out TPixel _ ) ;
238241 }
239242 }
240243 }
@@ -248,7 +251,7 @@ public void Invoke(int y)
248251 private readonly ImageFrame < TPixel > source ;
249252 private readonly Rectangle bounds ;
250253 private readonly float scale ;
251- private readonly int bitDepth ;
254+ private readonly int spread ;
252255
253256 [ MethodImpl ( InliningOptions . ShortMethod ) ]
254257 public PaletteDitherRowOperation (
@@ -262,19 +265,23 @@ public PaletteDitherRowOperation(
262265 this . source = source ;
263266 this . bounds = bounds ;
264267 this . scale = processor . DitherScale ;
265- this . bitDepth = ColorNumerics . GetBitsNeededForColorDepth ( processor . Palette . Length ) ;
268+ this . spread = CalculatePaletteSpread ( processor . Palette . Length ) ;
266269 }
267270
268271 [ MethodImpl ( InliningOptions . ShortMethod ) ]
269272 public void Invoke ( int y )
270273 {
271- ref TPixel sourceRowRef = ref MemoryMarshal . GetReference ( this . source . GetPixelRowSpan ( y ) ) ;
274+ ref TPaletteDitherImageProcessor processor = ref Unsafe . AsRef ( this . processor ) ;
275+ int spread = this . spread ;
276+ float scale = this . scale ;
277+
278+ Span < TPixel > row = this . source . GetPixelRowSpan ( y ) . Slice ( this . bounds . X , this . bounds . Width ) ;
272279
273- for ( int x = this . bounds . Left ; x < this . bounds . Right ; x ++ )
280+ for ( int x = 0 ; x < row . Length ; x ++ )
274281 {
275- ref TPixel sourcePixel = ref Unsafe . Add ( ref sourceRowRef , x ) ;
276- TPixel dithered = this . dither . Dither ( sourcePixel , x , y , this . bitDepth , this . scale ) ;
277- sourcePixel = Unsafe . AsRef ( this . processor ) . GetPaletteColor ( dithered ) ;
282+ ref TPixel sourcePixel = ref row [ x ] ;
283+ TPixel dithered = this . dither . Dither ( sourcePixel , x , y , spread , scale ) ;
284+ sourcePixel = processor . GetPaletteColor ( dithered ) ;
278285 }
279286 }
280287 }
0 commit comments