1 module stb.image;
2 
3 import
4 		core.bitop,
5 		std.file,
6 		std.path,
7 		std.conv,
8 		std.math,
9 		std.range,
10 		std.string,
11 		std.mmfile,
12 		std.typecons,
13 		std.bitmanip,
14 		std.exception,
15 		std.algorithm,
16 
17 		core.stdc.string,
18 		etc.c.zlib,
19 
20 		stb.image.write,
21 		stb.image.resize,
22 		stb.image.binding;
23 
24 
25 struct Color
26 {
27 	this(uint r, uint g, uint b, uint a = 0)
28 	{
29 		this.r = cast(ubyte)r;
30 		this.g = cast(ubyte)g;
31 		this.b = cast(ubyte)b;
32 		this.a = cast(ubyte)a;
33 	}
34 
35 	static fromInt(uint n)
36 	{
37 		n = n.bswap;
38 		return *cast(Color*)&n;
39 	}
40 
41 	const
42 	{
43 		auto opBinary(string op: `*`)(in Color c)
44 		{
45 			return Color(
46 							r * c.r / 255,
47 							g * c.g / 255,
48 							b * c.b / 255,
49 							a * c.a / 255
50 												);
51 		}
52 
53 		auto opBinary(string op: `+`)(in Color c)
54 		{
55 			return Color(
56 							min(r + c.r, 255),
57 							min(g + c.g, 255),
58 							min(b + c.b, 255),
59 							min(a + c.a, 255)
60 												);
61 		}
62 
63 		auto opBinary(string op: `^`)(in Color c)
64 		{
65 			// TODO: CHECK FORCORRECTNESS
66 			auto od = 255 - c.a;
67 			auto ra = c.a + a * od / 255;
68 
69 			// TODO: WORKAROUND
70 			if(!ra) ra = 1;
71 
72 			return Color(
73 							(c.r * c.a + r * a * od / 255) / ra,
74 							(c.g * c.a + g * a * od / 255) / ra,
75 							(c.b * c.a + b * a * od / 255) / ra,
76 							ra
77 																	);
78 		}
79 
80 		bool isGray(ubyte n)
81 		{
82 			auto a = min(r, g, b), b = max(r, g, b);
83 			return abs(a - b) < 30 && b < n;
84 		}
85 
86 		bool compare(in Color c, ubyte d)
87 		{
88 			return abs(r - c.r) + abs(g - c.g) + abs(b - c.b) + abs(a - c.a) <= d * 4;
89 		}
90 	}
91 
92 	ref opOpAssign(string op)(in Color c)
93 	{
94 		return this = this.opBinary!op(c);
95 	}
96 
97 	ubyte
98 			r,
99 			g,
100 			b,
101 			a;
102 }
103 
104 static assert(Color.sizeof == 4);
105 
106 enum
107 {
108 	colorGray = Color(128, 128, 128, 200),
109 	colorBlack = Color(0, 0, 0, 255),
110 	colorWhite = Color(255, 255, 255, 255),
111 	colorTransparent = Color.init,
112 }
113 
114 enum
115 {
116 	IM_BMP,
117 	IM_TGA,
118 	IM_PNG,
119 	IM_JPG,
120 }
121 
122 final class Image
123 {
124 	this(string name)
125 	{
126 		auto f = new MmFile(name);
127 
128 		scope(exit)
129 		{
130 			f.destroy;
131 		}
132 
133 		this(f[]);
134 	}
135 
136 	this(in void[] data)
137 	{
138 		auto p = cast(Color*)stbi_load_from_memory(data.ptr, cast(uint)data.length, cast(int*)&_w, cast(int*)&_h, null, 4);
139 		enforce(p, `unknown/corrupted image`);
140 
141 		_data = p[0.._w * _h].dup;
142 		stbi_image_free(p);
143 	}
144 
145 	this(uint w, uint h, const(void)[] data = null)
146 	{
147 		assert(w && h);
148 
149 		if(data.length)
150 		{
151 			_data = cast(Color[])data;
152 			assert(_data.length == w * h);
153 		}
154 		else
155 		{
156 			_data = new Color[w * h];
157 		}
158 
159 		_w = w;
160 		_h = h;
161 	}
162 
163 	this(in Image im)
164 	{
165 		_w = im._w;
166 		_h = im._h;
167 		_data = im._data.dup;
168 	}
169 
170 	auto blit(in Image im, uint x, uint y)
171 	{
172 		assert(x + im._w <= _w && y + im._h <= _h);
173 
174 		foreach(j; 0..im._h)
175 		{
176 			memcpy(&this[x, j + y], &im[0, j], im.w * 4);
177 		}
178 
179 		return this;
180 	}
181 
182 	const
183 	{
184 		auto dup()
185 		{
186 			return new Image(this);
187 		}
188 
189 		void saveToFile(string name)
190 		{
191 			ubyte f;
192 
193 			switch(name.extension.stripLeft(`.`).toLower)
194 			{
195 			case `png`:
196 				f = IM_PNG;
197 				break;
198 			case `bmp`:
199 				f = IM_BMP;
200 				break;
201 			case `tga`:
202 				f = IM_TGA;
203 				break;
204 			case `jpg`:
205 			case `jpeg`:
206 				f = IM_JPG;
207 				break;
208 			default:
209 				throw new Exception(`unknown image extension`);
210 			}
211 
212 			saveToFile(name, f);
213 		}
214 
215 		void saveToFile(string name, ubyte format)
216 		{
217 			std.file.write(name, save(format));
218 		}
219 
220 		auto save(ubyte format)
221 		{
222 			bool res;
223 			void[] data;
224 
225 			final switch(format)
226 			{
227 			case IM_BMP:
228 				res = !!stbi_write_bmp_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr);
229 				break;
230 			case IM_TGA:
231 				res = !!stbi_write_tga_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr);
232 				break;
233 			case IM_PNG:
234 				res = !!stbi_write_png_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr, 0);
235 				break;
236 			case IM_JPG:
237 				//res = !!stbi_write_jpg_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr, 95);
238 			}
239 
240 			assert(res);
241 			return data;
242 		}
243 
244 		auto subImage(uint x, uint y, uint w, uint h)
245 		{
246 			assert(x + w <= _w && y + h <= _h);
247 			auto res = new Image(w, h);
248 
249 			foreach(i; 0..w)
250 			foreach(j; 0..h)
251 				res[j][] = this[y + j][x..x + w][];
252 
253 			return res;
254 		}
255 
256 		auto resize(uint w, uint h)
257 		{
258 			auto res = new Image(w, h);
259 			stbir_resize_uint8(cast(ubyte*)_data.ptr, _w, _h, 0, cast(ubyte*)res[].ptr, w, h, 0, 4);
260 			return res;
261 		}
262 
263 		/*auto rotate(float angle)
264 		{
265 			Image res;
266 			if(angle > 85 && angle < 95)
267 			{
268 				res = new Image(_h, _w);
269 
270 				foreach(i; 0.._w)
271 				foreach(j; 0.._h)
272 					res[j, i] = this[i, j]; // TODO: IT'S -90, NOT 90
273 					//res[j, i] = this[_w - i - 1, j];
274 			}
275 			return res;
276 		}*/
277 	}
278 
279 	void clean()
280 	{
281 		_data
282 				.filter!((ref a) => a.compare(Color(255, 0, 255, 255), 10))
283 				.each!((ref a) => a.a = 0);
284 
285 		normalizeTransparentPixels;
286 	}
287 
288 	void normalizeTransparentPixels()
289 	{
290 		const uint N = _w * _h;
291 
292 		auto opaque = new byte[N];
293 		auto loose = new bool[N];
294 
295 		uint[] pending;
296 		uint[] pendingNext;
297 
298 		static immutable byte[2][8] offsets =
299 		[
300 			[ -1, -1 ],
301 			[  0, -1 ],
302 			[  1, -1 ],
303 			[ -1,  0 ],
304 			[  1,  0 ],
305 			[ -1,  1 ],
306 			[  0,  1 ],
307 			[  1,  1 ]
308 		];
309 
310 		auto image = cast(ubyte[])_data;
311 
312 		for(uint i = 0, j = 3; i < N; i++, j += 4)
313 		{
314 			if(image[j] == 0)
315 			{
316 				bool isLoose = true;
317 
318 				int x = i % _w;
319 				int y = i / _w;
320 
321 				for(int k = 0; k < 8; k++)
322 				{
323 					int s = offsets[k][0];
324 					int t = offsets[k][1];
325 
326 					if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h)
327 					{
328 						uint index = j + 4 * (s + t * _w);
329 
330 						if(image[index] != 0)
331 						{
332 							isLoose = false;
333 							break;
334 						}
335 					}
336 				}
337 
338 				if(isLoose)
339 				{
340 					loose[i] = true;
341 				}
342 				else
343 				{
344 					pending ~= i;
345 				}
346 			}
347 			else
348 			{
349 				opaque[i] = -1;
350 			}
351 		}
352 
353 		while(pending.length > 0)
354 		{
355 			pendingNext = null;
356 
357 			for(uint p = 0; p < pending.length; p++)
358 			{
359 				uint i = pending[p] * 4;
360 				uint j = pending[p];
361 
362 				int x = j % _w;
363 				int y = j / _w;
364 
365 				int r = 0;
366 				int g = 0;
367 				int b = 0;
368 
369 				int count = 0;
370 
371 				for(uint k = 0; k < 8; k++)
372 				{
373 					int s = offsets[k][0];
374 					int t = offsets[k][1];
375 
376 					if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h)
377 					{
378 						t *= _w;
379 
380 						if(opaque[j + s + t] & 1)
381 						{
382 							uint index = i + 4 * (s + t);
383 
384 							r += image[index + 0];
385 							g += image[index + 1];
386 							b += image[index + 2];
387 
388 							count++;
389 						}
390 					}
391 				}
392 
393 				if(count > 0)
394 				{
395 					image[i + 0] = cast(ubyte)(r / count);
396 					image[i + 1] = cast(ubyte)(g / count);
397 					image[i + 2] = cast(ubyte)(b / count);
398 
399 					opaque[j] = cast(byte)0xFE;
400 
401 					for(uint k = 0; k < 8; k++)
402 					{
403 						int s = offsets[k][0];
404 						int t = offsets[k][1];
405 
406 						if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h)
407 						{
408 							uint index = j + s + t * _w;
409 
410 							if(loose[index])
411 							{
412 								pendingNext ~= index;
413 								loose[index] = false;
414 							}
415 						}
416 					}
417 				}
418 				else
419 				{
420 					pendingNext ~= j;
421 				}
422 			}
423 
424 			if(pendingNext.length > 0)
425 			{
426 				for(uint p = 0; p < pending.length; p++)
427 				{
428 					opaque[pending[p]] >>= 1;
429 				}
430 			}
431 
432 			pending.swap(pendingNext);
433 		}
434 	}
435 
436 	auto flip()
437 	{
438 		foreach(j; 0.._h / 2)
439 		foreach(i; 0.._w)
440 		{
441 			swap(this[i, j], this[i, _h - j - 1]);
442 		}
443 
444 		return this;
445 	}
446 
447 	auto mirror()
448 	{
449 		foreach(i; 0.._w / 2)
450 		foreach(j; 0.._h)
451 		{
452 			swap(this[i, j], this[_w - i - 1, j]);
453 		}
454 
455 		return this;
456 	}
457 
458 	inout
459 	{
460 		auto toMipmaps()
461 		{
462 			inout(Image)[] res = [ this ];
463 
464 			while(true)
465 			{
466 				auto im = res.back;
467 
468 				if(im.w == 1 && im.h == 1)
469 				{
470 					break;
471 				}
472 
473 				auto
474 						w = max(im.w / 2, 1),
475 						h = max(im.h / 2, 1);
476 
477 				res ~= cast(inout(Image))resize(w, h);
478 			}
479 
480 			return res;
481 		}
482 
483 		ref opIndex(uint x, uint y)
484 		{
485 			return _data[y * _w + x];
486 		}
487 
488 		auto opIndex(uint y)
489 		{
490 			auto c = y * _w;
491 			return _data[c..c + _w];
492 		}
493 
494 		auto opSlice()
495 		{
496 			return _data;
497 		}
498 	}
499 
500 	@property const
501 	{
502 		auto w() { return _w; }
503 		auto h() { return _h; }
504 	}
505 
506 private:
507 	static extern(C) void writerCallback(void* ptr, void* data, int len)
508 	{
509 		*cast(void[]*)ptr ~= data[0..len];
510 	}
511 
512 	Color[] _data;
513 
514 	uint
515 			_w,
516 			_h;
517 }
518 
519 private extern(C):
520 
521 ubyte* compress_for_stb_image_write(in ubyte* data, uint len, uint* resLen, int level)
522 {
523 	import core.stdc.stdlib;
524 
525 	auto dst = compressBound(len);
526 	auto res = cast(ubyte*)malloc(dst);
527 
528 	if(compress2(res, &dst, data, len, level) == Z_OK)
529 	{
530 		*resLen = cast(uint)dst;
531 		return res;
532 	}
533 
534 	free(res);
535 	return null;
536 }