0N/A/*
2362N/A * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.imageio.plugins.png;
0N/A
0N/Aimport java.awt.Rectangle;
0N/Aimport java.awt.image.ColorModel;
0N/Aimport java.awt.image.IndexColorModel;
0N/Aimport java.awt.image.Raster;
0N/Aimport java.awt.image.WritableRaster;
0N/Aimport java.awt.image.RenderedImage;
0N/Aimport java.awt.image.SampleModel;
0N/Aimport java.io.ByteArrayOutputStream;
0N/Aimport java.io.DataOutput;
0N/Aimport java.io.IOException;
0N/Aimport java.io.OutputStream;
0N/Aimport java.util.Iterator;
0N/Aimport java.util.Locale;
0N/Aimport java.util.zip.Deflater;
0N/Aimport java.util.zip.DeflaterOutputStream;
0N/Aimport javax.imageio.IIOException;
0N/Aimport javax.imageio.IIOImage;
0N/Aimport javax.imageio.ImageTypeSpecifier;
0N/Aimport javax.imageio.ImageWriteParam;
0N/Aimport javax.imageio.ImageWriter;
0N/Aimport javax.imageio.metadata.IIOMetadata;
0N/Aimport javax.imageio.metadata.IIOMetadata;
0N/Aimport javax.imageio.spi.ImageWriterSpi;
0N/Aimport javax.imageio.stream.ImageOutputStream;
0N/Aimport javax.imageio.stream.ImageOutputStreamImpl;
0N/A
0N/Aclass CRC {
0N/A
0N/A private static int[] crcTable = new int[256];
0N/A private int crc = 0xffffffff;
0N/A
0N/A static {
0N/A // Initialize CRC table
0N/A for (int n = 0; n < 256; n++) {
0N/A int c = n;
0N/A for (int k = 0; k < 8; k++) {
0N/A if ((c & 1) == 1) {
0N/A c = 0xedb88320 ^ (c >>> 1);
0N/A } else {
0N/A c >>>= 1;
0N/A }
0N/A
0N/A crcTable[n] = c;
0N/A }
0N/A }
0N/A }
0N/A
0N/A public CRC() {}
0N/A
0N/A public void reset() {
0N/A crc = 0xffffffff;
0N/A }
0N/A
0N/A public void update(byte[] data, int off, int len) {
0N/A for (int n = 0; n < len; n++) {
0N/A crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
0N/A }
0N/A }
0N/A
0N/A public void update(int data) {
0N/A crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
0N/A }
0N/A
0N/A public int getValue() {
0N/A return crc ^ 0xffffffff;
0N/A }
0N/A}
0N/A
0N/A
0N/Afinal class ChunkStream extends ImageOutputStreamImpl {
0N/A
0N/A private ImageOutputStream stream;
0N/A private long startPos;
0N/A private CRC crc = new CRC();
0N/A
0N/A public ChunkStream(int type, ImageOutputStream stream) throws IOException {
0N/A this.stream = stream;
0N/A this.startPos = stream.getStreamPosition();
0N/A
0N/A stream.writeInt(-1); // length, will backpatch
0N/A writeInt(type);
0N/A }
0N/A
0N/A public int read() throws IOException {
0N/A throw new RuntimeException("Method not available");
0N/A }
0N/A
0N/A public int read(byte[] b, int off, int len) throws IOException {
0N/A throw new RuntimeException("Method not available");
0N/A }
0N/A
0N/A public void write(byte[] b, int off, int len) throws IOException {
0N/A crc.update(b, off, len);
0N/A stream.write(b, off, len);
0N/A }
0N/A
0N/A public void write(int b) throws IOException {
0N/A crc.update(b);
0N/A stream.write(b);
0N/A }
0N/A
0N/A public void finish() throws IOException {
0N/A // Write CRC
0N/A stream.writeInt(crc.getValue());
0N/A
0N/A // Write length
0N/A long pos = stream.getStreamPosition();
0N/A stream.seek(startPos);
0N/A stream.writeInt((int)(pos - startPos) - 12);
0N/A
0N/A // Return to end of chunk and flush to minimize buffering
0N/A stream.seek(pos);
0N/A stream.flushBefore(pos);
0N/A }
0N/A
0N/A protected void finalize() throws Throwable {
0N/A // Empty finalizer (for improved performance; no need to call
0N/A // super.finalize() in this case)
0N/A }
0N/A}
0N/A
0N/A// Compress output and write as a series of 'IDAT' chunks of
0N/A// fixed length.
0N/Afinal class IDATOutputStream extends ImageOutputStreamImpl {
0N/A
0N/A private static byte[] chunkType = {
0N/A (byte)'I', (byte)'D', (byte)'A', (byte)'T'
0N/A };
0N/A
0N/A private ImageOutputStream stream;
0N/A private int chunkLength;
0N/A private long startPos;
0N/A private CRC crc = new CRC();
0N/A
0N/A Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
0N/A byte[] buf = new byte[512];
0N/A
0N/A private int bytesRemaining;
0N/A
0N/A public IDATOutputStream(ImageOutputStream stream, int chunkLength)
0N/A throws IOException {
0N/A this.stream = stream;
0N/A this.chunkLength = chunkLength;
0N/A startChunk();
0N/A }
0N/A
0N/A private void startChunk() throws IOException {
0N/A crc.reset();
0N/A this.startPos = stream.getStreamPosition();
0N/A stream.writeInt(-1); // length, will backpatch
0N/A
0N/A crc.update(chunkType, 0, 4);
0N/A stream.write(chunkType, 0, 4);
0N/A
0N/A this.bytesRemaining = chunkLength;
0N/A }
0N/A
0N/A private void finishChunk() throws IOException {
0N/A // Write CRC
0N/A stream.writeInt(crc.getValue());
0N/A
0N/A // Write length
0N/A long pos = stream.getStreamPosition();
0N/A stream.seek(startPos);
0N/A stream.writeInt((int)(pos - startPos) - 12);
0N/A
0N/A // Return to end of chunk and flush to minimize buffering
0N/A stream.seek(pos);
0N/A stream.flushBefore(pos);
0N/A }
0N/A
0N/A public int read() throws IOException {
0N/A throw new RuntimeException("Method not available");
0N/A }
0N/A
0N/A public int read(byte[] b, int off, int len) throws IOException {
0N/A throw new RuntimeException("Method not available");
0N/A }
0N/A
0N/A public void write(byte[] b, int off, int len) throws IOException {
0N/A if (len == 0) {
0N/A return;
0N/A }
0N/A
0N/A if (!def.finished()) {
0N/A def.setInput(b, off, len);
0N/A while (!def.needsInput()) {
0N/A deflate();
0N/A }
0N/A }
0N/A }
0N/A
0N/A public void deflate() throws IOException {
0N/A int len = def.deflate(buf, 0, buf.length);
0N/A int off = 0;
0N/A
0N/A while (len > 0) {
0N/A if (bytesRemaining == 0) {
0N/A finishChunk();
0N/A startChunk();
0N/A }
0N/A
0N/A int nbytes = Math.min(len, bytesRemaining);
0N/A crc.update(buf, off, nbytes);
0N/A stream.write(buf, off, nbytes);
0N/A
0N/A off += nbytes;
0N/A len -= nbytes;
0N/A bytesRemaining -= nbytes;
0N/A }
0N/A }
0N/A
0N/A public void write(int b) throws IOException {
0N/A byte[] wbuf = new byte[1];
0N/A wbuf[0] = (byte)b;
0N/A write(wbuf, 0, 1);
0N/A }
0N/A
0N/A public void finish() throws IOException {
751N/A try {
751N/A if (!def.finished()) {
751N/A def.finish();
751N/A while (!def.finished()) {
751N/A deflate();
751N/A }
0N/A }
751N/A finishChunk();
751N/A } finally {
751N/A def.end();
0N/A }
0N/A }
0N/A
0N/A protected void finalize() throws Throwable {
0N/A // Empty finalizer (for improved performance; no need to call
0N/A // super.finalize() in this case)
0N/A }
0N/A}
0N/A
0N/A
0N/Aclass PNGImageWriteParam extends ImageWriteParam {
0N/A
0N/A public PNGImageWriteParam(Locale locale) {
0N/A super();
0N/A this.canWriteProgressive = true;
0N/A this.locale = locale;
0N/A }
0N/A}
0N/A
0N/A/**
0N/A */
0N/Apublic class PNGImageWriter extends ImageWriter {
0N/A
0N/A ImageOutputStream stream = null;
0N/A
0N/A PNGMetadata metadata = null;
0N/A
0N/A // Factors from the ImageWriteParam
0N/A int sourceXOffset = 0;
0N/A int sourceYOffset = 0;
0N/A int sourceWidth = 0;
0N/A int sourceHeight = 0;
0N/A int[] sourceBands = null;
0N/A int periodX = 1;
0N/A int periodY = 1;
0N/A
0N/A int numBands;
0N/A int bpp;
0N/A
0N/A RowFilter rowFilter = new RowFilter();
0N/A byte[] prevRow = null;
0N/A byte[] currRow = null;
0N/A byte[][] filteredRows = null;
0N/A
0N/A // Per-band scaling tables
0N/A //
0N/A // After the first call to initializeScaleTables, either scale and scale0
0N/A // will be valid, or scaleh and scalel will be valid, but not both.
0N/A //
0N/A // The tables will be designed for use with a set of input but depths
0N/A // given by sampleSize, and an output bit depth given by scalingBitDepth.
0N/A //
0N/A int[] sampleSize = null; // Sample size per band, in bits
0N/A int scalingBitDepth = -1; // Output bit depth of the scaling tables
0N/A
0N/A // Tables for 1, 2, 4, or 8 bit output
0N/A byte[][] scale = null; // 8 bit table
0N/A byte[] scale0 = null; // equivalent to scale[0]
0N/A
0N/A // Tables for 16 bit output
0N/A byte[][] scaleh = null; // High bytes of output
0N/A byte[][] scalel = null; // Low bytes of output
0N/A
0N/A int totalPixels; // Total number of pixels to be written by write_IDAT
0N/A int pixelsDone; // Running count of pixels written by write_IDAT
0N/A
0N/A public PNGImageWriter(ImageWriterSpi originatingProvider) {
0N/A super(originatingProvider);
0N/A }
0N/A
0N/A public void setOutput(Object output) {
0N/A super.setOutput(output);
0N/A if (output != null) {
0N/A if (!(output instanceof ImageOutputStream)) {
0N/A throw new IllegalArgumentException("output not an ImageOutputStream!");
0N/A }
0N/A this.stream = (ImageOutputStream)output;
0N/A } else {
0N/A this.stream = null;
0N/A }
0N/A }
0N/A
0N/A private static int[] allowedProgressivePasses = { 1, 7 };
0N/A
0N/A public ImageWriteParam getDefaultWriteParam() {
0N/A return new PNGImageWriteParam(getLocale());
0N/A }
0N/A
0N/A public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
0N/A return null;
0N/A }
0N/A
0N/A public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
0N/A ImageWriteParam param) {
0N/A PNGMetadata m = new PNGMetadata();
0N/A m.initialize(imageType, imageType.getSampleModel().getNumBands());
0N/A return m;
0N/A }
0N/A
0N/A public IIOMetadata convertStreamMetadata(IIOMetadata inData,
0N/A ImageWriteParam param) {
0N/A return null;
0N/A }
0N/A
0N/A public IIOMetadata convertImageMetadata(IIOMetadata inData,
0N/A ImageTypeSpecifier imageType,
0N/A ImageWriteParam param) {
0N/A // TODO - deal with imageType
0N/A if (inData instanceof PNGMetadata) {
0N/A return (PNGMetadata)((PNGMetadata)inData).clone();
0N/A } else {
0N/A return new PNGMetadata(inData);
0N/A }
0N/A }
0N/A
0N/A private void write_magic() throws IOException {
0N/A // Write signature
0N/A byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
0N/A stream.write(magic);
0N/A }
0N/A
0N/A private void write_IHDR() throws IOException {
0N/A // Write IHDR chunk
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
0N/A cs.writeInt(metadata.IHDR_width);
0N/A cs.writeInt(metadata.IHDR_height);
0N/A cs.writeByte(metadata.IHDR_bitDepth);
0N/A cs.writeByte(metadata.IHDR_colorType);
0N/A if (metadata.IHDR_compressionMethod != 0) {
0N/A throw new IIOException(
0N/A"Only compression method 0 is defined in PNG 1.1");
0N/A }
0N/A cs.writeByte(metadata.IHDR_compressionMethod);
0N/A if (metadata.IHDR_filterMethod != 0) {
0N/A throw new IIOException(
0N/A"Only filter method 0 is defined in PNG 1.1");
0N/A }
0N/A cs.writeByte(metadata.IHDR_filterMethod);
0N/A if (metadata.IHDR_interlaceMethod < 0 ||
0N/A metadata.IHDR_interlaceMethod > 1) {
0N/A throw new IIOException(
0N/A"Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1");
0N/A }
0N/A cs.writeByte(metadata.IHDR_interlaceMethod);
0N/A cs.finish();
0N/A }
0N/A
0N/A private void write_cHRM() throws IOException {
0N/A if (metadata.cHRM_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.cHRM_TYPE, stream);
0N/A cs.writeInt(metadata.cHRM_whitePointX);
0N/A cs.writeInt(metadata.cHRM_whitePointY);
0N/A cs.writeInt(metadata.cHRM_redX);
0N/A cs.writeInt(metadata.cHRM_redY);
0N/A cs.writeInt(metadata.cHRM_greenX);
0N/A cs.writeInt(metadata.cHRM_greenY);
0N/A cs.writeInt(metadata.cHRM_blueX);
0N/A cs.writeInt(metadata.cHRM_blueY);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_gAMA() throws IOException {
0N/A if (metadata.gAMA_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.gAMA_TYPE, stream);
0N/A cs.writeInt(metadata.gAMA_gamma);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_iCCP() throws IOException {
0N/A if (metadata.iCCP_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.iCCP_TYPE, stream);
0N/A cs.writeBytes(metadata.iCCP_profileName);
0N/A cs.writeByte(0); // null terminator
0N/A
0N/A cs.writeByte(metadata.iCCP_compressionMethod);
0N/A cs.write(metadata.iCCP_compressedProfile);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_sBIT() throws IOException {
0N/A if (metadata.sBIT_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.sBIT_TYPE, stream);
0N/A int colorType = metadata.IHDR_colorType;
0N/A if (metadata.sBIT_colorType != colorType) {
0N/A processWarningOccurred(0,
0N/A"sBIT metadata has wrong color type.\n" +
0N/A"The chunk will not be written.");
0N/A return;
0N/A }
0N/A
0N/A if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
0N/A colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
0N/A cs.writeByte(metadata.sBIT_grayBits);
0N/A } else if (colorType == PNGImageReader.PNG_COLOR_RGB ||
0N/A colorType == PNGImageReader.PNG_COLOR_PALETTE ||
0N/A colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
0N/A cs.writeByte(metadata.sBIT_redBits);
0N/A cs.writeByte(metadata.sBIT_greenBits);
0N/A cs.writeByte(metadata.sBIT_blueBits);
0N/A }
0N/A
0N/A if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
0N/A colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
0N/A cs.writeByte(metadata.sBIT_alphaBits);
0N/A }
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_sRGB() throws IOException {
0N/A if (metadata.sRGB_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.sRGB_TYPE, stream);
0N/A cs.writeByte(metadata.sRGB_renderingIntent);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_PLTE() throws IOException {
0N/A if (metadata.PLTE_present) {
0N/A if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY ||
0N/A metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
0N/A // PLTE cannot occur in a gray image
0N/A
0N/A processWarningOccurred(0,
0N/A"A PLTE chunk may not appear in a gray or gray alpha image.\n" +
0N/A"The chunk will not be written");
0N/A return;
0N/A }
0N/A
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.PLTE_TYPE, stream);
0N/A
0N/A int numEntries = metadata.PLTE_red.length;
0N/A byte[] palette = new byte[numEntries*3];
0N/A int index = 0;
0N/A for (int i = 0; i < numEntries; i++) {
0N/A palette[index++] = metadata.PLTE_red[i];
0N/A palette[index++] = metadata.PLTE_green[i];
0N/A palette[index++] = metadata.PLTE_blue[i];
0N/A }
0N/A
0N/A cs.write(palette);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_hIST() throws IOException, IIOException {
0N/A if (metadata.hIST_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.hIST_TYPE, stream);
0N/A
0N/A if (!metadata.PLTE_present) {
0N/A throw new IIOException("hIST chunk without PLTE chunk!");
0N/A }
0N/A
0N/A cs.writeChars(metadata.hIST_histogram,
0N/A 0, metadata.hIST_histogram.length);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_tRNS() throws IOException, IIOException {
0N/A if (metadata.tRNS_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.tRNS_TYPE, stream);
0N/A int colorType = metadata.IHDR_colorType;
0N/A int chunkType = metadata.tRNS_colorType;
0N/A
0N/A // Special case: image is RGB and chunk is Gray
0N/A // Promote chunk contents to RGB
0N/A int chunkRed = metadata.tRNS_red;
0N/A int chunkGreen = metadata.tRNS_green;
0N/A int chunkBlue = metadata.tRNS_blue;
0N/A if (colorType == PNGImageReader.PNG_COLOR_RGB &&
0N/A chunkType == PNGImageReader.PNG_COLOR_GRAY) {
0N/A chunkType = colorType;
0N/A chunkRed = chunkGreen = chunkBlue =
0N/A metadata.tRNS_gray;
0N/A }
0N/A
0N/A if (chunkType != colorType) {
0N/A processWarningOccurred(0,
0N/A"tRNS metadata has incompatible color type.\n" +
0N/A"The chunk will not be written.");
0N/A return;
0N/A }
0N/A
0N/A if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
0N/A if (!metadata.PLTE_present) {
0N/A throw new IIOException("tRNS chunk without PLTE chunk!");
0N/A }
0N/A cs.write(metadata.tRNS_alpha);
0N/A } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) {
0N/A cs.writeShort(metadata.tRNS_gray);
0N/A } else if (colorType == PNGImageReader.PNG_COLOR_RGB) {
0N/A cs.writeShort(chunkRed);
0N/A cs.writeShort(chunkGreen);
0N/A cs.writeShort(chunkBlue);
0N/A } else {
0N/A throw new IIOException("tRNS chunk for color type 4 or 6!");
0N/A }
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_bKGD() throws IOException {
0N/A if (metadata.bKGD_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.bKGD_TYPE, stream);
0N/A int colorType = metadata.IHDR_colorType & 0x3;
0N/A int chunkType = metadata.bKGD_colorType;
0N/A
0N/A // Special case: image is RGB(A) and chunk is Gray
0N/A // Promote chunk contents to RGB
0N/A int chunkRed = metadata.bKGD_red;
0N/A int chunkGreen = metadata.bKGD_red;
0N/A int chunkBlue = metadata.bKGD_red;
0N/A if (colorType == PNGImageReader.PNG_COLOR_RGB &&
0N/A chunkType == PNGImageReader.PNG_COLOR_GRAY) {
0N/A // Make a gray bKGD chunk look like RGB
0N/A chunkType = colorType;
0N/A chunkRed = chunkGreen = chunkBlue =
0N/A metadata.bKGD_gray;
0N/A }
0N/A
0N/A // Ignore status of alpha in colorType
0N/A if (chunkType != colorType) {
0N/A processWarningOccurred(0,
0N/A"bKGD metadata has incompatible color type.\n" +
0N/A"The chunk will not be written.");
0N/A return;
0N/A }
0N/A
0N/A if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
0N/A cs.writeByte(metadata.bKGD_index);
0N/A } else if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
0N/A colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
0N/A cs.writeShort(metadata.bKGD_gray);
0N/A } else { // colorType == PNGImageReader.PNG_COLOR_RGB ||
0N/A // colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
0N/A cs.writeShort(chunkRed);
0N/A cs.writeShort(chunkGreen);
0N/A cs.writeShort(chunkBlue);
0N/A }
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_pHYs() throws IOException {
0N/A if (metadata.pHYs_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.pHYs_TYPE, stream);
0N/A cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis);
0N/A cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis);
0N/A cs.writeByte(metadata.pHYs_unitSpecifier);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_sPLT() throws IOException {
0N/A if (metadata.sPLT_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.sPLT_TYPE, stream);
0N/A
0N/A cs.writeBytes(metadata.sPLT_paletteName);
0N/A cs.writeByte(0); // null terminator
0N/A
0N/A cs.writeByte(metadata.sPLT_sampleDepth);
0N/A int numEntries = metadata.sPLT_red.length;
0N/A
0N/A if (metadata.sPLT_sampleDepth == 8) {
0N/A for (int i = 0; i < numEntries; i++) {
0N/A cs.writeByte(metadata.sPLT_red[i]);
0N/A cs.writeByte(metadata.sPLT_green[i]);
0N/A cs.writeByte(metadata.sPLT_blue[i]);
0N/A cs.writeByte(metadata.sPLT_alpha[i]);
0N/A cs.writeShort(metadata.sPLT_frequency[i]);
0N/A }
0N/A } else { // sampleDepth == 16
0N/A for (int i = 0; i < numEntries; i++) {
0N/A cs.writeShort(metadata.sPLT_red[i]);
0N/A cs.writeShort(metadata.sPLT_green[i]);
0N/A cs.writeShort(metadata.sPLT_blue[i]);
0N/A cs.writeShort(metadata.sPLT_alpha[i]);
0N/A cs.writeShort(metadata.sPLT_frequency[i]);
0N/A }
0N/A }
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_tIME() throws IOException {
0N/A if (metadata.tIME_present) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.tIME_TYPE, stream);
0N/A cs.writeShort(metadata.tIME_year);
0N/A cs.writeByte(metadata.tIME_month);
0N/A cs.writeByte(metadata.tIME_day);
0N/A cs.writeByte(metadata.tIME_hour);
0N/A cs.writeByte(metadata.tIME_minute);
0N/A cs.writeByte(metadata.tIME_second);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_tEXt() throws IOException {
0N/A Iterator keywordIter = metadata.tEXt_keyword.iterator();
0N/A Iterator textIter = metadata.tEXt_text.iterator();
0N/A
0N/A while (keywordIter.hasNext()) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.tEXt_TYPE, stream);
0N/A String keyword = (String)keywordIter.next();
0N/A cs.writeBytes(keyword);
0N/A cs.writeByte(0);
0N/A
0N/A String text = (String)textIter.next();
0N/A cs.writeBytes(text);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
770N/A private byte[] deflate(byte[] b) throws IOException {
0N/A ByteArrayOutputStream baos = new ByteArrayOutputStream();
0N/A DeflaterOutputStream dos = new DeflaterOutputStream(baos);
984N/A dos.write(b);
0N/A dos.close();
0N/A return baos.toByteArray();
0N/A }
0N/A
0N/A private void write_iTXt() throws IOException {
770N/A Iterator<String> keywordIter = metadata.iTXt_keyword.iterator();
770N/A Iterator<Boolean> flagIter = metadata.iTXt_compressionFlag.iterator();
770N/A Iterator<Integer> methodIter = metadata.iTXt_compressionMethod.iterator();
770N/A Iterator<String> languageIter = metadata.iTXt_languageTag.iterator();
770N/A Iterator<String> translatedKeywordIter =
0N/A metadata.iTXt_translatedKeyword.iterator();
770N/A Iterator<String> textIter = metadata.iTXt_text.iterator();
0N/A
0N/A while (keywordIter.hasNext()) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream);
770N/A
770N/A cs.writeBytes(keywordIter.next());
0N/A cs.writeByte(0);
0N/A
770N/A Boolean compressed = flagIter.next();
770N/A cs.writeByte(compressed ? 1 : 0);
0N/A
770N/A cs.writeByte(methodIter.next().intValue());
770N/A
770N/A cs.writeBytes(languageIter.next());
0N/A cs.writeByte(0);
0N/A
770N/A
770N/A cs.write(translatedKeywordIter.next().getBytes("UTF8"));
0N/A cs.writeByte(0);
0N/A
770N/A String text = textIter.next();
770N/A if (compressed) {
770N/A cs.write(deflate(text.getBytes("UTF8")));
0N/A } else {
770N/A cs.write(text.getBytes("UTF8"));
0N/A }
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void write_zTXt() throws IOException {
0N/A Iterator keywordIter = metadata.zTXt_keyword.iterator();
0N/A Iterator methodIter = metadata.zTXt_compressionMethod.iterator();
0N/A Iterator textIter = metadata.zTXt_text.iterator();
0N/A
0N/A while (keywordIter.hasNext()) {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.zTXt_TYPE, stream);
0N/A String keyword = (String)keywordIter.next();
0N/A cs.writeBytes(keyword);
0N/A cs.writeByte(0);
0N/A
0N/A int compressionMethod = ((Integer)methodIter.next()).intValue();
0N/A cs.writeByte(compressionMethod);
0N/A
0N/A String text = (String)textIter.next();
984N/A cs.write(deflate(text.getBytes("ISO-8859-1")));
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private void writeUnknownChunks() throws IOException {
0N/A Iterator typeIter = metadata.unknownChunkType.iterator();
0N/A Iterator dataIter = metadata.unknownChunkData.iterator();
0N/A
0N/A while (typeIter.hasNext() && dataIter.hasNext()) {
0N/A String type = (String)typeIter.next();
0N/A ChunkStream cs = new ChunkStream(chunkType(type), stream);
0N/A byte[] data = (byte[])dataIter.next();
0N/A cs.write(data);
0N/A cs.finish();
0N/A }
0N/A }
0N/A
0N/A private static int chunkType(String typeString) {
0N/A char c0 = typeString.charAt(0);
0N/A char c1 = typeString.charAt(1);
0N/A char c2 = typeString.charAt(2);
0N/A char c3 = typeString.charAt(3);
0N/A
0N/A int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
0N/A return type;
0N/A }
0N/A
0N/A private void encodePass(ImageOutputStream os,
0N/A RenderedImage image,
0N/A int xOffset, int yOffset,
0N/A int xSkip, int ySkip) throws IOException {
0N/A int minX = sourceXOffset;
0N/A int minY = sourceYOffset;
0N/A int width = sourceWidth;
0N/A int height = sourceHeight;
0N/A
0N/A // Adjust offsets and skips based on source subsampling factors
0N/A xOffset *= periodX;
0N/A xSkip *= periodX;
0N/A yOffset *= periodY;
0N/A ySkip *= periodY;
0N/A
0N/A // Early exit if no data for this pass
0N/A int hpixels = (width - xOffset + xSkip - 1)/xSkip;
0N/A int vpixels = (height - yOffset + ySkip - 1)/ySkip;
0N/A if (hpixels == 0 || vpixels == 0) {
0N/A return;
0N/A }
0N/A
0N/A // Convert X offset and skip from pixels to samples
0N/A xOffset *= numBands;
0N/A xSkip *= numBands;
0N/A
0N/A // Create row buffers
0N/A int samplesPerByte = 8/metadata.IHDR_bitDepth;
0N/A int numSamples = width*numBands;
0N/A int[] samples = new int[numSamples];
0N/A
0N/A int bytesPerRow = hpixels*numBands;
0N/A if (metadata.IHDR_bitDepth < 8) {
0N/A bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
0N/A } else if (metadata.IHDR_bitDepth == 16) {
0N/A bytesPerRow *= 2;
0N/A }
0N/A
0N/A IndexColorModel icm_gray_alpha = null;
0N/A if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA &&
0N/A image.getColorModel() instanceof IndexColorModel)
0N/A {
0N/A // reserve space for alpha samples
0N/A bytesPerRow *= 2;
0N/A
0N/A // will be used to calculate alpha value for the pixel
0N/A icm_gray_alpha = (IndexColorModel)image.getColorModel();
0N/A }
0N/A
0N/A currRow = new byte[bytesPerRow + bpp];
0N/A prevRow = new byte[bytesPerRow + bpp];
0N/A filteredRows = new byte[5][bytesPerRow + bpp];
0N/A
0N/A int bitDepth = metadata.IHDR_bitDepth;
0N/A for (int row = minY + yOffset; row < minY + height; row += ySkip) {
0N/A Rectangle rect = new Rectangle(minX, row, width, 1);
0N/A Raster ras = image.getData(rect);
0N/A if (sourceBands != null) {
0N/A ras = ras.createChild(minX, row, width, 1, minX, row,
0N/A sourceBands);
0N/A }
0N/A
0N/A ras.getPixels(minX, row, width, 1, samples);
0N/A
0N/A if (image.getColorModel().isAlphaPremultiplied()) {
0N/A WritableRaster wr = ras.createCompatibleWritableRaster();
0N/A wr.setPixels(wr.getMinX(), wr.getMinY(),
0N/A wr.getWidth(), wr.getHeight(),
0N/A samples);
0N/A
0N/A image.getColorModel().coerceData(wr, false);
0N/A wr.getPixels(wr.getMinX(), wr.getMinY(),
0N/A wr.getWidth(), wr.getHeight(),
0N/A samples);
0N/A }
0N/A
0N/A // Reorder palette data if necessary
0N/A int[] paletteOrder = metadata.PLTE_order;
0N/A if (paletteOrder != null) {
0N/A for (int i = 0; i < numSamples; i++) {
0N/A samples[i] = paletteOrder[samples[i]];
0N/A }
0N/A }
0N/A
0N/A int count = bpp; // leave first 'bpp' bytes zero
0N/A int pos = 0;
0N/A int tmp = 0;
0N/A
0N/A switch (bitDepth) {
0N/A case 1: case 2: case 4:
0N/A // Image can only have a single band
0N/A
0N/A int mask = samplesPerByte - 1;
0N/A for (int s = xOffset; s < numSamples; s += xSkip) {
0N/A byte val = scale0[samples[s]];
0N/A tmp = (tmp << bitDepth) | val;
0N/A
0N/A if ((pos++ & mask) == mask) {
0N/A currRow[count++] = (byte)tmp;
0N/A tmp = 0;
0N/A pos = 0;
0N/A }
0N/A }
0N/A
0N/A // Left shift the last byte
0N/A if ((pos & mask) != 0) {
0N/A tmp <<= ((8/bitDepth) - pos)*bitDepth;
0N/A currRow[count++] = (byte)tmp;
0N/A }
0N/A break;
0N/A
0N/A case 8:
0N/A if (numBands == 1) {
0N/A for (int s = xOffset; s < numSamples; s += xSkip) {
0N/A currRow[count++] = scale0[samples[s]];
0N/A if (icm_gray_alpha != null) {
0N/A currRow[count++] =
0N/A scale0[icm_gray_alpha.getAlpha(0xff & samples[s])];
0N/A }
0N/A }
0N/A } else {
0N/A for (int s = xOffset; s < numSamples; s += xSkip) {
0N/A for (int b = 0; b < numBands; b++) {
0N/A currRow[count++] = scale[b][samples[s + b]];
0N/A }
0N/A }
0N/A }
0N/A break;
0N/A
0N/A case 16:
0N/A for (int s = xOffset; s < numSamples; s += xSkip) {
0N/A for (int b = 0; b < numBands; b++) {
0N/A currRow[count++] = scaleh[b][samples[s + b]];
0N/A currRow[count++] = scalel[b][samples[s + b]];
0N/A }
0N/A }
0N/A break;
0N/A }
0N/A
0N/A // Perform filtering
0N/A int filterType = rowFilter.filterRow(metadata.IHDR_colorType,
0N/A currRow, prevRow,
0N/A filteredRows,
0N/A bytesPerRow, bpp);
0N/A
0N/A os.write(filterType);
0N/A os.write(filteredRows[filterType], bpp, bytesPerRow);
0N/A
0N/A // Swap current and previous rows
0N/A byte[] swap = currRow;
0N/A currRow = prevRow;
0N/A prevRow = swap;
0N/A
0N/A pixelsDone += hpixels;
0N/A processImageProgress(100.0F*pixelsDone/totalPixels);
0N/A
0N/A // If write has been aborted, just return;
0N/A // processWriteAborted will be called later
0N/A if (abortRequested()) {
0N/A return;
0N/A }
0N/A }
0N/A }
0N/A
0N/A // Use sourceXOffset, etc.
0N/A private void write_IDAT(RenderedImage image) throws IOException {
0N/A IDATOutputStream ios = new IDATOutputStream(stream, 32768);
751N/A try {
751N/A if (metadata.IHDR_interlaceMethod == 1) {
751N/A for (int i = 0; i < 7; i++) {
751N/A encodePass(ios, image,
751N/A PNGImageReader.adam7XOffset[i],
751N/A PNGImageReader.adam7YOffset[i],
751N/A PNGImageReader.adam7XSubsampling[i],
751N/A PNGImageReader.adam7YSubsampling[i]);
751N/A if (abortRequested()) {
751N/A break;
751N/A }
0N/A }
751N/A } else {
751N/A encodePass(ios, image, 0, 0, 1, 1);
0N/A }
751N/A } finally {
751N/A ios.finish();
0N/A }
0N/A }
0N/A
0N/A private void writeIEND() throws IOException {
0N/A ChunkStream cs = new ChunkStream(PNGImageReader.IEND_TYPE, stream);
0N/A cs.finish();
0N/A }
0N/A
0N/A // Check two int arrays for value equality, always returns false
0N/A // if either array is null
0N/A private boolean equals(int[] s0, int[] s1) {
0N/A if (s0 == null || s1 == null) {
0N/A return false;
0N/A }
0N/A if (s0.length != s1.length) {
0N/A return false;
0N/A }
0N/A for (int i = 0; i < s0.length; i++) {
0N/A if (s0[i] != s1[i]) {
0N/A return false;
0N/A }
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A // Initialize the scale/scale0 or scaleh/scalel arrays to
0N/A // hold the results of scaling an input value to the desired
0N/A // output bit depth
0N/A private void initializeScaleTables(int[] sampleSize) {
0N/A int bitDepth = metadata.IHDR_bitDepth;
0N/A
0N/A // If the existing tables are still valid, just return
0N/A if (bitDepth == scalingBitDepth &&
0N/A equals(sampleSize, this.sampleSize)) {
0N/A return;
0N/A }
0N/A
0N/A // Compute new tables
0N/A this.sampleSize = sampleSize;
0N/A this.scalingBitDepth = bitDepth;
0N/A int maxOutSample = (1 << bitDepth) - 1;
0N/A if (bitDepth <= 8) {
0N/A scale = new byte[numBands][];
0N/A for (int b = 0; b < numBands; b++) {
0N/A int maxInSample = (1 << sampleSize[b]) - 1;
0N/A int halfMaxInSample = maxInSample/2;
0N/A scale[b] = new byte[maxInSample + 1];
0N/A for (int s = 0; s <= maxInSample; s++) {
0N/A scale[b][s] =
0N/A (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
0N/A }
0N/A }
0N/A scale0 = scale[0];
0N/A scaleh = scalel = null;
0N/A } else { // bitDepth == 16
0N/A // Divide scaling table into high and low bytes
0N/A scaleh = new byte[numBands][];
0N/A scalel = new byte[numBands][];
0N/A
0N/A for (int b = 0; b < numBands; b++) {
0N/A int maxInSample = (1 << sampleSize[b]) - 1;
0N/A int halfMaxInSample = maxInSample/2;
0N/A scaleh[b] = new byte[maxInSample + 1];
0N/A scalel[b] = new byte[maxInSample + 1];
0N/A for (int s = 0; s <= maxInSample; s++) {
0N/A int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
0N/A scaleh[b][s] = (byte)(val >> 8);
0N/A scalel[b][s] = (byte)(val & 0xff);
0N/A }
0N/A }
0N/A scale = null;
0N/A scale0 = null;
0N/A }
0N/A }
0N/A
0N/A public void write(IIOMetadata streamMetadata,
0N/A IIOImage image,
0N/A ImageWriteParam param) throws IIOException {
0N/A if (stream == null) {
0N/A throw new IllegalStateException("output == null!");
0N/A }
0N/A if (image == null) {
0N/A throw new IllegalArgumentException("image == null!");
0N/A }
0N/A if (image.hasRaster()) {
0N/A throw new UnsupportedOperationException("image has a Raster!");
0N/A }
0N/A
0N/A RenderedImage im = image.getRenderedImage();
0N/A SampleModel sampleModel = im.getSampleModel();
0N/A this.numBands = sampleModel.getNumBands();
0N/A
0N/A // Set source region and subsampling to default values
0N/A this.sourceXOffset = im.getMinX();
0N/A this.sourceYOffset = im.getMinY();
0N/A this.sourceWidth = im.getWidth();
0N/A this.sourceHeight = im.getHeight();
0N/A this.sourceBands = null;
0N/A this.periodX = 1;
0N/A this.periodY = 1;
0N/A
0N/A if (param != null) {
0N/A // Get source region and subsampling factors
0N/A Rectangle sourceRegion = param.getSourceRegion();
0N/A if (sourceRegion != null) {
0N/A Rectangle imageBounds = new Rectangle(im.getMinX(),
0N/A im.getMinY(),
0N/A im.getWidth(),
0N/A im.getHeight());
0N/A // Clip to actual image bounds
0N/A sourceRegion = sourceRegion.intersection(imageBounds);
0N/A sourceXOffset = sourceRegion.x;
0N/A sourceYOffset = sourceRegion.y;
0N/A sourceWidth = sourceRegion.width;
0N/A sourceHeight = sourceRegion.height;
0N/A }
0N/A
0N/A // Adjust for subsampling offsets
0N/A int gridX = param.getSubsamplingXOffset();
0N/A int gridY = param.getSubsamplingYOffset();
0N/A sourceXOffset += gridX;
0N/A sourceYOffset += gridY;
0N/A sourceWidth -= gridX;
0N/A sourceHeight -= gridY;
0N/A
0N/A // Get subsampling factors
0N/A periodX = param.getSourceXSubsampling();
0N/A periodY = param.getSourceYSubsampling();
0N/A
0N/A int[] sBands = param.getSourceBands();
0N/A if (sBands != null) {
0N/A sourceBands = sBands;
0N/A numBands = sourceBands.length;
0N/A }
0N/A }
0N/A
0N/A // Compute output dimensions
0N/A int destWidth = (sourceWidth + periodX - 1)/periodX;
0N/A int destHeight = (sourceHeight + periodY - 1)/periodY;
0N/A if (destWidth <= 0 || destHeight <= 0) {
0N/A throw new IllegalArgumentException("Empty source region!");
0N/A }
0N/A
0N/A // Compute total number of pixels for progress notification
0N/A this.totalPixels = destWidth*destHeight;
0N/A this.pixelsDone = 0;
0N/A
0N/A // Create metadata
0N/A IIOMetadata imd = image.getMetadata();
0N/A if (imd != null) {
0N/A metadata = (PNGMetadata)convertImageMetadata(imd,
0N/A ImageTypeSpecifier.createFromRenderedImage(im),
0N/A null);
0N/A } else {
0N/A metadata = new PNGMetadata();
0N/A }
0N/A
0N/A if (param != null) {
0N/A // Use Adam7 interlacing if set in write param
0N/A switch (param.getProgressiveMode()) {
0N/A case ImageWriteParam.MODE_DEFAULT:
0N/A metadata.IHDR_interlaceMethod = 1;
0N/A break;
0N/A case ImageWriteParam.MODE_DISABLED:
0N/A metadata.IHDR_interlaceMethod = 0;
0N/A break;
0N/A // MODE_COPY_FROM_METADATA should alreay be taken care of
0N/A // MODE_EXPLICIT is not allowed
0N/A }
0N/A }
0N/A
0N/A // Initialize bitDepth and colorType
0N/A metadata.initialize(new ImageTypeSpecifier(im), numBands);
0N/A
0N/A // Overwrite IHDR width and height values with values from image
0N/A metadata.IHDR_width = destWidth;
0N/A metadata.IHDR_height = destHeight;
0N/A
0N/A this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
0N/A
0N/A // Initialize scaling tables for this image
0N/A initializeScaleTables(sampleModel.getSampleSize());
0N/A
0N/A clearAbortRequest();
0N/A
0N/A processImageStarted(0);
0N/A
0N/A try {
0N/A write_magic();
0N/A write_IHDR();
0N/A
0N/A write_cHRM();
0N/A write_gAMA();
0N/A write_iCCP();
0N/A write_sBIT();
0N/A write_sRGB();
0N/A
0N/A write_PLTE();
0N/A
0N/A write_hIST();
0N/A write_tRNS();
0N/A write_bKGD();
0N/A
0N/A write_pHYs();
0N/A write_sPLT();
0N/A write_tIME();
0N/A write_tEXt();
0N/A write_iTXt();
0N/A write_zTXt();
0N/A
0N/A writeUnknownChunks();
0N/A
0N/A write_IDAT(im);
0N/A
0N/A if (abortRequested()) {
0N/A processWriteAborted();
0N/A } else {
0N/A // Finish up and inform the listeners we are done
0N/A writeIEND();
0N/A processImageComplete();
0N/A }
0N/A } catch (IOException e) {
0N/A throw new IIOException("I/O error writing PNG file!", e);
0N/A }
0N/A }
0N/A}