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