1 // Written in the D programming language. 2 3 /** 4 * Copyright: Copyright 2012 - 5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * Authors: Callum Anderson, Stewart Gordon 7 * Date: July 24, 2012 8 */ 9 module imaged.png; 10 11 import 12 std.file, 13 std.stdio, 14 std.math, 15 std.algorithm, 16 std.conv, 17 std.zlib, 18 undead.stream; 19 20 import 21 imaged.image; 22 23 24 /** 25 * Png decoder. 26 */ 27 class PngDecoder : Decoder 28 { 29 enum Chunk 30 { 31 NONE, 32 IHDR, // header 33 IDAT, // image 34 PLTE, // palette 35 IEND // end of image 36 } 37 38 39 // Empty constructor, usefule for parsing a stream manually 40 this(in bool logging = false) 41 { 42 m_logging = logging; 43 zliber = new UnCompress(); 44 } 45 46 47 // Constructor taking a filename 48 this(in string filename, in bool logging = false) 49 { 50 this(logging); 51 parseFile(filename); 52 53 } // c'tor 54 55 56 // Parse one byte 57 override void parseByte(ubyte bite) 58 { 59 segment.buffer ~= bite; 60 61 if (!m_haveHeader && (segment.buffer.length == 8)) 62 { 63 if (segment.buffer[0..8] == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) 64 { 65 // File has correct header 66 segment.buffer.destroy; 67 m_pendingChunk = true; 68 m_haveHeader = true; 69 } 70 else 71 { 72 // Not a valid png 73 m_errorState.code = 1; 74 m_errorState.message = "Header does not match PNG type!"; 75 return; 76 } 77 } 78 79 if (m_pendingChunk && (segment.buffer.length == 8)) 80 { 81 m_pendingChunk = false; 82 83 segment.chunkLength = fourBytesToInt(segment.buffer[0..4]); 84 char[] type = cast(char[])segment.buffer[4..8]; 85 86 if (type == "IHDR") 87 { 88 segment.chunkType = Chunk.IHDR; 89 } 90 else if (type == "IDAT") 91 { 92 segment.chunkType = Chunk.IDAT; 93 } 94 else if (type == "PLTE") 95 { 96 segment.chunkType = Chunk.PLTE; 97 } 98 else if (type == "IEND") 99 { 100 segment.chunkType = Chunk.IEND; 101 } 102 } 103 104 if (segment.chunkType != Chunk.IDAT) 105 { 106 if (m_haveHeader && !m_pendingChunk && (segment.buffer.length == segment.chunkLength + 8 + 4)) 107 { 108 processChunk(); 109 m_previousChunk = segment.chunkType; 110 m_pendingChunk = true; 111 segment = PNGSegment(); 112 } 113 } 114 else 115 { 116 if (segment.buffer.length > 8 && segment.buffer.length <= segment.chunkLength + 8) 117 { 118 uncompressStream([bite]); 119 } 120 else if (segment.buffer.length == segment.chunkLength + 8 + 4) 121 { 122 processChunk(); 123 m_previousChunk = segment.chunkType; 124 m_pendingChunk = true; 125 segment = PNGSegment(); 126 } 127 } 128 129 m_totalBytesParsed ++; 130 } // parseByte 131 132 133 private: 134 135 bool m_haveHeader = false; 136 Chunk m_previousChunk = Chunk.NONE; 137 bool m_pendingChunk = false; 138 uint m_totalBytesParsed; 139 ubyte m_interlacePass = 0; 140 141 int[7] m_pixPerLine; 142 int[7] m_scanLines; 143 144 struct InterLace 145 { 146 int imageRow; 147 int[7] start_row = [ 0, 0, 4, 0, 2, 0, 1 ]; 148 int[7] start_col = [ 0, 4, 0, 2, 0, 1, 0 ]; 149 int[7] row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; 150 int[7] col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; 151 int[7] block_height = [ 8, 8, 4, 4, 2, 2, 1 ]; 152 int[7] block_width = [ 8, 4, 4, 2, 2, 1, 1 ]; 153 } 154 InterLace m_ilace; 155 156 157 struct PNGSegment 158 { 159 Chunk chunkType = Chunk.NONE; 160 int chunkLength; 161 ubyte[] buffer; 162 } 163 PNGSegment segment; 164 165 ubyte[] scanLine1, scanLine2; 166 int m_currentScanLine; 167 UnCompress zliber; 168 uint checkSum; 169 170 int m_width, m_height; 171 int m_bitDepth, 172 m_colorType, 173 m_compression, 174 m_filter, 175 m_interlace, 176 m_bytesPerScanline, 177 m_nChannels, 178 m_stride, 179 m_pixelScale; 180 181 ubyte[] m_palette; 182 183 /** 184 * Color types are: 185 * Color Allowed Interpretation 186 * Type Bit Depths 187 * 0 1,2,4,8,16 Each pixel is a grayscale sample. 188 * 2 8,16 Each pixel is an R,G,B triple. 189 * 3 1,2,4,8 Each pixel is a palette index; a PLTE chunk must appear. 190 * 4 8,16 Each pixel is a grayscale sample followed by an alpha sample. 191 * 6 8,16 Each pixel is an R,G,B triple, followed by an alpha sample. 192 */ 193 194 // COnvert 4 bytes to an integer 195 int fourBytesToInt(ubyte[] bytes) 196 { 197 return (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]); 198 } 199 200 201 void processChunk() 202 { 203 /** 204 * Remeber - first 8 bytes in the segment.buffer are length (4byte) and type (4byte) 205 * So chunk data begins at index 8 of the buffer. We keep this stuff for calculating 206 * the checksum (it actually only uses the chunk data and the type field). 207 */ 208 209 debug 210 { 211 if (m_logging) writeln("PNG ProcessChunk: Processing " ~ to!string(segment.chunkType)); 212 } 213 214 // Compare checksums, but let chunk types determine how to handle failed checks 215 bool csum_passed = true; 216 uint csum_calc = crc32(0, segment.buffer[4..$-4]); 217 uint csum_read = fourBytesToInt(segment.buffer[$-4..$]); 218 if (csum_calc != csum_read) 219 { 220 csum_passed = false; 221 debug 222 { 223 if (m_logging) writeln("PNG: Error - checksum failed!"); 224 } 225 } 226 227 switch(segment.chunkType) 228 { 229 case Chunk.IHDR: // IHDR chunk contains height, width info 230 { 231 if (!csum_passed) 232 { 233 errorState.code = 1; 234 errorState.message = "PNG: Checksum failed in IHDR!"; 235 return; 236 } 237 238 m_width = fourBytesToInt(segment.buffer[8..12]); 239 m_height = fourBytesToInt(segment.buffer[12..16]); 240 m_bitDepth = segment.buffer[16]; 241 m_colorType = segment.buffer[17]; 242 m_compression = segment.buffer[18]; 243 m_filter = segment.buffer[19]; 244 m_interlace = segment.buffer[20]; 245 m_pixelScale = 1; 246 247 // This function sets some state, like m_nChannels 248 allocateImage(); 249 250 // Calculate pixels per line and scanlines per pass for interlacing 251 if (m_interlace == 1) 252 { 253 setInterlace(); 254 } 255 256 auto bitStride = m_nChannels*m_bitDepth; 257 auto imageBitsPerLine = m_width*bitStride; 258 259 m_bytesPerScanline = 1 + imageBitsPerLine/8; 260 m_stride = bitStride/8; 261 262 if (imageBitsPerLine % 8 > 0) 263 { 264 m_bytesPerScanline ++; 265 m_stride = 1; 266 } 267 268 if (m_stride == 0) 269 { 270 m_stride = 1; 271 } 272 273 // Initialize this scanLine, since it will be empty initially 274 scanLine1 = new ubyte[](m_bytesPerScanline); 275 276 debug 277 { 278 if (m_logging) 279 { 280 writefln("PNG\n Width: %s\nHeight: %s\nBitDepth: %s\nColorType: %s\n" ~ 281 "Compression: %s\nFilter: %s\nInterlacing: %s\nStride: %s", 282 m_width, m_height, m_bitDepth, m_colorType, 283 m_compression, m_filter, m_interlace, m_stride); 284 } 285 } 286 break; 287 } 288 case Chunk.IDAT: // Actual image data 289 { 290 if (!csum_passed) 291 { 292 errorState.code = 1; 293 errorState.message = "PNG: Checksum failed in IDAT!"; 294 return; 295 } 296 break; 297 } 298 case Chunk.PLTE: // Palette chunk 299 { 300 if (!csum_passed) 301 { 302 errorState.code = 1; 303 errorState.message = "PNG: Checksum failed in IPLTE!"; 304 return; 305 } 306 307 if (m_colorType == 3) 308 m_palette = segment.buffer[8..$-4].dup; 309 310 break; 311 } 312 case Chunk.IEND: // Image end 313 { 314 // Flush out the rest of the stream 315 uncompressStream(scanLine2, true); 316 break; 317 } 318 319 default: 320 { 321 debug 322 { 323 if (m_logging) writeln("PNG ProcessChunk: Un-handled chunk " ~ to!string(segment.chunkType)); 324 } 325 break; 326 } 327 } 328 } // processChunk 329 330 331 // Allocate the image 332 void allocateImage() 333 { 334 switch (m_colorType) 335 { 336 case 0: // greyscale 337 { 338 m_nChannels = 1; 339 switch(m_bitDepth) 340 { 341 case 1: 342 m_image = new Img!(Px.L8)(m_width, m_height); 343 m_pixelScale = 255; 344 break; 345 case 2: 346 m_image = new Img!(Px.L8)(m_width, m_height); 347 m_pixelScale = 64; 348 break; 349 case 4: 350 m_image = new Img!(Px.L8)(m_width, m_height); 351 m_pixelScale = 16; 352 break; 353 case 8: 354 m_image = new Img!(Px.L8)(m_width, m_height); 355 break; 356 case 16: 357 m_image = new Img!(Px.L16)(m_width, m_height); 358 break; 359 default: 360 m_errorState.code = 1; 361 m_errorState.message = "PNG: Greyscale image with incorrect bit depth detected"; 362 } 363 break; 364 } 365 case 2: // rgb 366 { 367 m_nChannels = 3; 368 switch(m_bitDepth) 369 { 370 case 8: 371 m_image = new Img!(Px.R8G8B8)(m_width, m_height); 372 break; 373 case 16: 374 m_image = new Img!(Px.R16G16B16)(m_width, m_height); 375 break; 376 default: 377 m_errorState.code = 1; 378 m_errorState.message = "PNG: RGB image with incorrect bit depth detected"; 379 } 380 break; 381 } 382 case 3: // palette 383 { 384 m_nChannels = 1; 385 m_image = new Img!(Px.R8G8B8)(m_width, m_height); 386 break; 387 } 388 case 4: // greyscale + alpha 389 { 390 m_nChannels = 2; 391 switch(m_bitDepth) 392 { 393 case 8: 394 m_image = new Img!(Px.L8A8)(m_width, m_height); 395 break; 396 case 16: 397 m_image = new Img!(Px.L16A16)(m_width, m_height); 398 break; 399 default: 400 m_errorState.code = 1; 401 m_errorState.message = "PNG: Greysca;+alpha with incorrect bit depth detected"; 402 } 403 break; 404 } 405 case 6: // rgba 406 { 407 m_nChannels = 4; 408 switch(m_bitDepth) 409 { 410 case 8: 411 m_image = new Img!(Px.R8G8B8A8)(m_width, m_height); 412 break; 413 case 16: 414 m_image = new Img!(Px.R16G16B16A16)(m_width, m_height); 415 break; 416 default: 417 m_errorState.code = 1; 418 m_errorState.message = "PNG: RGBA image with incorrect bit depth detected"; 419 } 420 break; 421 } 422 default: // error 423 m_errorState.code = 1; 424 m_errorState.message = "PNG: Incorrect color type detected"; 425 } 426 } // allocateImage 427 428 429 // Set some state dealing with interlacing 430 void setInterlace() 431 { 432 foreach(i; 0..m_width) 433 { 434 if (i % 8 == 0) m_pixPerLine[0] ++; 435 if (i % 8 == 4) m_pixPerLine[1] ++; 436 if (i % 8 == 0 || 437 i % 8 == 4) m_pixPerLine[2] ++; 438 if (i % 8 == 2 || 439 i % 8 == 6) m_pixPerLine[3] ++; 440 if (i % 8 == 0 || 441 i % 8 == 2 || 442 i % 8 == 4 || 443 i % 8 == 6 ) m_pixPerLine[4] ++; 444 if (i % 8 == 1 || 445 i % 8 == 3 || 446 i % 8 == 5 || 447 i % 8 == 7 ) m_pixPerLine[5] ++; 448 } 449 m_pixPerLine[6] = m_width; 450 451 foreach(i; 0..m_height) 452 { 453 if (i % 8 == 0) m_scanLines[0] ++; 454 if (i % 8 == 0) m_scanLines[1] ++; 455 if (i % 8 == 4) m_scanLines[2] ++; 456 if (i % 8 == 0 || 457 i % 8 == 4) m_scanLines[3] ++; 458 if (i % 8 == 2 || 459 i % 8 == 6 ) m_scanLines[4] ++; 460 if (i % 8 == 0 || 461 i % 8 == 2 || 462 i % 8 == 4 || 463 i % 8 == 6 ) m_scanLines[5] ++; 464 if (i % 8 == 1 || 465 i % 8 == 3 || 466 i % 8 == 5 || 467 i % 8 == 7 ) m_scanLines[6] ++; 468 } 469 } // setInterlace 470 471 472 // Uncompress the stream, apply filters, and store image 473 void uncompressStream(ubyte[] stream, bool finalize = false) 474 { 475 if (m_currentScanLine >= m_height || m_interlacePass >= 7) 476 return; 477 478 ubyte[] data; 479 if (!finalize) 480 { 481 data = cast(ubyte[])(zliber.uncompress(cast(void[])stream)); 482 } 483 else 484 { // finalize means flush out any remaining data 485 data = cast(ubyte[])(zliber.flush()); 486 } 487 488 // Number of bytes in a scanline depends on the interlace pass 489 int bytesPerLine, nscanLines; 490 passInfo(bytesPerLine, nscanLines); 491 492 int taken = 0, takeLen = 0; // bytes taken, bytes to take 493 while (taken < data.length) 494 { 495 // Put data into the lower scanline first 496 takeLen = cast(int) (bytesPerLine - scanLine2.length); 497 if (takeLen > 0 && taken + takeLen <= data.length) 498 { 499 scanLine2 ~= data[taken..taken+takeLen]; 500 taken += takeLen; 501 } 502 else if (takeLen > 0) 503 { 504 scanLine2 ~= data[taken..$]; 505 taken += data.length - taken; 506 } 507 508 if (scanLine2.length == bytesPerLine) 509 { 510 // Have a full scanline, so filter it... 511 filter(); 512 513 auto scanLineStride = m_stride; 514 515 // Unpack the bits if needed 516 ubyte[] sLine; 517 if (m_bitDepth < 8) 518 { 519 sLine = unpackBits(scanLine2[1..$]); 520 } 521 else 522 { 523 sLine = scanLine2[1..$]; 524 } 525 526 // For palette colortypes, convert scanline to RGB 527 if (m_colorType == 3) 528 { 529 sLine = convertPaletteToRGB(sLine); 530 scanLineStride = 3; 531 } 532 533 // Scale the bits up to 0-255 534 if (m_colorType != 3 && m_bitDepth < 8) 535 { 536 sLine[] *= cast(ubyte)m_pixelScale; 537 } 538 539 // Put it into the Image 540 if (m_interlace == 0) 541 { 542 // Non-interlaced 543 m_image.setRow(m_currentScanLine, sLine); 544 } 545 else 546 { 547 // Image is interlaced, so fill not just given pixels, but blocks of pixels 548 with(m_ilace) 549 { 550 int pass = m_interlacePass; 551 int col = start_col[pass]; // Image column 552 int i = 0; // scanline offset 553 554 while (col < m_width) 555 { 556 // Max x,y indices to fill up to for a given pass 557 auto maxY = min(block_height[pass], m_height - imageRow); 558 auto maxX = min(block_width[pass], m_width - col); 559 560 foreach(py; 0..maxY) 561 { 562 foreach(px; 0..maxX) 563 { 564 m_image.setPixel(col + px, imageRow + py, sLine[i..i+scanLineStride]); 565 } 566 } 567 568 col = col + col_increment[pass]; 569 i += scanLineStride; 570 571 } // while col < m_width 572 } // with(m_ilace) 573 } // if m_interlace 574 575 576 // Increment scanline counter 577 m_currentScanLine ++; 578 579 // Swap the scanlines 580 auto tmp = scanLine1; 581 scanLine1 = scanLine2; 582 scanLine2 = scanLine1; 583 scanLine2.destroy; 584 585 if (m_interlace == 1) 586 { 587 m_ilace.imageRow += m_ilace.row_increment[m_interlacePass]; 588 } 589 590 if (m_interlace == 1 && m_currentScanLine == nscanLines) 591 { 592 m_currentScanLine = 0; 593 m_interlacePass ++; 594 595 if (m_interlacePass == 7 || m_currentScanLine >= m_height) 596 { 597 break; 598 } 599 else 600 { 601 scanLine1 = new ubyte[](m_bytesPerScanline); 602 m_ilace.imageRow = m_ilace.start_row[m_interlacePass]; 603 604 // Recalc pass info 605 passInfo(bytesPerLine, nscanLines); 606 } 607 } 608 } 609 610 } // while (taken < data.length) 611 } // uncompressStream 612 613 614 // Calculate some per-pass info 615 void passInfo(out int bytesPerLine, out int nscanLines) 616 { 617 bytesPerLine = 0; // number of bytes in a scanline (dependent on interlace pass) 618 nscanLines = 0; // number of scanlines (also depends on interlace pass) 619 620 if (m_interlace == 1) 621 { 622 if (m_bitDepth < 8) 623 { 624 auto bitsPerLine = m_pixPerLine[m_interlacePass]*m_bitDepth; 625 bytesPerLine = bitsPerLine/8; 626 627 if (bitsPerLine % 8 > 0) 628 { 629 bytesPerLine ++; 630 } 631 bytesPerLine ++; // need to acount for the filter-type byte 632 } 633 else 634 { 635 bytesPerLine = m_pixPerLine[m_interlacePass]*m_stride + 1; 636 } 637 638 nscanLines = m_scanLines[m_interlacePass]; 639 } 640 else 641 { 642 bytesPerLine = m_bytesPerScanline; 643 nscanLines = m_height; 644 } 645 } // passInfo 646 647 648 // Unpack a scanline's worth of bits into a byte array 649 ubyte[] unpackBits(ubyte[] data) 650 { 651 int bytesPerLine = 0; 652 if (m_interlace == 0) 653 { 654 bytesPerLine = m_width; 655 } 656 else 657 { 658 bytesPerLine = m_pixPerLine[m_interlacePass]; 659 } 660 661 ubyte[] unpacked; 662 unpacked.length = bytesPerLine; 663 664 auto data_index = 0, byte_index = 0; 665 while(byte_index < bytesPerLine) 666 { 667 for(int j = 0; j < 8; j += m_bitDepth) 668 { 669 auto mask = ((1<<m_bitDepth)-1) << (8 - j - m_bitDepth); 670 auto val = ((data[data_index] & mask) >> (8 - j - m_bitDepth)); 671 unpacked[byte_index] = cast(ubyte) val; 672 byte_index ++; 673 if (byte_index >= bytesPerLine) 674 break; 675 } 676 data_index ++; 677 } 678 679 return unpacked; 680 } // unpackBits 681 682 683 // Convert a scanline of palette values to a scanline of 8-bit RGB's 684 ubyte[] convertPaletteToRGB(ubyte[] data) 685 { 686 ubyte[] rgb; 687 rgb.length = data.length * 3; 688 foreach(i; 0..data.length) 689 { 690 rgb[i*3] = m_palette[data[i]*3]; 691 rgb[i*3 + 1] = m_palette[data[i]*3 + 1]; 692 rgb[i*3 + 2] = m_palette[data[i]*3 + 2]; 693 } 694 return rgb; 695 } // convertPaletteToRGB 696 697 698 // Apply filters to a scanline 699 void filter() 700 { 701 int filterType = scanLine2[0]; 702 703 switch(filterType) 704 { 705 case 0: // no filtering, excellent 706 { 707 break; 708 } 709 case 1: // difference filter, using previous pixel on same scanline 710 { 711 filter1(); 712 break; 713 } 714 case 2: // difference filter, using pixel on scanline above, same column 715 { 716 filter2(); 717 break; 718 } 719 case 3: // average filter, average of pixel above and pixel to left 720 { 721 filter3(); 722 break; 723 } 724 case 4: // Paeth filter 725 { 726 filter4(); 727 break; 728 } 729 default: 730 { 731 if (m_logging) 732 { 733 writeln("PNG: Unhandled filter (" ~ to!string(filterType) ~ ") on scan line " 734 ~ to!string(m_currentScanLine) ~ ", Pass: " ~ to!string(m_interlacePass)); 735 } 736 break; 737 } 738 } // switch filterType 739 } 740 741 742 // Apply filter 1 to scanline (difference filter) 743 void filter1() 744 { 745 // Can't use vector op because results need to accumulate 746 for(int i=m_stride+1; i<scanLine2.length; i+=m_stride) 747 { 748 scanLine2[i..i+m_stride] += ( scanLine2[i-m_stride..i] ); 749 } 750 } 751 752 753 // Apply filter 2 to scanline (difference filter, using scanline above) 754 void filter2() 755 { 756 scanLine2[1..$] += scanLine1[1..$]; 757 } 758 759 760 // Apply filter 3 to scanline (average filter) 761 void filter3() 762 { 763 scanLine2[1..1+m_stride] += cast(ubyte[])(scanLine1[1..1+m_stride] / 2); 764 765 for(int i=m_stride+1; i<scanLine2.length; i+=m_stride) 766 { 767 scanLine2[i..i+m_stride] += cast(ubyte[])(( scanLine2[i-m_stride..i] + 768 scanLine1[i..i+m_stride] ) / 2); 769 } 770 } 771 772 773 // Apply filter 4 to scanline (Paeth filter) 774 void filter4() 775 { 776 int paeth(ubyte a, ubyte b, ubyte c) 777 { 778 int p = (a + b - c); 779 int pa = abs(p - a); 780 int pb = abs(p - b); 781 int pc = abs(p - c); 782 783 int pred = 0; 784 if ((pa <= pb) && (pa <= pc)) 785 { 786 pred = a; 787 } 788 else if (pb <= pc) 789 { 790 pred = b; 791 } 792 else 793 { 794 pred = c; 795 } 796 return pred; 797 } 798 799 foreach(i; 1..m_stride+1) 800 { 801 scanLine2[i] += paeth(0, scanLine1[i], 0); 802 } 803 804 for(int i=m_stride+1; i<scanLine2.length; i+=m_stride) 805 { 806 foreach(j; 0..m_stride) 807 { 808 scanLine2[i+j] += paeth(scanLine2[i+j-m_stride], scanLine1[i+j], scanLine1[i+j-m_stride]); 809 } 810 } 811 812 } // filter4 813 } // PngDecoder 814 815 816 /** 817 * PNG encoder for writing out Image classes to files as PNG. 818 * TODO: currently won't work with 16-bit Images. 819 */ 820 class PngEncoder : Encoder 821 { 822 /** 823 * Params: 824 * img = the image containing the data to write as a png 825 * filename = filename of the output 826 * Returns: true if writing succeeded, else false. 827 */ 828 override bool write(in Image img, string filename) 829 { 830 // The required PNG header 831 ubyte[] outData = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; 832 833 // Add in image header info 834 appendChunk(createHeaderChunk(img), outData); 835 836 // Filter and compress the data 837 auto rowLengthBytes = img.width*img.stride; 838 839 PNGChunk idat = PNGChunk("IDAT"); 840 ubyte[] imageData; 841 ubyte[] scanLine1, scanLine2; 842 scanLine1.length = rowLengthBytes; // initialize this scanline, since it will be empty to start 843 844 foreach(row; 0..img.height) 845 { 846 scanLine2 = img.pixels[row*rowLengthBytes..(row+1)*rowLengthBytes].dup; 847 848 // Apply adaptive filter 849 ubyte filterType; // this will hold the actual filter that was used 850 ubyte[] filtered = filter(img, scanLine1, scanLine2, filterType); 851 imageData ~= filterType ~ filtered; 852 853 // Swap the scanlines 854 auto tmp = scanLine1; 855 scanLine1 = scanLine2; 856 scanLine2 = scanLine1; 857 scanLine2.destroy; 858 } 859 860 idat.data = cast(ubyte[]) compress(cast(void[])imageData); 861 appendChunk(idat, outData); 862 863 // End the image with an IEND 864 appendChunk(PNGChunk("IEND"), outData); 865 866 // Write the PNG 867 std.file.write(filename, outData); 868 869 return true; 870 } 871 872 private: 873 874 struct PNGChunk 875 { 876 string type; 877 ubyte[] data; 878 } 879 880 // Array of function pointers containing filter algorithms 881 static ubyte[] function(in Image, in ubyte[], in ubyte[], out uint)[5] m_filters = 882 [&PngEncoder.filter0, &PngEncoder.filter1, 883 &PngEncoder.filter2, &PngEncoder.filter3, 884 &PngEncoder.filter4]; 885 886 /** 887 * Determines which filter type to apply to a given scanline by simply trying all of them, 888 * and calculating the sum of the absolute value in each filtered line. The filter which 889 * gives the minimum absolute sum will be used. 890 * Params: 891 * scanLine1 = the scanline $(I above) the scanline to be filtered 892 * scanLine2 = the scanline to be filtered 893 * filterType = a variable to hold the actual filter type that was used 894 * Returns: the filtered scanline 895 */ 896 static ubyte[] filter(in Image img, in ubyte[] scanLine1, ubyte[] scanLine2, out ubyte filterType) 897 { 898 uint sum = 0, minSum = uint.max; 899 ubyte[] filtered = scanLine2.dup; 900 901 foreach(index, filter; m_filters) 902 { 903 auto s = filter(img, scanLine1, scanLine2, sum); 904 if (sum < minSum) 905 { 906 filtered = s; 907 minSum = sum; 908 filterType = cast(ubyte)index; 909 } 910 } 911 return filtered; 912 } 913 914 // Filter 0 means no filtering 915 static ubyte[] filter0(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum) 916 { 917 ubyte[] filtered = scanLine2.dup; 918 absSum = reduce!("a + abs(b)")(0, filtered); 919 return filtered; 920 } 921 922 // Filter 1 is a difference between the current pixel and the previous pixl on same scanline 923 static ubyte[] filter1(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum) 924 { 925 ubyte[] filtered = scanLine2.dup; 926 uint s = img.stride; 927 absSum = reduce!("a + abs(b)")(0, filtered[0..s]); 928 for(int i = s; i < scanLine2.length; i++) 929 { 930 filtered[i] = cast(ubyte) (scanLine2[i] - scanLine2[i-s]); 931 absSum += abs(filtered[i]); 932 } 933 return filtered; 934 } 935 936 // FIlter 2 is a difference between current pixel and same pixel on scanline above 937 static ubyte[] filter2(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum) 938 { 939 ubyte[] filtered = scanLine2.dup; 940 filtered[] = scanLine2[] - scanLine1[]; 941 absSum = reduce!("a + abs(b)")(0, filtered); 942 return filtered; 943 } 944 945 // Filter 3 is an average of previous pixel on same scanline ,and same pixel on line above 946 static ubyte[] filter3(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum) 947 { 948 ubyte[] filtered = scanLine2.dup; 949 950 for(int i = 0; i < img.stride; i++) 951 { 952 filtered[i] = cast(ubyte) (scanLine2[i] - (scanLine1[i]/2) ); 953 absSum += abs(filtered[i]); 954 } 955 956 for(int i = img.stride; i < scanLine2.length; i++) 957 { 958 filtered[i] = cast(ubyte) (scanLine2[i] - (scanLine1[i]+scanLine2[i-img.stride])/2); 959 absSum += abs(filtered[i]); 960 } 961 return filtered; 962 } 963 964 // Paeth filter 965 static ubyte[] filter4(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum) 966 { 967 int paeth(ubyte a, ubyte b, ubyte c) 968 { 969 int p = (a + b - c); 970 int pa = abs(p - a); 971 int pb = abs(p - b); 972 int pc = abs(p - c); 973 974 int pred = 0; 975 if ((pa <= pb) && (pa <= pc)) 976 { 977 pred = a; 978 } 979 else if (pb <= pc) 980 { 981 pred = b; 982 } 983 else 984 { 985 pred = c; 986 } 987 return pred; 988 } 989 990 ubyte[] filtered = scanLine2.dup; 991 992 for(int i = 0; i < img.stride; i++) 993 { 994 filtered[i] = cast(ubyte) (scanLine2[i] - paeth(0, scanLine1[i], 0) ); 995 absSum += abs(filtered[i]); 996 } 997 998 for(int i = img.stride; i < scanLine2.length; i++) 999 { 1000 filtered[i] = cast(ubyte) (scanLine2[i] - paeth(scanLine2[i-img.stride], 1001 scanLine1[i], 1002 scanLine1[i-img.stride]) ); 1003 absSum += abs(filtered[i]); 1004 } 1005 return filtered; 1006 } 1007 1008 // Create the header chunk 1009 PNGChunk createHeaderChunk(in Image img) 1010 { 1011 PNGChunk ihdr; 1012 ihdr.type = "IHDR"; 1013 ihdr.data.length = 13; 1014 ihdr.data[0..4] = bigEndianBytes(img.width); 1015 ihdr.data[4..8] = bigEndianBytes(img.height); 1016 ihdr.data[8] = cast(ubyte)img.bitDepth; 1017 ihdr.data[9] = getColorType(img); 1018 ihdr.data[10..12] = [0,0]; 1019 ihdr.data[12] = 0; // non-interlaced 1020 return ihdr; 1021 } 1022 1023 // Append a chunk to the output data, fixing up endianness and calculating checksum 1024 void appendChunk(in PNGChunk chunk, ref ubyte[] data) 1025 { 1026 assert(chunk.type.length == 4); 1027 1028 ubyte[] typeAndData = cast(ubyte[])chunk.type ~ chunk.data; 1029 uint checksum = crc32(0, typeAndData); 1030 1031 data ~= bigEndianBytes(cast(uint)chunk.data.length) ~ 1032 typeAndData ~ 1033 bigEndianBytes(checksum); 1034 } 1035 1036 // Figure out the PNG colortype of an image 1037 ubyte getColorType(in Image img) 1038 { 1039 ubyte colorType; 1040 if (img.pixelFormat == Px.L8 || 1041 img.pixelFormat == Px.L16 ) 1042 { 1043 colorType = 0; 1044 } 1045 else if (img.pixelFormat == Px.L8A8 || 1046 img.pixelFormat == Px.L16A16 ) 1047 { 1048 colorType = 4; 1049 } 1050 else if (img.pixelFormat == Px.R8G8B8 || 1051 img.pixelFormat == Px.R16G16B16 ) 1052 { 1053 colorType = 2; 1054 } 1055 else if (img.pixelFormat == Px.R8G8B8A8 || 1056 img.pixelFormat == Px.R16G16B16A16 ) 1057 { 1058 colorType = 6; 1059 } 1060 else 1061 { 1062 assert(0); 1063 } 1064 return colorType; 1065 } 1066 1067 // Convert a uint to corrct endianness for writing (must be in big endian/network order for writing) 1068 version (LittleEndian) 1069 { 1070 uint bigEndian(uint value) 1071 { 1072 return (value << 24) | ((value & 0x0000FF00) << 8) 1073 | ((value & 0x00FF0000) >> 8) | (value >> 24); 1074 } 1075 } 1076 else 1077 { 1078 uint bigEndian(uint value) { return value; } 1079 } 1080 1081 ubyte[] bigEndianBytes(uint value) 1082 { 1083 uint[] be = [bigEndian(value)]; 1084 return cast(ubyte[])be; 1085 } 1086 1087 } // PngEncoder 1088 1089