1 module imageformats.tga;
2 
3 import std.algorithm;
4 import std.bitmanip;   // endianness stuff
5 import std.stdio;    // File
6 import imageformats;
7 
8 private:
9 
10 public bool detect_tga(Reader stream) {
11     try {
12         auto hdr = read_tga_header(stream);
13         return true;
14     } catch (Throwable) {
15         return false;
16     } finally {
17         stream.seek(0, SEEK_SET);
18     }
19 }
20 
21 ///
22 public struct TGA_Header {
23    ubyte id_length;
24    ubyte palette_type;
25    ubyte data_type;
26    ushort palette_start;
27    ushort palette_length;
28    ubyte palette_bits;
29    ushort x_origin;
30    ushort y_origin;
31    ushort width;
32    ushort height;
33    ubyte bits_pp;
34    ubyte flags;
35 }
36 
37 ///
38 public TGA_Header read_tga_header(in char[] filename) {
39     scope reader = new FileReader(filename);
40     return read_tga_header(reader);
41 }
42 
43 ///
44 public TGA_Header read_tga_header_from_mem(in ubyte[] source) {
45     scope reader = new MemReader(source);
46     return read_tga_header(reader);
47 }
48 
49 ///
50 public IFImage read_tga(in char[] filename, long req_chans = 0) {
51     scope reader = new FileReader(filename);
52     return read_tga(reader, req_chans);
53 }
54 
55 ///
56 public IFImage read_tga_from_mem(in ubyte[] source, long req_chans = 0) {
57     scope reader = new MemReader(source);
58     return read_tga(reader, req_chans);
59 }
60 
61 ///
62 public void write_tga(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
63 {
64     scope writer = new FileWriter(file);
65     write_tga(writer, w, h, data, tgt_chans);
66 }
67 
68 ///
69 public ubyte[] write_tga_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
70     scope writer = new MemWriter();
71     write_tga(writer, w, h, data, tgt_chans);
72     return writer.result;
73 }
74 
75 ///
76 public void read_tga_info(in char[] filename, out int w, out int h, out int chans) {
77     scope reader = new FileReader(filename);
78     return read_tga_info(reader, w, h, chans);
79 }
80 
81 ///
82 public void read_tga_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) {
83     scope reader = new MemReader(source);
84     return read_tga_info(reader, w, h, chans);
85 }
86 
87 TGA_Header read_tga_header(Reader stream) {
88     ubyte[18] tmp = void;
89     stream.readExact(tmp, tmp.length);
90 
91     TGA_Header hdr = {
92         id_length       : tmp[0],
93         palette_type    : tmp[1],
94         data_type       : tmp[2],
95         palette_start   : littleEndianToNative!ushort(tmp[3..5]),
96         palette_length  : littleEndianToNative!ushort(tmp[5..7]),
97         palette_bits    : tmp[7],
98         x_origin        : littleEndianToNative!ushort(tmp[8..10]),
99         y_origin        : littleEndianToNative!ushort(tmp[10..12]),
100         width           : littleEndianToNative!ushort(tmp[12..14]),
101         height          : littleEndianToNative!ushort(tmp[14..16]),
102         bits_pp         : tmp[16],
103         flags           : tmp[17],
104     };
105 
106     if (hdr.width < 1 || hdr.height < 1 || hdr.palette_type > 1
107         || (hdr.palette_type == 0 && (hdr.palette_start
108                                      || hdr.palette_length
109                                      || hdr.palette_bits))
110         || (4 <= hdr.data_type && hdr.data_type <= 8) || 12 <= hdr.data_type)
111         throw new ImageIOException("corrupt TGA header");
112 
113     return hdr;
114 }
115 
116 package IFImage read_tga(Reader stream, long req_chans = 0) {
117     if (req_chans < 0 || 4 < req_chans)
118         throw new ImageIOException("come on...");
119 
120     TGA_Header hdr = read_tga_header(stream);
121 
122     if (hdr.width < 1 || hdr.height < 1)
123         throw new ImageIOException("invalid dimensions");
124     if (hdr.flags & 0xc0)   // two bits
125         throw new ImageIOException("interlaced TGAs not supported");
126     if (hdr.flags & 0x10)
127         throw new ImageIOException("right-to-left TGAs not supported");
128     ubyte attr_bits_pp = (hdr.flags & 0xf);
129     if (! (attr_bits_pp == 0 || attr_bits_pp == 8)) // some set it 0 although data has 8
130         throw new ImageIOException("only 8-bit alpha/attribute(s) supported");
131     if (hdr.palette_type)
132         throw new ImageIOException("paletted TGAs not supported");
133 
134     bool rle = false;
135     switch (hdr.data_type) with (TGA_DataType) {
136         //case 1: ;   // paletted, uncompressed
137         case TrueColor:
138             if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32))
139                 throw new ImageIOException("not supported");
140             break;
141         case Gray:
142             if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8)))
143                 throw new ImageIOException("not supported");
144             break;
145         //case 9: ;   // paletted, RLE
146         case TrueColor_RLE:
147             if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32))
148                 throw new ImageIOException("not supported");
149             rle = true;
150             break;
151         case Gray_RLE:
152             if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8)))
153                 throw new ImageIOException("not supported");
154             rle = true;
155             break;
156         default: throw new ImageIOException("data type not supported");
157     }
158 
159     int src_chans = hdr.bits_pp / 8;
160 
161     if (hdr.id_length)
162         stream.seek(hdr.id_length, SEEK_CUR);
163 
164     TGA_Decoder dc = {
165         stream         : stream,
166         w              : hdr.width,
167         h              : hdr.height,
168         origin_at_top  : cast(bool) (hdr.flags & 0x20),
169         bytes_pp       : hdr.bits_pp / 8,
170         rle            : rle,
171         tgt_chans      : (req_chans == 0) ? src_chans : cast(int) req_chans,
172     };
173 
174     switch (dc.bytes_pp) {
175         case 1: dc.src_fmt = _ColFmt.Y; break;
176         case 2: dc.src_fmt = _ColFmt.YA; break;
177         case 3: dc.src_fmt = _ColFmt.BGR; break;
178         case 4: dc.src_fmt = _ColFmt.BGRA; break;
179         default: throw new ImageIOException("TGA: format not supported");
180     }
181 
182     IFImage result = {
183         w      : dc.w,
184         h      : dc.h,
185         c      : cast(ColFmt) dc.tgt_chans,
186         pixels : decode_tga(dc),
187     };
188     return result;
189 }
190 
191 void write_tga(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
192     if (w < 1 || h < 1 || ushort.max < w || ushort.max < h)
193         throw new ImageIOException("invalid dimensions");
194     ulong src_chans = data.length / w / h;
195     if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans)
196         throw new ImageIOException("invalid channel count");
197     if (src_chans * w * h != data.length)
198         throw new ImageIOException("mismatching dimensions and length");
199 
200     TGA_Encoder ec = {
201         stream    : stream,
202         w         : cast(ushort) w,
203         h         : cast(ushort) h,
204         src_chans : cast(int) src_chans,
205         tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans),
206         rle       : true,
207         data      : data,
208     };
209 
210     write_tga(ec);
211     stream.flush();
212 }
213 
214 struct TGA_Decoder {
215     Reader stream;
216     int w, h;
217     bool origin_at_top;    // src
218     uint bytes_pp;
219     bool rle;   // run length compressed
220     _ColFmt src_fmt;
221     uint tgt_chans;
222 }
223 
224 ubyte[] decode_tga(ref TGA_Decoder dc) {
225     auto result = new ubyte[dc.w * dc.h * dc.tgt_chans];
226 
227     immutable size_t tgt_linesize = dc.w * dc.tgt_chans;
228     immutable size_t src_linesize = dc.w * dc.bytes_pp;
229     auto src_line = new ubyte[src_linesize];
230 
231     immutable ptrdiff_t tgt_stride = (dc.origin_at_top) ? tgt_linesize : -tgt_linesize;
232     ptrdiff_t ti                   = (dc.origin_at_top) ? 0 : (dc.h-1) * tgt_linesize;
233 
234     const LineConv!ubyte convert = get_converter!ubyte(dc.src_fmt, dc.tgt_chans);
235 
236     if (!dc.rle) {
237         foreach (_j; 0 .. dc.h) {
238             dc.stream.readExact(src_line, src_linesize);
239             convert(src_line, result[ti .. ti + tgt_linesize]);
240             ti += tgt_stride;
241         }
242         return result;
243     }
244 
245     // ----- RLE  -----
246 
247     auto rbuf = new ubyte[src_linesize];
248     size_t plen = 0;      // packet length
249     bool its_rle = false;
250 
251     foreach (_j; 0 .. dc.h) {
252         // fill src_line with uncompressed data (this works like a stream)
253         size_t wanted = src_linesize;
254         while (wanted) {
255             if (plen == 0) {
256                 dc.stream.readExact(rbuf, 1);
257                 its_rle = cast(bool) (rbuf[0] & 0x80);
258                 plen = ((rbuf[0] & 0x7f) + 1) * dc.bytes_pp; // length in bytes
259             }
260             const size_t gotten = src_linesize - wanted;
261             const size_t copysize = min(plen, wanted);
262             if (its_rle) {
263                 dc.stream.readExact(rbuf, dc.bytes_pp);
264                 for (size_t p = gotten; p < gotten+copysize; p += dc.bytes_pp)
265                     src_line[p .. p+dc.bytes_pp] = rbuf[0 .. dc.bytes_pp];
266             } else {    // it's raw
267                 auto slice = src_line[gotten .. gotten+copysize];
268                 dc.stream.readExact(slice, copysize);
269             }
270             wanted -= copysize;
271             plen -= copysize;
272         }
273 
274         convert(src_line, result[ti .. ti + tgt_linesize]);
275         ti += tgt_stride;
276     }
277 
278     return result;
279 }
280 
281 // ----------------------------------------------------------------------
282 // TGA encoder
283 
284 immutable ubyte[18] tga_footer_sig =
285     ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.', 0];
286 
287 struct TGA_Encoder {
288     Writer stream;
289     ushort w, h;
290     int src_chans;
291     int tgt_chans;
292     bool rle;   // run length compression
293     const(ubyte)[] data;
294 }
295 
296 void write_tga(ref TGA_Encoder ec) {
297     ubyte data_type;
298     bool has_alpha = false;
299     switch (ec.tgt_chans) with (TGA_DataType) {
300         case 1: data_type = ec.rle ? Gray_RLE : Gray;                             break;
301         case 2: data_type = ec.rle ? Gray_RLE : Gray;           has_alpha = true; break;
302         case 3: data_type = ec.rle ? TrueColor_RLE : TrueColor;                   break;
303         case 4: data_type = ec.rle ? TrueColor_RLE : TrueColor; has_alpha = true; break;
304         default: throw new ImageIOException("internal error");
305     }
306 
307     ubyte[18] hdr = void;
308     hdr[0] = 0;         // id length
309     hdr[1] = 0;         // palette type
310     hdr[2] = data_type;
311     hdr[3..8] = 0;         // palette start (2), len (2), bits per palette entry (1)
312     hdr[8..12] = 0;     // x origin (2), y origin (2)
313     hdr[12..14] = nativeToLittleEndian(ec.w);
314     hdr[14..16] = nativeToLittleEndian(ec.h);
315     hdr[16] = cast(ubyte) (ec.tgt_chans * 8);     // bits per pixel
316     hdr[17] = (has_alpha) ? 0x8 : 0x0;     // flags: attr_bits_pp = 8
317     ec.stream.rawWrite(hdr);
318 
319     write_image_data(ec);
320 
321     ubyte[26] ftr = void;
322     ftr[0..4] = 0;   // extension area offset
323     ftr[4..8] = 0;   // developer directory offset
324     ftr[8..26] = tga_footer_sig;
325     ec.stream.rawWrite(ftr);
326 }
327 
328 void write_image_data(ref TGA_Encoder ec) {
329     _ColFmt tgt_fmt;
330     switch (ec.tgt_chans) {
331         case 1: tgt_fmt = _ColFmt.Y; break;
332         case 2: tgt_fmt = _ColFmt.YA; break;
333         case 3: tgt_fmt = _ColFmt.BGR; break;
334         case 4: tgt_fmt = _ColFmt.BGRA; break;
335         default: throw new ImageIOException("internal error");
336     }
337 
338     const LineConv!ubyte convert = get_converter!ubyte(ec.src_chans, tgt_fmt);
339 
340     immutable size_t src_linesize = ec.w * ec.src_chans;
341     immutable size_t tgt_linesize = ec.w * ec.tgt_chans;
342     auto tgt_line = new ubyte[tgt_linesize];
343 
344     ptrdiff_t si = (ec.h-1) * src_linesize;     // origin at bottom
345 
346     if (!ec.rle) {
347         foreach (_; 0 .. ec.h) {
348             convert(ec.data[si .. si + src_linesize], tgt_line);
349             ec.stream.rawWrite(tgt_line);
350             si -= src_linesize; // origin at bottom
351         }
352         return;
353     }
354 
355     // ----- RLE  -----
356 
357     immutable bytes_pp = ec.tgt_chans;
358     immutable size_t max_packets_per_line = (tgt_linesize+127) / 128;
359     auto tgt_cmp = new ubyte[tgt_linesize + max_packets_per_line];  // compressed line
360     foreach (_; 0 .. ec.h) {
361         convert(ec.data[si .. si + src_linesize], tgt_line);
362         ubyte[] compressed_line = rle_compress(tgt_line, tgt_cmp, ec.w, bytes_pp);
363         ec.stream.rawWrite(compressed_line);
364         si -= src_linesize; // origin at bottom
365     }
366 }
367 
368 ubyte[] rle_compress(in ubyte[] line, ubyte[] tgt_cmp, in size_t w, in int bytes_pp) pure {
369     immutable int rle_limit = (1 < bytes_pp) ? 2 : 3;  // run len worth an RLE packet
370     size_t runlen = 0;
371     size_t rawlen = 0;
372     size_t raw_i = 0; // start of raw packet data in line
373     size_t cmp_i = 0;
374     size_t pixels_left = w;
375     const (ubyte)[] px;
376     for (size_t i = bytes_pp; pixels_left; i += bytes_pp) {
377         runlen = 1;
378         px = line[i-bytes_pp .. i];
379         while (i < line.length && line[i .. i+bytes_pp] == px[0..$] && runlen < 128) {
380             ++runlen;
381             i += bytes_pp;
382         }
383         pixels_left -= runlen;
384 
385         if (runlen < rle_limit) {
386             // data goes to raw packet
387             rawlen += runlen;
388             if (128 <= rawlen) {     // full packet, need to store it
389                 size_t copysize = 128 * bytes_pp;
390                 tgt_cmp[cmp_i++] = 0x7f; // raw packet header
391                 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
392                 cmp_i += copysize;
393                 raw_i += copysize;
394                 rawlen -= 128;
395             }
396         } else {
397             // RLE packet is worth it
398 
399             // store raw packet first, if any
400             if (rawlen) {
401                 assert(rawlen < 128);
402                 size_t copysize = rawlen * bytes_pp;
403                 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
404                 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
405                 cmp_i += copysize;
406                 rawlen = 0;
407             }
408 
409             // store RLE packet
410             tgt_cmp[cmp_i++] = cast(ubyte) (0x80 | (runlen-1)); // packet header
411             tgt_cmp[cmp_i .. cmp_i+bytes_pp] = px[0..$];       // packet data
412             cmp_i += bytes_pp;
413             raw_i = i;
414         }
415     }   // for
416 
417     if (rawlen) {   // last packet of the line
418         size_t copysize = rawlen * bytes_pp;
419         tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
420         tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
421         cmp_i += copysize;
422     }
423     return tgt_cmp[0 .. cmp_i];
424 }
425 
426 enum TGA_DataType : ubyte {
427     Idx           = 1,
428     TrueColor     = 2,
429     Gray          = 3,
430     Idx_RLE       = 9,
431     TrueColor_RLE = 10,
432     Gray_RLE      = 11,
433 }
434 
435 package void read_tga_info(Reader stream, out int w, out int h, out int chans) {
436     TGA_Header hdr = read_tga_header(stream);
437     w = hdr.width;
438     h = hdr.height;
439 
440     // TGA is awkward...
441     auto dt = hdr.data_type;
442     if ((dt == TGA_DataType.TrueColor     || dt == TGA_DataType.Gray ||
443          dt == TGA_DataType.TrueColor_RLE || dt == TGA_DataType.Gray_RLE)
444          && (hdr.bits_pp % 8) == 0)
445     {
446         chans = hdr.bits_pp / 8;
447         return;
448     } else if (dt == TGA_DataType.Idx || dt == TGA_DataType.Idx_RLE) {
449         switch (hdr.palette_bits) {
450             case 15: chans = 3; return;
451             case 16: chans = 3; return; // one bit could be for some "interrupt control"
452             case 24: chans = 3; return;
453             case 32: chans = 4; return;
454             default:
455         }
456     }
457     chans = 0;  // unknown
458 }