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 bool flip          = origin_at_top ^ (VERTICAL_ORIENTATION_READ == 1);
124     const int tstride        = flip ? -tlinesz             : tlinesz;
125     int ti                   = flip ? (head.h-1) * tlinesz : 0;
126 
127     if (cast(ulong) head.w * head.h * tchans > MAXIMUM_IMAGE_SIZE)
128         return ERROR.bigimg;
129 
130     CHANS sfmt;
131     switch (schans) {
132         case 1: sfmt = CHANS.y; break;
133         case 2: sfmt = CHANS.ya; break;
134         case 3: sfmt = CHANS.bgr; break;
135         case 4: sfmt = CHANS.bgra; break;
136         default: assert(0);
137     }
138 
139     auto convert = cast(conv8) getconv(sfmt, tchans, 8);
140 
141     ubyte e;
142     ubyte[] result = new_buffer(head.w * head.h * tchans, e);
143     ubyte[] sline = new_buffer(slinesz, e);
144 
145     if (head.idlen)
146         skip(rc, head.idlen);
147 
148     if (e || rc.fail) {
149         _free(result.ptr);
150         _free(sline.ptr);
151         return e ? e : ERROR.stream;
152     }
153 
154     if (!rle) {
155         foreach (_; 0 .. head.h) {
156             read_block(rc, sline[0..$]);
157             convert(sline[0..$], result[ti .. ti + tlinesz]);
158             ti += tstride;
159         }
160     } else {
161         ubyte[4] pixel;
162         int plen = 0;   // packet length
163         bool its_rle = false;
164 
165         foreach (_; 0 .. head.h) {
166             int wanted = slinesz;   // fill sline with unpacked data
167             do {
168                 if (plen == 0) {
169                     const ubyte phead = read_u8(rc);
170                     its_rle = cast(bool) (phead & 0x80);
171                     plen = ((phead & 0x7f) + 1) * schans; // length in bytes
172                 }
173                 const int gotten = slinesz - wanted;
174                 const int copysize = wanted < plen ? wanted : plen;
175                 if (its_rle) {
176                     read_block(rc, pixel[0..schans]);
177                     for (int p = gotten; p < gotten+copysize; p += schans)
178                         sline[p .. p + schans] = pixel[0 .. schans];
179                 } else // raw packet
180                     read_block(rc, sline[gotten .. gotten+copysize]);
181                 wanted -= copysize;
182                 plen -= copysize;
183             } while (wanted);
184 
185             convert(sline[0..$], result[ti .. ti + tlinesz]);
186             ti += tstride;
187         }
188     }
189 
190     _free(sline.ptr);
191 
192     if (rc.fail) {
193         _free(result.ptr);
194         return ERROR.stream;
195     }
196 
197     image.w = head.w;
198     image.h = head.h;
199     image.c = cast(ubyte) tchans;
200     image.cinfile = schans;
201     image.bpc = tbpc;
202     if (tbpc == 8) {
203         image.buf8 = result;
204     } else {
205         image.buf16 = bpc8to16(result);
206         if (!image.buf16.ptr)
207             return ERROR.oom;
208     }
209     return e;
210 }
211 
212 ubyte write_tga(Writer* wc, int w, int h, in ubyte[] buf, in int reqchans)
213 {
214     if (w < 1 || h < 1 || w > ushort.max || h > ushort.max)
215         return ERROR.dim;
216     const int schans = cast(int) (buf.length / w / h);
217     if (schans < 1 || schans > 4 || schans * w * h != buf.length)
218         return ERROR.dim;
219     if (cast(uint) reqchans > 4)
220         return ERROR.unsupp;
221 
222     const int tchans = reqchans ? reqchans : schans;
223     const bool has_alpha = tchans == 2 || tchans == 4;
224     const ubyte datatype = tchans == 3 || tchans == 4
225                          ? DATATYPE.truecolor_rle
226                          : DATATYPE.gray_rle;
227 
228     const ubyte[16] zeros = 0;
229 
230     write_u8(wc, 0);    // id length
231     write_u8(wc, 0);    // palette type
232     write_u8(wc, datatype);
233     write_block(wc, zeros[0 .. 5+4]); // palette stuff + x&y-origin
234     write_u16le(wc, cast(ushort) w);
235     write_u16le(wc, cast(ushort) h);
236     write_u8(wc, cast(ubyte) (tchans * 8)); // bitspp
237     write_u8(wc, has_alpha ? 0x08 : 0x00);  // flags: attr_bitspp = 8
238 
239     if (wc.fail) return ERROR.stream;
240 
241     ubyte e = write_tga_idat(wc, w, h, buf, schans, tchans);
242 
243     write_block(wc, zeros[0..4+4]); // extension area + developer directory offsets
244     write_block(wc, cast(const(ubyte[])) "TRUEVISION-XFILE.\0");
245 
246     return wc.fail ? ERROR.stream : e;
247 }
248 
249 ubyte write_tga_idat(Writer* wc, in int w, in int h, in ubyte[] buf, in int schans,
250                                                                      in int tchans)
251 {
252     int tfmt;
253     switch (tchans) {
254         case 1: tfmt = CHANS.y; break;
255         case 2: tfmt = CHANS.ya; break;
256         case 3: tfmt = CHANS.bgr; break;
257         case 4: tfmt = CHANS.bgra; break;
258         default: assert(0);
259     }
260 
261     auto convert = cast(conv8) getconv(schans, tfmt, 8);
262 
263     const int slinesz = w * schans;
264     const int tlinesz = w * tchans;
265     const int maxpckts = (tlinesz + 127) / 128;  // max packets per line
266     const uint sbufsz = h * slinesz;
267     const int sstride = -slinesz * VERTICAL_ORIENTATION_WRITE;
268     uint si = (h - 1) * slinesz * (VERTICAL_ORIENTATION_WRITE == 1);
269 
270     ubyte e;
271     ubyte[] workbuf    = new_buffer(tlinesz + tlinesz + maxpckts, e);
272     ubyte[] tline      = workbuf[0..tlinesz];
273     ubyte[] compressed = workbuf[tlinesz .. tlinesz + (tlinesz + maxpckts)];
274 
275     for (; cast(uint) si < sbufsz; si += sstride) {
276         convert(buf[si .. si + slinesz], tline[0..$]);
277         const size_t compsz = rle_compress(tline, compressed, w, tchans);
278         write_block(wc, compressed[0..compsz]);
279     }
280 
281     _free(workbuf.ptr);
282     return wc.fail ? ERROR.stream : e;
283 }
284 
285 size_t rle_compress(in ubyte[] line, ubyte[] cmpr, in size_t w, in int bytespp)
286 {
287     const int rle_limit = 1 < bytespp ? 2 : 3;  // run length worth an RLE packet
288     size_t runlen = 0;
289     size_t rawlen = 0;
290     size_t ri = 0; // start of raw packet data in line
291     size_t ci = 0;
292     size_t pixels_left = w;
293     const(ubyte)[] px;
294 
295     for (size_t i = bytespp; pixels_left; i += bytespp) {
296         runlen = 1;
297         px = line[i-bytespp .. i];
298         while (i < line.length && line[i .. i+bytespp] == px[0..$] && runlen < 128) {
299             ++runlen;
300             i += bytespp;
301         }
302         pixels_left -= runlen;
303 
304         if (runlen < rle_limit) {
305             // data goes to raw packet
306             rawlen += runlen;
307             if (128 <= rawlen) {     // full packet, need to store it
308                 const size_t copysize = 128 * bytespp;
309                 cmpr[ci++] = 0x7f; // raw packet header
310                 cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
311                 ci += copysize;
312                 ri += copysize;
313                 rawlen -= 128;
314             }
315         } else { // RLE packet is worth it
316             // store raw packet first, if any
317             if (rawlen) {
318                 assert(rawlen < 128);
319                 const size_t copysize = rawlen * bytespp;
320                 cmpr[ci++] = cast(ubyte) (rawlen-1); // raw packet header
321                 cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
322                 ci += copysize;
323                 rawlen = 0;
324             }
325 
326             // store RLE packet
327             cmpr[ci++] = cast(ubyte) (0x80 | (runlen-1)); // packet header
328             cmpr[ci .. ci+bytespp] = px[0..$];       // packet data
329             ci += bytespp;
330             ri = i;
331         }
332     }   // for
333 
334     if (rawlen) {   // last packet of the line
335         const size_t copysize = rawlen * bytespp;
336         cmpr[ci++] = cast(ubyte) (rawlen-1); // raw packet header
337         cmpr[ci .. ci+copysize] = line[ri .. ri+copysize];
338         ci += copysize;
339     }
340 
341     return ci;
342 }