1 module utga;
2 
3 import std.stdio;
4 import std.algorithm;
5 import std.exception;
6 import core.stdc.string;
7 
8 
9 struct TGAHeader {
10 align (1):
11 	ubyte	idlength;
12 	ubyte 	colormaptype;
13 	ubyte 	datatypecode;
14 	ushort 	colormaporigin;
15 	ushort 	colormaplength;
16 	ubyte 	colormapdepth;
17 	ushort 	x_origin;
18 	ushort 	y_origin;
19 	ushort 	width;
20 	ushort 	height;
21 	ubyte 	bitsperpixel;
22 	ubyte 	imagedescriptor;
23 }
24 
25 struct TGAColor {
26 	ubyte[4] bgra;
27 	ubyte bytespp = 1;
28 
29 	this(ubyte R, ubyte G, ubyte B, ubyte A=255)
30 	{
31 		bytespp = 4;
32 		bgra[0] = B;
33 		bgra[1] = G;
34 		bgra[2] = R;
35 		bgra[3] = A;
36 	}
37 
38 	this(ubyte v)
39 	{
40 		bgra[0] = v;
41 	}
42 
43 	this(ubyte[] p)
44 	{
45 		bytespp = cast(ubyte)p.length;
46 		for (int i=0; i<cast(int)p.length; i++) {
47 			bgra[i] = p[i];
48 		}
49 	}
50 
51 	ubyte opIndex(int i)
52 	{
53 		return bgra[i];
54 	}
55 
56 	TGAColor opBinary(string op)(float intensity) if (op == "*")
57 	{
58 		TGAColor res = this;
59 		intensity = ( intensity > 1f ? 1f : (intensity < 0f ? 0f : intensity));
60 		for (int i=0; i<4; i++) res.bgra[i] = cast(ubyte)(bgra[i] * intensity);
61 		return res;
62 	}
63 }
64 
65 class TGAImage {
66 protected:
67 	ubyte[] _data;
68 	ushort _width;
69 	ushort _height;
70 	ubyte _bytespp;
71 
72 	bool load_rle_data(File input)
73 	{
74 		ulong pixelcount = _width*_height;
75 		ulong currentpixel = 0;
76 		ulong currentbyte = 0;
77 		TGAColor colorbuffer;
78 		do {
79 			ubyte chunkheader = 0;
80 			input.rawRead((&chunkheader)[0..1]);
81 			if (chunkheader<128) {
82 				chunkheader++;
83 				for (int i=0; i<chunkheader; i++) {
84 					input.rawRead(colorbuffer.bgra[0.._bytespp]);
85 					for (int t=0; t<_bytespp; t++)
86 						_data[currentbyte++] = colorbuffer.bgra[t];
87 					currentpixel++;
88 					if (currentpixel>pixelcount)
89 						return false;
90 				}
91 			}
92 			else {
93 				chunkheader -= 127;
94 				input.rawRead(colorbuffer.bgra[0.._bytespp]);
95 				for (int i=0; i<chunkheader; i++) {
96 					for (int t=0; t<_bytespp; t++)
97 						_data[currentbyte++] = colorbuffer.bgra[t];
98 					currentpixel++;
99 					if (currentpixel>pixelcount)
100 						return false;
101 				}
102 			}
103 		} while (currentpixel < pixelcount);
104 		return true;
105 	}
106 
107 	bool unload_rle_data(File output)
108 	{
109 		enum ubyte max_chunk_length = 128;
110 		ulong npixels = _width*_height;
111 		ulong curpix = 0;
112 		while (curpix<npixels) {
113 			ulong chunkstart = curpix*_bytespp;
114 			ulong curbyte = curpix*_bytespp;
115 			ubyte run_length = 1;
116 			bool raw = true;
117 			while (curpix+run_length<npixels && run_length<max_chunk_length) {
118 				bool succ_eq = true;
119 				for (int t=0; succ_eq && t<_bytespp; t++)
120 					succ_eq = (_data[curbyte+t]==_data[curbyte+t+_bytespp]);
121 				curbyte +=_bytespp;
122 				if (1==run_length) {
123 					raw = !succ_eq;
124 				}
125 				if (raw && succ_eq) {
126 					run_length--;
127 					break;
128 				}
129 				if (!raw && !succ_eq) {
130 					break;
131 				}
132 				run_length++;
133 			}
134 			curpix += run_length;
135 			auto tmp = cast(ubyte)(raw?run_length-1:run_length+127);
136 			output.rawWrite((&tmp)[0..1]);
137 			output.rawWrite(_data[chunkstart..(chunkstart+(raw?run_length*_bytespp:_bytespp))]);
138 		}
139 		return true;
140 	}
141 
142 public:
143 	enum Format {
144 		GRAYSCALE=1,
145 		RGB=3,
146 		RGBA=4
147 	}
148 
149 	this() {}
150 
151 	this(ushort w, ushort h, ubyte bpp)
152 	{
153 		_width = w;
154 		_height = h;
155 		_bytespp = bpp;
156 		_data.length = _width*_height*_bytespp;
157 	}
158 
159 	this(const TGAImage img)
160 	{
161 		_width = img._width;
162 		_height = img._height;
163 		_bytespp = img._bytespp;
164 		_data = img._data.dup;
165 	}
166 
167 	bool read_tga_file(const string filename)
168 	{
169 		if (_data) _data = [];
170 		auto input = File(filename, "rb");
171 		TGAHeader header;
172 		input.rawRead((&header)[0..1]);
173 		_width = header.width;
174 		_height = header.height;
175 		_bytespp = header.bitsperpixel >> 3;
176 		enforce(0<_width && 0<_height && (Format.GRAYSCALE==_bytespp || Format.RGB==_bytespp || Format.RGBA==_bytespp));
177 		_data.length = _bytespp*_width*_height;
178 		if (3==header.datatypecode || 2==header.datatypecode) {
179 			input.rawRead(_data);
180 		}
181 		else if (10==header.datatypecode || 11==header.datatypecode) {
182 			if (!load_rle_data(input)) {
183 				input.close;
184 				return false;
185 			}
186 		}
187 		else {
188 			input.close;
189 			return false;
190 		}
191 		if (!(header.imagedescriptor & 0x20)) {
192 			flip_vertically();
193 		}
194 		if (header.imagedescriptor & 0x10) {
195 			flip_horizontally();
196 		}
197 		input.close;
198 		return true;
199 	}
200 
201 	bool write_tga_file(const string filename, bool rle=true)
202 	{
203 		ubyte[4] developer_area_ref = [0, 0, 0, 0];
204 		ubyte[4] extension_area_ref = [0, 0, 0, 0];
205 		ubyte[18] footer = ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0'];
206 		auto output = File(filename, "wb");
207 		TGAHeader header;
208 		header.bitsperpixel = cast(ubyte)(_bytespp<<3);
209 		header.width = _width;
210 		header.height = _height;
211 		header.datatypecode = (_bytespp==Format.GRAYSCALE?(rle?11:3):(rle?10:2));
212 		header.imagedescriptor = 0x20;
213 		output.rawWrite((&header)[0..1]);
214 		if (!rle) {
215 			output.rawWrite(_data);
216 		}
217 		else {
218 			if (!unload_rle_data(output))
219 				return false;
220 		}
221 		output.rawWrite(developer_area_ref);
222 		output.rawWrite(extension_area_ref);
223 		output.rawWrite(footer);
224 		output.close;
225 		return true;
226 	}
227 
228 	bool flip_horizontally()
229 	{
230 		if (!_data) return false;
231 		int half = _width >> 1;
232 		for (int i=0; i<half; i++) {
233 			for (int j=0; j<_height; j++) {
234 				TGAColor c1 = get(i, j);
235 				TGAColor c2 = get(_width-1-i, j);
236 				set(i, j, c2);
237 				set(_width-1-i, j, c1);
238 			}
239 		}
240 		return true;
241 	}
242 
243 	bool flip_vertically()
244 	{
245 		if (!_data) return false;
246 		ulong bytes_per_line = _width*_bytespp;
247 		ubyte[] line; line.length = bytes_per_line;
248 		int half = _height >> 1;
249 		for (int j=0; j<half; j++) {
250 			ulong l1 = j*bytes_per_line;
251 			ulong l2 = (_height-1-j)*bytes_per_line;
252 			swapRanges(_data[l1..l1+bytes_per_line], _data[l2..l2+bytes_per_line]);
253 		}
254 		return true;
255 	}
256 
257 	bool scale(ushort w, ushort h)
258 	{
259 		if (w<=0 || h<=0 || !_data) return false;
260 		ubyte[] tdata; tdata.length = w*h*_bytespp;
261 		int nscanline = 0;
262 		int oscanline = 0;
263 		int erry = 0;
264 		ulong nlinebytes = w*_bytespp;
265 		ulong olinebytes = _width*_bytespp;
266 		for (int j=0; j<_height; j++) {
267 			int errx = _width-w;
268 			int nx = -cast(int)_bytespp;
269 			int ox = -cast(int)_bytespp;
270 			for (int i=0; i<_width; i++) {
271 				ox += _bytespp;
272 				errx += w;
273 				while (errx>=cast(int)_width) {
274 					errx -= _width;
275 					nx += _bytespp;
276 					tdata[nscanline+nx..nscanline+nx+_bytespp] = _data[oscanline+ox.._bytespp+oscanline+ox];
277 				}
278 			}
279 			erry += h;
280 			oscanline += olinebytes;
281 			while (erry>=cast(int)_height) {
282 				if (erry>=cast(int)_height<<1)
283 					tdata[nscanline+nlinebytes..nlinebytes+nscanline+nlinebytes] = tdata[nscanline..nscanline+nlinebytes];
284 				erry -= _height;
285 				nscanline += nlinebytes;
286 			}
287 		}
288 		_data = [];
289 		_data = tdata;
290 		_width = w;
291 		_height = h;
292 		return true;
293 	}
294 
295 	TGAColor get(int x, int y)
296 	{
297 		if (!_data || x<0 || y<0 || x>=_width || y>=_height)
298 			return TGAColor();
299 		return TGAColor(_data[(x+y*_width)*_bytespp..(x+y*_width)*_bytespp+_bytespp]);
300 	}
301 
302 	bool set(int x, int y, TGAColor c)
303 	{
304 		if (!_data || x<0 || y<0 || x>=_width || y>=_height)
305 			return false;
306 		memcpy(_data.ptr+(x+y*_width)*_bytespp, c.bgra.ptr, _bytespp);
307 		return true;
308 	}
309 
310 	bool set(int x, int y, const TGAColor c)
311 	{
312 		if (!_data || x<0 || y<0 || x>=_width || y>=_height)
313 			return false;
314 		memcpy(_data.ptr+(x+y*_width)*_bytespp, c.bgra.ptr, _bytespp);
315 		return true;
316 	}
317 
318 	TGAImage opAssing(const TGAImage img)
319 	{
320 		if (this != img) {
321 			if (_data) _data = [];
322 			_width = img._width;
323 			_height = img._height;
324 			_bytespp = img._bytespp;
325 			_data = img._data.dup;
326 		}
327 		return this;
328 	}
329 
330 	@property
331 	auto width()
332 	{
333 		return _width;
334 	}
335 
336 	@property
337 	auto height()
338 	{
339 		return _height;
340 	}
341 
342 	@property
343 	auto bytespp() {
344 		return _bytespp;
345 	}
346 
347 	@property
348 	auto data() {
349 		return _data;
350 	}
351 
352 	@property
353 	void clear() {
354 		_data = [];
355 		_data.length = _width*_height*_bytespp;
356 	}
357 }