1 /** 2 * Utility Windows GDI code. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.graphics.gdi; 15 16 version(Windows): 17 18 import std.exception; 19 import std.typecons; 20 21 import ae.sys.windows.imports; 22 mixin(importWin32!(q{wingdi}, q{public})); 23 mixin(importWin32!q{winuser}); 24 mixin(importWin32!q{windef}); 25 26 import ae.utils.graphics.color; 27 import ae.utils.graphics.draw; 28 import ae.utils.graphics.image : bitmapPixelStride; 29 import ae.utils.graphics.view; 30 31 pragma(lib, "gdi32"); 32 33 /// A canvas with added GDI functionality. 34 struct GDICanvas(COLOR) 35 { 36 /// Storage type used by the GDI buffer. 37 static if (is(COLOR == bool)) 38 alias StorageType = OneBitStorageBE; 39 else 40 alias StorageType = PlainStorageUnit!COLOR; 41 42 /// Wraps and owns the Windows API objects. 43 struct Data 44 { 45 HDC hdc; /// Windows GDI DC handle. 46 HBITMAP hbm; /// Windows GDI bitmap handle. 47 48 @disable this(this); 49 50 ~this() nothrow @nogc 51 { 52 alias DeleteDC_t = extern(Windows) void function(HDC ) nothrow @nogc; 53 alias DeleteObject_t = extern(Windows) void function(HBITMAP) nothrow @nogc; 54 auto pDeleteDC = cast(DeleteDC_t )&DeleteDC; 55 auto pDeleteObject = cast(DeleteObject_t)&DeleteObject; 56 pDeleteDC(hdc); hdc = null; 57 pDeleteObject(hbm); hbm = null; 58 } 59 } 60 61 RefCounted!Data data; /// Reference to the Windows API objects. 62 63 /// Geometry. 64 xy_t w, h; 65 private StorageType* pixelData; 66 private sizediff_t pixelStride; 67 68 /// `DirectView` interface. 69 inout(StorageType)[] scanline(xy_t y) inout 70 { 71 assert(y>=0 && y<h); 72 auto row = cast(void*)pixelData + y * pixelStride; 73 auto storageUnitsPerRow = (w + StorageType.length - 1) / StorageType.length; 74 return (cast(inout(StorageType)*)row)[0 .. storageUnitsPerRow]; 75 } 76 77 mixin DirectView; 78 79 this(uint w, uint h) 80 { 81 this.w = w; 82 this.h = h; 83 84 auto hddc = GetDC(null); 85 scope(exit) ReleaseDC(null, hddc); 86 data.hdc = CreateCompatibleDC(hddc); 87 88 BITMAPINFO bmi; 89 bmi.bmiHeader.biSize = bmi.bmiHeader.sizeof; 90 bmi.bmiHeader.biWidth = w; 91 bmi.bmiHeader.biHeight = -h; 92 bmi.bmiHeader.biPlanes = 1; 93 bmi.bmiHeader.biBitCount = StorageType.sizeof * 8 / StorageType.length; 94 bmi.bmiHeader.biCompression = BI_RGB; 95 void* pvBits; 96 data.hbm = CreateDIBSection(data.hdc, &bmi, DIB_RGB_COLORS, &pvBits, null, 0); 97 enforce(data.hbm && pvBits, "CreateDIBSection"); 98 SelectObject(data.hdc, data.hbm); 99 pixelData = cast(StorageType*)pvBits; 100 pixelStride = bitmapPixelStride!StorageType(w); 101 } /// 102 103 /// Forwards Windows GDI calls. 104 auto opDispatch(string F, A...)(A args) 105 if (is(typeof(mixin(F~"(data.hdc, args)")))) 106 { 107 mixin("return "~F~"(data.hdc, args);"); 108 } 109 } 110 111 /// 112 unittest 113 { 114 alias RGB = ae.utils.graphics.color.RGB; 115 116 // alias BGR COLOR; 117 alias BGRX COLOR; 118 auto b = GDICanvas!COLOR(100, 100); 119 b.fill(COLOR(255, 255, 255)); 120 121 const str = "Hello, world!"; 122 auto f = CreateFont(-11, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Tahoma"); scope(exit) DeleteObject(f); 123 b.SelectObject(f); 124 b.SetBkColor(0xFFFFFF); 125 b.SetTextColor(0x0000FF); 126 b.TextOutA(10, 10, str.ptr, cast(uint)str.length); 127 128 b.SetPixel(5, 5, 0xFF0000); 129 GdiFlush(); 130 b[6, 6] = COLOR(255, 0, 0); 131 132 import ae.utils.graphics.image; 133 auto i = b.copy.colorMap!(c => RGB(c.r,c.g,c.b))(); 134 assert(i[5, 5] == RGB(0, 0, 255)); 135 assert(i[6, 6] == RGB(0, 0, 255)); 136 assert(i[7, 7] == RGB(255, 255, 255)); 137 138 // i.savePNG("gditest.png"); 139 // i.savePNM("gditest.pnm"); 140 } 141 142 unittest 143 { 144 auto b = GDICanvas!bool(100, 100); 145 b.fill(true); 146 147 b.SetPixel(5, 5, 0x000000); 148 GdiFlush(); 149 b[6, 6] = false; 150 151 import ae.utils.graphics.image : copy; 152 auto i = b.copy.colorMap!(c => L8(c * 0xFF))(); 153 assert(i[5, 5] == L8(0)); 154 assert(i[6, 6] == L8(0)); 155 assert(i[7, 7] == L8(255)); 156 }