BufferedGraphicsContext
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;
}
}
}