1 // Copyright (c) 2014 Tero Hänninen 2 // Boost Software License - Version 1.0 - August 17th, 2003 3 module imageformats; 4 5 import std.algorithm; // min, reverse 6 import std.bitmanip; // endianness stuff 7 import std.stdio; // File 8 import std.string; // toLower, lastIndexOf 9 10 struct IFImage { 11 long w, h; 12 ColFmt c; 13 ubyte[] pixels; 14 } 15 16 enum ColFmt { 17 Y = 1, 18 YA = 2, 19 RGB = 3, 20 RGBA = 4, 21 } 22 23 IFImage read_image(in char[] file, long req_chans = 0) { 24 const(char)[] ext = extract_extension_lowercase(file); 25 26 IFImage function(Reader, long) read_image; 27 switch (ext) { 28 case "png": read_image = &read_png; break; 29 case "tga": read_image = &read_tga; break; 30 case "bmp": read_image = &read_bmp; break; 31 case "jpg": read_image = &read_jpeg; break; 32 case "jpeg": read_image = &read_jpeg; break; 33 default: throw new ImageIOException("unknown image extension/type"); 34 } 35 scope reader = new Reader(file); 36 return read_image(reader, req_chans); 37 } 38 39 void write_image(in char[] file, long w, long h, in ubyte[] data, long req_chans = 0) { 40 const(char)[] ext = extract_extension_lowercase(file); 41 42 void function(Writer, long, long, in ubyte[], long) write_image; 43 switch (ext) { 44 case "png": write_image = &write_png; break; 45 case "tga": write_image = &write_tga; break; 46 default: throw new ImageIOException("unknown image extension/type"); 47 } 48 scope writer = new Writer(file); 49 write_image(writer, w, h, data, req_chans); 50 } 51 52 // chans is set to zero if num of channels is unknown 53 void read_image_info(in char[] file, out long w, out long h, out long chans) { 54 const(char)[] ext = extract_extension_lowercase(file); 55 56 void function(Reader, out long, out long, out long) read_image_info; 57 switch (ext) { 58 case "png": read_image_info = &read_png_info; break; 59 case "tga": read_image_info = &read_tga_info; break; 60 case "bmp": read_image_info = &read_bmp_info; break; 61 case "jpg": read_image_info = &read_jpeg_info; break; 62 case "jpeg": read_image_info = &read_jpeg_info; break; 63 default: throw new ImageIOException("unknown image extension/type"); 64 } 65 scope reader = new Reader(file); 66 return read_image_info(reader, w, h, chans); 67 } 68 69 class ImageIOException : Exception { 70 @safe pure const 71 this(string msg, string file = __FILE__, size_t line = __LINE__) { 72 super(msg, file, line); 73 } 74 } 75 76 // From here, things are private by default and public only explicitly. 77 private: 78 79 // -------------------------------------------------------------------------------- 80 // PNG 81 82 import std.digest.crc; 83 import std.zlib; 84 85 public struct PNG_Header { 86 int width; 87 int height; 88 ubyte bit_depth; 89 ubyte color_type; 90 ubyte compression_method; 91 ubyte filter_method; 92 ubyte interlace_method; 93 } 94 95 public PNG_Header read_png_header(in char[] filename) { 96 scope reader = new Reader(filename); 97 return read_png_header(reader); 98 } 99 100 PNG_Header read_png_header(Reader stream) { 101 ubyte[33] tmp = void; // file header, IHDR len+type+data+crc 102 stream.readExact(tmp, tmp.length); 103 104 ubyte[4] crc = crc32Of(tmp[12..29]); 105 reverse(crc[]); 106 if ( tmp[0..8] != png_file_header[0..$] || 107 tmp[8..16] != [0x0,0x0,0x0,0xd,'I','H','D','R'] || 108 crc != tmp[29..33] ) 109 throw new ImageIOException("corrupt header"); 110 111 PNG_Header header = { 112 width : bigEndianToNative!int(tmp[16..20]), 113 height : bigEndianToNative!int(tmp[20..24]), 114 bit_depth : tmp[24], 115 color_type : tmp[25], 116 compression_method : tmp[26], 117 filter_method : tmp[27], 118 interlace_method : tmp[28], 119 }; 120 return header; 121 } 122 123 public IFImage read_png(in char[] filename, long req_chans = 0) { 124 scope reader = new Reader(filename); 125 return read_png(reader, req_chans); 126 } 127 128 public IFImage read_png_from_mem(in ubyte[] source, long req_chans = 0) { 129 scope reader = new Reader(source); 130 return read_png(reader, req_chans); 131 } 132 133 IFImage read_png(Reader stream, long req_chans = 0) { 134 if (req_chans < 0 || 4 < req_chans) 135 throw new ImageIOException("come on..."); 136 137 PNG_Header hdr = read_png_header(stream); 138 139 if (hdr.width < 1 || hdr.height < 1 || int.max < cast(ulong) hdr.width * hdr.height) 140 throw new ImageIOException("invalid dimensions"); 141 if (hdr.bit_depth != 8) 142 throw new ImageIOException("only 8-bit images supported"); 143 if (! (hdr.color_type == PNG_ColorType.Y || 144 hdr.color_type == PNG_ColorType.RGB || 145 hdr.color_type == PNG_ColorType.Idx || 146 hdr.color_type == PNG_ColorType.YA || 147 hdr.color_type == PNG_ColorType.RGBA) ) 148 throw new ImageIOException("color type not supported"); 149 if (hdr.compression_method != 0 || hdr.filter_method != 0 || 150 (hdr.interlace_method != 0 && hdr.interlace_method != 1)) 151 throw new ImageIOException("not supported"); 152 153 PNG_Decoder dc = { 154 stream : stream, 155 src_indexed : (hdr.color_type == PNG_ColorType.Idx), 156 src_chans : channels(cast(PNG_ColorType) hdr.color_type), 157 ilace : hdr.interlace_method, 158 w : hdr.width, 159 h : hdr.height, 160 }; 161 dc.tgt_chans = (req_chans == 0) ? dc.src_chans : cast(int) req_chans; 162 163 IFImage result = { 164 w : dc.w, 165 h : dc.h, 166 c : cast(ColFmt) dc.tgt_chans, 167 pixels : decode_png(dc) 168 }; 169 return result; 170 } 171 172 public void write_png(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0) 173 { 174 scope writer = new Writer(file); 175 write_png(writer, w, h, data, tgt_chans); 176 } 177 178 public ubyte[] write_png_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) { 179 scope writer = new Writer(); 180 write_png(writer, w, h, data, tgt_chans); 181 return writer.result; 182 } 183 184 immutable ubyte[8] png_file_header = 185 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; 186 187 int channels(PNG_ColorType ct) pure nothrow { 188 final switch (ct) with (PNG_ColorType) { 189 case Y: return 1; 190 case RGB, Idx: return 3; 191 case YA: return 2; 192 case RGBA: return 4; 193 } 194 } 195 196 PNG_ColorType color_type(long channels) pure nothrow { 197 switch (channels) { 198 case 1: return PNG_ColorType.Y; 199 case 2: return PNG_ColorType.YA; 200 case 3: return PNG_ColorType.RGB; 201 case 4: return PNG_ColorType.RGBA; 202 default: assert(0); 203 } 204 } 205 206 struct PNG_Decoder { 207 Reader stream; 208 bool src_indexed; 209 int src_chans; 210 int tgt_chans; 211 size_t w, h; 212 ubyte ilace; 213 214 UnCompress uc; 215 CRC32 crc; 216 ubyte[12] chunkmeta; // crc | length and type 217 ubyte[] read_buf; 218 ubyte[] uc_buf; // uncompressed 219 ubyte[] palette; 220 } 221 222 ubyte[] decode_png(ref PNG_Decoder dc) { 223 dc.uc = new UnCompress(HeaderFormat.deflate); 224 dc.read_buf = new ubyte[4096]; 225 226 enum Stage { 227 IHDR_parsed, 228 PLTE_parsed, 229 IDAT_parsed, 230 IEND_parsed, 231 } 232 233 ubyte[] result; 234 auto stage = Stage.IHDR_parsed; 235 dc.stream.readExact(dc.chunkmeta[4..$], 8); // next chunk's len and type 236 237 while (stage != Stage.IEND_parsed) { 238 int len = bigEndianToNative!int(dc.chunkmeta[4..8]); 239 if (len < 0) 240 throw new ImageIOException("chunk too long"); 241 242 // standard allows PLTE chunk for non-indexed images too but we don't 243 dc.crc.put(dc.chunkmeta[8..12]); // type 244 switch (cast(char[]) dc.chunkmeta[8..12]) { // chunk type 245 case "IDAT": 246 if (! (stage == Stage.IHDR_parsed || 247 (stage == Stage.PLTE_parsed && dc.src_indexed)) ) 248 throw new ImageIOException("corrupt chunk stream"); 249 result = read_IDAT_stream(dc, len); 250 stage = Stage.IDAT_parsed; 251 break; 252 case "PLTE": 253 if (stage != Stage.IHDR_parsed) 254 throw new ImageIOException("corrupt chunk stream"); 255 int entries = len / 3; 256 if (len % 3 != 0 || 256 < entries) 257 throw new ImageIOException("corrupt chunk"); 258 dc.palette = new ubyte[len]; 259 dc.stream.readExact(dc.palette, dc.palette.length); 260 dc.crc.put(dc.palette); 261 dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type 262 ubyte[4] crc = dc.crc.finish; 263 reverse(crc[]); 264 if (crc != dc.chunkmeta[0..4]) 265 throw new ImageIOException("corrupt chunk"); 266 stage = Stage.PLTE_parsed; 267 break; 268 case "IEND": 269 if (stage != Stage.IDAT_parsed) 270 throw new ImageIOException("corrupt chunk stream"); 271 dc.stream.readExact(dc.chunkmeta, 4); // crc 272 if (len != 0 || dc.chunkmeta[0..4] != [0xae, 0x42, 0x60, 0x82]) 273 throw new ImageIOException("corrupt chunk"); 274 stage = Stage.IEND_parsed; 275 break; 276 case "IHDR": 277 throw new ImageIOException("corrupt chunk stream"); 278 default: 279 // unknown chunk, ignore but check crc 280 while (0 < len) { 281 size_t bytes = min(len, dc.read_buf.length); 282 dc.stream.readExact(dc.read_buf, bytes); 283 len -= bytes; 284 dc.crc.put(dc.read_buf[0..bytes]); 285 } 286 dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type 287 ubyte[4] crc = dc.crc.finish; 288 reverse(crc[]); 289 if (crc != dc.chunkmeta[0..4]) 290 throw new ImageIOException("corrupt chunk"); 291 } 292 } 293 294 return result; 295 } 296 297 enum PNG_ColorType : ubyte { 298 Y = 0, 299 RGB = 2, 300 Idx = 3, 301 YA = 4, 302 RGBA = 6, 303 } 304 305 enum PNG_FilterType : ubyte { 306 None = 0, 307 Sub = 1, 308 Up = 2, 309 Average = 3, 310 Paeth = 4, 311 } 312 313 enum InterlaceMethod { 314 None = 0, Adam7 = 1 315 } 316 317 ubyte[] read_IDAT_stream(ref PNG_Decoder dc, int len) { 318 bool metaready = false; // chunk len, type, crc 319 320 immutable uint filter_step = dc.src_indexed ? 1 : dc.src_chans; 321 immutable size_t tgt_linesize = cast(size_t) (dc.w * dc.tgt_chans); 322 323 ubyte[] depaletted_line = dc.src_indexed ? new ubyte[cast(size_t)dc.w * 3] : null; 324 ubyte[] result = new ubyte[cast(size_t)(dc.w * dc.h * dc.tgt_chans)]; 325 326 const LineConv chan_convert = get_converter(dc.src_chans, dc.tgt_chans); 327 328 void depalette_convert(in ubyte[] src_line, ubyte[] tgt_line) { 329 for (size_t s, d; s < src_line.length; s+=1, d+=3) { 330 size_t pidx = src_line[s] * 3; 331 if (dc.palette.length < pidx + 3) 332 throw new ImageIOException("palette idx wrong"); 333 depaletted_line[d .. d+3] = dc.palette[pidx .. pidx+3]; 334 } 335 chan_convert(depaletted_line[0 .. src_line.length*3], tgt_line); 336 } 337 338 void simple_convert(in ubyte[] src_line, ubyte[] tgt_line) { 339 chan_convert(src_line, tgt_line); 340 } 341 342 const convert = dc.src_indexed ? &depalette_convert : &simple_convert; 343 344 if (dc.ilace == InterlaceMethod.None) { 345 immutable size_t src_sl_size = cast(size_t) dc.w * filter_step; 346 auto cline = new ubyte[src_sl_size+1]; // current line + filter byte 347 auto pline = new ubyte[src_sl_size+1]; // previous line, inited to 0 348 debug(DebugPNG) assert(pline[0] == 0); 349 350 size_t tgt_si = 0; // scanline index in target buffer 351 foreach (j; 0 .. dc.h) { 352 uncompress_line(dc, len, metaready, cline); 353 ubyte filter_type = cline[0]; 354 355 recon(cline[1..$], pline[1..$], filter_type, filter_step); 356 convert(cline[1 .. $], result[tgt_si .. tgt_si + tgt_linesize]); 357 tgt_si += tgt_linesize; 358 359 ubyte[] _swap = pline; 360 pline = cline; 361 cline = _swap; 362 } 363 } else { 364 // Adam7 interlacing 365 366 immutable size_t[7] redw = [ 367 (dc.w + 7) / 8, 368 (dc.w + 3) / 8, 369 (dc.w + 3) / 4, 370 (dc.w + 1) / 4, 371 (dc.w + 1) / 2, 372 (dc.w + 0) / 2, 373 (dc.w + 0) / 1, 374 ]; 375 immutable size_t[7] redh = [ 376 (dc.h + 7) / 8, 377 (dc.h + 7) / 8, 378 (dc.h + 3) / 8, 379 (dc.h + 3) / 4, 380 (dc.h + 1) / 4, 381 (dc.h + 1) / 2, 382 (dc.h + 0) / 2, 383 ]; 384 385 const size_t max_scanline_size = cast(size_t) (dc.w * filter_step); 386 const linebuf0 = new ubyte[max_scanline_size+1]; // +1 for filter type byte 387 const linebuf1 = new ubyte[max_scanline_size+1]; // +1 for filter type byte 388 auto redlinebuf = new ubyte[cast(size_t) dc.w * dc.tgt_chans]; 389 390 foreach (pass; 0 .. 7) { 391 const A7_Catapult tgt_px = a7_catapults[pass]; // target pixel 392 const size_t src_linesize = redw[pass] * filter_step; 393 auto cline = cast(ubyte[]) linebuf0[0 .. src_linesize+1]; 394 auto pline = cast(ubyte[]) linebuf1[0 .. src_linesize+1]; 395 396 foreach (j; 0 .. redh[pass]) { 397 uncompress_line(dc, len, metaready, cline); 398 ubyte filter_type = cline[0]; 399 400 recon(cline[1..$], pline[1..$], filter_type, filter_step); 401 convert(cline[1 .. $], redlinebuf[0 .. redw[pass]*dc.tgt_chans]); 402 403 for (size_t i, redi; i < redw[pass]; ++i, redi += dc.tgt_chans) { 404 size_t tgt = tgt_px(i, j, dc.w) * dc.tgt_chans; 405 result[tgt .. tgt + dc.tgt_chans] = 406 redlinebuf[redi .. redi + dc.tgt_chans]; 407 } 408 409 ubyte[] _swap = pline; 410 pline = cline; 411 cline = _swap; 412 } 413 } 414 } 415 416 if (!metaready) { 417 dc.stream.readExact(dc.chunkmeta, 12); // crc | len & type 418 ubyte[4] crc = dc.crc.finish; 419 reverse(crc[]); 420 if (crc != dc.chunkmeta[0..4]) 421 throw new ImageIOException("corrupt chunk"); 422 } 423 return result; 424 } 425 426 alias A7_Catapult = size_t function(size_t redx, size_t redy, size_t dstw); 427 immutable A7_Catapult[7] a7_catapults = [ 428 &a7_red1_to_dst, 429 &a7_red2_to_dst, 430 &a7_red3_to_dst, 431 &a7_red4_to_dst, 432 &a7_red5_to_dst, 433 &a7_red6_to_dst, 434 &a7_red7_to_dst, 435 ]; 436 437 pure nothrow { 438 size_t a7_red1_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*8*dstw + redx*8; } 439 size_t a7_red2_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*8*dstw + redx*8+4; } 440 size_t a7_red3_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*8+4)*dstw + redx*4; } 441 size_t a7_red4_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*4*dstw + redx*4+2; } 442 size_t a7_red5_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*4+2)*dstw + redx*2; } 443 size_t a7_red6_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*2*dstw + redx*2+1; } 444 size_t a7_red7_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*2+1)*dstw + redx; } 445 } 446 447 void uncompress_line(ref PNG_Decoder dc, ref int length, ref bool metaready, ubyte[] dst) { 448 size_t readysize = min(dst.length, dc.uc_buf.length); 449 dst[0 .. readysize] = dc.uc_buf[0 .. readysize]; 450 dc.uc_buf = dc.uc_buf[readysize .. $]; 451 452 if (readysize == dst.length) 453 return; 454 455 while (readysize != dst.length) { 456 // need new data for dc.uc_buf... 457 if (length <= 0) { // IDAT is read -> read next chunks meta 458 dc.stream.readExact(dc.chunkmeta, 12); // crc | len & type 459 ubyte[4] crc = dc.crc.finish; 460 reverse(crc[]); 461 if (crc != dc.chunkmeta[0..4]) 462 throw new ImageIOException("corrupt chunk"); 463 464 length = bigEndianToNative!int(dc.chunkmeta[4..8]); 465 if (dc.chunkmeta[8..12] != "IDAT") { 466 // no new IDAT chunk so flush, this is the end of the IDAT stream 467 metaready = true; 468 dc.uc_buf = cast(ubyte[]) dc.uc.flush(); 469 size_t part2 = dst.length - readysize; 470 if (dc.uc_buf.length < part2) 471 throw new ImageIOException("not enough data"); 472 dst[readysize .. readysize+part2] = dc.uc_buf[0 .. part2]; 473 dc.uc_buf = dc.uc_buf[part2 .. $]; 474 return; 475 } 476 if (length <= 0) // empty IDAT chunk 477 throw new ImageIOException("not enough data"); 478 dc.crc.put(dc.chunkmeta[8..12]); // type 479 } 480 481 size_t bytes = min(length, dc.read_buf.length); 482 dc.stream.readExact(dc.read_buf, bytes); 483 length -= bytes; 484 dc.crc.put(dc.read_buf[0..bytes]); 485 486 if (bytes <= 0) 487 throw new ImageIOException("not enough data"); 488 489 dc.uc_buf = cast(ubyte[]) dc.uc.uncompress(dc.read_buf[0..bytes].dup); 490 491 size_t part2 = min(dst.length - readysize, dc.uc_buf.length); 492 dst[readysize .. readysize+part2] = dc.uc_buf[0 .. part2]; 493 dc.uc_buf = dc.uc_buf[part2 .. $]; 494 readysize += part2; 495 } 496 } 497 498 void recon(ubyte[] cline, in ubyte[] pline, ubyte ftype, int fstep) pure { 499 switch (ftype) with (PNG_FilterType) { 500 case None: 501 break; 502 case Sub: 503 foreach (k; fstep .. cline.length) 504 cline[k] += cline[k-fstep]; 505 break; 506 case Up: 507 foreach (k; 0 .. cline.length) 508 cline[k] += pline[k]; 509 break; 510 case Average: 511 foreach (k; 0 .. fstep) 512 cline[k] += pline[k] / 2; 513 foreach (k; fstep .. cline.length) 514 cline[k] += cast(ubyte) 515 ((cast(uint) cline[k-fstep] + cast(uint) pline[k]) / 2); 516 break; 517 case Paeth: 518 foreach (i; 0 .. fstep) 519 cline[i] += paeth(0, pline[i], 0); 520 foreach (i; fstep .. cline.length) 521 cline[i] += paeth(cline[i-fstep], pline[i], pline[i-fstep]); 522 break; 523 default: 524 throw new ImageIOException("filter type not supported"); 525 } 526 } 527 528 ubyte paeth(ubyte a, ubyte b, ubyte c) pure nothrow { 529 int pc = cast(int) c; 530 int pa = cast(int) b - pc; 531 int pb = cast(int) a - pc; 532 pc = pa + pb; 533 if (pa < 0) pa = -pa; 534 if (pb < 0) pb = -pb; 535 if (pc < 0) pc = -pc; 536 537 if (pa <= pb && pa <= pc) { 538 return a; 539 } else if (pb <= pc) { 540 return b; 541 } 542 return c; 543 } 544 545 // ---------------------------------------------------------------------- 546 // PNG encoder 547 548 void write_png(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) { 549 if (w < 1 || h < 1 || int.max < w || int.max < h) 550 throw new ImageIOException("invalid dimensions"); 551 uint src_chans = cast(uint) (data.length / w / h); 552 if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans) 553 throw new ImageIOException("invalid channel count"); 554 if (src_chans * w * h != data.length) 555 throw new ImageIOException("mismatching dimensions and length"); 556 557 PNG_Encoder ec = { 558 stream : stream, 559 w : cast(size_t) w, 560 h : cast(size_t) h, 561 src_chans : src_chans, 562 tgt_chans : tgt_chans ? cast(uint) tgt_chans : src_chans, 563 data : data, 564 }; 565 566 write_png(ec); 567 stream.flush(); 568 } 569 570 struct PNG_Encoder { 571 Writer stream; 572 size_t w, h; 573 uint src_chans; 574 uint tgt_chans; 575 const(ubyte)[] data; 576 577 CRC32 crc; 578 579 uint writelen; // how much written of current idat data 580 ubyte[] chunk_buf; // len type data crc 581 ubyte[] data_buf; // slice of chunk_buf, for just chunk data 582 } 583 584 void write_png(ref PNG_Encoder ec) { 585 ubyte[33] hdr = void; 586 hdr[ 0 .. 8] = png_file_header; 587 hdr[ 8 .. 16] = [0x0, 0x0, 0x0, 0xd, 'I','H','D','R']; 588 hdr[16 .. 20] = nativeToBigEndian(cast(uint) ec.w); 589 hdr[20 .. 24] = nativeToBigEndian(cast(uint) ec.h); 590 hdr[24 ] = 8; // bit depth 591 hdr[25 ] = color_type(ec.tgt_chans); 592 hdr[26 .. 29] = 0; // compression, filter and interlace methods 593 ec.crc.start(); 594 ec.crc.put(hdr[12 .. 29]); 595 ubyte[4] crc = ec.crc.finish(); 596 reverse(crc[]); 597 hdr[29 .. 33] = crc; 598 ec.stream.rawWrite(hdr); 599 600 write_IDATs(ec); 601 602 static immutable ubyte[12] iend = 603 [0, 0, 0, 0, 'I','E','N','D', 0xae, 0x42, 0x60, 0x82]; 604 ec.stream.rawWrite(iend); 605 } 606 607 void write_IDATs(ref PNG_Encoder ec) { 608 static immutable ubyte[4] IDAT_type = ['I','D','A','T']; 609 immutable long max_idatlen = 4 * 4096; 610 ec.writelen = 0; 611 ec.chunk_buf = new ubyte[8 + max_idatlen + 4]; 612 ec.data_buf = ec.chunk_buf[8 .. 8 + max_idatlen]; 613 ec.chunk_buf[4 .. 8] = IDAT_type; 614 615 immutable size_t linesize = ec.w * ec.tgt_chans + 1; // +1 for filter type 616 ubyte[] cline = new ubyte[linesize]; 617 ubyte[] pline = new ubyte[linesize]; 618 debug(DebugPNG) assert(pline[0] == 0); 619 620 ubyte[] filtered_line = new ubyte[linesize]; 621 ubyte[] filtered_image; 622 623 const LineConv convert = get_converter(ec.src_chans, ec.tgt_chans); 624 625 immutable size_t filter_step = ec.tgt_chans; // step between pixels, in bytes 626 immutable size_t src_linesize = ec.w * ec.src_chans; 627 628 size_t si = 0; 629 foreach (j; 0 .. ec.h) { 630 convert(ec.data[si .. si+src_linesize], cline[1..$]); 631 si += src_linesize; 632 633 foreach (i; 1 .. filter_step+1) 634 filtered_line[i] = cast(ubyte) (cline[i] - paeth(0, pline[i], 0)); 635 foreach (i; filter_step+1 .. cline.length) 636 filtered_line[i] = cast(ubyte) 637 (cline[i] - paeth(cline[i-filter_step], pline[i], pline[i-filter_step])); 638 639 filtered_line[0] = PNG_FilterType.Paeth; 640 641 filtered_image ~= filtered_line; 642 643 ubyte[] _swap = pline; 644 pline = cline; 645 cline = _swap; 646 } 647 648 const (void)[] xx = compress(filtered_image, 6); 649 650 ec.write_to_IDAT_stream(xx); 651 if (0 < ec.writelen) 652 ec.write_IDAT_chunk(); 653 } 654 655 void write_to_IDAT_stream(ref PNG_Encoder ec, in void[] _compressed) { 656 ubyte[] compressed = cast(ubyte[]) _compressed; 657 while (compressed.length) { 658 size_t space_left = ec.data_buf.length - ec.writelen; 659 size_t writenow_len = min(space_left, compressed.length); 660 ec.data_buf[ec.writelen .. ec.writelen + writenow_len] = 661 compressed[0 .. writenow_len]; 662 ec.writelen += writenow_len; 663 compressed = compressed[writenow_len .. $]; 664 if (ec.writelen == ec.data_buf.length) 665 ec.write_IDAT_chunk(); 666 } 667 } 668 669 // chunk: len type data crc, type is already in buf 670 void write_IDAT_chunk(ref PNG_Encoder ec) { 671 ec.chunk_buf[0 .. 4] = nativeToBigEndian!uint(ec.writelen); 672 ec.crc.put(ec.chunk_buf[4 .. 8 + ec.writelen]); // crc of type and data 673 ubyte[4] crc = ec.crc.finish(); 674 reverse(crc[]); 675 ec.chunk_buf[8 + ec.writelen .. 8 + ec.writelen + 4] = crc; 676 ec.stream.rawWrite(ec.chunk_buf[0 .. 8 + ec.writelen + 4]); 677 ec.writelen = 0; 678 } 679 680 void read_png_info(Reader stream, out long w, out long h, out long chans) { 681 PNG_Header hdr = read_png_header(stream); 682 w = hdr.width; 683 h = hdr.height; 684 chans = channels(cast(PNG_ColorType) hdr.color_type); 685 } 686 687 // -------------------------------------------------------------------------------- 688 // TGA 689 690 public struct TGA_Header { 691 ubyte id_length; 692 ubyte palette_type; 693 ubyte data_type; 694 ushort palette_start; 695 ushort palette_length; 696 ubyte palette_bits; 697 ushort x_origin; 698 ushort y_origin; 699 ushort width; 700 ushort height; 701 ubyte bits_pp; 702 ubyte flags; 703 } 704 705 public TGA_Header read_tga_header(in char[] filename) { 706 scope reader = new Reader(filename); 707 return read_tga_header(reader); 708 } 709 710 TGA_Header read_tga_header(Reader stream) { 711 ubyte[18] tmp = void; 712 stream.readExact(tmp, tmp.length); 713 714 TGA_Header header = { 715 id_length : tmp[0], 716 palette_type : tmp[1], 717 data_type : tmp[2], 718 palette_start : littleEndianToNative!ushort(tmp[3..5]), 719 palette_length : littleEndianToNative!ushort(tmp[5..7]), 720 palette_bits : tmp[7], 721 x_origin : littleEndianToNative!ushort(tmp[8..10]), 722 y_origin : littleEndianToNative!ushort(tmp[10..12]), 723 width : littleEndianToNative!ushort(tmp[12..14]), 724 height : littleEndianToNative!ushort(tmp[14..16]), 725 bits_pp : tmp[16], 726 flags : tmp[17], 727 }; 728 return header; 729 } 730 731 public IFImage read_tga(in char[] filename, long req_chans = 0) { 732 scope reader = new Reader(filename); 733 return read_tga(reader, req_chans); 734 } 735 736 public IFImage read_tga_from_mem(in ubyte[] source, long req_chans = 0) { 737 scope reader = new Reader(source); 738 return read_tga(reader, req_chans); 739 } 740 741 IFImage read_tga(Reader stream, long req_chans = 0) { 742 if (req_chans < 0 || 4 < req_chans) 743 throw new ImageIOException("come on..."); 744 745 TGA_Header hdr = read_tga_header(stream); 746 747 if (hdr.width < 1 || hdr.height < 1) 748 throw new ImageIOException("invalid dimensions"); 749 if (hdr.flags & 0xc0) // two bits 750 throw new ImageIOException("interlaced TGAs not supported"); 751 if (hdr.flags & 0x10) 752 throw new ImageIOException("right-to-left TGAs not supported"); 753 ubyte attr_bits_pp = (hdr.flags & 0xf); 754 if (! (attr_bits_pp == 0 || attr_bits_pp == 8)) // some set it 0 although data has 8 755 throw new ImageIOException("only 8-bit alpha/attribute(s) supported"); 756 if (hdr.palette_type) 757 throw new ImageIOException("paletted TGAs not supported"); 758 759 bool rle = false; 760 switch (hdr.data_type) with (TGA_DataType) { 761 //case 1: ; // paletted, uncompressed 762 case TrueColor: 763 if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32)) 764 throw new ImageIOException("not supported"); 765 break; 766 case Gray: 767 if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8))) 768 throw new ImageIOException("not supported"); 769 break; 770 //case 9: ; // paletted, RLE 771 case TrueColor_RLE: 772 if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32)) 773 throw new ImageIOException("not supported"); 774 rle = true; 775 break; 776 case Gray_RLE: 777 if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8))) 778 throw new ImageIOException("not supported"); 779 rle = true; 780 break; 781 default: throw new ImageIOException("data type not supported"); 782 } 783 784 int src_chans = hdr.bits_pp / 8; 785 786 if (hdr.id_length) 787 stream.seek(hdr.id_length, SEEK_CUR); 788 789 TGA_Decoder dc = { 790 stream : stream, 791 w : hdr.width, 792 h : hdr.height, 793 origin_at_top : cast(bool) (hdr.flags & 0x20), 794 bytes_pp : hdr.bits_pp / 8, 795 rle : rle, 796 tgt_chans : (req_chans == 0) ? src_chans : cast(int) req_chans, 797 }; 798 799 switch (dc.bytes_pp) { 800 case 1: dc.src_fmt = _ColFmt.Y; break; 801 case 2: dc.src_fmt = _ColFmt.YA; break; 802 case 3: dc.src_fmt = _ColFmt.BGR; break; 803 case 4: dc.src_fmt = _ColFmt.BGRA; break; 804 default: throw new ImageIOException("TGA: format not supported"); 805 } 806 807 IFImage result = { 808 w : dc.w, 809 h : dc.h, 810 c : cast(ColFmt) dc.tgt_chans, 811 pixels : decode_tga(dc), 812 }; 813 return result; 814 } 815 816 public void write_tga(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0) 817 { 818 scope writer = new Writer(file); 819 write_tga(writer, w, h, data, tgt_chans); 820 } 821 822 public ubyte[] write_tga_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) { 823 scope writer = new Writer(); 824 write_tga(writer, w, h, data, tgt_chans); 825 return writer.result; 826 } 827 828 void write_tga(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) { 829 if (w < 1 || h < 1 || ushort.max < w || ushort.max < h) 830 throw new ImageIOException("invalid dimensions"); 831 ulong src_chans = data.length / w / h; 832 if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans) 833 throw new ImageIOException("invalid channel count"); 834 if (src_chans * w * h != data.length) 835 throw new ImageIOException("mismatching dimensions and length"); 836 837 TGA_Encoder ec = { 838 stream : stream, 839 w : cast(ushort) w, 840 h : cast(ushort) h, 841 src_chans : cast(int) src_chans, 842 tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans), 843 rle : true, 844 data : data, 845 }; 846 847 write_tga(ec); 848 stream.flush(); 849 } 850 851 struct TGA_Decoder { 852 Reader stream; 853 size_t w, h; 854 bool origin_at_top; // src 855 uint bytes_pp; 856 bool rle; // run length compressed 857 _ColFmt src_fmt; 858 uint tgt_chans; 859 } 860 861 ubyte[] decode_tga(ref TGA_Decoder dc) { 862 auto result = new ubyte[dc.w * dc.h * dc.tgt_chans]; 863 864 immutable size_t tgt_linesize = dc.w * dc.tgt_chans; 865 immutable size_t src_linesize = dc.w * dc.bytes_pp; 866 auto src_line = new ubyte[src_linesize]; 867 868 immutable ptrdiff_t tgt_stride = (dc.origin_at_top) ? tgt_linesize : -tgt_linesize; 869 ptrdiff_t ti = (dc.origin_at_top) ? 0 : (dc.h-1) * tgt_linesize; 870 871 const LineConv convert = get_converter(dc.src_fmt, dc.tgt_chans); 872 873 if (!dc.rle) { 874 foreach (_j; 0 .. dc.h) { 875 dc.stream.readExact(src_line, src_linesize); 876 convert(src_line, result[ti .. ti + tgt_linesize]); 877 ti += tgt_stride; 878 } 879 return result; 880 } 881 882 // ----- RLE ----- 883 884 auto rbuf = new ubyte[src_linesize]; 885 size_t plen = 0; // packet length 886 bool its_rle = false; 887 888 foreach (_j; 0 .. dc.h) { 889 // fill src_line with uncompressed data (this works like a stream) 890 size_t wanted = src_linesize; 891 while (wanted) { 892 if (plen == 0) { 893 dc.stream.readExact(rbuf, 1); 894 its_rle = cast(bool) (rbuf[0] & 0x80); 895 plen = ((rbuf[0] & 0x7f) + 1) * dc.bytes_pp; // length in bytes 896 } 897 const size_t gotten = src_linesize - wanted; 898 const size_t copysize = min(plen, wanted); 899 if (its_rle) { 900 dc.stream.readExact(rbuf, dc.bytes_pp); 901 for (size_t p = gotten; p < gotten+copysize; p += dc.bytes_pp) 902 src_line[p .. p+dc.bytes_pp] = rbuf[0 .. dc.bytes_pp]; 903 } else { // it's raw 904 auto slice = src_line[gotten .. gotten+copysize]; 905 dc.stream.readExact(slice, copysize); 906 } 907 wanted -= copysize; 908 plen -= copysize; 909 } 910 911 convert(src_line, result[ti .. ti + tgt_linesize]); 912 ti += tgt_stride; 913 } 914 915 return result; 916 } 917 918 // ---------------------------------------------------------------------- 919 // TGA encoder 920 921 immutable ubyte[18] tga_footer_sig = 922 ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.', 0]; 923 924 struct TGA_Encoder { 925 Writer stream; 926 ushort w, h; 927 int src_chans; 928 int tgt_chans; 929 bool rle; // run length compression 930 const(ubyte)[] data; 931 } 932 933 void write_tga(ref TGA_Encoder ec) { 934 ubyte data_type; 935 bool has_alpha = false; 936 switch (ec.tgt_chans) with (TGA_DataType) { 937 case 1: data_type = ec.rle ? Gray_RLE : Gray; break; 938 case 2: data_type = ec.rle ? Gray_RLE : Gray; has_alpha = true; break; 939 case 3: data_type = ec.rle ? TrueColor_RLE : TrueColor; break; 940 case 4: data_type = ec.rle ? TrueColor_RLE : TrueColor; has_alpha = true; break; 941 default: throw new ImageIOException("internal error"); 942 } 943 944 ubyte[18] hdr = void; 945 hdr[0] = 0; // id length 946 hdr[1] = 0; // palette type 947 hdr[2] = data_type; 948 hdr[3..8] = 0; // palette start (2), len (2), bits per palette entry (1) 949 hdr[8..12] = 0; // x origin (2), y origin (2) 950 hdr[12..14] = nativeToLittleEndian(ec.w); 951 hdr[14..16] = nativeToLittleEndian(ec.h); 952 hdr[16] = cast(ubyte) (ec.tgt_chans * 8); // bits per pixel 953 hdr[17] = (has_alpha) ? 0x8 : 0x0; // flags: attr_bits_pp = 8 954 ec.stream.rawWrite(hdr); 955 956 write_image_data(ec); 957 958 ubyte[26] ftr = void; 959 ftr[0..4] = 0; // extension area offset 960 ftr[4..8] = 0; // developer directory offset 961 ftr[8..26] = tga_footer_sig; 962 ec.stream.rawWrite(ftr); 963 } 964 965 void write_image_data(ref TGA_Encoder ec) { 966 _ColFmt tgt_fmt; 967 switch (ec.tgt_chans) { 968 case 1: tgt_fmt = _ColFmt.Y; break; 969 case 2: tgt_fmt = _ColFmt.YA; break; 970 case 3: tgt_fmt = _ColFmt.BGR; break; 971 case 4: tgt_fmt = _ColFmt.BGRA; break; 972 default: throw new ImageIOException("internal error"); 973 } 974 975 const LineConv convert = get_converter(ec.src_chans, tgt_fmt); 976 977 immutable size_t src_linesize = ec.w * ec.src_chans; 978 immutable size_t tgt_linesize = ec.w * ec.tgt_chans; 979 auto tgt_line = new ubyte[tgt_linesize]; 980 981 ptrdiff_t si = (ec.h-1) * src_linesize; // origin at bottom 982 983 if (!ec.rle) { 984 foreach (_; 0 .. ec.h) { 985 convert(ec.data[si .. si + src_linesize], tgt_line); 986 ec.stream.rawWrite(tgt_line); 987 si -= src_linesize; // origin at bottom 988 } 989 return; 990 } 991 992 // ----- RLE ----- 993 994 immutable bytes_pp = ec.tgt_chans; 995 immutable size_t max_packets_per_line = (tgt_linesize+127) / 128; 996 auto tgt_cmp = new ubyte[tgt_linesize + max_packets_per_line]; // compressed line 997 foreach (_; 0 .. ec.h) { 998 convert(ec.data[si .. si + src_linesize], tgt_line); 999 ubyte[] compressed_line = rle_compress(tgt_line, tgt_cmp, ec.w, bytes_pp); 1000 ec.stream.rawWrite(compressed_line); 1001 si -= src_linesize; // origin at bottom 1002 } 1003 } 1004 1005 ubyte[] rle_compress(in ubyte[] line, ubyte[] tgt_cmp, in size_t w, in int bytes_pp) pure { 1006 immutable int rle_limit = (1 < bytes_pp) ? 2 : 3; // run len worth an RLE packet 1007 size_t runlen = 0; 1008 size_t rawlen = 0; 1009 size_t raw_i = 0; // start of raw packet data in line 1010 size_t cmp_i = 0; 1011 size_t pixels_left = w; 1012 const (ubyte)[] px; 1013 for (size_t i = bytes_pp; pixels_left; i += bytes_pp) { 1014 runlen = 1; 1015 px = line[i-bytes_pp .. i]; 1016 while (i < line.length && line[i .. i+bytes_pp] == px[0..$] && runlen < 128) { 1017 ++runlen; 1018 i += bytes_pp; 1019 } 1020 pixels_left -= runlen; 1021 1022 if (runlen < rle_limit) { 1023 // data goes to raw packet 1024 rawlen += runlen; 1025 if (128 <= rawlen) { // full packet, need to store it 1026 size_t copysize = 128 * bytes_pp; 1027 tgt_cmp[cmp_i++] = 0x7f; // raw packet header 1028 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 1029 cmp_i += copysize; 1030 raw_i += copysize; 1031 rawlen -= 128; 1032 } 1033 } else { 1034 // RLE packet is worth it 1035 1036 // store raw packet first, if any 1037 if (rawlen) { 1038 assert(rawlen < 128); 1039 size_t copysize = rawlen * bytes_pp; 1040 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header 1041 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 1042 cmp_i += copysize; 1043 rawlen = 0; 1044 } 1045 1046 // store RLE packet 1047 tgt_cmp[cmp_i++] = cast(ubyte) (0x80 | (runlen-1)); // packet header 1048 tgt_cmp[cmp_i .. cmp_i+bytes_pp] = px[0..$]; // packet data 1049 cmp_i += bytes_pp; 1050 raw_i = i; 1051 } 1052 } // for 1053 1054 if (rawlen) { // last packet of the line 1055 size_t copysize = rawlen * bytes_pp; 1056 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header 1057 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize]; 1058 cmp_i += copysize; 1059 } 1060 return tgt_cmp[0 .. cmp_i]; 1061 } 1062 1063 enum TGA_DataType : ubyte { 1064 Idx = 1, 1065 TrueColor = 2, 1066 Gray = 3, 1067 Idx_RLE = 9, 1068 TrueColor_RLE = 10, 1069 Gray_RLE = 11, 1070 } 1071 1072 void read_tga_info(Reader stream, out long w, out long h, out long chans) { 1073 TGA_Header hdr = read_tga_header(stream); 1074 w = hdr.width; 1075 h = hdr.height; 1076 1077 // TGA is awkward... 1078 auto dt = hdr.data_type; 1079 if ((dt == TGA_DataType.TrueColor || dt == TGA_DataType.Gray || 1080 dt == TGA_DataType.TrueColor_RLE || dt == TGA_DataType.Gray_RLE) 1081 && (hdr.bits_pp % 8) == 0) 1082 { 1083 chans = hdr.bits_pp / 8; 1084 return; 1085 } else if (dt == TGA_DataType.Idx || dt == TGA_DataType.Idx_RLE) { 1086 switch (hdr.palette_bits) { 1087 case 15: chans = 3; return; 1088 case 16: chans = 3; return; // one bit could be for some "interrupt control" 1089 case 24: chans = 3; return; 1090 case 32: chans = 4; return; 1091 default: 1092 } 1093 } 1094 chans = 0; // unknown 1095 } 1096 1097 // -------------------------------------------------------------------------------- 1098 // BMP 1099 1100 public IFImage read_bmp(in char[] filename, long req_chans = 0) { 1101 scope reader = new Reader(filename); 1102 return read_bmp(reader, req_chans); 1103 } 1104 1105 public IFImage read_bmp_from_mem(in ubyte[] source, long req_chans = 0) { 1106 scope reader = new Reader(source); 1107 return read_bmp(reader, req_chans); 1108 } 1109 1110 public BMP_Header read_bmp_header(in char[] filename) { 1111 scope reader = new Reader(filename); 1112 return read_bmp_header(reader); 1113 } 1114 1115 public struct BMP_Header { 1116 size_t file_size; 1117 size_t pixel_data_offset; 1118 1119 size_t dib_size; 1120 ptrdiff_t width; 1121 ptrdiff_t height; 1122 ushort planes; 1123 uint dib_version; 1124 DibV1 dib_v1; 1125 DibV2 dib_v2; 1126 uint dib_v3_alpha_mask; 1127 DibV4 dib_v4; 1128 DibV5 dib_v5; 1129 } 1130 1131 public struct DibV1 { 1132 size_t bits_pp; 1133 uint compression; 1134 size_t idat_size; 1135 size_t pixels_per_meter_x; 1136 size_t pixels_per_meter_y; 1137 size_t palette_length; 1138 uint important_color_count; 1139 } 1140 1141 public struct DibV2 { 1142 uint red_mask; 1143 uint green_mask; 1144 uint blue_mask; 1145 } 1146 1147 public struct DibV4 { 1148 uint color_space_type; 1149 ubyte[36] color_space_endpoints; 1150 uint gamma_red; 1151 uint gamma_green; 1152 uint gamma_blue; 1153 uint intent; 1154 } 1155 1156 public struct DibV5 { 1157 uint icc_profile_data; 1158 uint icc_profile_size; 1159 } 1160 1161 BMP_Header read_bmp_header(Reader stream) { 1162 ubyte[18] tmp = void; // bmp header + size of dib header 1163 stream.readExact(tmp[], tmp.length); 1164 1165 if (tmp[0..2] != ['B', 'M']) 1166 throw new ImageIOException("corrupt header"); 1167 1168 size_t dib_size = littleEndianToNative!uint(tmp[14..18]); 1169 uint dib_version; 1170 switch (dib_size) { 1171 case 12: dib_version = 0; break; 1172 case 40: dib_version = 1; break; 1173 case 52: dib_version = 2; break; 1174 case 56: dib_version = 3; break; 1175 case 108: dib_version = 4; break; 1176 case 124: dib_version = 5; break; 1177 default: throw new ImageIOException("unsupported dib version"); 1178 } 1179 auto dib_header = new ubyte[dib_size-4]; 1180 stream.readExact(dib_header[], dib_header.length); 1181 1182 DibV1 dib_v1; 1183 DibV2 dib_v2; 1184 uint dib_v3_alpha_mask; 1185 DibV4 dib_v4; 1186 DibV5 dib_v5; 1187 1188 if (1 <= dib_version) { 1189 DibV1 v1 = { 1190 bits_pp : cast(size_t) littleEndianToNative!ushort(dib_header[10..12]), 1191 compression : littleEndianToNative!uint(dib_header[12..16]), 1192 idat_size : cast(size_t) littleEndianToNative!uint(dib_header[16..20]), 1193 pixels_per_meter_x : cast(size_t) littleEndianToNative!uint(dib_header[20..24]), 1194 pixels_per_meter_y : cast(size_t) littleEndianToNative!uint(dib_header[24..28]), 1195 palette_length : cast(size_t) littleEndianToNative!uint(dib_header[28..32]), 1196 important_color_count : littleEndianToNative!uint(dib_header[32..36]), 1197 }; 1198 dib_v1 = v1; 1199 } 1200 1201 if (2 <= dib_version) { 1202 DibV2 v2 = { 1203 red_mask : littleEndianToNative!uint(dib_header[36..40]), 1204 green_mask : littleEndianToNative!uint(dib_header[40..44]), 1205 blue_mask : littleEndianToNative!uint(dib_header[44..48]), 1206 }; 1207 dib_v2 = v2; 1208 } 1209 1210 if (3 <= dib_version) { 1211 dib_v3_alpha_mask = littleEndianToNative!uint(dib_header[48..52]); 1212 } 1213 1214 if (4 <= dib_version) { 1215 DibV4 v4 = { 1216 color_space_type : littleEndianToNative!uint(dib_header[52..56]), 1217 color_space_endpoints : dib_header[56..92], 1218 gamma_red : littleEndianToNative!uint(dib_header[92..96]), 1219 gamma_green : littleEndianToNative!uint(dib_header[96..100]), 1220 gamma_blue : littleEndianToNative!uint(dib_header[100..104]), 1221 intent : littleEndianToNative!uint(dib_header[104..108]), 1222 }; 1223 dib_v4 = v4; 1224 } 1225 1226 if (5 <= dib_version) { 1227 DibV5 v5 = { 1228 icc_profile_data : littleEndianToNative!uint(dib_header[108..112]), 1229 icc_profile_size : littleEndianToNative!uint(dib_header[112..116]), 1230 }; 1231 dib_v5 = v5; 1232 } 1233 1234 BMP_Header header = { 1235 file_size : cast(size_t) littleEndianToNative!uint(tmp[2..6]), 1236 pixel_data_offset : cast(size_t) littleEndianToNative!uint(tmp[10..14]), 1237 width : littleEndianToNative!int(dib_header[0..4]), 1238 height : littleEndianToNative!int(dib_header[4..8]), 1239 planes : littleEndianToNative!ushort(dib_header[8..10]), 1240 dib_version : dib_version, 1241 dib_v1 : dib_v1, 1242 dib_v2 : dib_v2, 1243 dib_v3_alpha_mask : dib_v3_alpha_mask, 1244 dib_v4 : dib_v4, 1245 dib_v5 : dib_v5, 1246 }; 1247 return header; 1248 } 1249 1250 enum CMP_RGB = 0; 1251 enum CMP_BITS = 3; 1252 1253 IFImage read_bmp(Reader stream, long req_chans = 0) { 1254 if (req_chans < 0 || 4 < req_chans) 1255 throw new ImageIOException("unknown color format"); 1256 1257 BMP_Header hdr = read_bmp_header(stream); 1258 1259 if (hdr.width < 1 || hdr.height == 0) { throw new ImageIOException("invalid dimensions"); } 1260 if (hdr.pixel_data_offset < (14 + hdr.dib_size) 1261 || hdr.pixel_data_offset > 0xffffff /* arbitrary */) { 1262 throw new ImageIOException("invalid pixel data offset"); 1263 } 1264 if (hdr.planes != 1) { throw new ImageIOException("not supported"); } 1265 1266 auto bytes_pp = 1; 1267 bool paletted = true; 1268 size_t palette_length = 256; 1269 bool rgb_masked = false; 1270 auto pe_bytes_pp = 3; 1271 1272 if (1 <= hdr.dib_version) { 1273 if (256 < hdr.dib_v1.palette_length) 1274 throw new ImageIOException("ivnalid palette length"); 1275 if (hdr.dib_v1.bits_pp <= 8 && 1276 (hdr.dib_v1.palette_length == 0 || hdr.dib_v1.compression != CMP_RGB)) 1277 throw new ImageIOException("invalid format"); 1278 if (hdr.dib_v1.compression != CMP_RGB && hdr.dib_v1.compression != CMP_BITS) 1279 throw new ImageIOException("unsupported compression"); 1280 1281 switch (hdr.dib_v1.bits_pp) { 1282 case 8 : bytes_pp = 1; paletted = true; break; 1283 case 24 : bytes_pp = 3; paletted = false; break; 1284 case 32 : bytes_pp = 4; paletted = false; break; 1285 default: throw new ImageIOException("not supported"); 1286 } 1287 1288 palette_length = hdr.dib_v1.palette_length; 1289 rgb_masked = hdr.dib_v1.compression == CMP_BITS; 1290 pe_bytes_pp = 4; 1291 } 1292 1293 size_t mask_to_idx(uint mask) { 1294 switch (mask) { 1295 case 0xff00_0000: return 3; 1296 case 0x00ff_0000: return 2; 1297 case 0x0000_ff00: return 1; 1298 case 0x0000_00ff: return 0; 1299 default: throw new ImageIOException("unsupported mask"); 1300 } 1301 } 1302 1303 size_t redi = 2; 1304 size_t greeni = 1; 1305 size_t bluei = 0; 1306 if (rgb_masked) { 1307 if (hdr.dib_version < 2) 1308 throw new ImageIOException("invalid format"); 1309 redi = mask_to_idx(hdr.dib_v2.red_mask); 1310 greeni = mask_to_idx(hdr.dib_v2.green_mask); 1311 bluei = mask_to_idx(hdr.dib_v2.blue_mask); 1312 } 1313 1314 bool alpha_masked = false; 1315 size_t alphai = 0; 1316 if (3 <= hdr.dib_version && hdr.dib_v3_alpha_mask != 0) { 1317 alpha_masked = true; 1318 alphai = mask_to_idx(hdr.dib_v3_alpha_mask); 1319 } 1320 1321 ubyte[] depaletted_line = null; 1322 ubyte[] palette = null; 1323 if (paletted) { 1324 depaletted_line = new ubyte[hdr.width * pe_bytes_pp]; 1325 palette = new ubyte[palette_length * pe_bytes_pp]; 1326 stream.readExact(palette[], palette.length); 1327 } 1328 1329 stream.seek(hdr.pixel_data_offset, SEEK_SET); 1330 1331 immutable tgt_chans = (0 < req_chans) ? req_chans 1332 : (alpha_masked) ? _ColFmt.RGBA 1333 : _ColFmt.RGB; 1334 1335 const LineConv convert = get_converter(_ColFmt.BGRA, tgt_chans); 1336 1337 immutable size_t src_linesize = hdr.width * bytes_pp; // without padding 1338 immutable size_t src_pad = (paletted) ? 0 : 3 - ((src_linesize-1) % 4); 1339 immutable ptrdiff_t tgt_linesize = (hdr.width * cast(int) tgt_chans); 1340 1341 immutable ptrdiff_t tgt_stride = (hdr.height < 0) ? tgt_linesize : -tgt_linesize; 1342 ptrdiff_t ti = (hdr.height < 0) ? 0 : (hdr.height-1) * tgt_linesize; 1343 1344 auto src_line_buf = new ubyte[src_linesize + src_pad]; 1345 auto bgra_line_buf = (paletted) ? null : new ubyte[hdr.width * 4]; 1346 auto result = new ubyte[hdr.width * abs(hdr.height) * cast(int) tgt_chans]; 1347 1348 foreach (_; 0 .. abs(hdr.height)) { 1349 stream.readExact(src_line_buf[], src_line_buf.length); 1350 auto src_line = src_line_buf[0..src_linesize]; 1351 1352 if (paletted) { 1353 size_t ps = pe_bytes_pp; 1354 size_t di = 0; 1355 foreach (idx; src_line[]) { 1356 size_t i = idx * ps; 1357 depaletted_line[di .. di+ps] = palette[i .. i+ps]; 1358 if (ps == 4) { 1359 depaletted_line[di+3] = 255; 1360 } 1361 di += ps; 1362 } 1363 convert(depaletted_line[], result[ti .. (ti+tgt_linesize)]); 1364 } else { 1365 for (size_t si, di; si < src_line.length; si+=bytes_pp, di+=4) { 1366 bgra_line_buf[di + 0] = src_line[si + bluei]; 1367 bgra_line_buf[di + 1] = src_line[si + greeni]; 1368 bgra_line_buf[di + 2] = src_line[si + redi]; 1369 bgra_line_buf[di + 3] = (alpha_masked) ? src_line[si + alphai] 1370 : 255; 1371 } 1372 convert(bgra_line_buf[], result[ti .. (ti+tgt_linesize)]); 1373 } 1374 1375 ti += tgt_stride; 1376 } 1377 1378 IFImage ret = { 1379 w : hdr.width, 1380 h : abs(hdr.height), 1381 c : cast(ColFmt) tgt_chans, 1382 pixels : result, 1383 }; 1384 return ret; 1385 } 1386 1387 void read_bmp_info(Reader stream, out long w, out long h, out long chans) { 1388 BMP_Header hdr = read_bmp_header(stream); 1389 w = abs(hdr.width); 1390 h = abs(hdr.height); 1391 chans = (hdr.dib_version >= 3 && hdr.dib_v3_alpha_mask != 0) ? ColFmt.RGBA 1392 : ColFmt.RGB; 1393 } 1394 1395 // -------------------------------------------------------------------------------- 1396 // Baseline JPEG decoder 1397 1398 import std.math; // floor, ceil 1399 import core.stdc.stdlib : alloca; 1400 1401 //debug = DebugJPEG; 1402 1403 public IFImage read_jpeg(in char[] filename, long req_chans = 0) { 1404 scope reader = new Reader(filename); 1405 return read_jpeg(reader, req_chans); 1406 } 1407 1408 public IFImage read_jpeg_from_mem(in ubyte[] source, long req_chans = 0) { 1409 scope reader = new Reader(source); 1410 return read_jpeg(reader, req_chans); 1411 } 1412 1413 IFImage read_jpeg(Reader stream, long req_chans = 0) { 1414 if (req_chans < 0 || 4 < req_chans) 1415 throw new ImageIOException("come on..."); 1416 1417 // SOI 1418 ubyte[2] tmp = void; 1419 stream.readExact(tmp, tmp.length); 1420 if (tmp[0..2] != [0xff, 0xd8]) 1421 throw new ImageIOException("not JPEG"); 1422 1423 JPEG_Decoder dc = { stream: stream }; 1424 1425 read_markers(dc); // reads until first scan header or eoi 1426 if (dc.eoi_reached) 1427 throw new ImageIOException("no image data"); 1428 1429 dc.tgt_chans = (req_chans == 0) ? dc.num_comps : cast(int) req_chans; 1430 1431 IFImage result = { 1432 w : dc.width, 1433 h : dc.height, 1434 c : cast(ColFmt) dc.tgt_chans, 1435 pixels : decode_jpeg(dc), 1436 }; 1437 return result; 1438 } 1439 1440 struct JPEG_Decoder { 1441 Reader stream; 1442 1443 bool has_frame_header = false; 1444 bool eoi_reached = false; 1445 1446 ubyte[64][4] qtables; 1447 HuffTab[2] ac_tables; 1448 HuffTab[2] dc_tables; 1449 1450 ubyte cb; // current byte (next bit always at MSB) 1451 int bits_left; // num of unused bits in cb 1452 1453 bool correct_comp_ids; 1454 Component[3] comps; 1455 ubyte num_comps; 1456 int[3] index_for; // index_for[0] is index of comp that comes first in stream 1457 int tgt_chans; 1458 1459 size_t width, height; 1460 1461 int hmax, vmax; 1462 1463 ushort restart_interval; // number of MCUs in restart interval 1464 1465 // image component 1466 struct Component { 1467 ubyte id; 1468 ubyte sfx, sfy; // sampling factors, aka. h and v 1469 long x, y; // total num of samples, without fill samples 1470 ubyte qtable; 1471 ubyte ac_table; 1472 ubyte dc_table; 1473 int pred; // dc prediction 1474 ubyte[] data; // reconstructed samples 1475 } 1476 1477 int num_mcu_x; 1478 int num_mcu_y; 1479 } 1480 1481 struct HuffTab { 1482 // TODO where in the spec does it say 256 values/codes at most? 1483 ubyte[256] values; 1484 ubyte[257] sizes; 1485 short[16] mincode, maxcode; 1486 short[16] valptr; 1487 } 1488 1489 enum Marker : ubyte { 1490 SOI = 0xd8, // start of image 1491 SOF0 = 0xc0, // start of frame / baseline DCT 1492 //SOF1 = 0xc1, // start of frame / extended seq. 1493 //SOF2 = 0xc2, // start of frame / progressive DCT 1494 SOF3 = 0xc3, // start of frame / lossless 1495 SOF9 = 0xc9, // start of frame / extended seq., arithmetic 1496 SOF11 = 0xcb, // start of frame / lossless, arithmetic 1497 DHT = 0xc4, // define huffman tables 1498 DQT = 0xdb, // define quantization tables 1499 DRI = 0xdd, // define restart interval 1500 SOS = 0xda, // start of scan 1501 DNL = 0xdc, // define number of lines 1502 RST0 = 0xd0, // restart entropy coded data 1503 // ... 1504 RST7 = 0xd7, // restart entropy coded data 1505 APP0 = 0xe0, // application 0 segment 1506 // ... 1507 APPf = 0xef, // application f segment 1508 //DAC = 0xcc, // define arithmetic conditioning table 1509 COM = 0xfe, // comment 1510 EOI = 0xd9, // end of image 1511 } 1512 1513 void read_markers(ref JPEG_Decoder dc) { 1514 bool has_next_scan_header = false; 1515 while (!has_next_scan_header && !dc.eoi_reached) { 1516 ubyte[2] marker; 1517 dc.stream.readExact(marker, 2); 1518 1519 if (marker[0] != 0xff) 1520 throw new ImageIOException("no marker"); 1521 while (marker[1] == 0xff) 1522 dc.stream.readExact(marker[1..$], 1); 1523 1524 debug(DebugJPEG) writefln("marker: %s (%1$x)\t", cast(Marker) marker[1]); 1525 switch (marker[1]) with (Marker) { 1526 case DHT: dc.read_huffman_tables(); break; 1527 case DQT: dc.read_quantization_tables(); break; 1528 case SOF0: 1529 if (dc.has_frame_header) 1530 throw new ImageIOException("extra frame header"); 1531 debug(DebugJPEG) writeln(); 1532 dc.read_frame_header(); 1533 dc.has_frame_header = true; 1534 break; 1535 case SOS: 1536 if (!dc.has_frame_header) 1537 throw new ImageIOException("no frame header"); 1538 dc.read_scan_header(); 1539 has_next_scan_header = true; 1540 break; 1541 case DRI: dc.read_restart_interval(); break; 1542 case EOI: dc.eoi_reached = true; break; 1543 case APP0: .. case APPf: goto case; 1544 case COM: 1545 debug(DebugJPEG) writefln("-> skipping segment"); 1546 ubyte[2] lenbuf = void; 1547 dc.stream.readExact(lenbuf, lenbuf.length); 1548 int len = bigEndianToNative!ushort(lenbuf) - 2; 1549 dc.stream.seek(len, SEEK_CUR); 1550 break; 1551 default: throw new ImageIOException("invalid / unsupported marker"); 1552 } 1553 } 1554 } 1555 1556 // DHT -- define huffman tables 1557 void read_huffman_tables(ref JPEG_Decoder dc) { 1558 ubyte[19] tmp = void; 1559 dc.stream.readExact(tmp, 2); 1560 int len = bigEndianToNative!ushort(tmp[0..2]); 1561 len -= 2; 1562 1563 while (0 < len) { 1564 dc.stream.readExact(tmp, 17); // info byte & the BITS 1565 ubyte table_slot = tmp[0] & 0xf; // must be 0 or 1 for baseline 1566 ubyte table_class = tmp[0] >> 4; // 0 = dc table, 1 = ac table 1567 if (1 < table_slot || 1 < table_class) 1568 throw new ImageIOException("invalid / not supported"); 1569 1570 // compute total number of huffman codes 1571 int mt = 0; 1572 foreach (i; 1..17) 1573 mt += tmp[i]; 1574 if (256 < mt) // TODO where in the spec? 1575 throw new ImageIOException("invalid / not supported"); 1576 1577 if (table_class == 0) { 1578 dc.stream.readExact(dc.dc_tables[table_slot].values, mt); 1579 derive_table(dc.dc_tables[table_slot], tmp[1..17]); 1580 } else { 1581 dc.stream.readExact(dc.ac_tables[table_slot].values, mt); 1582 derive_table(dc.ac_tables[table_slot], tmp[1..17]); 1583 } 1584 1585 len -= 17 + mt; 1586 } 1587 } 1588 1589 // num_values is the BITS 1590 void derive_table(ref HuffTab table, in ref ubyte[16] num_values) { 1591 short[256] codes; 1592 1593 int k = 0; 1594 foreach (i; 0..16) { 1595 foreach (j; 0..num_values[i]) { 1596 table.sizes[k] = cast(ubyte) (i + 1); 1597 ++k; 1598 } 1599 } 1600 table.sizes[k] = 0; 1601 1602 k = 0; 1603 short code = 0; 1604 ubyte si = table.sizes[k]; 1605 while (true) { 1606 do { 1607 codes[k] = code; 1608 ++code; 1609 ++k; 1610 } while (si == table.sizes[k]); 1611 1612 if (table.sizes[k] == 0) 1613 break; 1614 1615 debug(DebugJPEG) assert(si < table.sizes[k]); 1616 do { 1617 code <<= 1; 1618 ++si; 1619 } while (si != table.sizes[k]); 1620 } 1621 1622 derive_mincode_maxcode_valptr( 1623 table.mincode, table.maxcode, table.valptr, 1624 codes, num_values 1625 ); 1626 } 1627 1628 // F.15 1629 void derive_mincode_maxcode_valptr( 1630 ref short[16] mincode, ref short[16] maxcode, ref short[16] valptr, 1631 in ref short[256] codes, in ref ubyte[16] num_values) pure 1632 { 1633 mincode[] = -1; 1634 maxcode[] = -1; 1635 valptr[] = -1; 1636 1637 int j = 0; 1638 foreach (i; 0..16) { 1639 if (num_values[i] != 0) { 1640 valptr[i] = cast(short) j; 1641 mincode[i] = codes[j]; 1642 j += num_values[i] - 1; 1643 maxcode[i] = codes[j]; 1644 j += 1; 1645 } 1646 } 1647 } 1648 1649 // DQT -- define quantization tables 1650 void read_quantization_tables(ref JPEG_Decoder dc) { 1651 ubyte[2] tmp = void; 1652 dc.stream.readExact(tmp, 2); 1653 int len = bigEndianToNative!ushort(tmp[0..2]); 1654 if (len % 65 != 2) 1655 throw new ImageIOException("invalid / not supported"); 1656 len -= 2; 1657 while (0 < len) { 1658 dc.stream.readExact(tmp, 1); 1659 ubyte table_info = tmp[0]; 1660 ubyte table_slot = table_info & 0xf; 1661 ubyte precision = table_info >> 4; // 0 = 8 bit, 1 = 16 bit 1662 if (3 < table_slot || precision != 0) // only 8 bit for baseline 1663 throw new ImageIOException("invalid / not supported"); 1664 1665 dc.stream.readExact(dc.qtables[table_slot], 64); 1666 len -= 1 + 64; 1667 } 1668 } 1669 1670 // SOF0 -- start of frame 1671 void read_frame_header(ref JPEG_Decoder dc) { 1672 ubyte[9] tmp = void; 1673 dc.stream.readExact(tmp, 8); 1674 int len = bigEndianToNative!ushort(tmp[0..2]); // 8 + num_comps*3 1675 ubyte precision = tmp[2]; 1676 dc.height = bigEndianToNative!ushort(tmp[3..5]); 1677 dc.width = bigEndianToNative!ushort(tmp[5..7]); 1678 dc.num_comps = tmp[7]; 1679 1680 if ( precision != 8 || 1681 (dc.num_comps != 1 && dc.num_comps != 3) || 1682 len != 8 + dc.num_comps*3 ) 1683 throw new ImageIOException("invalid / not supported"); 1684 1685 dc.hmax = 0; 1686 dc.vmax = 0; 1687 int mcu_du = 0; // data units in one mcu 1688 dc.stream.readExact(tmp, dc.num_comps*3); 1689 foreach (i; 0..dc.num_comps) { 1690 ubyte ci = tmp[i*3]; 1691 // JFIF says ci should be i+1, but there are images where ci is i. Normalize ids 1692 // so that ci == i, always. So much for standards... 1693 if (i == 0) { dc.correct_comp_ids = ci == i+1; } 1694 if ((dc.correct_comp_ids && ci != i+1) 1695 || (!dc.correct_comp_ids && ci != i)) 1696 throw new ImageIOException("invalid component id"); 1697 if (dc.correct_comp_ids) { ci -= 1; } 1698 1699 dc.index_for[i] = ci; 1700 auto comp = &dc.comps[ci]; 1701 comp.id = ci; 1702 ubyte sampling_factors = tmp[i*3 + 1]; 1703 comp.sfx = sampling_factors >> 4; 1704 comp.sfy = sampling_factors & 0xf; 1705 comp.qtable = tmp[i*3 + 2]; 1706 if ( comp.sfy < 1 || 4 < comp.sfy || 1707 comp.sfx < 1 || 4 < comp.sfx || 1708 3 < comp.qtable ) 1709 throw new ImageIOException("invalid / not supported"); 1710 1711 if (dc.hmax < comp.sfx) dc.hmax = comp.sfx; 1712 if (dc.vmax < comp.sfy) dc.vmax = comp.sfy; 1713 1714 mcu_du += comp.sfx * comp.sfy; 1715 } 1716 if (10 < mcu_du) 1717 throw new ImageIOException("invalid / not supported"); 1718 1719 foreach (i; 0..dc.num_comps) { 1720 dc.comps[i].x = cast(long) ceil(dc.width * (cast(double) dc.comps[i].sfx / dc.hmax)); 1721 dc.comps[i].y = cast(long) ceil(dc.height * (cast(double) dc.comps[i].sfy / dc.vmax)); 1722 1723 debug(DebugJPEG) writefln("%d comp %d sfx/sfy: %d/%d", i, dc.comps[i].id, 1724 dc.comps[i].sfx, 1725 dc.comps[i].sfy); 1726 } 1727 1728 long mcu_w = dc.hmax * 8; 1729 long mcu_h = dc.vmax * 8; 1730 dc.num_mcu_x = cast(int) ((dc.width + mcu_w-1) / mcu_w); 1731 dc.num_mcu_y = cast(int) ((dc.height + mcu_h-1) / mcu_h); 1732 1733 debug(DebugJPEG) { 1734 writefln("\tlen: %s", len); 1735 writefln("\tprecision: %s", precision); 1736 writefln("\tdimensions: %s x %s", dc.width, dc.height); 1737 writefln("\tnum_comps: %s", dc.num_comps); 1738 writefln("\tnum_mcu_x: %s", dc.num_mcu_x); 1739 writefln("\tnum_mcu_y: %s", dc.num_mcu_y); 1740 } 1741 1742 } 1743 1744 // SOS -- start of scan 1745 void read_scan_header(ref JPEG_Decoder dc) { 1746 ubyte[3] tmp = void; 1747 dc.stream.readExact(tmp, tmp.length); 1748 ushort len = bigEndianToNative!ushort(tmp[0..2]); 1749 ubyte num_scan_comps = tmp[2]; 1750 1751 if ( num_scan_comps != dc.num_comps || 1752 len != (6+num_scan_comps*2) ) 1753 throw new ImageIOException("invalid / not supported"); 1754 1755 auto buf = (cast(ubyte*) alloca((len-3) * ubyte.sizeof))[0..len-3]; 1756 dc.stream.readExact(buf, buf.length); 1757 1758 foreach (i; 0..num_scan_comps) { 1759 uint comp_id = buf[i*2] - ((dc.correct_comp_ids) ? 1 : 0); 1760 int ci; // component index 1761 while (ci < dc.num_comps && dc.comps[ci].id != comp_id) ++ci; 1762 if (dc.num_comps <= ci) 1763 throw new ImageIOException("invalid component id"); 1764 1765 ubyte tables = buf[i*2+1]; 1766 dc.comps[ci].dc_table = tables >> 4; 1767 dc.comps[ci].ac_table = tables & 0xf; 1768 if ( 1 < dc.comps[ci].dc_table || 1769 1 < dc.comps[ci].ac_table ) 1770 throw new ImageIOException("invalid / not supported"); 1771 } 1772 1773 // ignore these 1774 //ubyte spectral_start = buf[$-3]; 1775 //ubyte spectral_end = buf[$-2]; 1776 //ubyte approx = buf[$-1]; 1777 } 1778 1779 void read_restart_interval(ref JPEG_Decoder dc) { 1780 ubyte[4] tmp = void; 1781 dc.stream.readExact(tmp, tmp.length); 1782 ushort len = bigEndianToNative!ushort(tmp[0..2]); 1783 if (len != 4) 1784 throw new ImageIOException("invalid / not supported"); 1785 dc.restart_interval = bigEndianToNative!ushort(tmp[2..4]); 1786 debug(DebugJPEG) writeln("restart interval set to: ", dc.restart_interval); 1787 } 1788 1789 // reads data after the SOS segment 1790 ubyte[] decode_jpeg(ref JPEG_Decoder dc) { 1791 foreach (ref comp; dc.comps[0..dc.num_comps]) 1792 comp.data = new ubyte[dc.num_mcu_x*comp.sfx*8*dc.num_mcu_y*comp.sfy*8]; 1793 1794 // E.7 -- Multiple scans are for progressive images which are not supported 1795 //while (!dc.eoi_reached) { 1796 decode_scan(dc); // E.2.3 1797 //read_markers(dc); // reads until next scan header or eoi 1798 //} 1799 1800 // throw away fill samples and convert to target format 1801 return dc.reconstruct(); 1802 } 1803 1804 // E.2.3 and E.8 and E.9 1805 void decode_scan(ref JPEG_Decoder dc) { 1806 debug(DebugJPEG) writeln("decode scan..."); 1807 1808 int intervals, mcus; 1809 if (0 < dc.restart_interval) { 1810 int total_mcus = dc.num_mcu_x * dc.num_mcu_y; 1811 intervals = (total_mcus + dc.restart_interval-1) / dc.restart_interval; 1812 mcus = dc.restart_interval; 1813 } else { 1814 intervals = 1; 1815 mcus = dc.num_mcu_x * dc.num_mcu_y; 1816 } 1817 debug(DebugJPEG) writeln("intervals: ", intervals); 1818 1819 foreach (mcu_j; 0 .. dc.num_mcu_y) { 1820 foreach (mcu_i; 0 .. dc.num_mcu_x) { 1821 1822 // decode mcu 1823 foreach (_c; 0..dc.num_comps) { 1824 auto comp = &dc.comps[dc.index_for[_c]]; 1825 foreach (du_j; 0 .. comp.sfy) { 1826 foreach (du_i; 0 .. comp.sfx) { 1827 // decode entropy, dequantize & dezigzag 1828 short[64] data = decode_block(dc, *comp, dc.qtables[comp.qtable]); 1829 // idct & level-shift 1830 long outx = (mcu_i * comp.sfx + du_i) * 8; 1831 long outy = (mcu_j * comp.sfy + du_j) * 8; 1832 long dst_stride = dc.num_mcu_x * comp.sfx*8; 1833 ubyte* dst = comp.data.ptr + outy*dst_stride + outx; 1834 stbi__idct_block(dst, dst_stride, data); 1835 } 1836 } 1837 } 1838 1839 --mcus; 1840 1841 if (!mcus) { 1842 --intervals; 1843 if (!intervals) 1844 return; 1845 1846 read_restart(dc.stream); // RSTx marker 1847 1848 if (intervals == 1) { 1849 // last interval, may have fewer MCUs than defined by DRI 1850 mcus = (dc.num_mcu_y - mcu_j - 1) * dc.num_mcu_x + dc.num_mcu_x - mcu_i - 1; 1851 } else { 1852 mcus = dc.restart_interval; 1853 } 1854 1855 // reset decoder 1856 dc.cb = 0; 1857 dc.bits_left = 0; 1858 foreach (k; 0..dc.num_comps) 1859 dc.comps[k].pred = 0; 1860 } 1861 1862 } 1863 } 1864 } 1865 1866 // RST0-RST7 1867 void read_restart(Reader stream) { 1868 ubyte[2] tmp = void; 1869 stream.readExact(tmp, tmp.length); 1870 if (tmp[0] != 0xff || tmp[1] < Marker.RST0 || Marker.RST7 < tmp[1]) 1871 throw new ImageIOException("reset marker missing"); 1872 // the markers should cycle 0 through 7, could check that here... 1873 } 1874 1875 immutable ubyte[64] dezigzag = [ 1876 0, 1, 8, 16, 9, 2, 3, 10, 1877 17, 24, 32, 25, 18, 11, 4, 5, 1878 12, 19, 26, 33, 40, 48, 41, 34, 1879 27, 20, 13, 6, 7, 14, 21, 28, 1880 35, 42, 49, 56, 57, 50, 43, 36, 1881 29, 22, 15, 23, 30, 37, 44, 51, 1882 58, 59, 52, 45, 38, 31, 39, 46, 1883 53, 60, 61, 54, 47, 55, 62, 63, 1884 ]; 1885 1886 // decode entropy, dequantize & dezigzag (see section F.2) 1887 short[64] decode_block(ref JPEG_Decoder dc, ref JPEG_Decoder.Component comp, 1888 in ref ubyte[64] qtable) 1889 { 1890 short[64] res = 0; 1891 1892 ubyte t = decode_huff(dc, dc.dc_tables[comp.dc_table]); 1893 int diff = t ? dc.receive_and_extend(t) : 0; 1894 1895 comp.pred = comp.pred + diff; 1896 res[0] = cast(short) (comp.pred * qtable[0]); 1897 1898 int k = 1; 1899 do { 1900 ubyte rs = decode_huff(dc, dc.ac_tables[comp.ac_table]); 1901 ubyte rrrr = rs >> 4; 1902 ubyte ssss = rs & 0xf; 1903 1904 if (ssss == 0) { 1905 if (rrrr != 0xf) 1906 break; // end of block 1907 k += 16; // run length is 16 1908 continue; 1909 } 1910 1911 k += rrrr; 1912 1913 if (63 < k) 1914 throw new ImageIOException("corrupt block"); 1915 res[dezigzag[k]] = cast(short) (dc.receive_and_extend(ssss) * qtable[k]); 1916 k += 1; 1917 } while (k < 64); 1918 1919 return res; 1920 } 1921 1922 int receive_and_extend(ref JPEG_Decoder dc, ubyte s) { 1923 // receive 1924 int symbol = 0; 1925 foreach (_; 0..s) 1926 symbol = (symbol << 1) + nextbit(dc); 1927 // extend 1928 int vt = 1 << (s-1); 1929 if (symbol < vt) 1930 return symbol + (-1 << s) + 1; 1931 return symbol; 1932 } 1933 1934 // F.16 -- the DECODE 1935 ubyte decode_huff(ref JPEG_Decoder dc, in ref HuffTab tab) { 1936 short code = nextbit(dc); 1937 1938 int i = 0; 1939 while (tab.maxcode[i] < code) { 1940 code = cast(short) ((code << 1) + nextbit(dc)); 1941 i += 1; 1942 if (tab.maxcode.length <= i) 1943 throw new ImageIOException("corrupt huffman coding"); 1944 } 1945 int j = tab.valptr[i] + code - tab.mincode[i]; 1946 if (tab.values.length <= cast(uint) j) 1947 throw new ImageIOException("corrupt huffman coding"); 1948 return tab.values[j]; 1949 } 1950 1951 // F.2.2.5 and F.18 1952 ubyte nextbit(ref JPEG_Decoder dc) { 1953 if (!dc.bits_left) { 1954 ubyte[1] bytebuf; 1955 dc.stream.readExact(bytebuf, 1); 1956 dc.cb = bytebuf[0]; 1957 dc.bits_left = 8; 1958 1959 if (dc.cb == 0xff) { 1960 dc.stream.readExact(bytebuf, 1); 1961 if (bytebuf[0] != 0x0) { 1962 throw new ImageIOException("unexpected marker"); 1963 } 1964 } 1965 } 1966 1967 ubyte r = dc.cb >> 7; 1968 dc.cb <<= 1; 1969 dc.bits_left -= 1; 1970 return r; 1971 } 1972 1973 ubyte[] reconstruct(in ref JPEG_Decoder dc) { 1974 auto result = new ubyte[dc.width * dc.height * dc.tgt_chans]; 1975 1976 switch (dc.num_comps * 10 + dc.tgt_chans) { 1977 case 34, 33: 1978 foreach (const ref comp; dc.comps[0..dc.num_comps]) { 1979 if (comp.sfx != dc.hmax || comp.sfy != dc.vmax) 1980 return dc.upsample_rgb(result); 1981 } 1982 1983 size_t si, di; 1984 foreach (j; 0 .. dc.height) { 1985 foreach (i; 0 .. dc.width) { 1986 result[di .. di+3] = ycbcr_to_rgb( 1987 dc.comps[0].data[si+i], 1988 dc.comps[1].data[si+i], 1989 dc.comps[2].data[si+i], 1990 ); 1991 if (dc.tgt_chans == 4) 1992 result[di+3] = 255; 1993 di += dc.tgt_chans; 1994 } 1995 si += dc.num_mcu_x * dc.comps[0].sfx * 8; 1996 } 1997 return result; 1998 case 32, 12, 31, 11: 1999 const comp = &dc.comps[0]; 2000 if (comp.sfx == dc.hmax && comp.sfy == dc.vmax) { 2001 size_t si, di; 2002 if (dc.tgt_chans == 2) { 2003 foreach (j; 0 .. dc.height) { 2004 foreach (i; 0 .. dc.width) { 2005 result[di++] = comp.data[si+i]; 2006 result[di++] = 255; 2007 } 2008 si += dc.num_mcu_x * comp.sfx * 8; 2009 } 2010 } else { 2011 foreach (j; 0 .. dc.height) { 2012 result[di .. di+dc.width] = comp.data[si .. si+dc.width]; 2013 si += dc.num_mcu_x * comp.sfx * 8; 2014 di += dc.width; 2015 } 2016 } 2017 return result; 2018 } else { 2019 // need to resample (haven't tested this...) 2020 return dc.upsample_gray(result); 2021 } 2022 case 14, 13: 2023 const comp = &dc.comps[0]; 2024 size_t si, di; 2025 foreach (j; 0 .. dc.height) { 2026 foreach (i; 0 .. dc.width) { 2027 result[di .. di+3] = comp.data[si+i]; 2028 if (dc.tgt_chans == 4) 2029 result[di+3] = 255; 2030 di += dc.tgt_chans; 2031 } 2032 si += dc.num_mcu_x * comp.sfx * 8; 2033 } 2034 return result; 2035 default: assert(0); 2036 } 2037 } 2038 2039 ubyte[] upsample_gray(in ref JPEG_Decoder dc, ubyte[] result) { 2040 const size_t stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8; 2041 const double si0yratio = cast(double) dc.comps[0].y / dc.height; 2042 const double si0xratio = cast(double) dc.comps[0].x / dc.width; 2043 size_t si0, di; 2044 2045 foreach (j; 0 .. dc.height) { 2046 si0 = cast(size_t) floor(j * si0yratio) * stride0; 2047 foreach (i; 0 .. dc.width) { 2048 result[di] = dc.comps[0].data[si0 + cast(size_t) floor(i * si0xratio)]; 2049 if (dc.tgt_chans == 2) 2050 result[di+1] = 255; 2051 di += dc.tgt_chans; 2052 } 2053 } 2054 return result; 2055 } 2056 2057 ubyte[] upsample_rgb(in ref JPEG_Decoder dc, ubyte[] result) { 2058 const size_t stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8; 2059 const size_t stride1 = dc.num_mcu_x * dc.comps[1].sfx * 8; 2060 const size_t stride2 = dc.num_mcu_x * dc.comps[2].sfx * 8; 2061 2062 const double si0yratio = cast(double) dc.comps[0].y / dc.height; 2063 const double si1yratio = cast(double) dc.comps[1].y / dc.height; 2064 const double si2yratio = cast(double) dc.comps[2].y / dc.height; 2065 const double si0xratio = cast(double) dc.comps[0].x / dc.width; 2066 const double si1xratio = cast(double) dc.comps[1].x / dc.width; 2067 const double si2xratio = cast(double) dc.comps[2].x / dc.width; 2068 size_t si0, si1, si2, di; 2069 2070 foreach (j; 0 .. dc.height) { 2071 si0 = cast(size_t) floor(j * si0yratio) * stride0; 2072 si1 = cast(size_t) floor(j * si1yratio) * stride1; 2073 si2 = cast(size_t) floor(j * si2yratio) * stride2; 2074 2075 foreach (i; 0 .. dc.width) { 2076 result[di .. di+3] = ycbcr_to_rgb( 2077 dc.comps[0].data[si0 + cast(size_t) floor(i * si0xratio)], 2078 dc.comps[1].data[si1 + cast(size_t) floor(i * si1xratio)], 2079 dc.comps[2].data[si2 + cast(size_t) floor(i * si2xratio)], 2080 ); 2081 if (dc.tgt_chans == 4) 2082 result[di+3] = 255; 2083 di += dc.tgt_chans; 2084 } 2085 } 2086 return result; 2087 } 2088 2089 ubyte[3] ycbcr_to_rgb(ubyte y, ubyte cb, ubyte cr) pure { 2090 ubyte[3] rgb = void; 2091 rgb[0] = clamp(y + 1.402*(cr-128)); 2092 rgb[1] = clamp(y - 0.34414*(cb-128) - 0.71414*(cr-128)); 2093 rgb[2] = clamp(y + 1.772*(cb-128)); 2094 return rgb; 2095 } 2096 2097 ubyte clamp(float x) pure { 2098 if (x < 0) return 0; 2099 if (255 < x) return 255; 2100 return cast(ubyte) x; 2101 } 2102 2103 // ------------------------------------------------------------ 2104 // The IDCT stuff here (to the next dashed line) is copied and adapted from 2105 // stb_image which is released under public domain. Many thanks to stb_image 2106 // author, Sean Barrett. 2107 // Link: https://github.com/nothings/stb/blob/master/stb_image.h 2108 2109 pure int f2f(float x) { return cast(int) (x * 4096 + 0.5); } 2110 pure int fsh(int x) { return x << 12; } 2111 2112 // from stb_image, derived from jidctint -- DCT_ISLOW 2113 pure void STBI__IDCT_1D(ref int t0, ref int t1, ref int t2, ref int t3, 2114 ref int x0, ref int x1, ref int x2, ref int x3, 2115 int s0, int s1, int s2, int s3, int s4, int s5, int s6, int s7) 2116 { 2117 int p1,p2,p3,p4,p5; 2118 //int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; 2119 p2 = s2; 2120 p3 = s6; 2121 p1 = (p2+p3) * f2f(0.5411961f); 2122 t2 = p1 + p3 * f2f(-1.847759065f); 2123 t3 = p1 + p2 * f2f( 0.765366865f); 2124 p2 = s0; 2125 p3 = s4; 2126 t0 = fsh(p2+p3); 2127 t1 = fsh(p2-p3); 2128 x0 = t0+t3; 2129 x3 = t0-t3; 2130 x1 = t1+t2; 2131 x2 = t1-t2; 2132 t0 = s7; 2133 t1 = s5; 2134 t2 = s3; 2135 t3 = s1; 2136 p3 = t0+t2; 2137 p4 = t1+t3; 2138 p1 = t0+t3; 2139 p2 = t1+t2; 2140 p5 = (p3+p4)*f2f( 1.175875602f); 2141 t0 = t0*f2f( 0.298631336f); 2142 t1 = t1*f2f( 2.053119869f); 2143 t2 = t2*f2f( 3.072711026f); 2144 t3 = t3*f2f( 1.501321110f); 2145 p1 = p5 + p1*f2f(-0.899976223f); 2146 p2 = p5 + p2*f2f(-2.562915447f); 2147 p3 = p3*f2f(-1.961570560f); 2148 p4 = p4*f2f(-0.390180644f); 2149 t3 += p1+p4; 2150 t2 += p2+p3; 2151 t1 += p2+p4; 2152 t0 += p1+p3; 2153 } 2154 2155 // idct and level-shift 2156 pure void stbi__idct_block(ubyte* dst, long dst_stride, in ref short[64] data) { 2157 int i; 2158 int[64] val; 2159 int* v = val.ptr; 2160 const(short)* d = data.ptr; 2161 2162 // columns 2163 for (i=0; i < 8; ++i,++d, ++v) { 2164 // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing 2165 if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 2166 && d[40]==0 && d[48]==0 && d[56]==0) { 2167 // no shortcut 0 seconds 2168 // (1|2|3|4|5|6|7)==0 0 seconds 2169 // all separate -0.047 seconds 2170 // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds 2171 int dcterm = d[0] << 2; 2172 v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; 2173 } else { 2174 int t0,t1,t2,t3,x0,x1,x2,x3; 2175 STBI__IDCT_1D( 2176 t0, t1, t2, t3, 2177 x0, x1, x2, x3, 2178 d[ 0], d[ 8], d[16], d[24], 2179 d[32], d[40], d[48], d[56] 2180 ); 2181 // constants scaled things up by 1<<12; let's bring them back 2182 // down, but keep 2 extra bits of precision 2183 x0 += 512; x1 += 512; x2 += 512; x3 += 512; 2184 v[ 0] = (x0+t3) >> 10; 2185 v[56] = (x0-t3) >> 10; 2186 v[ 8] = (x1+t2) >> 10; 2187 v[48] = (x1-t2) >> 10; 2188 v[16] = (x2+t1) >> 10; 2189 v[40] = (x2-t1) >> 10; 2190 v[24] = (x3+t0) >> 10; 2191 v[32] = (x3-t0) >> 10; 2192 } 2193 } 2194 2195 ubyte* o = dst; 2196 for (i=0, v=val.ptr; i < 8; ++i,v+=8,o+=dst_stride) { 2197 // no fast case since the first 1D IDCT spread components out 2198 int t0,t1,t2,t3,x0,x1,x2,x3; 2199 STBI__IDCT_1D( 2200 t0, t1, t2, t3, 2201 x0, x1, x2, x3, 2202 v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7] 2203 ); 2204 // constants scaled things up by 1<<12, plus we had 1<<2 from first 2205 // loop, plus horizontal and vertical each scale by sqrt(8) so together 2206 // we've got an extra 1<<3, so 1<<17 total we need to remove. 2207 // so we want to round that, which means adding 0.5 * 1<<17, 2208 // aka 65536. Also, we'll end up with -128 to 127 that we want 2209 // to encode as 0-255 by adding 128, so we'll add that before the shift 2210 x0 += 65536 + (128<<17); 2211 x1 += 65536 + (128<<17); 2212 x2 += 65536 + (128<<17); 2213 x3 += 65536 + (128<<17); 2214 // tried computing the shifts into temps, or'ing the temps to see 2215 // if any were out of range, but that was slower 2216 o[0] = stbi__clamp((x0+t3) >> 17); 2217 o[7] = stbi__clamp((x0-t3) >> 17); 2218 o[1] = stbi__clamp((x1+t2) >> 17); 2219 o[6] = stbi__clamp((x1-t2) >> 17); 2220 o[2] = stbi__clamp((x2+t1) >> 17); 2221 o[5] = stbi__clamp((x2-t1) >> 17); 2222 o[3] = stbi__clamp((x3+t0) >> 17); 2223 o[4] = stbi__clamp((x3-t0) >> 17); 2224 } 2225 } 2226 2227 // clamp to 0-255 2228 pure ubyte stbi__clamp(int x) { 2229 if (cast(uint) x > 255) { 2230 if (x < 0) return 0; 2231 if (x > 255) return 255; 2232 } 2233 return cast(ubyte) x; 2234 } 2235 2236 // the above is adapted from stb_image 2237 // ------------------------------------------------------------ 2238 2239 void read_jpeg_info(Reader stream, out long w, out long h, out long chans) { 2240 ubyte[2] marker = void; 2241 stream.readExact(marker, 2); 2242 2243 // SOI 2244 if (marker[0..2] != [0xff, 0xd8]) 2245 throw new ImageIOException("not JPEG"); 2246 2247 while (true) { 2248 stream.readExact(marker, 2); 2249 2250 if (marker[0] != 0xff) 2251 throw new ImageIOException("no frame header"); 2252 while (marker[1] == 0xff) 2253 stream.readExact(marker[1..$], 1); 2254 2255 enum SKIP = 0xff; 2256 switch (marker[1]) with (Marker) { 2257 case SOF0: .. case SOF3: goto case; 2258 case SOF9: .. case SOF11: 2259 ubyte[8] tmp; 2260 stream.readExact(tmp[0..8], 8); 2261 //int len = bigEndianToNative!ushort(tmp[0..2]); 2262 w = bigEndianToNative!ushort(tmp[5..7]); 2263 h = bigEndianToNative!ushort(tmp[3..5]); 2264 chans = tmp[7]; 2265 return; 2266 case SOS, EOI: throw new ImageIOException("no frame header"); 2267 case DRI, DHT, DQT, COM: goto case SKIP; 2268 case APP0: .. case APPf: goto case SKIP; 2269 case SKIP: 2270 ubyte[2] lenbuf = void; 2271 stream.readExact(lenbuf, 2); 2272 int skiplen = bigEndianToNative!ushort(lenbuf) - 2; 2273 stream.seek(skiplen, SEEK_CUR); 2274 break; 2275 default: throw new ImageIOException("unsupported marker"); 2276 } 2277 } 2278 assert(0); 2279 } 2280 2281 // -------------------------------------------------------------------------------- 2282 // Conversions 2283 2284 enum _ColFmt : int { 2285 Unknown = 0, 2286 Y = 1, 2287 YA, 2288 RGB, 2289 RGBA, 2290 BGR, 2291 BGRA, 2292 } 2293 2294 alias LineConv = void function(in ubyte[] src, ubyte[] tgt); 2295 2296 LineConv get_converter(long src_chans, long tgt_chans) pure { 2297 long combo(long a, long b) pure nothrow { return a*16 + b; } 2298 2299 if (src_chans == tgt_chans) 2300 return ©_line; 2301 2302 switch (combo(src_chans, tgt_chans)) with (_ColFmt) { 2303 case combo(Y, YA) : return &Y_to_YA; 2304 case combo(Y, RGB) : return &Y_to_RGB; 2305 case combo(Y, RGBA) : return &Y_to_RGBA; 2306 case combo(Y, BGR) : return &Y_to_BGR; 2307 case combo(Y, BGRA) : return &Y_to_BGRA; 2308 case combo(YA, Y) : return &YA_to_Y; 2309 case combo(YA, RGB) : return &YA_to_RGB; 2310 case combo(YA, RGBA) : return &YA_to_RGBA; 2311 case combo(YA, BGR) : return &YA_to_BGR; 2312 case combo(YA, BGRA) : return &YA_to_BGRA; 2313 case combo(RGB, Y) : return &RGB_to_Y; 2314 case combo(RGB, YA) : return &RGB_to_YA; 2315 case combo(RGB, RGBA) : return &RGB_to_RGBA; 2316 case combo(RGB, BGR) : return &RGB_to_BGR; 2317 case combo(RGB, BGRA) : return &RGB_to_BGRA; 2318 case combo(RGBA, Y) : return &RGBA_to_Y; 2319 case combo(RGBA, YA) : return &RGBA_to_YA; 2320 case combo(RGBA, RGB) : return &RGBA_to_RGB; 2321 case combo(RGBA, BGR) : return &RGBA_to_BGR; 2322 case combo(RGBA, BGRA) : return &RGBA_to_BGRA; 2323 case combo(BGR, Y) : return &BGR_to_Y; 2324 case combo(BGR, YA) : return &BGR_to_YA; 2325 case combo(BGR, RGB) : return &BGR_to_RGB; 2326 case combo(BGR, RGBA) : return &BGR_to_RGBA; 2327 case combo(BGRA, Y) : return &BGRA_to_Y; 2328 case combo(BGRA, YA) : return &BGRA_to_YA; 2329 case combo(BGRA, RGB) : return &BGRA_to_RGB; 2330 case combo(BGRA, RGBA) : return &BGRA_to_RGBA; 2331 default : throw new ImageIOException("internal error"); 2332 } 2333 } 2334 2335 void copy_line(in ubyte[] src, ubyte[] tgt) pure nothrow { 2336 tgt[0..$] = src[0..$]; 2337 } 2338 2339 ubyte luminance(ubyte r, ubyte g, ubyte b) pure nothrow { 2340 return cast(ubyte) (0.21*r + 0.64*g + 0.15*b); // somewhat arbitrary weights 2341 } 2342 2343 void Y_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2344 for (size_t k, t; k < src.length; k+=1, t+=2) { 2345 tgt[t] = src[k]; 2346 tgt[t+1] = 255; 2347 } 2348 } 2349 2350 alias Y_to_BGR = Y_to_RGB; 2351 void Y_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow { 2352 for (size_t k, t; k < src.length; k+=1, t+=3) 2353 tgt[t .. t+3] = src[k]; 2354 } 2355 2356 alias Y_to_BGRA = Y_to_RGBA; 2357 void Y_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2358 for (size_t k, t; k < src.length; k+=1, t+=4) { 2359 tgt[t .. t+3] = src[k]; 2360 tgt[t+3] = 255; 2361 } 2362 } 2363 2364 void YA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow { 2365 for (size_t k, t; k < src.length; k+=2, t+=1) 2366 tgt[t] = src[k]; 2367 } 2368 2369 alias YA_to_BGR = YA_to_RGB; 2370 void YA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow { 2371 for (size_t k, t; k < src.length; k+=2, t+=3) 2372 tgt[t .. t+3] = src[k]; 2373 } 2374 2375 alias YA_to_BGRA = YA_to_RGBA; 2376 void YA_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2377 for (size_t k, t; k < src.length; k+=2, t+=4) { 2378 tgt[t .. t+3] = src[k]; 2379 tgt[t+3] = src[k+1]; 2380 } 2381 } 2382 2383 void RGB_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow { 2384 for (size_t k, t; k < src.length; k+=3, t+=1) 2385 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 2386 } 2387 2388 void RGB_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2389 for (size_t k, t; k < src.length; k+=3, t+=2) { 2390 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 2391 tgt[t+1] = 255; 2392 } 2393 } 2394 2395 void RGB_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2396 for (size_t k, t; k < src.length; k+=3, t+=4) { 2397 tgt[t .. t+3] = src[k .. k+3]; 2398 tgt[t+3] = 255; 2399 } 2400 } 2401 2402 void RGBA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow { 2403 for (size_t k, t; k < src.length; k+=4, t+=1) 2404 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 2405 } 2406 2407 void RGBA_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2408 for (size_t k, t; k < src.length; k+=4, t+=2) { 2409 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 2410 tgt[t+1] = src[k+3]; 2411 } 2412 } 2413 2414 void RGBA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow { 2415 for (size_t k, t; k < src.length; k+=4, t+=3) 2416 tgt[t .. t+3] = src[k .. k+3]; 2417 } 2418 2419 void BGR_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow { 2420 for (size_t k, t; k < src.length; k+=3, t+=1) 2421 tgt[t] = luminance(src[k+2], src[k+1], src[k+1]); 2422 } 2423 2424 void BGR_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2425 for (size_t k, t; k < src.length; k+=3, t+=2) { 2426 tgt[t] = luminance(src[k+2], src[k+1], src[k+1]); 2427 tgt[t+1] = 255; 2428 } 2429 } 2430 2431 alias RGB_to_BGR = BGR_to_RGB; 2432 void BGR_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow { 2433 for (size_t k; k < src.length; k+=3) { 2434 tgt[k ] = src[k+2]; 2435 tgt[k+1] = src[k+1]; 2436 tgt[k+2] = src[k ]; 2437 } 2438 } 2439 2440 alias RGB_to_BGRA = BGR_to_RGBA; 2441 void BGR_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2442 for (size_t k, t; k < src.length; k+=3, t+=4) { 2443 tgt[t ] = src[k+2]; 2444 tgt[t+1] = src[k+1]; 2445 tgt[t+2] = src[k ]; 2446 tgt[t+3] = 255; 2447 } 2448 } 2449 2450 void BGRA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow { 2451 for (size_t k, t; k < src.length; k+=4, t+=1) 2452 tgt[t] = luminance(src[k+2], src[k+1], src[k]); 2453 } 2454 2455 void BGRA_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2456 for (size_t k, t; k < src.length; k+=4, t+=2) { 2457 tgt[t] = luminance(src[k+2], src[k+1], src[k]); 2458 tgt[t+1] = 255; 2459 } 2460 } 2461 2462 alias RGBA_to_BGR = BGRA_to_RGB; 2463 void BGRA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow { 2464 for (size_t k, t; k < src.length; k+=4, t+=3) { 2465 tgt[t ] = src[k+2]; 2466 tgt[t+1] = src[k+1]; 2467 tgt[t+2] = src[k ]; 2468 } 2469 } 2470 2471 alias RGBA_to_BGRA = BGRA_to_RGBA; 2472 void BGRA_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow { 2473 for (size_t k, t; k < src.length; k+=4, t+=4) { 2474 tgt[t ] = src[k+2]; 2475 tgt[t+1] = src[k+1]; 2476 tgt[t+2] = src[k ]; 2477 tgt[t+3] = src[k+3]; 2478 } 2479 } 2480 2481 // -------------------------------------------------------------------------------- 2482 2483 class Reader { 2484 const void delegate(ubyte[], size_t) readExact; 2485 const void delegate(ptrdiff_t, int) seek; 2486 2487 this(in char[] filename) { 2488 this(File(filename.idup, "rb")); 2489 } 2490 2491 this(File f) { 2492 if (!f.isOpen) throw new ImageIOException("File not open"); 2493 this.f = f; 2494 this.readExact = &file_readExact; 2495 this.seek = &file_seek; 2496 this.source = null; 2497 } 2498 2499 this(in ubyte[] source) { 2500 this.source = source; 2501 this.readExact = &mem_readExact; 2502 this.seek = &mem_seek; 2503 } 2504 2505 private: 2506 2507 File f; 2508 void file_readExact(ubyte[] buffer, size_t bytes) { 2509 auto slice = this.f.rawRead(buffer[0..bytes]); 2510 if (slice.length != bytes) 2511 throw new Exception("not enough data"); 2512 } 2513 void file_seek(ptrdiff_t offset, int origin) { this.f.seek(offset, origin); } 2514 2515 const ubyte[] source; 2516 ptrdiff_t cursor; 2517 void mem_readExact(ubyte[] buffer, size_t bytes) { 2518 if (source.length - cursor < bytes) 2519 throw new Exception("not enough data"); 2520 buffer[0..bytes] = source[cursor .. cursor+bytes]; 2521 cursor += bytes; 2522 } 2523 void mem_seek(ptrdiff_t offset, int origin) { 2524 switch (origin) { 2525 case SEEK_SET: 2526 if (offset < 0 || source.length <= offset) 2527 throw new Exception("seek error"); 2528 cursor = offset; 2529 break; 2530 case SEEK_CUR: 2531 ptrdiff_t dst = cursor + offset; 2532 if (dst < 0 || source.length <= dst) 2533 throw new Exception("seek error"); 2534 cursor = dst; 2535 break; 2536 case SEEK_END: 2537 if (0 <= offset || source.length < -offset) 2538 throw new Exception("seek error"); 2539 cursor = cast(ptrdiff_t) source.length + offset; 2540 break; 2541 default: assert(0); 2542 } 2543 } 2544 } 2545 2546 class Writer { 2547 const void delegate(in ubyte[]) rawWrite; 2548 const void delegate() flush; 2549 2550 this(in char[] filename) { 2551 this(File(filename.idup, "wb")); 2552 } 2553 2554 this(File f) { 2555 if (!f.isOpen) throw new ImageIOException("File not open"); 2556 this.f = f; 2557 this.rawWrite = &file_rawWrite; 2558 this.flush = &file_flush; 2559 } 2560 2561 this() { 2562 this.rawWrite = &mem_rawWrite; 2563 this.flush = &mem_flush; 2564 } 2565 2566 @property ubyte[] result() { return buffer; } 2567 2568 private: 2569 2570 File f; 2571 void file_rawWrite(in ubyte[] block) { this.f.rawWrite(block); } 2572 void file_flush() { this.f.flush(); } 2573 2574 ubyte[] buffer; 2575 void mem_rawWrite(in ubyte[] block) { this.buffer ~= block; } 2576 void mem_flush() { } 2577 } 2578 2579 const(char)[] extract_extension_lowercase(in char[] filename) { 2580 ptrdiff_t di = filename.lastIndexOf('.'); 2581 return (0 < di && di+1 < filename.length) ? filename[di+1..$].toLower() : ""; 2582 }