1: package ;
2:
3: import ;
4: import ;
5: import ;
6: import ;
7: import ;
8: import ;
9: import ;
10: import ;
11:
12:
55:
56: public class PngEncoder {
57:
58:
59: public static final boolean ENCODE_ALPHA = true;
60:
61:
62: public static final boolean NO_ALPHA = false;
63:
64:
65: public static final int FILTER_NONE = 0;
66:
67:
68: public static final int FILTER_SUB = 1;
69:
70:
71: public static final int FILTER_UP = 2;
72:
73:
74: public static final int FILTER_LAST = 2;
75:
76:
77: protected static final byte[] IHDR = {73, 72, 68, 82};
78:
79:
80: protected static final byte[] IDAT = {73, 68, 65, 84};
81:
82:
83: protected static final byte[] IEND = {73, 69, 78, 68};
84:
85:
86: protected static final byte[] PHYS = {(byte)'p', (byte)'H', (byte)'Y',
87: (byte)'s'};
88:
89:
90: protected byte[] pngBytes;
91:
92:
93: protected byte[] priorRow;
94:
95:
96: protected byte[] leftBytes;
97:
98:
99: protected Image image;
100:
101:
102: protected int width;
103:
104:
105: protected int height;
106:
107:
108: protected int bytePos;
109:
110:
111: protected int maxPos;
112:
113:
114: protected CRC32 crc = new CRC32();
115:
116:
117: protected long crcValue;
118:
119:
120: protected boolean encodeAlpha;
121:
122:
123: protected int filter;
124:
125:
126: protected int bytesPerPixel;
127:
128:
129: private int xDpi = 0;
130:
131:
132: private int yDpi = 0;
133:
134:
135: static private float INCH_IN_METER_UNIT = 0.0254f;
136:
137:
141: protected int compressionLevel;
142:
143:
146: public PngEncoder() {
147: this(null, false, FILTER_NONE, 0);
148: }
149:
150:
157: public PngEncoder(Image image) {
158: this(image, false, FILTER_NONE, 0);
159: }
160:
161:
169: public PngEncoder(Image image, boolean encodeAlpha) {
170: this(image, encodeAlpha, FILTER_NONE, 0);
171: }
172:
173:
182: public PngEncoder(Image image, boolean encodeAlpha, int whichFilter) {
183: this(image, encodeAlpha, whichFilter, 0);
184: }
185:
186:
187:
198: public PngEncoder(Image image, boolean encodeAlpha, int whichFilter,
199: int compLevel) {
200: this.image = image;
201: this.encodeAlpha = encodeAlpha;
202: setFilter(whichFilter);
203: if (compLevel >= 0 && compLevel <= 9) {
204: this.compressionLevel = compLevel;
205: }
206: }
207:
208:
215: public void setImage(Image image) {
216: this.image = image;
217: this.pngBytes = null;
218: }
219:
220:
225: public Image getImage() {
226: return this.image;
227: }
228:
229:
236: public byte[] pngEncode(boolean encodeAlpha) {
237: byte[] pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10};
238:
239: if (this.image == null) {
240: return null;
241: }
242: this.width = this.image.getWidth(null);
243: this.height = this.image.getHeight(null);
244:
245:
249: this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
250:
251:
254: this.maxPos = 0;
255:
256: this.bytePos = writeBytes(pngIdBytes, 0);
257:
258: writeHeader();
259: writeResolution();
260:
261: if (writeImageData()) {
262: writeEnd();
263: this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos);
264: }
265: else {
266: this.pngBytes = null;
267: }
268: return this.pngBytes;
269: }
270:
271:
277: public byte[] pngEncode() {
278: return pngEncode(this.encodeAlpha);
279: }
280:
281:
286: public void setEncodeAlpha(boolean encodeAlpha) {
287: this.encodeAlpha = encodeAlpha;
288: }
289:
290:
295: public boolean getEncodeAlpha() {
296: return this.encodeAlpha;
297: }
298:
299:
304: public void setFilter(int whichFilter) {
305: this.filter = FILTER_NONE;
306: if (whichFilter <= FILTER_LAST) {
307: this.filter = whichFilter;
308: }
309: }
310:
311:
316: public int getFilter() {
317: return this.filter;
318: }
319:
320:
326: public void setCompressionLevel(int level) {
327: if (level >= 0 && level <= 9) {
328: this.compressionLevel = level;
329: }
330: }
331:
332:
337: public int getCompressionLevel() {
338: return this.compressionLevel;
339: }
340:
341:
349: protected byte[] resizeByteArray(byte[] array, int newLength) {
350: byte[] newArray = new byte[newLength];
351: int oldLength = array.length;
352:
353: System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength));
354: return newArray;
355: }
356:
357:
368: protected int writeBytes(byte[] data, int offset) {
369: this.maxPos = Math.max(this.maxPos, offset + data.length);
370: if (data.length + offset > this.pngBytes.length) {
371: this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length
372: + Math.max(1000, data.length));
373: }
374: System.arraycopy(data, 0, this.pngBytes, offset, data.length);
375: return offset + data.length;
376: }
377:
378:
390: protected int writeBytes(byte[] data, int nBytes, int offset) {
391: this.maxPos = Math.max(this.maxPos, offset + nBytes);
392: if (nBytes + offset > this.pngBytes.length) {
393: this.pngBytes = resizeByteArray(this.pngBytes, this.pngBytes.length
394: + Math.max(1000, nBytes));
395: }
396: System.arraycopy(data, 0, this.pngBytes, offset, nBytes);
397: return offset + nBytes;
398: }
399:
400:
407: protected int writeInt2(int n, int offset) {
408: byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)};
409: return writeBytes(temp, offset);
410: }
411:
412:
419: protected int writeInt4(int n, int offset) {
420: byte[] temp = {(byte) ((n >> 24) & 0xff),
421: (byte) ((n >> 16) & 0xff),
422: (byte) ((n >> 8) & 0xff),
423: (byte) (n & 0xff)};
424: return writeBytes(temp, offset);
425: }
426:
427:
434: protected int writeByte(int b, int offset) {
435: byte[] temp = {(byte) b};
436: return writeBytes(temp, offset);
437: }
438:
439:
442: protected void writeHeader() {
443:
444: int startPos = this.bytePos = writeInt4(13, this.bytePos);
445: this.bytePos = writeBytes(IHDR, this.bytePos);
446: this.width = this.image.getWidth(null);
447: this.height = this.image.getHeight(null);
448: this.bytePos = writeInt4(this.width, this.bytePos);
449: this.bytePos = writeInt4(this.height, this.bytePos);
450: this.bytePos = writeByte(8, this.bytePos);
451: this.bytePos = writeByte((this.encodeAlpha) ? 6 : 2, this.bytePos);
452:
453: this.bytePos = writeByte(0, this.bytePos);
454: this.bytePos = writeByte(0, this.bytePos);
455: this.bytePos = writeByte(0, this.bytePos);
456: this.crc.reset();
457: this.crc.update(this.pngBytes, startPos, this.bytePos - startPos);
458: this.crcValue = this.crc.getValue();
459: this.bytePos = writeInt4((int) this.crcValue, this.bytePos);
460: }
461:
462:
472: protected void filterSub(byte[] pixels, int startPos, int width) {
473: int offset = this.bytesPerPixel;
474: int actualStart = startPos + offset;
475: int nBytes = width * this.bytesPerPixel;
476: int leftInsert = offset;
477: int leftExtract = 0;
478:
479: for (int i = actualStart; i < startPos + nBytes; i++) {
480: this.leftBytes[leftInsert] = pixels[i];
481: pixels[i] = (byte) ((pixels[i] - this.leftBytes[leftExtract])
482: % 256);
483: leftInsert = (leftInsert + 1) % 0x0f;
484: leftExtract = (leftExtract + 1) % 0x0f;
485: }
486: }
487:
488:
496: protected void filterUp(byte[] pixels, int startPos, int width) {
497:
498: final int nBytes = width * this.bytesPerPixel;
499:
500: for (int i = 0; i < nBytes; i++) {
501: final byte currentByte = pixels[startPos + i];
502: pixels[startPos + i] = (byte) ((pixels[startPos + i]
503: - this.priorRow[i]) % 256);
504: this.priorRow[i] = currentByte;
505: }
506: }
507:
508:
517: protected boolean writeImageData() {
518: int rowsLeft = this.height;
519: int startRow = 0;
520: int nRows;
521:
522: byte[] scanLines;
523: int scanPos;
524: int startPos;
525:
526:
527: byte[] compressedLines;
528: int nCompressed;
529:
530:
531:
532: PixelGrabber pg;
533:
534: this.bytesPerPixel = (this.encodeAlpha) ? 4 : 3;
535:
536: Deflater scrunch = new Deflater(this.compressionLevel);
537: ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
538:
539: DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes,
540: scrunch);
541: try {
542: while (rowsLeft > 0) {
543: nRows = Math.min(32767 / (this.width
544: * (this.bytesPerPixel + 1)), rowsLeft);
545: nRows = Math.max(nRows, 1);
546:
547: int[] pixels = new int[this.width * nRows];
548:
549: pg = new PixelGrabber(this.image, 0, startRow,
550: this.width, nRows, pixels, 0, this.width);
551: try {
552: pg.grabPixels();
553: }
554: catch (Exception e) {
555: System.err.println("interrupted waiting for pixels!");
556: return false;
557: }
558: if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
559: System.err.println("image fetch aborted or errored");
560: return false;
561: }
562:
563:
567: scanLines = new byte[this.width * nRows * this.bytesPerPixel
568: + nRows];
569:
570: if (this.filter == FILTER_SUB) {
571: this.leftBytes = new byte[16];
572: }
573: if (this.filter == FILTER_UP) {
574: this.priorRow = new byte[this.width * this.bytesPerPixel];
575: }
576:
577: scanPos = 0;
578: startPos = 1;
579: for (int i = 0; i < this.width * nRows; i++) {
580: if (i % this.width == 0) {
581: scanLines[scanPos++] = (byte) this.filter;
582: startPos = scanPos;
583: }
584: scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
585: scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff);
586: scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff);
587: if (this.encodeAlpha) {
588: scanLines[scanPos++] = (byte) ((pixels[i] >> 24)
589: & 0xff);
590: }
591: if ((i % this.width == this.width - 1)
592: && (this.filter != FILTER_NONE)) {
593: if (this.filter == FILTER_SUB) {
594: filterSub(scanLines, startPos, this.width);
595: }
596: if (this.filter == FILTER_UP) {
597: filterUp(scanLines, startPos, this.width);
598: }
599: }
600: }
601:
602:
605: compBytes.write(scanLines, 0, scanPos);
606:
607: startRow += nRows;
608: rowsLeft -= nRows;
609: }
610: compBytes.close();
611:
612:
615: compressedLines = outBytes.toByteArray();
616: nCompressed = compressedLines.length;
617:
618: this.crc.reset();
619: this.bytePos = writeInt4(nCompressed, this.bytePos);
620: this.bytePos = writeBytes(IDAT, this.bytePos);
621: this.crc.update(IDAT);
622: this.bytePos = writeBytes(compressedLines, nCompressed,
623: this.bytePos);
624: this.crc.update(compressedLines, 0, nCompressed);
625:
626: this.crcValue = this.crc.getValue();
627: this.bytePos = writeInt4((int) this.crcValue, this.bytePos);
628: scrunch.finish();
629: scrunch.end();
630: return true;
631: }
632: catch (IOException e) {
633: System.err.println(e.toString());
634: return false;
635: }
636: }
637:
638:
641: protected void writeEnd() {
642: this.bytePos = writeInt4(0, this.bytePos);
643: this.bytePos = writeBytes(IEND, this.bytePos);
644: this.crc.reset();
645: this.crc.update(IEND);
646: this.crcValue = this.crc.getValue();
647: this.bytePos = writeInt4((int) this.crcValue, this.bytePos);
648: }
649:
650:
651:
656: public void setXDpi(int xDpi) {
657: this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT);
658:
659: }
660:
661:
666: public int getXDpi() {
667: return Math.round(this.xDpi * INCH_IN_METER_UNIT);
668: }
669:
670:
675: public void setYDpi(int yDpi) {
676: this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT);
677: }
678:
679:
684: public int getYDpi() {
685: return Math.round(this.yDpi * INCH_IN_METER_UNIT);
686: }
687:
688:
694: public void setDpi(int xDpi, int yDpi) {
695: this.xDpi = Math.round(xDpi / INCH_IN_METER_UNIT);
696: this.yDpi = Math.round(yDpi / INCH_IN_METER_UNIT);
697: }
698:
699:
702: protected void writeResolution() {
703: if (this.xDpi > 0 && this.yDpi > 0) {
704:
705: final int startPos = this.bytePos = writeInt4(9, this.bytePos);
706: this.bytePos = writeBytes(PHYS, this.bytePos);
707: this.bytePos = writeInt4(this.xDpi, this.bytePos);
708: this.bytePos = writeInt4(this.yDpi, this.bytePos);
709: this.bytePos = writeByte(1, this.bytePos);
710:
711: this.crc.reset();
712: this.crc.update(this.pngBytes, startPos, this.bytePos - startPos);
713: this.crcValue = this.crc.getValue();
714: this.bytePos = writeInt4((int) this.crcValue, this.bytePos);
715: }
716: }
717: }