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 }