1 module operators;
2 
3 /+
4  +           Copyright Andrej Mitrovic 2011.
5  +  Distributed under the Boost Software License, Version 1.0.
6  +     (See accompanying file LICENSE_1_0.txt or copy at
7  +           http://www.boost.org/LICENSE_1_0.txt)
8  +/
9 
10 import std.conv;
11 import std.utf;
12 
13 /+
14  + Demonstrates the usage of Cairo compositing operators and alpha-blitting.
15  +
16  + Note: Other samples in this directory do not use alpha-blending, and should
17  + probably be rewritten. Sorry for that! :)
18  +
19  + Notes:
20  + You have to use an RGB32 surface type, otherwise you won't be able
21  + to use numerous alpha-blending operators (you will get a runtime exception).
22  +
23  + I'm using CairoD's Win32Surface ctor that can create a surface type
24  + based on the format enum supplied. Alternatively I could have manually
25  + created a DIBSection and constructed a Win32Surface with that.
26  +
27  + I'm using AlphaBlend (symbol name is actually 'GdiAlphaBlend' in Gdi32.lib),
28  + if you get undefined symbol errors it could mean the Gdi32.lib import lib
29  + distributed with DMD/GDC is outdated. You can create a new OMF-compatible
30  + one by grabbing the Windows SDK, downloading coffimplib
31  + (ftp://ftp.digitalmars.com/coffimplib.zip), and browsing to
32  + where Gdi32.lib is located, e.g.:
33  +
34  +      C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib
35  +
36  + and calling:
37  +      coffimplib Gdi32_coff.lib gdi32.lib
38  +
39  + Then copy 'Gdi32_coff.lib' to DMD\dmd2\windows\lib\, delete the old 'gdi32.lib',
40  + and rename 'Gdi32_coff.lib' to 'gdi32.lib'.
41  +
42  + I use 2 paint buffers, one is the foreground that only paints to certain
43  + regions and has an alpha, the other acts as a background with its entire
44  + surface painted white with alpha 1.0 (max).
45  +
46  + I use AlphaBlit to blit the foreground to the background. AlphaBlit is set
47  + to blit by using per-pixel alpha values (this is configurable to other settings).
48  +
49  + The reason I'm using 2 paint buffers and not just 1 foreground buffer and
50  + a white pre-painted window device context is because the latter usually introduces
51  + graphical glitches. For example, painting the window white directly via a
52  + device context (and e.g. the GDI FillRect function) and then alpha-blitting the
53  + foreground results in 2 refresh events. This would introduce flicker effects,
54  + so it's better to keep a separate background buffer to blend with when drawing,
55  + and then blit to the screen as necessary.
56  +/
57 
58 import core.runtime;
59 import std.exception;
60 import std.process;
61 import std.stdio;
62 
63 pragma(lib, "gdi32.lib");
64 import windows.windef;
65 import windows.winuser;
66 import windows.wingdi;
67 
68 string appName     = "CairoWindow";
69 string description = "A simple win32 window with Cairo drawing";
70 HINSTANCE hinst;
71 
72 import cairo.cairo;
73 import cairo.win32;
74 
75 alias cairo.cairo.RGB RGB;  // conflicts with win32.wingdi.RGB
76 
77 struct AlphaBlendType
78 {
79     static normal = BLENDFUNCTION(AC_SRC_OVER, 0, 255, AC_SRC_ALPHA);
80 }
81 
82 extern(Windows) BOOL GdiAlphaBlend(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
83 void AlphaBlit(HDC dstHdc, HDC srcHdc, int width, int height, BLENDFUNCTION blendType = AlphaBlendType.normal)
84 {
85     auto result = GdiAlphaBlend(dstHdc, 0, 0, width, height,
86                                 srcHdc, 0, 0, width, height, blendType);
87     // enforce(result != 0);
88 }
89 
90 extern (Windows)
91 int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
92 {
93     int result;
94 
95     try
96     {
97         Runtime.initialize();
98         result = myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
99         Runtime.terminate();
100     }
101     catch (Throwable o)
102     {
103         MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
104         result = 0;
105     }
106 
107     return result;
108 }
109 
110 int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
111 {
112     hinst = hInstance;
113     HACCEL hAccel;
114     HWND hwnd;
115     MSG  msg;
116     WNDCLASS wndclass;
117 
118     // commented out so we do not redraw the entire screen on resize
119     // wndclass.style         = CS_HREDRAW | CS_VREDRAW;
120     wndclass.style         = WS_CLIPCHILDREN;
121     wndclass.lpfnWndProc   = &WndProc;
122     wndclass.cbClsExtra    = 0;
123     wndclass.cbWndExtra    = 0;
124     wndclass.hInstance     = hInstance;
125     wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
126     wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
127     wndclass.hbrBackground = null;
128     wndclass.lpszMenuName  = appName.toUTF16z;
129     wndclass.lpszClassName = appName.toUTF16z;
130 
131     if (!RegisterClass(&wndclass))
132     {
133         MessageBox(null, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
134         return 0;
135     }
136 
137     hwnd = CreateWindow(appName.toUTF16z,              // window class name
138                         description.toUTF16z,          // window caption
139                         WS_OVERLAPPEDWINDOW,           // window style
140                         (1680 - 900) / 2,                 // initial x position
141                         (1050 - 700) / 2,                 // initial y position
142                         1100,                 // initial x size
143                         800,                 // initial y size
144                         null,                          // parent window handle
145                         null,                          // window menu handle
146                         hInstance,                     // program instance handle
147                         null);                         // creation parameters
148 
149     ShowWindow(hwnd, iCmdShow);
150     UpdateWindow(hwnd);
151 
152     while (GetMessage(&msg, null, 0, 0))
153     {
154         TranslateMessage(&msg);
155         DispatchMessage(&msg);
156     }
157 
158     return msg.wParam;
159 }
160 
161 extern (Windows)
162 LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
163 {
164     switch (message)
165     {
166         case WM_CREATE:
167         {
168             window = new Window(hwnd);
169             return 0;
170         }
171 
172         default:
173     }
174 
175     if (window)
176         return window.process(hwnd, message, wParam, lParam);
177     else
178         return DefWindowProc(hwnd, message, wParam, lParam);
179 }
180 
181 Window window;
182 
183 struct PaintBuffer
184 {
185     int width, height;
186 
187     this(HDC localHdc, int cxClient, int cyClient)
188     {
189         surf = new Win32Surface(Format.CAIRO_FORMAT_ARGB32, cxClient, cyClient);
190         ctx = Context(surf);
191         initialized = true;
192     }
193 
194     ~this()
195     {
196         if (initialized)  // struct dtors are still buggy sometimes
197         {
198             ctx.dispose();
199             surf.finish();
200             surf.dispose();
201             initialized = false;
202         }
203     }
204 
205     bool initialized;
206     Context ctx;
207     Win32Surface surf;
208 }
209 
210 class Window
211 {
212     int width, height;
213     HWND hwnd;
214     PAINTSTRUCT ps;
215     PaintBuffer foreBuffer;  // drawing buffer
216     PaintBuffer backBuffer;  // white background, alpha-blended over with foreBuffer
217     bool needsRedraw;  // if false we only blit, otherwise we re-draw via cairo
218 
219     this(HWND hwnd)
220     {
221         this.hwnd = hwnd;
222 
223         auto hDesk = GetDesktopWindow();
224         RECT rc;
225         GetClientRect(hDesk, &rc);
226 
227         auto localHdc = GetDC(hwnd);
228         foreBuffer = PaintBuffer(localHdc, rc.right, rc.bottom);
229         backBuffer = PaintBuffer(localHdc, rc.right, rc.bottom);
230         needsRedraw = true;
231     }
232 
233     LRESULT process(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
234     {
235         switch (message)
236         {
237             case WM_DESTROY:
238                 foreBuffer.clear();
239                 PostQuitMessage(0);
240                 return 0;
241 
242             case WM_PAINT:
243                 OnPaint(hwnd, message, wParam, lParam);
244                 return 0;
245 
246             case WM_ERASEBKGND:
247                 return 1;
248 
249             case WM_SIZE:
250             {
251                 width  = LOWORD(lParam);
252                 height = HIWORD(lParam);
253                 return 0;
254             }
255 
256             default:
257         }
258 
259         return DefWindowProc(hwnd, message, wParam, lParam);
260     }
261 
262     void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
263     {
264         HDC winHDC = BeginPaint(hwnd, &ps);
265 
266         // find visible window area
267         RECT boundRect = ps.rcPaint;
268 
269         if (needsRedraw)  // need to redraw with cairo
270         {
271             // paint backround buffer white
272             backBuffer.ctx.setSourceRGBA(1, 1, 1, 1);
273             backBuffer.ctx.paint();
274 
275             // draw foreground
276             draw(foreBuffer.ctx);
277 
278             // blit bg to fg only in exposed area
279             with (boundRect)
280             {
281                 // blit fg HDC to bg HDC
282                 AlphaBlit(backBuffer.surf.getDC(), foreBuffer.surf.getDC(), right - left, bottom - top);
283             }
284 
285             needsRedraw = false;
286         }
287 
288         with (boundRect)  // blit only exposed area of window
289         {
290             // blit backBuffer to window
291             BitBlt(winHDC, left, top, right, bottom, backBuffer.surf.getDC(), left, top, SRCCOPY);
292         }
293 
294         EndPaint(hwnd, &ps);
295     }
296 
297     void draw(Context ctx)
298     {
299         static immutable ops =
300         [
301             Operator.CAIRO_OPERATOR_CLEAR,
302             Operator.CAIRO_OPERATOR_SOURCE,
303             Operator.CAIRO_OPERATOR_OVER,
304             Operator.CAIRO_OPERATOR_IN,
305             Operator.CAIRO_OPERATOR_OUT,
306             Operator.CAIRO_OPERATOR_ATOP,
307             Operator.CAIRO_OPERATOR_DEST,
308             Operator.CAIRO_OPERATOR_DEST_OVER,
309             Operator.CAIRO_OPERATOR_DEST_IN,
310             Operator.CAIRO_OPERATOR_DEST_OUT,
311             Operator.CAIRO_OPERATOR_DEST_ATOP,
312             Operator.CAIRO_OPERATOR_XOR,
313             Operator.CAIRO_OPERATOR_ADD,
314             Operator.CAIRO_OPERATOR_SATURATE,
315 
316             // note: not supported on RGB24, only RGB32 with alpha.
317             Operator.CAIRO_OPERATOR_MULTIPLY,
318             Operator.CAIRO_OPERATOR_SCREEN,
319             Operator.CAIRO_OPERATOR_OVERLAY,
320             Operator.CAIRO_OPERATOR_DARKEN,
321             Operator.CAIRO_OPERATOR_LIGHTEN,
322             Operator.CAIRO_OPERATOR_COLOR_DODGE,
323             Operator.CAIRO_OPERATOR_COLOR_BURN,
324             Operator.CAIRO_OPERATOR_HARD_LIGHT,
325             Operator.CAIRO_OPERATOR_SOFT_LIGHT,
326             Operator.CAIRO_OPERATOR_DIFFERENCE,
327             Operator.CAIRO_OPERATOR_EXCLUSION,
328             Operator.CAIRO_OPERATOR_HSL_HUE,
329             Operator.CAIRO_OPERATOR_HSL_SATURATION,
330             Operator.CAIRO_OPERATOR_HSL_COLOR,
331             Operator.CAIRO_OPERATOR_HSL_LUMINOSITY
332         ];
333 
334         size_t colIdx;
335         size_t rowIdx;
336         foreach (op; ops)
337         {
338             ctx.save();
339 
340             ctx.rectangle(colIdx * 180, (rowIdx * 140), 160, 120);
341             ctx.clip();
342 
343             ctx.rectangle(colIdx * 180, (rowIdx * 140), 120, 90);
344             ctx.setSourceRGBA(0.7, 0, 0, 0.8);
345             ctx.fill();
346 
347             ctx.setOperator(op);
348 
349             ctx.rectangle((colIdx * 180) + 40, (rowIdx * 140) + 30, 120, 90);
350             ctx.setSourceRGBA(0, 0, 0.9, 0.4);
351             ctx.fill();
352 
353             rowIdx++;
354 
355             if (rowIdx == 5)  // 5 examples in a row
356             {
357                 rowIdx = 0;
358                 colIdx++;
359             }
360 
361             ctx.restore();
362 
363             ctx.save();
364             ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);
365             ctx.setSourceRGBA(0, 0, 0, 1);
366             ctx.selectFontFace("Verdana", FontSlant.CAIRO_FONT_SLANT_NORMAL, FontWeight.CAIRO_FONT_WEIGHT_NORMAL);
367             ctx.setFontSize(10);
368             ctx.moveTo(10 + (colIdx * 180), 20 + ((rowIdx * 140)));
369             ctx.showText(to!string(op));
370             ctx.restore();
371         }
372     }
373 }