public void DitherPerformanceTest(bool errorDiffusion) { using var bmpRef = Icons.Information.ExtractBitmap(new Size(256, 256)); IQuantizer quantizer = PredefinedColorsQuantizer.SystemDefault8BppPalette(); IDitherer ditherer = errorDiffusion ? (IDitherer)ErrorDiffusionDitherer.FloydSteinberg : OrderedDitherer.Bayer8x8; new PerformanceTest { TestName = $"{bmpRef.Width}x{bmpRef.Height}@{bmpRef.GetColorCount()} {(errorDiffusion ? "Error Diffusion" : "Ordered")}", Iterations = 100, CpuAffinity = null } .AddCase(() => { using var result = bmpRef.CloneBitmap(); result.Dither(quantizer, ditherer); }, "BitmapExtensions.Dither") .AddCase(() => { using var result = bmpRef.CloneBitmap(); using (IBitmapDataInternal bitmapData = BitmapDataFactory.CreateBitmapData(result, ImageLockMode.ReadWrite)) using (IQuantizingSession quantizingSession = quantizer.Initialize(bitmapData)) using (IDitheringSession ditheringSession = ditherer.Initialize(bitmapData, quantizingSession)) { var row = bitmapData.DoGetRow(0); int width = bitmapData.Width; do { for (int x = 0; x < width; x++) { row.DoSetColor32(x, ditheringSession.GetDitheredColor(row.DoGetColor32(x), x, row.Index)); } } while (row.MoveNextRow()); } }, "Sequential dithering") .DoTest() .DumpResults(Console.Out); }
public void QuantizePerformanceTest() { //using var bmpRef = new Bitmap(@"D:\Letolt\MYSTY8RQER62.jpg"); using var bmpRef = Icons.Information.ExtractBitmap(new Size(256, 256)); IQuantizer quantizer = PredefinedColorsQuantizer.SystemDefault8BppPalette(); new PerformanceTest { TestName = $"{bmpRef.Width}x{bmpRef.Height}@{bmpRef.GetColorCount()}", Iterations = 100, CpuAffinity = null } .AddCase(() => { using var result = bmpRef.CloneBitmap(); result.Quantize(quantizer); }, "BitmapExtensions.Quantize") .AddCase(() => { using var result = bmpRef.CloneBitmap(); using (IBitmapDataInternal bitmapData = BitmapDataFactory.CreateBitmapData(result, ImageLockMode.ReadWrite)) using (IQuantizingSession session = quantizer.Initialize(bitmapData)) { var row = bitmapData.DoGetRow(0); int width = bitmapData.Width; do { for (int x = 0; x < width; x++) { row.DoSetColor32(x, session.GetQuantizedColor(row.DoGetColor32(x))); } } while (row.MoveNextRow()); } }, "Sequential quantization") .DoTest() .DumpResults(Console.Out); }
internal void PerformDraw(IQuantizer?quantizer, IDitherer?ditherer) { if (quantizer == null) { PerformDrawDirect(); return; } IReadableBitmapData initSource = SourceRectangle.Size == Source.GetSize() ? Source : Source.Clip(SourceRectangle); try { Debug.Assert(!quantizer.InitializeReliesOnContent || !Source.HasMultiLevelAlpha(), "This draw performs blending on-the-fly but the used quantizer would require two-pass processing"); context.Progress?.New(DrawingOperation.InitializingQuantizer); using (IQuantizingSession quantizingSession = quantizer.Initialize(initSource, context)) { if (context.IsCancellationRequested) { return; } if (quantizingSession == null) { throw new InvalidOperationException(Res.ImagingQuantizerInitializeNull); } // quantizing without dithering if (ditherer == null) { PerformDrawWithQuantizer(quantizingSession); return; } // quantizing with dithering Debug.Assert(!ditherer.InitializeReliesOnContent || !Source.HasMultiLevelAlpha(), "This draw performs blending on-the-fly but the used ditherer would require two-pass processing"); context.Progress?.New(DrawingOperation.InitializingDitherer); using IDitheringSession ditheringSession = ditherer.Initialize(initSource, quantizingSession, context); if (context.IsCancellationRequested) { return; } if (ditheringSession == null) { throw new InvalidOperationException(Res.ImagingDithererInitializeNull); } PerformDrawWithDithering(quantizingSession, ditheringSession); } } finally { if (!ReferenceEquals(initSource, Source)) { initSource.Dispose(); } } }
internal InterleavedGradientNoiseDitheringSession(IQuantizingSession quantizingSession, InterleavedGradientNoiseDitherer ditherer) : base(quantizingSession) { if (ditherer.strength > 0f) { Strength = ditherer.strength; return; } CalibrateStrength(-127, 127); }
internal DitheringSessionRaster(IQuantizingSession quantizer, ErrorDiffusionDitherer ditherer, IBitmapData source) { this.quantizer = quantizer; this.ditherer = ditherer; ImageWidth = source.Width; imageHeight = source.Height; byBrightness = ditherer.byBrightness ?? quantizer.Palette?.IsGrayscale ?? false; // Initializing a circular buffer for the diffused errors. // This helps to minimize used memory because it needs only a few lines to be stored. // Another solution could be to store resulting colors instead of just the errors but then the color // entries would be clipped not just in the end but in every iteration, and small errors would be lost // that could stack up otherwise. // See also the ErrorDiffusionDitherer constructor for more comments on why using floats. errorsBuffer = new CircularList <(float, float, float)[]>(ditherer.matrixHeight);
internal static Color32 BlendOrMakeTransparent(this IQuantizingSession session, Color32 origColor) { // the color can be considered fully transparent if (origColor.A < session.AlphaThreshold) { // and even the quantizer returns a transparent color var c = session.GetQuantizedColor(origColor); if (c.A == 0) { return(c); } } // the color will not be transparent in the end: blending return(origColor.BlendWithBackground(session.BackColor)); }
internal RandomNoiseDitheringSession(IQuantizingSession quantizingSession, RandomNoiseDitherer ditherer) : base(quantizingSession) { this.ditherer = ditherer; // If we have don't have a seed, we must use a thread safe random generator because pixels can be queried in any order random = ditherer.seed == null ? ThreadSafeRandom.Instance : new FastRandom(ditherer.seed.Value); if (ditherer.strength > 0f) { Strength = ditherer.strength; return; } CalibrateStrength(-127, 127); }
public void ClearWithDitheringTest(PixelFormat pixelFormat, uint argb, bool errorDiffusion) { const int size = 512; Color color = Color.FromArgb((int)argb); var ditherer = errorDiffusion ? (IDitherer)ErrorDiffusionDitherer.FloydSteinberg : OrderedDitherer.Bayer8x8; new PerformanceTest { TestName = $"{pixelFormat} {size}x{size} {(errorDiffusion ? "Error Diffusion" : "Ordered Dithering")}", Iterations = 10, CpuAffinity = null } .AddCase(() => { using var bmp = new Bitmap(size, size, pixelFormat); using IBitmapDataInternal acc = BitmapDataFactory.CreateBitmapData(bmp, ImageLockMode.ReadWrite); IQuantizer quantizer = PredefinedColorsQuantizer.FromBitmapData(acc); var c = new Color32(color); using (IQuantizingSession quantizingSession = quantizer.Initialize(acc)) using (IDitheringSession ditheringSession = ditherer.Initialize(acc, quantizingSession)) { IReadWriteBitmapDataRow row = acc.DoGetRow(0); do { for (int x = 0; x < acc.Width; x++) { row[x] = ditheringSession.GetDitheredColor(c, x, row.Index); } } while (row.MoveNextRow()); } }, "Sequential clear") .AddCase(() => { using var bmp = new Bitmap(size, size, pixelFormat); bmp.Clear(color, ditherer); }, "BitmapDataAccessor.Clear") .DoTest() .DumpResults(Console.Out); }
internal NativeBitmapData(Bitmap bitmap, PixelFormat pixelFormat, ImageLockMode lockMode, IQuantizingSession quantizingSession) : base(bitmap, pixelFormat, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold, quantizingSession.Palette) { }
internal DitheringSessionSerpentine(IQuantizingSession quantizer, ErrorDiffusionDitherer ditherer, IReadableBitmapData source) : base(quantizer, ditherer, source) { this.source = source; preprocessedResults = new Color32[ImageWidth]; }
protected VariableStrengthDitheringSessionBase(IQuantizingSession quantizingSession) { QuantizingSession = quantizingSession ?? throw new ArgumentNullException(nameof(quantizingSession), PublicResources.ArgumentNull); }
internal void PerformCopyWithQuantizer(IQuantizingSession quantizingSession, bool skipTransparent) { // Sequential processing if (SourceRectangle.Width < parallelThreshold >> quantizingScale) { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); IBitmapDataRowInternal rowSrc = Source.DoGetRow(SourceRectangle.Y); IBitmapDataRowInternal rowDst = Target.DoGetRow(TargetRectangle.Y); byte alphaThreshold = quantizingSession.AlphaThreshold; for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } for (int x = 0; x < SourceRectangle.Width; x++) { Color32 colorSrc = rowSrc.DoGetColor32(x + SourceRectangle.X); if (skipTransparent && colorSrc.A < alphaThreshold) { continue; } rowDst.DoSetColor32(x + TargetRectangle.X, quantizingSession.GetQuantizedColor(colorSrc)); } rowSrc.MoveNextRow(); rowDst.MoveNextRow(); context.Progress?.Increment(); } return; } IBitmapDataInternal source = Source; IBitmapDataInternal target = Target; Point sourceLocation = SourceRectangle.Location; Point targetLocation = TargetRectangle.Location; int sourceWidth = SourceRectangle.Width; ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, y => { IQuantizingSession session = quantizingSession; IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y); IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y); int offsetSrc = sourceLocation.X; int offsetDst = targetLocation.X; int width = sourceWidth; byte alphaThreshold = session.AlphaThreshold; bool skip = skipTransparent; for (int x = 0; x < width; x++) { Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc); if (skip && colorSrc.A < alphaThreshold) { continue; } rowDst.DoSetColor32(x + offsetDst, session.GetQuantizedColor(colorSrc)); } }); }
private void PerformDrawWithDithering(IQuantizingSession quantizingSession, IDitheringSession ditheringSession) { IBitmapDataInternal source = Source; IBitmapDataInternal target = Target; Point sourceLocation = SourceRectangle.Location; Point targetLocation = TargetRectangle.Location; int sourceWidth = SourceRectangle.Width; // Sequential processing if (ditheringSession.IsSequential || SourceRectangle.Width < parallelThreshold >> ditheringScale) { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } ProcessRow(y); context.Progress?.Increment(); } return; } // Parallel processing ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, ProcessRow); #region Local Methods void ProcessRow(int y) { IDitheringSession session = ditheringSession; IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y); IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y); int offsetSrc = sourceLocation.X; int offsetDst = targetLocation.X; byte alphaThreshold = quantizingSession.AlphaThreshold; int width = sourceWidth; for (int x = 0; x < width; x++) { Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc); // fully solid source: overwrite if (colorSrc.A == Byte.MaxValue) { rowDst.DoSetColor32(x + offsetDst, session.GetDitheredColor(colorSrc, x, y)); continue; } // fully transparent source: skip if (colorSrc.A == 0) { continue; } // source here has a partial transparency: we need to read the target color int pos = x + offsetDst; Color32 colorDst = rowDst.DoGetColor32(pos); // non-transparent target: blending if (colorDst.A != 0) { colorSrc = colorDst.A == Byte.MaxValue // target pixel is fully solid: simple blending ? colorSrc.BlendWithBackground(colorDst) // both source and target pixels are partially transparent: complex blending : colorSrc.BlendWith(colorDst); } // overwriting target color only if blended color has high enough alpha if (colorSrc.A < alphaThreshold) { continue; } rowDst.DoSetColor32(pos, session.GetDitheredColor(colorSrc, x, y)); } } #endregion }
internal void PerformCopyWithDithering(IQuantizingSession quantizingSession, IDitheringSession ditheringSession, bool skipTransparent) { // Sequential processing if (ditheringSession.IsSequential || SourceRectangle.Width < parallelThreshold >> ditheringScale) { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); IBitmapDataRowInternal rowSrc = Source.DoGetRow(SourceRectangle.Y); IBitmapDataRowInternal rowDst = Target.DoGetRow(TargetRectangle.Y); byte alphaThreshold = quantizingSession.AlphaThreshold; for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } // we can pass x, y to the dithering session because if there is an offset it was initialized by a properly clipped rectangle for (int x = 0; x < SourceRectangle.Width; x++) { Color32 colorSrc = rowSrc.DoGetColor32(x + SourceRectangle.X); if (skipTransparent && colorSrc.A < alphaThreshold) { continue; } rowDst.DoSetColor32(x + TargetRectangle.X, ditheringSession.GetDitheredColor(colorSrc, x, y)); } rowSrc.MoveNextRow(); rowDst.MoveNextRow(); context.Progress?.Increment(); } return; } // Parallel processing IBitmapDataInternal source = Source; IBitmapDataInternal target = Target; Point sourceLocation = SourceRectangle.Location; Point targetLocation = TargetRectangle.Location; int sourceWidth = SourceRectangle.Width; ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, y => { IDitheringSession session = ditheringSession; IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y); IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y); int offsetSrc = sourceLocation.X; int offsetDst = targetLocation.X; int width = sourceWidth; byte alphaThreshold = quantizingSession.AlphaThreshold; bool skip = skipTransparent; // we can pass x, y to the dithering session because if there is an offset it was initialized by a properly clipped rectangle for (int x = 0; x < width; x++) { Color32 colorSrc = rowSrc.DoGetColor32(x + offsetSrc); if (skip && colorSrc.A < alphaThreshold) { continue; } rowDst.DoSetColor32(x + offsetDst, session.GetDitheredColor(colorSrc, x, y)); } }); }
IDitheringSession IDitherer.Initialize(IReadableBitmapData source, IQuantizingSession quantizer, IAsyncContext?context) => new RandomNoiseDitheringSession(quantizer, this);
/// <summary> /// Creates a native <see cref="IBitmapDataInternal"/> by a quantizer session re-using its palette if possible. /// </summary> internal static IBitmapDataInternal CreateBitmapData(Bitmap bitmap, ImageLockMode lockMode, IQuantizingSession quantizingSession) { if (bitmap == null) { throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull); } var pixelFormat = bitmap.PixelFormat; if (!pixelFormat.IsIndexed() || quantizingSession.Palette == null) { return(CreateBitmapData(bitmap, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold)); } // checking if bitmap and quantizer palette has the same entries if (!quantizingSession.Palette.Equals(bitmap.Palette.Entries)) { return(CreateBitmapData(bitmap, lockMode, quantizingSession.BackColor, quantizingSession.AlphaThreshold)); } if (!lockMode.IsDefined()) { throw new ArgumentOutOfRangeException(nameof(lockMode), PublicResources.EnumOutOfRange(lockMode)); } // here the quantizer and the target bitmap uses the same palette switch (pixelFormat) { case PixelFormat.Format8bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow8I>(bitmap, pixelFormat, lockMode, quantizingSession)); case PixelFormat.Format4bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow4I>(bitmap, pixelFormat, lockMode, quantizingSession)); case PixelFormat.Format1bppIndexed: return(new NativeBitmapData <NativeBitmapDataRow1I>(bitmap, pixelFormat, lockMode, quantizingSession)); default: throw new InvalidOperationException(Res.InternalError($"Unexpected indexed format: {pixelFormat}")); } }