1: package ;
2:
3: import ;
4: import ;
5: import ;
6: import ;
7: import ;
8: import ;
9: import ;
10: import ;
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:
252: writeHeader();
253: writeResolution();
254:
255: 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);
445: this.bytePos = writeByte((this.encodeAlpha) ? 6 : 2, this.bytePos);
446:
447: this.bytePos = writeByte(0, this.bytePos);
448: this.bytePos = writeByte(0, this.bytePos);
449: this.bytePos = writeByte(0, this.bytePos);
450: 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;
513: int startRow = 0;
514: int nRows;
515:
516: byte[] scanLines;
517: int scanPos;
518: int startPos;
519:
520:
521: byte[] compressedLines;
522: int nCompressed;
523:
524:
525:
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);
703:
704: crc.reset();
705: crc.update(pngBytes, startPos, bytePos - startPos);
706: crcValue = crc.getValue();
707: bytePos = writeInt4((int) crcValue, bytePos);
708: }
709: }
710: }