1 module dfl.clippingform;
2 
3 import core.sys.windows.windows;
4 
5 import dfl.clipboard;
6 import dfl.drawing;
7 import dfl.form;
8 import dfl.event;
9 import dfl.control;
10 import dfl.base;
11 
12 import core.memory;
13 
14 private extern (Windows) {
15    struct RGNDATAHEADER {
16       DWORD dwSize;
17       DWORD iType;
18       DWORD nCount;
19       DWORD nRgnSize;
20       RECT rcBound;
21    }
22 
23    struct RGNDATA {
24       RGNDATAHEADER rdh;
25       ubyte[1] Buffer;
26    }
27 
28    struct XFORM {
29       FLOAT eM11;
30       FLOAT eM12;
31       FLOAT eM21;
32       FLOAT eM22;
33       FLOAT eDx;
34       FLOAT eDy;
35    }
36 
37    enum {
38       RDH_RECTANGLES = 1
39    }
40    enum {
41       BI_RGB = 0
42    }
43    enum {
44       DIB_RGB_COLORS = 0
45    }
46 
47    HRGN ExtCreateRegion(void*, DWORD, RGNDATA*);
48    int GetDIBits(HDC, HBITMAP, UINT, UINT, PVOID, LPBITMAPINFO, UINT);
49 }
50 
51 struct RegionRects {
52 private:
53    RGNDATA* _rgn = null;
54    size_t _capacity = 0;
55    size_t _width = 0;
56    size_t _height = 0;
57 public:
58 
59    const @property size_t width() {
60       return _width;
61    }
62 
63    const @property size_t height() {
64       return _height;
65    }
66 
67    void clear() {
68       if (_rgn) {
69          GC.free(_rgn);
70       }
71       _rgn = null;
72       _capacity = 0;
73       _width = 0;
74       _height = 0;
75    }
76 
77    void add(RECT rc) {
78       if (_capacity == 0) {
79          _capacity = 1024;
80          _rgn = cast(RGNDATA*) GC.malloc(RGNDATAHEADER.sizeof + RECT.sizeof * _capacity);
81          _rgn.rdh.nCount = 0;
82       } else if (_rgn.rdh.nCount == _capacity) {
83          _capacity *= 2;
84          _rgn = cast(RGNDATA*) GC.realloc(cast(void*) _rgn,
85             RGNDATAHEADER.sizeof + RECT.sizeof * _capacity);
86       }
87       (cast(RECT*) _rgn.Buffer.ptr)[_rgn.rdh.nCount++] = rc;
88    }
89 
90    void add(int l, int t, int r, int b) {
91       add(RECT(l, t, r, b));
92    }
93 
94    void opCatAssign(RECT rc) {
95       add(rc);
96    }
97 
98    @property Region region() {
99       if (_rgn is null) {
100          return null;
101       }
102       with (_rgn.rdh) {
103          dwSize = RGNDATAHEADER.sizeof;
104          iType = RDH_RECTANGLES;
105          nRgnSize = RGNDATAHEADER.sizeof + RECT.sizeof * nCount;
106          rcBound = RECT(0, 0, _width, _height);
107       }
108       if (auto hRgn = ExtCreateRegion(null, _rgn.rdh.nRgnSize, _rgn)) {
109          return new Region(hRgn);
110       }
111       throw new Exception("Failed to make a region data.");
112    }
113 
114    private Region createClippingRegionFromHDC(HBITMAP hBitmap) {
115       HDC hDC = CreateCompatibleDC(null);
116       auto h = _height;
117       auto w = _width;
118       if (!hDC) {
119          throw new Exception("Failed to get device context data.");
120       }
121       BITMAPINFOHEADER bi;
122       with (bi) {
123          biSize = BITMAPINFOHEADER.sizeof;
124          biWidth = w;
125          biHeight = h;
126          biPlanes = 1;
127          biBitCount = 32;
128          biCompression = BI_RGB;
129       }
130       auto pxs = new COLORREF[w];
131       COLORREF tr;
132       for (int y = 1; y < h; ++y) {
133          GetDIBits(hDC, hBitmap, h - y, 1, pxs.ptr, cast(BITMAPINFO*)&bi, DIB_RGB_COLORS);
134          if (y == 1) {
135             tr = pxs[0];
136          }
137          for (int x = 0; x < w; x++) {
138             if (pxs[x] == tr) {
139                continue;
140             }
141             int sx = x;
142             while (x < w) {
143                if (pxs[x++] == tr) {
144                   break;
145                }
146             }
147             add(sx, y - 1, x - 1, y);
148          }
149       }
150       return region;
151    }
152 
153    Region create(MemoryGraphics g) {
154       clear();
155       _width = g.width;
156       _height = g.height;
157       return createClippingRegionFromHDC(cast(HBITMAP) g.hbitmap);
158    }
159 
160    Region create(Image img) {
161       clear();
162       _width = img.width;
163       _height = img.height;
164       if (auto bmp = cast(Bitmap) img) {
165          return createClippingRegionFromHDC(cast(HBITMAP) bmp.handle);
166       }
167       auto g = new MemoryGraphics(img.width, img.height);
168       img.draw(g, Point(0, 0));
169       return create(g);
170    }
171 }
172 
173 class ClippingForm : Form {
174 private:
175    Image m_Image;
176    RegionRects m_RegionRects;
177 protected:
178    override void createParams(ref CreateParams cp) {
179       super.createParams(cp);
180       cp.style = WS_EX_TOPMOST | WS_EX_TOOLWINDOW;
181    }
182 
183 public:
184 
185    @property Image clipping() {
186       return m_Image;
187    }
188 
189    @property void clipping(Image img) {
190       m_Image = img;
191    }
192 
193    override void onHandleCreated(EventArgs ea) {
194       if (m_Image) {
195          region = m_RegionRects.create(m_Image);
196       }
197       super.onHandleCreated(ea);
198    }
199 
200    override void onPaint(PaintEventArgs pea) {
201       if (m_Image) {
202          m_Image.draw(pea.graphics, Point(0, 0));
203       } else {
204          super.onPaint(pea);
205       }
206    }
207 }