1 /* 2 Copyright (c) 2011-2015 Timur Gafarov, Martin Cejp 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 // dimage is actually stripped out part of dlib - just to support reading PNG and JPEG 30 module dimage.png; //dlib.image.io.png 31 32 private 33 { 34 import std.stdio; 35 import std.math; 36 import std.string; 37 import std.range; 38 39 import dimage.memory; 40 import dimage.stream; 41 import dimage.compound; 42 //import dlib.filesystem.local; 43 //import dlib.math.utils; 44 import dimage.zlib; 45 import dimage.image; 46 //import dlib.image.io.io; 47 48 //import dlib.core.memory; 49 //import dlib.core.stream; 50 //import dlib.core.compound; 51 //import dlib.filesystem.local; 52 //import dlib.math.utils; 53 //import dlib.coding.zlib; 54 //import dlib.image.image; 55 //import dlib.image.io.io; 56 } 57 58 // uncomment this to see debug messages: 59 //version = PNGDebug; 60 61 static const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10]; 62 static const ubyte[4] IHDR = ['I', 'H', 'D', 'R']; 63 static const ubyte[4] IEND = ['I', 'E', 'N', 'D']; 64 static const ubyte[4] IDAT = ['I', 'D', 'A', 'T']; 65 static const ubyte[4] PLTE = ['P', 'L', 'T', 'E']; 66 static const ubyte[4] tRNS = ['t', 'R', 'N', 'S']; 67 static const ubyte[4] bKGD = ['b', 'K', 'G', 'D']; 68 static const ubyte[4] tEXt = ['t', 'E', 'X', 't']; 69 static const ubyte[4] iTXt = ['i', 'T', 'X', 't']; 70 static const ubyte[4] zTXt = ['z', 'T', 'X', 't']; 71 72 enum ColorType: ubyte 73 { 74 Greyscale = 0, // allowed bit depths: 1, 2, 4, 8 and 16 75 RGB = 2, // allowed bit depths: 8 and 16 76 Palette = 3, // allowed bit depths: 1, 2, 4 and 8 77 GreyscaleAlpha = 4, // allowed bit depths: 8 and 16 78 RGBA = 6, // allowed bit depths: 8 and 16 79 Any = 7 // one of the above 80 } 81 82 enum FilterMethod: ubyte 83 { 84 None = 0, 85 Sub = 1, 86 Up = 2, 87 Average = 3, 88 Paeth = 4 89 } 90 91 struct PNGChunk 92 { 93 uint length; 94 ubyte[4] type; 95 ubyte[] data; 96 uint crc; 97 98 void free() 99 { 100 if (data.ptr) 101 Delete(data); 102 } 103 } 104 105 struct PNGHeader 106 { 107 union 108 { 109 struct 110 { 111 uint width; 112 uint height; 113 ubyte bitDepth; 114 ubyte colorType; 115 ubyte compressionMethod; 116 ubyte filterMethod; 117 ubyte interlaceMethod; 118 }; 119 ubyte[13] bytes; 120 } 121 } 122 class PNGLoadException: ImageLoadException 123 { 124 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 125 { 126 super(msg, file, line, next); 127 } 128 } 129 130 /* 131 * Load PNG from file using local FileSystem. 132 * Causes GC allocation 133 */ 134 //SuperImage loadPNG(string filename) 135 //{ 136 // InputStream input = openForInput(filename); 137 // auto img = loadPNG(input); 138 // input.close(); 139 // return img; 140 //} 141 142 /* 143 * Save PNG to file using local FileSystem. 144 * Causes GC allocation 145 */ 146 //void savePNG(SuperImage img, string filename) 147 //{ 148 // OutputStream output = openForOutput(filename); 149 // Compound!(bool, string) res = 150 // savePNG(img, output); 151 // output.close(); 152 // 153 // if (!res[0]) 154 // throw new PNGLoadException(res[1]); 155 //} 156 157 /* 158 * Load PNG from stream using default image factory. 159 * Causes GC allocation 160 */ 161 SuperImage loadPNG(InputStream istrm) 162 { 163 Compound!(SuperImage, string) res = 164 loadPNG(istrm, defaultImageFactory); 165 if (res[0] is null) 166 throw new PNGLoadException(res[1]); 167 else 168 return res[0]; 169 } 170 171 /* 172 * Load PNG from stream using specified image factory. 173 * GC-free 174 */ 175 Compound!(SuperImage, string) loadPNG( 176 InputStream istrm, 177 SuperImageFactory imgFac) 178 { 179 SuperImage img = null; 180 181 Compound!(SuperImage, string) error(string errorMsg) 182 { 183 if (img) 184 { 185 img.free(); 186 img = null; 187 } 188 return compound(img, errorMsg); 189 } 190 191 bool readChunk(PNGChunk* chunk) 192 { 193 if (!istrm.readBE!uint(&chunk.length) 194 || !istrm.fillArray(chunk.type)) 195 { 196 return false; 197 } 198 199 version(PNGDebug) writefln("Chunk length = %s", chunk.length); 200 version(PNGDebug) writefln("Chunk type = %s", cast(char[])chunk.type); 201 202 if (chunk.length > 0) 203 { 204 chunk.data = New!(ubyte[])(chunk.length); 205 206 if (!istrm.fillArray(chunk.data)) 207 { 208 return false; 209 } 210 } 211 212 version(PNGDebug) writefln("Chunk data.length = %s", chunk.data.length); 213 214 if (!istrm.readBE!uint(&chunk.crc)) 215 { 216 return false; 217 } 218 219 // TODO: reimplement CRC check with ranges instead of concatenation 220 uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data)); 221 222 version(PNGDebug) 223 { 224 writefln("Chunk CRC = %X", chunk.crc); 225 writefln("Calculated CRC = %X", calculatedCRC); 226 writeln("-------------------"); 227 } 228 229 if (chunk.crc != calculatedCRC) 230 { 231 return false; 232 } 233 234 return true; 235 } 236 237 bool readHeader(PNGHeader* hdr, PNGChunk* chunk) 238 { 239 hdr.bytes[] = chunk.data[]; 240 hdr.width = bigEndian(hdr.width); 241 hdr.height = bigEndian(hdr.height); 242 243 version(PNGDebug) 244 { 245 writefln("width = %s", hdr.width); 246 writefln("height = %s", hdr.height); 247 writefln("bitDepth = %s", hdr.bitDepth); 248 writefln("colorType = %s", hdr.colorType); 249 writefln("compressionMethod = %s", hdr.compressionMethod); 250 writefln("filterMethod = %s", hdr.filterMethod); 251 writefln("interlaceMethod = %s", hdr.interlaceMethod); 252 writeln("----------------"); 253 } 254 255 return true; 256 } 257 258 ubyte[8] signatureBuffer; 259 260 if (!istrm.fillArray(signatureBuffer)) 261 { 262 return error("loadPNG error: signature check failed"); 263 } 264 265 version(PNGDebug) 266 { 267 writeln("----------------"); 268 writeln("PNG Signature: ", signatureBuffer); 269 writeln("----------------"); 270 } 271 272 PNGHeader hdr; 273 274 ZlibDecoder zlibDecoder; 275 276 ubyte[] palette; 277 ubyte[] transparency; 278 uint paletteSize = 0; 279 280 bool endChunk = false; 281 while (!endChunk && istrm.readable) 282 { 283 PNGChunk chunk; 284 bool res = readChunk(&chunk); 285 if (!res) 286 { 287 chunk.free(); 288 return error("loadPNG error: failed to read chunk"); 289 } 290 else 291 { 292 if (chunk.type == IEND) 293 { 294 endChunk = true; 295 chunk.free(); 296 } 297 else if (chunk.type == IHDR) 298 { 299 if (chunk.data.length < hdr.bytes.length) 300 return error("loadPNG error: illegal header chunk"); 301 302 readHeader(&hdr, &chunk); 303 chunk.free(); 304 305 bool supportedIndexed = 306 (hdr.colorType == ColorType.Palette) && 307 (hdr.bitDepth == 1 || 308 hdr.bitDepth == 2 || 309 hdr.bitDepth == 4 || 310 hdr.bitDepth == 8); 311 312 if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed) 313 return error("loadPNG error: unsupported bit depth"); 314 315 if (hdr.compressionMethod != 0) 316 return error("loadPNG error: unsupported compression method"); 317 318 if (hdr.filterMethod != 0) 319 return error("loadPNG error: unsupported filter method"); 320 321 if (hdr.interlaceMethod != 0) 322 return error("loadPNG error: interlacing is not supported"); 323 324 uint bufferLength = ((hdr.width * hdr.bitDepth + 7) / 8) * hdr.height + hdr.height; 325 ubyte[] buffer = New!(ubyte[])(bufferLength); 326 327 zlibDecoder = ZlibDecoder(buffer); 328 329 version(PNGDebug) 330 { 331 writefln("buffer.length = %s", bufferLength); 332 writeln("----------------"); 333 } 334 } 335 else if (chunk.type == IDAT) 336 { 337 zlibDecoder.decode(chunk.data); 338 chunk.free(); 339 } 340 else if (chunk.type == PLTE) 341 { 342 palette = chunk.data; 343 } 344 else if (chunk.type == tRNS) 345 { 346 transparency = chunk.data; 347 version(PNGDebug) 348 { 349 writeln("----------------"); 350 writefln("transparency.length = %s", transparency.length); 351 writeln("----------------"); 352 } 353 } 354 else 355 { 356 chunk.free(); 357 } 358 } 359 } 360 361 // finalize decoder 362 version(PNGDebug) writefln("zlibDecoder.hasEnded = %s", zlibDecoder.hasEnded); 363 if (!zlibDecoder.hasEnded) 364 return error("loadPNG error: unexpected end of zlib stream"); 365 366 ubyte[] buffer = zlibDecoder.buffer; 367 version(PNGDebug) writefln("buffer.length = %s", buffer.length); 368 369 bool transparencyPalette; 370 371 // create image 372 if (hdr.colorType == ColorType.Greyscale) 373 { 374 if (hdr.bitDepth == 8) 375 img = imgFac.createImage(hdr.width, hdr.height, 1, 8); 376 else if (hdr.bitDepth == 16) 377 img = imgFac.createImage(hdr.width, hdr.height, 1, 16); 378 } 379 else if (hdr.colorType == ColorType.GreyscaleAlpha) 380 { 381 if (hdr.bitDepth == 8) 382 img = imgFac.createImage(hdr.width, hdr.height, 2, 8); 383 else if (hdr.bitDepth == 16) 384 img = imgFac.createImage(hdr.width, hdr.height, 2, 16); 385 } 386 else if (hdr.colorType == ColorType.RGB) 387 { 388 if (hdr.bitDepth == 8) 389 img = imgFac.createImage(hdr.width, hdr.height, 3, 8); 390 else if (hdr.bitDepth == 16) 391 img = imgFac.createImage(hdr.width, hdr.height, 3, 16); 392 } 393 else if (hdr.colorType == ColorType.RGBA) 394 { 395 if (hdr.bitDepth == 8) 396 img = imgFac.createImage(hdr.width, hdr.height, 4, 8); 397 else if (hdr.bitDepth == 16) 398 img = imgFac.createImage(hdr.width, hdr.height, 4, 16); 399 } 400 else if (hdr.colorType == ColorType.Palette) 401 { 402 if (transparency.length > 0) { 403 img = imgFac.createImage(hdr.width, hdr.height, 4, 8); 404 transparencyPalette = true; 405 } else 406 img = imgFac.createImage(hdr.width, hdr.height, 3, 8); 407 } 408 else 409 return error("loadPNG error: unsupported color type"); 410 411 version(PNGDebug) 412 { 413 writefln("img.width = %s", img.width); 414 writefln("img.height = %s", img.height); 415 writefln("img.bitDepth = %s", img.bitDepth); 416 writefln("img.channels = %s", img.channels); 417 writeln("----------------"); 418 } 419 420 bool indexed = (hdr.colorType == ColorType.Palette); 421 422 // don't close the stream, just release our reference 423 istrm = null; 424 425 // apply filtering to the image data 426 ubyte[] buffer2; 427 string errorMsg; 428 if (!filter(&hdr, img.channels, indexed, buffer, buffer2, errorMsg)) 429 { 430 return error(errorMsg); 431 } 432 Delete(buffer); 433 buffer = buffer2; 434 435 // if a palette is used, substitute target colors 436 if (indexed) 437 { 438 if (palette.length == 0) 439 return error("loadPNG error: palette chunk not found"); 440 441 ubyte[] pdata = New!(ubyte[])(img.width * img.height * img.channels); 442 if (hdr.bitDepth == 8) 443 { 444 for (int i = 0; i < buffer.length; ++i) 445 { 446 ubyte b = buffer[i]; 447 pdata[i * img.channels + 0] = palette[b * 3 + 0]; 448 pdata[i * img.channels + 1] = palette[b * 3 + 1]; 449 pdata[i * img.channels + 2] = palette[b * 3 + 2]; 450 if (transparency.length > 0) 451 pdata[i * img.channels + 3] = 452 b < transparency.length ? transparency[b] : 0; 453 } 454 } 455 else // bit depths 1, 2, 4 456 { 457 int srcindex = 0; 458 int srcshift = 8 - hdr.bitDepth; 459 ubyte mask = cast(ubyte)((1 << hdr.bitDepth) - 1); 460 int sz = img.width * img.height; 461 for (int dstindex = 0; dstindex < sz; dstindex++) 462 { 463 auto b = ((buffer[srcindex] >> srcshift) & mask); 464 //assert(b * 3 + 2 < palette.length); 465 pdata[dstindex * img.channels + 0] = palette[b * 3 + 0]; 466 pdata[dstindex * img.channels + 1] = palette[b * 3 + 1]; 467 pdata[dstindex * img.channels + 2] = palette[b * 3 + 2]; 468 469 if (transparency.length > 0) 470 pdata[dstindex * img.channels + 3] = 471 b < transparency.length ? transparency[b] : 0; 472 473 if (srcshift <= 0) 474 { 475 srcshift = 8 - hdr.bitDepth; 476 srcindex++; 477 } 478 else 479 { 480 srcshift -= hdr.bitDepth; 481 } 482 } 483 } 484 485 Delete(buffer); 486 buffer = pdata; 487 488 Delete(palette); 489 490 if (transparency.length > 0) 491 Delete(transparency); 492 } 493 494 //if (img.data.length != buffer.length) 495 // return error("loadPNG error: uncompressed data length mismatch"); 496 // 497 //foreach(i, v; buffer) 498 // img.data[i] = v; 499 500 int bufindex = 0; 501 if (hdr.colorType == ColorType.Greyscale) 502 { 503 if (hdr.bitDepth == 8) { 504 //img = imgFac.createImage(hdr.width, hdr.height, 1, 8); 505 for (int i = 0; i < img.length; i++) { 506 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex])<<8) | ((cast(uint)buffer[bufindex])<<0) | 0xFF000000; 507 bufindex += 1; 508 } 509 } else if (hdr.bitDepth == 16) { 510 //img = imgFac.createImage(hdr.width, hdr.height, 1, 16); 511 assert(false); 512 } 513 } 514 else if (hdr.colorType == ColorType.GreyscaleAlpha) 515 { 516 if (hdr.bitDepth == 8) { 517 //img = imgFac.createImage(hdr.width, hdr.height, 2, 8); 518 for (int i = 0; i < img.length; i++) { 519 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex])<<8) | ((cast(uint)buffer[bufindex])<<0) | ((cast(uint)buffer[bufindex + 1])<<24); 520 bufindex += 2; 521 } 522 } else if (hdr.bitDepth == 16) { 523 //img = imgFac.createImage(hdr.width, hdr.height, 2, 16); 524 assert(false); 525 } 526 } 527 else if (hdr.colorType == ColorType.RGB) 528 { 529 if (hdr.bitDepth == 8) { 530 //img = imgFac.createImage(hdr.width, hdr.height, 3, 8); 531 for (int i = 0; i < img.length; i++) { 532 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | 0xFF000000; 533 bufindex += 3; 534 } 535 } else if (hdr.bitDepth == 16) { 536 //img = imgFac.createImage(hdr.width, hdr.height, 3, 16); 537 assert(false); 538 } 539 } 540 else if (hdr.colorType == ColorType.RGBA) 541 { 542 if (hdr.bitDepth == 8) { 543 //img = imgFac.createImage(hdr.width, hdr.height, 4, 8); 544 for (int i = 0; i < img.length; i++) { 545 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | ((cast(uint)buffer[bufindex + 3])<<24); 546 bufindex += 4; 547 } 548 } else if (hdr.bitDepth == 16) { 549 //img = imgFac.createImage(hdr.width, hdr.height, 4, 16); 550 assert(false); 551 } 552 } 553 else if (hdr.colorType == ColorType.Palette) 554 { 555 if (transparencyPalette) { 556 for (int i = 0; i < img.length; i++) { 557 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | ((cast(uint)buffer[bufindex + 3])<<24); 558 bufindex += 4; 559 } 560 //img = imgFac.createImage(hdr.width, hdr.height, 4, 8); 561 } else { 562 //img = imgFac.createImage(hdr.width, hdr.height, 3, 8); 563 for (int i = 0; i < img.length; i++) { 564 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | 0xFF000000; 565 bufindex += 3; 566 } 567 } 568 } 569 570 Delete(buffer); 571 572 return compound(img, ""); 573 } 574 575 version (ENABLE_SAVE_PNG) { 576 /* 577 * Save PNG to stream. 578 * GC-free 579 */ 580 Compound!(bool, string) savePNG(SuperImage img, OutputStream output) 581 in 582 { 583 assert (img.data.length); 584 } 585 do 586 { 587 Compound!(bool, string) error(string errorMsg) 588 { 589 return compound(false, errorMsg); 590 } 591 592 if (img.bitDepth != 8) 593 return error("savePNG error: only 8-bit images are supported by encoder"); 594 595 bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData) 596 { 597 PNGChunk hdrChunk; 598 hdrChunk.length = cast(uint)chunkData.length; 599 hdrChunk.type = chunkType; 600 hdrChunk.data = chunkData; 601 hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data)); 602 603 if (!output.writeBE!uint(hdrChunk.length) 604 || !output.writeArray(hdrChunk.type)) 605 return false; 606 607 if (chunkData.length) 608 if (!output.writeArray(hdrChunk.data)) 609 return false; 610 611 if (!output.writeBE!uint(hdrChunk.crc)) 612 return false; 613 614 return true; 615 } 616 617 bool writeHeader() 618 { 619 PNGHeader hdr; 620 hdr.width = networkByteOrder(img.width); 621 hdr.height = networkByteOrder(img.height); 622 hdr.bitDepth = 8; 623 if (img.channels == 4) 624 hdr.colorType = ColorType.RGBA; 625 else if (img.channels == 3) 626 hdr.colorType = ColorType.RGB; 627 else if (img.channels == 2) 628 hdr.colorType = ColorType.GreyscaleAlpha; 629 else if (img.channels == 1) 630 hdr.colorType = ColorType.Greyscale; 631 hdr.compressionMethod = 0; 632 hdr.filterMethod = 0; 633 hdr.interlaceMethod = 0; 634 635 return writeChunk(IHDR, hdr.bytes); 636 } 637 638 output.writeArray(PNGSignature); 639 if (!writeHeader()) 640 return error("savePNG error: write failed (disk full?)"); 641 642 //TODO: filtering 643 ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height); 644 foreach(y; 0..img.height) 645 { 646 auto rowStart = y * (img.width * img.channels + 1); 647 raw[rowStart] = 0; // No filter 648 649 foreach(x; 0..img.width) 650 { 651 auto dataIndex = (y * img.width + x) * img.channels; 652 auto rawIndex = rowStart + 1 + x * img.channels; 653 654 foreach(ch; 0..img.channels) 655 raw[rawIndex + ch] = img.data[dataIndex + ch]; 656 } 657 } 658 659 ubyte[] buffer = New!(ubyte[])(64 * 1024); 660 ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw); 661 while (!zlibEncoder.ended) 662 { 663 auto len = zlibEncoder.encode(); 664 if (len > 0) 665 writeChunk(IDAT, zlibEncoder.buffer[0..len]); 666 } 667 668 writeChunk(IEND, []); 669 670 Delete(buffer); 671 Delete(raw); 672 673 return compound(true, ""); 674 } 675 } 676 677 /* 678 * performs the paeth PNG filter from pixels values: 679 * a = back 680 * b = up 681 * c = up and back 682 */ 683 pure ubyte paeth(ubyte a, ubyte b, ubyte c) 684 { 685 int p = a + b - c; 686 int pa = abs(p - a); 687 int pb = abs(p - b); 688 int pc = abs(p - c); 689 if (pa <= pb && pa <= pc) return a; 690 else if (pb <= pc) return b; 691 else return c; 692 } 693 694 bool filter(PNGHeader* hdr, 695 uint channels, 696 bool indexed, 697 ubyte[] ibuffer, 698 out ubyte[] obuffer, 699 out string errorMsg) 700 { 701 uint dataSize = cast(uint)ibuffer.length; 702 uint scanlineSize; 703 704 uint calculatedSize; 705 if (indexed) 706 { 707 calculatedSize = hdr.width * hdr.height * hdr.bitDepth / 8 + hdr.height; 708 scanlineSize = hdr.width * hdr.bitDepth / 8 + 1; 709 } 710 else 711 { 712 calculatedSize = hdr.width * hdr.height * channels + hdr.height; 713 scanlineSize = hdr.width * channels + 1; 714 } 715 716 version(PNGDebug) 717 { 718 writefln("[filter] dataSize = %s", dataSize); 719 writefln("[filter] calculatedSize = %s", calculatedSize); 720 } 721 722 if (dataSize != calculatedSize) 723 { 724 errorMsg = "loadPNG error: image size and data mismatch"; 725 return false; 726 } 727 728 obuffer = New!(ubyte[])(calculatedSize - hdr.height); 729 730 ubyte pback, pup, pupback, cbyte; 731 732 for (int i = 0; i < hdr.height; ++i) 733 { 734 pback = 0; 735 736 // get the first byte of a scanline 737 ubyte scanFilter = ibuffer[i * scanlineSize]; 738 739 if (indexed) 740 { 741 // TODO: support filtering for indexed images 742 if (scanFilter != FilterMethod.None) 743 { 744 errorMsg = "loadPNG error: filtering is not supported for indexed images"; 745 return false; 746 } 747 748 for (int j = 1; j < scanlineSize; ++j) 749 { 750 ubyte b = ibuffer[(i * scanlineSize) + j]; 751 obuffer[(i * (scanlineSize-1) + j - 1)] = b; 752 } 753 continue; 754 } 755 756 for (int j = 0; j < hdr.width; ++j) 757 { 758 for (int k = 0; k < channels; ++k) 759 { 760 if (i == 0) pup = 0; 761 else pup = obuffer[((i-1) * hdr.width + j) * channels + k]; // (hdr.height-(i-1)-1) 762 if (j == 0) pback = 0; 763 else pback = obuffer[(i * hdr.width + j-1) * channels + k]; 764 if (i == 0 || j == 0) pupback = 0; 765 else pupback = obuffer[((i-1) * hdr.width + j - 1) * channels + k]; 766 767 // get the current byte from ibuffer 768 cbyte = ibuffer[i * (hdr.width * channels + 1) + j * channels + k + 1]; 769 770 // filter, then set the current byte in data 771 switch (scanFilter) 772 { 773 case FilterMethod.None: 774 obuffer[(i * hdr.width + j) * channels + k] = cbyte; 775 break; 776 case FilterMethod.Sub: 777 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + pback); 778 break; 779 case FilterMethod.Up: 780 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + pup); 781 break; 782 case FilterMethod.Average: 783 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2); 784 break; 785 case FilterMethod.Paeth: 786 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback)); 787 break; 788 default: 789 errorMsg = format("loadPNG error: unknown scanline filter (%s)", scanFilter); 790 return false; 791 } 792 } 793 } 794 } 795 796 return true; 797 } 798 799 uint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R) 800 { 801 uint[256] generateTable() 802 { 803 uint[256] table; 804 uint crc; 805 for (int i = 0; i < 256; i++) 806 { 807 crc = i; 808 for (int j = 0; j < 8; j++) 809 crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1; 810 table[i] = crc; 811 } 812 return table; 813 } 814 815 static const uint[256] table = generateTable(); 816 817 uint crc; 818 819 crc = inCrc ^ 0xFFFFFFFF; 820 foreach(v; range) 821 crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF]; 822 823 return (crc ^ 0xFFFFFFFF); 824 } 825 826 static if (false) { 827 unittest 828 { 829 import std.base64; 830 831 InputStream png() { 832 string minimal = 833 "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII="; 834 835 ubyte[] bytes = Base64.decode(minimal); 836 return new ArrayStream(bytes, bytes.length); 837 } 838 839 SuperImage img = loadPNG(png()); 840 841 assert(img.width == 1); 842 assert(img.height == 1); 843 assert(img.channels == 3); 844 assert(img.pixelSize == 3); 845 assert(img.data == [0xff, 0x00, 0x00]); 846 847 createDir("tests", false); 848 savePNG(img, "tests/minimal.png"); 849 loadPNG("tests/minimal.png"); 850 } 851 }