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  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  *   Guillaume Piolat <contact@auburnsounds.com>
13  */
14 
15 module dplug.graphics.draw;
16 
17 import std.algorithm : sort, min;
18 import std.math;
19 
20 import dplug.graphics.view;
21 
22 version(unittest) import dplug.graphics.image;
23 
24 // Constraints could be simpler if this was fixed:
25 // https://d.puremagic.com/issues/show_bug.cgi?id=12386
26 
27 /// Get the pixel color at the specified coordinates,
28 /// or fall back to the specified default value if
29 /// the coordinates are out of bounds.
30 COLOR safeGet(V, COLOR)(auto ref V v, int x, int y, COLOR def)
31 	if (isView!V && is(COLOR : ViewColor!V))
32 {
33 	if (x>=0 && y>=0 && x<v.w && y<v.h)
34 		return v[x, y];
35 	else
36 		return def;
37 }
38 
39 unittest
40 {
41 	auto v = onePixel(7);
42 	assert(v.safeGet(0, 0, 0) == 7);
43 	assert(v.safeGet(0, 1, 0) == 0);
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 
77 
78 /// Fills the whole writable view with a solid color.
79 deprecated("Use fillAll instead.") alias fill = fillAll;
80 void fillAll(V, COLOR)(auto ref V v, COLOR c)
81 	if (isWritableView!V
82 	 && is(COLOR : ViewColor!V))
83 {
84 	foreach (y; 0..v.h)
85 	{
86 		static if (isDirectView!V)
87 			v.scanline(y)[] = c;
88 		else
89 			foreach (x; 0..v.w)
90 				v[x, y] = c;
91 	}
92 }
93 
94 // ***************************************************************************
95 
96 enum CheckHLine =
97 q{
98 	static if (CHECKED)
99 	{
100 		if (x1 >= v.w || x2 <= 0 || y < 0 || y >= v.h || x1 >= x2) return;
101 		if (x1 <    0) x1 =   0;
102 		if (x2 >= v.w) x2 = v.w;
103 	}
104 	assert(x1 <= x2);
105 };
106 
107 enum CheckVLine =
108 q{
109 	static if (CHECKED)
110 	{
111 		if (x < 0 || x >= v.w || y1 >= v.h || y2 <= 0 || y1 >= y2) return;
112 		if (y1 <    0) y1 =   0;
113 		if (y2 >= v.h) y2 = v.h;
114 	}
115 	assert(y1 <= y2);
116 };
117 
118 void hline(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int x2, int y, COLOR c)
119 	if (isWritableView!V && is(COLOR : ViewColor!V))
120 {
121 	mixin(CheckHLine);
122 	v.scanline(y)[x1..x2] = c;
123 }
124 
125 void vline(bool CHECKED=true, V, COLOR)(auto ref V v, int x, int y1, int y2, COLOR c)
126 {
127 	mixin(CheckVLine);
128 	foreach (y; y1..y2) // FUTURE: optimize
129 		v[x, y] = c;
130 }
131 
132 /+
133 void line(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c)
134 	if (isWritableView!V && is(COLOR : ViewColor!V))
135 {
136 	mixin FixMath;
137 
138 	enum DrawLine = q{
139 		// Axis-independent part. Mixin context:
140 		// a0 .. a1  - longer side
141 		// b0 .. b1  - shorter side
142 		// DrawPixel - mixin to draw a pixel at coordinates (a, b)
143 
144 		if (a0 == a1)
145 			return;
146 
147 		if (a0 > a1)
148 		{
149 			 swap(a0, a1);
150 			 swap(b0, b1);
151 		}
152 
153 		// Use fixed-point for b position and offset per 1 pixel along "a" axis
154 		assert(b0 < (1L<<coordinateBits) && b1 < (1L<<coordinateBits));
155 		SignedBitsType!(coordinateBits*2) bPos = b0 << coordinateBits;
156 		SignedBitsType!(coordinateBits*2) bOff = ((b1-b0) << coordinateBits) / (a1-a0);
157 
158 		foreach (a; a0..a1+1)
159 		{
160 			int b = (bPos += bOff) >> coordinateBits;
161 			mixin(DrawPixel);
162 		}
163 	};
164 
165 	if (abs(x2-x1) > abs(y2-y1))
166 	{
167 		alias x1 a0;
168 		alias x2 a1;
169 		alias y1 b0;
170 		alias y2 b1;
171 		enum DrawPixel = q{ v.putPixel!CHECKED(a, b, c); };
172 		mixin(DrawLine);
173 	}
174 	else
175 	{
176 		alias y1 a0;
177 		alias y2 a1;
178 		alias x1 b0;
179 		alias x2 b1;
180 		enum DrawPixel = q{ v.putPixel!CHECKED(b, a, c); };
181 		mixin(DrawLine);
182 	}
183 }
184 +/
185 
186 /// Draws a rectangle with a solid line.
187 /// The coordinates represent bounds (open on the right) for the outside of the rectangle.
188 void rect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c)
189 	if (isWritableView!V && is(COLOR : ViewColor!V))
190 {
191 	sort2(x1, x2);
192 	sort2(y1, y2);
193 	v.hline!CHECKED(x1, x2, y1  , c);
194 	v.hline!CHECKED(x1, x2, y2-1, c);
195 	v.vline!CHECKED(x1  , y1, y2, c);
196 	v.vline!CHECKED(x2-1, y1, y2, c);
197 }
198 
199 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b) // [)
200 	if (isWritableView!V && is(COLOR : ViewColor!V))
201 {
202 	sort2(x1, x2);
203 	sort2(y1, y2);
204 	static if (CHECKED)
205 	{
206 		if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return;
207 		if (x1 <    0) x1 =   0;
208 		if (y1 <    0) y1 =   0;
209 		if (x2 >= v.w) x2 = v.w;
210 		if (y2 >= v.h) y2 = v.h;
211 	}
212 	foreach (y; y1..y2)
213 		v.scanline(y)[x1..x2] = b;
214 }
215 
216 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c, COLOR b) // [)
217 	if (isWritableView!V && is(COLOR : ViewColor!V))
218 {
219 	v.rect!CHECKED(x1, y1, x2, y2, c);
220 	if (x2-x1>2 && y2-y1>2)
221 		v.fillRect!CHECKED(x1+1, y1+1, x2-1, y2-1, b);
222 }
223 
224 /// Unchecked! Make sure area is bounded.
225 void uncheckedFloodFill(V, COLOR)(auto ref V v, int x, int y, COLOR c)
226 	if (isDirectView!V && is(COLOR : ViewColor!V))
227 {
228 	v.floodFillPtr(&v[x, y], c, v[x, y]);
229 }
230 
231 private void floodFillPtr(V, COLOR)(auto ref V v, COLOR* pp, COLOR c, COLOR f)
232 	if (isDirectView!V && is(COLOR : ViewColor!V))
233 {
234 	COLOR* p0 = pp; while (*p0==f) p0--; p0++;
235 	COLOR* p1 = pp; while (*p1==f) p1++; p1--;
236 	auto stride = v.scanline(1).ptr-v.scanline(0).ptr;
237 	for (auto p=p0; p<=p1; p++)
238 		*p = c;
239 	p0 -= stride; p1 -= stride;
240 	for (auto p=p0; p<=p1; p++)
241 		if (*p == f)
242 			v.floodFillPtr(p, c, f);
243 	p0 += stride*2; p1 += stride*2;
244 	for (auto p=p0; p<=p1; p++)
245 		if (*p == f)
246 			v.floodFillPtr(p, c, f);
247 }
248 
249 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c)
250 	if (isWritableView!V && is(COLOR : ViewColor!V))
251 {
252 	int x0 = x>r?x-r:0;
253 	int y0 = y>r?y-r:0;
254 	int x1 = min(x+r, v.w-1);
255 	int y1 = min(y+r, v.h-1);
256 	int rs = sqr(r);
257 	// FUTURE: optimize
258 	foreach (py; y0..y1+1)
259 		foreach (px; x0..x1+1)
260 			if (sqr(x-px) + sqr(y-py) < rs)
261 				v[px, py] = c;
262 }
263 
264 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c)
265 	if (isWritableView!V && is(COLOR : ViewColor!V))
266 {
267 	int x0 = x>r1?x-r1:0;
268 	int y0 = y>r1?y-r1:0;
269 	int x1 = min(x+r1, v.w-1);
270 	int y1 = min(y+r1, v.h-1);
271 	int r0s = sqr(r0);
272 	int r1s = sqr(r1);
273 	if (a0 > a1)
274 		a1 += (2 * PI);
275 	foreach (py; y0..y1+1)
276 		foreach (px; x0..x1+1)
277 		{
278 			int dx = px-x;
279 			int dy = py-y;
280 			int rs = sqr(dx) + sqr(dy);
281 			if (r0s <= rs && rs < r1s)
282 			{
283 				real a = atan2(cast(real)dy, cast(real)dx);
284 				if (a0 <= a && a <= a1)
285 					v[px, py] = c;
286 				else
287 				{
288 					a += 2 * PI;
289 					if (a0 <= a && a <= a1)
290 						v[px, py] = c;
291 				}
292 			}
293 		}
294 }
295 
296 struct Coord { int x, y; string toString() { import std..string; return format("%s", [this.tupleof]); } }
297 
298 
299 /+
300 void fillPoly(V, COLOR)(auto ref V v, Coord[] coords, COLOR f)
301 	if (isWritableView!V && is(COLOR : ViewColor!V))
302 {
303 	int minY, maxY;
304 	minY = maxY = coords[0].y;
305 	foreach (c; coords[1..$])
306 		minY = min(minY, c.y),
307 		maxY = max(maxY, c.y);
308 
309 	foreach (y; minY..maxY+1)
310 	{
311 		int[] intersections;
312 		for (uint i=0; i<coords.length; i++)
313 		{
314 			auto c0=coords[i], c1=coords[i==$-1?0:i+1];
315 			if (y==c0.y)
316 			{
317 				assert(y == coords[i%$].y);
318 				int pi = i-1; int py;
319 				while ((py=coords[(pi+$)%$].y)==y)
320 					pi--;
321 				int ni = i+1; int ny;
322 				while ((ny=coords[ni%$].y)==y)
323 					ni++;
324 				if (ni > coords.length)
325 					continue;
326 				if ((py>y) == (y>ny))
327 					intersections ~= coords[i%$].x;
328 				i = ni-1;
329 			}
330 			else
331 			if (c0.y<y && y<c1.y)
332 				intersections ~= itpl(c0.x, c1.x, y, c0.y, c1.y);
333 			else
334 			if (c1.y<y && y<c0.y)
335 				intersections ~= itpl(c1.x, c0.x, y, c1.y, c0.y);
336 		}
337 
338 		assert(intersections.length % 2==0);
339 		intersections.sort();
340 		for (uint i=0; i<intersections.length; i+=2)
341 			v.hline!true(intersections[i], intersections[i+1], y, f);
342 	}
343 }
344 +/
345 
346 // No caps
347 void thickLine(V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, int r, COLOR c)
348 	if (isWritableView!V && is(COLOR : ViewColor!V))
349 {
350 	int dx = x2-x1;
351 	int dy = y2-y1;
352 	int d  = cast(int)sqrt(cast(float)(sqr(dx)+sqr(dy)));
353 	if (d==0) return;
354 
355 	int nx = dx*r/d;
356 	int ny = dy*r/d;
357 
358 	fillPoly([
359 		Coord(x1-ny, y1+nx),
360 		Coord(x1+ny, y1-nx),
361 		Coord(x2+ny, y2-nx),
362 		Coord(x2-ny, y2+nx),
363 	], c);
364 }
365 
366 // No caps
367 void thickLinePoly(V, COLOR)(auto ref V v, Coord[] coords, int r, COLOR c)
368 	if (isWritableView!V && is(COLOR : ViewColor!V))
369 {
370 	foreach (i; 0..coords.length)
371 		thickLine(coords[i].tupleof, coords[(i+1)%$].tupleof, r, c);
372 }
373 
374 // ************************************************************************************************************************************
375 
376 mixin template FixMath(ubyte coordinateBitsParam = 16)
377 {
378 	enum coordinateBits = coordinateBitsParam;
379 
380 	static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!");
381 	/// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision.
382 	alias fix  = SignedBitsType!(COLOR.channelBits   + coordinateBits);
383 	/// Type to hold temporary values for multiplication and division
384 	alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits);
385 
386 	static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!");
387 	fix tofix(T:int  )(T x) { return cast(fix) (x<<COLOR.channelBits); }
388 	fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); }
389 	T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); }
390 
391 	fix fixsqr(fix x)        { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); }
392 	fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); }
393 	fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); }
394 
395 	static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!");
396 	/// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc.
397 	alias COLOR.ChannelType frac;
398 	/// Type to hold temporary values for multiplication and division
399 	alias UnsignedBitsType!(COLOR.channelBits*2) frac2;
400 
401 	frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); }
402 	frac fixfpart(fix x) { return cast(frac)x; }
403 	frac fracsqr(frac x        ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); }
404 	frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); }
405 
406 	frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); }
407 }
408 
409 // ************************************************************************************************************************************
410 
411 void whiteNoise(V)(V v)
412 	if (isWritableView!V)
413 {
414 	import std.random;
415 	alias COLOR = ViewColor!V;
416 
417 	for (int y=0;y<v.h/2;y++)
418 		for (int x=0;x<v.w/2;x++)
419 			v[x*2, y*2] = COLOR.monochrome(uniform!(COLOR.ChannelType)());
420 
421 	// interpolate
422 	enum AVERAGE = q{(a+b)/2};
423 
424 	for (int y=0;y<v.h/2;y++)
425 		for (int x=0;x<v.w/2-1;x++)
426 			v[x*2+1, y*2  ] = COLOR.op!AVERAGE(v[x*2  , y*2], v[x*2+2, y*2  ]);
427 	for (int y=0;y<v.h/2-1;y++)
428 		for (int x=0;x<v.w/2;x++)
429 			v[x*2  , y*2+1] = COLOR.op!AVERAGE(v[x*2  , y*2], v[x*2  , y*2+2]);
430 	for (int y=0;y<v.h/2-1;y++)
431 		for (int x=0;x<v.w/2-1;x++)
432 			v[x*2+1, y*2+1] = COLOR.op!AVERAGE(v[x*2+1, y*2], v[x*2+2, y*2+2]);
433 }
434 
435 private template softRoundShape(bool RING)
436 {
437 	void softRoundShape(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
438 		if (isWritableView!V && is(COLOR : ViewColor!V))
439 	{
440 		mixin FixMath;
441 
442 		assert(r0 <= r1);
443 		assert(r1 <= r2);
444 		assert(r2 < 256); // precision constraint - see SqrType
445 		//int ix = cast(int)x;
446 		//int iy = cast(int)y;
447 		//int ir1 = cast(int)sqr(r1-1);
448 		//int ir2 = cast(int)sqr(r2+1);
449 		int x1 = cast(int)(x-r2-1); if (x1<0) x1=0;
450 		int y1 = cast(int)(y-r2-1); if (y1<0) y1=0;
451 		int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w;
452 		int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h;
453 
454 		static if (RING)
455 		auto r0s = r0*r0;
456 		auto r1s = r1*r1;
457 		auto r2s = r2*r2;
458 		//float rds = r2s - r1s;
459 
460 		fix fx = tofix(x);
461 		fix fy = tofix(y);
462 
463 		static if (RING)
464 		fix fr0s = tofix(r0s);
465 		fix fr1s = tofix(r1s);
466 		fix fr2s = tofix(r2s);
467 
468 		static if (RING)
469 		fix fr10 = fr1s - fr0s;
470 		fix fr21 = fr2s - fr1s;
471 
472 		for (int cy=y1;cy<y2;cy++)
473 		{
474 			auto row = v.scanline(cy);
475 			for (int cx=x1;cx<x2;cx++)
476 			{
477 				alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point
478 				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
479 
480 				//static frac alphafunc(frac x) { return fracsqr(x); }
481 				static frac alphafunc(frac x) { return x; }
482 
483 				static if (RING)
484 				{
485 					if (frs<fr0s)
486 						{}
487 					else
488 					if (frs<fr2s)
489 					{
490 						frac alpha;
491 						if (frs<fr1s)
492 							alpha =  alphafunc(cast(frac)fixdiv(frs-fr0s, fr10));
493 						else
494 							alpha = ~alphafunc(cast(frac)fixdiv(frs-fr1s, fr21));
495 						row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha);
496 					}
497 				}
498 				else
499 				{
500 					if (frs<fr1s)
501 						row[cx] = color;
502 					else
503 					if (frs<fr2s)
504 					{
505 						frac alpha = ~alphafunc(cast(frac)fixdiv(frs-fr1s, fr21));
506 						row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha);
507 					}
508 				}
509 			}
510 		}
511 	}
512 }
513 
514 void softRing(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color)
515 	if (isWritableView!V && is(COLOR : ViewColor!V))
516 {
517 	v.softRoundShape!true(x, y, r0, r1, r2, color);
518 }
519 
520 void softCircle(V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color)
521 	if (isWritableView!V && is(COLOR : ViewColor!V))
522 {
523 	v.softRoundShape!false(x, y, 0, r1, r2, color);
524 }
525 
526 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true)
527 {
528 	void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha)
529 		if (isWritableView!V && is(COLOR : ViewColor!V))
530 	{
531 		mixin FixMath;
532 
533 		void plot(bool CHECKED2)(int x, int y, frac f)
534 		{
535 			static if (CHECKED2)
536 				if (x<0 || x>=v.w || y<0 || y>=v.h)
537 					return;
538 
539 			COLOR* p = v.pixelPtr(x, y);
540 			static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha);
541 			*p = COLOR.op!q{.blend(a, b, c)}(color, *p, f);
542 		}
543 
544 		fix fx = tofix(x);
545 		fix fy = tofix(y);
546 		int ix = fixto!int(fx);
547 		int iy = fixto!int(fy);
548 		static if (CHECKED)
549 			if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h)
550 			{
551 				plot!false(ix  , iy  , fracmul(~fixfpart(fx), ~fixfpart(fy)));
552 				plot!false(ix  , iy+1, fracmul(~fixfpart(fx),  fixfpart(fy)));
553 				plot!false(ix+1, iy  , fracmul( fixfpart(fx), ~fixfpart(fy)));
554 				plot!false(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
555 				return;
556 			}
557 		plot!CHECKED(ix  , iy  , fracmul(~fixfpart(fx), ~fixfpart(fy)));
558 		plot!CHECKED(ix  , iy+1, fracmul(~fixfpart(fx),  fixfpart(fy)));
559 		plot!CHECKED(ix+1, iy  , fracmul( fixfpart(fx), ~fixfpart(fy)));
560 		plot!CHECKED(ix+1, iy+1, fracmul( fixfpart(fx),  fixfpart(fy)));
561 	}
562 }
563 
564 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color)
565 	if (isWritableView!V && is(COLOR : ViewColor!V))
566 {
567 	//aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf
568 	alias aaPutPixel!(CHECKED, false) f;
569 	f(v, x, y, color, 0);
570 }
571 
572 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha)
573 	if (isWritableView!V && is(COLOR : ViewColor!V))
574 {
575 	mixin(CheckHLine);
576 
577 	if (alpha==0)
578 		return;
579 	else
580 	if (alpha==frac.max)
581 		v.scanline(y)[x1..x2] = color;
582 	else
583 		foreach (ref p; v.scanline(y)[x1..x2])
584 			p = COLOR.op!q{.blend(a, b, c)}(color, p, alpha);
585 }
586 
587 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha)
588 	if (isWritableView!V && is(COLOR : ViewColor!V))
589 {
590 	mixin(CheckVLine);
591 
592 	if (alpha==0)
593 		return;
594 	else
595 	if (alpha==frac.max)
596 		foreach (y; y1..y2)
597 			v[x, y] = color;
598 	else
599 		foreach (y; y1..y2)
600 		{
601 			auto p = v.pixelPtr(x, y);
602 			*p = COLOR.op!q{.blend(a, b, c)}(color, *p, alpha);
603 		}
604 }
605 
606 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color)
607 	if (isWritableView!V && is(COLOR : ViewColor!V))
608 {
609 	mixin FixMath;
610 
611 	sort2(x1, x2);
612 	sort2(y1, y2);
613 	fix x1f = tofix(x1); int x1i = fixto!int(x1f);
614 	fix y1f = tofix(y1); int y1i = fixto!int(y1f);
615 	fix x2f = tofix(x2); int x2i = fixto!int(x2f);
616 	fix y2f = tofix(y2); int y2i = fixto!int(y2f);
617 
618 	v.vline!CHECKED(x1i, y1i+1, y2i, color, ~fixfpart(x1f));
619 	v.vline!CHECKED(x2i, y1i+1, y2i, color,  fixfpart(x2f));
620 	v.hline!CHECKED(x1i+1, x2i, y1i, color, ~fixfpart(y1f));
621 	v.hline!CHECKED(x1i+1, x2i, y2i, color,  fixfpart(y2f));
622 	v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(~fixfpart(x1f), ~fixfpart(y1f)));
623 	v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(~fixfpart(x1f),  fixfpart(y2f)));
624 	v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul( fixfpart(x2f), ~fixfpart(y1f)));
625 	v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul( fixfpart(x2f),  fixfpart(y2f)));
626 
627 	v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color);
628 }
629 
630 void aaLine(bool CHECKED=true, V, COLOR)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color)
631 	if (isWritableView!V && is(COLOR : ViewColor!V))
632 {
633 	// Simplistic straight-forward implementation. FUTURE: optimize
634 	if (abs(x1-x2) > abs(y1-y2))
635 		for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1))
636 			v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color);
637 	else
638 		for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1))
639 			v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color);
640 }
641 
642 void aaLine(bool CHECKED=true, V, COLOR, frac)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color, frac alpha)
643 	if (isWritableView!V && is(COLOR : ViewColor!V))
644 {
645 	// ditto
646 	if (abs(x1-x2) > abs(y1-y2))
647 		for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1))
648 			v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color, alpha);
649 	else
650 		for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1))
651 			v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color, alpha);
652 }
653 
654 unittest
655 {
656 	// Test instantiation
657 	import dplug.graphics.color;
658     ImageRef!RGB i;
659     i.w = 100;
660     i.h = 100;
661     i.pitch = 100;
662     i.pixels = (new RGB[100 * 100]).ptr;
663 
664 	auto c = RGB(1, 2, 3);
665 	i.whiteNoise();
666 	i.aaLine(10, 10, 20, 20, c);
667 	i.aaLine(10f, 10f, 20f, 20f, c, 100);
668 	i.rect(10, 10, 20, 20, c);
669 	i.fillRect(10, 10, 20, 20, c);
670 	i.aaFillRect(10, 10, 20, 20, c);
671 	i.vline(10, 10, 20, c);
672 	i.vline(10, 10, 20, c);
673 //	i.line(10, 10, 20, 20, c);
674 	i.fillCircle(10, 10, 10, c);
675 	i.fillSector(10, 10, 10, 10, 0.0, (2 * PI), c);
676 	i.softRing(50, 50, 10, 15, 20, c);
677 	i.softCircle(50, 50, 10, 15, c);
678 //	i.fillPoly([Coord(10, 10), Coord(10, 20), Coord(20, 20)], c);
679 	i.uncheckedFloodFill(15, 15, RGB(4, 5, 6));
680 }
681