1 /**
2 Drawing functions. Port of ae.utils.graphics.
3 In many many cases, you should use dplug:canvas instead.
4 
5 License:
6     This Source Code Form is subject to the terms of
7     the Mozilla Public License, v. 2.0. If a copy of
8     the MPL was not distributed with this file, You
9     can obtain one at http://mozilla.org/MPL/2.0/.
10  
11  Copyright: Vladimir Panteleev <vladimir@thecybershadow.net>
12  Copyright: Guillaume Piolat <contact@auburnsounds.com>
13  */
14 
15 module dplug.graphics.draw;
16 
17 import core.stdc.math: floorf, ceilf;
18 
19 import std.math;
20 
21 import dplug.math.box;
22 
23 import dplug.core.nogc;
24 import dplug.core.vec;
25 import dplug.graphics.image;
26 
27 
28 nothrow:
29 @nogc:
30 
31 // Constraints could be simpler if this was fixed:
32 // https://d.puremagic.com/issues/show_bug.cgi?id=12386
33 
34 /// Get the pixel color at the specified coordinates,
35 /// or fall back to the specified default value if
36 /// the coordinates are out of bounds.
37 COLOR safeGet(V, COLOR)(auto ref V v, int x, int y, COLOR def)
38     if (isView!V && is(COLOR : ViewColor!V))
39 {
40     if (x>=0 && y>=0 && x<v.w && y<v.h)
41         return v[x, y];
42     else
43         return def;
44 }
45 
46 /// Set the pixel color at the specified coordinates
47 /// if the coordinates are not out of bounds.
48 void safePut(V, COLOR)(auto ref V v, int x, int y, COLOR value)
49     if (isWritableView!V && is(COLOR : ViewColor!V))
50 {
51     if (x>=0 && y>=0 && x<v.w && y<v.h)
52         v[x, y] = value;
53 }
54 
55 
56 /// Forwards to safePut or opIndex, depending on the
57 /// CHECKED parameter. Allows propagation of a
58 /// CHECKED parameter from other callers.
59 void putPixel(bool CHECKED, V, COLOR)(auto ref V v, int x, int y, COLOR value)
60     if (isWritableView!V && is(COLOR : ViewColor!V))
61 {
62     static if (CHECKED)
63         v.safePut(x, y, value);
64     else
65         v[x, y] = value;
66 }
67 
68 
69 /// Gets a pixel's address from a direct view.
70 ViewColor!V* pixelPtr(V)(auto ref V v, int x, int y)
71     if (isDirectView!V)
72 {
73     return &v.scanline(y)[x];
74 }
75 
76 void fillAll(V, COLOR)(auto ref V v, COLOR c)
77     if (isWritableView!V
78      && is(COLOR : ViewColor!V))
79 {
80     foreach (y; 0..v.h)
81     {
82         static if (isDirectView!V)
83             v.scanline(y)[] = c;
84         else
85             foreach (x; 0..v.w)
86                 v[x, y] = c;
87     }
88 }
89 
90 // ***************************************************************************
91 
92 enum CheckHLine =
93 q{
94     static if (CHECKED)
95     {
96         if (x1 >= v.w || x2 <= 0 || y < 0 || y >= v.h || x1 >= x2) return;
97         if (x1 <    0) x1 =   0;
98         if (x2 >= v.w) x2 = v.w;
99     }
100     assert(x1 <= x2);
101 };
102 
103 enum CheckVLine =
104 q{
105     static if (CHECKED)
106     {
107         if (x < 0 || x >= v.w || y1 >= v.h || y2 <= 0 || y1 >= y2) return;
108         if (y1 <    0) y1 =   0;
109         if (y2 >= v.h) y2 = v.h;
110     }
111     assert(y1 <= y2);
112 };
113 
114 void hline(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int x2, int y, COLOR c)
115     if (isWritableView!V && is(COLOR : ViewColor!V))
116 {
117     mixin(CheckHLine);
118     v.scanline(y)[x1..x2] = c;
119 }
120 
121 void vline(bool CHECKED=true, V, COLOR)(auto ref V v, int x, int y1, int y2, COLOR c)
122 {
123     mixin(CheckVLine);
124     foreach (y; y1..y2) // FUTURE: optimize
125         v[x, y] = c;
126 }
127 
128 /// Draws a rectangle with a solid line.
129 /// The coordinates represent bounds (open on the right) for the outside of the rectangle.
130 void rect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c)
131     if (isWritableView!V && is(COLOR : ViewColor!V))
132 {
133     sort2(x1, x2);
134     sort2(y1, y2);
135     v.hline!CHECKED(x1, x2, y1  , c);
136     v.hline!CHECKED(x1, x2, y2-1, c);
137     v.vline!CHECKED(x1  , y1, y2, c);
138     v.vline!CHECKED(x2-1, y1, y2, c);
139 }
140 
141 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b) // [)
142     if (isWritableView!V && is(COLOR : ViewColor!V))
143 {
144     sort2(x1, x2);
145     sort2(y1, y2);
146     static if (CHECKED)
147     {
148         if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return;
149         if (x1 <    0) x1 =   0;
150         if (y1 <    0) y1 =   0;
151         if (x2 >= v.w) x2 = v.w;
152         if (y2 >= v.h) y2 = v.h;
153     }
154     foreach (y; y1..y2)
155         v.scanline(y)[x1..x2] = b;
156 }
157 
158 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c, COLOR b) // [)
159     if (isWritableView!V && is(COLOR : ViewColor!V))
160 {
161     v.rect!CHECKED(x1, y1, x2, y2, c);
162     if (x2-x1>2 && y2-y1>2)
163         v.fillRect!CHECKED(x1+1, y1+1, x2-1, y2-1, b);
164 }
165 
166 /// Unchecked! Make sure area is bounded.
167 void uncheckedFloodFill(V, COLOR)(auto ref V v, int x, int y, COLOR c)
168     if (isDirectView!V && is(COLOR : ViewColor!V))
169 {
170     v.floodFillPtr(&v[x, y], c, v[x, y]);
171 }
172 
173 private void floodFillPtr(V, COLOR)(auto ref V v, COLOR* pp, COLOR c, COLOR f)
174     if (isDirectView!V && is(COLOR : ViewColor!V))
175 {
176     COLOR* p0 = pp; while (*p0==f) p0--; p0++;
177     COLOR* p1 = pp; while (*p1==f) p1++; p1--;
178     auto stride = v.scanline(1).ptr-v.scanline(0).ptr;
179     for (auto p=p0; p<=p1; p++)
180         *p = c;
181     p0 -= stride; p1 -= stride;
182     for (auto p=p0; p<=p1; p++)
183         if (*p == f)
184             v.floodFillPtr(p, c, f);
185     p0 += stride*2; p1 += stride*2;
186     for (auto p=p0; p<=p1; p++)
187         if (*p == f)
188             v.floodFillPtr(p, c, f);
189 }
190 
191 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c)
192     if (isWritableView!V && is(COLOR : ViewColor!V))
193 {
194     int x0 = x > r ? x-r : 0;
195     int y0 = y > r ? y-r : 0;
196     int x1 = x+r;
197     if (x1 > v.w-1)
198         x1 = v.w-1;
199     int y1 = y+r;
200     if (y1 > v.h-1)
201         y1 = v.h-1;
202     int rs = sqr(r);
203     // FUTURE: optimize
204     foreach (py; y0..y1+1)
205         foreach (px; x0..x1+1)
206             if (sqr(x-px) + sqr(y-py) < rs)
207                 v[px, py] = c;
208 }
209 
210 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c)
211     if (isWritableView!V && is(COLOR : ViewColor!V))
212 {
213     int x0 = x>r1?x-r1:0;
214     int y0 = y>r1?y-r1:0;
215     int x1 = x+r1;
216     if (x1 > v.w-1)
217         x1 = v.w-1;
218     int y1 = y+r1;
219     if (y1 > v.h-1)
220         y1 = v.h-1;
221 
222 
223     int r0s = sqr(r0);
224     int r1s = sqr(r1);
225     if (a0 > a1)
226         a1 += (2 * PI);
227     foreach (py; y0..y1+1)
228         foreach (px; x0..x1+1)
229         {
230             int dx = px-x;
231             int dy = py-y;
232             int rs = sqr(dx) + sqr(dy);
233             if (r0s <= rs && rs < r1s)
234             {
235                 real a = atan2(cast(real)dy, cast(real)dx);
236                 if (a0 <= a && a <= a1)
237                     v[px, py] = c;
238                 else
239                 {
240                     a += 2 * PI;
241                     if (a0 <= a && a <= a1)
242                         v[px, py] = c;
243                 }
244             }
245         }
246 }
247 
248 mixin template FixMath(ubyte coordinateBitsParam = 16)
249 {
250     enum coordinateBits = coordinateBitsParam;
251 
252     static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!");
253     /// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision.
254     alias fix  = SignedBitsType!(COLOR.channelBits   + coordinateBits);
255     /// Type to hold temporary values for multiplication and division
256     alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits);
257 
258     static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!");
259     fix tofix(T:int  )(T x) { return cast(fix) (x<<COLOR.channelBits); }
260     fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); }
261     T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); }
262 
263     fix fixsqr(fix x)        { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); }
264     fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); }
265     fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); }
266 
267     static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!");
268     /// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc.
269     alias COLOR.ChannelType frac;
270     /// Type to hold temporary values for multiplication and division
271     alias UnsignedBitsType!(COLOR.channelBits*2) frac2;
272 
273     frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); }
274     frac fixfpart(fix x) { return cast(frac)x; }
275     frac fracsqr(frac x        ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); }
276     frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); }
277 
278     frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); }
279 }
280 
281 // ************************************************************************************************************************************
282 
283 private template softRoundShape(bool RING)
284 {
285     void softRoundShape(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
286         if (isWritableView!V && is(COLOR : ViewColor!V))
287     {
288         mixin FixMath;
289 
290         assert(r0 <= r1);
291         assert(r1 <= r2);
292         assert(r2 < 256); // precision constraint - see SqrType
293         //int ix = cast(int)x;
294         //int iy = cast(int)y;
295         //int ir1 = cast(int)sqr(r1-1);
296         //int ir2 = cast(int)sqr(r2+1);
297         int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
298         int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
299         int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
300         int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
301 
302         static if (RING)
303         auto r0s = r0*r0;
304         auto r1s = r1*r1;
305         auto r2s = r2*r2;
306         //float rds = r2s - r1s;
307 
308         fix fx = tofix(x);
309         fix fy = tofix(y);
310 
311         static if (RING)
312         fix fr0s = tofix(r0s);
313         fix fr1s = tofix(r1s);
314         fix fr2s = tofix(r2s);
315 
316         static if (RING)
317         fix fr10 = fr1s - fr0s;
318         fix fr21 = fr2s - fr1s;
319 
320         for (int cy=y1;cy<y2;cy++)
321         {
322             auto row = v.scanline(cy);
323             for (int cx=x1;cx<x2;cx++)
324             {
325                 alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point
326                 fix frs = cast(fix)((sqr(cast(SqrType)fx-tofix(cx)) + sqr(cast(SqrType)fy-tofix(cy))) >> COLOR.channelBits); // shift-right only once instead of once-per-sqr
327 
328                 //static frac alphafunc(frac x) { return fracsqr(x); }
329                 static frac alphafunc(frac x) { return x; }
330 
331                 static if (RING)
332                 {
333                     if (frs<fr0s)
334                         {}
335                     else
336                     if (frs<fr2s)
337                     {
338                         frac alpha;
339                         if (frs<fr1s)
340                             alpha =  alphafunc(cast(frac)fixdiv(frs-fr0s, fr10));
341                         else
342                             alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)));
343                         row[cx] = blendColor(color, row[cx], alpha);
344                     }
345                 }
346                 else
347                 {
348                     if (frs<fr1s)
349                         row[cx] = color;
350                     else
351                     if (frs<fr2s)
352                     {
353                         frac alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)));
354                         row[cx] = blendColor(color, row[cx], alpha);
355                     }
356                 }
357             }
358         }
359     }
360 }
361 
362 void softRing(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
363     if (isWritableView!V && is(COLOR : ViewColor!V))
364 {
365     v.softRoundShape!true(x, y, r0, r1, r2, color);
366 }
367 
368 void softCircle(V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color)
369     if (isWritableView!V && is(COLOR : ViewColor!V))
370 {
371     v.softRoundShape!false(x, y, 0, r1, r2, color);
372 }
373 
374 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true)
375 {
376     void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha)
377         if (isWritableView!V && is(COLOR : ViewColor!V))
378     {
379         mixin FixMath;
380 
381         void plot(bool CHECKED2)(int x, int y, frac f)
382         {
383             static if (CHECKED2)
384                 if (x<0 || x>=v.w || y<0 || y>=v.h)
385                     return;
386 
387             COLOR* p = v.pixelPtr(x, y);
388             static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha);
389             *p = blendColor(color, *p, f);
390         }
391 
392         fix fx = tofix(x);
393         fix fy = tofix(y);
394         int ix = fixto!int(fx);
395         int iy = fixto!int(fy);
396         static if (CHECKED)
397             if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h)
398             {
399                 plot!false(ix  , iy  , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy))));
400                 plot!false(ix  , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)),  fixfpart(fy)));
401                 plot!false(ix+1, iy  , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy))));
402                 plot!false(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
403                 return;
404             }
405         plot!CHECKED(ix  , iy  , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy))));
406         plot!CHECKED(ix  , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)),  fixfpart(fy)));
407         plot!CHECKED(ix+1, iy  , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy))));
408         plot!CHECKED(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
409     }
410 }
411 
412 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color)
413     if (isWritableView!V && is(COLOR : ViewColor!V))
414 {
415     //aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf
416     alias aaPutPixel!(CHECKED, false) f;
417     f(v, x, y, color, 0);
418 }
419 
420 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha)
421     if (isWritableView!V && is(COLOR : ViewColor!V))
422 {
423     mixin(CheckHLine);
424 
425     if (alpha==0)
426         return;
427     else
428     if (alpha==frac.max)
429         v.scanline(y)[x1..x2] = color;
430     else
431         foreach (ref p; v.scanline(y)[x1..x2])
432             p = blendColor(color, p, alpha);
433 }
434 
435 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha)
436     if (isWritableView!V && is(COLOR : ViewColor!V))
437 {
438     mixin(CheckVLine);
439 
440     if (alpha==0)
441         return;
442     else
443     if (alpha==frac.max)
444         foreach (y; y1..y2)
445             v[x, y] = color;
446     else
447         foreach (y; y1..y2)
448         {
449             auto p = v.pixelPtr(x, y);
450             *p = blendColor(color, *p, alpha);
451         }
452 }
453 
454 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color)
455     if (isWritableView!V && is(COLOR : ViewColor!V))
456 {
457     mixin FixMath;
458 
459     sort2(x1, x2);
460     sort2(y1, y2);
461     fix x1f = tofix(x1); int x1i = fixto!int(x1f);
462     fix y1f = tofix(y1); int y1i = fixto!int(y1f);
463     fix x2f = tofix(x2); int x2i = fixto!int(x2f);
464     fix y2f = tofix(y2); int y2i = fixto!int(y2f);
465 
466     v.vline!CHECKED(x1i, y1i+1, y2i, color, cast(ubyte)(~cast(int)fixfpart(x1f)));
467     v.vline!CHECKED(x2i, y1i+1, y2i, color,  fixfpart(x2f));
468     v.hline!CHECKED(x1i+1, x2i, y1i, color, cast(ubyte)(~cast(int)fixfpart(y1f)));
469     v.hline!CHECKED(x1i+1, x2i, y2i, color,  fixfpart(y2f));
470     v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) ,
471                                                   cast(ubyte)(~cast(int)fixfpart(y1f))) );
472     v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) ,  fixfpart(y2f)));
473     v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul( fixfpart(x2f), cast(ubyte)(~cast(int)fixfpart(y1f))) );
474     v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul( fixfpart(x2f),  fixfpart(y2f)));
475 
476     v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color);
477 }
478 
479 unittest
480 {
481     // Test instantiation    
482     ImageRef!RGB i;
483     i.w = 100;
484     i.h = 100;
485     i.pitch = 100;
486     RGB[] rgb;
487     rgb.reallocBuffer(100*100);
488     scope(exit) rgb.reallocBuffer(0);
489     i.pixels = rgb.ptr;
490 
491     auto c = RGB(1, 2, 3);
492     i.rect(10, 10, 20, 20, c);
493     i.fillRect(10, 10, 20, 20, c);
494     i.aaFillRect(10, 10, 20, 20, c);
495     i.vline(10, 10, 20, c);
496     i.vline(10, 10, 20, c);
497     i.fillCircle(10, 10, 10, c);
498     i.fillSector(10, 10, 10, 10, 0.0, (2 * PI), c);
499     i.softRing(50, 50, 10, 15, 20, c);
500     i.softCircle(50, 50, 10, 15, c);
501     i.uncheckedFloodFill(15, 15, RGB(4, 5, 6));
502 }
503 
504 
505 
506 /// Rough anti-aliased fillsector
507 void aaFillSector(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float a0, float a1, COLOR c)
508     if (isWritableView!V && is(COLOR : ViewColor!V))
509 {
510     alias ChannelType = COLOR.ChannelType;
511 
512     if (a0 == a1)
513         return;
514 
515     int x0 = cast(int)floorf(x - r1 - 1);
516     int x1 = cast(int)ceilf(x + r1 + 1);
517 
518     int y0 = cast(int)floorf(y - r1 - 1);
519     int y1 = cast(int)ceilf(y + r1 + 1);
520 
521     float r0s = r0-1;
522     if (r0s < 0) r0s = 0;
523     r0s = r0s * r0s;
524     float r1s = (r1 + 1) * (r1 + 1);
525 
526     if (a0 > a1)
527         a1 += 2 * PI;
528 
529     if (a0 < -PI || a1 < -PI)
530     {
531         // else atan2 will never produce angles below PI
532         a0 += 2 * PI;
533         a1 += 2 * PI;
534     }
535 
536     int xmin = x0;
537     int xmax = x1+1;
538     int ymin = y0;
539     int ymax = y1+1;
540 
541     // avoids to draw out of bounds
542     if (xmin < 0)
543         xmin = 0;
544     if (ymin < 0)
545         ymin = 0;
546     if (xmax > v.w)
547         xmax = v.w;
548     if (ymax > v.h)
549         ymax = v.h;
550 
551     foreach (py; ymin .. ymax)
552     {
553         foreach (px; xmin .. xmax)
554         {
555             float dx = px-x;
556             float dy = py-y;
557             float rsq = dx * dx + dy * dy;
558 
559             if(r0s <= rsq && rsq <= r1s)
560             {
561                 float rs = sqrt(rsq);
562 
563                 // How much angle is one pixel at this radius?
564                 // It's actually rule of 3.
565                 // 2*pi radians => 2*pi*radius pixels
566                 // ???          => 1 pixel
567                 float aTransition = 1.0f / rs;
568 
569 
570                 if (r0 <= rs && rs < r1)
571                 {
572                     float alpha = 1.0f;
573                     if (r0 + 1 > rs)
574                         alpha = rs - r0;
575                     if (rs + 1 > r1)
576                         alpha = r1 - rs;
577 
578                     float a = atan2(dy, dx);
579                     bool inSector = (a0 <= a && a <= a1);
580                     if (inSector)
581                     {
582                         float alpha2 = alpha;
583                         if (a0 + aTransition > a)
584                             alpha2 *= (a-a0) / aTransition;
585                         else if (a + aTransition > a1)
586                             alpha2 *= (a1 - a)/aTransition;
587 
588                         auto p = v.pixelPtr(px, py);
589                         *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
590                     }
591                     else
592                     {
593                         a += 2 * PI;
594                         bool inSector2 = (a0 <= a && a <= a1);
595                         if(inSector2 )
596                         {
597                             float alpha2 = alpha;
598                             if (a0 + aTransition > a)
599                                 alpha2 *= (a-a0) / aTransition;
600                             else if (a + aTransition > a1)
601                                 alpha2 *= (a1 - a)/aTransition;
602 
603                             auto p = v.pixelPtr(px, py);
604                             *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
605                         }
606                     }
607                 }
608             }
609         }
610     }
611 }
612 
613 /// Fill rectangle while interpolating a color horiontally
614 void horizontalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1)
615     if (isWritableView!V && is(COLOR : ViewColor!V))
616 {
617     alias ChannelType = COLOR.ChannelType;
618 
619     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
620 
621     int x0 = rect.min.x;
622     int x1 = rect.max.x;
623     immutable float invX1mX0 = 1.0f / (x1 - x0);
624 
625     foreach (px; inter.min.x .. inter.max.x)
626     {
627         float fAlpha =  (px - x0) * invX1mX0;
628         static if (curvature != 1.0f)
629             fAlpha = fAlpha ^^ curvature;
630         ChannelType alpha = cast(ChannelType)( 0.5f + ChannelType.max * fAlpha );  // Not being generic here
631         COLOR c = blendColor(c1, c0, alpha); // warning .blend is confusing, c1 comes first
632         vline(v, px, inter.min.y, inter.max.y, c);
633     }
634 }
635 
636 void verticalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1)
637 if (isWritableView!V && is(COLOR : ViewColor!V))
638 {
639     alias ChannelType = COLOR.ChannelType;
640 
641     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
642 
643     int x0 = rect.min.x;
644     int y0 = rect.min.y;
645     int x1 = rect.max.x;
646     int y1 = rect.max.y;
647 
648     immutable float invY1mY0 = 1.0f / (y1 - y0);
649 
650     foreach (py; inter.min.y .. inter.max.y)
651     {
652         float fAlpha =  (py - y0) * invY1mY0;
653         static if (curvature != 1.0f)
654             fAlpha = fAlpha ^^ curvature;
655         ChannelType alpha = cast(ChannelType)( 0.5f + ChannelType.max * fAlpha );  // Not being generic here
656         COLOR c = blendColor(c1, c0, alpha); // warning .blend is confusing, c1 comes first
657         hline(v, inter.min.x, inter.max.x, py, c);
658     }
659 }
660 
661 
662 void aaSoftDisc(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color, float globalAlpha = 1.0f)
663 if (isWritableView!V && is(COLOR : ViewColor!V))
664 {
665     alias ChannelType = COLOR.ChannelType;
666     assert(r1 <= r2);
667     int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
668     int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
669     int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
670     int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
671 
672     auto r1s = r1*r1;
673     auto r2s = r2*r2;
674 
675     float fx = x;
676     float fy = y;
677 
678     immutable float fr1s = r1s;
679     immutable float fr2s = r2s;
680 
681     immutable float fr21 = fr2s - fr1s;
682     immutable float invfr21 = 1 / fr21;
683 
684     for (int cy=y1;cy<y2;cy++)
685     {
686         auto row = v.scanline(cy);
687         for (int cx=x1;cx<x2;cx++)
688         {
689             float dx =  (fx - cx);
690             float dy =  (fy - cy);
691             float frs = dx*dx + dy*dy;
692 
693             if (frs<fr1s)
694                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
695             else
696             {
697                 if (frs<fr2s)
698                 {
699                     float alpha = (frs-fr1s) * invfr21;
700                     static if (curvature != 1.0f)
701                         alpha = alpha ^^ curvature;
702                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
703                 }
704             }
705         }
706     }
707 }
708 
709 void aaSoftEllipse(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, float scaleX, float scaleY, COLOR color, float globalAlpha = 1.0f)
710 if (isWritableView!V && is(COLOR : ViewColor!V))
711 {
712     alias ChannelType = COLOR.ChannelType;
713     assert(r1 <= r2);
714     int x1 = cast(int)(x-r2*scaleX-1); if (x1<0) x1=0;
715     int y1 = cast(int)(y-r2*scaleY-1); if (y1<0) y1=0;
716     int x2 = cast(int)(x+r2*scaleX+1); if (x2>v.w) x2 = v.w;
717     int y2 = cast(int)(y+r2*scaleY+1); if (y2>v.h) y2 = v.h;
718 
719     float invScaleX = 1 / scaleX;
720     float invScaleY = 1 / scaleY;
721 
722     auto r1s = r1*r1;
723     auto r2s = r2*r2;
724 
725     float fx = x;
726     float fy = y;
727 
728     immutable float fr1s = r1s;
729     immutable float fr2s = r2s;
730 
731     immutable float fr21 = fr2s - fr1s;
732     immutable float invfr21 = 1 / fr21;
733 
734     for (int cy=y1;cy<y2;cy++)
735     {
736         auto row = v.scanline(cy);
737         for (int cx=x1;cx<x2;cx++)
738         {
739             float dx =  (fx - cx) * invScaleX;
740             float dy =  (fy - cy) * invScaleY;
741             float frs = dx*dx + dy*dy;
742 
743             if (frs<fr1s)
744                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
745             else
746             {
747                 if (frs<fr2s)
748                 {
749                     float alpha = (frs-fr1s) * invfr21;
750                     static if (curvature != 1.0f)
751                         alpha = alpha ^^ curvature;
752                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
753                 }
754             }
755         }
756     }
757 }
758 
759 /// Draw a circle gradually fading in between r1 and r2 and fading out between r2 and r3
760 void aaSoftCircle(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, float r3, COLOR color, float globalAlpha = 1.0f)
761 if (isWritableView!V && is(COLOR : ViewColor!V))
762 {
763     alias ChannelType = COLOR.ChannelType;
764     assert(r1 <= r2);
765     assert(r2 <= r3);
766     int x1 = cast(int)(x-r3-1); if (x1<0) x1=0;
767     int y1 = cast(int)(y-r3-1); if (y1<0) y1=0;
768     int x2 = cast(int)(x+r3+1); if (x2>v.w) x2 = v.w;
769     int y2 = cast(int)(y+r3+1); if (y2>v.h) y2 = v.h;
770 
771     auto r1s = r1*r1;
772     auto r2s = r2*r2;
773     auto r3s = r3*r3;
774 
775     float fx = x;
776     float fy = y;
777 
778     immutable float fr1s = r1s;
779     immutable float fr2s = r2s;
780     immutable float fr3s = r3s;
781 
782     immutable float fr21 = fr2s - fr1s;
783     immutable float fr32 = fr3s - fr2s;
784     immutable float invfr21 = 1 / fr21;
785     immutable float invfr32 = 1 / fr32;
786 
787     for (int cy=y1;cy<y2;cy++)
788     {
789         auto row = v.scanline(cy);
790         for (int cx=x1;cx<x2;cx++)
791         {
792             float frs = (fx - cx)*(fx - cx) + (fy - cy)*(fy - cy);
793 
794             if (frs >= fr1s)
795             {
796                 if (frs < fr3s)
797                 {
798                     float alpha = void;
799                     if (frs >= fr2s)
800                         alpha = (frs - fr2s) * invfr32;
801                     else
802                         alpha = 1 - (frs - fr1s) * invfr21;
803 
804                     static if (curvature != 1.0f)
805                         alpha = alpha ^^ curvature;
806                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
807                 }
808             }
809         }
810     }
811 }
812 
813 
814 void aaFillRectFloat(bool CHECKED=true, V, COLOR)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color, float globalAlpha = 1.0f)
815     if (isWritableView!V && is(COLOR : ViewColor!V))
816 {
817     if (globalAlpha == 0)
818         return;
819 
820     alias ChannelType = COLOR.ChannelType;
821 
822     sort2(x1, x2);
823     sort2(y1, y2);
824 
825     int ix1 = cast(int)(floorf(x1));
826     int iy1 = cast(int)(floorf(y1));
827     int ix2 = cast(int)(floorf(x2));
828     int iy2 = cast(int)(floorf(y2));
829     float fx1 = x1 - ix1;
830     float fy1 = y1 - iy1;
831     float fx2 = x2 - ix2;
832     float fy2 = y2 - iy2;
833 
834     static ChannelType toAlpha(float fraction) pure nothrow @nogc
835     {
836         return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
837     }
838 
839     v.aaPutPixelFloat!CHECKED(ix1, iy1, color, toAlpha(globalAlpha * (1-fx1) * (1-fy1) ));
840     v.hline!CHECKED(ix1+1, ix2, iy1, color, toAlpha(globalAlpha * (1 - fy1) ));
841     v.aaPutPixelFloat!CHECKED(ix2, iy1, color, toAlpha(globalAlpha * fx2 * (1-fy1) ));
842 
843     v.vline!CHECKED(ix1, iy1+1, iy2, color, toAlpha(globalAlpha * (1 - fx1)));
844     v.vline!CHECKED(ix2, iy1+1, iy2, color, toAlpha(globalAlpha * fx2));
845 
846     v.aaPutPixelFloat!CHECKED(ix1, iy2, color, toAlpha(globalAlpha * (1-fx1) * fy2 ));
847     v.hline!CHECKED(ix1+1, ix2, iy2, color,  toAlpha(globalAlpha * fy2));
848     v.aaPutPixelFloat!CHECKED(ix2, iy2, color, toAlpha(globalAlpha * fx2 * fy2 ));
849 
850     v.fillRectFloat!CHECKED(ix1+1, iy1+1, ix2, iy2, color, globalAlpha);
851 }
852 
853 void fillRectFloat(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b, float globalAlpha = 1.0f) // [)
854 if (isWritableView!V && is(COLOR : ViewColor!V))
855 {
856     if (globalAlpha == 0)
857         return;
858 
859     sort2(x1, x2);
860     sort2(y1, y2);
861     static if (CHECKED)
862     {
863         if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return;
864         if (x1 <    0) x1 =   0;
865         if (y1 <    0) y1 =   0;
866         if (x2 >= v.w) x2 = v.w;
867         if (y2 >= v.h) y2 = v.h;
868     }
869 
870     if (globalAlpha == 1)
871     {
872         foreach (y; y1..y2)
873             v.scanline(y)[x1..x2] = b;
874     }
875     else
876     {
877         alias ChannelType = COLOR.ChannelType;
878         static ChannelType toAlpha(float fraction) pure nothrow @nogc
879         {
880             return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
881         }
882 
883         ChannelType alpha = toAlpha(globalAlpha);
884 
885         foreach (y; y1..y2)
886         {
887             COLOR[] scan = v.scanline(y);
888             foreach (x; x1..x2)
889             {
890                 scan[x] = blendColor(b, scan[x], alpha);
891             }
892         }
893     }
894 }
895 
896 void aaPutPixelFloat(bool CHECKED=true, V, COLOR, A)(auto ref V v, int x, int y, COLOR color, A alpha)
897     if (is(COLOR.ChannelType == A))
898 {
899     static if (CHECKED)
900         if (x<0 || x>=v.w || y<0 || y>=v.h)
901             return;
902 
903     COLOR* p = v.pixelPtr(x, y);
904     *p = blendColor(color, *p, alpha);
905 }
906 
907 
908 /// Blits a view onto another.
909 /// The views must have the same size.
910 /// PERF: optimize that
911 void blendWithAlpha(SRC, DST)(auto ref SRC srcView, auto ref DST dstView, auto ref ImageRef!L8 alphaView)
912 {
913     static assert(isDirectView!SRC);
914     static assert(isDirectView!DST);
915     static assert(isWritableView!DST);
916 
917     static ubyte blendByte(ubyte a, ubyte b, ubyte f) nothrow @nogc
918     {
919         int sum = ( f * a + b * (cast(ubyte)(~cast(int)f)) ) + 127;
920         return cast(ubyte)(sum / 255 );// ((sum+1)*257) >> 16 ); // integer divide by 255
921     }
922 
923     static ushort blendShort(ushort a, ushort b, ubyte f) nothrow @nogc
924     {
925         ushort ff = (f << 8) | f;
926         int sum = ( ff * a + b * (cast(ushort)(~cast(int)ff)) ) + 32768;
927         return cast(ushort)( sum >> 16 ); // MAYDO: this doesn't map to the full range
928     }
929 
930     alias COLOR = ViewColor!DST;
931     assert(srcView.w == dstView.w && srcView.h == dstView.h, "View size mismatch");
932 
933     foreach (y; 0..srcView.h)
934     {
935         COLOR* srcScan = srcView.scanline(y).ptr;
936         COLOR* dstScan = dstView.scanline(y).ptr;
937         L8* alphaScan = alphaView.scanline(y).ptr;
938 
939         foreach (x; 0..srcView.w)
940         {
941             ubyte alpha = alphaScan[x].l;
942             if (alpha == 0)
943                 continue;
944             static if (is(COLOR == RGBA))
945             {
946                 dstScan[x].r = blendByte(srcScan[x].r, dstScan[x].r, alpha);
947                 dstScan[x].g = blendByte(srcScan[x].g, dstScan[x].g, alpha);
948                 dstScan[x].b = blendByte(srcScan[x].b, dstScan[x].b, alpha);
949                 dstScan[x].a = blendByte(srcScan[x].a, dstScan[x].a, alpha);
950             }
951             else static if (is(COLOR == L16))
952                 dstScan[x].l = blendShort(srcScan[x].l, dstScan[x].l, alpha);
953             else
954                 static assert(false);
955         }
956     }
957 }
958