1 // Written in the D programming language.
2 
3 /**
4 This module contains drawing buffer implementation.
5 
6 
7 Synopsis:
8 
9 ----
10 import dlangui.graphics.drawbuf;
11 
12 ----
13 
14 Copyright: Vadim Lopatin, 2014
15 License:   Boost License 1.0
16 Authors:   Vadim Lopatin, coolreader.org@gmail.com
17 */
18 module dlangui.graphics.drawbuf;
19 
20 public import dlangui.core.config;
21 public import dlangui.core.types;
22 public import dlangui.core.math3d;
23 import dlangui.core.logger;
24 import dlangui.graphics.colors;
25 
26 /**
27  * 9-patch image scaling information (see Android documentation).
28  *
29  *
30  */
31 struct NinePatch {
32     /// frame (non-scalable) part size for left, top, right, bottom edges.
33     Rect frame;
34     /// padding (distance to content area) for left, top, right, bottom edges.
35     Rect padding;
36 }
37 
38 enum PatternType : int {
39     solid,
40     dotted,
41 }
42 
43 static if (ENABLE_OPENGL) {
44     /// non thread safe
45     private __gshared uint drawBufIdGenerator = 0;
46 }
47 
48 /// Custom draw delegate for OpenGL direct drawing
49 alias OpenGLDrawableDelegate = void delegate(Rect windowRect, Rect rc);
50 
51 /// drawing buffer - image container which allows to perform some drawing operations
52 class DrawBuf : RefCountedObject {
53     protected Rect _clipRect;
54     protected NinePatch * _ninePatch;
55     protected uint _alpha;
56 
57     /// get current alpha setting (to be applied to all drawing operations)
58     @property uint alpha() { return _alpha; }
59     /// set new alpha setting (to be applied to all drawing operations)
60     @property void alpha(uint alpha) {
61         _alpha = alpha;
62         if (_alpha > 0xFF)
63             _alpha = 0xFF;
64     }
65 
66     /// apply additional transparency to current drawbuf alpha value
67     void addAlpha(uint alpha) {
68         _alpha = blendAlpha(_alpha, alpha);
69     }
70 
71     /// applies current drawbuf alpha to argb color value
72     uint applyAlpha(uint argb) {
73         if (!_alpha)
74             return argb; // no drawbuf alpha
75         uint a1 = (argb >> 24) & 0xFF;
76         if (a1 == 0xFF)
77             return argb; // fully transparent
78         uint a2 = _alpha & 0xFF;
79         uint a = blendAlpha(a1, a2);
80         return (argb & 0xFFFFFF) | (a << 24);
81     }
82 
83     static if (ENABLE_OPENGL) {
84         protected uint _id;
85         /// unique ID of drawbug instance, for using with hardware accelerated rendering for caching
86         @property uint id() { return _id; }
87     }
88 
89     this() {
90         static if (ENABLE_OPENGL) {
91             _id = drawBufIdGenerator++;
92         }
93         debug _instanceCount++;
94     }
95 
96     debug private static __gshared int _instanceCount;
97     debug @property static int instanceCount() { return _instanceCount; }
98     ~this() {
99         /*import core.memory : gc_inFinalizer;*/
100         if (APP_IS_SHUTTING_DOWN/* || gc_inFinalizer*/)
101             onResourceDestroyWhileShutdown("DrawBuf", this.classinfo.name);
102 
103         debug _instanceCount--;
104         clear();
105     }
106 
107     protected void function(uint) _onDestroyCallback;
108     @property void onDestroyCallback(void function(uint) callback) { _onDestroyCallback = callback; }
109     @property void function(uint) onDestroyCallback() { return _onDestroyCallback; }
110 
111     /// Call to remove this image from OpenGL cache when image is updated.
112     void invalidate() {
113         static if (ENABLE_OPENGL) {
114             if (_onDestroyCallback) {
115                 // remove from cache
116                 _onDestroyCallback(_id);
117                 // assign new ID
118                 _id = drawBufIdGenerator++;
119             }
120         }
121     }
122 
123     // ===================================================
124     // 9-patch functions (image scaling using 9-patch markup - unscaled frame and scaled middle parts).
125     // See Android documentation for details.
126 
127     /// get nine patch information pointer, null if this is not a nine patch image buffer
128     @property const (NinePatch) * ninePatch() const { return _ninePatch; }
129     /// set nine patch information pointer, null if this is not a nine patch image buffer
130     @property void ninePatch(NinePatch * ninePatch) { _ninePatch = ninePatch; }
131     /// check whether there is nine-patch information available for drawing buffer
132     @property bool hasNinePatch() { return _ninePatch !is null; }
133     /// override to detect nine patch using image 1-pixel border; returns true if 9-patch markup is found in image.
134     bool detectNinePatch() { return false; }
135 
136     /// returns current width
137     @property int width() { return 0; }
138     /// returns current height
139     @property int height() { return 0; }
140 
141     // ===================================================
142     // clipping rectangle functions
143 
144     /// init clip rectangle to full buffer size
145     void resetClipping() {
146         _clipRect = Rect(0, 0, width, height);
147     }
148     @property bool hasClipping() {
149         return _clipRect.left != 0 || _clipRect.top != 0 || _clipRect.right != width || _clipRect.bottom != height;
150     }
151     /// returns clipping rectangle, when clipRect.isEmpty == true -- means no clipping.
152     @property ref Rect clipRect() { return _clipRect; }
153     /// returns clipping rectangle, or (0,0,dx,dy) when no clipping.
154     //@property Rect clipOrFullRect() { return _clipRect.empty ? Rect(0,0,width,height) : _clipRect; }
155     /// sets new clipping rectangle, when clipRect.isEmpty == true -- means no clipping.
156     @property void clipRect(const ref Rect rect) {
157         _clipRect = rect;
158         _clipRect.intersect(Rect(0, 0, width, height));
159     }
160     /// sets new clipping rectangle, intersect with previous one.
161     @property void intersectClipRect(const ref Rect rect) {
162         //if (_clipRect.empty)
163         //    _clipRect = rect;
164         //else
165         _clipRect.intersect(rect);
166         _clipRect.intersect(Rect(0, 0, width, height));
167     }
168     /// returns true if rectangle is completely clipped out and cannot be drawn.
169     @property bool isClippedOut(const ref Rect rect) {
170         //Rect rc = clipOrFullRect();
171         return !_clipRect.intersects(rect);
172     }
173     /// apply clipRect and buffer bounds clipping to rectangle
174     bool applyClipping(ref Rect rc) {
175         //if (!_clipRect.empty())
176         rc.intersect(_clipRect);
177         if (rc.left < 0)
178             rc.left = 0;
179         if (rc.top < 0)
180             rc.top = 0;
181         if (rc.right > width)
182             rc.right = width;
183         if (rc.bottom > height)
184             rc.bottom = height;
185         return !rc.empty();
186     }
187     /// apply clipRect and buffer bounds clipping to rectangle; if clippinup applied to first rectangle, reduce second rectangle bounds proportionally.
188     bool applyClipping(ref Rect rc, ref Rect rc2) {
189         if (rc.empty || rc2.empty)
190             return false;
191         //if (!_clipRect.empty())
192         if (!rc.intersects(_clipRect))
193             return false;
194         if (rc.width == rc2.width && rc.height == rc2.height) {
195             // unscaled
196             //if (!_clipRect.empty) {
197                 if (rc.left < _clipRect.left) {
198                     rc2.left += _clipRect.left - rc.left;
199                     rc.left = _clipRect.left;
200                 }
201                 if (rc.top < _clipRect.top) {
202                     rc2.top += _clipRect.top - rc.top;
203                     rc.top = _clipRect.top;
204                 }
205                 if (rc.right > _clipRect.right) {
206                     rc2.right -= rc.right - _clipRect.right;
207                     rc.right = _clipRect.right;
208                 }
209                 if (rc.bottom > _clipRect.bottom) {
210                     rc2.bottom -= rc.bottom - _clipRect.bottom;
211                     rc.bottom = _clipRect.bottom;
212                 }
213             //}
214             if (rc.left < 0) {
215                 rc2.left += -rc.left;
216                 rc.left = 0;
217             }
218             if (rc.top < 0) {
219                 rc2.top += -rc.top;
220                 rc.top = 0;
221             }
222             if (rc.right > width) {
223                 rc2.right -= rc.right - width;
224                 rc.right = width;
225             }
226             if (rc.bottom > height) {
227                 rc2.bottom -= rc.bottom - height;
228                 rc.bottom = height;
229             }
230         } else {
231             // scaled
232             int dstdx = rc.width;
233             int dstdy = rc.height;
234             int srcdx = rc2.width;
235             int srcdy = rc2.height;
236             //if (!_clipRect.empty) {
237                 if (rc.left < _clipRect.left) {
238                     rc2.left += (_clipRect.left - rc.left) * srcdx / dstdx;
239                     rc.left = _clipRect.left;
240                 }
241                 if (rc.top < _clipRect.top) {
242                     rc2.top += (_clipRect.top - rc.top) * srcdy / dstdy;
243                     rc.top = _clipRect.top;
244                 }
245                 if (rc.right > _clipRect.right) {
246                     rc2.right -= (rc.right - _clipRect.right) * srcdx / dstdx;
247                     rc.right = _clipRect.right;
248                 }
249                 if (rc.bottom > _clipRect.bottom) {
250                     rc2.bottom -= (rc.bottom - _clipRect.bottom) * srcdy / dstdy;
251                     rc.bottom = _clipRect.bottom;
252                 }
253             //}
254             if (rc.left < 0) {
255                 rc2.left -= (rc.left) * srcdx / dstdx;
256                 rc.left = 0;
257             }
258             if (rc.top < 0) {
259                 rc2.top -= (rc.top) * srcdy / dstdy;
260                 rc.top = 0;
261             }
262             if (rc.right > width) {
263                 rc2.right -= (rc.right - width) * srcdx / dstdx;
264                 rc.right = width;
265             }
266             if (rc.bottom > height) {
267                 rc2.bottom -= (rc.bottom - height) * srcdx / dstdx;
268                 rc.bottom = height;
269             }
270         }
271         return !rc.empty() && !rc2.empty();
272     }
273     /// reserved for hardware-accelerated drawing - begins drawing batch
274     void beforeDrawing() { _alpha = 0; }
275     /// reserved for hardware-accelerated drawing - ends drawing batch
276     void afterDrawing() { }
277     /// returns buffer bits per pixel
278     @property int bpp() { return 0; }
279     // returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory
280     //uint * scanLine(int y) { return null; }
281     /// resize buffer
282     abstract void resize(int width, int height);
283 
284     //========================================================
285     // Drawing methods.
286 
287     /// fill the whole buffer with solid color (no clipping applied)
288     abstract void fill(uint color);
289     /// fill rectangle with solid color (clipping is applied)
290     abstract void fillRect(Rect rc, uint color);
291     /// fill rectangle with a gradient (clipping is applied)
292     abstract void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4);
293     /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted
294     void fillRectPattern(Rect rc, uint color, int pattern) {
295         // default implementation: does not support patterns
296         fillRect(rc, color);
297     }
298     /// draw pixel at (x, y) with specified color
299     abstract void drawPixel(int x, int y, uint color);
300     /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
301     abstract void drawGlyph(int x, int y, Glyph * glyph, uint color);
302     /// draw source buffer rectangle contents to destination buffer
303     abstract void drawFragment(int x, int y, DrawBuf src, Rect srcrect);
304     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
305     abstract void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect);
306     /// draw unscaled image at specified coordinates
307     void drawImage(int x, int y, DrawBuf src) {
308         drawFragment(x, y, src, Rect(0, 0, src.width, src.height));
309     }
310     /// draws rectangle frame of specified color and widths (per side), and optinally fills inner area
311     void drawFrame(Rect rc, uint frameColor, Rect frameSideWidths, uint innerAreaColor = 0xFFFFFFFF) {
312         // draw frame
313         if (!isFullyTransparentColor(frameColor)) {
314             Rect r;
315             // left side
316             r = rc;
317             r.right = r.left + frameSideWidths.left;
318             if (!r.empty)
319                 fillRect(r, frameColor);
320             // right side
321             r = rc;
322             r.left = r.right - frameSideWidths.right;
323             if (!r.empty)
324                 fillRect(r, frameColor);
325             // top side
326             r = rc;
327             r.left += frameSideWidths.left;
328             r.right -= frameSideWidths.right;
329             Rect rc2 = r;
330             rc2.bottom = r.top + frameSideWidths.top;
331             if (!rc2.empty)
332                 fillRect(rc2, frameColor);
333             // bottom side
334             rc2 = r;
335             rc2.top = r.bottom - frameSideWidths.bottom;
336             if (!rc2.empty)
337                 fillRect(rc2, frameColor);
338         }
339         // draw internal area
340         if (!isFullyTransparentColor(innerAreaColor)) {
341             rc.left += frameSideWidths.left;
342             rc.top += frameSideWidths.top;
343             rc.right -= frameSideWidths.right;
344             rc.bottom -= frameSideWidths.bottom;
345             if (!rc.empty)
346                 fillRect(rc, innerAreaColor);
347         }
348     }
349 
350     /// draw focus rectangle; vertical gradient supported - colors[0] is top color, colors[1] is bottom color
351     void drawFocusRect(Rect rc, const uint[] colors) {
352         // override for faster performance when using OpenGL
353         if (colors.length < 1)
354             return;
355         uint color1 = colors[0];
356         uint color2 = colors.length > 1 ? colors[1] : color1;
357         if (isFullyTransparentColor(color1) && isFullyTransparentColor(color2))
358             return;
359         // draw horizontal lines
360         foreach(int x; rc.left .. rc.right) {
361             if ((x ^ rc.top) & 1)
362                 fillRect(Rect(x, rc.top, x + 1, rc.top + 1), color1);
363             if ((x ^ (rc.bottom - 1)) & 1)
364                 fillRect(Rect(x, rc.bottom - 1, x + 1, rc.bottom), color2);
365         }
366         // draw vertical lines
367         foreach(int y; rc.top + 1 .. rc.bottom - 1) {
368             uint color = color1 == color2 ? color1 : blendARGB(color2, color1, 255 / (rc.bottom - rc.top));
369             if ((y ^ rc.left) & 1)
370                 fillRect(Rect(rc.left, y, rc.left + 1, y + 1), color);
371             if ((y ^ (rc.right - 1)) & 1)
372                 fillRect(Rect(rc.right - 1, y, rc.right, y + 1), color);
373         }
374     }
375 
376     /// draw filled triangle in float coordinates; clipping is already applied
377     protected void fillTriangleFClipped(PointF p1, PointF p2, PointF p3, uint colour) {
378         // override and implement it
379     }
380 
381     /// find intersection of line p1..p2 with clip rectangle
382     protected bool intersectClipF(ref PointF p1, ref PointF p2, ref bool p1moved, ref bool p2moved) {
383         if (p1.x < _clipRect.left && p2.x < _clipRect.left)
384             return true;
385         if (p1.x >= _clipRect.right && p2.x >= _clipRect.right)
386             return true;
387         if (p1.y < _clipRect.top && p2.y < _clipRect.top)
388             return true;
389         if (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom)
390             return true;
391         // horizontal clip
392         if (p1.x < _clipRect.left && p2.x >= _clipRect.left) {
393             // move p1 to clip left
394             p1 += (p2 - p1) * ((_clipRect.left - p1.x) / (p2.x - p1.x));
395             p1moved = true;
396         }
397         if (p2.x < _clipRect.left && p1.x >= _clipRect.left) {
398             // move p2 to clip left
399             p2 += (p1 - p2) * ((_clipRect.left - p2.x) / (p1.x - p2.x));
400             p2moved = true;
401         }
402         if (p1.x > _clipRect.right && p2.x < _clipRect.right) {
403             // move p1 to clip right
404             p1 += (p2 - p1) * ((_clipRect.right - p1.x) / (p2.x - p1.x));
405             p1moved = true;
406         }
407         if (p2.x > _clipRect.right && p1.x < _clipRect.right) {
408             // move p1 to clip right
409             p2 += (p1 - p2) * ((_clipRect.right - p2.x) / (p1.x - p2.x));
410             p2moved = true;
411         }
412         // vertical clip
413         if (p1.y < _clipRect.top && p2.y >= _clipRect.top) {
414             // move p1 to clip left
415             p1 += (p2 - p1) * ((_clipRect.top - p1.y) / (p2.y - p1.y));
416             p1moved = true;
417         }
418         if (p2.y < _clipRect.top && p1.y >= _clipRect.top) {
419             // move p2 to clip left
420             p2 += (p1 - p2) * ((_clipRect.top - p2.y) / (p1.y - p2.y));
421             p2moved = true;
422         }
423         if (p1.y > _clipRect.bottom && p2.y < _clipRect.bottom) {
424             // move p1 to clip right             <0              <0
425             p1 += (p2 - p1) * ((_clipRect.bottom - p1.y) / (p2.y - p1.y));
426             p1moved = true;
427         }
428         if (p2.y > _clipRect.bottom && p1.y < _clipRect.bottom) {
429             // move p1 to clip right
430             p2 += (p1 - p2) * ((_clipRect.bottom - p2.y) / (p1.y - p2.y));
431             p2moved = true;
432         }
433         return false;
434     }
435 
436     /// draw filled triangle in float coordinates
437     void fillTriangleF(PointF p1, PointF p2, PointF p3, uint colour) {
438         if (_clipRect.empty) // clip rectangle is empty - all drawables are clipped out
439             return;
440         // apply clipping
441         bool p1insideClip = (p1.x >= _clipRect.left && p1.x < _clipRect.right && p1.y >= _clipRect.top && p1.y < _clipRect.bottom);
442         bool p2insideClip = (p2.x >= _clipRect.left && p2.x < _clipRect.right && p2.y >= _clipRect.top && p2.y < _clipRect.bottom);
443         bool p3insideClip = (p3.x >= _clipRect.left && p3.x < _clipRect.right && p3.y >= _clipRect.top && p3.y < _clipRect.bottom);
444         if (p1insideClip && p2insideClip && p3insideClip) {
445             // all points inside clipping area - no clipping required
446             fillTriangleFClipped(p1, p2, p3, colour);
447             return;
448         }
449         // do triangle clipping
450         // check if all points outside the same bound
451         if ((p1.x < _clipRect.left && p2.x < _clipRect.left && p3.x < _clipRect.left)
452             || (p1.x >= _clipRect.right && p2.x >= _clipRect.right && p3.x >= _clipRect.bottom)
453             || (p1.y < _clipRect.top && p2.y < _clipRect.top && p3.y < _clipRect.top)
454             || (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom && p3.y >= _clipRect.bottom))
455             return;
456         /++
457          +                   side 1
458          +  p1-------p11------------p21--------------p2
459          +   \                                       /
460          +    \                                     /
461          +     \                                   /
462          +      \                                 /
463          +    p13\                               /p22
464          +        \                             /
465          +         \                           /
466          +          \                         /
467          +           \                       /  side 2
468          +    side 3  \                     /
469          +             \                   /
470          +              \                 /
471          +               \               /p32
472          +             p33\             /
473          +                 \           /
474          +                  \         /
475          +                   \       /
476          +                    \     /
477          +                     \   /
478          +                      \ /
479          +                      p3
480          +/
481         PointF p11 = p1;
482         PointF p13 = p1;
483         PointF p21 = p2;
484         PointF p22 = p2;
485         PointF p32 = p3;
486         PointF p33 = p3;
487         bool p1moved = false;
488         bool p2moved = false;
489         bool p3moved = false;
490         bool side1clipped = intersectClipF(p11, p21, p1moved, p2moved);
491         bool side2clipped = intersectClipF(p22, p32, p2moved, p3moved);
492         bool side3clipped = intersectClipF(p33, p13, p3moved, p1moved);
493         if (!p1moved && !p2moved && !p3moved) {
494             // no moved - no clipping
495             fillTriangleFClipped(p1, p2, p3, colour);
496         } else if (p1moved && !p2moved && !p3moved) {
497             fillTriangleFClipped(p11, p2, p3, colour);
498             fillTriangleFClipped(p3, p13, p11, colour);
499         } else if (!p1moved && p2moved && !p3moved) {
500             fillTriangleFClipped(p22, p3, p1, colour);
501             fillTriangleFClipped(p1, p21, p22, colour);
502         } else if (!p1moved && !p2moved && p3moved) {
503             fillTriangleFClipped(p33, p1, p2, colour);
504             fillTriangleFClipped(p2, p32, p33, colour);
505         } else if (p1moved && p2moved && !p3moved) {
506             if (!side1clipped) {
507                 fillTriangleFClipped(p13, p11, p21, colour);
508                 fillTriangleFClipped(p21, p22, p13, colour);
509             }
510             fillTriangleFClipped(p22, p3, p13, colour);
511         } else if (!p1moved && p2moved && p3moved) {
512             if (!side2clipped) {
513                 fillTriangleFClipped(p21, p22, p32, colour);
514                 fillTriangleFClipped(p32, p33, p21, colour);
515             }
516             fillTriangleFClipped(p21, p33, p1, colour);
517         } else if (p1moved && !p2moved && p3moved) {
518             if (!side3clipped) {
519                 fillTriangleFClipped(p13, p11, p32, colour);
520                 fillTriangleFClipped(p32, p33, p13, colour);
521             }
522             fillTriangleFClipped(p11, p2, p32, colour);
523         } else if (p1moved && p2moved && p3moved) {
524             if (side1clipped) {
525                 fillTriangleFClipped(p13, p22, p32, colour);
526                 fillTriangleFClipped(p32, p33, p13, colour);
527             } else if (side2clipped) {
528                 fillTriangleFClipped(p11, p21, p33, colour);
529                 fillTriangleFClipped(p33, p13, p11, colour);
530             } else if (side3clipped) {
531                 fillTriangleFClipped(p11, p21, p22, colour);
532                 fillTriangleFClipped(p22, p32, p11, colour);
533             } else {
534                 fillTriangleFClipped(p13, p11, p21, colour);
535                 fillTriangleFClipped(p21, p22, p13, colour);
536                 fillTriangleFClipped(p22, p32, p33, colour);
537                 fillTriangleFClipped(p33, p13, p22, colour);
538             }
539         }
540     }
541 
542     /// draw filled quad in float coordinates
543     void fillQuadF(PointF p1, PointF p2, PointF p3, PointF p4, uint colour) {
544         fillTriangleF(p1, p2, p3, colour);
545         fillTriangleF(p3, p4, p1, colour);
546     }
547 
548     /// draw line of arbitrary width in float coordinates
549     void drawLineF(PointF p1, PointF p2, float width, uint colour) {
550         // direction vector
551         PointF v = (p2 - p1).normalized;
552         // calculate normal vector
553         // calculate normal vector : rotate CCW 90 degrees
554         PointF n = v.rotated90ccw();
555         // rotate CCW 90 degrees
556         n.y = v.x;
557         n.x = -v.y;
558         // offset by normal * half_width
559         n *= width / 2;
560         // draw line using quad
561         fillQuadF(p1 - n, p2 - n, p2 + n, p1 + n, colour);
562     }
563 
564     // find intersection point for two vectors with start points p1, p2 and normalized directions dir1, dir2
565     protected static PointF intersectVectors(PointF p1, PointF dir1, PointF p2, PointF dir2) {
566         /*
567         L1 = P1 + a * V1
568         L2 = P2 + b * V2
569         P1 + a * V1 = P2 + b * V2
570         a * V1 = (P2 - P1) + b * V2
571         a * (V1 X V2) = (P2 - P1) X V2
572         a = (P2 - P1) * V2 / (V1*V2)
573         return P1 + a * V1
574         */
575         // just return middle point
576         PointF p2p1 = (p2 - p1); //.normalized;
577         float d1 = p2p1.crossProduct(dir2);
578         float d2 = dir1.crossProduct(dir2);
579         // a * d1 = d2
580         if (d2 >= -0.1f && d2 <= 0.1f) {
581             return p1; //PointF((p1.x + p2.x)/2, (p1.y + p2.y)/2);
582         }
583         float a = d1 / d2;
584         return p1 + dir1 * a;
585     }
586 
587     protected void calcLineSegmentQuad(PointF p0, PointF p1, PointF p2, PointF p3, float width, ref PointF[4] quad) {
588         // direction vector
589         PointF v = (p2 - p1).normalized;
590         // calculate normal vector : rotate CCW 90 degrees
591         PointF n = v.rotated90ccw();
592         // offset by normal * half_width
593         n *= width / 2;
594         // draw line using quad
595         PointF pp10 = p1 - n;
596         PointF pp20 = p2 - n;
597         PointF pp11 = p1 + n;
598         PointF pp21 = p2 + n;
599         if ((p1 - p0).length > 0.1f) {
600             // has prev segment
601             PointF prevv = (p1 - p0).normalized;
602             PointF prevn = prevv.rotated90ccw();
603             PointF prev10 = p1 - prevn * width / 2;
604             PointF prev11 = p1 + prevn * width / 2;
605             PointF intersect0 = intersectVectors(pp10, -v, prev10, prevv);
606             PointF intersect1 = intersectVectors(pp11, -v, prev11, prevv);
607             pp10 = intersect0;
608             pp11 = intersect1;
609         }
610         if ((p3 - p2).length > 0.1f) {
611             // has next segment
612             PointF nextv = (p3 - p2).normalized;
613             PointF nextn = nextv.rotated90ccw();
614             PointF next20 = p2 - nextn * width / 2;
615             PointF next21 = p2 + nextn * width / 2;
616             PointF intersect0 = intersectVectors(pp20, v, next20, -nextv);
617             PointF intersect1 = intersectVectors(pp21, v, next21, -nextv);
618             pp20 = intersect0;
619             pp21 = intersect1;
620         }
621         quad[0] = pp10;
622         quad[1] = pp20;
623         quad[2] = pp21;
624         quad[3] = pp11;
625     }
626     /// draw line of arbitrary width in float coordinates p1..p2 with angle based on previous (p0..p1) and next (p2..p3) segments
627     void drawLineSegmentF(PointF p0, PointF p1, PointF p2, PointF p3, float width, uint colour) {
628         PointF[4] quad;
629         calcLineSegmentQuad(p0, p1, p2, p3, width, quad);
630         fillQuadF(quad[0], quad[1], quad[2], quad[3], colour);
631     }
632 
633     /// draw poly line of arbitrary width in float coordinates; when cycled is true, connect first and last point (optionally fill inner area)
634     void polyLineF(PointF[] points, float width, uint colour, bool cycled = false, uint innerAreaColour = COLOR_TRANSPARENT) {
635         if (points.length < 2)
636             return;
637         bool hasInnerArea = !isFullyTransparentColor(innerAreaColour);
638         if (isFullyTransparentColor(colour)) {
639             if (hasInnerArea)
640                 fillPolyF(points, innerAreaColour);
641             return;
642         }
643         int len = cast(int)points.length;
644         if (hasInnerArea) {
645             PointF[] innerArea;
646             innerArea.assumeSafeAppend;
647             //Log.d("fill poly inner: ", points);
648             for(int i = 0; i < len; i++) {
649                 PointF[4] quad;
650                 int index0 = i - 1;
651                 int index1 = i;
652                 int index2 = i + 1;
653                 int index3 = i + 2;
654                 if (index0 < 0)
655                     index0 = cycled ? len - 1 : 0;
656                 index2 %= len; // only can be if cycled
657                 index3 %= len; // only can be if cycled
658                 if (!cycled) {
659                     if (index1 == len - 1) {
660                         index0 = index1;
661                         index2 = 0;
662                         index3 = 0;
663                     } else if (index1 == len - 2) {
664                         index2 = len - 1;
665                         index3 = len - 1;
666                     }
667                 }
668                 //Log.d("lineSegment - inner ", index0, ", ", index1, ", ", index2, ", ", index3);
669                 calcLineSegmentQuad(points[index0], points[index1], points[index2], points[index3], width, quad);
670                 innerArea ~= quad[3];
671             }
672             fillPolyF(innerArea, innerAreaColour);
673         }
674         if (!isFullyTransparentColor(colour)) {
675             for(int i = 0; i < len; i++) {
676                 int index0 = i - 1;
677                 int index1 = i;
678                 int index2 = i + 1;
679                 int index3 = i + 2;
680                 if (index0 < 0)
681                     index0 = cycled ? len - 1 : 0;
682                 index2 %= len; // only can be if cycled
683                 index3 %= len; // only can be if cycled
684                 if (!cycled) {
685                     if (index1 == len - 1) {
686                         index0 = index1;
687                         index2 = 0;
688                         index3 = 0;
689                     } else if (index1 == len - 2) {
690                         index2 = len - 1;
691                         index3 = len - 1;
692                     }
693                 }
694                 //Log.d("lineSegment - outer ", index0, ", ", index1, ", ", index2, ", ", index3);
695                 if (cycled || i + 1 < len)
696                     drawLineSegmentF(points[index0], points[index1], points[index2], points[index3], width, colour);
697             }
698         }
699     }
700 
701     /// draw filled polyline (vertexes must be in clockwise order)
702     void fillPolyF(PointF[] points, uint colour) {
703         if (points.length < 3) {
704             return;
705         }
706         if (points.length == 3) {
707             fillTriangleF(points[0], points[1], points[2], colour);
708             return;
709         }
710         PointF[] list = points.dup;
711         bool moved;
712         while (list.length > 3) {
713             moved = false;
714             for (int i = 0; i < list.length; i++) {
715                 PointF p1 = list[i + 0];
716                 PointF p2 = list[(i + 1) % list.length];
717                 PointF p3 = list[(i + 2) % list.length];
718                 float cross = (p2 - p1).crossProduct(p3 - p2);
719                 if (cross > 0) {
720                     // draw triangle
721                     fillTriangleF(p1, p2, p3, colour);
722                     int indexToRemove = (i + 1) % (cast(int)list.length);
723                     // remove triangle from poly
724                     for (int j = indexToRemove; j + 1 < list.length; j++)
725                         list[j] = list[j + 1];
726                     list.length = list.length - 1;
727                     i += 2;
728                     moved = true;
729                 }
730             }
731             if (list.length == 3) {
732                 fillTriangleF(list[0], list[1], list[2], colour);
733                 break;
734             }
735             if (!moved)
736                 break;
737         }
738     }
739 
740     /// draw ellipse or filled ellipse
741     void drawEllipseF(float centerX, float centerY, float xRadius, float yRadius, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) {
742         import std.math : sin, cos, PI;
743         if (xRadius < 0)
744             xRadius = -xRadius;
745         if (yRadius < 0)
746             yRadius = -yRadius;
747         int numLines = cast(int)((xRadius + yRadius) / 5);
748         if (numLines < 4)
749             numLines = 4;
750         float step = PI * 2 / numLines;
751         float angle = 0;
752         PointF[] points;
753         points.assumeSafeAppend;
754         for (int i = 0; i < numLines; i++) {
755             float x = centerX + cos(angle) * xRadius;
756             float y = centerY + sin(angle) * yRadius;
757             angle += step;
758             points ~= PointF(x, y);
759         }
760         polyLineF(points, lineWidth, lineColor, true, fillColor);
761     }
762 
763     /// draw ellipse arc or filled ellipse arc
764     void drawEllipseArcF(float centerX, float centerY, float xRadius, float yRadius, float startAngle, float endAngle, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) {
765         import std.math : sin, cos, PI;
766         if (xRadius < 0)
767             xRadius = -xRadius;
768         if (yRadius < 0)
769             yRadius = -yRadius;
770         startAngle = startAngle * 2 * PI / 360;
771         endAngle = endAngle * 2 * PI / 360;
772         if (endAngle < startAngle)
773             endAngle += 2 * PI;
774         float angleDiff = endAngle - startAngle;
775         if (angleDiff > 2*PI)
776             angleDiff %= 2*PI;
777         int numLines = cast(int)((xRadius + yRadius) / angleDiff);
778         if (numLines < 3)
779             numLines = 4;
780         float step = angleDiff / numLines;
781         float angle = startAngle;
782         PointF[] points;
783         points.assumeSafeAppend;
784         points ~= PointF(centerX, centerY);
785         for (int i = 0; i < numLines; i++) {
786             float x = centerX + cos(angle) * xRadius;
787             float y = centerY + sin(angle) * yRadius;
788             angle += step;
789             points ~= PointF(x, y);
790         }
791         polyLineF(points, lineWidth, lineColor, true, fillColor);
792     }
793 
794     /// draw poly line of width == 1px; when cycled is true, connect first and last point
795     void polyLine(Point[] points, uint colour, bool cycled) {
796         if (points.length < 2)
797             return;
798         for(int i = 0; i + 1 < points.length; i++) {
799             drawLine(points[i], points[i + 1], colour);
800         }
801         if (cycled && points.length > 2)
802             drawLine(points[$ - 1], points[0], colour);
803     }
804 
805     /// draw line from point p1 to p2 with specified color
806     void drawLine(Point p1, Point p2, uint colour) {
807         if (!clipLine(_clipRect, p1, p2))
808             return;
809         // from rosettacode.org
810         import std.math: abs;
811         immutable int dx = p2.x - p1.x;
812         immutable int ix = (dx > 0) - (dx < 0);
813         immutable int dx2 = abs(dx) * 2;
814         int dy = p2.y - p1.y;
815         immutable int iy = (dy > 0) - (dy < 0);
816         immutable int dy2 = abs(dy) * 2;
817         drawPixel(p1.x, p1.y, colour);
818         if (dx2 >= dy2) {
819             int error = dy2 - (dx2 / 2);
820             while (p1.x != p2.x) {
821                 if (error >= 0 && (error || (ix > 0))) {
822                     error -= dx2;
823                     p1.y += iy;
824                 }
825                 error += dy2;
826                 p1.x += ix;
827                 drawPixel(p1.x, p1.y, colour);
828             }
829         } else {
830             int error = dx2 - (dy2 / 2);
831             while (p1.y != p2.y) {
832                 if (error >= 0 && (error || (iy > 0))) {
833                     error -= dy2;
834                     p1.x += ix;
835                 }
836                 error += dx2;
837                 p1.y += iy;
838                 drawPixel(p1.x, p1.y, colour);
839             }
840         }
841     }
842 
843     // basically a modified drawEllipseArcF that doesn't draw but gives you the lines instead!
844     static PointF[] makeArcPath(PointF center, float radiusX, float radiusY, float startAngle, float endAngle) {
845         import std.math : sin, cos, PI, abs, sqrt;
846         radiusX = abs(radiusX);
847         radiusY = abs(radiusY);
848         startAngle = startAngle * 2 * PI / 360;
849         endAngle = endAngle * 2 * PI / 360;
850         if (endAngle < startAngle) 
851             endAngle += 2 * PI;
852         float angleDiff = endAngle - startAngle;
853         if (angleDiff > 2*PI) 
854             angleDiff %= 2*PI;
855         int numLines = cast(int)(sqrt(radiusX*radiusX + radiusY*radiusY) / angleDiff);
856         if (numLines < 3) 
857             numLines = 4;
858         float step = angleDiff / numLines;
859         float angle = startAngle;
860         PointF[] points;
861         points.assumeSafeAppend;
862         if ( radiusX == 0 ) {
863             points ~= PointF(center.x, center.y);
864         }
865         else  for (int i; i < numLines+1; i++) {
866             float x = center.x + cos(angle) * radiusX;
867             float y = center.y + sin(angle) * radiusY;
868             angle += step;
869             points ~= PointF(x, y);
870         }
871         return points;
872     }
873 
874     // calculates inwards XY offsets from rect corners
875     static PointF[4] calcRectRoundedCornerRadius(vec4 corners, float w, float h, bool keepSquareXY) {
876         import std.algorithm.comparison : min;
877         // clamps radius to corner
878         static float clampRadius(float r, float len) pure @safe @nogc {
879             if (len - 2*r < 0) return len/2; 
880             return r;
881         }
882         if (keepSquareXY) {
883             auto minSize = min(w, h);
884             w = h = minSize;
885         }
886         PointF[4] cornerRad;
887         cornerRad[0] = PointF( clampRadius(corners.x, w), clampRadius(corners.x, h) );
888         cornerRad[1] = PointF( -clampRadius(corners.y, w), clampRadius(corners.y, h) );
889         cornerRad[2] = PointF( clampRadius(corners.z, w), -clampRadius(corners.z, h) );
890         cornerRad[3] = PointF( -clampRadius(corners.w, w), -clampRadius(corners.w, h) );
891         return cornerRad;
892     }
893 
894     /// builds outer rounded rect path
895     /// the last parameter optionally writes out indices of first segment of corners excluding top-left
896     static PointF[] makeRoundedRectPath(Rect rect, vec4 corners, bool keepSquareXY, size_t[] outCornerSegmentsStart = null) {
897         import std.range : chain;
898         import std.array : array;
899         PointF[4] cornerRad = calcRectRoundedCornerRadius(corners, rect.width, rect.height, keepSquareXY);
900         PointF topLeftC  = PointF(rect.left, rect.top) + cornerRad[0];
901         PointF topRightC = PointF(rect.left, rect.top) + cornerRad[1] + PointF(rect.width, 0);
902         PointF botLeftC  = PointF(rect.left, rect.top) + cornerRad[2] + PointF(0, rect.height);
903         PointF botRightC = PointF(rect.left, rect.top) + cornerRad[3] + PointF(rect.width, rect.height);
904         auto lt = makeArcPath(topLeftC, cornerRad[0].x, cornerRad[0].y, 180, 270);
905         auto rt = makeArcPath(topRightC, cornerRad[1].x, cornerRad[1].y, 270, 0);
906         auto lb = makeArcPath(botLeftC, cornerRad[2].x, cornerRad[2].y, 90, 180);
907         auto rb = makeArcPath(botRightC, cornerRad[3].x, cornerRad[3].y, 0, 90);
908         if ( outCornerSegmentsStart.length ) {
909             outCornerSegmentsStart[0] = lt.length;
910             outCornerSegmentsStart[1] = lt.length + rt.length;
911             outCornerSegmentsStart[2] = lt.length + rt.length + lb.length;
912         }
913         auto outerPath = chain( lt, rt, rb, lb, lt[0..1] );
914         return outerPath.array();
915     }
916 
917     /// draws rect with rounded corners
918     void drawRoundedRectF(Rect rect, vec4 corners, bool keepSquareXY, float frameWidth, uint frameColor, uint fillColor = COLOR_TRANSPARENT) {
919         auto fullPath = makeRoundedRectPath(rect, corners, keepSquareXY);
920         // fill inner area, doing this manually by sectors to reduce flickering artifacts
921         if ( fillColor != COLOR_TRANSPARENT ) {
922             PointF center = PointF(rect.middlex, rect.middley);
923             for( int i = 0; i < fullPath.length-1; i++ ) {
924                 fillTriangleF(center, fullPath[i], fullPath[i+1], fillColor);
925             }
926         }
927         if (frameColor != COLOR_TRANSPARENT && frameWidth > 0) {
928             for( int i = 0; i < fullPath.length-1; i++ ) {
929                 drawLineF(fullPath[i], fullPath[i+1], frameWidth, frameColor);
930             }
931         }
932     }
933 
934     /// create drawbuf with copy of current buffer with changed colors (returns this if not supported)
935     DrawBuf transformColors(ref ColorTransform transform) {
936         return this;
937     }
938 
939     /// draw custom OpenGL scene
940     void drawCustomOpenGLScene(Rect rc, OpenGLDrawableDelegate handler) {
941         // override it for OpenGL draw buffer
942         Log.w("drawCustomOpenGLScene is called for non-OpenGL DrawBuf");
943     }
944 
945     void clear() {
946         resetClipping();
947     }
948 }
949 
950 alias DrawBufRef = Ref!DrawBuf;
951 
952 /// RAII setting/restoring of a DrawBuf clip rectangle
953 struct ClipRectSaver {
954     private DrawBuf _buf;
955     private Rect _oldClipRect;
956     private uint _oldAlpha;
957 
958     /// apply (intersect) new clip rectangle and alpha to draw buf
959     /// set `intersect` parameter to `false`, if you want to draw something outside of the widget
960     this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0, bool intersect = true) {
961         _buf = buf;
962         _oldClipRect = buf.clipRect;
963         _oldAlpha = buf.alpha;
964         if (intersect)
965             buf.intersectClipRect(newClipRect);
966         else
967             buf.clipRect = newClipRect;
968         if (newAlpha)
969             buf.addAlpha(newAlpha);
970     }
971     /// restore previous clip rectangle
972     ~this() {
973         _buf.clipRect = _oldClipRect;
974         _buf.alpha = _oldAlpha;
975     }
976 }
977 
978 class ColorDrawBufBase : DrawBuf {
979     int _dx;
980     int _dy;
981     /// returns buffer bits per pixel
982     override @property int bpp() { return 32; }
983     @property override int width() { return _dx; }
984     @property override int height() { return _dy; }
985 
986     /// returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory
987     uint * scanLine(int y) { return null; }
988 
989     /// draw source buffer rectangle contents to destination buffer
990     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
991         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
992         if (applyClipping(dstrect, srcrect)) {
993             if (src.applyClipping(srcrect, dstrect)) {
994                 int dx = srcrect.width;
995                 int dy = srcrect.height;
996                 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
997                 if (colorDrawBuf !is null) {
998                     foreach(yy; 0 .. dy) {
999                         uint * srcrow = colorDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
1000                         uint * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
1001                         if (!_alpha) {
1002                             // simplified version - no alpha blending
1003                             foreach(i; 0 .. dx) {
1004                                 uint pixel = srcrow[i];
1005                                 uint alpha = pixel >> 24;
1006                                 if (!alpha)
1007                                     dstrow[i] = pixel;
1008                                 else if (alpha < 254) {
1009                                     // apply blending
1010                                     dstrow[i] = blendARGB(dstrow[i], pixel, alpha);
1011                                 }
1012                             }
1013                         } else {
1014                             // combine two alphas
1015                             foreach(i; 0 .. dx) {
1016                                 uint pixel = srcrow[i];
1017                                 uint alpha = blendAlpha(_alpha, pixel >> 24);
1018                                 if (!alpha)
1019                                     dstrow[i] = pixel;
1020                                 else if (alpha < 254) {
1021                                     // apply blending
1022                                     dstrow[i] = blendARGB(dstrow[i], pixel, alpha);
1023                                 }
1024                             }
1025                         }
1026 
1027                     }
1028                 }
1029             }
1030         }
1031     }
1032 
1033 	import std.container.array;
1034 
1035     /// Create mapping of source coordinates to destination coordinates, for resize.
1036     private Array!int createMap(int dst0, int dst1, int src0, int src1, double k) {
1037         int dd = dst1 - dst0;
1038         //int sd = src1 - src0;
1039 		Array!int res;
1040 		res.length = dd;
1041         foreach(int i; 0 .. dd)
1042             res[i] = src0 + cast(int)(i * k);//sd / dd;
1043         return res;
1044     }
1045 
1046     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
1047     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
1048         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
1049         if (_alpha >= 254)
1050             return; // fully transparent - don't draw
1051 		double kx = cast(double)srcrect.width / dstrect.width;
1052 		double ky = cast(double)srcrect.height / dstrect.height;
1053         if (applyClipping(dstrect, srcrect)) {
1054             auto xmapArray = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right, kx);
1055             auto ymapArray = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom, ky);
1056 
1057             int * xmap = &xmapArray[0];
1058             int * ymap = &ymapArray[0];
1059             int dx = dstrect.width;
1060             int dy = dstrect.height;
1061             ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
1062             if (colorDrawBuf !is null) {
1063                 foreach(y; 0 .. dy) {
1064                     uint * srcrow = colorDrawBuf.scanLine(ymap[y]);
1065                     uint * dstrow = scanLine(dstrect.top + y) + dstrect.left;
1066                     if (!_alpha) {
1067                         // simplified alpha calculation
1068                         foreach(x; 0 .. dx) {
1069                             uint srcpixel = srcrow[xmap[x]];
1070                             uint dstpixel = dstrow[x];
1071                             uint alpha = srcpixel >> 24;
1072                             if (!alpha)
1073                                 dstrow[x] = srcpixel;
1074                             else if (alpha < 255) {
1075                                 // apply blending
1076                                 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha);
1077                             }
1078                         }
1079                     } else {
1080                         // blending two alphas
1081                         foreach(x; 0 .. dx) {
1082                             uint srcpixel = srcrow[xmap[x]];
1083                             uint dstpixel = dstrow[x];
1084                             uint srca = srcpixel >> 24;
1085                             uint alpha = !srca ? _alpha : blendAlpha(_alpha, srca);
1086                             if (!alpha)
1087                                 dstrow[x] = srcpixel;
1088                             else if (alpha < 255) {
1089                                 // apply blending
1090                                 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha);
1091                             }
1092                         }
1093                     }
1094                 }
1095             }
1096         }
1097     }
1098 
1099     /// detect position of black pixels in row for 9-patch markup
1100     private bool detectHLine(int y, ref int x0, ref int x1) {
1101         uint * line = scanLine(y);
1102         bool foundUsed = false;
1103         x0 = 0;
1104         x1 = 0;
1105         foreach(int x; 1 .. _dx - 1) {
1106             if (isBlackPixel(line[x])) { // opaque black pixel
1107                 if (!foundUsed) {
1108                     x0 = x;
1109                     foundUsed = true;
1110                 }
1111                 x1 = x + 1;
1112             }
1113         }
1114         return x1 > x0;
1115     }
1116 
1117     static bool isBlackPixel(uint c) {
1118         if (((c >> 24) & 255) > 10)
1119             return false;
1120         if (((c >> 16) & 255) > 10)
1121             return false;
1122         if (((c >> 8) & 255) > 10)
1123             return false;
1124         if (((c >> 0) & 255) > 10)
1125             return false;
1126         return true;
1127     }
1128 
1129     /// detect position of black pixels in column for 9-patch markup
1130     private bool detectVLine(int x, ref int y0, ref int y1) {
1131         bool foundUsed = false;
1132         y0 = 0;
1133         y1 = 0;
1134         foreach(int y; 1 .. _dy - 1) {
1135             uint * line = scanLine(y);
1136             if (isBlackPixel(line[x])) { // opaque black pixel
1137                 if (!foundUsed) {
1138                     y0 = y;
1139                     foundUsed = true;
1140                 }
1141                 y1 = y + 1;
1142             }
1143         }
1144         return y1 > y0;
1145     }
1146     /// detect nine patch using image 1-pixel border (see Android documentation)
1147     override bool detectNinePatch() {
1148         if (_dx < 3 || _dy < 3)
1149             return false; // image is too small
1150         int x00, x01, x10, x11, y00, y01, y10, y11;
1151         bool found = true;
1152         found = found && detectHLine(0, x00, x01);
1153         found = found && detectHLine(_dy - 1, x10, x11);
1154         found = found && detectVLine(0, y00, y01);
1155         found = found && detectVLine(_dx - 1, y10, y11);
1156         if (!found)
1157             return false; // no black pixels on 1-pixel frame
1158         NinePatch * p = new NinePatch();
1159         p.frame.left = x00 - 1;
1160         p.frame.right = _dx - x01 - 1;
1161         p.frame.top = y00 - 1;
1162         p.frame.bottom = _dy - y01 - 1;
1163         p.padding.left = x10 - 1;
1164         p.padding.right = _dx - x11 - 1;
1165         p.padding.top = y10 - 1;
1166         p.padding.bottom = _dy - y11 - 1;
1167         _ninePatch = p;
1168         //Log.d("NinePatch detected: frame=", p.frame, " padding=", p.padding, " left+right=", p.frame.left + p.frame.right, " dx=", _dx);
1169         return true;
1170     }
1171 
1172     override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
1173         ubyte[] src = glyph.glyph;
1174         int srcdx = glyph.blackBoxX;
1175         int srcdy = glyph.blackBoxY;
1176         bool clipping = true; //!_clipRect.empty();
1177         color = applyAlpha(color);
1178         bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None;
1179         foreach(int yy; 0 .. srcdy) {
1180             int liney = y + yy;
1181             if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
1182                 continue;
1183             if (liney < 0 || liney >= _dy)
1184                 continue;
1185             uint * row = scanLine(liney);
1186             ubyte * srcrow = src.ptr + yy * srcdx;
1187             foreach(int xx; 0 .. srcdx) {
1188                 int colx = x + (subpixel ? xx / 3 : xx);
1189                 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
1190                     continue;
1191                 if (colx < 0 || colx >= _dx)
1192                     continue;
1193                 uint alpha2 = (color >> 24);
1194                 uint alpha1 = srcrow[xx] ^ 255;
1195                 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
1196                 if (subpixel) {
1197                     int x0 = xx % 3;
1198                     ubyte * dst = cast(ubyte*)(row + colx);
1199                     ubyte * pcolor = cast(ubyte*)(&color);
1200                     blendSubpixel(dst, pcolor, alpha, x0, glyph.subpixelMode);
1201                 } else {
1202                     uint pixel = row[colx];
1203                     if (alpha < 255) {
1204                         if (!alpha)
1205                             row[colx] = pixel;
1206                         else {
1207                             // apply blending
1208                             row[colx] = blendARGB(pixel, color, alpha);
1209                         }
1210                     }
1211                 }
1212             }
1213         }
1214     }
1215 
1216     void drawGlyphToTexture(int x, int y, Glyph * glyph) {
1217         ubyte[] src = glyph.glyph;
1218         int srcdx = glyph.blackBoxX;
1219         int srcdy = glyph.blackBoxY;
1220         bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None;
1221         foreach(int yy; 0 .. srcdy) {
1222             int liney = y + yy;
1223             uint * row = scanLine(liney);
1224             ubyte * srcrow = src.ptr + yy * srcdx;
1225             int increment = subpixel ? 3 : 1;
1226             for (int xx = 0; xx <= srcdx - increment; xx += increment) {
1227                 int colx = x + (subpixel ? xx / 3 : xx);
1228                 if (subpixel) {
1229                     uint t1 = srcrow[xx];
1230                     uint t2 = srcrow[xx + 1];
1231                     uint t3 = srcrow[xx + 2];
1232                     //uint pixel = ((t2 ^ 0x00) << 24) | ((t1  ^ 0xFF)<< 16) | ((t2 ^ 0xFF) << 8) | (t3 ^ 0xFF);
1233                     uint pixel = ((t2 ^ 0x00) << 24) | 0xFFFFFF;
1234                     row[colx] = pixel;
1235                 } else {
1236                     uint alpha1 = srcrow[xx] ^ 0xFF;
1237                     //uint pixel = (alpha1 << 24) | 0xFFFFFF; //(alpha1 << 16) || (alpha1 << 8) || alpha1;
1238                     //uint pixel = ((alpha1 ^ 0xFF) << 24) | (alpha1 << 16) | (alpha1 << 8) | alpha1;
1239                     uint pixel = ((alpha1 ^ 0xFF) << 24) | 0xFFFFFF;
1240                     row[colx] = pixel;
1241                 }
1242             }
1243         }
1244     }
1245 
1246     override void fillRect(Rect rc, uint color) {
1247         uint alpha = color >> 24;
1248         if (applyClipping(rc)) {
1249             foreach(y; rc.top .. rc.bottom) {
1250                 uint * row = scanLine(y);
1251                 if (!alpha) {
1252                     row[rc.left .. rc.right] = color;
1253                 } else if (alpha < 254) {
1254                     foreach(x; rc.left .. rc.right) {
1255                         // apply blending
1256                         row[x] = blendARGB(row[x], color, alpha);
1257                     }
1258                 }
1259             }
1260         }
1261     }
1262 
1263     /// fill rectangle with a gradient (clipping is applied)
1264     override void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) {
1265         if (applyClipping(rc)) {
1266             foreach (y; rc.top .. rc.bottom) {
1267                 // interpolate vertically at the side edges
1268                 uint ay = (255 * (y - rc.top)) / (rc.bottom - rc.top);
1269                 uint cl = blendARGB(color2, color1, ay);
1270                 uint cr = blendARGB(color4, color3, ay);
1271 
1272                 uint * row = scanLine(y);
1273                 foreach (x; rc.left .. rc.right) {
1274                     // interpolate horizontally
1275                     uint ax = (255 * (x - rc.left)) / (rc.right - rc.left);
1276                     row[x] = blendARGB(cr, cl, ax);
1277                 }
1278             }
1279         }
1280     }
1281 
1282     /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted
1283     override void fillRectPattern(Rect rc, uint color, int pattern) {
1284         uint alpha = color >> 24;
1285         if (alpha == 255) // fully transparent
1286             return;
1287         if (applyClipping(rc)) {
1288             foreach(y; rc.top .. rc.bottom) {
1289                 uint * row = scanLine(y);
1290                 if (!alpha) {
1291                     if (pattern == 1) {
1292                         foreach(x; rc.left .. rc.right) {
1293                             if ((x ^ y) & 1)
1294                                 row[x] = color;
1295                         }
1296                     } else {
1297                         row[rc.left .. rc.right] = color;
1298                     }
1299                 } else if (alpha < 254) {
1300                     foreach(x; rc.left .. rc.right) {
1301                         // apply blending
1302                         if (pattern != 1 || ((x ^ y) & 1) != 0)
1303                             row[x] = blendARGB(row[x], color, alpha);
1304                     }
1305                 }
1306             }
1307         }
1308     }
1309 
1310     /// draw pixel at (x, y) with specified color
1311     override void drawPixel(int x, int y, uint color) {
1312         if (!_clipRect.isPointInside(x, y))
1313             return;
1314         color = applyAlpha(color);
1315         uint * row = scanLine(y);
1316         uint alpha = color >> 24;
1317         if (!alpha) {
1318             row[x] = color;
1319         } else if (alpha < 254) {
1320             // apply blending
1321             row[x] = blendARGB(row[x], color, alpha);
1322         }
1323     }
1324 }
1325 
1326 class GrayDrawBuf : DrawBuf {
1327     protected int _dx;
1328     protected int _dy;
1329     /// returns buffer bits per pixel
1330     override @property int bpp() { return 8; }
1331     @property override int width() { return _dx; }
1332     @property override int height() { return _dy; }
1333 
1334     protected MallocBuf!ubyte _buf;
1335 
1336     this(int width, int height) {
1337         resize(width, height);
1338     }
1339     ubyte * scanLine(int y) {
1340         if (y >= 0 && y < _dy)
1341             return _buf.ptr + _dx * y;
1342         return null;
1343     }
1344     override void resize(int width, int height) {
1345         if (_dx == width && _dy == height)
1346             return;
1347         _dx = width;
1348         _dy = height;
1349         _buf.length = _dx * _dy;
1350         resetClipping();
1351     }
1352     override void fill(uint color) {
1353         if (hasClipping) {
1354             fillRect(_clipRect, color);
1355             return;
1356         }
1357         int len = _dx * _dy;
1358         ubyte * p = _buf.ptr;
1359         ubyte cl = rgbToGray(color);
1360         foreach(i; 0 .. len)
1361             p[i] = cl;
1362     }
1363 
1364     /// draw source buffer rectangle contents to destination buffer
1365     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
1366         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
1367         if (applyClipping(dstrect, srcrect)) {
1368             if (src.applyClipping(srcrect, dstrect)) {
1369                 int dx = srcrect.width;
1370                 int dy = srcrect.height;
1371                 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
1372                 if (grayDrawBuf !is null) {
1373                     foreach(yy; 0 .. dy) {
1374                         ubyte * srcrow = grayDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
1375                         ubyte * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
1376                         foreach(i; 0 .. dx) {
1377                             ubyte pixel = srcrow[i];
1378                             dstrow[i] = pixel;
1379                         }
1380                     }
1381                 }
1382             }
1383         }
1384     }
1385 
1386     /// Create mapping of source coordinates to destination coordinates, for resize.
1387     private int[] createMap(int dst0, int dst1, int src0, int src1) {
1388         int dd = dst1 - dst0;
1389         int sd = src1 - src0;
1390         int[] res = new int[dd];
1391         foreach(int i; 0 .. dd)
1392             res[i] = src0 + i * sd / dd;
1393         return res;
1394     }
1395     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
1396     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
1397         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
1398         if (applyClipping(dstrect, srcrect)) {
1399             int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right);
1400             int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom);
1401             int dx = dstrect.width;
1402             int dy = dstrect.height;
1403             GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
1404             if (grayDrawBuf !is null) {
1405                 foreach(y; 0 .. dy) {
1406                     ubyte * srcrow = grayDrawBuf.scanLine(ymap[y]);
1407                     ubyte * dstrow = scanLine(dstrect.top + y) + dstrect.left;
1408                     foreach(x; 0 .. dx) {
1409                         ubyte srcpixel = srcrow[xmap[x]];
1410                         ubyte dstpixel = dstrow[x];
1411                         dstrow[x] = srcpixel;
1412                     }
1413                 }
1414             }
1415         }
1416     }
1417 
1418     /// detect position of black pixels in row for 9-patch markup
1419     private bool detectHLine(int y, ref int x0, ref int x1) {
1420         ubyte * line = scanLine(y);
1421         bool foundUsed = false;
1422         x0 = 0;
1423         x1 = 0;
1424         foreach(int x; 1 .. _dx - 1) {
1425             if (line[x] == 0x00000000) { // opaque black pixel
1426                 if (!foundUsed) {
1427                     x0 = x;
1428                     foundUsed = true;
1429                 }
1430                 x1 = x + 1;
1431             }
1432         }
1433         return x1 > x0;
1434     }
1435 
1436     /// detect position of black pixels in column for 9-patch markup
1437     private bool detectVLine(int x, ref int y0, ref int y1) {
1438         bool foundUsed = false;
1439         y0 = 0;
1440         y1 = 0;
1441         foreach(int y; 1 .. _dy - 1) {
1442             ubyte * line = scanLine(y);
1443             if (line[x] == 0x00000000) { // opaque black pixel
1444                 if (!foundUsed) {
1445                     y0 = y;
1446                     foundUsed = true;
1447                 }
1448                 y1 = y + 1;
1449             }
1450         }
1451         return y1 > y0;
1452     }
1453     /// detect nine patch using image 1-pixel border (see Android documentation)
1454     override bool detectNinePatch() {
1455         if (_dx < 3 || _dy < 3)
1456             return false; // image is too small
1457         int x00, x01, x10, x11, y00, y01, y10, y11;
1458         bool found = true;
1459         found = found && detectHLine(0, x00, x01);
1460         found = found && detectHLine(_dy - 1, x10, x11);
1461         found = found && detectVLine(0, y00, y01);
1462         found = found && detectVLine(_dx - 1, y10, y11);
1463         if (!found)
1464             return false; // no black pixels on 1-pixel frame
1465         NinePatch * p = new NinePatch();
1466         p.frame.left = x00 - 1;
1467         p.frame.right = _dy - y01 - 1;
1468         p.frame.top = y00 - 1;
1469         p.frame.bottom = _dy - y01 - 1;
1470         p.padding.left = x10 - 1;
1471         p.padding.right = _dy - y11 - 1;
1472         p.padding.top = y10 - 1;
1473         p.padding.bottom = _dy - y11 - 1;
1474         _ninePatch = p;
1475         return true;
1476     }
1477     override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
1478         ubyte[] src = glyph.glyph;
1479         int srcdx = glyph.blackBoxX;
1480         int srcdy = glyph.blackBoxY;
1481         bool clipping = true; //!_clipRect.empty();
1482         foreach(int yy; 0 .. srcdy) {
1483             int liney = y + yy;
1484             if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
1485                 continue;
1486             if (liney < 0 || liney >= _dy)
1487                 continue;
1488             ubyte * row = scanLine(liney);
1489             ubyte * srcrow = src.ptr + yy * srcdx;
1490             foreach(int xx; 0 .. srcdx) {
1491                 int colx = xx + x;
1492                 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
1493                     continue;
1494                 if (colx < 0 || colx >= _dx)
1495                     continue;
1496                 uint alpha1 = srcrow[xx] ^ 255;
1497                 uint alpha2 = (color >> 24);
1498                 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
1499                 uint pixel = row[colx];
1500                 if (!alpha)
1501                     row[colx] = cast(ubyte)pixel;
1502                 else if (alpha < 255) {
1503                     // apply blending
1504                     row[colx] = cast(ubyte)blendARGB(pixel, color, alpha);
1505                 }
1506             }
1507         }
1508     }
1509     override void fillRect(Rect rc, uint color) {
1510         if (applyClipping(rc)) {
1511             uint alpha = color >> 24;
1512             ubyte cl = rgbToGray(color);
1513             foreach(y; rc.top .. rc.bottom) {
1514                 ubyte * row = scanLine(y);
1515                 foreach(x; rc.left .. rc.right) {
1516                     if (!alpha)
1517                         row[x] = cl;
1518                     else if (alpha < 255) {
1519                         // apply blending
1520                         row[x] = blendGray(row[x], cl, alpha);
1521                     }
1522                 }
1523             }
1524         }
1525     }
1526 
1527     /// fill rectangle with a gradient (clipping is applied)
1528     override void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) {
1529         if (applyClipping(rc)) {
1530             ubyte c1 = rgbToGray(color1);
1531             ubyte c2 = rgbToGray(color2);
1532             ubyte c3 = rgbToGray(color3);
1533             ubyte c4 = rgbToGray(color4);
1534             foreach (y; rc.top .. rc.bottom) {
1535                 // interpolate vertically at the side edges
1536                 uint ay = (255 * (y - rc.top)) / (rc.bottom - rc.top);
1537                 ubyte cl = blendGray(c2, c1, ay);
1538                 ubyte cr = blendGray(c4, c3, ay);
1539 
1540                 ubyte * row = scanLine(y);
1541                 foreach (x; rc.left .. rc.right) {
1542                     // interpolate horizontally
1543                     uint ax = (255 * (x - rc.left)) / (rc.right - rc.left);
1544                     row[x] = blendGray(cr, cl, ax);
1545                 }
1546             }
1547         }
1548     }
1549 
1550     /// draw pixel at (x, y) with specified color
1551     override void drawPixel(int x, int y, uint color) {
1552         if (!_clipRect.isPointInside(x, y))
1553             return;
1554         color = applyAlpha(color);
1555         ubyte cl = rgbToGray(color);
1556         ubyte * row = scanLine(y);
1557         uint alpha = color >> 24;
1558         if (!alpha) {
1559             row[x] = cl;
1560         } else if (alpha < 254) {
1561             // apply blending
1562             row[x] = blendGray(row[x], cl, alpha);
1563         }
1564     }
1565 }
1566 
1567 class ColorDrawBuf : ColorDrawBufBase {
1568     protected MallocBuf!uint _buf;
1569 
1570     /// create ARGB8888 draw buf of specified width and height
1571     this(int width, int height) {
1572         resize(width, height);
1573     }
1574     /// create copy of ColorDrawBuf
1575     this(ColorDrawBuf v) {
1576         this(v.width, v.height);
1577         if (auto len = _buf.length)
1578             _buf.ptr[0 .. len] = v._buf.ptr[0 .. len];
1579     }
1580     /// create resized copy of ColorDrawBuf
1581     this(ColorDrawBuf v, int dx, int dy) {
1582         this(dx, dy);
1583         fill(0xFFFFFFFF);
1584         drawRescaled(Rect(0, 0, dx, dy), v, Rect(0, 0, v.width, v.height));
1585     }
1586 
1587     void invertAndPreMultiplyAlpha() {
1588         foreach(ref pixel; _buf[]) {
1589             uint a = (pixel >> 24) & 0xFF;
1590             uint r = (pixel >> 16) & 0xFF;
1591             uint g = (pixel >> 8) & 0xFF;
1592             uint b = (pixel >> 0) & 0xFF;
1593             a ^= 0xFF;
1594             if (a > 0xFC) {
1595                 r = ((r * a) >> 8) & 0xFF;
1596                 g = ((g * a) >> 8) & 0xFF;
1597                 b = ((b * a) >> 8) & 0xFF;
1598             }
1599             pixel = (a << 24) | (r << 16) | (g << 8) | (b << 0);
1600         }
1601     }
1602 
1603     void invertAlpha() {
1604         foreach(ref pixel; _buf[])
1605             pixel ^= 0xFF000000;
1606     }
1607 
1608     void invertByteOrder() {
1609         foreach(ref pixel; _buf[]) {
1610             pixel = (pixel & 0xFF00FF00) |
1611                 ((pixel & 0xFF0000) >> 16) |
1612                 ((pixel & 0xFF) << 16);
1613         }
1614     }
1615 
1616     // for passing of image to OpenGL texture
1617     void invertAlphaAndByteOrder() {
1618         foreach(ref pixel; _buf[]) {
1619             pixel = ((pixel & 0xFF00FF00) |
1620                 ((pixel & 0xFF0000) >> 16) |
1621                 ((pixel & 0xFF) << 16));
1622             pixel ^= 0xFF000000;
1623         }
1624     }
1625 
1626     override uint * scanLine(int y) {
1627         if (y >= 0 && y < _dy)
1628             return _buf.ptr + _dx * y;
1629         return null;
1630     }
1631 
1632     override void resize(int width, int height) {
1633         if (_dx == width && _dy == height)
1634             return;
1635         _dx = width;
1636         _dy = height;
1637         _buf.length = _dx * _dy;
1638         resetClipping();
1639     }
1640 
1641     override void fill(uint color) {
1642         if (hasClipping) {
1643             fillRect(_clipRect, color);
1644             return;
1645         }
1646         int len = _dx * _dy;
1647         uint * p = _buf.ptr;
1648         foreach(i; 0 .. len)
1649             p[i] = color;
1650     }
1651 
1652     override DrawBuf transformColors(ref ColorTransform transform) {
1653         if (transform.empty)
1654             return this;
1655         bool skipFrame = hasNinePatch;
1656         ColorDrawBuf res = new ColorDrawBuf(_dx, _dy);
1657         if (hasNinePatch) {
1658             NinePatch * p = new NinePatch;
1659             *p = *_ninePatch;
1660             res.ninePatch = p;
1661         }
1662         foreach(int y; 0 .. _dy) {
1663             uint * srcline = scanLine(y);
1664             uint * dstline = res.scanLine(y);
1665             bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1);
1666             foreach(int x; 0 .. _dx) {
1667                 bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1);
1668                 if (!allowTransformX || !allowTransformY)
1669                     dstline[x] = srcline[x];
1670                 else
1671                     dstline[x] = transform.transform(srcline[x]);
1672             }
1673         }
1674         return res;
1675     }
1676 
1677     /// Apply Gaussian blur on the image
1678     void blur(uint blurSize) {
1679         if (blurSize == 0)
1680             return; // trivial case
1681 
1682         // utility functions to get and set color
1683         float[4] get(uint[] buf, uint x, uint y) {
1684             uint c = buf[x + y * _dx];
1685             float a = 255 - (c >> 24);
1686             float r = (c >> 16) & 0xFF;
1687             float g = (c >>  8) & 0xFF;
1688             float b = (c >>  0) & 0xFF;
1689             return [r, g, b, a];
1690         }
1691         void set(uint[] buf, uint x, uint y, float[4] c) {
1692             buf[x + y * _dx] = makeRGBA(c[0], c[1], c[2], 255 - c[3]);
1693         }
1694 
1695 
1696         import std.algorithm : max, min;
1697         import std.math;
1698 
1699         // Gaussian function
1700         float weight(in float x, in float sigma) pure nothrow {
1701             enum inv_sqrt_2pi = 1 / sqrt(2 * PI);
1702             return exp(- x ^^ 2 / (2 * sigma ^^ 2)) * inv_sqrt_2pi / sigma;
1703         }
1704 
1705         void blurOneDimension(uint[] bufIn, uint[] bufOut, uint blurSize, bool horizontally) {
1706 
1707             float sigma = blurSize > 2 ? blurSize / 3.0 : blurSize / 2.0;
1708 
1709             foreach (x; 0 .. _dx) {
1710                 foreach (y; 0 .. _dy) {
1711                     float[4] c;
1712                     c[] = 0;
1713 
1714                     float sum = 0;
1715                     foreach (int i; 1 .. blurSize + 1) {
1716                         float[4] c1 = get(bufIn, 
1717                             horizontally ? min(x + i, _dx - 1) : x, 
1718                             horizontally ? y : min(y + i, _dy - 1)
1719                         );
1720                         float[4] c2 = get(bufIn, 
1721                             horizontally ? max(x - i, 0) : x, 
1722                             horizontally ? y : max(y - i, 0)
1723                         );
1724                         float w = weight(i, sigma);
1725                         c[] += (c1[] + c2[]) * w;
1726                         sum += 2 * w;
1727                     }
1728                     c[] += get(bufIn, x, y)[] * (1 - sum);
1729                     set(bufOut, x, y, c);
1730                 }
1731             }
1732         }
1733         // intermediate buffer for image
1734         uint[] tmpbuf;
1735         tmpbuf.length = _buf.length;
1736         // do horizontal blur
1737         blurOneDimension(_buf[], tmpbuf, blurSize, true);
1738         // then do vertical blur
1739         blurOneDimension(tmpbuf, _buf[], blurSize, false);
1740     }
1741 }
1742 
1743 
1744 // line clipping algorithm from https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
1745 private alias OutCode = int;
1746 
1747 private const int INSIDE = 0; // 0000
1748 private const int LEFT = 1;   // 0001
1749 private const int RIGHT = 2;  // 0010
1750 private const int BOTTOM = 4; // 0100
1751 private const int TOP = 8;    // 1000
1752 
1753 // Compute the bit code for a point (x, y) using the clip rectangle
1754 // bounded diagonally by (xmin, ymin), and (xmax, ymax)
1755 
1756 // ASSUME THAT xmax, xmin, ymax and ymin are global constants.
1757 
1758 private OutCode ComputeOutCode(Rect clipRect, double x, double y)
1759 {
1760     OutCode code;
1761 
1762     code = INSIDE;          // initialised as being inside of clip window
1763 
1764     if (x < clipRect.left)           // to the left of clip window
1765         code |= LEFT;
1766     else if (x > clipRect.right)      // to the right of clip window
1767         code |= RIGHT;
1768     if (y < clipRect.top)           // below the clip window
1769         code |= BOTTOM;
1770     else if (y > clipRect.bottom)      // above the clip window
1771         code |= TOP;
1772 
1773     return code;
1774 }
1775 
1776 package bool clipLine(ref Rect clipRect, ref Point p1, ref Point p2) {
1777     double x0 = p1.x;
1778     double y0 = p1.y;
1779     double x1 = p2.x;
1780     double y1 = p2.y;
1781     bool res = CohenSutherlandLineClipAndDraw(clipRect, x0, y0, x1, y1);
1782     if (res) {
1783         p1.x = cast(int)x0;
1784         p1.y = cast(int)y0;
1785         p2.x = cast(int)x1;
1786         p2.y = cast(int)y1;
1787     }
1788     return res;
1789 }
1790 
1791 // Cohen–Sutherland clipping algorithm clips a line from
1792 // P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
1793 // diagonal from (xmin, ymin) to (xmax, ymax).
1794 private bool CohenSutherlandLineClipAndDraw(ref Rect clipRect, ref double x0, ref double y0, ref double x1, ref double y1)
1795 {
1796     // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
1797     OutCode outcode0 = ComputeOutCode(clipRect, x0, y0);
1798     OutCode outcode1 = ComputeOutCode(clipRect, x1, y1);
1799     bool accept = false;
1800 
1801     while (true) {
1802         if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop
1803             accept = true;
1804             break;
1805         } else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop
1806             break;
1807         } else {
1808             // failed both tests, so calculate the line segment to clip
1809             // from an outside point to an intersection with clip edge
1810             double x, y;
1811 
1812             // At least one endpoint is outside the clip rectangle; pick it.
1813             OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
1814 
1815             // Now find the intersection point;
1816             // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
1817             if (outcodeOut & TOP) {           // point is above the clip rectangle
1818                 x = x0 + (x1 - x0) * (clipRect.bottom - y0) / (y1 - y0);
1819                 y = clipRect.bottom;
1820             } else if (outcodeOut & BOTTOM) { // point is below the clip rectangle
1821                 x = x0 + (x1 - x0) * (clipRect.top - y0) / (y1 - y0);
1822                 y = clipRect.top;
1823             } else if (outcodeOut & RIGHT) {  // point is to the right of clip rectangle
1824                 y = y0 + (y1 - y0) * (clipRect.right - x0) / (x1 - x0);
1825                 x = clipRect.right;
1826             } else if (outcodeOut & LEFT) {   // point is to the left of clip rectangle
1827                 y = y0 + (y1 - y0) * (clipRect.left - x0) / (x1 - x0);
1828                 x = clipRect.left;
1829             }
1830 
1831             // Now we move outside point to intersection point to clip
1832             // and get ready for next pass.
1833             if (outcodeOut == outcode0) {
1834                 x0 = x;
1835                 y0 = y;
1836                 outcode0 = ComputeOutCode(clipRect, x0, y0);
1837             } else {
1838                 x1 = x;
1839                 y1 = y;
1840                 outcode1 = ComputeOutCode(clipRect, x1, y1);
1841             }
1842         }
1843     }
1844     return accept;
1845 }