506 lines
14 KiB
C#
506 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace BizHawk.Client.EmuHawk.CustomControls
|
|
{
|
|
/// <summary>
|
|
/// Wrapper for GDI rendering functions
|
|
/// This class is not thread-safe as GDI functions should be called from the UI thread
|
|
/// </summary>
|
|
public sealed class GdiRenderer : IControlRenderer
|
|
{
|
|
// Cache of all the Fonts used, rather than create them again and again
|
|
private readonly Dictionary<Font, FontCacheEntry> _fontsCache = new Dictionary<Font, FontCacheEntry>();
|
|
|
|
private class FontCacheEntry
|
|
{
|
|
public IntPtr HFont;
|
|
public IntPtr RotatedHFont;
|
|
}
|
|
|
|
// Cache of all the brushes used, rather than create them again and again
|
|
private readonly Dictionary<Color, IntPtr> _brushCache = new Dictionary<Color, IntPtr>();
|
|
|
|
private Graphics _g;
|
|
private IntPtr _hdc;
|
|
private IntPtr _currentBrush = IntPtr.Zero;
|
|
|
|
#region Construct and Destroy
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var brush in _brushCache)
|
|
{
|
|
if (brush.Value != IntPtr.Zero)
|
|
{
|
|
DeleteObject(brush.Value);
|
|
}
|
|
}
|
|
|
|
foreach (var fc in _fontsCache)
|
|
{
|
|
DeleteObject(fc.Value.HFont);
|
|
DeleteObject(fc.Value.RotatedHFont);
|
|
}
|
|
|
|
System.Diagnostics.Debug.Assert(_hdc == IntPtr.Zero, "Disposed a GDIRenderer while it held an HDC");
|
|
System.Diagnostics.Debug.Assert(_g == null, "Disposed a GDIRenderer while it held a Graphics");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Api
|
|
|
|
public void DrawBitmap(Bitmap bitmap, Point point)
|
|
{
|
|
IntPtr hBmp = bitmap.GetHbitmap();
|
|
var bitHdc = CreateCompatibleDC(CurrentHdc);
|
|
IntPtr old = SelectObject(bitHdc, hBmp);
|
|
AlphaBlend(CurrentHdc, point.X, point.Y, bitmap.Width, bitmap.Height, bitHdc, 0, 0, bitmap.Width, bitmap.Height, new BLENDFUNCTION(AC_SRC_OVER, 0, 0xff, AC_SRC_ALPHA));
|
|
SelectObject(bitHdc, old);
|
|
DeleteDC(bitHdc);
|
|
DeleteObject(hBmp);
|
|
}
|
|
|
|
public IDisposable LockGraphics(Graphics g, int width, int height)
|
|
{
|
|
_g = g;
|
|
_hdc = g.GetHdc();
|
|
SetBkMode(_hdc, BkModes.TRANSPARENT);
|
|
var l = new GdiGraphicsLock(this);
|
|
StartOffScreenBitmap(width, height);
|
|
return l;
|
|
}
|
|
|
|
public Size MeasureString(string str, Font font)
|
|
{
|
|
SetFont(font);
|
|
|
|
var size = new Size();
|
|
GetTextExtentPoint32(CurrentHdc, str, str.Length, ref size);
|
|
return size;
|
|
}
|
|
|
|
public void DrawString(string str, Point point)
|
|
{
|
|
TextOut(CurrentHdc, point.X, point.Y, str, str.Length);
|
|
}
|
|
|
|
public static IntPtr CreateNormalHFont(Font font, int width)
|
|
{
|
|
var logFont = new LOGFONT();
|
|
font.ToLogFont(logFont);
|
|
logFont.lfWidth = width;
|
|
logFont.lfOutPrecision = (byte)FontPrecision.OUT_TT_ONLY_PRECIS;
|
|
var ret = CreateFontIndirect(logFont);
|
|
return ret;
|
|
}
|
|
|
|
//this returns an IntPtr font because .net's Font class will erase the relevant properties when using its Font.FromLogFont()
|
|
//note that whether this is rotated clockwise or CCW might affect how you have to position the text (right-aligned sometimes?, up or down by the height of the font?)
|
|
public static IntPtr CreateRotatedHFont(Font font, bool cw)
|
|
{
|
|
LOGFONT logF = new LOGFONT();
|
|
font.ToLogFont(logF);
|
|
logF.lfEscapement = cw ? 2700 : 900;
|
|
logF.lfOrientation = logF.lfEscapement;
|
|
logF.lfOutPrecision = (byte)FontPrecision.OUT_TT_ONLY_PRECIS;
|
|
|
|
var ret = CreateFontIndirect(logF);
|
|
return ret;
|
|
}
|
|
|
|
// TODO: this should go away and be abstracted internally
|
|
public static void DestroyHFont(IntPtr hFont)
|
|
{
|
|
DeleteObject(hFont);
|
|
}
|
|
|
|
public void PrepDrawString(Font font, Color color, bool rotate = false)
|
|
{
|
|
var fontEntry = GetCachedHFont(font);
|
|
SetGraphicsMode(CurrentHdc, 2); // shouldn't be necessary.. cant hurt
|
|
SelectObject(CurrentHdc, rotate ? fontEntry.RotatedHFont : fontEntry.HFont);
|
|
SetTextColor(color);
|
|
}
|
|
|
|
// Set the text color of the device context
|
|
private void SetTextColor(Color color)
|
|
{
|
|
int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R;
|
|
SetTextColor(CurrentHdc, rgb);
|
|
}
|
|
|
|
public void DrawRectangle(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect)
|
|
{
|
|
Rectangle(CurrentHdc, nLeftRect, nTopRect, nRightRect, nBottomRect);
|
|
}
|
|
|
|
public void SetBrush(Color color)
|
|
{
|
|
if (_brushCache.ContainsKey(color))
|
|
{
|
|
_currentBrush = _brushCache[color];
|
|
}
|
|
else
|
|
{
|
|
int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R;
|
|
var newBrush = CreateSolidBrush(rgb);
|
|
_brushCache.Add(color, newBrush);
|
|
_currentBrush = newBrush;
|
|
}
|
|
}
|
|
|
|
public void FillRectangle(int x, int y, int w, int h)
|
|
{
|
|
var r = new GDIRect(new Rectangle(x, y, w, h));
|
|
FillRect(CurrentHdc, ref r, _currentBrush);
|
|
}
|
|
|
|
public void SetSolidPen(Color color)
|
|
{
|
|
int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R;
|
|
SelectObject(CurrentHdc, GetStockObject((int)PaintObjects.DC_PEN));
|
|
SetDCPenColor(CurrentHdc, rgb);
|
|
}
|
|
|
|
public void Line(int x1, int y1, int x2, int y2)
|
|
{
|
|
MoveToEx(CurrentHdc, x1, y1, IntPtr.Zero);
|
|
LineTo(CurrentHdc, x2, y2);
|
|
}
|
|
|
|
private IntPtr CurrentHdc => _bitHdc != IntPtr.Zero ? _bitHdc : _hdc;
|
|
|
|
private IntPtr _bitMap = IntPtr.Zero;
|
|
private IntPtr _bitHdc = IntPtr.Zero;
|
|
private int _bitW;
|
|
private int _bitH;
|
|
|
|
private void StartOffScreenBitmap(int width, int height)
|
|
{
|
|
_bitW = width;
|
|
_bitH = height;
|
|
|
|
_bitHdc = CreateCompatibleDC(_hdc);
|
|
_bitMap = CreateCompatibleBitmap(_hdc, width, height);
|
|
SelectObject(_bitHdc, _bitMap);
|
|
SetBkMode(_bitHdc, BkModes.TRANSPARENT);
|
|
}
|
|
|
|
private void EndOffScreenBitmap()
|
|
{
|
|
_bitW = 0;
|
|
_bitH = 0;
|
|
|
|
DeleteObject(_bitMap);
|
|
DeleteObject(_bitHdc);
|
|
|
|
_bitHdc = IntPtr.Zero;
|
|
_bitMap = IntPtr.Zero;
|
|
}
|
|
|
|
private void CopyToScreen()
|
|
{
|
|
BitBlt(_hdc, 0, 0, _bitW, _bitH, _bitHdc, 0, 0, 0x00CC0020);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
// Set a resource (e.g. a font) for the current device context.
|
|
private void SetFont(Font font)
|
|
{
|
|
var blah = GetCachedHFont(font);
|
|
SelectObject(CurrentHdc, blah.HFont);
|
|
}
|
|
|
|
private FontCacheEntry GetCachedHFont(Font font)
|
|
{
|
|
FontCacheEntry fontEntry;
|
|
var result = _fontsCache.TryGetValue(font, out fontEntry);
|
|
if (!result)
|
|
{
|
|
// Hack! The 6 is hardcoded to make tastudio look like taseditor, because taseditor is so perfect and wonderful
|
|
fontEntry = new FontCacheEntry
|
|
{
|
|
HFont = CreateNormalHFont(font, 6),
|
|
RotatedHFont = CreateRotatedHFont(font, true)
|
|
};
|
|
_fontsCache.Add(font, fontEntry);
|
|
}
|
|
|
|
return fontEntry;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Imports
|
|
|
|
// ReSharper disable IdentifierTypo
|
|
[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
|
|
private static extern IntPtr CreateFontIndirect(
|
|
[In, MarshalAs(UnmanagedType.LPStruct)]LOGFONT lplf
|
|
);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern int Rectangle(IntPtr hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int FillRect(IntPtr hdc, [In] ref GDIRect lprc, IntPtr hbr);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern int SetBkMode(IntPtr hdc, BkModes mode);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
|
|
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiObj);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern int SetTextColor(IntPtr hdc, int color);
|
|
|
|
[DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32W")]
|
|
private static extern int GetTextExtentPoint32(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string str, int len, ref Size size);
|
|
|
|
[DllImport("gdi32.dll", EntryPoint = "TextOutW")]
|
|
private static extern bool TextOut(IntPtr hdc, int x, int y, [MarshalAs(UnmanagedType.LPWStr)] string str, int len);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
public static extern int SetGraphicsMode(IntPtr hdc, int iMode);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern bool DeleteObject(IntPtr hObject);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern IntPtr CreateSolidBrush(int color);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern uint MoveToEx(IntPtr hdc, int x, int y, IntPtr point);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern uint LineTo(IntPtr hdc, int nXEnd, int nYEnd);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern IntPtr GetStockObject(int fnObject);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern uint SetDCPenColor(IntPtr hdc, int crColor);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
|
|
|
[DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
|
|
public static extern bool DeleteDC([In] IntPtr hdc);
|
|
|
|
[DllImport("gdi32.dll")]
|
|
private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int height);
|
|
|
|
[DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
|
|
|
|
[DllImport("gdi32.dll", EntryPoint = "GdiAlphaBlend")]
|
|
static extern bool AlphaBlend(IntPtr hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, BLENDFUNCTION blendFunction);
|
|
|
|
// ReSharper disable InconsistentNaming
|
|
// ReSharper disable UnusedMember.Global
|
|
// ReSharper disable UnusedMember.Local
|
|
// ReSharper disable NotAccessedField.Local
|
|
// ReSharper disable ArrangeTypeMemberModifiers
|
|
public enum FontPrecision : byte
|
|
{
|
|
|
|
OUT_DEFAULT_PRECIS = 0,
|
|
OUT_STRING_PRECIS = 1,
|
|
OUT_CHARACTER_PRECIS = 2,
|
|
OUT_STROKE_PRECIS = 3,
|
|
OUT_TT_PRECIS = 4,
|
|
OUT_DEVICE_PRECIS = 5,
|
|
OUT_RASTER_PRECIS = 6,
|
|
OUT_TT_ONLY_PRECIS = 7,
|
|
OUT_OUTLINE_PRECIS = 8,
|
|
OUT_SCREEN_OUTLINE_PRECIS = 9,
|
|
OUT_PS_ONLY_PRECIS = 10,
|
|
}
|
|
|
|
// It is important for this to be the right declaration
|
|
// See more here http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.framework.drawing/2004-04/0319.html
|
|
// If it's wrong (I had a wrong one from pinvoke.net) then ToLogFont will fail mysteriously
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
|
class LOGFONT
|
|
{
|
|
public int lfHeight = 0;
|
|
public int lfWidth = 0;
|
|
public int lfEscapement = 0;
|
|
public int lfOrientation = 0;
|
|
public int lfWeight = 0;
|
|
public byte lfItalic = 0;
|
|
public byte lfUnderline = 0;
|
|
public byte lfStrikeOut = 0;
|
|
public byte lfCharSet = 0;
|
|
public byte lfOutPrecision = 0;
|
|
public byte lfClipPrecision = 0;
|
|
public byte lfQuality = 0;
|
|
public byte lfPitchAndFamily = 0;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
|
public string lfFaceName = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The graphics mode that can be set by SetGraphicsMode.
|
|
/// </summary>
|
|
public enum GraphicsMode : int
|
|
{
|
|
/// <summary>
|
|
/// Sets the graphics mode that is compatible with 16-bit Windows. This is the default mode. If
|
|
/// this value is specified, the application can only modify the world-to-device transform by
|
|
/// calling functions that set window and viewport extents and origins, but not by using
|
|
/// SetWorldTransform or ModifyWorldTransform; calls to those functions will fail.
|
|
/// Examples of functions that set window and viewport extents and origins are SetViewportExtEx
|
|
/// and SetWindowExtEx.
|
|
/// </summary>
|
|
GM_COMPATIBLE = 1,
|
|
/// <summary>
|
|
/// Sets the advanced graphics mode that allows world transformations. This value must be
|
|
/// specified if the application will set or modify the world transformation for the specified
|
|
/// device context. In this mode all graphics, including text output, fully conform to the
|
|
/// world-to-device transformation specified in the device context.
|
|
/// </summary>
|
|
GM_ADVANCED = 2,
|
|
}
|
|
|
|
/// <summary>
|
|
/// The XFORM structure specifies a world-space to page-space transformation.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct XFORM
|
|
{
|
|
public float eM11;
|
|
public float eM12;
|
|
public float eM21;
|
|
public float eM22;
|
|
public float eDx;
|
|
public float eDy;
|
|
|
|
public XFORM(float eM11, float eM12, float eM21, float eM22, float eDx, float eDy)
|
|
{
|
|
this.eM11 = eM11;
|
|
this.eM12 = eM12;
|
|
this.eM21 = eM21;
|
|
this.eM22 = eM22;
|
|
this.eDx = eDx;
|
|
this.eDy = eDy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows implicit conversion to a managed transformation matrix.
|
|
/// </summary>
|
|
public static implicit operator System.Drawing.Drawing2D.Matrix(XFORM xf)
|
|
{
|
|
return new System.Drawing.Drawing2D.Matrix(xf.eM11, xf.eM12, xf.eM21, xf.eM22, xf.eDx, xf.eDy);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows implicit conversion from a managed transformation matrix.
|
|
/// </summary>
|
|
public static implicit operator XFORM(System.Drawing.Drawing2D.Matrix m)
|
|
{
|
|
float[] elems = m.Elements;
|
|
return new XFORM(elems[0], elems[1], elems[2], elems[3], elems[4], elems[5]);
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct BLENDFUNCTION
|
|
{
|
|
byte BlendOp;
|
|
byte BlendFlags;
|
|
byte SourceConstantAlpha;
|
|
byte AlphaFormat;
|
|
|
|
public BLENDFUNCTION(byte op, byte flags, byte alpha, byte format)
|
|
{
|
|
BlendOp = op;
|
|
BlendFlags = flags;
|
|
SourceConstantAlpha = alpha;
|
|
AlphaFormat = format;
|
|
}
|
|
}
|
|
|
|
const byte AC_SRC_OVER = 0x00;
|
|
const byte AC_SRC_ALPHA = 0x01;
|
|
|
|
#endregion
|
|
|
|
#region Classes, Structs, and Enums
|
|
|
|
private class GdiGraphicsLock : IDisposable
|
|
{
|
|
private readonly GdiRenderer _gdi;
|
|
|
|
public GdiGraphicsLock(GdiRenderer gdi)
|
|
{
|
|
_gdi = gdi;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_gdi.CopyToScreen();
|
|
_gdi.EndOffScreenBitmap();
|
|
_gdi._g.ReleaseHdc(_gdi._hdc);
|
|
_gdi._hdc = IntPtr.Zero;
|
|
_gdi._g = null;
|
|
}
|
|
}
|
|
|
|
private struct GDIRect
|
|
{
|
|
private int left;
|
|
private int top;
|
|
private int right;
|
|
private int bottom;
|
|
|
|
public GDIRect(Rectangle r)
|
|
{
|
|
left = r.Left;
|
|
top = r.Top;
|
|
bottom = r.Bottom;
|
|
right = r.Right;
|
|
}
|
|
}
|
|
|
|
private enum PaintObjects
|
|
{
|
|
WHITE_BRUSH = 0,
|
|
LTGRAY_BRUSH = 1,
|
|
GRAY_BRUSH = 2,
|
|
DKGRAY_BRUSH = 3,
|
|
BLACK_BRUSH = 4,
|
|
NULL_BRUSH = 5,
|
|
WHITE_PEN = 6,
|
|
BLACK_PEN = 7,
|
|
NULL_PEN = 8,
|
|
OEM_FIXED_FONT = 10,
|
|
ANSI_FIXED_FONT = 11,
|
|
ANSI_VAR_FONT = 12,
|
|
SYSTEM_FONT = 13,
|
|
DEVICE_DEFAULT_FONT = 14,
|
|
DEFAULT_PALETTE = 15,
|
|
SYSTEM_FIXED_FONT = 16,
|
|
DC_BRUSH = 18,
|
|
DC_PEN = 19,
|
|
}
|
|
|
|
private enum BkModes : int
|
|
{
|
|
TRANSPARENT = 1,
|
|
OPAQUE = 2
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|