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 }