0001: /*
0002: * $RCSfile: PNGImageEncoder.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.1 $
0009: * $Date: 2005/02/11 04:55:37 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.codecimpl;
0013:
0014: import java.awt.image.IndexColorModel;
0015: import java.awt.image.ColorModel;
0016: import java.awt.image.Raster;
0017: import java.awt.image.RenderedImage;
0018: import java.awt.image.SampleModel;
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.DataOutput;
0021: import java.io.DataOutputStream;
0022: import java.io.FileOutputStream;
0023: import java.io.FilterOutputStream;
0024: import java.io.IOException;
0025: import java.io.OutputStream;
0026: import java.util.Calendar;
0027: import java.util.Date;
0028: import java.util.GregorianCalendar;
0029: import java.util.TimeZone;
0030: import java.util.zip.Deflater;
0031: import java.util.zip.DeflaterOutputStream;
0032: import com.sun.media.jai.codec.FileSeekableStream;
0033: import com.sun.media.jai.codec.ImageCodec;
0034: import com.sun.media.jai.codec.ImageDecoder;
0035: import com.sun.media.jai.codec.ImageEncoder;
0036: import com.sun.media.jai.codec.ImageEncoderImpl;
0037: import com.sun.media.jai.codec.PNGDecodeParam;
0038: import com.sun.media.jai.codec.PNGEncodeParam;
0039: import com.sun.media.jai.codec.SeekableStream;
0040:
0041: class CRC {
0042:
0043: private static int[] crcTable = new int[256];
0044:
0045: static {
0046: // Initialize CRC table
0047: for (int n = 0; n < 256; n++) {
0048: int c = n;
0049: for (int k = 0; k < 8; k++) {
0050: if ((c & 1) == 1) {
0051: c = 0xedb88320 ^ (c >>> 1);
0052: } else {
0053: c >>>= 1;
0054: }
0055:
0056: crcTable[n] = c;
0057: }
0058: }
0059: }
0060:
0061: public static int updateCRC(int crc, byte[] data, int off, int len) {
0062: int c = crc;
0063:
0064: for (int n = 0; n < len; n++) {
0065: c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
0066: }
0067:
0068: return c;
0069: }
0070: }
0071:
0072: class ChunkStream extends OutputStream implements DataOutput {
0073:
0074: private String type;
0075: private ByteArrayOutputStream baos;
0076: private DataOutputStream dos;
0077:
0078: public ChunkStream(String type) throws IOException {
0079: this .type = type;
0080:
0081: this .baos = new ByteArrayOutputStream();
0082: this .dos = new DataOutputStream(baos);
0083: }
0084:
0085: public void write(byte[] b) throws IOException {
0086: dos.write(b);
0087: }
0088:
0089: public void write(byte[] b, int off, int len) throws IOException {
0090: dos.write(b, off, len);
0091: }
0092:
0093: public void write(int b) throws IOException {
0094: dos.write(b);
0095: }
0096:
0097: public void writeBoolean(boolean v) throws IOException {
0098: dos.writeBoolean(v);
0099: }
0100:
0101: public void writeByte(int v) throws IOException {
0102: dos.writeByte(v);
0103: }
0104:
0105: public void writeBytes(String s) throws IOException {
0106: dos.writeBytes(s);
0107: }
0108:
0109: public void writeChar(int v) throws IOException {
0110: dos.writeChar(v);
0111: }
0112:
0113: public void writeChars(String s) throws IOException {
0114: dos.writeChars(s);
0115: }
0116:
0117: public void writeDouble(double v) throws IOException {
0118: dos.writeDouble(v);
0119: }
0120:
0121: public void writeFloat(float v) throws IOException {
0122: dos.writeFloat(v);
0123: }
0124:
0125: public void writeInt(int v) throws IOException {
0126: dos.writeInt(v);
0127: }
0128:
0129: public void writeLong(long v) throws IOException {
0130: dos.writeLong(v);
0131: }
0132:
0133: public void writeShort(int v) throws IOException {
0134: dos.writeShort(v);
0135: }
0136:
0137: public void writeUTF(String str) throws IOException {
0138: dos.writeUTF(str);
0139: }
0140:
0141: public void writeToStream(DataOutputStream output)
0142: throws IOException {
0143: byte[] typeSignature = new byte[4];
0144: typeSignature[0] = (byte) type.charAt(0);
0145: typeSignature[1] = (byte) type.charAt(1);
0146: typeSignature[2] = (byte) type.charAt(2);
0147: typeSignature[3] = (byte) type.charAt(3);
0148:
0149: dos.flush();
0150: baos.flush();
0151:
0152: byte[] data = baos.toByteArray();
0153: int len = data.length;
0154:
0155: output.writeInt(len);
0156: output.write(typeSignature);
0157: output.write(data, 0, len);
0158:
0159: int crc = 0xffffffff;
0160: crc = CRC.updateCRC(crc, typeSignature, 0, 4);
0161: crc = CRC.updateCRC(crc, data, 0, len);
0162: output.writeInt(crc ^ 0xffffffff);
0163: }
0164: }
0165:
0166: class IDATOutputStream extends FilterOutputStream {
0167:
0168: private static final byte[] typeSignature = { (byte) 'I',
0169: (byte) 'D', (byte) 'A', (byte) 'T' };
0170:
0171: private int bytesWritten = 0;
0172: private int segmentLength;
0173: byte[] buffer;
0174:
0175: public IDATOutputStream(OutputStream output, int segmentLength) {
0176: super (output);
0177: this .segmentLength = segmentLength;
0178: this .buffer = new byte[segmentLength];
0179: }
0180:
0181: public void close() throws IOException {
0182: flush();
0183: }
0184:
0185: private void writeInt(int x) throws IOException {
0186: out.write(x >> 24);
0187: out.write((x >> 16) & 0xff);
0188: out.write((x >> 8) & 0xff);
0189: out.write(x & 0xff);
0190: }
0191:
0192: public void flush() throws IOException {
0193: // Length
0194: writeInt(bytesWritten);
0195: // 'IDAT' signature
0196: out.write(typeSignature);
0197: // Data
0198: out.write(buffer, 0, bytesWritten);
0199:
0200: int crc = 0xffffffff;
0201: crc = CRC.updateCRC(crc, typeSignature, 0, 4);
0202: crc = CRC.updateCRC(crc, buffer, 0, bytesWritten);
0203:
0204: // CRC
0205: writeInt(crc ^ 0xffffffff);
0206:
0207: // Reset buffer
0208: bytesWritten = 0;
0209: }
0210:
0211: public void write(byte[] b) throws IOException {
0212: this .write(b, 0, b.length);
0213: }
0214:
0215: public void write(byte[] b, int off, int len) throws IOException {
0216: while (len > 0) {
0217: int bytes = Math.min(segmentLength - bytesWritten, len);
0218: System.arraycopy(b, off, buffer, bytesWritten, bytes);
0219: off += bytes;
0220: len -= bytes;
0221: bytesWritten += bytes;
0222:
0223: if (bytesWritten == segmentLength) {
0224: flush();
0225: }
0226: }
0227: }
0228:
0229: public void write(int b) throws IOException {
0230: buffer[bytesWritten++] = (byte) b;
0231: if (bytesWritten == segmentLength) {
0232: flush();
0233: }
0234: }
0235: }
0236:
0237: /**
0238: * An ImageEncoder for the PNG file format.
0239: *
0240: * @since EA4
0241: */
0242: public class PNGImageEncoder extends ImageEncoderImpl {
0243:
0244: private static final int PNG_COLOR_GRAY = 0;
0245: private static final int PNG_COLOR_RGB = 2;
0246: private static final int PNG_COLOR_PALETTE = 3;
0247: private static final int PNG_COLOR_GRAY_ALPHA = 4;
0248: private static final int PNG_COLOR_RGB_ALPHA = 6;
0249:
0250: private static final byte[] magic = { (byte) 137, (byte) 80,
0251: (byte) 78, (byte) 71, (byte) 13, (byte) 10, (byte) 26,
0252: (byte) 10 };
0253:
0254: private static int filterPrintableLatin1(byte[] data) {
0255: int len = 0;
0256: int prev = 0;
0257: for (int i = 0; i < data.length; i++) {
0258: int d = data[i] & 0xFF;
0259: if (prev == 32 && d == 32)
0260: continue;
0261: if ((d > 32 && d <= 126) || (d >= 161 && d <= 255))
0262: data[len++] = (byte) d;
0263: prev = d;
0264: }
0265: return len;
0266: }
0267:
0268: private PNGEncodeParam param;
0269:
0270: private RenderedImage image;
0271: private int width;
0272: private int height;
0273: private int bitDepth;
0274: private int bitShift;
0275: private int numBands;
0276: private int colorType;
0277:
0278: private int bpp; // bytes per pixel, rounded up
0279:
0280: private boolean skipAlpha = false;
0281: private boolean compressGray = false;
0282:
0283: private boolean interlace;
0284:
0285: private byte[] redPalette = null;
0286: private byte[] greenPalette = null;
0287: private byte[] bluePalette = null;
0288: private byte[] alphaPalette = null;
0289:
0290: private DataOutputStream dataOutput;
0291:
0292: public PNGImageEncoder(OutputStream output, PNGEncodeParam param) {
0293: super (output, param);
0294:
0295: if (param != null) {
0296: this .param = (PNGEncodeParam) param;
0297: }
0298: this .dataOutput = new DataOutputStream(output);
0299: }
0300:
0301: private void writeMagic() throws IOException {
0302: dataOutput.write(magic);
0303: }
0304:
0305: private void writeIHDR() throws IOException {
0306: ChunkStream cs = new ChunkStream("IHDR");
0307: cs.writeInt(width);
0308: cs.writeInt(height);
0309: cs.writeByte((byte) bitDepth);
0310: cs.writeByte((byte) colorType);
0311: cs.writeByte((byte) 0);
0312: cs.writeByte((byte) 0);
0313: cs.writeByte(interlace ? (byte) 1 : (byte) 0);
0314:
0315: cs.writeToStream(dataOutput);
0316: }
0317:
0318: private byte[] prevRow = null;
0319: private byte[] currRow = null;
0320:
0321: private byte[][] filteredRows = null;
0322:
0323: private static int clamp(int val, int maxValue) {
0324: return (val > maxValue) ? maxValue : val;
0325: }
0326:
0327: private void encodePass(OutputStream os, Raster ras, int xOffset,
0328: int yOffset, int xSkip, int ySkip) throws IOException {
0329: int minX = ras.getMinX();
0330: int minY = ras.getMinY();
0331: int width = ras.getWidth();
0332: int height = ras.getHeight();
0333:
0334: xOffset *= numBands;
0335: xSkip *= numBands;
0336:
0337: int samplesPerByte = 8 / bitDepth;
0338:
0339: int numSamples = width * numBands;
0340: int[] samples = new int[numSamples];
0341:
0342: int pixels = (numSamples - xOffset + xSkip - 1) / xSkip;
0343: int bytesPerRow = pixels * numBands;
0344: if (bitDepth < 8) {
0345: bytesPerRow = (bytesPerRow + samplesPerByte - 1)
0346: / samplesPerByte;
0347: } else if (bitDepth == 16) {
0348: bytesPerRow *= 2;
0349: }
0350:
0351: if (bytesPerRow == 0) {
0352: return;
0353: }
0354:
0355: currRow = new byte[bytesPerRow + bpp];
0356: prevRow = new byte[bytesPerRow + bpp];
0357:
0358: filteredRows = new byte[5][bytesPerRow + bpp];
0359:
0360: int maxValue = (1 << bitDepth) - 1;
0361:
0362: for (int row = minY + yOffset; row < minY + height; row += ySkip) {
0363: ras.getPixels(minX, row, width, 1, samples);
0364:
0365: if (compressGray) {
0366: int shift = 8 - bitDepth;
0367: for (int i = 0; i < width; i++) {
0368: samples[i] >>= shift;
0369: }
0370: }
0371:
0372: int count = bpp; // leave first 'bpp' bytes zero
0373: int pos = 0;
0374: int tmp = 0;
0375:
0376: switch (bitDepth) {
0377: case 1:
0378: case 2:
0379: case 4:
0380: // Image can only have a single band
0381:
0382: int mask = samplesPerByte - 1;
0383: for (int s = xOffset; s < numSamples; s += xSkip) {
0384: int val = clamp(samples[s] >> bitShift, maxValue);
0385: tmp = (tmp << bitDepth) | val;
0386:
0387: if ((pos++ & mask) == mask) {
0388: currRow[count++] = (byte) tmp;
0389: tmp = 0;
0390: }
0391: }
0392:
0393: // Left shift the last byte
0394: if ((pos & mask) != 0) {
0395: // Fix 4655018: PNGImageEncoder doesn't correctly write some
0396: // bilevel images.
0397: // modify "pos" to "pos & mask" in the sentence below.
0398: tmp <<= (8 / bitDepth - (pos & mask)) * bitDepth;
0399: currRow[count++] = (byte) tmp;
0400: }
0401: break;
0402:
0403: case 8:
0404: for (int s = xOffset; s < numSamples; s += xSkip) {
0405: for (int b = 0; b < numBands; b++) {
0406: currRow[count++] = (byte) clamp(
0407: samples[s + b] >> bitShift, maxValue);
0408: }
0409: }
0410: break;
0411:
0412: case 16:
0413: for (int s = xOffset; s < numSamples; s += xSkip) {
0414: for (int b = 0; b < numBands; b++) {
0415: int val = clamp(samples[s + b] >> bitShift,
0416: maxValue);
0417: currRow[count++] = (byte) (val >> 8);
0418: currRow[count++] = (byte) (val & 0xff);
0419: }
0420: }
0421: break;
0422: }
0423:
0424: // Perform filtering
0425: int filterType = param.filterRow(currRow, prevRow,
0426: filteredRows, bytesPerRow, bpp);
0427:
0428: os.write(filterType);
0429: os.write(filteredRows[filterType], bpp, bytesPerRow);
0430:
0431: // Swap current and previous rows
0432: byte[] swap = currRow;
0433: currRow = prevRow;
0434: prevRow = swap;
0435: }
0436: }
0437:
0438: private void writeIDAT() throws IOException {
0439: IDATOutputStream ios = new IDATOutputStream(dataOutput, 8192);
0440: DeflaterOutputStream dos = new DeflaterOutputStream(ios,
0441: new Deflater(9));
0442:
0443: // Future work - don't convert entire image to a Raster
0444: Raster ras = image.getData();
0445:
0446: if (skipAlpha) {
0447: int numBands = ras.getNumBands() - 1;
0448: int[] bandList = new int[numBands];
0449: for (int i = 0; i < numBands; i++) {
0450: bandList[i] = i;
0451: }
0452: ras = ras.createChild(0, 0, ras.getWidth(),
0453: ras.getHeight(), 0, 0, bandList);
0454: }
0455:
0456: if (interlace) {
0457: // Interlacing pass 1
0458: encodePass(dos, ras, 0, 0, 8, 8);
0459: // Interlacing pass 2
0460: encodePass(dos, ras, 4, 0, 8, 8);
0461: // Interlacing pass 3
0462: encodePass(dos, ras, 0, 4, 4, 8);
0463: // Interlacing pass 4
0464: encodePass(dos, ras, 2, 0, 4, 4);
0465: // Interlacing pass 5
0466: encodePass(dos, ras, 0, 2, 2, 4);
0467: // Interlacing pass 6
0468: encodePass(dos, ras, 1, 0, 2, 2);
0469: // Interlacing pass 7
0470: encodePass(dos, ras, 0, 1, 1, 2);
0471: } else {
0472: encodePass(dos, ras, 0, 0, 1, 1);
0473: }
0474:
0475: dos.finish();
0476: ios.flush();
0477: }
0478:
0479: private void writeIEND() throws IOException {
0480: ChunkStream cs = new ChunkStream("IEND");
0481: cs.writeToStream(dataOutput);
0482: }
0483:
0484: private static final float[] srgbChroma = { 0.31270F, 0.329F,
0485: 0.64F, 0.33F, 0.3F, 0.6F, 0.15F, 0.06F };
0486:
0487: private void writeCHRM() throws IOException {
0488: if (param.isChromaticitySet() || param.isSRGBIntentSet()) {
0489: ChunkStream cs = new ChunkStream("cHRM");
0490:
0491: float[] chroma;
0492: if (!param.isSRGBIntentSet()) {
0493: chroma = param.getChromaticity();
0494: } else {
0495: chroma = srgbChroma; // SRGB chromaticities
0496: }
0497:
0498: for (int i = 0; i < 8; i++) {
0499: cs.writeInt((int) (chroma[i] * 100000));
0500: }
0501: cs.writeToStream(dataOutput);
0502: }
0503: }
0504:
0505: private void writeGAMA() throws IOException {
0506: if (param.isGammaSet() || param.isSRGBIntentSet()) {
0507: ChunkStream cs = new ChunkStream("gAMA");
0508:
0509: float gamma;
0510: if (!param.isSRGBIntentSet()) {
0511: gamma = param.getGamma();
0512: } else {
0513: gamma = 1.0F / 2.2F; // SRGB gamma
0514: }
0515:
0516: cs.writeInt((int) (gamma * 100000));
0517: cs.writeToStream(dataOutput);
0518: }
0519: }
0520:
0521: private void writeICCP() throws IOException {
0522: if (param.isICCProfileDataSet()) {
0523: ChunkStream cs = new ChunkStream("iCCP");
0524: String name = param.getICCProfileName();
0525: if (name == null || name.length() < 1) {
0526: name = "JAI-Placed Profile";
0527: } else {
0528: name = name.trim();
0529: if (name.length() > 79) {
0530: name = name.substring(0, 79);
0531: }
0532: }
0533: // PNG actually only allows printable Latin 1
0534: // characters (33-126 and 161-255) and spaces (32),
0535: //but no leading, trailing, or consecutive spaces.
0536: byte[] ICCProfileName = name.getBytes("ISO-8859-1");
0537: int length = filterPrintableLatin1(ICCProfileName);
0538:
0539: // load the actual profile as bytes
0540: byte[] ICCProfileData = param.getICCProfileData();
0541: ByteArrayOutputStream iccDflStream = new ByteArrayOutputStream(
0542: ICCProfileData.length);
0543: // compress (deflate) the profile
0544: DeflaterOutputStream dfl = new DeflaterOutputStream(
0545: iccDflStream);
0546: dfl.write(ICCProfileData);
0547: dfl.finish();
0548:
0549: // write name
0550: cs.write(ICCProfileName, 0, length);
0551: // write null delimiter
0552: cs.writeByte(0);
0553: // write compression type (always 0)
0554: cs.writeByte(0);
0555: // write ICC data
0556: cs.write(iccDflStream.toByteArray());
0557: dfl.close();
0558:
0559: cs.writeToStream(dataOutput);
0560: }
0561: }
0562:
0563: private void writeSBIT() throws IOException {
0564: if (param.isSignificantBitsSet()) {
0565: ChunkStream cs = new ChunkStream("sBIT");
0566: int[] significantBits = param.getSignificantBits();
0567: int len = significantBits.length;
0568: for (int i = 0; i < len; i++) {
0569: cs.writeByte(significantBits[i]);
0570: }
0571: cs.writeToStream(dataOutput);
0572: }
0573: }
0574:
0575: private void writeSRGB() throws IOException {
0576: if (param.isSRGBIntentSet()) {
0577: ChunkStream cs = new ChunkStream("sRGB");
0578:
0579: int intent = param.getSRGBIntent();
0580: cs.write(intent);
0581: cs.writeToStream(dataOutput);
0582: }
0583: }
0584:
0585: private void writePLTE() throws IOException {
0586: if (redPalette == null) {
0587: return;
0588: }
0589:
0590: ChunkStream cs = new ChunkStream("PLTE");
0591: for (int i = 0; i < redPalette.length; i++) {
0592: cs.writeByte(redPalette[i]);
0593: cs.writeByte(greenPalette[i]);
0594: cs.writeByte(bluePalette[i]);
0595: }
0596:
0597: cs.writeToStream(dataOutput);
0598: }
0599:
0600: private void writeBKGD() throws IOException {
0601: if (param.isBackgroundSet()) {
0602: ChunkStream cs = new ChunkStream("bKGD");
0603:
0604: switch (colorType) {
0605: case PNG_COLOR_GRAY:
0606: case PNG_COLOR_GRAY_ALPHA:
0607: int gray = ((PNGEncodeParam.Gray) param)
0608: .getBackgroundGray();
0609: cs.writeShort(gray);
0610: break;
0611:
0612: case PNG_COLOR_PALETTE:
0613: int index = ((PNGEncodeParam.Palette) param)
0614: .getBackgroundPaletteIndex();
0615: cs.writeByte(index);
0616: break;
0617:
0618: case PNG_COLOR_RGB:
0619: case PNG_COLOR_RGB_ALPHA:
0620: int[] rgb = ((PNGEncodeParam.RGB) param)
0621: .getBackgroundRGB();
0622: cs.writeShort(rgb[0]);
0623: cs.writeShort(rgb[1]);
0624: cs.writeShort(rgb[2]);
0625: break;
0626: }
0627:
0628: cs.writeToStream(dataOutput);
0629: }
0630: }
0631:
0632: private void writeHIST() throws IOException {
0633: if (param.isPaletteHistogramSet()) {
0634: ChunkStream cs = new ChunkStream("hIST");
0635:
0636: int[] hist = param.getPaletteHistogram();
0637: for (int i = 0; i < hist.length; i++) {
0638: cs.writeShort(hist[i]);
0639: }
0640:
0641: cs.writeToStream(dataOutput);
0642: }
0643: }
0644:
0645: private void writeTRNS() throws IOException {
0646: if (param.isTransparencySet()
0647: && (colorType != PNG_COLOR_GRAY_ALPHA)
0648: && (colorType != PNG_COLOR_RGB_ALPHA)) {
0649: ChunkStream cs = new ChunkStream("tRNS");
0650:
0651: if (param instanceof PNGEncodeParam.Palette) {
0652: byte[] t = ((PNGEncodeParam.Palette) param)
0653: .getPaletteTransparency();
0654: for (int i = 0; i < t.length; i++) {
0655: cs.writeByte(t[i]);
0656: }
0657: } else if (param instanceof PNGEncodeParam.Gray) {
0658: int t = ((PNGEncodeParam.Gray) param)
0659: .getTransparentGray();
0660: cs.writeShort(t);
0661: } else if (param instanceof PNGEncodeParam.RGB) {
0662: int[] t = ((PNGEncodeParam.RGB) param)
0663: .getTransparentRGB();
0664: cs.writeShort(t[0]);
0665: cs.writeShort(t[1]);
0666: cs.writeShort(t[2]);
0667: }
0668:
0669: cs.writeToStream(dataOutput);
0670: } else if (colorType == PNG_COLOR_PALETTE) {
0671: int lastEntry = Math.min(255, alphaPalette.length - 1);
0672: int nonOpaque;
0673: for (nonOpaque = lastEntry; nonOpaque >= 0; nonOpaque--) {
0674: if (alphaPalette[nonOpaque] != (byte) 255) {
0675: break;
0676: }
0677: }
0678:
0679: if (nonOpaque >= 0) {
0680: ChunkStream cs = new ChunkStream("tRNS");
0681: for (int i = 0; i <= nonOpaque; i++) {
0682: cs.writeByte(alphaPalette[i]);
0683: }
0684: cs.writeToStream(dataOutput);
0685: }
0686: }
0687: }
0688:
0689: private void writePHYS() throws IOException {
0690: if (param.isPhysicalDimensionSet()) {
0691: ChunkStream cs = new ChunkStream("pHYs");
0692:
0693: int[] dims = param.getPhysicalDimension();
0694: cs.writeInt(dims[0]);
0695: cs.writeInt(dims[1]);
0696: cs.writeByte((byte) dims[2]);
0697:
0698: cs.writeToStream(dataOutput);
0699: }
0700: }
0701:
0702: private void writeSPLT() throws IOException {
0703: if (param.isSuggestedPaletteSet()) {
0704: ChunkStream cs = new ChunkStream("sPLT");
0705:
0706: System.out.println("sPLT not supported yet.");
0707:
0708: cs.writeToStream(dataOutput);
0709: }
0710: }
0711:
0712: private void writeTIME() throws IOException {
0713: if (param.isModificationTimeSet()) {
0714: ChunkStream cs = new ChunkStream("tIME");
0715:
0716: Date date = param.getModificationTime();
0717: TimeZone gmt = TimeZone.getTimeZone("GMT");
0718:
0719: GregorianCalendar cal = new GregorianCalendar(gmt);
0720: cal.setTime(date);
0721:
0722: int year = cal.get(Calendar.YEAR);
0723: int month = cal.get(Calendar.MONTH);
0724: int day = cal.get(Calendar.DAY_OF_MONTH);
0725: int hour = cal.get(Calendar.HOUR_OF_DAY);
0726: int minute = cal.get(Calendar.MINUTE);
0727: int second = cal.get(Calendar.SECOND);
0728:
0729: cs.writeShort(year);
0730: cs.writeByte(month + 1);
0731: cs.writeByte(day);
0732: cs.writeByte(hour);
0733: cs.writeByte(minute);
0734: cs.writeByte(second);
0735:
0736: cs.writeToStream(dataOutput);
0737: }
0738: }
0739:
0740: private void writeTEXT() throws IOException {
0741: if (param.isTextSet()) {
0742: String[] text = param.getText();
0743:
0744: for (int i = 0; i < text.length / 2; i++) {
0745: text[i << 1] = text[i << 1].trim();
0746:
0747: byte[] keyword = text[2 * i].getBytes("ISO-8859-1");
0748: int len = filterPrintableLatin1(keyword);
0749: ChunkStream cs = new ChunkStream("tEXt");
0750: cs.write(keyword, 0, Math.min(len, 79));
0751:
0752: text[(i << 1) + 1] = text[(i << 1) + 1].trim();
0753: byte[] value = text[(i << 1) + 1].getBytes();
0754: len = filterPrintableLatin1(value);
0755:
0756: cs.write(0);
0757: cs.write(value, 0, len);
0758:
0759: cs.writeToStream(dataOutput);
0760: }
0761: }
0762: }
0763:
0764: private void writeZTXT() throws IOException {
0765: if (param.isCompressedTextSet()) {
0766: String[] text = param.getCompressedText();
0767:
0768: for (int i = 0; i < text.length / 2; i++) {
0769: text[i << 1] = text[i << 1].trim();
0770:
0771: byte[] keyword = text[2 * i].getBytes();
0772: int len = filterPrintableLatin1(keyword);
0773: ChunkStream cs = new ChunkStream("zTXt");
0774: cs.write(keyword, 0, Math.min(len, 79));
0775:
0776: text[(i << 1) + 1] = text[(i << 1) + 1].trim();
0777: byte[] value = text[2 * i + 1].getBytes();
0778: len = filterPrintableLatin1(value);
0779:
0780: cs.write(0);
0781: cs.write(0);
0782:
0783: DeflaterOutputStream dos = new DeflaterOutputStream(cs);
0784: dos.write(value, 0, len);
0785: dos.finish();
0786:
0787: cs.writeToStream(dataOutput);
0788: }
0789: }
0790: }
0791:
0792: private void writePrivateChunks() throws IOException {
0793: int numChunks = param.getNumPrivateChunks();
0794: for (int i = 0; i < numChunks; i++) {
0795: String type = param.getPrivateChunkType(i);
0796: char char3 = type.charAt(3);
0797:
0798: byte[] data = param.getPrivateChunkData(i);
0799:
0800: ChunkStream cs = new ChunkStream(type);
0801: cs.write(data);
0802: cs.writeToStream(dataOutput);
0803: }
0804: }
0805:
0806: /**
0807: * Analyzes a set of palettes and determines if it can be expressed
0808: * as a standard set of gray values, with zero or one values being
0809: * fully transparent and the rest being fully opaque. If it
0810: * is possible to express the data thusly, the method returns
0811: * a suitable instance of PNGEncodeParam.Gray; otherwise it
0812: * returns null.
0813: */
0814: private PNGEncodeParam.Gray createGrayParam(byte[] redPalette,
0815: byte[] greenPalette, byte[] bluePalette, byte[] alphaPalette) {
0816: PNGEncodeParam.Gray param = new PNGEncodeParam.Gray();
0817: int numTransparent = 0;
0818:
0819: int grayFactor = 255 / ((1 << bitDepth) - 1);
0820: int entries = 1 << bitDepth;
0821: for (int i = 0; i < entries; i++) {
0822: byte red = redPalette[i];
0823: if ((red != i * grayFactor) || (red != greenPalette[i])
0824: || (red != bluePalette[i])) {
0825: return null;
0826: }
0827:
0828: // All alphas must be 255 except at most 1 can be 0
0829: byte alpha = alphaPalette[i];
0830: if (alpha == (byte) 0) {
0831: param.setTransparentGray(i);
0832:
0833: ++numTransparent;
0834: if (numTransparent > 1) {
0835: return null;
0836: }
0837: } else if (alpha != (byte) 255) {
0838: return null;
0839: }
0840: }
0841:
0842: return param;
0843: }
0844:
0845: public void encode(RenderedImage im) throws IOException {
0846: this .image = im;
0847: this .width = image.getWidth();
0848: this .height = image.getHeight();
0849:
0850: SampleModel sampleModel = image.getSampleModel();
0851:
0852: int[] sampleSize = sampleModel.getSampleSize();
0853:
0854: // Set bitDepth to a sentinel value
0855: this .bitDepth = -1;
0856: this .bitShift = 0;
0857:
0858: // Allow user to override the bit depth of gray images
0859: if (param instanceof PNGEncodeParam.Gray) {
0860: PNGEncodeParam.Gray paramg = (PNGEncodeParam.Gray) param;
0861: if (paramg.isBitDepthSet()) {
0862: this .bitDepth = paramg.getBitDepth();
0863: }
0864:
0865: if (paramg.isBitShiftSet()) {
0866: this .bitShift = paramg.getBitShift();
0867: }
0868: }
0869:
0870: // Get bit depth from image if not set in param
0871: if (this .bitDepth == -1) {
0872: // Get bit depth from channel 0 of the image
0873:
0874: this .bitDepth = sampleSize[0];
0875: // Ensure all channels have the same bit depth
0876: for (int i = 1; i < sampleSize.length; i++) {
0877: if (sampleSize[i] != bitDepth) {
0878: throw new RuntimeException();
0879: }
0880: }
0881:
0882: // Round bit depth up to a power of 2
0883: if (bitDepth > 2 && bitDepth < 4) {
0884: bitDepth = 4;
0885: } else if (bitDepth > 4 && bitDepth < 8) {
0886: bitDepth = 8;
0887: } else if (bitDepth > 8 && bitDepth < 16) {
0888: bitDepth = 16;
0889: } else if (bitDepth > 16) {
0890: throw new RuntimeException();
0891: }
0892: }
0893:
0894: this .numBands = sampleModel.getNumBands();
0895: this .bpp = numBands * ((bitDepth == 16) ? 2 : 1);
0896:
0897: ColorModel colorModel = image.getColorModel();
0898: if (colorModel instanceof IndexColorModel) {
0899: if (bitDepth < 1 || bitDepth > 8) {
0900: throw new RuntimeException();
0901: }
0902: if (sampleModel.getNumBands() != 1) {
0903: throw new RuntimeException();
0904: }
0905:
0906: IndexColorModel icm = (IndexColorModel) colorModel;
0907: int size = icm.getMapSize();
0908:
0909: redPalette = new byte[size];
0910: greenPalette = new byte[size];
0911: bluePalette = new byte[size];
0912: alphaPalette = new byte[size];
0913:
0914: icm.getReds(redPalette);
0915: icm.getGreens(greenPalette);
0916: icm.getBlues(bluePalette);
0917: icm.getAlphas(alphaPalette);
0918:
0919: this .bpp = 1;
0920:
0921: if (param == null) {
0922: param = createGrayParam(redPalette, greenPalette,
0923: bluePalette, alphaPalette);
0924: }
0925:
0926: // If param is still null, it can't be expressed as gray
0927: if (param == null) {
0928: param = new PNGEncodeParam.Palette();
0929: }
0930:
0931: if (param instanceof PNGEncodeParam.Palette) {
0932: // If palette not set in param, create one from the ColorModel.
0933: PNGEncodeParam.Palette parami = (PNGEncodeParam.Palette) param;
0934: if (parami.isPaletteSet()) {
0935: int[] palette = parami.getPalette();
0936: size = palette.length / 3;
0937:
0938: int index = 0;
0939: for (int i = 0; i < size; i++) {
0940: redPalette[i] = (byte) palette[index++];
0941: greenPalette[i] = (byte) palette[index++];
0942: bluePalette[i] = (byte) palette[index++];
0943: alphaPalette[i] = (byte) 255;
0944: }
0945: }
0946: this .colorType = PNG_COLOR_PALETTE;
0947: } else if (param instanceof PNGEncodeParam.Gray) {
0948: redPalette = greenPalette = bluePalette = alphaPalette = null;
0949: this .colorType = PNG_COLOR_GRAY;
0950: } else {
0951: throw new RuntimeException();
0952: }
0953: } else if (numBands == 1) {
0954: if (param == null) {
0955: param = new PNGEncodeParam.Gray();
0956: }
0957: this .colorType = PNG_COLOR_GRAY;
0958: } else if (numBands == 2) {
0959: if (param == null) {
0960: param = new PNGEncodeParam.Gray();
0961: }
0962:
0963: if (param.isTransparencySet()) {
0964: skipAlpha = true;
0965: numBands = 1;
0966: if ((sampleSize[0] == 8) && (bitDepth < 8)) {
0967: compressGray = true;
0968: }
0969: bpp = (bitDepth == 16) ? 2 : 1;
0970: this .colorType = PNG_COLOR_GRAY;
0971: } else {
0972: if (this .bitDepth < 8) {
0973: this .bitDepth = 8;
0974: }
0975: this .colorType = PNG_COLOR_GRAY_ALPHA;
0976: }
0977: } else if (numBands == 3) {
0978: if (param == null) {
0979: param = new PNGEncodeParam.RGB();
0980: }
0981: this .colorType = PNG_COLOR_RGB;
0982: } else if (numBands == 4) {
0983: if (param == null) {
0984: param = new PNGEncodeParam.RGB();
0985: }
0986: if (param.isTransparencySet()) {
0987: skipAlpha = true;
0988: numBands = 3;
0989: bpp = (bitDepth == 16) ? 6 : 3;
0990: this .colorType = PNG_COLOR_RGB;
0991: } else {
0992: this .colorType = PNG_COLOR_RGB_ALPHA;
0993: }
0994: }
0995:
0996: interlace = param.getInterlacing();
0997:
0998: writeMagic();
0999:
1000: writeIHDR();
1001:
1002: writeCHRM();
1003: writeGAMA();
1004: writeICCP();
1005: writeSBIT();
1006: writeSRGB();
1007:
1008: writePLTE();
1009:
1010: writeHIST();
1011: writeTRNS();
1012: writeBKGD();
1013:
1014: writePHYS();
1015: writeSPLT();
1016: writeTIME();
1017: writeTEXT();
1018: writeZTXT();
1019:
1020: writePrivateChunks();
1021:
1022: writeIDAT();
1023:
1024: writeIEND();
1025:
1026: dataOutput.flush();
1027:
1028: //Fix: 4636212. This sentence closes the provided stream.
1029: // Thus, the call for flush() in EncodeRIF will fail on
1030: // SeekableOutputStream. Also, the flush method in
1031: // SeekableOutputStream is improved.
1032: //dataOutput.close();
1033: }
1034:
1035: // public static void main(String[] args) {
1036: // try {
1037: // SeekableStream stream = new FileSeekableStream(args[0]);
1038: // String[] names = ImageCodec.getDecoderNames(stream);
1039:
1040: // ImageDecoder dec =
1041: // ImageCodec.createImageDecoder(names[0], stream, null);
1042: // RenderedImage im = dec.decodeAsRenderedImage();
1043:
1044: // OutputStream output = new FileOutputStream(args[1]);
1045:
1046: // PNGEncodeParam param = null;
1047: // Object o = im.getProperty("encode_param");
1048: // if ((o != null) && (o instanceof PNGEncodeParam)) {
1049: // param = (PNGEncodeParam)o;
1050: // } else {
1051: // param = PNGEncodeParam.getDefaultEncodeParam(im);
1052: // }
1053:
1054: // if (param instanceof PNGEncodeParam.RGB) {
1055: // int[] rgb = { 50, 100, 150 };
1056: // ((PNGEncodeParam.RGB)param).setBackgroundRGB(rgb);
1057: // }
1058:
1059: // param.setChromaticity(0.32270F, 0.319F,
1060: // 0.65F, 0.32F,
1061: // 0.31F, 0.58F,
1062: // 0.16F, 0.04F);
1063:
1064: // param.setGamma(3.5F);
1065:
1066: // int[] sbits = { 8, 8, 8 };
1067: // param.setSignificantBits(sbits);
1068:
1069: // param.setSRGBIntent(0);
1070:
1071: // String[] text = new String[4];
1072: // text[0] = "Title";
1073: // text[1] = "PNG Test Image";
1074: // text[2] = "Author";
1075: // text[3] = "Daniel Rice";
1076: // param.setText(text);
1077:
1078: // String[] ztext = new String[2];
1079: // ztext[0] = "Description";
1080: // ztext[1] = "A really incredibly long-winded description of extremely little if any substance whatsoever.";
1081: // param.setCompressedText(ztext);
1082:
1083: // ImageEncoder enc = new PNGImageEncoder(output, param);
1084: // enc.encode(im);
1085: // } catch (Exception e) {
1086: // e.printStackTrace();
1087: // System.exit(1);
1088: // }
1089: // }
1090:
1091: }
|