1 module farbfelded;
2 
3 private
4 {
5 	import std.algorithm;
6 	import std.conv;
7 	import std.math;
8 	import std.stdio;
9 	import std.string;
10 	
11 	template addProperty(T, string propertyName, string defaultValue = T.init.to!string)
12 	{	 
13 		const char[] addProperty = format(
14 			`
15 			private %2$s %1$s = %4$s;
16 	 
17 			void set%3$s(%2$s %1$s)
18 			{
19 				this.%1$s = %1$s;
20 			}
21 	 
22 			%2$s get%3$s()
23 			{
24 				return %1$s;
25 			}
26 			`,
27 			"_" ~ propertyName.toLower,
28 			T.stringof,
29 			propertyName,
30 			defaultValue
31 			);
32 	}
33 	
34 	enum BYTE_ORDER
35 	{
36 		LITTLE_ENDIAN,
37 		BIG_ENDIAN
38 	}
39 	
40 	T buildFromBytes(T)(BYTE_ORDER byteOrder, ubyte[] bytes...)
41 	{
42 		T mask;
43 		size_t shift;
44 	
45 		foreach (i, e; bytes)
46 		{
47 			final switch (byteOrder) with (BYTE_ORDER)
48 			{
49 				case LITTLE_ENDIAN:
50 					shift = (i << 3);
51 					break;
52 				case BIG_ENDIAN:
53 					shift = ((bytes.length - i - 1) << 3);
54 					break;
55 			}
56 			mask |= (e << shift);
57 		}
58 	
59 		return mask;
60 	}
61 	
62 	auto buildFromValue(T)(BYTE_ORDER byteOrder, T value)
63 	{
64 		ubyte[] data;
65 		T mask = cast(T) 0xff;
66 		size_t shift;
67 		 
68 		foreach (i; 0..T.sizeof)
69 		{		
70 			final switch (byteOrder) with (BYTE_ORDER)
71 			{
72 				case LITTLE_ENDIAN:
73 					shift = (i << 3);
74 					break;
75 				case BIG_ENDIAN:
76 					shift = ((T.sizeof - i - 1) << 3);
77 					break;
78 			}
79 			
80 			data ~= cast(ubyte) (
81 				(value & (mask << shift)) >> shift
82 			);
83 		}
84 		 
85 		return data;
86 	}
87 }
88 
89 
90 class RGBAColor
91 {
92 	mixin(addProperty!(int, "R"));
93 	mixin(addProperty!(int, "G"));
94 	mixin(addProperty!(int, "B"));
95 	mixin(addProperty!(int, "A"));
96 
97 	this(int R = 0, int G = 0, int B = 0, int A = 0)
98 	{
99 		this._r = R;
100 		this._g = G;
101 		this._b = B;
102 		this._a = A;
103 	}
104 
105 	const float luminance709()
106 	{
107 	   return (_r  * 0.2126f + _g  * 0.7152f + _b  * 0.0722f);
108 	}
109 	
110 	const float luminance601()
111 	{
112 	   return (_r * 0.3f + _g * 0.59f + _b * 0.11f);
113 	}
114 	
115 	const float luminanceAverage()
116 	{
117 	   return (_r + _g + _b) / 3.0;
118 	}
119 
120 	alias luminance = luminance709;
121 
122 	override string toString()
123 	{		
124 		return format("RGBAColor(%d, %d, %d, %d, I = %f)", _r, _g, _b, _a, this.luminance);
125 	}
126 
127 	RGBAColor opBinary(string op, T)(auto ref T rhs)
128 	{
129 		return mixin(
130 			format(`new RGBAColor( 
131 				clamp(cast(int) (_r  %1$s rhs), 0, 65535),
132 				clamp(cast(int) (_g  %1$s rhs), 0, 65535),
133 				clamp(cast(int) (_b  %1$s rhs), 0, 65535),
134 				clamp(cast(int) (_a  %1$s rhs), 0, 65535)
135 				)
136 			`,
137 			op
138 			)
139 		);
140 	}
141 
142 	RGBAColor opBinary(string op)(RGBAColor rhs)
143 	{
144 		return mixin(
145 			format(`new RGBAColor( 
146 				clamp(cast(int) (_r  %1$s rhs.getR), 0, 65535),
147 				clamp(cast(int) (_g  %1$s rhs.getG), 0, 65535),
148 				clamp(cast(int) (_b  %1$s rhs.getB), 0, 65535),
149 				clamp(cast(int) (_a  %1$s rhs.getA), 0, 65535)
150 				)
151 			`,
152 			op
153 			)
154 		);
155 	}
156 }
157 
158 
159 class FarbfeldImage
160 {
161 	mixin(addProperty!(uint, "Width"));
162 	mixin(addProperty!(uint, "Height"));
163 	
164 	private
165 	{
166 		RGBAColor[] _image;
167 
168 		auto actualIndex(size_t i)
169 		{
170 			auto S = _width * _height;
171 		
172 			return clamp(i, 0, S - 1);
173 		}
174 
175 		auto actualIndex(size_t i, size_t j)
176 		{
177 			auto W = cast(size_t) clamp(i, 0, _width - 1);
178 			auto H = cast(size_t) clamp(j, 0, _height - 1);
179 			auto S = _width * _height;
180 		
181 			return clamp(W + H * _width, 0, S);
182 		}
183 	}
184 
185 	this(uint width = 0, uint height = 0, RGBAColor color = new RGBAColor(0, 0, 0, 0))
186 	{
187 		this._width = width;
188 		this._height = height;
189 
190 		foreach (x; 0.._width)
191 		{
192 			foreach (y; 0.._height)
193 			{
194 				_image ~= color;
195 			}	
196 		}
197 	}
198 
199 	RGBAColor opIndexAssign(RGBAColor color, size_t x, size_t y)
200 	{
201 		_image[actualIndex(x, y)] = color;
202 		return color;
203 	}
204 
205 	RGBAColor opIndexAssign(RGBAColor color, size_t x)
206 	{
207 		_image[actualIndex(x)] = color;
208 		return color;
209 	}
210 
211 	RGBAColor opIndex(size_t x, size_t y)
212 	{
213 		return _image[actualIndex(x, y)];
214 	}
215 
216 	RGBAColor opIndex(size_t x)
217 	{
218 		return _image[actualIndex(x)];
219 	}
220 
221 	override string toString()
222 	{
223 		string accumulator = "[";
224 
225 		foreach (x; 0.._width)
226 		{
227 			string tmp = "[";
228 			foreach (y; 0.._height)
229 			{
230 				tmp ~= _image[actualIndex(x, y)].toString ~ ", ";				
231 			}
232 			tmp = tmp[0..$-2] ~ "], ";
233 			accumulator ~= tmp;
234 		}
235 		return accumulator[0..$-2] ~ "]";
236 	}
237 
238 	alias width = getWidth;
239 	alias height = getHeight;
240 
241 	final RGBAColor[] array()
242 	{
243 		return _image;
244 	}
245 	
246 	final void array(RGBAColor[] image)
247 	{
248 		_image = image;
249 	}
250 
251 	final void changeCapacity(uint x, uint y)
252 	{
253 		long newLength = (x * y);
254 		
255 		if (newLength > _image.length)
256 		{
257 			auto restLength = cast(long) newLength - _image.length;
258 			_image.length += cast(size_t) restLength;
259 		}
260 		else
261 		{
262 			if (newLength < _image.length)
263 			{
264 				auto restLength = cast(long) _image.length - newLength;
265 				_image.length -= cast(size_t) restLength;
266 			}
267 		}
268 		_width = x;
269 		_height = y;
270 	}
271 	
272 	
273 	void load(string filename)
274 	{
275 		File file;
276 		file.open(filename, `rb`);
277 		
278 		// magic number is `farbfeld` (field size: 8 bytes)
279 		auto magicNumber = new void[8];
280 		file.rawRead!void(magicNumber);
281 		// image width (field size: 4 bytes) and image height (field size: 4 bytes)
282 		auto imageSizes = new ubyte[8];
283 		file.rawRead!ubyte(imageSizes);
284 		
285 		_width = buildFromBytes!uint(BYTE_ORDER.BIG_ENDIAN, imageSizes[0..4]);
286 		_height = buildFromBytes!uint(BYTE_ORDER.BIG_ENDIAN, imageSizes[4..$]);
287 		_image = [];
288 		
289 		foreach (i; 0.._width)
290 		{
291 			foreach (j; 0.._height)
292 			{
293 				auto pixel = new ubyte[8];
294 				file.rawRead!ubyte(pixel);
295 				
296 				auto R = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[0..2]);
297 				auto G = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[2..4]);
298 				auto B = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[4..6]);
299 				auto A = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[6..$]);
300 				
301 				_image ~= new RGBAColor(R, G, B, A);
302 			}
303 		}
304 	}
305 	
306 	
307 	void save(string filename)
308 	{
309 		File file;
310 		file.open(filename, `wb`);
311 		
312 		// magic number
313 		file.write(`farbfeld`);
314 		//width
315 		file.write(cast(char[]) buildFromValue!uint(BYTE_ORDER.BIG_ENDIAN, _width));
316 		// height
317 		file.write(cast(char[]) buildFromValue!uint(BYTE_ORDER.BIG_ENDIAN, _height));
318 		
319 		foreach (i; 0..(_width * _height))
320 		{
321 			auto pixel = _image[i];
322 			
323 			auto R = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getR);
324 			auto G = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getG);
325 			auto B = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getB);
326 			auto A = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getA);
327 			
328 			file.write(cast(char[]) R);
329 			file.write(cast(char[]) G);
330 			file.write(cast(char[]) B);
331 			file.write(cast(char[]) A);
332 		}
333 	}
334 }