1 // Copyright 2019 Tero Hänninen. All rights reserved.
2 // SPDX-License-Identifier: BSD-2-Clause
3 module imagefmt.tga;
4 
5 import imagefmt;
6 
7 @nogc nothrow package:
8 
9 struct TGAHeader {
10     int w;
11     int h;
12     ubyte idlen;
13     ubyte palettetype;
14     ubyte datatype;
15     ubyte bitspp;
16     ubyte flags;
17 }
18 
19 enum DATATYPE {
20     nodata        = 0,
21     idx           = 1,
22     truecolor     = 2,
23     gray          = 3,
24     idx_rle       = 9,
25     truecolor_rle = 10,
26     gray_rle      = 11,
27 }
28 
29 bool detect_tga(Reader* rc)
30 {
31     TGAHeader head;
32     const bool result = read_tga_header(rc, head) == 0;
33     reset2start(rc);
34     return result;
35 }
36 
37 IFInfo read_tga_info(Reader* rc)
38 {
39     TGAHeader head;
40     IFInfo info;
41     info.e = read_tga_header(rc, head);
42     if (info.e) return info;
43     info.w = head.w;
44     info.h = head.h;
45     info.c = 0;
46     const dt = head.datatype;
47     if ((dt == DATATYPE.truecolor     || dt == DATATYPE.gray ||
48          dt == DATATYPE.truecolor_rle || dt == DATATYPE.gray_rle)
49          && (head.bitspp % 8) == 0)
50     {
51         info.c = head.bitspp / 8;
52     }
53     info.e = info.c ? 0 : ERROR.unsupp;
54     return info;
55 }
56 
57 // TGA doesn't have a signature so validate some values right here for detection.
58 ubyte read_tga_header(Reader* rc, out TGAHeader head)
59 {
60     head.idlen        = read_u8(rc);
61     head.palettetype  = read_u8(rc);
62     head.datatype     = read_u8(rc);
63     ushort palettebeg = read_u16le(rc);
64     ushort palettelen = read_u16le(rc);
65     ubyte palettebits = read_u8(rc);
66     skip(rc, 2 + 2);    // x-origin, y-origin
67     head.w            = read_u16le(rc);
68     head.h            = read_u16le(rc);
69     head.bitspp       = read_u8(rc);
70     head.flags        = read_u8(rc);
71 
72     if (head.w < 1 || head.h < 1 || head.palettetype > 1
73     || (head.palettetype == 0 && (palettebeg || palettelen || palettebits))
74     || (head.datatype > 3 && head.datatype < 9) || head.datatype > 11)
75         return ERROR.data;
76 
77     return 0;
78 }
79 
80 ubyte read_tga(Reader* rc, out IFImage image, in int reqchans, in int reqbpc)
81 {
82     if (cast(uint) reqchans > 4)
83         return ERROR.arg;
84     const ubyte tbpc = cast(ubyte) (reqbpc ? reqbpc : 8);
85     if (tbpc != 8 && tbpc != 16)
86         return ERROR.unsupp;
87     TGAHeader head;
88     if (ubyte e = read_tga_header(rc, head))
89         return e;
90     if (head.w < 1 || head.h < 1)
91         return ERROR.dim;
92     if (head.flags & 0xc0)  // interlaced; two bits
93         return ERROR.unsupp;
94     if (head.flags & 0x10)  // right-to-left
95         return ERROR.unsupp;
96     const ubyte attr_bitspp = (head.flags & 0xf);
97     if (attr_bitspp != 0 && attr_bitspp != 8) // some set to 0 even if data has 8
98         return ERROR.unsupp;
99     if (head.palettetype)
100         return ERROR.unsupp;
101 
102     switch (head.datatype) {
103         case DATATYPE.truecolor:
104         case DATATYPE.truecolor_rle:
105             if (head.bitspp != 24 && head.bitspp != 32)
106                 return ERROR.unsupp;
107             break;
108         case DATATYPE.gray:
109         case DATATYPE.gray_rle:
110             if (head.bitspp != 8 && !(head.bitspp == 16 && attr_bitspp == 8))
111                 return ERROR.unsupp;
112             break;
113         default:
114             return ERROR.unsupp;
115     }
116 
117     const bool origin_at_top = (head.flags & 0x20) > 0;
118     const bool rle           = head.datatype >= 9 && head.datatype <= 11;
119     const int schans         = head.bitspp / 8;     // = bytes per pixel
120     const int tchans         = reqchans ? reqchans : schans;
121     const int slinesz        = head.w * schans;
122     const int tlinesz        = head.w * tchans;
123     const int tstride        = origin_at_top ? tlinesz : -tlinesz;
124     int ti                   = origin_at_top ? 0 : (head.h-1) * tlinesz;
125 
126     if (cast(ulong) head.w * head.h * tchans > MAXIMUM_IMAGE_SIZE)
127         return ERROR.bigimg;
128 
129     CHANS sfmt;
130     switch (schans) {
131         case 1: sfmt = CHANS.y; break;
132         case 2: sfmt = CHANS.ya; break;
133         case 3: sfmt = CHANS.bgr; break;
134         case 4: sfmt = CHANS.bgra; break;
135         default: assert(0);
136     }
137 
138     auto convert = cast(conv8) getconv(sfmt, tchans, 8);
139 
140     ubyte e;
141     ubyte[] result = new_buffer(head.w * head.h * tchans, e);
142     ubyte[] sline = new_buffer(slinesz, e);
143 
144     if (head.idlen)
145         skip(rc, head.idlen);
146 
147     if (e || rc.fail) {
148         _free(result.ptr);
149         _free(sline.ptr);
150         return e ? e : ERROR.stream;
151     }
152 
153     if (!rle) {
154         foreach (_; 0 .. head.h) {
155             read_block(rc, sline[0..$]);
156             convert(sline[0..$], result[ti .. ti + tlinesz]);
157             ti += tstride;
158         }
159         if (rc.fail) {
160             _free(result.ptr);
161             result = null;
162         }
163         _free(sline.ptr);
164 
165         image.w = head.w;
166         image.h = head.h;
167         image.c = cast(ubyte) tchans;
168         image.cinfile = cast(ubyte) schans;
169         image.bpc = 8;
170         image.buf8 = result;
171         return e;
172     }
173 
174     // ----- RLE -----
175 
176     ubyte[4] pixel;
177     int plen = 0;   // packet length
178     bool its_rle = false;
179 
180     foreach (_; 0 .. head.h) {
181         int wanted = slinesz;   // fill sline with unpacked data
182         do {
183             if (plen == 0) {
184                 const ubyte phead = read_u8(rc);
185                 its_rle = cast(bool) (phead & 0x80);
186                 plen = ((phead & 0x7f) + 1) * schans; // length in bytes
187             }
188             const int gotten = slinesz - wanted;
189             const int copysize = wanted < plen ? wanted : plen;
190             if (its_rle) {
191                 read_block(rc, pixel[0..schans]);
192                 for (int p = gotten; p < gotten+copysize; p += schans)
193                     sline[p .. p + schans] = pixel[0 .. schans];
194             } else // raw packet
195                 read_block(rc, sline[gotten .. gotten+copysize]);
196             wanted -= copysize;
197             plen -= copysize;
198         } while (wanted);
199 
200         convert(sline[0..$], result[ti .. ti + tlinesz]);
201         ti += tstride;
202     }
203 
204     if (rc.fail)
205         e = ERROR.stream;
206 
207     _free(sline.ptr);
208     if (e) {
209         _free(result.ptr);
210         return e;
211     }
212 
213     image.w = head.w;
214     image.h = head.h;
215     image.c = cast(ubyte) tchans;
216     image.cinfile = schans;
217     image.bpc = tbpc;
218     if (tbpc == 8) {
219         image.buf8 = result;
220     } else {
221         image.buf16 = bpc8to16(result);
222         if (!image.buf16.ptr)
223             return ERROR.oom;
224     }
225     return e;
226 }
227 
228 ubyte write_tga(Writer* wc, int w, int h, in ubyte[] buf, in int reqchans)
229 {
230     if (w < 1 || h < 1 || w > ushort.max || h > ushort.max)
231         return ERROR.dim;
232     const int schans = cast(int) (buf.length / w / h);
233     if (schans < 1 || schans > 4 || schans * w * h != buf.length)
234         return ERROR.dim;
235     if (cast(uint) reqchans > 4)
236         return ERROR.unsupp;
237 
238     const int tchans = reqchans ? reqchans : schans;
239     const bool has_alpha = tchans == 2 || tchans == 4;
240     const ubyte datatype = tchans == 3 || tchans == 4
241                          ? DATATYPE.truecolor_rle
242                          : DATATYPE.gray_rle;
243 
244     const ubyte[16] zeros = 0;
245 
246     write_u8(wc, 0);    // id length
247     write_u8(wc, 0);    // palette type
248     write_u8(wc, datatype);
249     write_block(wc, zeros[0 .. 5+4]); // palette stuff + x&y-origin
250     write_u16le(wc, cast(ushort) w);
251     write_u16le(wc, cast(ushort) h);
252     write_u8(wc, cast(ubyte) (tchans * 8)); // bitspp
253     write_u8(wc, has_alpha ? 0x08 : 0x00);  // flags: attr_bitspp = 8
254 
255     if (wc.fail) return ERROR.stream;
256 
257     ubyte e = write_tga_idat(wc, w, h, buf, schans, tchans);
258 
259     write_block(wc, zeros[0..4+4]); // extension area + developer directory offsets
260     write_block(wc, cast(const(ubyte[])) "TRUEVISION-XFILE.\0");
261 
262     return wc.fail ? ERROR.stream : e;
263 }
264 
265 ubyte write_tga_idat(Writer* wc, in int w, in int h, in ubyte[] buf, in int schans,
266                                                                      in int tchans)
267 {
268     int tfmt;
269     switch (tchans) {
270         case 1: tfmt = CHANS.y; break;
271         case 2: tfmt = CHANS.ya; break;
272         case 3: tfmt = CHANS.bgr; break;
273         case 4: tfmt = CHANS.bgra; break;
274         default: assert(0);
275     }
276 
277     auto convert = cast(conv8) getconv(schans, tfmt, 8);
278 
279     const size_t slinesz = w * schans;
280     const size_t tlinesz = w * tchans;
281     const size_t maxpckts = (tlinesz + 127) / 128;  // max packets per line
282 
283     ubyte e;
284     ubyte[] workbuf    = new_buffer(tlinesz + tlinesz + maxpckts, e);
285     ubyte[] tline      = workbuf[0..tlinesz];
286     ubyte[] compressed = workbuf[tlinesz .. tlinesz + (tlinesz + maxpckts)];
287 
288     const size_t sbufsz = h * slinesz;
289     for (size_t si = (h - 1) * slinesz; si < sbufsz; si -= slinesz) {
290         convert(buf[si .. si + slinesz], tline[0..$]);
291         const size_t compsz = rle_compress(tline, compressed, w, tchans);
292         write_block(wc, compressed[0..compsz]);
293     }
294 
295     _free(workbuf.ptr);
296     return wc.fail ? ERROR.stream : e;
297 }
298 
299 size_t rle_compress(in ubyte[] line, ubyte[] cmpr, in size_t w, in int bytespp)
300 {
301     const int rle_limit = 1 < bytespp ? 2 : 3;  // run length worth an RLE packet
302     size_t runlen = 0;
303     size_t rawlen = 0;
304     size_t ri = 0; // start of raw packet data in line
305     size_t ci = 0;
306     size_t pixels_left = w;
307     const(ubyte)[] px;
308 
309     for (size_t i = bytespp; pixels_left; i += bytespp) {
310         runlen = 1;
311         px = line[i-bytespp .. i];
312         while (i < line.length && line[i .. i+bytespp] == px[0..$] && runlen < 128) {
313             ++runlen;
314             i += bytespp;
315         }
316         pixels_left -= runlen;
317 
318         if (runlen < rle_limit) {
319             // data goes to raw packet
320             rawlen += runlen;
321             if (128 <= rawlen) {     // full packet, need to store it
322                 const size_t copysize = 128 * bytespp;
323                 cmpr[ci++] = 0x7f; // raw packet header
324                 cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
325                 ci += copysize;
326                 ri += copysize;
327                 rawlen -= 128;
328             }
329         } else { // RLE packet is worth it
330             // store raw packet first, if any
331             if (rawlen) {
332                 assert(rawlen < 128);
333                 const size_t copysize = rawlen * bytespp;
334                 cmpr[ci++] = cast(ubyte) (rawlen-1); // raw packet header
335                 cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
336                 ci += copysize;
337                 rawlen = 0;
338             }
339 
340             // store RLE packet
341             cmpr[ci++] = cast(ubyte) (0x80 | (runlen-1)); // packet header
342             cmpr[ci .. ci+bytespp] = px[0..$];       // packet data
343             ci += bytespp;
344             ri = i;
345         }
346     }   // for
347 
348     if (rawlen) {   // last packet of the line
349         const size_t copysize = rawlen * bytespp;
350         cmpr[ci++] = cast(ubyte) (rawlen-1); // raw packet header
351         cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
352         ci += copysize;
353     }
354 
355     return ci;
356 }