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