1 /* 2 Copyright (c) 2011-2021 Timur Gafarov, Martin Cejp, Vadim Lopatin 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 /** 30 * Decode and encode PNG/APNG images 31 * 32 * Copyright: Timur Gafarov, Martin Cejp, Vadim Lopatin 2011-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov, Martin Cejp, Vadim Lopatin 35 */ 36 module dlib.image.io.png; 37 38 import std.stdio; 39 import std.math; 40 import std..string; 41 import std.range; 42 import dlib.core.memory; 43 import dlib.core.stream; 44 import dlib.core.compound; 45 import dlib.filesystem.local; 46 import dlib.math.utils; 47 import dlib.math.interpolation; 48 import dlib.coding.zlib; 49 import dlib.image.color; 50 import dlib.image.image; 51 import dlib.image.animation; 52 import dlib.image.io; 53 54 // uncomment this to see debug messages: 55 //version = PNGDebug; 56 57 static const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10]; 58 59 // Standard chunks 60 static const ubyte[4] IHDR = ['I', 'H', 'D', 'R']; // Image header 61 static const ubyte[4] IEND = ['I', 'E', 'N', 'D']; // Image end 62 static const ubyte[4] IDAT = ['I', 'D', 'A', 'T']; // Image data 63 static const ubyte[4] PLTE = ['P', 'L', 'T', 'E']; // Palette 64 static const ubyte[4] tRNS = ['t', 'R', 'N', 'S']; // Transparency 65 static const ubyte[4] bKGD = ['b', 'K', 'G', 'D']; // Background color 66 static const ubyte[4] tEXt = ['t', 'E', 'X', 't']; // Textual data (uncompressed) 67 static const ubyte[4] zTXt = ['z', 'T', 'X', 't']; // Textual data (zlib compressed) 68 static const ubyte[4] iTXt = ['i', 'T', 'X', 't']; // International text 69 70 // Extension chunks 71 static const ubyte[4] oFFs = ['o', 'F', 'F', 's']; // Image offset 72 static const ubyte[4] pCAL = ['p', 'C', 'A', 'L']; // Calibration of pixel values 73 static const ubyte[4] sCAL = ['s', 'C', 'A', 'L']; // Physical scale of image subject 74 static const ubyte[4] gIFg = ['g', 'I', 'F', 'g']; // GIF graphic control 75 static const ubyte[4] gIFx = ['g', 'I', 'F', 'x']; // GIF application 76 static const ubyte[4] gIFt = ['g', 'I', 'F', 't']; // GIF plain text (deprecated) 77 static const ubyte[4] fRAc = ['f', 'R', 'A', 'c']; // Fractal image parameters 78 static const ubyte[4] sTER = ['s', 'T', 'E', 'R']; // Indicator of stereo image 79 static const ubyte[4] dSIG = ['d', 'S', 'I', 'G']; // Digital signature 80 81 // ImageMagick chunks 82 static const ubyte[4] vpAg = ['v', 'p', 'A', 'g']; // VirtualPage Tags 83 84 // APNG chunks 85 static const ubyte[4] acTL = ['a', 'c', 'T', 'L']; // Animation control 86 static const ubyte[4] fcTL = ['f', 'c', 'T', 'L']; // Frame control 87 static const ubyte[4] fdAT = ['f', 'd', 'A', 'T']; // Frame data 88 89 enum ColorType: ubyte 90 { 91 Greyscale = 0, // allowed bit depths: 1, 2, 4, 8 and 16 92 RGB = 2, // allowed bit depths: 8 and 16 93 Palette = 3, // allowed bit depths: 1, 2, 4 and 8 94 GreyscaleAlpha = 4, // allowed bit depths: 8 and 16 95 RGBA = 6, // allowed bit depths: 8 and 16 96 Any = 7 // one of the above 97 } 98 99 enum FilterMethod: ubyte 100 { 101 None = 0, 102 Sub = 1, 103 Up = 2, 104 Average = 3, 105 Paeth = 4 106 } 107 108 struct PNGChunk 109 { 110 uint length; 111 ubyte[4] type; 112 ubyte[] data; 113 uint crc; 114 115 void free() 116 { 117 if (data.ptr) 118 Delete(data); 119 } 120 } 121 122 struct PNGHeader 123 { 124 union 125 { 126 struct 127 { 128 uint width; 129 uint height; 130 ubyte bitDepth; 131 ubyte colorType; 132 ubyte compressionMethod; 133 ubyte filterMethod; 134 ubyte interlaceMethod; 135 }; 136 ubyte[13] bytes; 137 } 138 } 139 140 struct AnimationControlChunk 141 { 142 union 143 { 144 struct 145 { 146 uint numFrames; 147 uint numPlays; 148 }; 149 ubyte[8] bytes; 150 } 151 152 void readFromBuffer(ubyte[] data) 153 { 154 *(&numFrames) = *(cast(uint*)data.ptr); 155 numFrames = bigEndian(numFrames); 156 *(&numPlays) = *(cast(uint*)(data.ptr+4)); 157 numPlays = bigEndian(numPlays); 158 } 159 } 160 161 struct OffsetChunk 162 { 163 int posX; 164 int posY; 165 ubyte unitSpecifier; 166 167 void readFromBuffer(ubyte[] data) 168 { 169 *(&posX) = *(cast(int*)data.ptr); 170 posX = bigEndian(posX); 171 *(&posY) = *(cast(int*)(data.ptr+4)); 172 posY = bigEndian(posY); 173 *(&unitSpecifier) = *(data.ptr+8); 174 } 175 } 176 177 enum DisposeOp: ubyte 178 { 179 None = 0, 180 Background = 1, 181 Previous = 2 182 } 183 184 enum BlendOp: ubyte 185 { 186 Source = 0, 187 Over = 1 188 } 189 190 struct FrameControlChunk 191 { 192 union 193 { 194 struct 195 { 196 uint sequenceNumber; 197 uint width; 198 uint height; 199 uint x; 200 uint y; 201 ushort delayNumerator; 202 ushort delayDenominator; 203 ubyte disposeOp; 204 ubyte blendOp; 205 }; 206 ubyte[26] bytes; 207 } 208 209 void readFromBuffer(ubyte[] data) 210 { 211 *(&sequenceNumber) = *(cast(uint*)data.ptr); 212 sequenceNumber = bigEndian(sequenceNumber); 213 *(&width) = *(cast(uint*)(data.ptr+4)); 214 width = bigEndian(width); 215 *(&height) = *(cast(uint*)(data.ptr+8)); 216 height = bigEndian(height); 217 *(&x) = *(cast(uint*)(data.ptr+12)); 218 x = bigEndian(x); 219 *(&y) = *(cast(uint*)(data.ptr+16)); 220 y = bigEndian(y); 221 *(&delayNumerator) = *(cast(ushort*)(data.ptr+20)); 222 delayNumerator = bigEndian(delayNumerator); 223 *(&delayDenominator) = *(cast(ushort*)(data.ptr+22)); 224 delayDenominator = bigEndian(delayDenominator); 225 disposeOp = data[24]; 226 blendOp = data[25]; 227 } 228 } 229 230 struct PNGImage 231 { 232 // Common PNG data 233 PNGHeader hdr; 234 uint numChannels; 235 uint bitDepth; 236 uint bytesPerChannel; 237 bool isAnimated = false; 238 239 // Data for indexed PNG 240 ubyte[] palette; 241 ubyte[] transparency; 242 uint paletteSize = 0; 243 244 // APNG data 245 uint numFrames = 1; 246 uint numLoops = 0; 247 bool decodingFirstFrame; 248 FrameControlChunk frame; 249 250 // Offset 251 OffsetChunk offset; 252 253 ZlibDecoder decoder; 254 255 ubyte[] frameBuffer; 256 uint frameSize; 257 258 ubyte[] filteredBuffer; 259 uint filteredBufferSize; 260 261 void initDecoder() 262 { 263 ubyte[] buffer; 264 265 if (decoder.buffer.length) 266 { 267 //Delete(decoder.buffer); 268 buffer = decoder.buffer; 269 } 270 else 271 { 272 uint bufferLength = hdr.width * hdr.height * numChannels * bytesPerChannel + hdr.height; 273 buffer = New!(ubyte[])(bufferLength); 274 } 275 276 decoder = ZlibDecoder(buffer); 277 } 278 279 void initFrameBuffer() 280 { 281 if (frameBuffer.length) 282 Delete(frameBuffer); 283 284 if (filteredBuffer.length) 285 Delete(filteredBuffer); 286 287 frameBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel); 288 filteredBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel); 289 } 290 291 void free() 292 { 293 if (decoder.buffer.length) 294 Delete(decoder.buffer); 295 296 if (frameBuffer.length) 297 Delete(frameBuffer); 298 299 if (filteredBuffer.length) 300 Delete(filteredBuffer); 301 302 if (palette.length) 303 Delete(palette); 304 305 if (transparency.length) 306 Delete(transparency); 307 } 308 } 309 310 class PNGLoadException: ImageLoadException 311 { 312 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 313 { 314 super(msg, file, line, next); 315 } 316 } 317 318 /** 319 * Load PNG from file using local FileSystem. 320 * Causes GC allocation 321 */ 322 SuperImage loadPNG(string filename) 323 { 324 InputStream input = openForInput(filename); 325 auto img = loadPNG(input); 326 input.close(); 327 return img; 328 } 329 330 /** 331 * Load animated PNG (APNG) from file using local FileSystem. 332 * Causes GC allocation 333 */ 334 SuperAnimatedImage loadAPNG(string filename) 335 { 336 InputStream input = openForInput(filename); 337 auto img = loadAPNG(input); 338 input.close(); 339 return img; 340 } 341 342 /** 343 * Save PNG to file using local FileSystem. 344 * Causes GC allocation 345 */ 346 void savePNG(SuperImage img, string filename) 347 { 348 OutputStream output = openForOutput(filename); 349 Compound!(bool, string) res = savePNG(img, output); 350 output.close(); 351 352 if (!res[0]) 353 throw new PNGLoadException(res[1]); 354 } 355 356 /** 357 * Save APNG to file using local FileSystem. 358 * Causes GC allocation 359 */ 360 void saveAPNG(SuperAnimatedImage img, string filename) 361 { 362 OutputStream output = openForOutput(filename); 363 Compound!(bool, string) res = saveAPNG(img, output); 364 output.close(); 365 366 if (!res[0]) 367 throw new PNGLoadException(res[1]); 368 } 369 370 /** 371 * Load PNG from stream using default image factory. 372 * Causes GC allocation 373 */ 374 SuperImage loadPNG(InputStream istrm) 375 { 376 Compound!(SuperImage, string) res = 377 loadPNG(istrm, defaultImageFactory); 378 if (res[0] is null) 379 throw new PNGLoadException(res[1]); 380 else 381 return res[0]; 382 } 383 384 /// 385 unittest 386 { 387 import std.base64; 388 389 InputStream png() { 390 string minimal = 391 "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII="; 392 393 ubyte[] bytes = Base64.decode(minimal); 394 return new ArrayStream(bytes, bytes.length); 395 } 396 397 SuperImage img = loadPNG(png()); 398 399 assert(img.width == 1); 400 assert(img.height == 1); 401 assert(img.channels == 3); 402 assert(img.pixelSize == 3); 403 assert(img.data == [0xff, 0x00, 0x00]); 404 405 createDir("tests", false); 406 savePNG(img, "tests/minimal.png"); 407 loadPNG("tests/minimal.png"); 408 } 409 410 /** 411 * Load animated PNG (APNG) from stream using default animated image factory. 412 * Causes GC allocation 413 */ 414 SuperAnimatedImage loadAPNG(InputStream istrm) 415 { 416 Compound!(SuperImage, string) res = 417 loadPNG(istrm, animatedImageFactory); 418 if (res[0] is null) 419 throw new PNGLoadException(res[1]); 420 else 421 return cast(SuperAnimatedImage)res[0]; 422 } 423 424 /** 425 * Load PNG from stream using specified image factory. 426 * GC-free 427 */ 428 Compound!(SuperImage, string) loadPNG( 429 InputStream istrm, 430 SuperImageFactory imgFac) 431 { 432 PNGImage png; 433 434 SuperImage img = null; 435 SuperAnimatedImage animImg = null; 436 SuperImage tmpImg = null; 437 438 void finalize() 439 { 440 png.free(); 441 442 if (tmpImg) 443 { 444 tmpImg.free(); 445 tmpImg = null; 446 } 447 448 // don't close the stream, just release our reference 449 istrm = null; 450 } 451 452 Compound!(SuperImage, string) error(string errorMsg) 453 { 454 finalize(); 455 if (img) 456 { 457 img.free(); 458 img = null; 459 } 460 return compound(img, errorMsg); 461 } 462 463 ubyte[8] signatureBuffer; 464 465 if (!istrm.fillArray(signatureBuffer)) 466 { 467 return error("loadPNG error: signature check failed"); 468 } 469 470 version(PNGDebug) 471 { 472 writeln("----------------"); 473 writeln("PNG Signature: ", signatureBuffer); 474 writeln("----------------"); 475 } 476 477 bool endChunk = false; 478 while (!endChunk && istrm.readable) 479 { 480 PNGChunk chunk; 481 auto res = readChunk(&png, istrm, &chunk); 482 483 if (!res[0]) 484 { 485 chunk.free(); 486 return error(res[1]); 487 } 488 else 489 { 490 if (chunk.type == IEND) 491 { 492 endChunk = true; 493 chunk.free(); 494 } 495 else if (chunk.type == IHDR) 496 { 497 res = readIHDR(&png, &chunk); 498 chunk.free(); 499 if (!res[0]) 500 { 501 return error(res[1]); 502 } 503 504 png.decodingFirstFrame = true; 505 png.frame.width = png.hdr.width; 506 png.frame.height = png.hdr.height; 507 png.frame.x = 0; 508 png.frame.y = 0; 509 510 png.initFrameBuffer(); 511 png.initDecoder(); 512 } 513 else if (chunk.type == IDAT) 514 { 515 png.decoder.decode(chunk.data); 516 chunk.free(); 517 } 518 else if (chunk.type == PLTE) 519 { 520 png.palette = chunk.data; 521 } 522 else if (chunk.type == tRNS) 523 { 524 png.transparency = chunk.data; 525 526 if (png.hdr.colorType == ColorType.Palette) 527 { 528 if (png.transparency.length > 0) 529 png.numChannels = 4; 530 else 531 png.numChannels = 3; 532 } 533 534 version(PNGDebug) 535 { 536 writefln("transparency.length = %s", png.transparency.length); 537 } 538 539 png.initFrameBuffer(); 540 png.initDecoder(); 541 } 542 else if (chunk.type == oFFs) 543 { 544 png.offset.readFromBuffer(chunk.data); 545 chunk.free(); 546 547 version(PNGDebug) 548 { 549 writefln("posX = %s", png.offset.posX); 550 writefln("posY = %s", png.offset.posY); 551 writefln("unitSpecifier = %s", png.offset.unitSpecifier); 552 } 553 } 554 else if (chunk.type == acTL) 555 { 556 AnimationControlChunk animControl; 557 animControl.readFromBuffer(chunk.data); 558 png.numFrames = animControl.numFrames; 559 png.numLoops = animControl.numPlays; 560 png.isAnimated = true; 561 562 version(PNGDebug) 563 { 564 writefln("numFrames = %s", png.numFrames); 565 writefln("numLoops = %s", png.numLoops); 566 } 567 568 chunk.free(); 569 } 570 else if (chunk.type == fcTL) 571 { 572 png.frame.readFromBuffer(chunk.data); 573 574 version(PNGDebug) 575 { 576 writefln("sequenceNumber = %s", png.frame.sequenceNumber); 577 writefln("frameWidth = %s", png.frame.width); 578 writefln("frameHeight = %s", png.frame.height); 579 writefln("frameX = %s", png.frame.x); 580 writefln("frameY = %s", png.frame.y); 581 writefln("delayNumerator = %s", png.frame.delayNumerator); 582 writefln("delayDenominator = %s", png.frame.delayDenominator); 583 writefln("disposeOp = %s", cast(DisposeOp)png.frame.disposeOp); 584 writefln("blendOp = %s", cast(BlendOp)png.frame.blendOp); 585 } 586 587 png.initDecoder(); 588 589 chunk.free(); 590 } 591 else if (chunk.type == fdAT) 592 { 593 uint dataSequenceNumber; 594 *(&dataSequenceNumber) = *(cast(uint*)chunk.data.ptr); 595 dataSequenceNumber = bigEndian(dataSequenceNumber); 596 version(PNGDebug) 597 { 598 writefln("sequenceNumber = %s", dataSequenceNumber); 599 } 600 601 png.decoder.decode(chunk.data[4..$]); 602 chunk.free(); 603 } 604 else 605 { 606 chunk.free(); 607 } 608 609 version(PNGDebug) 610 { 611 writeln("----------------"); 612 } 613 } 614 615 if (png.decoder.hasEnded) 616 { 617 if (img is null) 618 { 619 tmpImg = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth); 620 img = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth, png.numFrames); 621 622 if (png.isAnimated) 623 animImg = cast(SuperAnimatedImage)img; 624 } 625 626 res = fillFrame(&png); 627 if (res[0]) 628 { 629 if (png.decodingFirstFrame) 630 { 631 png.decodingFirstFrame = false; 632 633 if (tmpImg.data.length != png.frameBuffer.length) 634 { 635 return error("loadPNG error: uncompressed data length mismatch"); 636 } 637 638 tmpImg.data[] = png.frameBuffer[0..png.frameSize]; 639 img.data[] = tmpImg.data[]; 640 641 if (animImg) 642 { 643 disposeFrame(&png, animImg, tmpImg, true); 644 } 645 } 646 else 647 { 648 blitFrame(&png, png.frameBuffer, tmpImg); 649 650 img.data[] = tmpImg.data[]; 651 652 uint f = animImg.currentFrame; 653 animImg.currentFrame = f - 1; 654 disposeFrame(&png, animImg, tmpImg, false); 655 animImg.currentFrame = f; 656 } 657 658 if (animImg) 659 { 660 if (animImg.currentFrame == animImg.numFrames-1) 661 { 662 // Last frame, stop here 663 animImg.currentFrame = 0; 664 break; 665 } 666 } 667 } 668 else 669 { 670 return error(res[1]); 671 } 672 673 if (animImg) 674 { 675 animImg.advanceFrame(); 676 } 677 else 678 { 679 // Stop decoding if we don't need animation 680 break; 681 } 682 } 683 } 684 685 finalize(); 686 return compound(img, ""); 687 } 688 689 /** 690 * Load animated PNG (APNG) from stream using specified image factory. 691 * GC-free 692 */ 693 Compound!(SuperAnimatedImage, string) loadAPNG( 694 InputStream istrm, 695 SuperImageFactory imgFac) 696 { 697 SuperAnimatedImage img = null; 698 auto res = loadPNG(istrm, imgFac); 699 if (res[0]) 700 img = cast(SuperAnimatedImage)res[0]; 701 return compound(img, res[1]); 702 } 703 704 /** 705 * Save APNG to stream. 706 * GC-free 707 */ 708 Compound!(bool, string) saveAPNG(SuperAnimatedImage img, OutputStream output) 709 in 710 { 711 assert (img.data.length); 712 } 713 do 714 { 715 ubyte[] raw; 716 ubyte[] buffer; 717 718 void finalize() 719 { 720 if (buffer.length) 721 Delete(buffer); 722 if (raw.length) 723 Delete(raw); 724 } 725 726 Compound!(bool, string) error(string errorMsg) 727 { 728 finalize(); 729 return compound(false, errorMsg); 730 } 731 732 if (img.bitDepth != 8) 733 return error("savePNG error: only 8-bit images are supported by encoder"); 734 735 bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData) 736 { 737 PNGChunk hdrChunk; 738 hdrChunk.length = cast(uint)chunkData.length; 739 hdrChunk.type = chunkType; 740 hdrChunk.data = chunkData; 741 hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data)); 742 743 if (!output.writeBE!uint(hdrChunk.length) 744 || !output.writeArray(hdrChunk.type)) 745 return false; 746 747 if (chunkData.length) 748 if (!output.writeArray(hdrChunk.data)) 749 return false; 750 751 if (!output.writeBE!uint(hdrChunk.crc)) 752 return false; 753 754 return true; 755 } 756 757 bool writeHeader() 758 { 759 PNGHeader hdr; 760 hdr.width = networkByteOrder(img.width); 761 hdr.height = networkByteOrder(img.height); 762 hdr.bitDepth = 8; 763 if (img.channels == 4) 764 hdr.colorType = ColorType.RGBA; 765 else if (img.channels == 3) 766 hdr.colorType = ColorType.RGB; 767 else if (img.channels == 2) 768 hdr.colorType = ColorType.GreyscaleAlpha; 769 else if (img.channels == 1) 770 hdr.colorType = ColorType.Greyscale; 771 hdr.compressionMethod = 0; 772 hdr.filterMethod = 0; 773 hdr.interlaceMethod = 0; 774 775 return writeChunk(IHDR, hdr.bytes); 776 } 777 778 uint seqNumber = 0; 779 780 bool writeAnimationControlChunk() 781 { 782 AnimationControlChunk actl; 783 actl.numFrames = networkByteOrder(img.numFrames); 784 actl.numPlays = networkByteOrder(0); 785 return writeChunk(acTL, actl.bytes); 786 } 787 788 bool writeFrameControlChunk() 789 { 790 FrameControlChunk fctl; 791 fctl.sequenceNumber = networkByteOrder(seqNumber); 792 seqNumber++; 793 fctl.width = networkByteOrder(img.width); 794 fctl.height = networkByteOrder(img.height); 795 fctl.x = networkByteOrder(0); 796 fctl.y = networkByteOrder(0); 797 // TODO: add timeStep to SuperAnimatedImage 798 fctl.delayNumerator = networkByteOrder(75); 799 fctl.delayDenominator = networkByteOrder(1000); 800 fctl.disposeOp = DisposeOp.Background; 801 fctl.blendOp = BlendOp.Source; 802 return writeChunk(fcTL, fctl.bytes); 803 } 804 805 bool writeFrameDataChunk(ubyte[] data) 806 { 807 uint len = cast(uint)data.length + 4; 808 ubyte[4] type = fdAT; 809 uint seq = seqNumber; 810 uint seqBE = networkByteOrder(seqNumber); 811 seqNumber++; 812 ubyte[4] seqNumberBytes; 813 seqNumberBytes = (cast(ubyte*)&seqBE)[0..4][]; 814 uint crc = crc32(chain(type[0..$], seqNumberBytes[0..$], data)); 815 816 if (!output.writeBE!uint(len) 817 || !output.writeArray(type)) 818 return false; 819 820 if (!output.writeBE!uint(seq)) 821 return false; 822 823 if (data.length) 824 if (!output.writeArray(data)) 825 return false; 826 827 if (!output.writeBE!uint(crc)) 828 return false; 829 830 return true; 831 } 832 833 output.writeArray(PNGSignature); 834 if (!writeHeader()) 835 return error("savePNG error: write failed (disk full?)"); 836 837 if (!writeAnimationControlChunk()) 838 return error("savePNG error: write failed (disk full?)"); 839 840 //TODO: filtering 841 raw = New!(ubyte[])(img.width * img.height * img.channels + img.height); 842 buffer = New!(ubyte[])(64 * 1024); 843 844 bool encode(uint frame) 845 { 846 if (!writeFrameControlChunk()) 847 return false; 848 849 foreach(y; 0..img.height) 850 { 851 auto rowStart = y * (img.width * img.channels + 1); 852 raw[rowStart] = 0; // No filter 853 854 foreach(x; 0..img.width) 855 { 856 auto dataIndex = (y * img.width + x) * img.channels; 857 auto rawIndex = rowStart + 1 + x * img.channels; 858 859 foreach(ch; 0..img.channels) 860 raw[rawIndex + ch] = img.data[dataIndex + ch]; 861 } 862 } 863 864 ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw); 865 while (!zlibEncoder.ended) 866 { 867 auto len = zlibEncoder.encode(); 868 if (len > 0) 869 { 870 bool res; 871 if (frame == 0) 872 res = writeChunk(IDAT, zlibEncoder.buffer[0..len]); 873 else 874 res = writeFrameDataChunk(zlibEncoder.buffer[0..len]); 875 876 if (!res) 877 return false; 878 } 879 } 880 881 return true; 882 } 883 884 uint startFrame = img.currentFrame; 885 foreach(f; 0..img.numFrames) 886 { 887 img.currentFrame = f; 888 if (!encode(f)) 889 return error("savePNG error: write failed (disk full?)"); 890 } 891 img.currentFrame = startFrame; 892 893 writeChunk(IEND, []); 894 895 finalize(); 896 return compound(true, ""); 897 } 898 899 /** 900 * Save PNG to stream. 901 * GC-free 902 */ 903 Compound!(bool, string) savePNG(SuperImage img, OutputStream output) 904 in 905 { 906 assert (img.data.length); 907 } 908 do 909 { 910 Compound!(bool, string) error(string errorMsg) 911 { 912 return compound(false, errorMsg); 913 } 914 915 if (img.bitDepth != 8) 916 return error("savePNG error: only 8-bit images are supported by encoder"); 917 918 bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData) 919 { 920 PNGChunk hdrChunk; 921 hdrChunk.length = cast(uint)chunkData.length; 922 hdrChunk.type = chunkType; 923 hdrChunk.data = chunkData; 924 hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data)); 925 926 if (!output.writeBE!uint(hdrChunk.length) 927 || !output.writeArray(hdrChunk.type)) 928 return false; 929 930 if (chunkData.length) 931 if (!output.writeArray(hdrChunk.data)) 932 return false; 933 934 if (!output.writeBE!uint(hdrChunk.crc)) 935 return false; 936 937 return true; 938 } 939 940 bool writeHeader() 941 { 942 PNGHeader hdr; 943 hdr.width = networkByteOrder(img.width); 944 hdr.height = networkByteOrder(img.height); 945 hdr.bitDepth = 8; 946 if (img.channels == 4) 947 hdr.colorType = ColorType.RGBA; 948 else if (img.channels == 3) 949 hdr.colorType = ColorType.RGB; 950 else if (img.channels == 2) 951 hdr.colorType = ColorType.GreyscaleAlpha; 952 else if (img.channels == 1) 953 hdr.colorType = ColorType.Greyscale; 954 hdr.compressionMethod = 0; 955 hdr.filterMethod = 0; 956 hdr.interlaceMethod = 0; 957 958 return writeChunk(IHDR, hdr.bytes); 959 } 960 961 output.writeArray(PNGSignature); 962 if (!writeHeader()) 963 return error("savePNG error: write failed (disk full?)"); 964 965 //TODO: filtering 966 ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height); 967 foreach(y; 0..img.height) 968 { 969 auto rowStart = y * (img.width * img.channels + 1); 970 raw[rowStart] = 0; // No filter 971 972 foreach(x; 0..img.width) 973 { 974 auto dataIndex = (y * img.width + x) * img.channels; 975 auto rawIndex = rowStart + 1 + x * img.channels; 976 977 foreach(ch; 0..img.channels) 978 raw[rawIndex + ch] = img.data[dataIndex + ch]; 979 } 980 } 981 982 ubyte[] buffer = New!(ubyte[])(64 * 1024); 983 ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw); 984 while (!zlibEncoder.ended) 985 { 986 auto len = zlibEncoder.encode(); 987 if (len > 0) 988 writeChunk(IDAT, zlibEncoder.buffer[0..len]); 989 } 990 991 writeChunk(IEND, []); 992 993 Delete(buffer); 994 Delete(raw); 995 996 return compound(true, ""); 997 } 998 999 Compound!(bool, string) err(string msg) 1000 { 1001 return compound(false, msg); 1002 } 1003 1004 Compound!(bool, string) suc() 1005 { 1006 return compound(true, ""); 1007 } 1008 1009 Compound!(bool, string) readChunk( 1010 PNGImage* png, 1011 InputStream istrm, 1012 PNGChunk* chunk) 1013 { 1014 if (!istrm.readBE!uint(&chunk.length) || 1015 !istrm.fillArray(chunk.type)) 1016 { 1017 return err("loadPNG error: failed to read chunk, invalid PNG stream"); 1018 } 1019 1020 version(PNGDebug) writefln("Chunk length = %s", chunk.length); 1021 version(PNGDebug) writefln("Chunk type = %s", cast(char[])chunk.type); 1022 1023 if (chunk.length > 0) 1024 { 1025 chunk.data = New!(ubyte[])(chunk.length); 1026 if (!istrm.fillArray(chunk.data)) 1027 { 1028 return err("loadPNG error: failed to read chunk data, invalid PNG stream"); 1029 } 1030 } 1031 1032 version(PNGDebug) writefln("Chunk data.length = %s", chunk.data.length); 1033 1034 if (!istrm.readBE!uint(&chunk.crc)) 1035 { 1036 return err("loadPNG error: failed to read chunk CRC, invalid PNG stream"); 1037 } 1038 1039 uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data)); 1040 1041 version(PNGDebug) 1042 { 1043 writefln("Chunk CRC = %X", chunk.crc); 1044 writefln("Calculated CRC = %X", calculatedCRC); 1045 } 1046 1047 if (chunk.crc != calculatedCRC) 1048 { 1049 return err("loadPNG error: chunk CRC check failed"); 1050 } 1051 1052 return suc(); 1053 } 1054 1055 Compound!(bool, string) readIHDR( 1056 PNGImage* png, 1057 PNGChunk* chunk) 1058 { 1059 PNGHeader* hdr = &png.hdr; 1060 1061 if (chunk.data.length < hdr.bytes.length) 1062 return err("loadPNG error: illegal header chunk"); 1063 1064 hdr.bytes[] = chunk.data[0..hdr.bytes.length]; 1065 hdr.width = bigEndian(hdr.width); 1066 hdr.height = bigEndian(hdr.height); 1067 1068 version(PNGDebug) 1069 { 1070 writefln("width = %s", hdr.width); 1071 writefln("height = %s", hdr.height); 1072 writefln("bitDepth = %s", hdr.bitDepth); 1073 writefln("colorType = %s", hdr.colorType); 1074 writefln("compressionMethod = %s", hdr.compressionMethod); 1075 writefln("filterMethod = %s", hdr.filterMethod); 1076 writefln("interlaceMethod = %s", hdr.interlaceMethod); 1077 } 1078 1079 bool supportedIndexed = 1080 (hdr.colorType == ColorType.Palette) && 1081 (hdr.bitDepth == 1 || 1082 hdr.bitDepth == 2 || 1083 hdr.bitDepth == 4 || 1084 hdr.bitDepth == 8); 1085 1086 if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed) 1087 return err("loadPNG error: unsupported bit depth"); 1088 1089 if (hdr.compressionMethod != 0) 1090 return err("loadPNG error: unsupported compression method"); 1091 1092 if (hdr.filterMethod != 0) 1093 return err("loadPNG error: unsupported filter method"); 1094 1095 if (hdr.interlaceMethod != 0) 1096 return err("loadPNG error: interlacing is not supported"); 1097 1098 if (hdr.colorType == ColorType.Greyscale) 1099 png.numChannels = 1; 1100 else if (hdr.colorType == ColorType.GreyscaleAlpha) 1101 png.numChannels = 2; 1102 else if (hdr.colorType == ColorType.RGB) 1103 png.numChannels = 3; 1104 else if (hdr.colorType == ColorType.RGBA) 1105 png.numChannels = 4; 1106 else if (hdr.colorType == ColorType.Palette) 1107 { 1108 if (png.transparency.length > 0) 1109 png.numChannels = 4; 1110 else 1111 png.numChannels = 3; 1112 } 1113 else 1114 return err("loadPNG error: unsupported color type"); 1115 1116 if (hdr.colorType == ColorType.Palette) 1117 png.bitDepth = 8; 1118 else 1119 png.bitDepth = hdr.bitDepth; 1120 1121 png.bytesPerChannel = png.bitDepth / 8; 1122 1123 version(PNGDebug) 1124 { 1125 writefln("bytesPerChannel = %s", png.bytesPerChannel); 1126 } 1127 1128 return suc(); 1129 } 1130 1131 Compound!(bool, string) fillFrame(PNGImage* png) 1132 { 1133 ubyte[] decodedBuffer = png.decoder.buffer; 1134 version(PNGDebug) writefln("decodedBuffer.length = %s", decodedBuffer.length); 1135 1136 bool indexed = (png.hdr.colorType == ColorType.Palette); 1137 1138 uint calculatedSize; 1139 if (indexed) 1140 calculatedSize = (png.frame.width * png.frame.height * png.hdr.bitDepth) / 8 + png.frame.height; 1141 else 1142 calculatedSize = png.frame.width * png.frame.height * (png.hdr.bitDepth / 8) * png.numChannels + png.frame.height; 1143 1144 png.frameSize = png.frame.width * png.frame.height * png.numChannels * png.bytesPerChannel; 1145 png.filteredBufferSize = calculatedSize - png.frame.height; 1146 1147 version(PNGDebug) 1148 { 1149 writefln("calculatedSize = %s", calculatedSize); 1150 writefln("frameSize = %s", png.frameSize); 1151 writefln("filteredBufferSize = %s", png.filteredBufferSize); 1152 1153 writefln("png.frameBuffer.length = %s", png.frameBuffer.length); 1154 } 1155 1156 ubyte[] pdata = png.frameBuffer[0..png.frameSize]; 1157 ubyte[] filteredBuffer = png.filteredBuffer[0..png.filteredBufferSize]; 1158 1159 if (decodedBuffer.length != calculatedSize) 1160 { 1161 return err("loadPNG error: image size and data mismatch"); 1162 } 1163 1164 // apply filtering to the image data 1165 auto res = filter(png, indexed, decodedBuffer, filteredBuffer); 1166 if (!res[0]) 1167 { 1168 return err(res[1]); 1169 } 1170 1171 // if a palette is used, substitute target colors 1172 if (indexed) 1173 { 1174 if (png.palette.length == 0) 1175 return err("loadPNG error: palette chunk not found"); 1176 1177 if (png.hdr.bitDepth == 8) 1178 { 1179 for (int i = 0; i < filteredBuffer.length; ++i) 1180 { 1181 ubyte b = filteredBuffer[i]; 1182 pdata[i * png.numChannels + 0] = png.palette[b * 3 + 0]; 1183 pdata[i * png.numChannels + 1] = png.palette[b * 3 + 1]; 1184 pdata[i * png.numChannels + 2] = png.palette[b * 3 + 2]; 1185 if (png.transparency.length > 0) 1186 pdata[i * png.numChannels + 3] = 1187 b < png.transparency.length ? png.transparency[b] : 255; 1188 } 1189 } 1190 else // bit depths 1, 2, 4 1191 { 1192 int srcindex = 0; 1193 int srcshift = 8 - png.hdr.bitDepth; 1194 ubyte mask = cast(ubyte)((1 << png.hdr.bitDepth) - 1); 1195 int sz = png.frame.width * png.frame.height; 1196 for (int dstindex = 0; dstindex < sz; dstindex++) 1197 { 1198 auto b = ((filteredBuffer[srcindex] >> srcshift) & mask); 1199 //assert(b * 3 + 2 < palette.length); 1200 pdata[dstindex * png.numChannels + 0] = png.palette[b * 3 + 0]; 1201 pdata[dstindex * png.numChannels + 1] = png.palette[b * 3 + 1]; 1202 pdata[dstindex * png.numChannels + 2] = png.palette[b * 3 + 2]; 1203 1204 if (png.transparency.length > 0) 1205 pdata[dstindex * png.numChannels + 3] = 1206 b < png.transparency.length ? png.transparency[b] : 255; 1207 1208 if (srcshift <= 0) 1209 { 1210 srcshift = 8 - png.hdr.bitDepth; 1211 srcindex++; 1212 } 1213 else 1214 { 1215 srcshift -= png.hdr.bitDepth; 1216 } 1217 } 1218 } 1219 } 1220 else 1221 { 1222 pdata[] = filteredBuffer[]; 1223 } 1224 1225 return suc(); 1226 } 1227 1228 void blitFrame( 1229 PNGImage* png, 1230 ubyte[] frameBuffer, 1231 SuperImage img) 1232 { 1233 for(uint y = 0; y < png.frame.height; y++) 1234 { 1235 for(uint x = 0; x < png.frame.width; x++) 1236 { 1237 Color4f c1 = img[png.frame.x + x, png.frame.y + y]; 1238 Color4f c2 = getColor(png, frameBuffer, x, y); 1239 1240 if (png.frame.blendOp == BlendOp.Source) 1241 img[png.frame.x + x, png.frame.y + y] = c2; 1242 else 1243 img[png.frame.x + x, png.frame.y + y] = alphaOver(c1, c2); 1244 } 1245 } 1246 } 1247 1248 void disposeFrame( 1249 PNGImage* png, 1250 SuperImage prevImg, 1251 SuperImage img, 1252 bool firstFrame) 1253 { 1254 if (png.frame.disposeOp != DisposeOp.None) 1255 for(uint y = 0; y < png.hdr.height; y++) 1256 { 1257 for(uint x = 0; x < png.hdr.width; x++) 1258 { 1259 if (png.frame.disposeOp == DisposeOp.Previous && !firstFrame) 1260 img[x, y] = prevImg[x, y]; 1261 else 1262 img[x, y] = Color4f(0, 0, 0, 0); 1263 } 1264 } 1265 } 1266 1267 Color4f getColor( 1268 PNGImage* png, 1269 ubyte[] pixData, 1270 uint x, 1271 uint y) 1272 { 1273 uint bitDepth = png.bitDepth; 1274 uint channels = png.numChannels; 1275 uint pixelSize = png.bytesPerChannel * channels; 1276 uint index = (y * png.frame.width + x) * pixelSize; 1277 uint maxv = (2 ^^ bitDepth) - 1; 1278 1279 Color4 res = Color4(0, 0, 0, 0); 1280 1281 if (channels == 1 && bitDepth == 8) 1282 { 1283 auto v = pixData[index]; 1284 res = Color4(v, v, v); 1285 } 1286 else if (channels == 2 && bitDepth == 8) 1287 { 1288 auto v = pixData[index]; 1289 res = Color4(v, v, v, pixData[index+1]); 1290 } 1291 else if (channels == 3 && bitDepth == 8) 1292 { 1293 res = Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv); 1294 } 1295 else if (channels == 4 && bitDepth == 8) 1296 { 1297 res = Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]); 1298 } 1299 else if (channels == 1 && bitDepth == 16) 1300 { 1301 ushort v = pixData[index] << 8 | pixData[index+1]; 1302 res = Color4(v, v, v); 1303 } 1304 else if (channels == 2 && bitDepth == 16) 1305 { 1306 ushort v = pixData[index] << 8 | pixData[index+1]; 1307 ushort a = pixData[index+2] << 8 | pixData[index+3]; 1308 res = Color4(v, v, v, a); 1309 } 1310 else if (channels == 3 && bitDepth == 16) 1311 { 1312 ushort r = pixData[index] << 8 | pixData[index+1]; 1313 ushort g = pixData[index+2] << 8 | pixData[index+3]; 1314 ushort b = pixData[index+4] << 8 | pixData[index+5]; 1315 ushort a = cast(ushort)maxv; 1316 res = Color4(r, g, b, a); 1317 } 1318 else if (channels == 4 && bitDepth == 16) 1319 { 1320 ushort r = pixData[index] << 8 | pixData[index+1]; 1321 ushort g = pixData[index+2] << 8 | pixData[index+3]; 1322 ushort b = pixData[index+4] << 8 | pixData[index+5]; 1323 ushort a = pixData[index+6] << 8 | pixData[index+7]; 1324 res = Color4(r, g, b, a); 1325 } 1326 else 1327 assert(0); 1328 1329 return Color4f(res, bitDepth); 1330 } 1331 1332 /* 1333 * Performs the paeth PNG filter from pixels values: 1334 * a = back 1335 * b = up 1336 * c = up and back 1337 */ 1338 pure ubyte paeth(ubyte a, ubyte b, ubyte c) 1339 { 1340 int p = a + b - c; 1341 int pa = std.math.abs(p - a); 1342 int pb = std.math.abs(p - b); 1343 int pc = std.math.abs(p - c); 1344 if (pa <= pb && pa <= pc) return a; 1345 else if (pb <= pc) return b; 1346 else return c; 1347 } 1348 1349 Compound!(bool, string) filter( 1350 PNGImage* png, 1351 bool indexed, 1352 ubyte[] ibuffer, 1353 ubyte[] obuffer) 1354 { 1355 uint width = png.frame.width; 1356 uint height = png.frame.height; 1357 uint channels = png.numChannels; 1358 1359 uint bytesPerPixel = png.hdr.bitDepth / 8; // 1 for 8bit, 2 for 16bit 1360 1361 uint scanlineSize; 1362 if (indexed) 1363 scanlineSize = (width * png.hdr.bitDepth) / 8 + 1; 1364 else 1365 scanlineSize = width * bytesPerPixel * channels + 1; 1366 1367 ubyte pback, pup, pupback, cbyte; 1368 1369 for (int i = 0; i < height; ++i) 1370 { 1371 pback = 0; 1372 1373 // get the first byte of a scanline 1374 ubyte scanFilter = ibuffer[i * scanlineSize]; 1375 1376 if (indexed) 1377 { 1378 width = scanlineSize - 1; 1379 for (int j = 0; j < width; ++j) 1380 { 1381 if (i == 0) pup = 0; 1382 else pup = obuffer[(i-1) * width + j]; 1383 if (j == 0) pback = 0; 1384 else pback = obuffer[i * width + j-1]; 1385 if (i == 0 || j == 0) pupback = 0; 1386 else pupback = obuffer[(i-1) * width + j-1]; 1387 1388 cbyte = ibuffer[i * scanlineSize + j+1]; 1389 1390 // filter, then set the current byte in data 1391 switch (scanFilter) 1392 { 1393 case FilterMethod.None: 1394 obuffer[i * width + j] = cbyte; 1395 break; 1396 case FilterMethod.Sub: 1397 obuffer[i * width + j] = cast(ubyte)(cbyte + pback); 1398 break; 1399 case FilterMethod.Up: 1400 obuffer[i * width + j] = cast(ubyte)(cbyte + pup); 1401 break; 1402 case FilterMethod.Average: 1403 obuffer[i * width + j] = cast(ubyte)(cbyte + (pback + pup) / 2); 1404 break; 1405 case FilterMethod.Paeth: 1406 obuffer[i * width + j] = cast(ubyte)(cbyte + paeth(pback, pup, pupback)); 1407 break; 1408 default: 1409 return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter)); 1410 } 1411 } 1412 } 1413 else 1414 { 1415 for (int j = 0; j < width; ++j) 1416 { 1417 for (int k = 0; k < bytesPerPixel * channels; ++k) 1418 { 1419 if (i == 0) pup = 0; 1420 else pup = obuffer[((i-1) * width + j) * bytesPerPixel * channels + k]; 1421 if (j == 0) pback = 0; 1422 else pback = obuffer[(i * width + j-1) * bytesPerPixel * channels + k]; 1423 if (i == 0 || j == 0) pupback = 0; 1424 else pupback = obuffer[((i-1) * width + j-1) * bytesPerPixel * channels + k]; 1425 1426 // get the current byte from ibuffer 1427 cbyte = ibuffer[i * (width * bytesPerPixel * channels + 1) + j * bytesPerPixel * channels + k + 1]; 1428 1429 // filter, then set the current byte in data 1430 switch (scanFilter) 1431 { 1432 case FilterMethod.None: 1433 obuffer[(i * width + j) * bytesPerPixel * channels + k] = cbyte; 1434 break; 1435 case FilterMethod.Sub: 1436 obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pback); 1437 break; 1438 case FilterMethod.Up: 1439 obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pup); 1440 break; 1441 case FilterMethod.Average: 1442 obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2); 1443 break; 1444 case FilterMethod.Paeth: 1445 obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback)); 1446 break; 1447 default: 1448 return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter)); 1449 } 1450 } 1451 } 1452 } 1453 } 1454 1455 return suc(); 1456 } 1457 1458 uint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R) 1459 { 1460 uint[256] generateTable() 1461 { 1462 uint[256] table; 1463 uint crc; 1464 for (int i = 0; i < 256; i++) 1465 { 1466 crc = i; 1467 for (int j = 0; j < 8; j++) 1468 crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1; 1469 table[i] = crc; 1470 } 1471 return table; 1472 } 1473 1474 static const uint[256] table = generateTable(); 1475 1476 uint crc; 1477 1478 crc = inCrc ^ 0xFFFFFFFF; 1479 foreach(v; range) 1480 crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF]; 1481 1482 return (crc ^ 0xFFFFFFFF); 1483 }