static void ClearIndexed(IAsyncContext context, IBitmapDataInternal bitmapData, int bpp, Color32 color, int width) { int index = bitmapData.Palette.GetNearestColorIndex(color); byte byteValue = bpp == 8 ? (byte)index : bpp == 4 ? (byte)((index << 4) | index) : index == 1 ? Byte.MaxValue : Byte.MinValue; int left = 0; if (bitmapData.RowSize > 0) { int factor = bpp == 8 ? 0 : bpp == 4 ? 1 : 3; // writing as longs if ((bitmapData.RowSize & 0b111) == 0) { int longWidth = bitmapData.RowSize >> 3; uint intValue = (uint)((byteValue << 24) | (byteValue << 16) | (byteValue << 8) | byteValue); ClearRaw(context, bitmapData, longWidth, ((ulong)intValue << 32) | intValue); left = (longWidth << 3) << factor; } // writing as integers else if ((bitmapData.RowSize & 0b11) == 0) { int intWidth = bitmapData.RowSize >> 2; ClearRaw(context, bitmapData, intWidth, (byteValue << 24) | (byteValue << 16) | (byteValue << 8) | byteValue); left = (intWidth << 2) << factor; }
static void Clear32Bpp(IAsyncContext context, IBitmapDataInternal bitmapData, Color32 color, int width) { int longWidth = bitmapData.RowSize >> 3; // writing as longs if (longWidth > 0) { Color32 rawColor = bitmapData.PixelFormat switch { PixelFormat.Format32bppPArgb => color.ToPremultiplied(), PixelFormat.Format32bppRgb => color.BlendWithBackground(bitmapData.BackColor), _ => color, }; uint argb = (uint)rawColor.ToArgb(); ClearRaw(context, bitmapData, longWidth, ((ulong)argb << 32) | argb); } // handling the rest (can be either the last column if width is odd, or even the whole content if RowSize is 0) int left = longWidth << 1; if (left < width && !context.IsCancellationRequested) { ClearDirectFallback(context, bitmapData, color, left); } }
private static void DoSaveWidePlatformDependent(IAsyncContext context, IBitmapDataInternal bitmapData, Rectangle rect, BinaryWriter writer) { PixelFormat pixelFormat = bitmapData.PixelFormat; int byteLength = pixelFormat.ToBitsPerPixel() >> 3; // using a temp 1x1 managed bitmap data for the conversion using IBitmapDataInternal tempData = CreateManagedBitmapData(new Size(1, 1), pixelFormat, bitmapData.BackColor, bitmapData.AlphaThreshold); IBitmapDataRowInternal tempRow = tempData.DoGetRow(0); IBitmapDataRowInternal row = bitmapData.DoGetRow(rect.Top); for (int y = 0; y < rect.Height; y++) { if (context.IsCancellationRequested) { return; } for (int x = rect.Left; x < rect.Right; x++) { tempRow.DoSetColor32(0, row.DoGetColor32(x)); for (int i = 0; i < byteLength; i++) { writer.Write(tempRow.DoReadRaw <byte>(i)); } } row.MoveNextRow(); context.Progress?.Increment(); } }
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); }
static void Clear16Bpp(IAsyncContext context, IBitmapDataInternal bitmapData, Color32 color, int width) { int longWidth = bitmapData.RowSize >> 3; // writing as longs if (longWidth > 0) { ushort shortValue = bitmapData.PixelFormat switch { PixelFormat.Format16bppArgb1555 => new Color16Argb1555(color).Value, PixelFormat.Format16bppRgb565 => new Color16Rgb565(color).Value, PixelFormat.Format16bppRgb555 => new Color16Rgb555(color).Value, _ => new Color16Gray(color).Value }; uint uintValue = (uint)((shortValue << 16) | shortValue); ClearRaw(context, bitmapData, longWidth, ((ulong)uintValue << 32) | uintValue); } // handling the rest (or even the whole content if RowSize is 0) int left = longWidth << 2; if (left < width && !context.IsCancellationRequested) { ClearDirectFallback(context, bitmapData, color, left); } }
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); }
internal CopySession(IAsyncContext context, IBitmapDataInternal sessionSource, IBitmapDataInternal sessionTarget, Rectangle actualSourceRectangle, Rectangle actualTargetRectangle) { this.context = context; Source = sessionSource; Target = sessionTarget; SourceRectangle = actualSourceRectangle; TargetRectangle = actualTargetRectangle; }
internal static void DoSaveBitmapData(IAsyncContext context, IBitmapDataInternal bitmapData, Rectangle rect, Stream stream) { PixelFormat pixelFormat = bitmapData.PixelFormat; Debug.Assert(pixelFormat == PixelFormat.Format32bppArgb || bitmapData.RowSize >= pixelFormat.GetByteWidth(rect.Right)); Debug.Assert(pixelFormat.IsAtByteBoundary(rect.Left)); context.Progress?.New(DrawingOperation.Saving, rect.Height + 1); var writer = new BinaryWriter(stream); writer.Write(magicNumber); writer.Write(rect.Width); writer.Write(rect.Height); writer.Write((int)pixelFormat); writer.Write(bitmapData.BackColor.ToArgb()); writer.Write(bitmapData.AlphaThreshold); Palette?palette = bitmapData.Palette; writer.Write(palette?.Count ?? 0); if (palette != null) { foreach (Color32 entry in palette.Entries) { writer.Write(entry.ToArgb()); } } context.Progress?.Increment(); if (context.IsCancellationRequested) { return; } try { if (pixelFormat.ToBitsPerPixel() > 32 && bitmapData is NativeBitmapDataBase && ColorExtensions.Max16BppValue != UInt16.MaxValue) { DoSaveWidePlatformDependent(context, bitmapData, rect, writer); return; } DoSaveRaw(context, bitmapData, rect, writer); } finally { stream.Flush(); } }
private static void DoClear(IAsyncContext context, IWritableBitmapData bitmapData, Color32 color, IDitherer ditherer) { IBitmapDataInternal accessor = bitmapData as IBitmapDataInternal ?? new BitmapDataWrapper(bitmapData, false, true); try { if (ditherer == null || !accessor.PixelFormat.CanBeDithered()) ClearDirect(context, accessor, color); else ClearWithDithering(context, accessor, color, ditherer); } finally { if (!ReferenceEquals(accessor, bitmapData)) accessor.Dispose(); } }
private void PerformCopyDirectPremultiplied() { // Sequential processing if (SourceRectangle.Width < parallelThreshold) { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); IBitmapDataRowInternal rowSrc = Source.DoGetRow(SourceRectangle.Y); IBitmapDataRowInternal rowDst = Target.DoGetRow(TargetRectangle.Y); for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } for (int x = 0; x < SourceRectangle.Width; x++) { rowDst.DoSetColor32Premultiplied(x + TargetRectangle.X, rowSrc.DoGetColor32Premultiplied(x + SourceRectangle.X)); } 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 => { 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; for (int x = 0; x < width; x++) { rowDst.DoSetColor32Premultiplied(x + offsetDst, rowSrc.DoGetColor32Premultiplied(x + offsetSrc)); } }); }
private static void DoSaveRaw(IAsyncContext context, IBitmapDataInternal bitmapData, Rectangle rect, BinaryWriter writer) { PixelFormat pixelFormat = bitmapData.PixelFormat; int bpp = pixelFormat.ToBitsPerPixel(); Debug.Assert(pixelFormat.IsAtByteBoundary(rect.Left)); switch (bpp) { case 1: rect.X >>= 3; rect.Width = pixelFormat.IsAtByteBoundary(rect.Width) ? rect.Width >> 3 : (rect.Width >> 3) + 1; DoSaveRawBytes(context, bitmapData, rect, writer); return; case 4: rect.X >>= 1; rect.Width = pixelFormat.IsAtByteBoundary(rect.Width) ? rect.Width >> 1 : (rect.Width >> 1) + 1; DoSaveRawBytes(context, bitmapData, rect, writer); return; case 8: DoSaveRawBytes(context, bitmapData, rect, writer); return; case 16: DoSaveRawShorts(context, bitmapData, rect, writer); return; case 32: DoSaveRawInts(context, bitmapData, rect, writer); return; case 64: DoSaveRawLongs(context, bitmapData, rect, writer); return; default: // 24/48bpp int byteSize = bpp >> 3; rect.X *= byteSize; rect.Width *= byteSize; DoSaveRawBytes(context, bitmapData, rect, writer); return; } }
private static void DoSaveRawLongs(IAsyncContext context, IBitmapDataInternal bitmapData, Rectangle rect, BinaryWriter writer) { IBitmapDataRowInternal row = bitmapData.DoGetRow(rect.Top); for (int y = 0; y < rect.Height; y++) { if (context.IsCancellationRequested) { return; } for (int x = rect.Left; x < rect.Right; x++) { writer.Write(row.DoReadRaw <long>(x)); } row.MoveNextRow(); context.Progress?.Increment(); } }
public void ReplaceColorTest() { using var bmp = Icons.Shield.ExtractBitmap(new Size(128, 128), true).Resize(new Size(256, 256)); new PerformanceTest { Iterations = 100, CpuAffinity = null } .AddCase(() => { using var result = bmp.CloneBitmap(); result.MakeTransparent(Color.Black); }, "Bitmap.MakeTransparent") .AddCase(() => { using var result = bmp.CloneBitmap(); result.ReplaceColor(Color.Black, Color.Transparent); }, "BitmapExtensions.ReplaceColor") .AddCase(() => { using var result = bmp.CloneBitmap(); using (IBitmapDataInternal bitmapData = BitmapDataFactory.CreateBitmapData(result, ImageLockMode.ReadWrite)) { Color32 from = new Color32(Color.Black); Color32 to = new Color32(Color.Transparent); IBitmapDataRowInternal row = bitmapData.DoGetRow(0); do { for (int x = 0; x < bitmapData.Width; x++) { if (row[x] != from) { continue; } row[x] = to; } } while (row.MoveNextRow()); } }, "Sequential processing") .DoTest() .DumpResults(Console.Out); }
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); }
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 }
private static void ClearDirect(IAsyncContext context, IBitmapDataInternal bitmapData, Color32 color) {
internal ClippedBitmapData(IBitmapData source, Rectangle clippingRegion) { if (source == null) { throw new ArgumentNullException(nameof(source)); } region = clippingRegion; // source is already clipped: unwrapping to prevent tiered nesting (not calling Unwrap because other types should not be extracted here) if (source is ClippedBitmapData parent) { BitmapData = parent.BitmapData; region.Offset(parent.region.Location); region.Intersect(parent.region); } else { BitmapData = source; region.Intersect(new Rectangle(Point.Empty, source.GetSize())); } if (region.IsEmpty) { throw new ArgumentOutOfRangeException(nameof(clippingRegion), PublicResources.ArgumentOutOfRange); } bitmapDataType = BitmapData switch { IBitmapDataInternal _ => BitmapDataType.Internal, IReadWriteBitmapData _ => BitmapDataType.ReadWrite, IReadableBitmapData _ => BitmapDataType.Readable, IWritableBitmapData _ => BitmapDataType.Writable, _ => BitmapDataType.None }; PixelFormat = BitmapData.PixelFormat; BackColor = BitmapData.BackColor; AlphaThreshold = BitmapData.AlphaThreshold; Palette = BitmapData.Palette; int bpp = PixelFormat.ToBitsPerPixel(); int maxRowSize = (region.Width * bpp) >> 3; RowSize = region.Left > 0 // Any clipping from the left disables raw access because ReadRaw/WriteRaw offset depends on size of T, // which will fail for any T whose size is not the same as the actual pixel size ? 0 // Even one byte padding is disabled to protect the right edge of a region by default : Math.Min(source.RowSize, maxRowSize); if (bpp >= 8 || RowSize < maxRowSize) { return; } // 1/4bpp: Adjust RowSize if needed // right edge: if not at byte boundary but that is the right edge of the original image, then we allow including padding if (PixelFormat.IsAtByteBoundary(region.Width) && region.Right == BitmapData.Width) { RowSize++; } }
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 static IReadWriteBitmapData?DoLoadBitmapData(IAsyncContext context, Stream stream) { context.Progress?.New(DrawingOperation.Loading, 1000); var reader = new BinaryReader(stream); if (reader.ReadInt32() != magicNumber) { throw new ArgumentException(Res.ImagingNotBitmapDataStream, nameof(stream)); } var size = new Size(reader.ReadInt32(), reader.ReadInt32()); var pixelFormat = (PixelFormat)reader.ReadInt32(); Color32 backColor = Color32.FromArgb(reader.ReadInt32()); byte alphaThreshold = reader.ReadByte(); Palette?palette = null; int paletteLength = reader.ReadInt32(); if (paletteLength > 0) { var entries = new Color32[paletteLength]; for (int i = 0; i < paletteLength; i++) { entries[i] = Color32.FromArgb(reader.ReadInt32()); } palette = new Palette(entries, backColor, alphaThreshold); } context.Progress?.SetProgressValue((int)(stream.Position * 1000 / stream.Length)); if (context.IsCancellationRequested) { return(null); } IBitmapDataInternal result = CreateManagedBitmapData(size, pixelFormat, backColor, alphaThreshold, palette); int bpp = pixelFormat.ToBitsPerPixel(); bool canceled = false; try { IBitmapDataRowInternal row = result.DoGetRow(0); for (int y = 0; y < result.Height; y++) { if (canceled = context.IsCancellationRequested) { return(null); } switch (bpp) { case 32: for (int x = 0; x < result.Width; x++) { row.DoWriteRaw(x, reader.ReadInt32()); } break; case 16: for (int x = 0; x < result.Width; x++) { row.DoWriteRaw(x, reader.ReadInt16()); } break; case 64: for (int x = 0; x < result.Width; x++) { row.DoWriteRaw(x, reader.ReadInt64()); } break; default: for (int x = 0; x < result.RowSize; x++) { row.DoWriteRaw(x, reader.ReadByte()); } break; } row.MoveNextRow(); context.Progress?.SetProgressValue((int)(stream.Position * 1000 / stream.Length)); } return((canceled = context.IsCancellationRequested) ? null : result); } finally { if (canceled) { result.Dispose(); } } }
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)); } }); }
internal void PerformDrawDirect() { IBitmapDataInternal source = Source; IBitmapDataInternal target = Target; Point sourceLocation = SourceRectangle.Location; Point targetLocation = TargetRectangle.Location; int sourceWidth = SourceRectangle.Width; // Sequential processing if (SourceRectangle.Width < parallelThreshold) { if (Target.IsFastPremultiplied()) { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } ProcessRowPremultiplied(y); context.Progress?.Increment(); } } else { context.Progress?.New(DrawingOperation.ProcessingPixels, SourceRectangle.Height); for (int y = 0; y < SourceRectangle.Height; y++) { if (context.IsCancellationRequested) { return; } ProcessRowStraight(y); context.Progress?.Increment(); } } return; } // Parallel processing Action <int> processRow = Target.IsFastPremultiplied() ? ProcessRowPremultiplied : (Action <int>)ProcessRowStraight; ParallelHelper.For(context, DrawingOperation.ProcessingPixels, 0, SourceRectangle.Height, processRow); #region Local Methods void ProcessRowStraight(int y) { IBitmapDataRowInternal rowSrc = source.DoGetRow(sourceLocation.Y + y); IBitmapDataRowInternal rowDst = target.DoGetRow(targetLocation.Y + y); int offsetSrc = sourceLocation.X; int offsetDst = targetLocation.X; byte alphaThreshold = target.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, colorSrc); 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, colorSrc); } } void ProcessRowPremultiplied(int y) { 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; for (int x = 0; x < width; x++) { Color32 colorSrc = rowSrc.DoGetColor32Premultiplied(x + offsetSrc); // fully solid source: overwrite if (colorSrc.A == Byte.MaxValue) { rowDst.DoSetColor32Premultiplied(x + offsetDst, colorSrc); 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.DoGetColor32Premultiplied(pos); // non-transparent target: blending if (colorDst.A != 0) { colorSrc = colorSrc.BlendWithPremultiplied(colorDst); } rowDst.DoSetColor32Premultiplied(pos, colorSrc); } } #endregion }