1 /**
2  * In-memory images.
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.image;
16 
17 import std.conv : to;
18 import std..string : format;
19 
20 import dplug.graphics.view;
21 
22 /// Represents a reference to COLOR data
23 /// already existing elsewhere in memory.
24 /// Assumes that pixels are stored row-by-row,
25 /// with a known distance between each row.
26 struct ImageRef(COLOR)
27 {
28 	int w, h;
29 	size_t pitch; /// In bytes, not COLORs
30 	COLOR* pixels;
31 
32 	/// Returns an array for the pixels at row y.
33 	COLOR[] scanline(int y)
34 	{
35 		assert(y>=0 && y<h);
36 		assert(pitch);
37 		auto row = cast(COLOR*)(cast(ubyte*)pixels + y*pitch);
38 		return row[0..w];
39 	}
40 
41 	mixin DirectView;
42 }
43 
44 unittest
45 {
46 	static assert(isDirectView!(ImageRef!ubyte));
47 }
48 
49 /// Convert a direct view to an ImageRef.
50 /// Assumes that the rows are evenly spaced.
51 ImageRef!(ViewColor!SRC) toRef(SRC)(auto ref SRC src)
52 	if (isDirectView!SRC)
53 {
54 	return ImageRef!(ViewColor!SRC)(src.w, src.h,
55 		src.h > 1 ? cast(ubyte*)src.scanline(1) - cast(ubyte*)src.scanline(0) : src.w,
56 		src.scanline(0).ptr);
57 }
58 
59 
60 // ***************************************************************************
61 
62 /// Performs linear downscale by a constant factor
63 template downscale(int HRX, int HRY=HRX)
64 {
65 	auto downscale(SRC, TARGET)(auto ref SRC src, auto ref TARGET target)
66 		if (isDirectView!SRC && isWritableView!TARGET)
67 	{
68 		alias lr = target;
69 		alias hr = src;
70 
71 		assert(hr.w % HRX == 0 && hr.h % HRY == 0, "Size mismatch");
72 
73 		lr.size(hr.w / HRX, hr.h / HRY);
74 
75 		foreach (y; 0..lr.h)
76 			foreach (x; 0..lr.w)
77 			{
78 				static if (HRX*HRY <= 0x100)
79 					enum EXPAND_BYTES = 1;
80 				else
81 				static if (HRX*HRY <= 0x10000)
82 					enum EXPAND_BYTES = 2;
83 				else
84 					static assert(0);
85 				static if (is(typeof(COLOR.init.a))) // downscale with alpha
86 				{
87 					ExpandType!(COLOR, EXPAND_BYTES+COLOR.init.a.sizeof) sum;
88 					ExpandType!(typeof(COLOR.init.a), EXPAND_BYTES) alphaSum;
89 					auto start = y*HRY*hr.stride + x*HRX;
90 					foreach (j; 0..HRY)
91 					{
92 						foreach (p; hr.pixels[start..start+HRX])
93 						{
94 							foreach (i, f; p.tupleof)
95 								static if (p.tupleof[i].stringof != "p.a")
96 								{
97 									enum FIELD = p.tupleof[i].stringof[2..$];
98 									mixin("sum."~FIELD~" += cast(typeof(sum."~FIELD~"))p."~FIELD~" * p.a;");
99 								}
100 							alphaSum += p.a;
101 						}
102 						start += hr.stride;
103 					}
104 					if (alphaSum)
105 					{
106 						auto result = cast(COLOR)(sum / alphaSum);
107 						result.a = cast(typeof(result.a))(alphaSum / (HRX*HRY));
108 						lr[x, y] = result;
109 					}
110 					else
111 					{
112 						static assert(COLOR.init.a == 0);
113 						lr[x, y] = COLOR.init;
114 					}
115 				}
116 				else
117 				{
118 					ExpandChannelType!(ViewColor!SRC, EXPAND_BYTES) sum;
119 					auto x0 = x*HRX;
120 					auto x1 = x0+HRX;
121 					foreach (j; y*HRY..(y+1)*HRY)
122 						foreach (p; hr.scanline(j)[x0..x1])
123 							sum += p;
124 					lr[x, y] = cast(ViewColor!SRC)(sum / (HRX*HRY));
125 				}
126 			}
127 
128 		return target;
129 	}
130 
131 	auto downscale(SRC)(auto ref SRC src)
132 		if (isView!SRC)
133 	{
134 		ViewImage!SRC target;
135 		return src.downscale(target);
136 	}
137 }
138 
139 // ***************************************************************************
140 
141 /// Copy the indicated row of src to a COLOR buffer.
142 void copyScanline(SRC, COLOR)(auto ref SRC src, int y, COLOR[] dst)
143 	if (isView!SRC && is(COLOR == ViewColor!SRC))
144 {
145 	static if (isDirectView!SRC)
146 		dst[] = src.scanline(y)[];
147 	else
148 	{
149 		assert(src.w == dst.length);
150 		foreach (x; 0..src.w)
151 			dst[x] = src[x, y];
152 	}
153 }
154 
155 /// Copy a view's pixels (top-to-bottom) to a COLOR buffer.
156 void copyPixels(SRC, COLOR)(auto ref SRC src, COLOR[] dst)
157 	if (isView!SRC && is(COLOR == ViewColor!SRC))
158 {
159 	assert(dst.length == src.w * src.h);
160 	foreach (y; 0..src.h)
161 		src.copyScanline(y, dst[y*src.w..(y+1)*src.w]);
162 }
163 
164 // ***************************************************************************
165 
166 // Workaround for https://d.puremagic.com/issues/show_bug.cgi?id=12433
167 
168 struct InputColor {}
169 alias GetInputColor(COLOR, INPUT) = Select!(is(COLOR == InputColor), INPUT, COLOR);
170 
171 struct TargetColor {}
172 enum isTargetColor(C, TARGET) = is(C == TargetColor) || is(C == ViewColor!TARGET);
173 
174 // ***************************************************************************