1 module imageformats.tga; 2 3 import std.algorithm : min; 4 import std.bitmanip : littleEndianToNative, nativeToLittleEndian; 5 import std.stdio : File, SEEK_SET, SEEK_CUR; 6 import std.typecons : scoped; 7 import imageformats; 8 9 private: 10 11 public bool detect_tga(Reader stream) { 12 try { 13 auto hdr = read_tga_header(stream); 14 return true; 15 } catch (Throwable) { 16 return false; 17 } finally { 18 stream.seek(0, SEEK_SET); 19 } 20 } 21 22 /// 23 public struct TGA_Header { 24 ubyte id_length; 25 ubyte palette_type; 26 ubyte data_type; 27 ushort palette_start; 28 ushort palette_length; 29 ubyte palette_bits; 30 ushort x_origin; 31 ushort y_origin; 32 ushort width; 33 ushort height; 34 ubyte bits_pp; 35 ubyte flags; 36 } 37 38 /// 39 public TGA_Header read_tga_header(in char[] filename) { 40 auto reader = scoped!FileReader(filename); 41 return read_tga_header(reader); 42 } 43 44 /// 45 public TGA_Header read_tga_header_from_mem(in ubyte[] source) { 46 auto reader = scoped!MemReader(source); 47 return read_tga_header(reader); 48 } 49 50 /// 51 public IFImage read_tga(in char[] filename, long req_chans = 0) { 52 auto reader = scoped!FileReader(filename); 53 return read_tga(reader, req_chans); 54 } 55 56 /// 57 public IFImage read_tga_from_mem(in ubyte[] source, long req_chans = 0) { 58 auto reader = scoped!MemReader(source); 59 return read_tga(reader, req_chans); 60 } 61 62 /// 63 public void write_tga(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0) 64 { 65 auto writer = scoped!FileWriter(file); 66 write_tga(writer, w, h, data, tgt_chans); 67 } 68 69 /// 70 public ubyte[] write_tga_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) { 71 auto writer = scoped!MemWriter(); 72 write_tga(writer, w, h, data, tgt_chans); 73 return writer.result; 74 } 75 76 /// 77 public void read_tga_info(in char[] filename, out int w, out int h, out int chans) { 78 auto reader = scoped!FileReader(filename); 79 return read_tga_info(reader, w, h, chans); 80 } 81 82 /// 83 public void read_tga_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) { 84 auto reader = scoped!MemReader(source); 85 return read_tga_info(reader, w, h, chans); 86 } 87 88 TGA_Header read_tga_header(Reader stream) { 89 ubyte[18] tmp = void; 90 stream.readExact(tmp, tmp.length); 91 92 TGA_Header hdr = { 93 id_length : tmp[0], 94 palette_type : tmp[1], 95 data_type : tmp[2], 96 palette_start : littleEndianToNative!ushort(tmp[3..5]), 97 palette_length : littleEndianToNative!ushort(tmp[5..7]), 98 palette_bits : tmp[7], 99 x_origin : littleEndianToNative!ushort(tmp[8..10]), 100 y_origin : littleEndianToNative!ushort(tmp[10..12]), 101 width : littleEndianToNative!ushort(tmp[12..14]), 102 height : littleEndianToNative!ushort(tmp[14..16]), 103 bits_pp : tmp[16], 104 flags : tmp[17], 105 }; 106 107 if (hdr.width < 1 || hdr.height < 1 || hdr.palette_type > 1 108 || (hdr.palette_type == 0 && (hdr.palette_start 109 || hdr.palette_length 110 || hdr.palette_bits)) 111 || (4 <= hdr.data_type && hdr.data_type <= 8) || 12 <= hdr.data_type) 112 throw new ImageIOException("corrupt TGA header"); 113 114 return hdr; 115 } 116 117 package IFImage read_tga(Reader stream, long req_chans = 0) { 118 if (req_chans < 0 || 4 < req_chans) 119 throw new ImageIOException("come on..."); 120 121 TGA_Header hdr = read_tga_header(stream); 122 123 if (hdr.width < 1 || hdr.height < 1) 124 throw new ImageIOException("invalid dimensions"); 125 if (hdr.flags & 0xc0) // two bits 126 throw new ImageIOException("interlaced TGAs not supported"); 127 if (hdr.flags & 0x10) 128 throw new ImageIOException("right-to-left TGAs not supported"); 129 ubyte attr_bits_pp = (hdr.flags & 0xf); 130 if (! (attr_bits_pp == 0 || attr_bits_pp == 8)) // some set it 0 although data has 8 131 throw new ImageIOException("only 8-bit alpha/attribute(s) supported"); 132 if (hdr.palette_type) 133 throw new ImageIOException("paletted TGAs not supported"); 134 135 bool rle = false; 136 switch (hdr.data_type) with (TGA_DataType) { 137 //case 1: ; // paletted, uncompressed 138 case TrueColor: 139 if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32)) 140 throw new ImageIOException("not supported"); 141 break; 142 case Gray: 143 if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8))) 144 throw new ImageIOException("not supported"); 145 break; 146 //case 9: ; // paletted, RLE 147 case TrueColor_RLE: 148 if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32)) 149 throw new ImageIOException("not supported"); 150 rle = true; 151 break; 152 case Gray_RLE: 153 if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8))) 154 throw new ImageIOException("not supported"); 155 rle = true; 156 break; 157 default: throw new ImageIOException("data type not supported"); 158 } 159 160 int src_chans = hdr.bits_pp / 8; 161 162 if (hdr.id_length) 163 stream.seek(hdr.id_length, SEEK_CUR); 164 165 TGA_Decoder dc = { 166 stream : stream, 167 w : hdr.width, 168 h : hdr.height, 169 origin_at_top : cast(bool) (hdr.flags & 0x20), 170 bytes_pp : hdr.bits_pp / 8, 171 rle : rle, 172 tgt_chans : (req_chans == 0) ? src_chans : cast(int) req_chans, 173 }; 174 175 switch (dc.bytes_pp) { 176 case 1: dc.src_fmt = _ColFmt.Y; break; 177 case 2: dc.src_fmt = _ColFmt.YA; break; 178 case 3: dc.src_fmt = _ColFmt.BGR; break; 179 case 4: dc.src_fmt = _ColFmt.BGRA; break; 180 default: throw new ImageIOException("TGA: format not supported"); 181 } 182 183 IFImage result = { 184 w : dc.w, 185 h : dc.h, 186 c : cast(ColFmt) dc.tgt_chans, 187 pixels : decode_tga(dc), 188 }; 189 return result; 190 } 191 192 void write_tga(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) { 193 if (w < 1 || h < 1 || ushort.max < w || ushort.max < h) 194 throw new ImageIOException("invalid dimensions"); 195 ulong src_chans = data.length / w / h; 196 if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans) 197 throw new ImageIOException("invalid channel count"); 198 if (src_chans * w * h != data.length) 199 throw new ImageIOException("mismatching dimensions and length"); 200 201 TGA_Encoder ec = { 202 stream : stream, 203 w : cast(ushort) w, 204 h : cast(ushort) h, 205 src_chans : cast(int) src_chans, 206 tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans), 207 rle : true, 208 data : data, 209 }; 210 211 write_tga(ec); 212 stream.flush(); 213 } 214 215 struct TGA_Decoder { 216 Reader stream; 217 int w, h; 218 bool origin_at_top; // src 219 uint bytes_pp; 220 bool rle; // run length compressed 221 _ColFmt src_fmt; 222 uint tgt_chans; 223 } 224 225 ubyte[] decode_tga(ref TGA_Decoder dc) { 226 auto result = new ubyte[dc.w * dc.h * dc.tgt_chans]; 227 228 immutable size_t tgt_linesize = dc.w * dc.tgt_chans; 229 immutable size_t src_linesize = dc.w * dc.bytes_pp; 230 auto src_line = new ubyte[src_linesize]; 231 232 immutable ptrdiff_t tgt_stride = (dc.origin_at_top) ? tgt_linesize : -tgt_linesize; 233 ptrdiff_t ti = (dc.origin_at_top) ? 0 : (dc.h-1) * tgt_linesize; 234 235 const LineConv!ubyte convert = get_converter!ubyte(dc.src_fmt, dc.tgt_chans); 236 237 if (!dc.rle) { 238 foreach (_j; 0 .. dc.h) { 239 dc.stream.readExact(src_line, src_linesize); 240 convert(src_line, result[ti .. ti + tgt_linesize]); 241 ti += tgt_stride; 242 } 243 return result; 244 } 245 246 // ----- RLE ----- 247 248 auto rbuf = new ubyte[src_linesize]; 249 size_t plen = 0; // packet length 250 bool its_rle = false; 251 252 foreach (_j; 0 .. dc.h) { 253 // fill src_line with uncompressed data (this works like a stream) 254 size_t wanted = src_linesize; 255 while (wanted) { 256 if (plen == 0) { 257 dc.stream.readExact(rbuf, 1); 258 its_rle = cast(bool) (rbuf[0] & 0x80); 259 plen = ((rbuf[0] & 0x7f) + 1) * dc.bytes_pp; // length in bytes 260 } 261 const size_t gotten = src_linesize - wanted; 262 const size_t copysize = min(plen, wanted); 263 if (its_rle) { 264 dc.stream.readExact(rbuf, dc.bytes_pp); 265 for (size_t p = gotten; p < gotten+copysize; p += dc.bytes_pp) 266 src_line[p .. p+dc.bytes_pp] = rbuf[0 .. dc.bytes_pp]; 267 } else { // it's raw 268 auto slice = src_line[gotten .. gotten+copysize]; 269 dc.stream.readExact(slice, copysize); 270 } 271 wanted -= copysize; 272 plen -= copysize; 273 } 274 275 convert(src_line, result[ti .. ti + tgt_linesize]); 276 ti += tgt_stride; 277 } 278 279 return result; 280 } 281 282 // ---------------------------------------------------------------------- 283 // TGA encoder 284 285 immutable ubyte[18] tga_footer_sig = 286 ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.', 0]; 287 288 struct TGA_Encoder { 289 Writer stream; 290 ushort w, h; 291 int src_chans; 292 int tgt_chans; 293 bool rle; // run length compression 294 const(ubyte)[] data; 295 } 296 297 void write_tga(ref TGA_Encoder ec) { 298 ubyte data_type; 299 bool has_alpha = false; 300 switch (ec.tgt_chans) with (TGA_DataType) { 301 case 1: data_type = ec.rle ? Gray_RLE : Gray; break; 302 case 2: data_type = ec.rle ? Gray_RLE : Gray; has_alpha = true; break; 303 case 3: data_type = ec.rle ? TrueColor_RLE : TrueColor; break; 304 case 4: data_type = ec.rle ? TrueColor_RLE : TrueColor; has_alpha = true; break; 305 default: throw new ImageIOException("internal error"); 306 } 307 308 ubyte[18] hdr = void; 309 hdr[0] = 0; // id length 310 hdr[1] = 0; // palette type 311 hdr[2] = data_type; 312 hdr[3..8] = 0; // palette start (2), len (2), bits per palette entry (1) 313 hdr[8..12] = 0; // x origin (2), y origin (2) 314 hdr[12..14] = nativeToLittleEndian(ec.w); 315 hdr[14..16] = nativeToLittleEndian(ec.h); 316 hdr[16] = cast(ubyte) (ec.tgt_chans * 8); // bits per pixel 317 hdr[17] = (has_alpha) ? 0x8 : 0x0; // flags: attr_bits_pp = 8 318 ec.stream.rawWrite(hdr); 319 320 write_image_data(ec); 321 322 ubyte[26] ftr = void; 323 ftr[0..4] = 0; // extension area offset 324 ftr[4..8] = 0; // developer directory offset 325 ftr[8..26] = tga_footer_sig; 326 ec.stream.rawWrite(ftr); 327 } 328 329 void write_image_data(ref TGA_Encoder ec) { 330 _ColFmt tgt_fmt; 331 switch (ec.tgt_chans) { 332 case 1: tgt_fmt = _ColFmt.Y; break; 333 case 2: tgt_fmt = _ColFmt.YA; break; 334 case 3: tgt_fmt = _ColFmt.BGR; break; 335 case 4: tgt_fmt = _ColFmt.BGRA; break; 336 default: throw new ImageIOException("internal error"); 337 } 338 339 const LineConv!ubyte convert = get_converter!ubyte(ec.src_chans, tgt_fmt); 340 341 immutable size_t src_linesize = ec.w * ec.src_chans; 342 immutable size_t tgt_linesize = ec.w * ec.tgt_chans; 343 auto tgt_line = new ubyte[tgt_linesize]; 344 345 ptrdiff_t si = (ec.h-1) * src_linesize; // origin at bottom 346 347 if (!ec.rle) { 348 foreach (_; 0 .. ec.h) { 349 convert(ec.data[si .. si + src_linesize], tgt_line); 350 ec.stream.rawWrite(tgt_line); 351 si -= src_linesize; // origin at bottom 352 } 353 return; 354 } 355 356 // ----- RLE ----- 357 358 immutable bytes_pp = ec.tgt_chans; 359 immutable size_t max_packets_per_line = (tgt_linesize+127) / 128; 360 auto tgt_cmp = new ubyte[tgt_linesize + max_packets_per_line]; // compressed line 361 foreach (_; 0 .. ec.h) { 362 convert(ec.data[si .. si + src_linesize], tgt_line); 363 ubyte[] compressed_line = rle_compress(tgt_line, tgt_cmp, ec.w, bytes_pp); 364 ec.stream.rawWrite(compressed_line); 365 si -= src_linesize; // origin at bottom 366 } 367 } 368 369 ubyte[] rle_compress(in ubyte[] line, ubyte[] tgt_cmp, in size_t w, in int bytes_pp) pure { 370 immutable int rle_limit = (1 < bytes_pp) ? 2 : 3; // run len worth an RLE packet 371 size_t runlen = 0; 372 size_t rawlen = 0; 373 size_t raw_i = 0; // start of raw packet data in line 374 size_t cmp_i = 0; 375 size_t pixels_left = w; 376 const (ubyte)[] px; 377 for (size_t i = bytes_pp; pixels_left; i += bytes_pp) { 378 runlen = 1; 379 px = line[i-bytes_pp .. i]; 380 while (i < line.length && line[i .. i+bytes_pp] == px[0..$] && runlen < 128) { 381 ++runlen; 382 i += bytes_pp; 383 } 384 pixels_left -= runlen; 385 386 if (runlen < rle_limit) { 387 // data goes to raw packet 388 rawlen += runlen; 389 if (128 <= rawlen) { // full packet, need to store it 390 size_t copysize = 128 * bytes_pp; 391 tgt_cmp[cmp_i++] = 0x7f; // raw packet header 392 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 393 cmp_i += copysize; 394 raw_i += copysize; 395 rawlen -= 128; 396 } 397 } else { 398 // RLE packet is worth it 399 400 // store raw packet first, if any 401 if (rawlen) { 402 assert(rawlen < 128); 403 size_t copysize = rawlen * bytes_pp; 404 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header 405 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 406 cmp_i += copysize; 407 rawlen = 0; 408 } 409 410 // store RLE packet 411 tgt_cmp[cmp_i++] = cast(ubyte) (0x80 | (runlen-1)); // packet header 412 tgt_cmp[cmp_i .. cmp_i+bytes_pp] = px[0..$]; // packet data 413 cmp_i += bytes_pp; 414 raw_i = i; 415 } 416 } // for 417 418 if (rawlen) { // last packet of the line 419 size_t copysize = rawlen * bytes_pp; 420 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header 421 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 422 cmp_i += copysize; 423 } 424 return tgt_cmp[0 .. cmp_i]; 425 } 426 427 enum TGA_DataType : ubyte { 428 Idx = 1, 429 TrueColor = 2, 430 Gray = 3, 431 Idx_RLE = 9, 432 TrueColor_RLE = 10, 433 Gray_RLE = 11, 434 } 435 436 package void read_tga_info(Reader stream, out int w, out int h, out int chans) { 437 TGA_Header hdr = read_tga_header(stream); 438 w = hdr.width; 439 h = hdr.height; 440 441 // TGA is awkward... 442 auto dt = hdr.data_type; 443 if ((dt == TGA_DataType.TrueColor || dt == TGA_DataType.Gray || 444 dt == TGA_DataType.TrueColor_RLE || dt == TGA_DataType.Gray_RLE) 445 && (hdr.bits_pp % 8) == 0) 446 { 447 chans = hdr.bits_pp / 8; 448 return; 449 } else if (dt == TGA_DataType.Idx || dt == TGA_DataType.Idx_RLE) { 450 switch (hdr.palette_bits) { 451 case 15: chans = 3; return; 452 case 16: chans = 3; return; // one bit could be for some "interrupt control" 453 case 24: chans = 3; return; 454 case 32: chans = 4; return; 455 default: 456 } 457 } 458 chans = 0; // unknown 459 }