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 }