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 }