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