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