private Palette?InitializePalette(IReadableBitmapData source, IAsyncContext context) { using var alg = new TAlg(); alg.Initialize(quantizer.maxColors, source); int width = source.Width; IReadableBitmapDataRow row = source.FirstRow; context.Progress?.New(DrawingOperation.InitializingQuantizer, source.Height); do { if (context.IsCancellationRequested) { return(null); } // TODO: parallel if possible for (int x = 0; x < width; x++) { Color32 c = row[x]; // handling alpha including full transparency if (c.A != Byte.MaxValue) { c = c.A < quantizer.alphaThreshold ? default : c.BlendWithBackground(quantizer.backColor); } alg.AddColor(c); } context.Progress?.Increment(); } while (row.MoveNextRow()); Color32[]? palette = alg.GeneratePalette(context); return(context.IsCancellationRequested ? null : new Palette(palette !, quantizer.backColor, quantizer.alphaThreshold)); }
internal override void Initialize(Bitmap source, bool isInUse) { // this must be the first line to prevent disposing source if next lines fail isSourceCloned = isInUse; PixelFormat origPixelFormat = source.PixelFormat; PixelFormat pixelFormat = origPixelFormat.HasAlpha() || origPixelFormat.IsIndexed() && source.Palette.Entries.Any(c => c.A != Byte.MaxValue) ? PixelFormat.Format32bppPArgb : PixelFormat.Format24bppRgb; targetBitmap = new Bitmap(Size.Width, Size.Height, pixelFormat); targetBitmapData = targetBitmap.GetReadWriteBitmapData(); // Locking on source image to avoid "bitmap region is already locked" if the UI is painting the image when we clone it. // This works this way because UI can repaint the image any time and is also locks the image for that period. // Another solution could be if we used a clone of the original image but it is better to avoid using multiple clones. if (isInUse) { // if image is in use (in the view of this VM) we lock it only for a short time to prevent the UI freezing lock (source) sourceBitmap = source.CloneCurrentFrame(); } else { // If no direct use could be detected using a long-term lock to spare a clone. // It is still needed because the image still can be used in the main V/VM. Monitor.Enter(source); sourceBitmap = source; } sourceBitmapData = sourceBitmap.GetReadableBitmapData(); }
private static void WriteRawBitmap(Bitmap bitmap, BinaryWriter bw) { Size size = bitmap.Size; bw.Write(size.Width); bw.Write(size.Height); PixelFormat pixelFormat = bitmap.PixelFormat; bw.Write((int)pixelFormat); if (pixelFormat.ToBitsPerPixel() <= 8) { Color[] palette = bitmap.Palette.Entries; bw.Write(palette.Length); foreach (Color color in palette) { bw.Write(color.ToArgb()); } } using (IReadableBitmapData data = bitmap.GetReadableBitmapData()) { int width = data.RowSize >> 2; IReadableBitmapDataRow row = data.FirstRow; do { for (int x = 0; x < width; x++) { bw.Write(row.ReadRaw <int>(x)); } } while (row.MoveNextRow()); } }
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(); } } }
public void CopyToClippedTest(PixelFormat pixelFormat) { using IReadableBitmapData src = Icons.Information.ExtractBitmap(new Size(256, 256)).ConvertPixelFormat(pixelFormat).GetReadableBitmapData(); Size targetSize = new Size(128, 128); using IReadWriteBitmapData dst = BitmapDataFactory.CreateBitmapData(targetSize, pixelFormat); new PerformanceTest { CpuAffinity = null, Iterations = 10_000 }
protected static void AssertAreEqual(IReadableBitmapData source, IReadableBitmapData target, bool allowDifferentPixelFormats = false, Rectangle sourceRectangle = default, Point targetLocation = default) { if (sourceRectangle == default) { sourceRectangle = new Rectangle(Point.Empty, source.GetSize()); } Assert.AreEqual(sourceRectangle.Size, target.GetSize()); if (!allowDifferentPixelFormats) { Assert.AreEqual(source.PixelFormat, target.PixelFormat); } IReadableBitmapDataRow rowSrc = source[sourceRectangle.Y]; IReadableBitmapDataRow rowDst = target[targetLocation.Y]; bool tolerantCompare = source.GetType() != target.GetType() && source.PixelFormat.ToBitsPerPixel() > 32; for (int y = 0; y < sourceRectangle.Height; y++) { if (tolerantCompare) { for (int x = 0; x < sourceRectangle.Width; x++) { Color32 c1 = rowSrc[x + sourceRectangle.X]; Color32 c2 = rowDst[x + targetLocation.X]; // this is faster than the asserts below if (c1.A != c2.A || Math.Abs(c1.R - c2.R) > 5 || Math.Abs(c1.G - c2.G) > 5 || Math.Abs(c1.B - c2.B) > 5) { Assert.Fail($"Diff at {x}; {rowSrc.Index}: {c1} vs. {c2}"); } //Assert.AreEqual(c1.A, c2.A, $"Diff at {x}; {rowSrc.Index}"); //Assert.That(() => Math.Abs(c1.R - c2.R), new LessThanOrEqualConstraint(1), $"Diff at {x}; {rowSrc.Index}"); //Assert.That(() => Math.Abs(c1.G - c2.G), new LessThanOrEqualConstraint(1), $"Diff at {x}; {rowSrc.Index}"); //Assert.That(() => Math.Abs(c1.B - c2.B), new LessThanOrEqualConstraint(1), $"Diff at {x}; {rowSrc.Index}"); } continue; } for (int x = 0; x < sourceRectangle.Width; x++) { Assert.AreEqual(rowSrc[x + sourceRectangle.X], rowDst[x + targetLocation.X], $"Diff at {x}; {rowSrc.Index}"); } } while (rowSrc.MoveNextRow() && rowDst.MoveNextRow()) { ; } }
public void ToGrayscaleTest() { using Bitmap bmp = Icons.Information.ExtractBitmap(new Size(256, 256)); new PerformanceTest { Iterations = 100, CpuAffinity = null } .AddCase(() => { using var result = new Bitmap(bmp.Width, bmp.Height); using (Graphics g = Graphics.FromImage(result)) { // Grayscale color matrix var colorMatrix = new ColorMatrix(new float[][] { new float[] { ColorExtensions.RLum, ColorExtensions.RLum, ColorExtensions.RLum, 0, 0 }, new float[] { ColorExtensions.GLum, ColorExtensions.GLum, ColorExtensions.GLum, 0, 0 }, new float[] { ColorExtensions.BLum, ColorExtensions.BLum, ColorExtensions.BLum, 0, 0 }, new float[] { 0, 0, 0, 1, 0 }, new float[] { 0, 0, 0, 0, 1 } }); using (var attrs = new ImageAttributes()) { attrs.SetColorMatrix(colorMatrix); g.DrawImage(bmp, new Rectangle(0, 0, result.Width, result.Height), 0, 0, result.Width, result.Height, GraphicsUnit.Pixel, attrs); } } }, "Graphics.DrawImage(..., ImageAttributes)") .AddCase(() => { using var result = new Bitmap(bmp.Width, bmp.Height); using IReadableBitmapData src = bmp.GetReadableBitmapData(); using IWritableBitmapData dst = result.GetWritableBitmapData(); IReadableBitmapDataRow rowSrc = src.FirstRow; IWritableBitmapDataRow rowDst = dst.FirstRow; do { for (int x = 0; x < src.Width; x++) { rowDst[x] = rowSrc[x].ToGray(); } } while (rowSrc.MoveNextRow() && rowDst.MoveNextRow()); }, "Sequential processing") .AddCase(() => { using var result = bmp.ToGrayscale(); }, "ImageExtensions.ToGrayscale") .DoTest() .DumpResults(Console.Out); }
IQuantizingSession IQuantizer.Initialize(IReadableBitmapData source, IAsyncContext?context) { context ??= AsyncContext.Null; switch (algorithm) { case Algorithm.Octree: return(new OptimizedPaletteQuantizerSession <OctreeQuantizer>(this, source, context)); case Algorithm.MedianCut: return(new OptimizedPaletteQuantizerSession <MedianCutQuantizer>(this, source, context)); case Algorithm.Wu: return(new OptimizedPaletteQuantizerSession <WuQuantizer>(this, source, context)); default: throw new InvalidOperationException(Res.InternalError($"Unexpected algorithm: {algorithm}")); } }
internal override void SetCompleted() { sourceBitmapData?.Dispose(); sourceBitmapData = null; if (isSourceCloned) { sourceBitmap?.Dispose(); } else if (sourceBitmap != null) { Monitor.Exit(sourceBitmap); } sourceBitmap = null; targetBitmapData?.Dispose(); targetBitmapData = null; base.SetCompleted(); }
public OptimizedPaletteQuantizerSession(OptimizedPaletteQuantizer quantizer, IReadableBitmapData source, IAsyncContext context) { this.quantizer = quantizer; Palette = InitializePalette(source, context); }
internal TestReadableBitmapData(IReadableBitmapData wrapped) => this.wrapped = wrapped;
public static Bitmap?ToBitmap(this Graphics graphics, bool visibleClipOnly) { if (!OSUtils.IsWindows) { throw new PlatformNotSupportedException(Res.RequiresWindows); } if (graphics == null) { throw new ArgumentNullException(nameof(graphics), PublicResources.ArgumentNull); } if (visibleClipOnly && graphics.IsVisibleClipEmpty) { return(null); } Bitmap result; RectangleF visibleRect; int sourceLeft, sourceTop, targetWidth, targetHeight; GraphicsState state = graphics.Save(); try { // resetting the identity matrix so VisibleClipBounds will be correct graphics.Transform = new Matrix(); // obtaining size in pixels graphics.PageUnit = GraphicsUnit.Pixel; visibleRect = graphics.VisibleClipBounds; sourceLeft = (int)visibleRect.Left; sourceTop = (int)visibleRect.Top; targetWidth = (int)visibleRect.Width; targetHeight = (int)visibleRect.Height; // there is a source image: copying so transparency is preserved Image?imgSource = graphics.GetBackingImage(); if (imgSource != null) { if (imgSource is Metafile) { throw new NotSupportedException(Res.GraphicsExtensionsToBitmapMetafileNotSupported); } if (!visibleClipOnly) { return((Bitmap)imgSource.Clone()); } if (targetWidth == 0 || targetHeight == 0) { return(null); } result = new Bitmap(targetWidth, targetHeight, imgSource.PixelFormat); using IReadableBitmapData src = ((Bitmap)imgSource).GetReadableBitmapData(); using IWritableBitmapData dst = result.GetWritableBitmapData(); src.CopyTo(dst, new Rectangle(sourceLeft, sourceTop, targetWidth, targetHeight), Point.Empty); return(result); } } finally { graphics.Restore(state); } IntPtr dcSource = graphics.GetHdc(); if (!visibleClipOnly) { sourceLeft = 0; sourceTop = 0; // obtaining container Window IntPtr hwnd = User32.WindowFromDC(dcSource); if (hwnd != IntPtr.Zero) { //// Show in whole screen //RECT rect; //GetWindowRect(hwnd, out rect); // the full rect of self control on screen //// Show in screen //GetWindowRect(hwnd, out rect); //left = -rect.Left; //top = -rect.Top; //width = GetDeviceCaps(dcSource, DeviceCap.HORZRES); //height = GetDeviceCaps(dcSource, DeviceCap.VERTRES); //visibleRect.Offset(rect.Left, rect.Top); //// Show in parent control //IntPtr hwndParent = GetParent(hwnd); //if (hwndParent != IntPtr.Zero) //{ // RECT rectParent; // GetWindowRect(hwndParent, out rectParent); // left = rectParent.Left - rect.Left; // top = rectParent.Top - rect.Top; // width = rectParent.Right - rectParent.Left; // height = rectParent.Bottom - rectParent.Top; // visibleRect.Offset(-left, -top); //} //else // Show in container control Rectangle rect = User32.GetClientRect(hwnd); if (rect.Right < visibleRect.Right && rect.Bottom < visibleRect.Bottom) { // Visible rect is larger than client rect: calculating from full size. // This is usually the case when Graphics is created for nonclient area rect = User32.GetWindowRect(hwnd); } targetWidth = rect.Right - rect.Left; targetHeight = rect.Bottom - rect.Top; } else if (visibleRect.Location != Point.Empty) { // no window: surrounding symmetrically or max 100 px targetWidth = (int)(visibleRect.Right + Math.Min(visibleRect.Left, 100f)); targetHeight = (int)(visibleRect.Bottom + Math.Min(visibleRect.Top, 100f)); } } // the container control is too small if (targetWidth <= 0 || targetHeight <= 0) { graphics.ReleaseHdc(dcSource); return(null); } // creating a compatible bitmap IntPtr dcTarget = Gdi32.CreateCompatibleDC(dcSource); IntPtr hbmResult = Gdi32.CreateCompatibleBitmap(dcSource, targetWidth, targetHeight); Gdi32.SelectObject(dcTarget, hbmResult); // Copy content Gdi32.BitBlt(dcTarget, 0, 0, targetWidth, targetHeight, dcSource, sourceLeft, sourceTop); result = Image.FromHbitmap(hbmResult); //cleanup graphics.ReleaseHdc(dcSource); Gdi32.DeleteDC(dcTarget); Gdi32.DeleteObject(hbmResult); return(result); }
internal DitheringSessionSerpentine(IQuantizingSession quantizer, ErrorDiffusionDitherer ditherer, IReadableBitmapData source) : base(quantizer, ditherer, source) { this.source = source; preprocessedResults = new Color32[ImageWidth]; }
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++; } }
IDitheringSession IDitherer.Initialize(IReadableBitmapData source, IQuantizingSession quantizer, IAsyncContext?context) => new RandomNoiseDitheringSession(quantizer, this);