001: package com.keypoint;
002:
003: /**
004: * PngEncoderB takes a Java BufferedImage object and creates a byte string which can be saved as a PNG file.
005: * The encoder will accept BufferedImages with eight-bit samples
006: * or 4-byte ARGB samples.
007: *
008: * There is also code to handle 4-byte samples returned as
009: * one int per pixel, but that has not been tested.
010: *
011: * Thanks to Jay Denny at KeyPoint Software
012: * http://www.keypoint.com/
013: * who let me develop this code on company time.
014: *
015: * You may contact me with (probably very-much-needed) improvements,
016: * comments, and bug fixes at:
017: *
018: * david@catcode.com
019: *
020: * This library is free software; you can redistribute it and/or
021: * modify it under the terms of the GNU Lesser General Public
022: * License as published by the Free Software Foundation; either
023: * version 2.1 of the License, or (at your option) any later version.
024: *
025: * This library is distributed in the hope that it will be useful,
026: * but WITHOUT ANY WARRANTY; without even the implied warranty of
027: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
028: * Lesser General Public License for more details.
029: *
030: * You should have received a copy of the GNU Lesser General Public
031: * License along with this library; if not, write to the Free Software
032: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
033: * A copy of the GNU LGPL may be found at
034: * http://www.gnu.org/copyleft/lesser.html,
035: *
036: * @author J. David Eisenberg
037: * @version 1.4, 31 March 2000
038: */
039:
040: import java.awt.image.BufferedImage;
041: import java.awt.image.DataBuffer;
042: import java.awt.image.IndexColorModel;
043: import java.awt.image.WritableRaster;
044: import java.io.ByteArrayOutputStream;
045: import java.io.IOException;
046: import java.util.zip.Deflater;
047: import java.util.zip.DeflaterOutputStream;
048:
049: public class PngEncoderB extends PngEncoder {
050: protected BufferedImage image;
051: protected WritableRaster wRaster;
052: protected int tType;
053:
054: /**
055: * Class constructor
056: */
057: public PngEncoderB() {
058: this (null, false, FILTER_NONE, 0);
059: }
060:
061: /**
062: * Class constructor specifying BufferedImage to encode, with no alpha channel encoding.
063: *
064: * @param image A Java BufferedImage object
065: */
066: public PngEncoderB(BufferedImage image) {
067: this (image, false, FILTER_NONE, 0);
068: }
069:
070: /**
071: * Class constructor specifying BufferedImage to encode, and whether to encode alpha.
072: *
073: * @param image A Java BufferedImage object
074: * @param encodeAlpha Encode the alpha channel? false=no; true=yes
075: */
076: public PngEncoderB(BufferedImage image, boolean encodeAlpha) {
077: this (image, encodeAlpha, FILTER_NONE, 0);
078: }
079:
080: /**
081: * Class constructor specifying BufferedImage to encode, whether to encode alpha, and filter to use.
082: *
083: * @param image A Java BufferedImage object
084: * @param encodeAlpha Encode the alpha channel? false=no; true=yes
085: * @param whichFilter 0=none, 1=sub, 2=up
086: */
087: public PngEncoderB(BufferedImage image, boolean encodeAlpha,
088: int whichFilter) {
089: this (image, encodeAlpha, whichFilter, 0);
090: }
091:
092: /**
093: * Class constructor specifying BufferedImage source to encode, whether to encode alpha, filter to use, and compression level
094: *
095: * @param image A Java BufferedImage object
096: * @param encodeAlpha Encode the alpha channel? false=no; true=yes
097: * @param whichFilter 0=none, 1=sub, 2=up
098: * @param compLevel 0..9
099: */
100: public PngEncoderB(BufferedImage image, boolean encodeAlpha,
101: int whichFilter, int compLevel) {
102: this .image = image;
103: this .encodeAlpha = encodeAlpha;
104: setFilter(whichFilter);
105: if (compLevel >= 0 && compLevel <= 9) {
106: this .compressionLevel = compLevel;
107: }
108: }
109:
110: /**
111: * Set the BufferedImage to be encoded
112: *
113: * @param image A Java BufferedImage object
114: */
115: public void setImage(BufferedImage image) {
116: this .image = image;
117: pngBytes = null;
118: }
119:
120: /**
121: * Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not.
122: *
123: * @param encodeAlpha boolean false=no alpha, true=encode alpha
124: * @return an array of bytes, or null if there was a problem
125: */
126: public byte[] pngEncode(boolean encodeAlpha) {
127: byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
128: int i;
129:
130: if (image == null) {
131: return null;
132: }
133: width = image.getWidth(null);
134: height = image.getHeight(null);
135: // this.image = image;
136:
137: if (!establishStorageInfo()) {
138: return null;
139: }
140:
141: /*
142: * start with an array that is big enough to hold all the pixels
143: * (plus filter bytes), and an extra 200 bytes for header info
144: */
145: pngBytes = new byte[((width + 1) * height * 3) + 200];
146:
147: /*
148: * keep track of largest byte written to the array
149: */
150: maxPos = 0;
151:
152: bytePos = writeBytes(pngIdBytes, 0);
153: hdrPos = bytePos;
154: writeHeader();
155: dataPos = bytePos;
156: if (writeImageData()) {
157: writeEnd();
158: pngBytes = resizeByteArray(pngBytes, maxPos);
159: } else {
160: pngBytes = null;
161: }
162: return pngBytes;
163: }
164:
165: /**
166: * Creates an array of bytes that is the PNG equivalent of the current image.
167: * Alpha encoding is determined by its setting in the constructor.
168: *
169: * @return an array of bytes, or null if there was a problem
170: */
171: public byte[] pngEncode() {
172: return pngEncode(encodeAlpha);
173: }
174:
175: /**
176: * Get and set variables that determine how picture is stored.
177: * <p/>
178: * Retrieves the writable raster of the buffered image,
179: * as well its transfer type.
180: * <p/>
181: * Sets number of output bytes per pixel, and, if only
182: * eight-bit bytes, turns off alpha encoding.
183: *
184: * @return true if 1-byte or 4-byte data, false otherwise
185: */
186: protected boolean establishStorageInfo() {
187: int dataBytes;
188:
189: wRaster = image.getRaster();
190: dataBytes = wRaster.getNumDataElements();
191: tType = wRaster.getTransferType();
192:
193: if (((tType == DataBuffer.TYPE_BYTE) && (dataBytes == 4))
194: || ((tType == DataBuffer.TYPE_INT) && (dataBytes == 1))) {
195: bytesPerPixel = (encodeAlpha) ? 4 : 3;
196: } else if ((tType == DataBuffer.TYPE_BYTE) && (dataBytes == 1)) {
197: bytesPerPixel = 1;
198: encodeAlpha = false; // one-byte samples
199: } else {
200: return false;
201: }
202: return true;
203: }
204:
205: /**
206: * Write a PNG "IHDR" chunk into the pngBytes array.
207: */
208: protected void writeHeader() {
209: int startPos;
210:
211: startPos = bytePos = writeInt4(13, bytePos);
212: bytePos = writeString("IHDR", bytePos);
213: width = image.getWidth(null);
214: height = image.getHeight(null);
215: bytePos = writeInt4(width, bytePos);
216: bytePos = writeInt4(height, bytePos);
217: bytePos = writeByte(8, bytePos); // bit depth
218: if (bytesPerPixel != 1) {
219: bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model
220: } else {
221: bytePos = writeByte(3, bytePos); // indexed
222: }
223: bytePos = writeByte(0, bytePos); // compression method
224: bytePos = writeByte(0, bytePos); // filter method
225: bytePos = writeByte(0, bytePos); // no interlace
226: crc.reset();
227: crc.update(pngBytes, startPos, bytePos - startPos);
228: crcValue = crc.getValue();
229: bytePos = writeInt4((int) crcValue, bytePos);
230: }
231:
232: protected void writePalette(IndexColorModel icm) {
233: byte[] redPal = new byte[256];
234: byte[] greenPal = new byte[256];
235: byte[] bluePal = new byte[256];
236: byte[] allPal = new byte[768];
237: int i;
238:
239: icm.getReds(redPal);
240: icm.getGreens(greenPal);
241: icm.getBlues(bluePal);
242: for (i = 0; i < 256; i++) {
243: allPal[i * 3] = redPal[i];
244: allPal[i * 3 + 1] = greenPal[i];
245: allPal[i * 3 + 2] = bluePal[i];
246: }
247: bytePos = writeInt4(768, bytePos);
248: bytePos = writeString("PLTE", bytePos);
249: crc.reset();
250: crc.update("PLTE".getBytes());
251: bytePos = writeBytes(allPal, bytePos);
252: crc.update(allPal);
253: crcValue = crc.getValue();
254: bytePos = writeInt4((int) crcValue, bytePos);
255: }
256:
257: /**
258: * Write the image data into the pngBytes array.
259: * This will write one or more PNG "IDAT" chunks. In order
260: * to conserve memory, this method grabs as many rows as will
261: * fit into 32K bytes, or the whole image; whichever is less.
262: *
263: * @return true if no errors; false if error grabbing pixels
264: */
265: protected boolean writeImageData() {
266: int rowsLeft = height; // number of rows remaining to write
267: int startRow = 0; // starting row to process this time through
268: int nRows; // how many rows to grab at a time
269:
270: byte[] scanLines; // the scan lines to be compressed
271: int scanPos; // where we are in the scan lines
272: int startPos; // where this line's actual pixels start (used for filtering)
273: int readPos; // position from which source pixels are read
274:
275: byte[] compressedLines; // the resultant compressed lines
276: int nCompressed; // how big is the compressed area?
277:
278: byte[] pixels; // storage area for byte-sized pixels
279: int[] iPixels; // storage area for int-sized pixels
280:
281: Deflater scrunch = new Deflater(compressionLevel);
282: ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
283:
284: DeflaterOutputStream compBytes = new DeflaterOutputStream(
285: outBytes, scrunch);
286:
287: if (bytesPerPixel == 1) {
288: writePalette((IndexColorModel) image.getColorModel());
289: }
290:
291: try {
292: while (rowsLeft > 0) {
293: nRows = Math.min(32767 / (width * (bytesPerPixel + 1)),
294: rowsLeft);
295: // nRows = rowsLeft;
296:
297: /*
298: * Create a data chunk. scanLines adds "nRows" for
299: * the filter bytes.
300: */
301: scanLines = new byte[width * nRows * bytesPerPixel
302: + nRows];
303:
304: if (filter == FILTER_SUB) {
305: leftBytes = new byte[16];
306: }
307: if (filter == FILTER_UP) {
308: priorRow = new byte[width * bytesPerPixel];
309: }
310:
311: if (tType == DataBuffer.TYPE_BYTE) {
312: pixels = (byte[]) wRaster.getDataElements(0,
313: startRow, width, nRows, null);
314: iPixels = null;
315: } else {
316: iPixels = (int[]) wRaster.getDataElements(0,
317: startRow, width, nRows, null);
318: pixels = null;
319: }
320:
321: scanPos = 0;
322: readPos = 0;
323: startPos = 1;
324: for (int i = 0; i < width * nRows; i++) {
325: if (i % width == 0) {
326: scanLines[scanPos++] = (byte) filter;
327: startPos = scanPos;
328: }
329:
330: if (bytesPerPixel == 1) {
331: scanLines[scanPos++] = pixels[readPos++];
332: } else if (tType == DataBuffer.TYPE_BYTE) {
333: scanLines[scanPos++] = pixels[readPos++];
334: scanLines[scanPos++] = pixels[readPos++];
335: scanLines[scanPos++] = pixels[readPos++];
336: if (encodeAlpha) {
337: scanLines[scanPos++] = pixels[readPos++];
338: } else {
339: readPos++;
340: }
341: } else {
342: scanLines[scanPos++] = (byte) ((iPixels[readPos] >> 16) & 0xff);
343: scanLines[scanPos++] = (byte) ((iPixels[readPos] >> 8) & 0xff);
344: scanLines[scanPos++] = (byte) ((iPixels[readPos]) & 0xff);
345: if (encodeAlpha) {
346: scanLines[scanPos++] = (byte) ((iPixels[readPos] >> 24) & 0xff);
347: }
348: readPos++;
349: }
350: if ((i % width == width - 1)
351: && (filter != FILTER_NONE)) {
352: if (filter == FILTER_SUB) {
353: filterSub(scanLines, startPos, width);
354: }
355: if (filter == FILTER_UP) {
356: filterUp(scanLines, startPos, width);
357: }
358: }
359: }
360:
361: /*
362: * Write these lines to the output area
363: */
364: compBytes.write(scanLines, 0, scanPos);
365:
366: startRow += nRows;
367: rowsLeft -= nRows;
368: }
369: compBytes.close();
370:
371: /*
372: * Write the compressed bytes
373: */
374: compressedLines = outBytes.toByteArray();
375: nCompressed = compressedLines.length;
376:
377: crc.reset();
378: bytePos = writeInt4(nCompressed, bytePos);
379: bytePos = writeString("IDAT", bytePos);
380: crc.update("IDAT".getBytes());
381: bytePos = writeBytes(compressedLines, nCompressed, bytePos);
382: crc.update(compressedLines, 0, nCompressed);
383:
384: crcValue = crc.getValue();
385: bytePos = writeInt4((int) crcValue, bytePos);
386: scrunch.finish();
387: return true;
388: } catch (IOException e) {
389: System.err.println(e.toString());
390: return false;
391: }
392: }
393:
394: }
|