1 package com.keypoint; 2 3 import java.awt.Image ; 4 import java.awt.image.ImageObserver ; 5 import java.awt.image.PixelGrabber ; 6 import java.io.ByteArrayOutputStream ; 7 import java.io.IOException ; 8 import java.util.zip.CRC32 ; 9 import java.util.zip.Deflater ; 10 import java.util.zip.DeflaterOutputStream ; 11 12 53 54 public class PngEncoder { 55 56 57 public static final boolean ENCODE_ALPHA = true; 58 59 60 public static final boolean NO_ALPHA = false; 61 62 63 public static final int FILTER_NONE = 0; 64 65 66 public static final int FILTER_SUB = 1; 67 68 69 public static final int FILTER_UP = 2; 70 71 72 public static final int FILTER_LAST = 2; 73 74 75 protected static final byte[] IHDR = {73, 72, 68, 82}; 76 77 78 protected static final byte[] IDAT = {73, 68, 65, 84}; 79 80 81 protected static final byte[] IEND = {73, 69, 78, 68}; 82 83 protected static final byte[] PHYS = {(byte)'p', (byte)'H', (byte)'Y', (byte)'s'}; 84 85 86 protected byte[] pngBytes; 87 88 89 protected byte[] priorRow; 90 91 92 protected byte[] leftBytes; 93 94 95 protected Image image; 96 97 98 protected int width; 99 100 101 protected int height; 102 103 104 protected int bytePos; 105 106 107 protected int maxPos; 108 109 110 protected CRC32 crc = new CRC32 (); 111 112 113 protected long crcValue; 114 115 116 protected boolean encodeAlpha; 117 118 119 protected int filter; 120 121 122 protected int bytesPerPixel; 123 124 125 private int xDpi = 0; 126 127 128 private int yDpi = 0; 129 130 131 static private float INCH_IN_METER_UNIT = 0.0254f; 132 133 137 protected int compressionLevel; 138 139 142 public PngEncoder() { 143 this(null, false, FILTER_NONE, 0); 144 } 145 146 153 public PngEncoder(Image image) { 154 this(image, false, FILTER_NONE, 0); 155 } 156 157 165 public PngEncoder(Image image, boolean encodeAlpha) { 166 this(image, encodeAlpha, FILTER_NONE, 0); 167 } 168 169 178 public PngEncoder(Image image, boolean encodeAlpha, int whichFilter) { 179 this(image, encodeAlpha, whichFilter, 0); 180 } 181 182 183 194 public PngEncoder(Image image, boolean encodeAlpha, int whichFilter, 195 int compLevel) { 196 this.image = image; 197 this.encodeAlpha = encodeAlpha; 198 setFilter(whichFilter); 199 if (compLevel >= 0 && compLevel <= 9) { 200 this.compressionLevel = compLevel; 201 } 202 } 203 204 211 public void setImage(Image image) { 212 this.image = image; 213 this.pngBytes = null; 214 } 215 216 219 public Image getImage() { 220 return image; 221 } 222 223 230 public byte[] pngEncode(boolean encodeAlpha) { 231 byte[] pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10}; 232 233 if (this.image == null) { 234 return null; 235 } 236 this.width = this.image.getWidth(null); 237 this.height = this.image.getHeight(null); 238 239 243 this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200]; 244 245 248 this.maxPos = 0; 249 250 this.bytePos = writeBytes(pngIdBytes, 0); 251 writeHeader(); 253 writeResolution(); 254 if (writeImageData()) { 256 writeEnd(); 257 this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos); 258 } 259 else { 260 this.pngBytes = null; 261 } 262 return this.pngBytes; 263 } 264 265 271 public byte[] pngEncode() { 272 return pngEncode(this.encodeAlpha); 273 } 274 275 280 public void setEncodeAlpha(boolean encodeAlpha) { 281 this.encodeAlpha = encodeAlpha; 282 } 283 284 289 public boolean getEncodeAlpha() { 290 return this.encodeAlpha; 291 } 292 293 298 public void setFilter(int whichFilter) { 299 this.filter = FILTER_NONE; 300 if (whichFilter <= FILTER_LAST) { 301 this.filter = whichFilter; 302 } 303 } 304 305 310 public int getFilter() { 311 return this.filter; 312 } 313 314 320 public void setCompressionLevel(int level) { 321 if (level >= 0 && level <= 9) { 322 this.compressionLevel = level; 323 } 324 } 325 326 331 public int getCompressionLevel() { 332 return this.compressionLevel; 333 } 334 335 343 protected byte[] resizeByteArray(byte[] array, int newLength) { 344 byte[] newArray = new byte[newLength]; 345 int oldLength = array.length; 346 347 System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength)); 348 return newArray; 349 } 350 351 362 protected int writeBytes(byte[] data, int offset) { 363 this.maxPos = Math.max(this.maxPos, offset + data.length); 364 if (data.length + offset > this.pngBytes.length) { 365 this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length 366 + Math.max(1000, data.length)); 367 } 368 System.arraycopy(data, 0, this.pngBytes, offset, data.length); 369 return offset + data.length; 370 } 371 372 384 protected int writeBytes(byte[] data, int nBytes, int offset) { 385 this.maxPos = Math.max(this.maxPos, offset + nBytes); 386 if (nBytes + offset > this.pngBytes.length) { 387 this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length 388 + Math.max(1000, nBytes)); 389 } 390 System.arraycopy(data, 0, this.pngBytes, offset, nBytes); 391 return offset + nBytes; 392 } 393 394 401 protected int writeInt2(int n, int offset) { 402 byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; 403 return writeBytes(temp, offset); 404 } 405 406 413 protected int writeInt4(int n, int offset) { 414 byte[] temp = {(byte) ((n >> 24) & 0xff), 415 (byte) ((n >> 16) & 0xff), 416 (byte) ((n >> 8) & 0xff), 417 (byte) (n & 0xff)}; 418 return writeBytes(temp, offset); 419 } 420 421 428 protected int writeByte(int b, int offset) { 429 byte[] temp = {(byte) b}; 430 return writeBytes(temp, offset); 431 } 432 433 436 protected void writeHeader() { 437 438 int startPos = this.bytePos = writeInt4(13, this.bytePos); 439 this.bytePos = writeBytes(IHDR, this.bytePos); 440 this.width = this.image.getWidth(null); 441 this.height = this.image.getHeight(null); 442 this.bytePos = writeInt4(this.width, this.bytePos); 443 this.bytePos = writeInt4(this.height, this.bytePos); 444 this.bytePos = writeByte(8, this.bytePos); this.bytePos = writeByte((this.encodeAlpha) ? 6 : 2, this.bytePos); 446 this.bytePos = writeByte(0, this.bytePos); this.bytePos = writeByte(0, this.bytePos); this.bytePos = writeByte(0, this.bytePos); this.crc.reset(); 451 this.crc.update(this.pngBytes, startPos, this.bytePos - startPos); 452 this.crcValue = this.crc.getValue(); 453 this.bytePos = writeInt4((int) this.crcValue, this.bytePos); 454 } 455 456 466 protected void filterSub(byte[] pixels, int startPos, int width) { 467 int offset = this.bytesPerPixel; 468 int actualStart = startPos + offset; 469 int nBytes = width * this.bytesPerPixel; 470 int leftInsert = offset; 471 int leftExtract = 0; 472 473 for (int i = actualStart; i < startPos + nBytes; i++) { 474 this.leftBytes[leftInsert] = pixels[i]; 475 pixels[i] = (byte) ((pixels[i] - this.leftBytes[leftExtract]) 476 % 256); 477 leftInsert = (leftInsert + 1) % 0x0f; 478 leftExtract = (leftExtract + 1) % 0x0f; 479 } 480 } 481 482 490 protected void filterUp(byte[] pixels, int startPos, int width) { 491 492 final int nBytes = width * this.bytesPerPixel; 493 494 for (int i = 0; i < nBytes; i++) { 495 final byte currentByte = pixels[startPos + i]; 496 pixels[startPos + i] = (byte) ((pixels[startPos + i] 497 - this.priorRow[i]) % 256); 498 this.priorRow[i] = currentByte; 499 } 500 } 501 502 511 protected boolean writeImageData() { 512 int rowsLeft = this.height; int startRow = 0; int nRows; 516 byte[] scanLines; int scanPos; int startPos; 521 byte[] compressedLines; int nCompressed; 524 526 PixelGrabber pg; 527 528 this.bytesPerPixel = (this.encodeAlpha) ? 4 : 3; 529 530 Deflater scrunch = new Deflater (this.compressionLevel); 531 ByteArrayOutputStream outBytes = new ByteArrayOutputStream (1024); 532 533 DeflaterOutputStream compBytes = new DeflaterOutputStream (outBytes, 534 scrunch); 535 try { 536 while (rowsLeft > 0) { 537 nRows = Math.min(32767 / (this.width 538 * (this.bytesPerPixel + 1)), rowsLeft); 539 nRows = Math.max(nRows, 1); 540 541 int[] pixels = new int[this.width * nRows]; 542 543 pg = new PixelGrabber (this.image, 0, startRow, 544 this.width, nRows, pixels, 0, this.width); 545 try { 546 pg.grabPixels(); 547 } 548 catch (Exception e) { 549 System.err.println("interrupted waiting for pixels!"); 550 return false; 551 } 552 if ((pg.getStatus() & ImageObserver.ABORT) != 0) { 553 System.err.println("image fetch aborted or errored"); 554 return false; 555 } 556 557 561 scanLines = new byte[this.width * nRows * this.bytesPerPixel 562 + nRows]; 563 564 if (this.filter == FILTER_SUB) { 565 this.leftBytes = new byte[16]; 566 } 567 if (this.filter == FILTER_UP) { 568 this.priorRow = new byte[this.width * this.bytesPerPixel]; 569 } 570 571 scanPos = 0; 572 startPos = 1; 573 for (int i = 0; i < this.width * nRows; i++) { 574 if (i % this.width == 0) { 575 scanLines[scanPos++] = (byte) this.filter; 576 startPos = scanPos; 577 } 578 scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff); 579 scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff); 580 scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff); 581 if (this.encodeAlpha) { 582 scanLines[scanPos++] = (byte) ((pixels[i] >> 24) 583 & 0xff); 584 } 585 if ((i % this.width == this.width - 1) 586 && (this.filter != FILTER_NONE)) { 587 if (this.filter == FILTER_SUB) { 588 filterSub(scanLines, startPos, this.width); 589 } 590 if (this.filter == FILTER_UP) { 591 filterUp(scanLines, startPos, this.width); 592 } 593 } 594 } 595 596 599 compBytes.write(scanLines, 0, scanPos); 600 601 startRow += nRows; 602 rowsLeft -= nRows; 603 } 604 compBytes.close(); 605 606 609 compressedLines = outBytes.toByteArray(); 610 nCompressed = compressedLines.length; 611 612 this.crc.reset(); 613 this.bytePos = writeInt4(nCompressed, this.bytePos); 614 this.bytePos = writeBytes(IDAT, this.bytePos); 615 this.crc.update(IDAT); 616 this.bytePos = writeBytes(compressedLines, nCompressed, 617 this.bytePos); 618 this.crc.update(compressedLines, 0, nCompressed); 619 620 this.crcValue = this.crc.getValue(); 621 this.bytePos = writeInt4((int) this.crcValue, this.bytePos); 622 scrunch.finish(); 623 return true; 624 } 625 catch (IOException e) { 626 System.err.println(e.toString()); 627 return false; 628 } 629 } 630 631 634 protected void writeEnd() { 635 this.bytePos = writeInt4(0, this.bytePos); 636 this.bytePos = writeBytes(IEND, this.bytePos); 637 this.crc.reset(); 638 this.crc.update(IEND); 639 this.crcValue = this.crc.getValue(); 640 this.bytePos = writeInt4((int) this.crcValue, this.bytePos); 641 } 642 643 644 649 public void setXDpi(int xDpi) { 650 this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT); 651 652 } 653 654 659 public int getXDpi() { 660 return Math.round(xDpi * INCH_IN_METER_UNIT); 661 } 662 663 668 public void setYDpi(int yDpi) { 669 this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT); 670 } 671 672 677 public int getYDpi() { 678 return Math.round(yDpi * INCH_IN_METER_UNIT); 679 } 680 681 687 public void setDpi(int xDpi, int yDpi) { 688 this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT); 689 this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT); 690 } 691 692 695 protected void writeResolution() { 696 if (xDpi > 0 && yDpi > 0) { 697 698 final int startPos = bytePos = writeInt4(9, bytePos); 699 bytePos = writeBytes(PHYS, bytePos); 700 bytePos = writeInt4(xDpi, bytePos); 701 bytePos = writeInt4(yDpi, bytePos); 702 bytePos = writeByte(1, bytePos); 704 crc.reset(); 705 crc.update(pngBytes, startPos, bytePos - startPos); 706 crcValue = crc.getValue(); 707 bytePos = writeInt4((int) crcValue, bytePos); 708 } 709 } 710 } 711 | Popular Tags |