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