1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains drawing buffer implementation.
7 
8 
9 Synopsis:
10 
11 ----
12 import dlangui.graphics.drawbuf;
13 
14 ----
15 
16 Copyright: Vadim Lopatin, 2014
17 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
18 Authors:   $(WEB coolreader.org, Vadim Lopatin)
19 */
20 module dlangui.graphics.drawbuf;
21 
22 public import dlangui.core.types;
23 import dlangui.core.logger;
24 
25 immutable uint COLOR_TRANSFORM_OFFSET_NONE = 0x80808080;
26 immutable uint COLOR_TRANSFORM_MULTIPLY_NONE = 0x40404040;
27 
28 /// blend two RGB pixels using alpha
29 uint blendARGB(uint dst, uint src, uint alpha) {
30     uint dstalpha = dst >> 24;
31     if (dstalpha > 0x80)
32         return src;
33     uint srcr = (src >> 16) & 0xFF;
34     uint srcg = (src >> 8) & 0xFF;
35     uint srcb = (src >> 0) & 0xFF;
36     uint dstr = (dst >> 16) & 0xFF;
37     uint dstg = (dst >> 8) & 0xFF;
38     uint dstb = (dst >> 0) & 0xFF;
39     uint ialpha = 256 - alpha;
40     uint r = ((srcr * ialpha + dstr * alpha) >> 8) & 0xFF;
41     uint g = ((srcg * ialpha + dstg * alpha) >> 8) & 0xFF;
42     uint b = ((srcb * ialpha + dstb * alpha) >> 8) & 0xFF;
43     return (r << 16) | (g << 8) | b;
44 }
45 
46 ubyte rgbToGray(uint color) {
47     uint srcr = (color >> 16) & 0xFF;
48     uint srcg = (color >> 8) & 0xFF;
49     uint srcb = (color >> 0) & 0xFF;
50     return cast(uint)(((srcr + srcg + srcg + srcb) >> 2) & 0xFF);
51 }
52 
53 // todo
54 struct ColorTransformHandler {
55     void init(ref ColorTransform transform) {
56 
57     }
58     uint transform(uint color) {
59         return color;
60     }
61 }
62 
63 uint transformComponent(int src, int addBefore, int multiply, int addAfter) {
64     int add1 = (cast(int)(addBefore << 1)) - 0x100;
65     int add2 = (cast(int)(addAfter << 1)) - 0x100;
66     int mul = cast(int)(multiply << 2);
67     int res = (((src + add1) * mul) >> 8) + add2;
68     if (res < 0)
69         res = 0;
70     else if (res > 255)
71         res = 255;
72     return cast(uint)res;
73 }
74 
75 uint transformRGBA(uint src, uint addBefore, uint multiply, uint addAfter) {
76     uint a = transformComponent(src >> 24, addBefore >> 24, multiply >> 24, addAfter >> 24);
77     uint r = transformComponent((src >> 16) & 0xFF, (addBefore >> 16) & 0xFF, (multiply >> 16) & 0xFF, (addAfter >> 16) & 0xFF);
78     uint g = transformComponent((src >> 8) & 0xFF, (addBefore >> 8) & 0xFF, (multiply >> 8) & 0xFF, (addAfter >> 8) & 0xFF);
79     uint b = transformComponent(src & 0xFF, addBefore & 0xFF, multiply & 0xFF, addAfter & 0xFF);
80     return (a << 24) | (r << 16) | (g << 8) | b;
81 }
82 
83 struct ColorTransform {
84     uint addBefore = COLOR_TRANSFORM_OFFSET_NONE;
85     uint multiply = COLOR_TRANSFORM_MULTIPLY_NONE;
86     uint addAfter = COLOR_TRANSFORM_OFFSET_NONE;
87     @property bool empty() const {
88         return addBefore == COLOR_TRANSFORM_OFFSET_NONE 
89             && multiply == COLOR_TRANSFORM_MULTIPLY_NONE
90             && addAfter == COLOR_TRANSFORM_OFFSET_NONE;
91     }
92     uint transform(uint color) {
93         return transformRGBA(color, addBefore, multiply, addAfter);
94     }
95 }
96 
97 
98 /// blend two RGB pixels using alpha
99 ubyte blendGray(ubyte dst, ubyte src, uint alpha) {
100     uint ialpha = 256 - alpha;
101     return cast(ubyte)(((src * ialpha + dst * alpha) >> 8) & 0xFF);
102 }
103 
104 /**
105  * 9-patch image scaling information (see Android documentation).
106  *
107  * 
108  */
109 struct NinePatch {
110     /// frame (non-scalable) part size for left, top, right, bottom edges.
111     Rect frame;
112     /// padding (distance to content area) for left, top, right, bottom edges.
113     Rect padding;
114 }
115 
116 version (USE_OPENGL) {
117     /// non thread safe
118     private __gshared uint drawBufIdGenerator = 0;
119 }
120 
121 /// drawing buffer - image container which allows to perform some drawing operations
122 class DrawBuf : RefCountedObject {
123     protected Rect _clipRect;
124     protected NinePatch * _ninePatch;
125 
126     version (USE_OPENGL) {
127         protected uint _id;
128         /// unique ID of drawbug instance, for using with hardware accelerated rendering for caching
129         @property uint id() { return _id; }
130     }
131 
132     this() {
133         version (USE_OPENGL) {
134             _id = drawBufIdGenerator++;
135         }
136     }
137     protected void function(uint) _onDestroyCallback;
138     @property void onDestroyCallback(void function(uint) callback) { _onDestroyCallback = callback; }
139     @property void function(uint) onDestroyCallback() { return _onDestroyCallback; }
140 
141     // ===================================================
142     // 9-patch functions (image scaling using 9-patch markup - unscaled frame and scaled middle parts).
143     // See Android documentation for details.
144 
145     /// get nine patch information pointer, null if this is not a nine patch image buffer
146     @property const (NinePatch) * ninePatch() const { return _ninePatch; }
147     /// set nine patch information pointer, null if this is not a nine patch image buffer
148     @property void ninePatch(NinePatch * ninePatch) { _ninePatch = ninePatch; }
149     /// check whether there is nine-patch information available for drawing buffer
150     @property bool hasNinePatch() { return _ninePatch !is null; }
151     /// override to detect nine patch using image 1-pixel border; returns true if 9-patch markup is found in image.
152     bool detectNinePatch() { return false; }
153 
154     /// returns current width
155     @property int width() { return 0; }
156     /// returns current height
157     @property int height() { return 0; }
158 
159     // ===================================================
160     // clipping rectangle functions
161 
162     /// returns clipping rectangle, when clipRect.isEmpty == true -- means no clipping.
163     @property ref Rect clipRect() { return _clipRect; }
164     /// returns clipping rectangle, or (0,0,dx,dy) when no clipping.
165     @property Rect clipOrFullRect() { return _clipRect.empty ? Rect(0,0,width,height) : _clipRect; }
166     /// sets new clipping rectangle, when clipRect.isEmpty == true -- means no clipping.
167     @property void clipRect(const ref Rect rect) { 
168         _clipRect = rect;
169         _clipRect.intersect(Rect(0, 0, width, height));
170     }
171     /// sets new clipping rectangle, when clipRect.isEmpty == true -- means no clipping.
172     @property void intersectClipRect(const ref Rect rect) {
173 		if (_clipRect.empty)
174 			_clipRect = rect;
175 		else
176 			_clipRect.intersect(rect);
177         _clipRect.intersect(Rect(0, 0, width, height));
178     }
179     /// apply clipRect and buffer bounds clipping to rectangle
180     bool applyClipping(ref Rect rc) {
181         if (!_clipRect.empty())
182             rc.intersect(_clipRect);
183         if (rc.left < 0)
184             rc.left = 0;
185         if (rc.top < 0)
186             rc.top = 0;
187         if (rc.right > width)
188             rc.right = width;
189         if (rc.bottom > height)
190             rc.bottom = height;
191         return !rc.empty();
192     }
193     /// apply clipRect and buffer bounds clipping to rectangle; if clippinup applied to first rectangle, reduce second rectangle bounds proportionally.
194     bool applyClipping(ref Rect rc, ref Rect rc2) {
195         if (rc.empty || rc2.empty)
196             return false;
197         if (!_clipRect.empty())
198             if (!rc.intersects(_clipRect))
199                 return false;
200         if (rc.width == rc2.width && rc.height == rc2.height) {
201             // unscaled
202             if (!_clipRect.empty()) {
203                 if (rc.left < _clipRect.left) {
204                     rc2.left += _clipRect.left - rc.left;
205                     rc.left = _clipRect.left;
206                 }
207                 if (rc.top < _clipRect.top) {
208                     rc2.top += _clipRect.top - rc.top;
209                     rc.top = _clipRect.top;
210                 }
211                 if (rc.right > _clipRect.right) {
212                     rc2.right -= rc.right - _clipRect.right;
213                     rc.right = _clipRect.right;
214                 }
215                 if (rc.bottom > _clipRect.bottom) {
216                     rc2.bottom -= rc.bottom - _clipRect.bottom;
217                     rc.bottom = _clipRect.bottom;
218                 }
219             }
220             if (rc.left < 0) {
221                 rc2.left += -rc.left;
222                 rc.left = 0;
223             }
224             if (rc.top < 0) {
225                 rc2.top += -rc.top;
226                 rc.top = 0;
227             }
228             if (rc.right > width) {
229                 rc2.right -= rc.right - width;
230                 rc.right = width;
231             }
232             if (rc.bottom > height) {
233                 rc2.bottom -= rc.bottom - height;
234                 rc.bottom = height;
235             }
236         } else {
237             // scaled
238             int dstdx = rc.width;
239             int dstdy = rc.height;
240             int srcdx = rc2.width;
241             int srcdy = rc2.height;
242             if (!_clipRect.empty()) {
243                 if (rc.left < _clipRect.left) {
244                     rc2.left += (_clipRect.left - rc.left) * srcdx / dstdx;
245                     rc.left = _clipRect.left;
246                 }
247                 if (rc.top < _clipRect.top) {
248                     rc2.top += (_clipRect.top - rc.top) * srcdy / dstdy;
249                     rc.top = _clipRect.top;
250                 }
251                 if (rc.right > _clipRect.right) {
252                     rc2.right -= (rc.right - _clipRect.right) * srcdx / dstdx;
253                     rc.right = _clipRect.right;
254                 }
255                 if (rc.bottom > _clipRect.bottom) {
256                     rc2.bottom -= (rc.bottom - _clipRect.bottom) * srcdy / dstdy;
257                     rc.bottom = _clipRect.bottom;
258                 }
259             }
260             if (rc.left < 0) {
261                 rc2.left -= (rc.left) * srcdx / dstdx;
262                 rc.left = 0;
263             }
264             if (rc.top < 0) {
265                 rc2.top -= (rc.top) * srcdy / dstdy;
266                 rc.top = 0;
267             }
268             if (rc.right > width) {
269                 rc2.right -= (rc.right - width) * srcdx / dstdx;
270                 rc.right = width;
271             }
272             if (rc.bottom > height) {
273                 rc2.bottom -= (rc.bottom - height) * srcdx / dstdx;
274                 rc.bottom = height;
275             }
276         }
277         return !rc.empty() && !rc2.empty();
278     }
279     /// reserved for hardware-accelerated drawing - begins drawing batch
280     void beforeDrawing() { }
281     /// reserved for hardware-accelerated drawing - ends drawing batch
282     void afterDrawing() { }
283     /// returns buffer bits per pixel
284     @property int bpp() { return 0; }
285     // returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory
286     //uint * scanLine(int y) { return null; }
287     /// resize buffer
288     abstract void resize(int width, int height);
289 
290     //========================================================
291     // Drawing methods.
292 
293     /// fill the whole buffer with solid color (no clipping applied)
294     abstract void fill(uint color);
295     /// fill rectangle with solid color (clipping is applied)
296     abstract void fillRect(Rect rc, uint color);
297     /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
298 	abstract void drawGlyph(int x, int y, Glyph * glyph, uint color);
299     /// draw source buffer rectangle contents to destination buffer
300     abstract void drawFragment(int x, int y, DrawBuf src, Rect srcrect);
301     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
302     abstract void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect);
303     /// draw unscaled image at specified coordinates
304     void drawImage(int x, int y, DrawBuf src) {
305         drawFragment(x, y, src, Rect(0, 0, src.width, src.height));
306     }
307 
308     /// create drawbuf with copy of current buffer with changed colors (returns this if not supported)
309     DrawBuf transformColors(ref ColorTransform transform) {
310         return this;
311     }
312 
313     void clear() {}
314     ~this() { clear(); }
315 }
316 
317 alias DrawBufRef = Ref!DrawBuf;
318 
319 /// RAII setting/restoring of clip rectangle
320 struct ClipRectSaver {
321     private DrawBuf _buf;
322     private Rect _oldClipRect;
323     this(DrawBuf buf, ref Rect newClipRect) {
324         _buf = buf;
325         _oldClipRect = buf.clipRect;
326         buf.intersectClipRect(newClipRect);
327     }
328     ~this() {
329         _buf.clipRect = _oldClipRect;
330     }
331 }
332 
333 class ColorDrawBufBase : DrawBuf {
334     int _dx;
335     int _dy;
336     /// returns buffer bits per pixel
337     override @property int bpp() { return 32; }
338     @property override int width() { return _dx; }
339     @property override int height() { return _dy; }
340 
341     /// returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory
342     uint * scanLine(int y) { return null; }
343 
344     /// draw source buffer rectangle contents to destination buffer
345     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
346         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
347         if (applyClipping(dstrect, srcrect)) {
348             if (src.applyClipping(srcrect, dstrect)) {
349                 int dx = srcrect.width;
350                 int dy = srcrect.height;
351                 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
352                 if (colorDrawBuf !is null) {
353                     for (int yy = 0; yy < dy; yy++) {
354                         uint * srcrow = colorDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
355                         uint * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
356                         for (int i = 0; i < dx; i++) {
357                             uint pixel = srcrow[i];
358                             uint alpha = pixel >> 24;
359                             if (!alpha)
360                                 dstrow[i] = pixel;
361                             else if (alpha < 255) {
362                                 // apply blending
363                                 dstrow[i] = blendARGB(dstrow[i], pixel, alpha);
364                             }
365                         }
366 
367                     }
368                 }
369             }
370         }
371     }
372 
373     /// Create mapping of source coordinates to destination coordinates, for resize.
374     private int[] createMap(int dst0, int dst1, int src0, int src1) {
375         int dd = dst1 - dst0;
376         int sd = src1 - src0;
377         int[] res = new int[dd];
378         for (int i = 0; i < dd; i++)
379             res[i] = src0 + i * sd / dd;
380         return res;
381     }
382     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
383     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
384         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
385         if (applyClipping(dstrect, srcrect)) {
386             int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right);
387             int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom);
388             int dx = dstrect.width;
389             int dy = dstrect.height;
390             ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src;
391             if (colorDrawBuf !is null) {
392                 for (int y = 0; y < dy; y++) {
393                     uint * srcrow = colorDrawBuf.scanLine(ymap[y]);
394                     uint * dstrow = scanLine(dstrect.top + y) + dstrect.left;
395                     for (int x = 0; x < dx; x++) {
396                         uint srcpixel = srcrow[xmap[x]];
397                         uint dstpixel = dstrow[x];
398                         uint alpha = (srcpixel >> 24) & 255;
399                         if (!alpha)
400                             dstrow[x] = srcpixel;
401                         else if (alpha < 255) {
402                             // apply blending
403                             dstrow[x] = blendARGB(dstpixel, srcpixel, alpha);
404                         }
405                     }
406                 }
407             }
408         }
409     }
410 
411     /// detect position of black pixels in row for 9-patch markup
412     private bool detectHLine(int y, ref int x0, ref int x1) {
413         uint * line = scanLine(y);
414     	bool foundUsed = false;
415         x0 = 0;
416         x1 = 0;
417     	for (int x = 1; x < _dx - 1; x++) {
418     		if (isBlackPixel(line[x])) { // opaque black pixel
419     			if (!foundUsed) {
420     				x0 = x;
421         			foundUsed = true;
422     			}
423     			x1 = x + 1;
424     		}
425     	}
426         return x1 > x0;
427     }
428 
429 	static bool isBlackPixel(uint c) {
430 		if (((c >> 24) & 255) > 10)
431 			return false;
432 		if (((c >> 16) & 255) > 10)
433 			return false;
434 		if (((c >> 8) & 255) > 10)
435 			return false;
436 		if (((c >> 0) & 255) > 10)
437 			return false;
438 		return true;
439 	}
440 	
441     /// detect position of black pixels in column for 9-patch markup
442     private bool detectVLine(int x, ref int y0, ref int y1) {
443     	bool foundUsed = false;
444         y0 = 0;
445         y1 = 0;
446     	for (int y = 1; y < _dy - 1; y++) {
447             uint * line = scanLine(y);
448     		if (isBlackPixel(line[x])) { // opaque black pixel
449     			if (!foundUsed) {
450     				y0 = y;
451         			foundUsed = true;
452     			}
453     			y1 = y + 1;
454     		}
455     	}
456         return y1 > y0;
457     }
458     /// detect nine patch using image 1-pixel border (see Android documentation)
459     override bool detectNinePatch() {
460         if (_dx < 3 || _dy < 3)
461             return false; // image is too small
462         int x00, x01, x10, x11, y00, y01, y10, y11;
463         bool found = true;
464         found = found && detectHLine(0, x00, x01);
465         found = found && detectHLine(_dy - 1, x10, x11);
466         found = found && detectVLine(0, y00, y01);
467         found = found && detectVLine(_dx - 1, y10, y11);
468         if (!found)
469             return false; // no black pixels on 1-pixel frame
470         NinePatch * p = new NinePatch();
471         p.frame.left = x00 - 1;
472         p.frame.right = _dx - x01 - 1;
473         p.frame.top = y00 - 1;
474         p.frame.bottom = _dy - y01 - 1;
475         p.padding.left = x10 - 1;
476         p.padding.right = _dx - x11 - 1;
477         p.padding.top = y10 - 1;
478         p.padding.bottom = _dy - y11 - 1;
479         _ninePatch = p;
480 		//Log.d("NinePatch detected: frame=", p.frame, " padding=", p.padding, " left+right=", p.frame.left + p.frame.right, " dx=", _dx);
481         return true;
482     }
483 	override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
484         ubyte[] src = glyph.glyph;
485         int srcdx = glyph.blackBoxX;
486         int srcdy = glyph.blackBoxY;
487 		bool clipping = !_clipRect.empty();
488 		for (int yy = 0; yy < srcdy; yy++) {
489 			int liney = y + yy;
490 			if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
491 				continue;
492 			if (liney < 0 || liney >= _dy)
493 				continue;
494 			uint * row = scanLine(liney);
495 			ubyte * srcrow = src.ptr + yy * srcdx;
496 			for (int xx = 0; xx < srcdx; xx++) {
497 				int colx = xx + x;
498 				if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
499 					continue;
500 				if (colx < 0 || colx >= _dx)
501 					continue;
502 				uint alpha1 = srcrow[xx] ^ 255;
503 				uint alpha2 = (color >> 24);
504 				uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
505 				uint pixel = row[colx];
506 				if (!alpha)
507 					row[colx] = pixel;
508 				else if (alpha < 255) {
509 					// apply blending
510 					row[colx] = blendARGB(pixel, color, alpha);
511 				}
512 			}
513 		}
514 	}
515     override void fillRect(Rect rc, uint color) {
516         if (applyClipping(rc)) {
517             for (int y = rc.top; y < rc.bottom; y++) {
518                 uint * row = scanLine(y);
519                 uint alpha = color >> 24;
520                 for (int x = rc.left; x < rc.right; x++) {
521                     if (!alpha)
522                         row[x] = color;
523                     else if (alpha < 255) {
524                         // apply blending
525                         row[x] = blendARGB(row[x], color, alpha);
526                     }
527                 }
528             }
529         }
530     }
531 }
532 
533 class GrayDrawBuf : DrawBuf {
534     int _dx;
535     int _dy;
536     /// returns buffer bits per pixel
537     override @property int bpp() { return 8; }
538     @property override int width() { return _dx; }
539     @property override int height() { return _dy; }
540 
541     ubyte[] _buf;
542     this(int width, int height) {
543         resize(width, height);
544     }
545     ubyte * scanLine(int y) {
546         if (y >= 0 && y < _dy)
547             return _buf.ptr + _dx * y;
548         return null;
549     }
550     override void resize(int width, int height) {
551         if (_dx == width && _dy == height)
552             return;
553         _dx = width;
554         _dy = height;
555         _buf.length = _dx * _dy;
556     }
557     override void fill(uint color) {
558         int len = _dx * _dy;
559         ubyte * p = _buf.ptr;
560         ubyte cl = rgbToGray(color);
561         for (int i = 0; i < len; i++)
562             p[i] = cl;
563     }
564 
565     /// draw source buffer rectangle contents to destination buffer
566     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
567         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
568         if (applyClipping(dstrect, srcrect)) {
569             if (src.applyClipping(srcrect, dstrect)) {
570                 int dx = srcrect.width;
571                 int dy = srcrect.height;
572                 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
573                 if (grayDrawBuf !is null) {
574                     for (int yy = 0; yy < dy; yy++) {
575                         ubyte * srcrow = grayDrawBuf.scanLine(srcrect.top + yy) + srcrect.left;
576                         ubyte * dstrow = scanLine(dstrect.top + yy) + dstrect.left;
577                         for (int i = 0; i < dx; i++) {
578                             ubyte pixel = srcrow[i];
579                             dstrow[i] = pixel;
580                         }
581                     }
582                 }
583             }
584         }
585     }
586 
587     /// Create mapping of source coordinates to destination coordinates, for resize.
588     private int[] createMap(int dst0, int dst1, int src0, int src1) {
589         int dd = dst1 - dst0;
590         int sd = src1 - src0;
591         int[] res = new int[dd];
592         for (int i = 0; i < dd; i++)
593             res[i] = src0 + i * sd / dd;
594         return res;
595     }
596     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
597     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
598         //Log.d("drawRescaled ", dstrect, " <- ", srcrect);
599         if (applyClipping(dstrect, srcrect)) {
600             int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right);
601             int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom);
602             int dx = dstrect.width;
603             int dy = dstrect.height;
604             GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src;
605             if (grayDrawBuf !is null) {
606                 for (int y = 0; y < dy; y++) {
607                     ubyte * srcrow = grayDrawBuf.scanLine(ymap[y]);
608                     ubyte * dstrow = scanLine(dstrect.top + y) + dstrect.left;
609                     for (int x = 0; x < dx; x++) {
610                         ubyte srcpixel = srcrow[xmap[x]];
611                         ubyte dstpixel = dstrow[x];
612                         dstrow[x] = srcpixel;
613                     }
614                 }
615             }
616         }
617     }
618 
619     /// detect position of black pixels in row for 9-patch markup
620     private bool detectHLine(int y, ref int x0, ref int x1) {
621         ubyte * line = scanLine(y);
622     	bool foundUsed = false;
623         x0 = 0;
624         x1 = 0;
625     	for (int x = 1; x < _dx - 1; x++) {
626     		if (line[x] == 0x00000000) { // opaque black pixel
627     			if (!foundUsed) {
628     				x0 = x;
629         			foundUsed = true;
630     			}
631     			x1 = x + 1;
632     		}
633     	}
634         return x1 > x0;
635     }
636 
637     /// detect position of black pixels in column for 9-patch markup
638     private bool detectVLine(int x, ref int y0, ref int y1) {
639     	bool foundUsed = false;
640         y0 = 0;
641         y1 = 0;
642     	for (int y = 1; y < _dy - 1; y++) {
643             ubyte * line = scanLine(y);
644     		if (line[x] == 0x00000000) { // opaque black pixel
645     			if (!foundUsed) {
646     				y0 = y;
647         			foundUsed = true;
648     			}
649     			y1 = y + 1;
650     		}
651     	}
652         return y1 > y0;
653     }
654     /// detect nine patch using image 1-pixel border (see Android documentation)
655     override bool detectNinePatch() {
656         if (_dx < 3 || _dy < 3)
657             return false; // image is too small
658         int x00, x01, x10, x11, y00, y01, y10, y11;
659         bool found = true;
660         found = found && detectHLine(0, x00, x01);
661         found = found && detectHLine(_dy - 1, x10, x11);
662         found = found && detectVLine(0, y00, y01);
663         found = found && detectVLine(_dx - 1, y10, y11);
664         if (!found)
665             return false; // no black pixels on 1-pixel frame
666         NinePatch * p = new NinePatch();
667         p.frame.left = x00 - 1;
668         p.frame.right = _dy - y01 - 1;
669         p.frame.top = y00 - 1;
670         p.frame.bottom = _dy - y01 - 1;
671         p.padding.left = x10 - 1;
672         p.padding.right = _dy - y11 - 1;
673         p.padding.top = y10 - 1;
674         p.padding.bottom = _dy - y11 - 1;
675         _ninePatch = p;
676         return true;
677     }
678 	override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
679         ubyte[] src = glyph.glyph;
680         int srcdx = glyph.blackBoxX;
681         int srcdy = glyph.blackBoxY;
682 		bool clipping = !_clipRect.empty();
683         ubyte cl = cast(ubyte)(color & 255);
684 		for (int yy = 0; yy < srcdy; yy++) {
685 			int liney = y + yy;
686 			if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
687 				continue;
688 			if (liney < 0 || liney >= _dy)
689 				continue;
690 			ubyte * row = scanLine(liney);
691 			ubyte * srcrow = src.ptr + yy * srcdx;
692 			for (int xx = 0; xx < srcdx; xx++) {
693 				int colx = xx + x;
694 				if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
695 					continue;
696 				if (colx < 0 || colx >= _dx)
697 					continue;
698 				uint alpha1 = srcrow[xx] ^ 255;
699 				uint alpha2 = (color >> 24);
700 				uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
701 				uint pixel = row[colx];
702 				if (!alpha)
703 					row[colx] = cast(ubyte)pixel;
704 				else if (alpha < 255) {
705 					// apply blending
706 					row[colx] = cast(ubyte)blendARGB(pixel, color, alpha);
707 				}
708 			}
709 		}
710 	}
711     override void fillRect(Rect rc, uint color) {
712         ubyte cl = rgbToGray(color);
713         if (applyClipping(rc)) {
714             for (int y = rc.top; y < rc.bottom; y++) {
715                 ubyte * row = scanLine(y);
716                 uint alpha = color >> 24;
717                 for (int x = rc.left; x < rc.right; x++) {
718                     if (!alpha)
719                         row[x] = cl;
720                     else if (alpha < 255) {
721                         // apply blending
722                         row[x] = blendGray(row[x], cl, alpha);
723                     }
724                 }
725             }
726         }
727     }
728 }
729 
730 class ColorDrawBuf : ColorDrawBufBase {
731     uint[] _buf;
732     this(int width, int height) {
733         resize(width, height);
734     }
735     override uint * scanLine(int y) {
736         if (y >= 0 && y < _dy)
737             return _buf.ptr + _dx * y;
738         return null;
739     }
740     override void resize(int width, int height) {
741         if (_dx == width && _dy == height)
742             return;
743         _dx = width;
744         _dy = height;
745         _buf.length = _dx * _dy;
746     }
747     override void fill(uint color) {
748         int len = _dx * _dy;
749         uint * p = _buf.ptr;
750         for (int i = 0; i < len; i++)
751             p[i] = color;
752     }
753     override DrawBuf transformColors(ref ColorTransform transform) {
754         if (transform.empty)
755             return this;
756         bool skipFrame = hasNinePatch;
757         ColorDrawBuf res = new ColorDrawBuf(_dx, _dy);
758         if (hasNinePatch) {
759             NinePatch * p = new NinePatch;
760             *p = *_ninePatch;
761             res.ninePatch = p;
762         }
763         for (int y = 0; y < _dy; y++) {
764             uint * srcline = scanLine(y);
765             uint * dstline = res.scanLine(y);
766             bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1);
767             for (int x = 0; x < _dx; x++) {
768                 bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1);
769                 if (!allowTransformX || !allowTransformY)
770                     dstline[x] = srcline[x];
771                 else
772                     dstline[x] = transform.transform(srcline[x]);
773             }
774         }
775         return res;
776     }
777 }
778 
779