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 deprecated 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 deprecated 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 }
502 
503 
504 
505 /// Rough anti-aliased fillsector
506 void aaFillSector(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float a0, float a1, COLOR c)
507     if (isWritableView!V && is(COLOR : ViewColor!V))
508 {
509     alias ChannelType = COLOR.ChannelType;
510 
511     if (a0 == a1)
512         return;
513 
514     int x0 = cast(int)floorf(x - r1 - 1);
515     int x1 = cast(int)ceilf(x + r1 + 1);
516 
517     int y0 = cast(int)floorf(y - r1 - 1);
518     int y1 = cast(int)ceilf(y + r1 + 1);
519 
520     float r0s = r0-1;
521     if (r0s < 0) r0s = 0;
522     r0s = r0s * r0s;
523     float r1s = (r1 + 1) * (r1 + 1);
524 
525     if (a0 > a1)
526         a1 += 2 * PI;
527 
528     if (a0 < -PI || a1 < -PI)
529     {
530         // else atan2 will never produce angles below PI
531         a0 += 2 * PI;
532         a1 += 2 * PI;
533     }
534 
535     int xmin = x0;
536     int xmax = x1+1;
537     int ymin = y0;
538     int ymax = y1+1;
539 
540     // avoids to draw out of bounds
541     if (xmin < 0)
542         xmin = 0;
543     if (ymin < 0)
544         ymin = 0;
545     if (xmax > v.w)
546         xmax = v.w;
547     if (ymax > v.h)
548         ymax = v.h;
549 
550     foreach (py; ymin .. ymax)
551     {
552         foreach (px; xmin .. xmax)
553         {
554             float dx = px-x;
555             float dy = py-y;
556             float rsq = dx * dx + dy * dy;
557 
558             if(r0s <= rsq && rsq <= r1s)
559             {
560                 float rs = sqrt(rsq);
561 
562                 // How much angle is one pixel at this radius?
563                 // It's actually rule of 3.
564                 // 2*pi radians => 2*pi*radius pixels
565                 // ???          => 1 pixel
566                 float aTransition = 1.0f / rs;
567 
568 
569                 if (r0 <= rs && rs < r1)
570                 {
571                     float alpha = 1.0f;
572                     if (r0 + 1 > rs)
573                         alpha = rs - r0;
574                     if (rs + 1 > r1)
575                         alpha = r1 - rs;
576 
577                     float a = atan2(dy, dx);
578                     bool inSector = (a0 <= a && a <= a1);
579                     if (inSector)
580                     {
581                         float alpha2 = alpha;
582                         if (a0 + aTransition > a)
583                             alpha2 *= (a-a0) / aTransition;
584                         else if (a + aTransition > a1)
585                             alpha2 *= (a1 - a)/aTransition;
586 
587                         auto p = v.pixelPtr(px, py);
588                         *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
589                     }
590                     else
591                     {
592                         a += 2 * PI;
593                         bool inSector2 = (a0 <= a && a <= a1);
594                         if(inSector2 )
595                         {
596                             float alpha2 = alpha;
597                             if (a0 + aTransition > a)
598                                 alpha2 *= (a-a0) / aTransition;
599                             else if (a + aTransition > a1)
600                                 alpha2 *= (a1 - a)/aTransition;
601 
602                             auto p = v.pixelPtr(px, py);
603                             *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
604                         }
605                     }
606                 }
607             }
608         }
609     }
610 }
611 
612 /**
613     Fill rectangle while interpolating a `COLOR` (can be depth) horiontally.
614     Params:
615          v     The surface to write to. That be clipped by a dirtyRect.
616          rect  The bounds of the slopped plane. The drawing itself will be 
617                clipped to its limit, and the limits of the surface.
618                Should NOT be clipped by the dirtyRect.
619          c0    Color at left edge.
620          c1    Color at right edge.
621 */
622 void horizontalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, 
623                                                        box2i rect, 
624                                                        COLOR c0, 
625                                                        COLOR c1)
626     if (isWritableView!V && is(COLOR : ViewColor!V))
627 {
628     alias Type = COLOR.ChannelType;
629 
630     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
631     if (inter.empty)
632         return;
633 
634     int x0 = rect.min.x;
635     int x1 = rect.max.x;
636     immutable float invX1mX0 = 1.0f / (x1 - x0);
637 
638     foreach (px; inter.min.x .. inter.max.x)
639     {
640         float fAlpha =  (px - x0) * invX1mX0;
641         static if (curvature != 1.0f)
642             fAlpha = fAlpha ^^ curvature;
643         Type alpha = cast(Type)( 0.5f + Type.max * fAlpha );
644         COLOR c = blendColor(c1, c0, alpha);
645         vline!false(v, px, inter.min.y, inter.max.y, c);
646     }
647 }
648 
649 /** 
650     Fill rectangle while interpolating a `COLOR` (can be depth) vertically.
651 
652     Params:
653          v     The surface to write to. That be clipped by a dirtyRect.
654          rect  The bounds of the slopped plane. The drawing itself will be 
655                clipped to its limit, and the limits of the surface.
656                Should NOT be clipped by the dirtyRect.
657          c0    Color at top edge.
658          c1    Color at bottom edge.
659 */
660 void verticalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1)
661     if (isWritableView!V && is(COLOR : ViewColor!V))
662 {
663     alias Type = COLOR.ChannelType;
664 
665     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
666     if (inter.empty)
667         return;
668 
669     int x0 = rect.min.x;
670     int y0 = rect.min.y;
671     int x1 = rect.max.x;
672     int y1 = rect.max.y;
673 
674     immutable float invY1mY0 = 1.0f / (y1 - y0);
675 
676     foreach (py; inter.min.y .. inter.max.y)
677     {
678         float fAlpha =  (py - y0) * invY1mY0;
679         static if (curvature != 1.0f)
680             fAlpha = fAlpha ^^ curvature;
681         Type alpha = cast(Type)( 0.5f + Type.max * fAlpha );
682         COLOR c = blendColor(c1, c0, alpha);
683         hline!false(v, inter.min.x, inter.max.x, py, c);
684     }
685 }
686 
687 
688 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)
689 if (isWritableView!V && is(COLOR : ViewColor!V))
690 {
691     alias ChannelType = COLOR.ChannelType;
692     assert(r1 <= r2);
693     int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
694     int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
695     int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
696     int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
697 
698     auto r1s = r1*r1;
699     auto r2s = r2*r2;
700 
701     float fx = x;
702     float fy = y;
703 
704     immutable float fr1s = r1s;
705     immutable float fr2s = r2s;
706 
707     immutable float fr21 = fr2s - fr1s;
708     immutable float invfr21 = 1 / fr21;
709 
710     for (int cy=y1;cy<y2;cy++)
711     {
712         auto row = v.scanline(cy);
713         for (int cx=x1;cx<x2;cx++)
714         {
715             float dx =  (fx - cx);
716             float dy =  (fy - cy);
717             float frs = dx*dx + dy*dy;
718 
719             if (frs<fr1s)
720                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
721             else
722             {
723                 if (frs<fr2s)
724                 {
725                     float alpha = (frs-fr1s) * invfr21;
726                     static if (curvature != 1.0f)
727                         alpha = alpha ^^ curvature;
728                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
729                 }
730             }
731         }
732     }
733 }
734 
735 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)
736 if (isWritableView!V && is(COLOR : ViewColor!V))
737 {
738     alias ChannelType = COLOR.ChannelType;
739     assert(r1 <= r2);
740     int x1 = cast(int)(x-r2*scaleX-1); if (x1<0) x1=0;
741     int y1 = cast(int)(y-r2*scaleY-1); if (y1<0) y1=0;
742     int x2 = cast(int)(x+r2*scaleX+1); if (x2>v.w) x2 = v.w;
743     int y2 = cast(int)(y+r2*scaleY+1); if (y2>v.h) y2 = v.h;
744 
745     float invScaleX = 1 / scaleX;
746     float invScaleY = 1 / scaleY;
747 
748     auto r1s = r1*r1;
749     auto r2s = r2*r2;
750 
751     float fx = x;
752     float fy = y;
753 
754     immutable float fr1s = r1s;
755     immutable float fr2s = r2s;
756 
757     immutable float fr21 = fr2s - fr1s;
758     immutable float invfr21 = 1 / fr21;
759 
760     for (int cy=y1;cy<y2;cy++)
761     {
762         auto row = v.scanline(cy);
763         for (int cx=x1;cx<x2;cx++)
764         {
765             float dx =  (fx - cx) * invScaleX;
766             float dy =  (fy - cy) * invScaleY;
767             float frs = dx*dx + dy*dy;
768 
769             if (frs<fr1s)
770                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
771             else
772             {
773                 if (frs<fr2s)
774                 {
775                     float alpha = (frs-fr1s) * invfr21;
776                     static if (curvature != 1.0f)
777                         alpha = alpha ^^ curvature;
778                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
779                 }
780             }
781         }
782     }
783 }
784 
785 /// Draw a circle gradually fading in between r1 and r2 and fading out between r2 and r3
786 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)
787 if (isWritableView!V && is(COLOR : ViewColor!V))
788 {
789     alias ChannelType = COLOR.ChannelType;
790     assert(r1 <= r2);
791     assert(r2 <= r3);
792     int x1 = cast(int)(x-r3-1); if (x1<0) x1=0;
793     int y1 = cast(int)(y-r3-1); if (y1<0) y1=0;
794     int x2 = cast(int)(x+r3+1); if (x2>v.w) x2 = v.w;
795     int y2 = cast(int)(y+r3+1); if (y2>v.h) y2 = v.h;
796 
797     auto r1s = r1*r1;
798     auto r2s = r2*r2;
799     auto r3s = r3*r3;
800 
801     float fx = x;
802     float fy = y;
803 
804     immutable float fr1s = r1s;
805     immutable float fr2s = r2s;
806     immutable float fr3s = r3s;
807 
808     immutable float fr21 = fr2s - fr1s;
809     immutable float fr32 = fr3s - fr2s;
810     immutable float invfr21 = 1 / fr21;
811     immutable float invfr32 = 1 / fr32;
812 
813     for (int cy=y1;cy<y2;cy++)
814     {
815         auto row = v.scanline(cy);
816         for (int cx=x1;cx<x2;cx++)
817         {
818             float frs = (fx - cx)*(fx - cx) + (fy - cy)*(fy - cy);
819 
820             if (frs >= fr1s)
821             {
822                 if (frs < fr3s)
823                 {
824                     float alpha = void;
825                     if (frs >= fr2s)
826                         alpha = (frs - fr2s) * invfr32;
827                     else
828                         alpha = 1 - (frs - fr1s) * invfr21;
829 
830                     static if (curvature != 1.0f)
831                         alpha = alpha ^^ curvature;
832                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
833                 }
834             }
835         }
836     }
837 }
838 
839 
840 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)
841     if (isWritableView!V && is(COLOR : ViewColor!V))
842 {
843     if (globalAlpha == 0)
844         return;
845 
846     alias ChannelType = COLOR.ChannelType;
847 
848     sort2(x1, x2);
849     sort2(y1, y2);
850 
851     int ix1 = cast(int)(floorf(x1));
852     int iy1 = cast(int)(floorf(y1));
853     int ix2 = cast(int)(floorf(x2));
854     int iy2 = cast(int)(floorf(y2));
855     float fx1 = x1 - ix1;
856     float fy1 = y1 - iy1;
857     float fx2 = x2 - ix2;
858     float fy2 = y2 - iy2;
859 
860     static ChannelType toAlpha(float fraction) pure nothrow @nogc
861     {
862         return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
863     }
864 
865     v.aaPutPixelFloat!CHECKED(ix1, iy1, color, toAlpha(globalAlpha * (1-fx1) * (1-fy1) ));
866     v.hline!CHECKED(ix1+1, ix2, iy1, color, toAlpha(globalAlpha * (1 - fy1) ));
867     v.aaPutPixelFloat!CHECKED(ix2, iy1, color, toAlpha(globalAlpha * fx2 * (1-fy1) ));
868 
869     v.vline!CHECKED(ix1, iy1+1, iy2, color, toAlpha(globalAlpha * (1 - fx1)));
870     v.vline!CHECKED(ix2, iy1+1, iy2, color, toAlpha(globalAlpha * fx2));
871 
872     v.aaPutPixelFloat!CHECKED(ix1, iy2, color, toAlpha(globalAlpha * (1-fx1) * fy2 ));
873     v.hline!CHECKED(ix1+1, ix2, iy2, color,  toAlpha(globalAlpha * fy2));
874     v.aaPutPixelFloat!CHECKED(ix2, iy2, color, toAlpha(globalAlpha * fx2 * fy2 ));
875 
876     v.fillRectFloat!CHECKED(ix1+1, iy1+1, ix2, iy2, color, globalAlpha);
877 }
878 
879 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) // [)
880 if (isWritableView!V && is(COLOR : ViewColor!V))
881 {
882     if (globalAlpha == 0)
883         return;
884 
885     sort2(x1, x2);
886     sort2(y1, y2);
887     static if (CHECKED)
888     {
889         if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return;
890         if (x1 <    0) x1 =   0;
891         if (y1 <    0) y1 =   0;
892         if (x2 >= v.w) x2 = v.w;
893         if (y2 >= v.h) y2 = v.h;
894     }
895 
896     if (globalAlpha == 1)
897     {
898         foreach (y; y1..y2)
899             v.scanline(y)[x1..x2] = b;
900     }
901     else
902     {
903         alias ChannelType = COLOR.ChannelType;
904         static ChannelType toAlpha(float fraction) pure nothrow @nogc
905         {
906             return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
907         }
908 
909         ChannelType alpha = toAlpha(globalAlpha);
910 
911         foreach (y; y1..y2)
912         {
913             COLOR[] scan = v.scanline(y);
914             foreach (x; x1..x2)
915             {
916                 scan[x] = blendColor(b, scan[x], alpha);
917             }
918         }
919     }
920 }
921 
922 void aaPutPixelFloat(bool CHECKED=true, V, COLOR, A)(auto ref V v, int x, int y, COLOR color, A alpha)
923     if (is(COLOR.ChannelType == A))
924 {
925     static if (CHECKED)
926         if (x<0 || x>=v.w || y<0 || y>=v.h)
927             return;
928 
929     COLOR* p = v.pixelPtr(x, y);
930     *p = blendColor(color, *p, alpha);
931 }
932 
933 
934 /// Blits a view onto another.
935 /// The views must have the same size.
936 /// PERF: optimize that
937 void blendWithAlpha(SRC, DST)(auto ref SRC srcView, 
938                               auto ref DST dstView, 
939                               auto ref ImageRef!L8 alphaView)
940 {
941     static assert(isDirectView!SRC);
942     static assert(isDirectView!DST);
943     static assert(isWritableView!DST);
944 
945     alias COLOR = ViewColor!DST;
946     assert(srcView.w == dstView.w && srcView.h == dstView.h, "View size mismatch");
947 
948     foreach (y; 0..srcView.h)
949     {
950         COLOR* srcScan = srcView.scanline(y).ptr;
951         COLOR* dstScan = dstView.scanline(y).ptr;
952         L8* alphaScan = alphaView.scanline(y).ptr;
953 
954         foreach (x; 0..srcView.w)
955         {
956             ubyte alpha = alphaScan[x].l;
957             if (alpha == 0)
958                 continue;
959             static if (is(COLOR == RGBA))
960             {
961                 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha);
962             }
963             else static if (is(COLOR == L16))
964             {
965                 ushort alpha16 = (alpha << 8) | alpha;
966                 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha16);
967             }
968             else
969                 static assert(false);
970         }
971     }
972 }
973