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