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 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c)
167     if (isWritableView!V && is(COLOR : ViewColor!V))
168 {
169     int x0 = x > r ? x-r : 0;
170     int y0 = y > r ? y-r : 0;
171     int x1 = x+r;
172     if (x1 > v.w-1)
173         x1 = v.w-1;
174     int y1 = y+r;
175     if (y1 > v.h-1)
176         y1 = v.h-1;
177     int rs = sqr(r);
178     // FUTURE: optimize
179     foreach (py; y0..y1+1)
180         foreach (px; x0..x1+1)
181             if (sqr(x-px) + sqr(y-py) < rs)
182                 v[px, py] = c;
183 }
184 
185 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c)
186     if (isWritableView!V && is(COLOR : ViewColor!V))
187 {
188     int x0 = x>r1?x-r1:0;
189     int y0 = y>r1?y-r1:0;
190     int x1 = x+r1;
191     if (x1 > v.w-1)
192         x1 = v.w-1;
193     int y1 = y+r1;
194     if (y1 > v.h-1)
195         y1 = v.h-1;
196 
197 
198     int r0s = sqr(r0);
199     int r1s = sqr(r1);
200     if (a0 > a1)
201         a1 += (2 * PI);
202     foreach (py; y0..y1+1)
203         foreach (px; x0..x1+1)
204         {
205             int dx = px-x;
206             int dy = py-y;
207             int rs = sqr(dx) + sqr(dy);
208             if (r0s <= rs && rs < r1s)
209             {
210                 real a = atan2(cast(real)dy, cast(real)dx);
211                 if (a0 <= a && a <= a1)
212                     v[px, py] = c;
213                 else
214                 {
215                     a += 2 * PI;
216                     if (a0 <= a && a <= a1)
217                         v[px, py] = c;
218                 }
219             }
220         }
221 }
222 
223 mixin template FixMath(ubyte coordinateBitsParam = 16)
224 {
225     enum coordinateBits = coordinateBitsParam;
226 
227     static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!");
228     /// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision.
229     alias fix  = SignedBitsType!(COLOR.channelBits   + coordinateBits);
230     /// Type to hold temporary values for multiplication and division
231     alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits);
232 
233     static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!");
234     fix tofix(T:int  )(T x) { return cast(fix) (x<<COLOR.channelBits); }
235     fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); }
236     T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); }
237 
238     fix fixsqr(fix x)        { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); }
239     fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); }
240     fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); }
241 
242     static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!");
243     /// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc.
244     alias COLOR.ChannelType frac;
245     /// Type to hold temporary values for multiplication and division
246     alias UnsignedBitsType!(COLOR.channelBits*2) frac2;
247 
248     frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); }
249     frac fixfpart(fix x) { return cast(frac)x; }
250     frac fracsqr(frac x        ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); }
251     frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); }
252 
253     frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); }
254 }
255 
256 // ************************************************************************************************************************************
257 
258 private template softRoundShape(bool RING)
259 {
260     void softRoundShape(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
261         if (isWritableView!V && is(COLOR : ViewColor!V))
262     {
263         mixin FixMath;
264 
265         assert(r0 <= r1);
266         assert(r1 <= r2);
267         assert(r2 < 256); // precision constraint - see SqrType
268         //int ix = cast(int)x;
269         //int iy = cast(int)y;
270         //int ir1 = cast(int)sqr(r1-1);
271         //int ir2 = cast(int)sqr(r2+1);
272         int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
273         int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
274         int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
275         int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
276 
277         static if (RING)
278         auto r0s = r0*r0;
279         auto r1s = r1*r1;
280         auto r2s = r2*r2;
281         //float rds = r2s - r1s;
282 
283         fix fx = tofix(x);
284         fix fy = tofix(y);
285 
286         static if (RING)
287         fix fr0s = tofix(r0s);
288         fix fr1s = tofix(r1s);
289         fix fr2s = tofix(r2s);
290 
291         static if (RING)
292         fix fr10 = fr1s - fr0s;
293         fix fr21 = fr2s - fr1s;
294 
295         for (int cy=y1;cy<y2;cy++)
296         {
297             auto row = v.scanline(cy);
298             for (int cx=x1;cx<x2;cx++)
299             {
300                 alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point
301                 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
302 
303                 //static frac alphafunc(frac x) { return fracsqr(x); }
304                 static frac alphafunc(frac x) { return x; }
305 
306                 static if (RING)
307                 {
308                     if (frs<fr0s)
309                         {}
310                     else
311                     if (frs<fr2s)
312                     {
313                         frac alpha;
314                         if (frs<fr1s)
315                             alpha =  alphafunc(cast(frac)fixdiv(frs-fr0s, fr10));
316                         else
317                             alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)));
318                         row[cx] = blendColor(color, row[cx], alpha);
319                     }
320                 }
321                 else
322                 {
323                     if (frs<fr1s)
324                         row[cx] = color;
325                     else
326                     if (frs<fr2s)
327                     {
328                         frac alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)));
329                         row[cx] = blendColor(color, row[cx], alpha);
330                     }
331                 }
332             }
333         }
334     }
335 }
336 
337 void softRing(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
338     if (isWritableView!V && is(COLOR : ViewColor!V))
339 {
340     v.softRoundShape!true(x, y, r0, r1, r2, color);
341 }
342 
343 void softCircle(V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color)
344     if (isWritableView!V && is(COLOR : ViewColor!V))
345 {
346     v.softRoundShape!false(x, y, 0, r1, r2, color);
347 }
348 
349 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true)
350 {
351     void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha)
352         if (isWritableView!V && is(COLOR : ViewColor!V))
353     {
354         mixin FixMath;
355 
356         void plot(bool CHECKED2)(int x, int y, frac f)
357         {
358             static if (CHECKED2)
359                 if (x<0 || x>=v.w || y<0 || y>=v.h)
360                     return;
361 
362             COLOR* p = v.pixelPtr(x, y);
363             static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha);
364             *p = blendColor(color, *p, f);
365         }
366 
367         fix fx = tofix(x);
368         fix fy = tofix(y);
369         int ix = fixto!int(fx);
370         int iy = fixto!int(fy);
371         static if (CHECKED)
372             if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h)
373             {
374                 plot!false(ix  , iy  , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy))));
375                 plot!false(ix  , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)),  fixfpart(fy)));
376                 plot!false(ix+1, iy  , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy))));
377                 plot!false(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
378                 return;
379             }
380         plot!CHECKED(ix  , iy  , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy))));
381         plot!CHECKED(ix  , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)),  fixfpart(fy)));
382         plot!CHECKED(ix+1, iy  , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy))));
383         plot!CHECKED(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
384     }
385 }
386 
387 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color)
388     if (isWritableView!V && is(COLOR : ViewColor!V))
389 {
390     //aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf
391     alias aaPutPixel!(CHECKED, false) f;
392     f(v, x, y, color, 0);
393 }
394 
395 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha)
396     if (isWritableView!V && is(COLOR : ViewColor!V))
397 {
398     mixin(CheckHLine);
399 
400     if (alpha==0)
401         return;
402     else
403     if (alpha==frac.max)
404         v.scanline(y)[x1..x2] = color;
405     else
406         foreach (ref p; v.scanline(y)[x1..x2])
407             p = blendColor(color, p, alpha);
408 }
409 
410 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha)
411     if (isWritableView!V && is(COLOR : ViewColor!V))
412 {
413     mixin(CheckVLine);
414 
415     if (alpha==0)
416         return;
417     else
418     if (alpha==frac.max)
419         foreach (y; y1..y2)
420             v[x, y] = color;
421     else
422         foreach (y; y1..y2)
423         {
424             auto p = v.pixelPtr(x, y);
425             *p = blendColor(color, *p, alpha);
426         }
427 }
428 
429 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color)
430     if (isWritableView!V && is(COLOR : ViewColor!V))
431 {
432     mixin FixMath;
433 
434     sort2(x1, x2);
435     sort2(y1, y2);
436     fix x1f = tofix(x1); int x1i = fixto!int(x1f);
437     fix y1f = tofix(y1); int y1i = fixto!int(y1f);
438     fix x2f = tofix(x2); int x2i = fixto!int(x2f);
439     fix y2f = tofix(y2); int y2i = fixto!int(y2f);
440 
441     v.vline!CHECKED(x1i, y1i+1, y2i, color, cast(ubyte)(~cast(int)fixfpart(x1f)));
442     v.vline!CHECKED(x2i, y1i+1, y2i, color,  fixfpart(x2f));
443     v.hline!CHECKED(x1i+1, x2i, y1i, color, cast(ubyte)(~cast(int)fixfpart(y1f)));
444     v.hline!CHECKED(x1i+1, x2i, y2i, color,  fixfpart(y2f));
445     v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) ,
446                                                   cast(ubyte)(~cast(int)fixfpart(y1f))) );
447     v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) ,  fixfpart(y2f)));
448     v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul( fixfpart(x2f), cast(ubyte)(~cast(int)fixfpart(y1f))) );
449     v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul( fixfpart(x2f),  fixfpart(y2f)));
450 
451     v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color);
452 }
453 
454 unittest
455 {
456     // Test instantiation    
457     ImageRef!RGB i;
458     i.w = 100;
459     i.h = 100;
460     i.pitch = 100;
461     RGB[] rgb;
462     rgb.reallocBuffer(100*100);
463     scope(exit) rgb.reallocBuffer(0);
464     i.pixels = rgb.ptr;
465 
466     auto c = RGB(1, 2, 3);
467     i.rect(10, 10, 20, 20, c);
468     i.fillRect(10, 10, 20, 20, c);
469     i.aaFillRect(10, 10, 20, 20, c);
470     i.vline(10, 10, 20, c);
471     i.vline(10, 10, 20, c);
472     i.fillCircle(10, 10, 10, c);
473     i.fillSector(10, 10, 10, 10, 0.0, (2 * PI), c);
474     i.softRing(50, 50, 10, 15, 20, c);
475     i.softCircle(50, 50, 10, 15, c);
476 }
477 
478 
479 
480 /// Rough anti-aliased fillsector
481 void aaFillSector(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float a0, float a1, COLOR c)
482     if (isWritableView!V && is(COLOR : ViewColor!V))
483 {
484     alias ChannelType = COLOR.ChannelType;
485 
486     if (a0 == a1)
487         return;
488 
489     int x0 = cast(int)floorf(x - r1 - 1);
490     int x1 = cast(int)ceilf(x + r1 + 1);
491 
492     int y0 = cast(int)floorf(y - r1 - 1);
493     int y1 = cast(int)ceilf(y + r1 + 1);
494 
495     float r0s = r0-1;
496     if (r0s < 0) r0s = 0;
497     r0s = r0s * r0s;
498     float r1s = (r1 + 1) * (r1 + 1);
499 
500     if (a0 > a1)
501         a1 += 2 * PI;
502 
503     if (a0 < -PI || a1 < -PI)
504     {
505         // else atan2 will never produce angles below PI
506         a0 += 2 * PI;
507         a1 += 2 * PI;
508     }
509 
510     int xmin = x0;
511     int xmax = x1+1;
512     int ymin = y0;
513     int ymax = y1+1;
514 
515     // avoids to draw out of bounds
516     if (xmin < 0)
517         xmin = 0;
518     if (ymin < 0)
519         ymin = 0;
520     if (xmax > v.w)
521         xmax = v.w;
522     if (ymax > v.h)
523         ymax = v.h;
524 
525     foreach (py; ymin .. ymax)
526     {
527         foreach (px; xmin .. xmax)
528         {
529             float dx = px-x;
530             float dy = py-y;
531             float rsq = dx * dx + dy * dy;
532 
533             if(r0s <= rsq && rsq <= r1s)
534             {
535                 float rs = sqrt(rsq);
536 
537                 // How much angle is one pixel at this radius?
538                 // It's actually rule of 3.
539                 // 2*pi radians => 2*pi*radius pixels
540                 // ???          => 1 pixel
541                 float aTransition = 1.0f / rs;
542 
543 
544                 if (r0 <= rs && rs < r1)
545                 {
546                     float alpha = 1.0f;
547                     if (r0 + 1 > rs)
548                         alpha = rs - r0;
549                     if (rs + 1 > r1)
550                         alpha = r1 - rs;
551 
552                     float a = atan2(dy, dx);
553                     bool inSector = (a0 <= a && a <= a1);
554                     if (inSector)
555                     {
556                         float alpha2 = alpha;
557                         if (a0 + aTransition > a)
558                             alpha2 *= (a-a0) / aTransition;
559                         else if (a + aTransition > a1)
560                             alpha2 *= (a1 - a)/aTransition;
561 
562                         auto p = v.pixelPtr(px, py);
563                         *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
564                     }
565                     else
566                     {
567                         a += 2 * PI;
568                         bool inSector2 = (a0 <= a && a <= a1);
569                         if(inSector2 )
570                         {
571                             float alpha2 = alpha;
572                             if (a0 + aTransition > a)
573                                 alpha2 *= (a-a0) / aTransition;
574                             else if (a + aTransition > a1)
575                                 alpha2 *= (a1 - a)/aTransition;
576 
577                             auto p = v.pixelPtr(px, py);
578                             *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max));
579                         }
580                     }
581                 }
582             }
583         }
584     }
585 }
586 
587 /**
588     Fill rectangle while interpolating a `COLOR` (can be depth) horiontally.
589     Params:
590          v     The surface to write to. That be clipped by a dirtyRect.
591          rect  The bounds of the slopped plane. The drawing itself will be 
592                clipped to its limit, and the limits of the surface.
593                Should NOT be clipped by the dirtyRect.
594          c0    Color at left edge.
595          c1    Color at right edge.
596 */
597 void horizontalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, 
598                                                        box2i rect, 
599                                                        COLOR c0, 
600                                                        COLOR c1)
601     if (isWritableView!V && is(COLOR : ViewColor!V))
602 {
603     alias Type = COLOR.ChannelType;
604 
605     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
606     if (inter.empty)
607         return;
608 
609     int x0 = rect.min.x;
610     int x1 = rect.max.x;
611     immutable float invX1mX0 = 1.0f / (x1 - x0);
612 
613     foreach (px; inter.min.x .. inter.max.x)
614     {
615         float fAlpha =  (px - x0) * invX1mX0;
616         static if (curvature != 1.0f)
617             fAlpha = fAlpha ^^ curvature;
618         Type alpha = cast(Type)( 0.5f + Type.max * fAlpha );
619         COLOR c = blendColor(c1, c0, alpha);
620         vline!false(v, px, inter.min.y, inter.max.y, c);
621     }
622 }
623 
624 /** 
625     Fill rectangle while interpolating a `COLOR` (can be depth) vertically.
626 
627     Params:
628          v     The surface to write to. That be clipped by a dirtyRect.
629          rect  The bounds of the slopped plane. The drawing itself will be 
630                clipped to its limit, and the limits of the surface.
631                Should NOT be clipped by the dirtyRect.
632          c0    Color at top edge.
633          c1    Color at bottom edge.
634 */
635 void verticalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1)
636     if (isWritableView!V && is(COLOR : ViewColor!V))
637 {
638     alias Type = COLOR.ChannelType;
639 
640     box2i inter = box2i(0, 0, v.w, v.h).intersection(rect);
641     if (inter.empty)
642         return;
643 
644     int x0 = rect.min.x;
645     int y0 = rect.min.y;
646     int x1 = rect.max.x;
647     int y1 = rect.max.y;
648 
649     immutable float invY1mY0 = 1.0f / (y1 - y0);
650 
651     foreach (py; inter.min.y .. inter.max.y)
652     {
653         float fAlpha =  (py - y0) * invY1mY0;
654         static if (curvature != 1.0f)
655             fAlpha = fAlpha ^^ curvature;
656         Type alpha = cast(Type)( 0.5f + Type.max * fAlpha );
657         COLOR c = blendColor(c1, c0, alpha);
658         hline!false(v, inter.min.x, inter.max.x, py, c);
659     }
660 }
661 
662 
663 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)
664 if (isWritableView!V && is(COLOR : ViewColor!V))
665 {
666     alias ChannelType = COLOR.ChannelType;
667     assert(r1 <= r2);
668     int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
669     int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
670     int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
671     int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
672 
673     auto r1s = r1*r1;
674     auto r2s = r2*r2;
675 
676     float fx = x;
677     float fy = y;
678 
679     immutable float fr1s = r1s;
680     immutable float fr2s = r2s;
681 
682     immutable float fr21 = fr2s - fr1s;
683     immutable float invfr21 = 1 / fr21;
684 
685     for (int cy=y1;cy<y2;cy++)
686     {
687         auto row = v.scanline(cy);
688         for (int cx=x1;cx<x2;cx++)
689         {
690             float dx =  (fx - cx);
691             float dy =  (fy - cy);
692             float frs = dx*dx + dy*dy;
693 
694             if (frs<fr1s)
695                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
696             else
697             {
698                 if (frs<fr2s)
699                 {
700                     float alpha = (frs-fr1s) * invfr21;
701                     static if (curvature != 1.0f)
702                         alpha = alpha ^^ curvature;
703                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
704                 }
705             }
706         }
707     }
708 }
709 
710 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)
711 if (isWritableView!V && is(COLOR : ViewColor!V))
712 {
713     alias ChannelType = COLOR.ChannelType;
714     assert(r1 <= r2);
715     int x1 = cast(int)(x-r2*scaleX-1); if (x1<0) x1=0;
716     int y1 = cast(int)(y-r2*scaleY-1); if (y1<0) y1=0;
717     int x2 = cast(int)(x+r2*scaleX+1); if (x2>v.w) x2 = v.w;
718     int y2 = cast(int)(y+r2*scaleY+1); if (y2>v.h) y2 = v.h;
719 
720     float invScaleX = 1 / scaleX;
721     float invScaleY = 1 / scaleY;
722 
723     auto r1s = r1*r1;
724     auto r2s = r2*r2;
725 
726     float fx = x;
727     float fy = y;
728 
729     immutable float fr1s = r1s;
730     immutable float fr2s = r2s;
731 
732     immutable float fr21 = fr2s - fr1s;
733     immutable float invfr21 = 1 / fr21;
734 
735     for (int cy=y1;cy<y2;cy++)
736     {
737         auto row = v.scanline(cy);
738         for (int cx=x1;cx<x2;cx++)
739         {
740             float dx =  (fx - cx) * invScaleX;
741             float dy =  (fy - cy) * invScaleY;
742             float frs = dx*dx + dy*dy;
743 
744             if (frs<fr1s)
745                 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha));
746             else
747             {
748                 if (frs<fr2s)
749                 {
750                     float alpha = (frs-fr1s) * invfr21;
751                     static if (curvature != 1.0f)
752                         alpha = alpha ^^ curvature;
753                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
754                 }
755             }
756         }
757     }
758 }
759 
760 /// Draw a circle gradually fading in between r1 and r2 and fading out between r2 and r3
761 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)
762 if (isWritableView!V && is(COLOR : ViewColor!V))
763 {
764     alias ChannelType = COLOR.ChannelType;
765     assert(r1 <= r2);
766     assert(r2 <= r3);
767     int x1 = cast(int)(x-r3-1); if (x1<0) x1=0;
768     int y1 = cast(int)(y-r3-1); if (y1<0) y1=0;
769     int x2 = cast(int)(x+r3+1); if (x2>v.w) x2 = v.w;
770     int y2 = cast(int)(y+r3+1); if (y2>v.h) y2 = v.h;
771 
772     auto r1s = r1*r1;
773     auto r2s = r2*r2;
774     auto r3s = r3*r3;
775 
776     float fx = x;
777     float fy = y;
778 
779     immutable float fr1s = r1s;
780     immutable float fr2s = r2s;
781     immutable float fr3s = r3s;
782 
783     immutable float fr21 = fr2s - fr1s;
784     immutable float fr32 = fr3s - fr2s;
785     immutable float invfr21 = 1 / fr21;
786     immutable float invfr32 = 1 / fr32;
787 
788     for (int cy=y1;cy<y2;cy++)
789     {
790         auto row = v.scanline(cy);
791         for (int cx=x1;cx<x2;cx++)
792         {
793             float frs = (fx - cx)*(fx - cx) + (fy - cy)*(fy - cy);
794 
795             if (frs >= fr1s)
796             {
797                 if (frs < fr3s)
798                 {
799                     float alpha = void;
800                     if (frs >= fr2s)
801                         alpha = (frs - fr2s) * invfr32;
802                     else
803                         alpha = 1 - (frs - fr1s) * invfr21;
804 
805                     static if (curvature != 1.0f)
806                         alpha = alpha ^^ curvature;
807                     row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha));
808                 }
809             }
810         }
811     }
812 }
813 
814 
815 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)
816     if (isWritableView!V && is(COLOR : ViewColor!V))
817 {
818     if (globalAlpha == 0)
819         return;
820 
821     alias ChannelType = COLOR.ChannelType;
822 
823     sort2(x1, x2);
824     sort2(y1, y2);
825 
826     int ix1 = cast(int)(floorf(x1));
827     int iy1 = cast(int)(floorf(y1));
828     int ix2 = cast(int)(floorf(x2));
829     int iy2 = cast(int)(floorf(y2));
830     float fx1 = x1 - ix1;
831     float fy1 = y1 - iy1;
832     float fx2 = x2 - ix2;
833     float fy2 = y2 - iy2;
834 
835     static ChannelType toAlpha(float fraction) pure nothrow @nogc
836     {
837         return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
838     }
839 
840     v.aaPutPixelFloat!CHECKED(ix1, iy1, color, toAlpha(globalAlpha * (1-fx1) * (1-fy1) ));
841     v.hline!CHECKED(ix1+1, ix2, iy1, color, toAlpha(globalAlpha * (1 - fy1) ));
842     v.aaPutPixelFloat!CHECKED(ix2, iy1, color, toAlpha(globalAlpha * fx2 * (1-fy1) ));
843 
844     v.vline!CHECKED(ix1, iy1+1, iy2, color, toAlpha(globalAlpha * (1 - fx1)));
845     v.vline!CHECKED(ix2, iy1+1, iy2, color, toAlpha(globalAlpha * fx2));
846 
847     v.aaPutPixelFloat!CHECKED(ix1, iy2, color, toAlpha(globalAlpha * (1-fx1) * fy2 ));
848     v.hline!CHECKED(ix1+1, ix2, iy2, color,  toAlpha(globalAlpha * fy2));
849     v.aaPutPixelFloat!CHECKED(ix2, iy2, color, toAlpha(globalAlpha * fx2 * fy2 ));
850 
851     v.fillRectFloat!CHECKED(ix1+1, iy1+1, ix2, iy2, color, globalAlpha);
852 }
853 
854 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) // [)
855 if (isWritableView!V && is(COLOR : ViewColor!V))
856 {
857     if (globalAlpha == 0)
858         return;
859 
860     sort2(x1, x2);
861     sort2(y1, y2);
862     static if (CHECKED)
863     {
864         if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return;
865         if (x1 <    0) x1 =   0;
866         if (y1 <    0) y1 =   0;
867         if (x2 >= v.w) x2 = v.w;
868         if (y2 >= v.h) y2 = v.h;
869     }
870 
871     if (globalAlpha == 1)
872     {
873         foreach (y; y1..y2)
874             v.scanline(y)[x1..x2] = b;
875     }
876     else
877     {
878         alias ChannelType = COLOR.ChannelType;
879         static ChannelType toAlpha(float fraction) pure nothrow @nogc
880         {
881             return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction));
882         }
883 
884         ChannelType alpha = toAlpha(globalAlpha);
885 
886         foreach (y; y1..y2)
887         {
888             COLOR[] scan = v.scanline(y);
889             foreach (x; x1..x2)
890             {
891                 scan[x] = blendColor(b, scan[x], alpha);
892             }
893         }
894     }
895 }
896 
897 void aaPutPixelFloat(bool CHECKED=true, V, COLOR, A)(auto ref V v, int x, int y, COLOR color, A alpha)
898     if (is(COLOR.ChannelType == A))
899 {
900     static if (CHECKED)
901         if (x<0 || x>=v.w || y<0 || y>=v.h)
902             return;
903 
904     COLOR* p = v.pixelPtr(x, y);
905     *p = blendColor(color, *p, alpha);
906 }
907 
908 
909 /// Blits a view onto another.
910 /// The views must have the same size.
911 /// PERF: optimize that
912 void blendWithAlpha(SRC, DST)(auto ref SRC srcView, 
913                               auto ref DST dstView, 
914                               auto ref ImageRef!L8 alphaView)
915 {
916     static assert(isDirectView!SRC);
917     static assert(isDirectView!DST);
918     static assert(isWritableView!DST);
919 
920     alias COLOR = ViewColor!DST;
921     assert(srcView.w == dstView.w && srcView.h == dstView.h, "View size mismatch");
922 
923     foreach (y; 0..srcView.h)
924     {
925         COLOR* srcScan = srcView.scanline(y).ptr;
926         COLOR* dstScan = dstView.scanline(y).ptr;
927         L8* alphaScan = alphaView.scanline(y).ptr;
928 
929         foreach (x; 0..srcView.w)
930         {
931             ubyte alpha = alphaScan[x].l;
932             if (alpha == 0)
933                 continue;
934             static if (is(COLOR == RGBA))
935             {
936                 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha);
937             }
938             else static if (is(COLOR == L16))
939             {
940                 ushort alpha16 = (alpha << 8) | alpha;
941                 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha16);
942             }
943             else
944                 static assert(false);
945         }
946     }
947 }
948