using System; using System.Text; using System.Collections.Generic; using System.Drawing; using System.Runtime.InteropServices; namespace BizHawk.Client.EmuHawk.CustomControls { /// /// Wrapper for GDI rendering functions /// This class is not thread-safe as GDI functions should be called from the UI thread /// public sealed class GDIRenderer : IDisposable { /// /// used for calculation. /// private static readonly int[] CharFit = new int[1]; /// /// used for calculation /// private static readonly int[] CharFitWidth = new int[1000]; /// /// Cache of all the HFONTs used, rather than create them again and again /// private readonly Dictionary FontsCache = new Dictionary(); class FontCacheEntry { public IntPtr HFont; } /// /// Cache of all the brushes used, rather than create them again and again /// private readonly Dictionary BrushCache = new Dictionary(); private Graphics _g; private IntPtr _hdc; private IntPtr _currentBrush = IntPtr.Zero; #region Construct and Destroy public GDIRenderer() { SetBkMode(_hdc, BkModes.OPAQUE); } 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); EndOffScreenBitmap(); 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 /// /// Draw a bitmap object at the given position /// public void DrawBitmap(Bitmap bitmap, Point point, bool blend = false) { IntPtr hbmp = bitmap.GetHbitmap(); var bitHDC = CreateCompatibleDC(CurrentHDC); IntPtr old = SelectObject(bitHDC, hbmp); if (blend) { 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)); } else { BitBlt(CurrentHDC, point.X, point.Y, bitmap.Width, bitmap.Height, bitHDC, 0, 0, 0xCC0020); } SelectObject(bitHDC, old); DeleteDC(bitHDC); DeleteObject(hbmp); } /// /// Required to use before calling drawing methods /// public GdiGraphicsLock LockGraphics(Graphics g) { _g = g; _hdc = g.GetHdc(); SetBkMode(_hdc, BkModes.TRANSPARENT); return new GdiGraphicsLock(this); } /// /// Measure the width and height of string when drawn on device context HDC /// using the given font /// public Size MeasureString(string str, Font font) { SetFont(font); var size = new Size(); GetTextExtentPoint32(CurrentHDC, str, str.Length, ref size); return size; } /// /// Measure the width and height of string when drawn on device context HDC /// using the given font /// Restrict the width of the string and get the number of characters able to fit in the restriction and /// the width those characters take /// /// the max width to render the string in /// the number of characters that will fit under restriction /// public Size MeasureString(string str, Font font, float maxWidth, out int charFit, out int charFitWidth) { SetFont(font); var size = new Size(); GetTextExtentExPoint(CurrentHDC, str, str.Length, (int)Math.Round(maxWidth), CharFit, CharFitWidth, ref size); charFit = CharFit[0]; charFitWidth = charFit > 0 ? CharFitWidth[charFit - 1] : 0; return size; } public void DrawString(string str, Point point) { TextOut(CurrentHDC, point.X, point.Y, str, str.Length); } //this returns an IntPtr HFONT 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.lfFaceName = "System"; logf.lfEscapement = 3600 - 450; logf.lfOrientation = logf.lfEscapement; logf.lfOutPrecision = (byte)FontPrecision.OUT_TT_ONLY_PRECIS; //this doesnt work! .net erases the relevant propreties.. it seems? //return Font.FromLogFont(logf); var ret = CreateFontIndirect(ref logf); return ret; } public static void DestroyHFont(IntPtr hfont) { DeleteObject(hfont); } public void PrepDrawString(IntPtr hfont, Color color) { SetGraphicsMode(CurrentHDC, 2); //shouldnt be necessary.. cant hurt SelectObject(_hdc, hfont); SetTextColor(color); } public void PrepDrawString(Font font, Color color) { SetFont(font); SetTextColor(color); } /// /// Draw the given string using the given font and foreground color at given location /// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15] /// public void DrawString(string str, Font font, Color color, Rectangle rect, TextFormatFlags flags) { SetFont(font); SetTextColor(color); var rect2 = new Rect(rect); DrawText(CurrentHDC, str, str.Length, ref rect2, (uint)flags); } /// /// Set the text color of the device context /// public void SetTextColor(Color color) { int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R; SetTextColor(CurrentHDC, rgb); } public void SetBackgroundColor(Color color) { int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R; SetBkColor(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 SetPenPosition(int x, int y) { MoveToEx(CurrentHDC, x, y, IntPtr.Zero); } 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 { get { return _bitHDC != IntPtr.Zero ? _bitHDC : _hdc; } } private IntPtr _bitMap = IntPtr.Zero; private IntPtr _bitHDC = IntPtr.Zero; private int _bitW; private int _bitH; public 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); } public void EndOffScreenBitmap() { _bitW = 0; _bitH = 0; DeleteObject(_bitMap); DeleteObject(_bitHDC); _bitHDC = IntPtr.Zero; _bitMap = IntPtr.Zero; } public void CopyToScreen() { BitBlt(_hdc, 0, 0, _bitW, _bitH, _bitHDC, 0, 0, 0x00CC0020); } #endregion #region Helpers /// /// Set a resource (e.g. a font) for the specified device context. /// private void SetFont(Font font) { SelectObject(_hdc, GetCachedHFont(font)); } private IntPtr GetCachedHFont(Font font) { //the original code struck me as bad. attempting to ID fonts by picking a subset of their fields is not gonna work. //don't call this.Font in InputRoll.cs, it is probably slow. //consider Fonts to be a jealously guarded resource (they need to be disposed, after all) and manage them carefully. //this cache maintains the HFONTs only. FontCacheEntry ce; if (!FontsCache.TryGetValue(font, out ce)) { FontsCache[font] = ce = new FontCacheEntry(); ce.HFont = font.ToHfont(); } return ce.HFont; } #endregion #region Imports [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll")] private static extern IntPtr BeginPaint(IntPtr hWnd, ref IntPtr lpPaint); [DllImport("user32.dll")] private static extern IntPtr EndPaint(IntPtr hWnd, IntPtr lpPaint); [DllImport("gdi32.dll")] static extern IntPtr CreateFontIndirect([In] ref 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")] private static extern int SetBkColor(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 = "GetTextExtentExPointW")] private static extern bool GetTextExtentExPoint(IntPtr hDc, [MarshalAs(UnmanagedType.LPWStr)]string str, int nLength, int nMaxExtent, int[] lpnFit, int[] alpDx, 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("user32.dll", EntryPoint = "DrawTextW")] private static extern int DrawText(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string str, int len, ref Rect rect, uint uFormat); [DllImport("gdi32.dll", EntryPoint = "ExtTextOutW")] private static extern bool ExtTextOut(IntPtr hdc, int X, int Y, uint fuOptions, uint cbCount, [In] IntPtr lpDx); [DllImport("gdi32.dll")] static extern bool SetWorldTransform(IntPtr hdc, [In] ref XFORM lpXform); [DllImport("gdi32.dll")] private static extern int SelectClipRgn(IntPtr hdc, IntPtr hrgn); [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 IntPtr CreatePen(int fnPenStyle, int nWidth, int color); [DllImport("gdi32.dll")] private static extern IntPtr MoveToEx(IntPtr hdc, int x, int y, IntPtr point); [DllImport("gdi32.dll")] private static extern IntPtr LineTo(IntPtr hdc, int nXEnd, int nYEnd); [DllImport("gdi32.dll")] private static extern IntPtr GetStockObject(int fnObject); [DllImport("gdi32.dll")] private static extern IntPtr 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); public enum FontWeight : int { FW_DONTCARE = 0, FW_THIN = 100, FW_EXTRALIGHT = 200, FW_LIGHT = 300, FW_NORMAL = 400, FW_MEDIUM = 500, FW_SEMIBOLD = 600, FW_BOLD = 700, FW_EXTRABOLD = 800, FW_HEAVY = 900, } public enum FontCharSet : byte { ANSI_CHARSET = 0, DEFAULT_CHARSET = 1, SYMBOL_CHARSET = 2, SHIFTJIS_CHARSET = 128, HANGEUL_CHARSET = 129, HANGUL_CHARSET = 129, GB2312_CHARSET = 134, CHINESEBIG5_CHARSET = 136, OEM_CHARSET = 255, JOHAB_CHARSET = 130, HEBREW_CHARSET = 177, ARABIC_CHARSET = 178, GREEK_CHARSET = 161, TURKISH_CHARSET = 162, VIETNAMESE_CHARSET = 163, THAI_CHARSET = 222, EASTEUROPE_CHARSET = 238, RUSSIAN_CHARSET = 204, MAC_CHARSET = 77, BALTIC_CHARSET = 186, } 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, } public enum FontClipPrecision : byte { CLIP_DEFAULT_PRECIS = 0, CLIP_CHARACTER_PRECIS = 1, CLIP_STROKE_PRECIS = 2, CLIP_MASK = 0xf, CLIP_LH_ANGLES = (1 << 4), CLIP_TT_ALWAYS = (2 << 4), CLIP_DFA_DISABLE = (4 << 4), CLIP_EMBEDDED = (8 << 4), } public enum FontQuality : byte { DEFAULT_QUALITY = 0, DRAFT_QUALITY = 1, PROOF_QUALITY = 2, NONANTIALIASED_QUALITY = 3, ANTIALIASED_QUALITY = 4, CLEARTYPE_QUALITY = 5, CLEARTYPE_NATURAL_QUALITY = 6, } [Flags] public enum FontPitchAndFamily : byte { DEFAULT_PITCH = 0, FIXED_PITCH = 1, VARIABLE_PITCH = 2, FF_DONTCARE = (0 << 4), FF_ROMAN = (1 << 4), FF_SWISS = (2 << 4), FF_MODERN = (3 << 4), FF_SCRIPT = (4 << 4), FF_DECORATIVE = (5 << 4), } //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; } /// /// The graphics mode that can be set by SetGraphicsMode. /// public enum GraphicsMode : int { /// /// 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. /// GM_COMPATIBLE = 1, /// /// 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. /// GM_ADVANCED = 2, } /// /// The XFORM structure specifies a world-space to page-space transformation. /// [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; } /// /// Allows implicit converstion to a managed transformation matrix. /// 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); } /// /// Allows implicit converstion from a managed transformation matrix. /// 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; [DllImport("gdi32.dll")] static extern int SetBitmapBits(IntPtr hbmp, uint cBytes, byte[] lpBits); #endregion #region Classes, Structs, and Enums public class GdiGraphicsLock : IDisposable { private readonly GDIRenderer Gdi; public GdiGraphicsLock(GDIRenderer gdi) { this.Gdi = gdi; } public void Dispose() { Gdi._g.ReleaseHdc(Gdi._hdc); Gdi._hdc = IntPtr.Zero; Gdi._g = null; } } private struct Rect { private int _left; private int _top; private int _right; private int _bottom; public Rect(Rectangle r) { _left = r.Left; _top = r.Top; _bottom = r.Bottom; _right = r.Right; } } 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 struct GDIPoint { private int x; private int y; private GDIPoint(int x, int y) { this.x = x; this.y = y; } } [Flags] public enum ETOOptions : uint { CLIPPED = 0x4, GLYPH_INDEX = 0x10, IGNORELANGUAGE = 0x1000, NUMERICSLATIN = 0x800, NUMERICSLOCAL = 0x400, OPAQUE = 0x2, PDY = 0x2000, RTLREADING = 0x800, } /// /// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15] /// [Flags] public enum TextFormatFlags : uint { Default = 0x00000000, Center = 0x00000001, Right = 0x00000002, VCenter = 0x00000004, Bottom = 0x00000008, WordBreak = 0x00000010, SingleLine = 0x00000020, ExpandTabs = 0x00000040, TabStop = 0x00000080, NoClip = 0x00000100, ExternalLeading = 0x00000200, CalcRect = 0x00000400, NoPrefix = 0x00000800, Internal = 0x00001000, EditControl = 0x00002000, PathEllipsis = 0x00004000, EndEllipsis = 0x00008000, ModifyString = 0x00010000, RtlReading = 0x00020000, WordEllipsis = 0x00040000, NoFullWidthCharBreak = 0x00080000, HidePrefix = 0x00100000, ProfixOnly = 0x00200000, } [Flags] public enum PenStyles { PS_SOLID = 0x00000000 // TODO } public 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, } public enum BkModes : int { TRANSPARENT = 1, OPAQUE = 2 } #endregion } }