<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.1.25080.3" />

BufferedGraphicsContext

public sealed class BufferedGraphicsContext : IDisposable
Provides methods for creating graphics buffers that can be used for double buffering.
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Gdi; namespace System.Drawing { [NullableContext(1)] [Nullable(0)] public sealed class BufferedGraphicsContext : IDisposable { private Size _maximumBuffer; private Size _bufferSize = Size.Empty; private Size _virtualSize; private Point _targetLoc; private HDC _compatDC; private HBITMAP _dib; private HBITMAP _oldBitmap; [Nullable(2)] private Graphics _compatGraphics; [Nullable(2)] private BufferedGraphics _buffer; private int _busy; private bool _invalidateWhenFree; private const int BufferFree = 0; private const int BufferBusyPainting = 1; private const int BufferBusyDisposing = 2; public Size MaximumBuffer { get { return _maximumBuffer; } set { if (value.Width <= 0 || value.Height <= 0) throw new ArgumentException(System.SR.Format(System.SR.InvalidArgumentValue, "MaximumBuffer", value), "value"); if (value.Width * value.Height < _maximumBuffer.Width * _maximumBuffer.Height) Invalidate(); _maximumBuffer = value; } } public BufferedGraphicsContext() { _maximumBuffer.Width = 225; _maximumBuffer.Height = 96; } ~BufferedGraphicsContext() { Dispose(false); } public BufferedGraphics Allocate(Graphics targetGraphics, Rectangle targetRectangle) { if (ShouldUseTempManager(targetRectangle)) return AllocBufferInTempManager(targetGraphics, HDC.Null, targetRectangle); return AllocBuffer(targetGraphics, HDC.Null, targetRectangle); } public BufferedGraphics Allocate(IntPtr targetDC, Rectangle targetRectangle) { if (ShouldUseTempManager(targetRectangle)) return AllocBufferInTempManager(null, (HDC)targetDC, targetRectangle); return AllocBuffer(null, (HDC)targetDC, targetRectangle); } private BufferedGraphics AllocBuffer([Nullable(2)] Graphics targetGraphics, HDC targetDC, Rectangle targetRectangle) { if (Interlocked.CompareExchange(ref _busy, 1, 0) != 0) return AllocBufferInTempManager(targetGraphics, targetDC, targetRectangle); _targetLoc = new Point(targetRectangle.X, targetRectangle.Y); try { Graphics bufferedGraphicsSurface = default(Graphics); if (targetGraphics != null) { IntPtr hdc = targetGraphics.GetHdc(); try { bufferedGraphicsSurface = CreateBuffer((HDC)hdc, targetRectangle.Width, targetRectangle.Height); } finally { targetGraphics.ReleaseHdcInternal(hdc); } } else bufferedGraphicsSurface = CreateBuffer(targetDC, targetRectangle.Width, targetRectangle.Height); _buffer = new BufferedGraphics(bufferedGraphicsSurface, this, targetGraphics, targetDC, _targetLoc, _virtualSize); } catch { _busy = 0; throw; } return _buffer; } private static BufferedGraphics AllocBufferInTempManager([Nullable(2)] Graphics targetGraphics, HDC targetDC, Rectangle targetRectangle) { BufferedGraphicsContext bufferedGraphicsContext = null; BufferedGraphics bufferedGraphics = null; try { bufferedGraphicsContext = new BufferedGraphicsContext(); bufferedGraphics = bufferedGraphicsContext.AllocBuffer(targetGraphics, targetDC, targetRectangle); bufferedGraphics.DisposeContext = true; return bufferedGraphics; } finally { if (bufferedGraphicsContext != null && (bufferedGraphics == null || !bufferedGraphics.DisposeContext)) bufferedGraphicsContext.Dispose(); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private bool ShouldUseTempManager(Rectangle targetBounds) { int num = targetBounds.Width * targetBounds.Height; Size maximumBuffer = MaximumBuffer; int width = maximumBuffer.Width; maximumBuffer = MaximumBuffer; return num > width * maximumBuffer.Height; } [NullableContext(0)] private unsafe bool FillBitmapInfo(HDC hdc, HPALETTE hpalette, BITMAPINFO* bitmapInfo) { CreateBitmapScope scope = new CreateBitmapScope(hdc, 1, 1); try { if (scope.IsNull) throw new OutOfMemoryException(System.SR.GraphicsBufferQueryFail); bitmapInfo->bmiHeader.biSize = (uint)sizeof(BITMAPINFOHEADER); PInvoke.GetDIBits(hdc, (HBITMAP)ref scope, 0, 0, null, bitmapInfo, DIB_USAGE.DIB_RGB_COLORS); if (bitmapInfo->bmiHeader.biBitCount > 8) { if (bitmapInfo->bmiHeader.biCompression == 3) PInvoke.GetDIBits(hdc, (HBITMAP)ref scope, 0, (uint)bitmapInfo->bmiHeader.biHeight, null, bitmapInfo, DIB_USAGE.DIB_RGB_COLORS); return true; } return FillColorTable(hdc, hpalette, bitmapInfo); } finally { scope.Dispose(); } } [NullableContext(0)] private unsafe bool FillColorTable(HDC hdc, HPALETTE hpalette, BITMAPINFO* bitmapInfo) { int num = 1 << (int)bitmapInfo->bmiHeader.biBitCount; if (num > 256) return false; PALETTEENTRY* ptr = stackalloc PALETTEENTRY[num]; Span<RGBQUAD> span = new Span<RGBQUAD>(&bitmapInfo->bmiColors, num); if (((!hpalette.IsNull) ? PInvokeCore.GetPaletteEntries(hpalette, 0, (uint)num, ptr) : PInvokeCore.GetPaletteEntries((HPALETTE)Graphics.GetHalftonePalette(), 0, (uint)num, ptr)) == 0) return false; for (int i = 0; i < num; i++) { span[i].rgbRed = ptr[i].peRed; span[i].rgbGreen = ptr[i].peGreen; span[i].rgbBlue = ptr[i].peBlue; span[i].rgbReserved = 0; } return true; } private Graphics CreateBuffer(HDC src, int width, int height) { _busy = 2; DisposeDC(); _busy = 1; _compatDC = PInvokeCore.CreateCompatibleDC(src); if (width > _bufferSize.Width || height > _bufferSize.Height) { int num = Math.Max(width, _bufferSize.Width); int num2 = Math.Max(height, _bufferSize.Height); _busy = 2; DisposeBitmap(); _busy = 1; _dib = CreateCompatibleDIB(src, HPALETTE.Null, num, num2); _bufferSize = new Size(num, num2); } _oldBitmap = (HBITMAP)PInvokeCore.SelectObject(_compatDC, _dib); _compatGraphics = Graphics.FromHdcInternal(_compatDC); _compatGraphics.TranslateTransform((float)(-_targetLoc.X), (float)(-_targetLoc.Y)); _virtualSize = new Size(width, height); GC.KeepAlive(this); return _compatGraphics; } private unsafe HBITMAP CreateCompatibleDIB(HDC hdc, HPALETTE hpalette, int ulWidth, int ulHeight) { if (hdc.IsNull) throw new ArgumentNullException("hdc"); HBITMAP result = HBITMAP.Null; BITMAPINFO* ptr = (BITMAPINFO*)stackalloc byte[(int)(uint)(sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD))]; OBJ_TYPE objectType = (OBJ_TYPE)PInvokeCore.GetObjectType(hdc); if ((uint)(objectType - 3) > 1 && objectType != OBJ_TYPE.OBJ_MEMDC && objectType != OBJ_TYPE.OBJ_ENHMETADC) throw new ArgumentException(System.SR.DCTypeInvalid); if (FillBitmapInfo(hdc, hpalette, ptr)) { ptr->bmiHeader.biWidth = ulWidth; ptr->bmiHeader.biHeight = ulHeight; if (ptr->bmiHeader.biCompression == 0) ptr->bmiHeader.biSizeImage = 0; else if (ptr->bmiHeader.biBitCount == 16) { ptr->bmiHeader.biSizeImage = (uint)(ulWidth * ulHeight * 2); } else if (ptr->bmiHeader.biBitCount == 32) { ptr->bmiHeader.biSizeImage = (uint)(ulWidth * ulHeight * 4); } else { ptr->bmiHeader.biSizeImage = 0; } ptr->bmiHeader.biClrUsed = 0; ptr->bmiHeader.biClrImportant = 0; void* ptr2 = null; result = PInvokeCore.CreateDIBSection(hdc, ptr, DIB_USAGE.DIB_RGB_COLORS, &ptr2, HANDLE.Null, 0); if (result.IsNull) throw new Win32Exception(Marshal.GetLastWin32Error()); } return result; } private void DisposeDC() { if (!_oldBitmap.IsNull && !_compatDC.IsNull) { PInvokeCore.SelectObject(_compatDC, _oldBitmap); _oldBitmap = HBITMAP.Null; } if (!_compatDC.IsNull) { PInvokeCore.DeleteDC(_compatDC); _compatDC = HDC.Null; } GC.KeepAlive(this); } private void DisposeBitmap() { if (!_dib.IsNull) { PInvokeCore.DeleteObject(_dib); _dib = HBITMAP.Null; } GC.KeepAlive(this); } private void Dispose(bool disposing) { int num = Interlocked.CompareExchange(ref _busy, 2, 0); if (disposing) { if (num == 1) throw new InvalidOperationException(System.SR.GraphicsBufferCurrentlyBusy); if (_compatGraphics != null) { _compatGraphics.Dispose(); _compatGraphics = null; } } DisposeDC(); DisposeBitmap(); if (_buffer != null) { _buffer.Dispose(); _buffer = null; } _bufferSize = Size.Empty; _virtualSize = Size.Empty; _busy = 0; } public void Invalidate() { if (Interlocked.CompareExchange(ref _busy, 2, 0) == 0) { Dispose(); _busy = 0; } else _invalidateWhenFree = true; } internal void ReleaseBuffer() { _buffer = null; if (_invalidateWhenFree) { _busy = 2; Dispose(); } else { _busy = 2; DisposeDC(); } _busy = 0; } } }