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