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