1 /**
2  * A `View` is the base abstraction for images. 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.view;
16 
17 import std.functional;
18 import std.typetuple;
19 import std.algorithm.mutation: swap;
20 import std.math;
21 
22 import dplug.core.math;
23 
24 public import dplug.graphics.color;
25 
26 /// A view is any type which provides a width, height,
27 /// and can be indexed to get the color at a specific
28 /// coordinate.
29 enum isView(T) =
30 	is(typeof(T.init.w) : size_t) && // width
31 	is(typeof(T.init.h) : size_t) && // height
32 	is(typeof(T.init[0, 0])     );   // color information
33 
34 /// Returns the color type of the specified view.
35 /// By convention, colors are structs with numeric
36 /// fields named after the channel they indicate.
37 alias ViewColor(T) = typeof(T.init[0, 0]);
38 
39 /// Views can be read-only or writable.
40 enum isWritableView(T) =
41 	isView!T &&
42 	is(typeof(T.init[0, 0] = ViewColor!T.init));
43 
44 /// Optionally, a view can also provide direct pixel
45 /// access. We call these "direct views".
46 enum isDirectView(T) =
47 	isView!T &&
48 	is(typeof(T.init.scanline(0)) : ViewColor!T[]);
49 
50 /// Mixin which implements view primitives on top of
51 /// existing direct view primitives.
52 mixin template DirectView()
53 {
54 	alias COLOR = typeof(scanline(0)[0]);
55 
56 	/// Implements the view[x, y] operator.
57 	ref COLOR opIndex(int x, int y)
58 	{
59 		return scanline(y)[x];
60 	}
61 
62 	/// Implements the view[x, y] = c operator.
63 	COLOR opIndexAssign(COLOR value, int x, int y)
64 	{
65 		return scanline(y)[x] = value;
66 	}
67 }
68 
69 // ***************************************************************************
70 
71 /// Returns a view which calculates pixels
72 /// on-demand using the specified formula.
73 template procedural(alias formula)
74 {
75 	alias fun = binaryFun!(formula, "x", "y");
76 	alias COLOR = typeof(fun(0, 0));
77 
78 	auto procedural(int w, int h)
79 	{
80 		struct Procedural
81 		{
82 			int w, h;
83 
84 			auto ref COLOR opIndex(int x, int y)
85 			{
86 				assert(x >= 0 && y >= 0 && x < w && y < h);
87 				return fun(x, y);
88 			}
89 		}
90 		return Procedural(w, h);
91 	}
92 }
93 
94 /// Returns a view of the specified dimensions
95 /// and same solid color.
96 auto solid(COLOR)(COLOR c, int w, int h)
97 {
98 	return procedural!((x, y) => c)(w, h);
99 }
100 
101 /// Return a 1x1 view of the specified color.
102 /// Useful for testing.
103 auto onePixel(COLOR)(COLOR c)
104 {
105 	return solid(c, 1, 1);
106 }
107 
108 unittest
109 {
110 	assert(onePixel(42)[0, 0] == 42);
111 }
112 
113 // ***************************************************************************
114 
115 /// Blits a view onto another.
116 /// The views must have the same size.
117 void blitTo(SRC, DST)(auto ref SRC src, auto ref DST dst)
118 	if (isView!SRC && isWritableView!DST)
119 {
120 	assert(src.w == dst.w && src.h == dst.h, "View size mismatch");
121 	foreach (y; 0..src.h)
122 	{
123 		static if (isDirectView!SRC && isDirectView!DST)
124 			dst.scanline(y)[] = src.scanline(y)[];
125 		else
126 		{
127 			foreach (x; 0..src.w)
128 				dst[x, y] = src[x, y];
129 		}
130 	}
131 }
132 
133 /// Helper function to blit an image onto another at a specified location.
134 void blitTo(SRC, DST)(auto ref SRC src, auto ref DST dst, int x, int y)
135 {
136 	src.blitTo(dst.crop(x, y, x+src.w, y+src.h));
137 }
138 
139 /// Default implementation for the .size method.
140 /// Asserts that the view has the desired size.
141 void size(V)(auto ref V src, int w, int h)
142 	if (isView!V)
143 {
144 	assert(src.w == w && src.h == h, "Wrong size for " ~ V.stringof);
145 }
146 
147 // ***************************************************************************
148 
149 /// Mixin which implements view primitives on top of
150 /// another view, using a coordinate transform function.
151 mixin template Warp(V)
152 	if (isView!V)
153 {
154 	V src;
155 
156 	auto ref ViewColor!V opIndex(int x, int y)
157 	{
158 		warp(x, y);
159 		return src[x, y];
160 	}
161 
162 	static if (isWritableView!V)
163 	ViewColor!V opIndexAssign(ViewColor!V value, int x, int y)
164 	{
165 		warp(x, y);
166 		return src[x, y] = value;
167 	}
168 }
169 
170 /// Crop a view to the specified rectangle.
171 auto crop(V)(auto ref V src, int x0, int y0, int x1, int y1)
172 	if (isView!V)
173 {
174 	assert( 0 <=    x0 &&  0 <=    y0);
175 	assert(x0 <=    x1 && y0 <=    y1);
176 	assert(x1 <= src.w && y1 <= src.h);
177 
178 	static struct Crop
179 	{
180 		mixin Warp!V;
181 
182 		int x0, y0, x1, y1;
183 
184 		@property int w() { return x1-x0; }
185 		@property int h() { return y1-y0; }
186 
187 		void warp(ref int x, ref int y)
188 		{
189 			x += x0;
190 			y += y0;
191 		}
192 
193 		static if (isDirectView!V)
194 		ViewColor!V[] scanline(int y)
195 		{
196 			return src.scanline(y0+y)[x0..x1];
197 		}
198 	}
199 
200 	static assert(isDirectView!V == isDirectView!Crop);
201 
202 	return Crop(src, x0, y0, x1, y1);
203 }
204 
205 unittest
206 {
207 	auto g = procedural!((x, y) => y)(1, 256);
208 	auto c = g.crop(0, 10, 1, 20);
209 	assert(c[0, 0] == 10);
210 }