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 <vladimir@thecybershadow.net> 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 static if (is(COLOR == bool)) 37 alias StorageType = OneBitStorageBE; 38 else 39 alias StorageType = PlainStorageUnit!COLOR; 40 41 struct Data 42 { 43 HDC hdc; 44 HBITMAP hbm; 45 46 @disable this(this); 47 48 ~this() nothrow @nogc 49 { 50 alias DeleteDC_t = extern(Windows) void function(HDC ) nothrow @nogc; 51 alias DeleteObject_t = extern(Windows) void function(HBITMAP) nothrow @nogc; 52 auto pDeleteDC = cast(DeleteDC_t )&DeleteDC; 53 auto pDeleteObject = cast(DeleteObject_t)&DeleteObject; 54 pDeleteDC(hdc); hdc = null; 55 pDeleteObject(hbm); hbm = null; 56 } 57 } 58 59 RefCounted!Data data; 60 61 xy_t w, h; 62 StorageType* pixelData; 63 sizediff_t pixelStride; 64 65 inout(StorageType)[] scanline(xy_t y) inout 66 { 67 assert(y>=0 && y<h); 68 auto row = cast(void*)pixelData + y * pixelStride; 69 auto storageUnitsPerRow = (w + StorageType.length - 1) / StorageType.length; 70 return (cast(inout(StorageType)*)row)[0 .. storageUnitsPerRow]; 71 } 72 73 mixin DirectView; 74 75 this(uint w, uint h) 76 { 77 this.w = w; 78 this.h = h; 79 80 auto hddc = GetDC(null); 81 scope(exit) ReleaseDC(null, hddc); 82 data.hdc = CreateCompatibleDC(hddc); 83 84 BITMAPINFO bmi; 85 bmi.bmiHeader.biSize = bmi.bmiHeader.sizeof; 86 bmi.bmiHeader.biWidth = w; 87 bmi.bmiHeader.biHeight = -h; 88 bmi.bmiHeader.biPlanes = 1; 89 bmi.bmiHeader.biBitCount = StorageType.sizeof * 8 / StorageType.length; 90 bmi.bmiHeader.biCompression = BI_RGB; 91 void* pvBits; 92 data.hbm = CreateDIBSection(data.hdc, &bmi, DIB_RGB_COLORS, &pvBits, null, 0); 93 enforce(data.hbm && pvBits, "CreateDIBSection"); 94 SelectObject(data.hdc, data.hbm); 95 pixelData = cast(StorageType*)pvBits; 96 pixelStride = bitmapPixelStride!StorageType(w); 97 } 98 99 auto opDispatch(string F, A...)(A args) 100 if (is(typeof(mixin(F~"(data.hdc, args)")))) 101 { 102 mixin("return "~F~"(data.hdc, args);"); 103 } 104 } 105 106 unittest 107 { 108 alias RGB = ae.utils.graphics.color.RGB; 109 110 // alias BGR COLOR; 111 alias BGRX COLOR; 112 auto b = GDICanvas!COLOR(100, 100); 113 b.fill(COLOR(255, 255, 255)); 114 115 const str = "Hello, world!"; 116 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); 117 b.SelectObject(f); 118 b.SetBkColor(0xFFFFFF); 119 b.SetTextColor(0x0000FF); 120 b.TextOutA(10, 10, str.ptr, cast(uint)str.length); 121 122 b.SetPixel(5, 5, 0xFF0000); 123 GdiFlush(); 124 b[6, 6] = COLOR(255, 0, 0); 125 126 import ae.utils.graphics.image; 127 auto i = b.copy.colorMap!(c => RGB(c.r,c.g,c.b))(); 128 assert(i[5, 5] == RGB(0, 0, 255)); 129 assert(i[6, 6] == RGB(0, 0, 255)); 130 assert(i[7, 7] == RGB(255, 255, 255)); 131 132 // i.savePNG("gditest.png"); 133 // i.savePNM("gditest.pnm"); 134 } 135 136 unittest 137 { 138 auto b = GDICanvas!bool(100, 100); 139 b.fill(true); 140 141 b.SetPixel(5, 5, 0x000000); 142 GdiFlush(); 143 b[6, 6] = false; 144 145 import ae.utils.graphics.image : copy; 146 auto i = b.copy.colorMap!(c => L8(c * 0xFF))(); 147 assert(i[5, 5] == L8(0)); 148 assert(i[6, 6] == L8(0)); 149 assert(i[7, 7] == L8(255)); 150 }