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 solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted
292     void fillRectPattern(Rect rc, uint color, int pattern) {
293         // default implementation: does not support patterns
294         fillRect(rc, color);
295     }
296     /// draw pixel at (x, y) with specified color 
297     abstract void drawPixel(int x, int y, uint color);
298     /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
299     abstract void drawGlyph(int x, int y, Glyph * glyph, uint color);
300     /// draw source buffer rectangle contents to destination buffer
301     abstract void drawFragment(int x, int y, DrawBuf src, Rect srcrect);
302     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
303     abstract void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect);
304     /// draw unscaled image at specified coordinates
305     void drawImage(int x, int y, DrawBuf src) {
306         drawFragment(x, y, src, Rect(0, 0, src.width, src.height));
307     }
308     /// draws rectangle frame of specified color and widths (per side), and optinally fills inner area
309     void drawFrame(Rect rc, uint frameColor, Rect frameSideWidths, uint innerAreaColor = 0xFFFFFFFF) {
310         // draw frame
311         if (!isFullyTransparentColor(frameColor)) {
312             Rect r;
313             // left side
314             r = rc;
315             r.right = r.left + frameSideWidths.left;
316             if (!r.empty)
317                 fillRect(r, frameColor);
318             // right side
319             r = rc;
320             r.left = r.right - frameSideWidths.right;
321             if (!r.empty)
322                 fillRect(r, frameColor);
323             // top side
324             r = rc;
325             r.left += frameSideWidths.left;
326             r.right -= frameSideWidths.right;
327             Rect rc2 = r;
328             rc2.bottom = r.top + frameSideWidths.top;
329             if (!rc2.empty)
330                 fillRect(rc2, frameColor);
331             // bottom side
332             rc2 = r;
333             rc2.top = r.bottom - frameSideWidths.bottom;
334             if (!rc2.empty)
335                 fillRect(rc2, frameColor);
336         }
337         // draw internal area
338         if (!isFullyTransparentColor(innerAreaColor)) {
339             rc.left += frameSideWidths.left;
340             rc.top += frameSideWidths.top;
341             rc.right -= frameSideWidths.right;
342             rc.bottom -= frameSideWidths.bottom;
343             if (!rc.empty)
344                 fillRect(rc, innerAreaColor);
345         }
346     }
347 
348     /// draw focus rectangle; vertical gradient supported - colors[0] is top color, colors[1] is bottom color
349     void drawFocusRect(Rect rc, const uint[] colors) {
350         // override for faster performance when using OpenGL
351         if (colors.length < 1)
352             return;
353         uint color1 = colors[0];
354         uint color2 = colors.length > 1 ? colors[1] : color1;
355         if (isFullyTransparentColor(color1) && isFullyTransparentColor(color2))
356             return;
357         // draw horizontal lines
358         foreach(int x; rc.left .. rc.right) {
359             if ((x ^ rc.top) & 1)
360                 fillRect(Rect(x, rc.top, x + 1, rc.top + 1), color1);
361             if ((x ^ (rc.bottom - 1)) & 1)
362                 fillRect(Rect(x, rc.bottom - 1, x + 1, rc.bottom), color2);
363         }
364         // draw vertical lines
365         foreach(int y; rc.top + 1 .. rc.bottom - 1) {
366             uint color = color1 == color2 ? color1 : blendARGB(color2, color1, 255 / (rc.bottom - rc.top));
367             if ((y ^ rc.left) & 1)
368                 fillRect(Rect(rc.left, y, rc.left + 1, y + 1), color);
369             if ((y ^ (rc.right - 1)) & 1)
370                 fillRect(Rect(rc.right - 1, y, rc.right, y + 1), color);
371         }
372     }
373 
374     /// draw filled triangle in float coordinates; clipping is already applied
375     protected void fillTriangleFClipped(PointF p1, PointF p2, PointF p3, uint colour) {
376         // override and implement it
377     }
378 
379     /// find intersection of line p1..p2 with clip rectangle
380     protected bool intersectClipF(ref PointF p1, ref PointF p2, ref bool p1moved, ref bool p2moved) {
381         if (p1.x < _clipRect.left && p2.x < _clipRect.left)
382             return true;
383         if (p1.x >= _clipRect.right && p2.x >= _clipRect.right)
384             return true;
385         if (p1.y < _clipRect.top && p2.y < _clipRect.top)
386             return true;
387         if (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom)
388             return true;
389         // horizontal clip
390         if (p1.x < _clipRect.left && p2.x >= _clipRect.left) {
391             // move p1 to clip left
392             p1 += (p2 - p1) * ((_clipRect.left - p1.x) / (p2.x - p1.x));
393             p1moved = true;
394         }
395         if (p2.x < _clipRect.left && p1.x >= _clipRect.left) {
396             // move p2 to clip left
397             p2 += (p1 - p2) * ((_clipRect.left - p2.x) / (p1.x - p2.x));
398             p2moved = true;
399         }
400         if (p1.x > _clipRect.right && p2.x < _clipRect.right) {
401             // move p1 to clip right
402             p1 += (p2 - p1) * ((_clipRect.right - p1.x) / (p2.x - p1.x));
403             p1moved = true;
404         }
405         if (p2.x > _clipRect.right && p1.x < _clipRect.right) {
406             // move p1 to clip right
407             p2 += (p1 - p2) * ((_clipRect.right - p2.x) / (p1.x - p2.x));
408             p2moved = true;
409         }
410         // vertical clip
411         if (p1.y < _clipRect.top && p2.y >= _clipRect.top) {
412             // move p1 to clip left
413             p1 += (p2 - p1) * ((_clipRect.top - p1.y) / (p2.y - p1.y));
414             p1moved = true;
415         }
416         if (p2.y < _clipRect.top && p1.y >= _clipRect.top) {
417             // move p2 to clip left
418             p2 += (p1 - p2) * ((_clipRect.top - p2.y) / (p1.y - p2.y));
419             p2moved = true;
420         }
421         if (p1.y > _clipRect.bottom && p2.y < _clipRect.bottom) {
422             // move p1 to clip right             <0              <0
423             p1 += (p2 - p1) * ((_clipRect.bottom - p1.y) / (p2.y - p1.y));
424             p1moved = true;
425         }
426         if (p2.y > _clipRect.bottom && p1.y < _clipRect.bottom) {
427             // move p1 to clip right
428             p2 += (p1 - p2) * ((_clipRect.bottom - p2.y) / (p1.y - p2.y));
429             p2moved = true;
430         }
431         return false;
432     }
433 
434     /// draw filled triangle in float coordinates
435     void fillTriangleF(PointF p1, PointF p2, PointF p3, uint colour) {
436         if (_clipRect.empty) // clip rectangle is empty - all drawables are clipped out
437             return;
438         // apply clipping
439         bool p1insideClip = (p1.x >= _clipRect.left && p1.x < _clipRect.right && p1.y >= _clipRect.top && p1.y < _clipRect.bottom);
440         bool p2insideClip = (p2.x >= _clipRect.left && p2.x < _clipRect.right && p2.y >= _clipRect.top && p2.y < _clipRect.bottom);
441         bool p3insideClip = (p3.x >= _clipRect.left && p3.x < _clipRect.right && p3.y >= _clipRect.top && p3.y < _clipRect.bottom);
442         if (p1insideClip && p2insideClip && p3insideClip) {
443             // all points inside clipping area - no clipping required
444             fillTriangleFClipped(p1, p2, p3, colour);
445             return;
446         }
447         // do triangle clipping
448         // check if all points outside the same bound
449         if ((p1.x < _clipRect.left && p2.x < _clipRect.left && p3.x < _clipRect.left)
450             || (p1.x >= _clipRect.right && p2.x >= _clipRect.right && p3.x >= _clipRect.bottom)
451             || (p1.y < _clipRect.top && p2.y < _clipRect.top && p3.y < _clipRect.top)
452             || (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom && p3.y >= _clipRect.bottom))
453             return;
454         /++
455          +                   side 1
456          +  p1-------p11------------p21--------------p2
457          +   \                                       /
458          +    \                                     /
459          +     \                                   /
460          +      \                                 /
461          +    p13\                               /p22
462          +        \                             /
463          +         \                           /
464          +          \                         /
465          +           \                       /  side 2
466          +    side 3  \                     /
467          +             \                   /
468          +              \                 /
469          +               \               /p32
470          +             p33\             /
471          +                 \           /
472          +                  \         /
473          +                   \       /
474          +                    \     /
475          +                     \   /
476          +                      \ /
477          +                      p3
478          +/
479         PointF p11 = p1;
480         PointF p13 = p1;
481         PointF p21 = p2;
482         PointF p22 = p2;
483         PointF p32 = p3;
484         PointF p33 = p3;
485         bool p1moved = false;
486         bool p2moved = false;
487         bool p3moved = false;
488         bool side1clipped = intersectClipF(p11, p21, p1moved, p2moved);
489         bool side2clipped = intersectClipF(p22, p32, p2moved, p3moved);
490         bool side3clipped = intersectClipF(p33, p13, p3moved, p1moved);
491         if (!p1moved && !p2moved && !p3moved) {
492             // no moved - no clipping
493             fillTriangleFClipped(p1, p2, p3, colour);
494         } else if (p1moved && !p2moved && !p3moved) {
495             fillTriangleFClipped(p11, p2, p3, colour);
496             fillTriangleFClipped(p3, p13, p11, colour);
497         } else if (!p1moved && p2moved && !p3moved) {
498             fillTriangleFClipped(p22, p3, p1, colour);
499             fillTriangleFClipped(p1, p21, p22, colour);
500         } else if (!p1moved && !p2moved && p3moved) {
501             fillTriangleFClipped(p33, p1, p2, colour);
502             fillTriangleFClipped(p2, p32, p33, colour);
503         } else if (p1moved && p2moved && !p3moved) {
504             if (!side1clipped) {
505                 fillTriangleFClipped(p13, p11, p21, colour);
506                 fillTriangleFClipped(p21, p22, p13, colour);
507             }
508             fillTriangleFClipped(p22, p3, p13, colour);
509         } else if (!p1moved && p2moved && p3moved) {
510             if (!side2clipped) {
511                 fillTriangleFClipped(p21, p22, p32, colour);
512                 fillTriangleFClipped(p32, p33, p21, colour);
513             }
514             fillTriangleFClipped(p21, p33, p1, colour);
515         } else if (p1moved && !p2moved && p3moved) {
516             if (!side3clipped) {
517                 fillTriangleFClipped(p13, p11, p32, colour);
518                 fillTriangleFClipped(p32, p33, p13, colour);
519             }
520             fillTriangleFClipped(p11, p2, p32, colour);
521         } else if (p1moved && p2moved && p3moved) {
522             if (side1clipped) {
523                 fillTriangleFClipped(p13, p22, p32, colour);
524                 fillTriangleFClipped(p32, p33, p13, colour);
525             } else if (side2clipped) {
526                 fillTriangleFClipped(p11, p21, p33, colour);
527                 fillTriangleFClipped(p33, p13, p11, colour);
528             } else if (side3clipped) {
529                 fillTriangleFClipped(p11, p21, p22, colour);
530                 fillTriangleFClipped(p22, p32, p11, colour);
531             } else {
532                 fillTriangleFClipped(p13, p11, p21, colour);
533                 fillTriangleFClipped(p21, p22, p13, colour);
534                 fillTriangleFClipped(p22, p32, p33, colour);
535                 fillTriangleFClipped(p33, p13, p22, colour);
536             }
537         }
538     }
539 
540     /// draw filled quad in float coordinates
541     void fillQuadF(PointF p1, PointF p2, PointF p3, PointF p4, uint colour) {
542         fillTriangleF(p1, p2, p3, colour);
543         fillTriangleF(p3, p4, p1, colour);
544     }
545 
546     /// draw line of arbitrary width in float coordinates
547     void drawLineF(PointF p1, PointF p2, float width, uint colour) {
548         // direction vector
549         PointF v = (p2 - p1).normalized;
550         // calculate normal vector
551         // calculate normal vector : rotate CCW 90 degrees
552         PointF n = v.rotated90ccw();
553         // rotate CCW 90 degrees
554         n.y = v.x;
555         n.x = -v.y;
556         // offset by normal * half_width
557         n *= width / 2;
558         // draw line using quad
559         fillQuadF(p1 - n, p2 - n, p2 + n, p1 + n, colour);
560     }
561 
562     // find intersection point for two vectors with start points p1, p2 and normalized directions dir1, dir2
563     protected static PointF intersectVectors(PointF p1, PointF dir1, PointF p2, PointF dir2) {
564         /*
565         L1 = P1 + a * V1
566         L2 = P2 + b * V2
567         P1 + a * V1 = P2 + b * V2
568         a * V1 = (P2 - P1) + b * V2
569         a * (V1 X V2) = (P2 - P1) X V2
570         a = (P2 - P1) * V2 / (V1*V2)
571         return P1 + a * V1
572         */
573         // just return middle point
574         PointF p2p1 = (p2 - p1); //.normalized;
575         float d1 = p2p1.crossProduct(dir2);
576         float d2 = dir1.crossProduct(dir2);
577         // a * d1 = d2
578         if (d2 >= -0.1f && d2 <= 0.1f) {
579             return p1; //PointF((p1.x + p2.x)/2, (p1.y + p2.y)/2);
580         }
581         float a = d1 / d2;
582         return p1 + dir1 * a;
583     }
584 
585     protected void calcLineSegmentQuad(PointF p0, PointF p1, PointF p2, PointF p3, float width, ref PointF[4] quad) {
586         // direction vector
587         PointF v = (p2 - p1).normalized;
588         // calculate normal vector : rotate CCW 90 degrees
589         PointF n = v.rotated90ccw();
590         // offset by normal * half_width
591         n *= width / 2;
592         // draw line using quad
593         PointF pp10 = p1 - n;
594         PointF pp20 = p2 - n;
595         PointF pp11 = p1 + n;
596         PointF pp21 = p2 + n;
597         if ((p1 - p0).length > 0.1f) {
598             // has prev segment
599             PointF prevv = (p1 - p0).normalized;
600             PointF prevn = prevv.rotated90ccw();
601             PointF prev10 = p1 - prevn * width / 2;
602             PointF prev11 = p1 + prevn * width / 2;
603             PointF intersect0 = intersectVectors(pp10, -v, prev10, prevv);
604             PointF intersect1 = intersectVectors(pp11, -v, prev11, prevv);
605             pp10 = intersect0;
606             pp11 = intersect1;
607         }
608         if ((p3 - p2).length > 0.1f) {
609             // has next segment
610             PointF nextv = (p3 - p2).normalized;
611             PointF nextn = nextv.rotated90ccw();
612             PointF next20 = p2 - nextn * width / 2;
613             PointF next21 = p2 + nextn * width / 2;
614             PointF intersect0 = intersectVectors(pp20, v, next20, -nextv);
615             PointF intersect1 = intersectVectors(pp21, v, next21, -nextv);
616             pp20 = intersect0;
617             pp21 = intersect1;
618         }
619         quad[0] = pp10;
620         quad[1] = pp20;
621         quad[2] = pp21;
622         quad[3] = pp11;
623     }
624     /// draw line of arbitrary width in float coordinates p1..p2 with angle based on previous (p0..p1) and next (p2..p3) segments
625     void drawLineSegmentF(PointF p0, PointF p1, PointF p2, PointF p3, float width, uint colour) {
626         PointF[4] quad;
627         calcLineSegmentQuad(p0, p1, p2, p3, width, quad);
628         fillQuadF(quad[0], quad[1], quad[2], quad[3], colour);
629     }
630 
631     /// draw poly line of arbitrary width in float coordinates; when cycled is true, connect first and last point (optionally fill inner area)
632     void polyLineF(PointF[] points, float width, uint colour, bool cycled = false, uint innerAreaColour = COLOR_TRANSPARENT) {
633         if (points.length < 2)
634             return;
635         bool hasInnerArea = !isFullyTransparentColor(innerAreaColour);
636         if (isFullyTransparentColor(colour)) {
637             if (hasInnerArea)
638                 fillPolyF(points, innerAreaColour);
639             return;
640         }
641         int len = cast(int)points.length;
642         if (hasInnerArea) {
643             PointF[] innerArea;
644             innerArea.assumeSafeAppend;
645             //Log.d("fill poly inner: ", points);
646             for(int i = 0; i < len; i++) {
647                 PointF[4] quad;
648                 int index0 = i - 1;
649                 int index1 = i;
650                 int index2 = i + 1;
651                 int index3 = i + 2;
652                 if (index0 < 0)
653                     index0 = cycled ? len - 1 : 0;
654                 index2 %= len; // only can be if cycled
655                 index3 %= len; // only can be if cycled
656                 if (!cycled) {
657                     if (index1 == len - 1) {
658                         index0 = index1;
659                         index2 = 0;
660                         index3 = 0;
661                     } else if (index1 == len - 2) {
662                         index2 = len - 1;
663                         index3 = len - 1;
664                     }
665                 }
666                 //Log.d("lineSegment - inner ", index0, ", ", index1, ", ", index2, ", ", index3);
667                 calcLineSegmentQuad(points[index0], points[index1], points[index2], points[index3], width, quad);
668                 innerArea ~= quad[3];
669             }
670             fillPolyF(innerArea, innerAreaColour);
671         }
672         if (!isFullyTransparentColor(colour)) {
673             for(int i = 0; i < len; i++) {
674                 int index0 = i - 1;
675                 int index1 = i;
676                 int index2 = i + 1;
677                 int index3 = i + 2;
678                 if (index0 < 0)
679                     index0 = cycled ? len - 1 : 0;
680                 index2 %= len; // only can be if cycled
681                 index3 %= len; // only can be if cycled
682                 if (!cycled) {
683                     if (index1 == len - 1) {
684                         index0 = index1;
685                         index2 = 0;
686                         index3 = 0;
687                     } else if (index1 == len - 2) {
688                         index2 = len - 1;
689                         index3 = len - 1;
690                     }
691                 }
692                 //Log.d("lineSegment - outer ", index0, ", ", index1, ", ", index2, ", ", index3);
693                 if (cycled || i + 1 < len)
694                     drawLineSegmentF(points[index0], points[index1], points[index2], points[index3], width, colour);
695             }
696         }
697     }
698 
699     /// draw filled polyline (vertexes must be in clockwise order)
700     void fillPolyF(PointF[] points, uint colour) {
701         if (points.length < 3) {
702             return;
703         }
704         if (points.length == 3) {
705             fillTriangleF(points[0], points[1], points[2], colour);
706             return;
707         }
708         PointF[] list = points.dup;
709         bool moved;
710         while (list.length > 3) {
711             moved = false;
712             for (int i = 0; i < list.length; i++) {
713                 PointF p1 = list[i + 0];
714                 PointF p2 = list[(i + 1) % list.length];
715                 PointF p3 = list[(i + 2) % list.length];
716                 float cross = (p2 - p1).crossProduct(p3 - p2);
717                 if (cross > 0) {
718                     // draw triangle
719                     fillTriangleF(p1, p2, p3, colour);
720                     int indexToRemove = (i + 1) % (cast(int)list.length);
721                     // remove triangle from poly
722                     for (int j = indexToRemove; j + 1 < list.length; j++)
723                         list[j] = list[j + 1];
724                     list.length = list.length - 1;
725                     i += 2;
726                     moved = true;
727                 }
728             }
729             if (list.length == 3) {
730                 fillTriangleF(list[0], list[1], list[2], colour);
731                 break;
732             }
733             if (!moved)
734                 break;
735         }
736     }
737 
738     /// draw ellipse or filled ellipse
739     void drawEllipseF(float centerX, float centerY, float xRadius, float yRadius, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) {
740         import std.math : sin, cos, PI;
741         if (xRadius < 0)
742             xRadius = -xRadius;
743         if (yRadius < 0)
744             yRadius = -yRadius;
745         int numLines = cast(int)((xRadius + yRadius) / 5);
746         if (numLines < 4)
747             numLines = 4;
748         float step = PI * 2 / numLines;
749         float angle = 0;
750         PointF[] points;
751         points.assumeSafeAppend;
752         for (int i = 0; i < numLines; i++) {
753             float x = centerX + cos(angle) * xRadius;
754             float y = centerY + sin(angle) * yRadius;
755             angle += step;
756             points ~= PointF(x, y);
757         }
758         polyLineF(points, lineWidth, lineColor, true, fillColor);
759     }
760 
761     /// draw ellipse arc or filled ellipse arc
762     void drawEllipseArcF(float centerX, float centerY, float xRadius, float yRadius, float startAngle, float endAngle, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) {
763         import std.math : sin, cos, PI;
764         if (xRadius < 0)
765             xRadius = -xRadius;
766         if (yRadius < 0)
767             yRadius = -yRadius;
768         startAngle = startAngle * 2 * PI / 360;
769         endAngle = endAngle * 2 * PI / 360;
770         if (endAngle < startAngle)
771             endAngle += 2 * PI;
772         float angleDiff = endAngle - startAngle;
773         if (angleDiff > 2*PI)
774             angleDiff %= 2*PI;
775         int numLines = cast(int)((xRadius + yRadius) / angleDiff);
776         if (numLines < 3)
777             numLines = 4;
778         float step = angleDiff / numLines;
779         float angle = startAngle;
780         PointF[] points;
781         points.assumeSafeAppend;
782         points ~= PointF(centerX, centerY);
783         for (int i = 0; i < numLines; i++) {
784             float x = centerX + cos(angle) * xRadius;
785             float y = centerY + sin(angle) * yRadius;
786             angle += step;
787             points ~= PointF(x, y);
788         }
789         polyLineF(points, lineWidth, lineColor, true, fillColor);
790     }
791 
792     /// draw poly line of width == 1px; when cycled is true, connect first and last point
793     void polyLine(Point[] points, uint colour, bool cycled) {
794         if (points.length < 2)
795             return;
796         for(int i = 0; i + 1 < points.length; i++) {
797             drawLine(points[i], points[i + 1], colour);
798         }
799         if (cycled && points.length > 2)
800             drawLine(points[$ - 1], points[0], colour);
801     }
802 
803     /// draw line from point p1 to p2 with specified color
804     void drawLine(Point p1, Point p2, uint colour) {
805         if (!clipLine(_clipRect, p1, p2))
806             return;
807         // from rosettacode.org
808         import std.math: abs;
809         immutable int dx = p2.x - p1.x;
810         immutable int ix = (dx > 0) - (dx < 0);
811         immutable int dx2 = abs(dx) * 2;
812         int dy = p2.y - p1.y;
813         immutable int iy = (dy > 0) - (dy < 0);
814         immutable int dy2 = abs(dy) * 2;
815         drawPixel(p1.x, p1.y, colour);
816         if (dx2 >= dy2) {
817             int error = dy2 - (dx2 / 2);
818             while (p1.x != p2.x) {
819                 if (error >= 0 && (error || (ix > 0))) {
820                     error -= dx2;
821                     p1.y += iy;
822                 }
823                 error += dy2;
824                 p1.x += ix;
825                 drawPixel(p1.x, p1.y, colour);
826             }
827         } else {
828             int error = dx2 - (dy2 / 2);
829             while (p1.y != p2.y) {
830                 if (error >= 0 && (error || (iy > 0))) {
831                     error -= dy2;
832                     p1.x += ix;
833                 }
834                 error += dx2;
835                 p1.y += iy;
836                 drawPixel(p1.x, p1.y, colour);
837             }
838         }
839     }
840 
841     /// create drawbuf with copy of current buffer with changed colors (returns this if not supported)
842     DrawBuf transformColors(ref ColorTransform transform) {
843         return this;
844     }
845 
846     /// draw custom OpenGL scene
847     void drawCustomOpenGLScene(Rect rc, OpenGLDrawableDelegate handler) {
848         // override it for OpenGL draw buffer
849         Log.w("drawCustomOpenGLScene is called for non-OpenGL DrawBuf");
850     }
851 
852     void clear() {
853         resetClipping();
854     }
855 }
856 
857 alias DrawBufRef = Ref!DrawBuf;
858 
859 /// RAII setting/restoring of clip rectangle
860 struct ClipRectSaver {
861     private DrawBuf _buf;
862     private Rect _oldClipRect;
863     private uint _oldAlpha;
864     /// apply (intersect) new clip rectangle and alpha to draw buf; restore 
865     this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0) {
866         _buf = buf;
867         _oldClipRect = buf.clipRect;
868         _oldAlpha = buf.alpha;
869         buf.intersectClipRect(newClipRect);
870         if (newAlpha)
871             buf.addAlpha(newAlpha);
872     }
873     ~this() {
874         _buf.clipRect = _oldClipRect;
875         _buf.alpha = _oldAlpha;
876     }
877 }
878 
879 class ColorDrawBufBase : DrawBuf {
880     int _dx;
881     int _dy;
882     /// returns buffer bits per pixel
883     override @property int bpp() { return 32; }
884     @property override int width() { return _dx; }
885     @property override int height() { return _dy; }
886 
887     /// returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory
888     uint * scanLine(int y) { return null; }
889 
890     /// draw source buffer rectangle contents to destination buffer
891     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
892         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
893         if (applyClipping(dstrect, srcrect)) {
894             if (src.applyClipping(srcrect, dstrect)) {
895                 int dx = srcrect.width;
896                 int dy = srcrect.height;
897                 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
898                 if (colorDrawBuf !is null) {
899                     foreach(yy; 0 .. dy) {
900                         uint * srcrow = colorDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
901                         uint * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
902                         if (!_alpha) {
903                             // simplified version - no alpha blending
904                             foreach(i; 0 .. dx) {
905                                 uint pixel = srcrow[i];
906                                 uint alpha = pixel >> 24;
907                                 if (!alpha)
908                                     dstrow[i] = pixel;
909                                 else if (alpha < 254) {
910                                     // apply blending
911                                     dstrow[i] = blendARGB(dstrow[i], pixel, alpha);
912                                 }
913                             }
914                         } else {
915                             // combine two alphas
916                             foreach(i; 0 .. dx) {
917                                 uint pixel = srcrow[i];
918                                 uint alpha = blendAlpha(_alpha, pixel >> 24);
919                                 if (!alpha)
920                                     dstrow[i] = pixel;
921                                 else if (alpha < 254) {
922                                     // apply blending
923                                     dstrow[i] = blendARGB(dstrow[i], pixel, alpha);
924                                 }
925                             }
926                         }
927 
928                     }
929                 }
930             }
931         }
932     }
933 
934 	import std.container.array;
935 
936     /// Create mapping of source coordinates to destination coordinates, for resize.
937     private Array!int createMap(int dst0, int dst1, int src0, int src1, double k) {
938         int dd = dst1 - dst0;
939         //int sd = src1 - src0;
940 		Array!int res;
941 		res.length = dd;
942         foreach(int i; 0 .. dd)
943             res[i] = src0 + cast(int)(i * k);//sd / dd;
944         return res;
945     }
946 
947     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
948     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
949         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
950         if (_alpha >= 254)
951             return; // fully transparent - don't draw
952 		double kx = cast(double)srcrect.width / dstrect.width;
953 		double ky = cast(double)srcrect.height / dstrect.height;
954         if (applyClipping(dstrect, srcrect)) {
955             auto xmapArray = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right, kx);
956             auto ymapArray = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom, ky);
957 
958             int * xmap = &xmapArray[0];
959             int * ymap = &ymapArray[0];
960             int dx = dstrect.width;
961             int dy = dstrect.height;
962             ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
963             if (colorDrawBuf !is null) {
964                 foreach(y; 0 .. dy) {
965                     uint * srcrow = colorDrawBuf.scanLine(ymap[y]);
966                     uint * dstrow = scanLine(dstrect.top + y) + dstrect.left;
967                     if (!_alpha) {
968                         // simplified alpha calculation
969                         foreach(x; 0 .. dx) {
970                             uint srcpixel = srcrow[xmap[x]];
971                             uint dstpixel = dstrow[x];
972                             uint alpha = srcpixel >> 24;
973                             if (!alpha)
974                                 dstrow[x] = srcpixel;
975                             else if (alpha < 255) {
976                                 // apply blending
977                                 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha);
978                             }
979                         }
980                     } else {
981                         // blending two alphas
982                         foreach(x; 0 .. dx) {
983                             uint srcpixel = srcrow[xmap[x]];
984                             uint dstpixel = dstrow[x];
985                             uint srca = srcpixel >> 24;
986                             uint alpha = !srca ? _alpha : blendAlpha(_alpha, srca);
987                             if (!alpha)
988                                 dstrow[x] = srcpixel;
989                             else if (alpha < 255) {
990                                 // apply blending
991                                 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha);
992                             }
993                         }
994                     }
995                 }
996             }
997         }
998     }
999 
1000     /// detect position of black pixels in row for 9-patch markup
1001     private bool detectHLine(int y, ref int x0, ref int x1) {
1002         uint * line = scanLine(y);
1003         bool foundUsed = false;
1004         x0 = 0;
1005         x1 = 0;
1006         foreach(int x; 1 .. _dx - 1) {
1007             if (isBlackPixel(line[x])) { // opaque black pixel
1008                 if (!foundUsed) {
1009                     x0 = x;
1010                     foundUsed = true;
1011                 }
1012                 x1 = x + 1;
1013             }
1014         }
1015         return x1 > x0;
1016     }
1017 
1018     static bool isBlackPixel(uint c) {
1019         if (((c >> 24) & 255) > 10)
1020             return false;
1021         if (((c >> 16) & 255) > 10)
1022             return false;
1023         if (((c >> 8) & 255) > 10)
1024             return false;
1025         if (((c >> 0) & 255) > 10)
1026             return false;
1027         return true;
1028     }
1029     
1030     /// detect position of black pixels in column for 9-patch markup
1031     private bool detectVLine(int x, ref int y0, ref int y1) {
1032         bool foundUsed = false;
1033         y0 = 0;
1034         y1 = 0;
1035         foreach(int y; 1 .. _dy - 1) {
1036             uint * line = scanLine(y);
1037             if (isBlackPixel(line[x])) { // opaque black pixel
1038                 if (!foundUsed) {
1039                     y0 = y;
1040                     foundUsed = true;
1041                 }
1042                 y1 = y + 1;
1043             }
1044         }
1045         return y1 > y0;
1046     }
1047     /// detect nine patch using image 1-pixel border (see Android documentation)
1048     override bool detectNinePatch() {
1049         if (_dx < 3 || _dy < 3)
1050             return false; // image is too small
1051         int x00, x01, x10, x11, y00, y01, y10, y11;
1052         bool found = true;
1053         found = found && detectHLine(0, x00, x01);
1054         found = found && detectHLine(_dy - 1, x10, x11);
1055         found = found && detectVLine(0, y00, y01);
1056         found = found && detectVLine(_dx - 1, y10, y11);
1057         if (!found)
1058             return false; // no black pixels on 1-pixel frame
1059         NinePatch * p = new NinePatch();
1060         p.frame.left = x00 - 1;
1061         p.frame.right = _dx - x01 - 1;
1062         p.frame.top = y00 - 1;
1063         p.frame.bottom = _dy - y01 - 1;
1064         p.padding.left = x10 - 1;
1065         p.padding.right = _dx - x11 - 1;
1066         p.padding.top = y10 - 1;
1067         p.padding.bottom = _dy - y11 - 1;
1068         _ninePatch = p;
1069         //Log.d("NinePatch detected: frame=", p.frame, " padding=", p.padding, " left+right=", p.frame.left + p.frame.right, " dx=", _dx);
1070         return true;
1071     }
1072 
1073     override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
1074         ubyte[] src = glyph.glyph;
1075         int srcdx = glyph.blackBoxX;
1076         int srcdy = glyph.blackBoxY;
1077         bool clipping = true; //!_clipRect.empty();
1078         color = applyAlpha(color);
1079         bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None;
1080         foreach(int yy; 0 .. srcdy) {
1081             int liney = y + yy;
1082             if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
1083                 continue;
1084             if (liney < 0 || liney >= _dy)
1085                 continue;
1086             uint * row = scanLine(liney);
1087             ubyte * srcrow = src.ptr + yy * srcdx;
1088             foreach(int xx; 0 .. srcdx) {
1089                 int colx = x + (subpixel ? xx / 3 : xx);
1090                 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
1091                     continue;
1092                 if (colx < 0 || colx >= _dx)
1093                     continue;
1094                 uint alpha2 = (color >> 24);
1095                 uint alpha1 = srcrow[xx] ^ 255;
1096                 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
1097                 if (subpixel) {
1098                     int x0 = xx % 3;
1099                     ubyte * dst = cast(ubyte*)(row + colx);
1100                     ubyte * pcolor = cast(ubyte*)(&color);
1101                     blendSubpixel(dst, pcolor, alpha, x0, glyph.subpixelMode);
1102                 } else {
1103                     uint pixel = row[colx];
1104                     if (alpha < 255) {
1105                         if (!alpha)
1106                             row[colx] = pixel;
1107                         else {
1108                             // apply blending
1109                             row[colx] = blendARGB(pixel, color, alpha);
1110                         }
1111                     }
1112                 }
1113             }
1114         }
1115     }
1116 
1117     void drawGlyphToTexture(int x, int y, Glyph * glyph) {
1118         ubyte[] src = glyph.glyph;
1119         int srcdx = glyph.blackBoxX;
1120         int srcdy = glyph.blackBoxY;
1121         bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None;
1122         foreach(int yy; 0 .. srcdy) {
1123             int liney = y + yy;
1124             uint * row = scanLine(liney);
1125             ubyte * srcrow = src.ptr + yy * srcdx;
1126             int increment = subpixel ? 3 : 1;
1127             for (int xx = 0; xx <= srcdx - increment; xx += increment) {
1128                 int colx = x + (subpixel ? xx / 3 : xx);
1129                 if (subpixel) {
1130                     uint t1 = srcrow[xx];
1131                     uint t2 = srcrow[xx + 1];
1132                     uint t3 = srcrow[xx + 2];
1133                     //uint pixel = ((t2 ^ 0x00) << 24) | ((t1  ^ 0xFF)<< 16) | ((t2 ^ 0xFF) << 8) | (t3 ^ 0xFF);
1134                     uint pixel = ((t2 ^ 0x00) << 24) | 0xFFFFFF;
1135                     row[colx] = pixel;
1136                 } else {
1137                     uint alpha1 = srcrow[xx] ^ 0xFF;
1138                     //uint pixel = (alpha1 << 24) | 0xFFFFFF; //(alpha1 << 16) || (alpha1 << 8) || alpha1;
1139                     //uint pixel = ((alpha1 ^ 0xFF) << 24) | (alpha1 << 16) | (alpha1 << 8) | alpha1;
1140                     uint pixel = ((alpha1 ^ 0xFF) << 24) | 0xFFFFFF;
1141                     row[colx] = pixel;
1142                 }
1143             }
1144         }
1145     }
1146 
1147     override void fillRect(Rect rc, uint color) {
1148         uint alpha = color >> 24;
1149         if (applyClipping(rc)) {
1150             foreach(y; rc.top .. rc.bottom) {
1151                 uint * row = scanLine(y);
1152                 if (!alpha) {
1153                     row[rc.left .. rc.right] = color;
1154                 } else if (alpha < 254) {
1155                     foreach(x; rc.left .. rc.right) {
1156                         // apply blending
1157                         row[x] = blendARGB(row[x], color, alpha);
1158                     }
1159                 }
1160             }
1161         }
1162     }
1163 
1164     /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted
1165     override void fillRectPattern(Rect rc, uint color, int pattern) {
1166         uint alpha = color >> 24;
1167         if (alpha == 255) // fully transparent
1168             return;
1169         if (applyClipping(rc)) {
1170             foreach(y; rc.top .. rc.bottom) {
1171                 uint * row = scanLine(y);
1172                 if (!alpha) {
1173                     if (pattern == 1) {
1174                         foreach(x; rc.left .. rc.right) {
1175                             if ((x ^ y) & 1)
1176                                 row[x] = color;
1177                         }
1178                     } else {
1179                         row[rc.left .. rc.right] = color;
1180                     }
1181                 } else if (alpha < 254) {
1182                     foreach(x; rc.left .. rc.right) {
1183                         // apply blending
1184                         if (pattern != 1 || ((x ^ y) & 1) != 0)
1185                             row[x] = blendARGB(row[x], color, alpha);
1186                     }
1187                 }
1188             }
1189         }
1190     }
1191 
1192     /// draw pixel at (x, y) with specified color 
1193     override void drawPixel(int x, int y, uint color) {
1194         if (!_clipRect.isPointInside(x, y))
1195             return;
1196         color = applyAlpha(color);
1197         uint * row = scanLine(y);
1198         uint alpha = color >> 24;
1199         if (!alpha) {
1200             row[x] = color;
1201         } else if (alpha < 254) {
1202             // apply blending
1203             row[x] = blendARGB(row[x], color, alpha);
1204         }
1205     }
1206 
1207 }
1208 
1209 class GrayDrawBuf : DrawBuf {
1210     int _dx;
1211     int _dy;
1212     /// returns buffer bits per pixel
1213     override @property int bpp() { return 8; }
1214     @property override int width() { return _dx; }
1215     @property override int height() { return _dy; }
1216 
1217     ubyte[] _buf;
1218     this(int width, int height) {
1219         resize(width, height);
1220     }
1221     ubyte * scanLine(int y) {
1222         if (y >= 0 && y < _dy)
1223             return _buf.ptr + _dx * y;
1224         return null;
1225     }
1226     override void resize(int width, int height) {
1227         if (_dx == width && _dy == height)
1228             return;
1229         _dx = width;
1230         _dy = height;
1231         _buf.length = _dx * _dy;
1232         resetClipping();
1233     }
1234     override void fill(uint color) {
1235         if (hasClipping) {
1236             fillRect(_clipRect, color);
1237             return;
1238         }
1239         int len = _dx * _dy;
1240         ubyte * p = _buf.ptr;
1241         ubyte cl = rgbToGray(color);
1242         foreach(i; 0 .. len)
1243             p[i] = cl;
1244     }
1245 
1246     /// draw source buffer rectangle contents to destination buffer
1247     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
1248         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
1249         if (applyClipping(dstrect, srcrect)) {
1250             if (src.applyClipping(srcrect, dstrect)) {
1251                 int dx = srcrect.width;
1252                 int dy = srcrect.height;
1253                 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
1254                 if (grayDrawBuf !is null) {
1255                     foreach(yy; 0 .. dy) {
1256                         ubyte * srcrow = grayDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
1257                         ubyte * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
1258                         foreach(i; 0 .. dx) {
1259                             ubyte pixel = srcrow[i];
1260                             dstrow[i] = pixel;
1261                         }
1262                     }
1263                 }
1264             }
1265         }
1266     }
1267 
1268     /// Create mapping of source coordinates to destination coordinates, for resize.
1269     private int[] createMap(int dst0, int dst1, int src0, int src1) {
1270         int dd = dst1 - dst0;
1271         int sd = src1 - src0;
1272         int[] res = new int[dd];
1273         foreach(int i; 0 .. dd)
1274             res[i] = src0 + i * sd / dd;
1275         return res;
1276     }
1277     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
1278     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
1279         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
1280         if (applyClipping(dstrect, srcrect)) {
1281             int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right);
1282             int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom);
1283             int dx = dstrect.width;
1284             int dy = dstrect.height;
1285             GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
1286             if (grayDrawBuf !is null) {
1287                 foreach(y; 0 .. dy) {
1288                     ubyte * srcrow = grayDrawBuf.scanLine(ymap[y]);
1289                     ubyte * dstrow = scanLine(dstrect.top + y) + dstrect.left;
1290                     foreach(x; 0 .. dx) {
1291                         ubyte srcpixel = srcrow[xmap[x]];
1292                         ubyte dstpixel = dstrow[x];
1293                         dstrow[x] = srcpixel;
1294                     }
1295                 }
1296             }
1297         }
1298     }
1299 
1300     /// detect position of black pixels in row for 9-patch markup
1301     private bool detectHLine(int y, ref int x0, ref int x1) {
1302         ubyte * line = scanLine(y);
1303         bool foundUsed = false;
1304         x0 = 0;
1305         x1 = 0;
1306         foreach(int x; 1 .. _dx - 1) {
1307             if (line[x] == 0x00000000) { // opaque black pixel
1308                 if (!foundUsed) {
1309                     x0 = x;
1310                     foundUsed = true;
1311                 }
1312                 x1 = x + 1;
1313             }
1314         }
1315         return x1 > x0;
1316     }
1317 
1318     /// detect position of black pixels in column for 9-patch markup
1319     private bool detectVLine(int x, ref int y0, ref int y1) {
1320         bool foundUsed = false;
1321         y0 = 0;
1322         y1 = 0;
1323         foreach(int y; 1 .. _dy - 1) {
1324             ubyte * line = scanLine(y);
1325             if (line[x] == 0x00000000) { // opaque black pixel
1326                 if (!foundUsed) {
1327                     y0 = y;
1328                     foundUsed = true;
1329                 }
1330                 y1 = y + 1;
1331             }
1332         }
1333         return y1 > y0;
1334     }
1335     /// detect nine patch using image 1-pixel border (see Android documentation)
1336     override bool detectNinePatch() {
1337         if (_dx < 3 || _dy < 3)
1338             return false; // image is too small
1339         int x00, x01, x10, x11, y00, y01, y10, y11;
1340         bool found = true;
1341         found = found && detectHLine(0, x00, x01);
1342         found = found && detectHLine(_dy - 1, x10, x11);
1343         found = found && detectVLine(0, y00, y01);
1344         found = found && detectVLine(_dx - 1, y10, y11);
1345         if (!found)
1346             return false; // no black pixels on 1-pixel frame
1347         NinePatch * p = new NinePatch();
1348         p.frame.left = x00 - 1;
1349         p.frame.right = _dy - y01 - 1;
1350         p.frame.top = y00 - 1;
1351         p.frame.bottom = _dy - y01 - 1;
1352         p.padding.left = x10 - 1;
1353         p.padding.right = _dy - y11 - 1;
1354         p.padding.top = y10 - 1;
1355         p.padding.bottom = _dy - y11 - 1;
1356         _ninePatch = p;
1357         return true;
1358     }
1359     override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
1360         ubyte[] src = glyph.glyph;
1361         int srcdx = glyph.blackBoxX;
1362         int srcdy = glyph.blackBoxY;
1363         bool clipping = true; //!_clipRect.empty();
1364         foreach(int yy; 0 .. srcdy) {
1365             int liney = y + yy;
1366             if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
1367                 continue;
1368             if (liney < 0 || liney >= _dy)
1369                 continue;
1370             ubyte * row = scanLine(liney);
1371             ubyte * srcrow = src.ptr + yy * srcdx;
1372             foreach(int xx; 0 .. srcdx) {
1373                 int colx = xx + x;
1374                 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
1375                     continue;
1376                 if (colx < 0 || colx >= _dx)
1377                     continue;
1378                 uint alpha1 = srcrow[xx] ^ 255;
1379                 uint alpha2 = (color >> 24);
1380                 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
1381                 uint pixel = row[colx];
1382                 if (!alpha)
1383                     row[colx] = cast(ubyte)pixel;
1384                 else if (alpha < 255) {
1385                     // apply blending
1386                     row[colx] = cast(ubyte)blendARGB(pixel, color, alpha);
1387                 }
1388             }
1389         }
1390     }
1391     override void fillRect(Rect rc, uint color) {
1392         if (applyClipping(rc)) {
1393             uint alpha = color >> 24;
1394             ubyte cl = rgbToGray(color);
1395             foreach(y; rc.top .. rc.bottom) {
1396                 ubyte * row = scanLine(y);
1397                 foreach(x; rc.left .. rc.right) {
1398                     if (!alpha)
1399                         row[x] = cl;
1400                     else if (alpha < 255) {
1401                         // apply blending
1402                         row[x] = blendGray(row[x], cl, alpha);
1403                     }
1404                 }
1405             }
1406         }
1407     }
1408 
1409     /// draw pixel at (x, y) with specified color 
1410     override void drawPixel(int x, int y, uint color) {
1411         if (!_clipRect.isPointInside(x, y))
1412             return;
1413         color = applyAlpha(color);
1414         ubyte cl = rgbToGray(color);
1415         ubyte * row = scanLine(y);
1416         uint alpha = color >> 24;
1417         if (!alpha) {
1418             row[x] = cl;
1419         } else if (alpha < 254) {
1420             // apply blending
1421             row[x] = blendGray(row[x], cl, alpha);
1422         }
1423     }
1424 }
1425 
1426 class ColorDrawBuf : ColorDrawBufBase {
1427     uint[] _buf;
1428     /// create ARGB8888 draw buf of specified width and height
1429     this(int width, int height) {
1430         resize(width, height);
1431     }
1432     /// create copy of ColorDrawBuf
1433     this(ColorDrawBuf v) {
1434         this(v.width, v.height);
1435         //_buf.length = v._buf.length;
1436         foreach(i; 0 .. _buf.length)
1437             _buf[i] = v._buf[i];
1438     }
1439     /// create resized copy of ColorDrawBuf
1440     this(ColorDrawBuf v, int dx, int dy) {
1441         this(dx, dy);
1442         fill(0xFFFFFFFF);
1443         drawRescaled(Rect(0, 0, dx, dy), v, Rect(0, 0, v.width, v.height));
1444     }
1445 
1446     void invertAndPreMultiplyAlpha() {
1447         foreach(ref pixel; _buf) {
1448             uint a = (pixel >> 24) & 0xFF;
1449             uint r = (pixel >> 16) & 0xFF;
1450             uint g = (pixel >> 8) & 0xFF;
1451             uint b = (pixel >> 0) & 0xFF;
1452             a ^= 0xFF;
1453             if (a > 0xFC) {
1454                 r = ((r * a) >> 8) & 0xFF;
1455                 g = ((g * a) >> 8) & 0xFF;
1456                 b = ((b * a) >> 8) & 0xFF;
1457             }
1458             pixel = (a << 24) | (r << 16) | (g << 8) | (b << 0);
1459         }
1460     }
1461 
1462     void invertAlpha() {
1463         foreach(ref pixel; _buf)
1464             pixel ^= 0xFF000000;
1465     }
1466 
1467     void invertByteOrder() {
1468         foreach(ref pixel; _buf) {
1469             pixel = (pixel & 0xFF00FF00) |
1470                 ((pixel & 0xFF0000) >> 16) |
1471                 ((pixel & 0xFF) << 16);
1472         }
1473     }
1474 
1475     // for passing of image to OpenGL texture
1476     void invertAlphaAndByteOrder() {
1477         foreach(ref pixel; _buf) {
1478             pixel = ((pixel & 0xFF00FF00) |
1479                 ((pixel & 0xFF0000) >> 16) |
1480                 ((pixel & 0xFF) << 16));
1481             pixel ^= 0xFF000000;
1482         }
1483     }
1484     override uint * scanLine(int y) {
1485         if (y >= 0 && y < _dy)
1486             return _buf.ptr + _dx * y;
1487         return null;
1488     }
1489     override void resize(int width, int height) {
1490         if (_dx == width && _dy == height)
1491             return;
1492         _dx = width;
1493         _dy = height;
1494         _buf.length = _dx * _dy;
1495         resetClipping();
1496     }
1497     override void fill(uint color) {
1498         if (hasClipping) {
1499             fillRect(_clipRect, color);
1500             return;
1501         }
1502         int len = _dx * _dy;
1503         uint * p = _buf.ptr;
1504         foreach(i; 0 .. len)
1505             p[i] = color;
1506     }
1507     override DrawBuf transformColors(ref ColorTransform transform) {
1508         if (transform.empty)
1509             return this;
1510         bool skipFrame = hasNinePatch;
1511         ColorDrawBuf res = new ColorDrawBuf(_dx, _dy);
1512         if (hasNinePatch) {
1513             NinePatch * p = new NinePatch;
1514             *p = *_ninePatch;
1515             res.ninePatch = p;
1516         }
1517         foreach(int y; 0 .. _dy) {
1518             uint * srcline = scanLine(y);
1519             uint * dstline = res.scanLine(y);
1520             bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1);
1521             foreach(int x; 0 .. _dx) {
1522                 bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1);
1523                 if (!allowTransformX || !allowTransformY)
1524                     dstline[x] = srcline[x];
1525                 else
1526                     dstline[x] = transform.transform(srcline[x]);
1527             }
1528         }
1529         return res;
1530     }
1531 }
1532 
1533 
1534 // line clipping algorithm from https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
1535 private alias OutCode = int;
1536 
1537 private const int INSIDE = 0; // 0000
1538 private const int LEFT = 1;   // 0001
1539 private const int RIGHT = 2;  // 0010
1540 private const int BOTTOM = 4; // 0100
1541 private const int TOP = 8;    // 1000
1542 
1543 // Compute the bit code for a point (x, y) using the clip rectangle
1544 // bounded diagonally by (xmin, ymin), and (xmax, ymax)
1545 
1546 // ASSUME THAT xmax, xmin, ymax and ymin are global constants.
1547 
1548 private OutCode ComputeOutCode(Rect clipRect, double x, double y)
1549 {
1550     OutCode code;
1551 
1552     code = INSIDE;          // initialised as being inside of clip window
1553 
1554     if (x < clipRect.left)           // to the left of clip window
1555         code |= LEFT;
1556     else if (x > clipRect.right)      // to the right of clip window
1557         code |= RIGHT;
1558     if (y < clipRect.top)           // below the clip window
1559         code |= BOTTOM;
1560     else if (y > clipRect.bottom)      // above the clip window
1561         code |= TOP;
1562 
1563     return code;
1564 }
1565 
1566 package bool clipLine(ref Rect clipRect, ref Point p1, ref Point p2) {
1567     double x0 = p1.x;
1568     double y0 = p1.y;
1569     double x1 = p2.x;
1570     double y1 = p2.y;
1571     bool res = CohenSutherlandLineClipAndDraw(clipRect, x0, y0, x1, y1);
1572     if (res) {
1573         p1.x = cast(int)x0;
1574         p1.y = cast(int)y0;
1575         p2.x = cast(int)x1;
1576         p2.y = cast(int)y1;
1577     }
1578     return res;
1579 }
1580 
1581 // Cohen–Sutherland clipping algorithm clips a line from
1582 // P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with 
1583 // diagonal from (xmin, ymin) to (xmax, ymax).
1584 private bool CohenSutherlandLineClipAndDraw(ref Rect clipRect, ref double x0, ref double y0, ref double x1, ref double y1)
1585 {
1586     // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
1587     OutCode outcode0 = ComputeOutCode(clipRect, x0, y0);
1588     OutCode outcode1 = ComputeOutCode(clipRect, x1, y1);
1589     bool accept = false;
1590 
1591     while (true) {
1592         if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop
1593             accept = true;
1594             break;
1595         } else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop
1596             break;
1597         } else {
1598             // failed both tests, so calculate the line segment to clip
1599             // from an outside point to an intersection with clip edge
1600             double x, y;
1601 
1602             // At least one endpoint is outside the clip rectangle; pick it.
1603             OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
1604 
1605             // Now find the intersection point;
1606             // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
1607             if (outcodeOut & TOP) {           // point is above the clip rectangle
1608                 x = x0 + (x1 - x0) * (clipRect.bottom - y0) / (y1 - y0);
1609                 y = clipRect.bottom;
1610             } else if (outcodeOut & BOTTOM) { // point is below the clip rectangle
1611                 x = x0 + (x1 - x0) * (clipRect.top - y0) / (y1 - y0);
1612                 y = clipRect.top;
1613             } else if (outcodeOut & RIGHT) {  // point is to the right of clip rectangle
1614                 y = y0 + (y1 - y0) * (clipRect.right - x0) / (x1 - x0);
1615                 x = clipRect.right;
1616             } else if (outcodeOut & LEFT) {   // point is to the left of clip rectangle
1617                 y = y0 + (y1 - y0) * (clipRect.left - x0) / (x1 - x0);
1618                 x = clipRect.left;
1619             }
1620 
1621             // Now we move outside point to intersection point to clip
1622             // and get ready for next pass.
1623             if (outcodeOut == outcode0) {
1624                 x0 = x;
1625                 y0 = y;
1626                 outcode0 = ComputeOutCode(clipRect, x0, y0);
1627             } else {
1628                 x1 = x;
1629                 y1 = y;
1630                 outcode1 = ComputeOutCode(clipRect, x1, y1);
1631             }
1632         }
1633     }
1634     return accept;
1635 }