- /*
- * @(#)PNGImageWriter.java 1.32 03/01/23
- *
- * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- package com.sun.imageio.plugins.png;
-
- import java.awt.Rectangle;
- import java.awt.image.ColorModel;
- import java.awt.image.Raster;
- 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;
- }
- }
-
-
- 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);
- }
- }
-
- // Compress output and write as a series of 'IDAT' chunks of
- // fixed length.
- 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 {
- if (!def.finished()) {
- def.finish();
- while (!def.finished()) {
- deflate();
- }
- }
- finishChunk();
- }
- }
-
-
- class PNGImageWriteParam extends ImageWriteParam {
-
- public PNGImageWriteParam(Locale locale) {
- super();
- this.canWriteProgressive = true;
- this.locale = locale;
- }
- }
-
- /**
- * @version 0.5
- */
- 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 = (String)keywordIter.next();
- cs.writeBytes(keyword);
- cs.writeByte(0);
-
- String text = (String)textIter.next();
- cs.writeBytes(text);
- cs.finish();
- }
- }
-
- private byte[] deflate(String s) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DeflaterOutputStream dos = new DeflaterOutputStream(baos);
-
- int len = s.length();
- for (int i = 0; i < len; i++) {
- dos.write((int)s.charAt(i));
- }
- 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);
- String keyword = (String)keywordIter.next();
- cs.writeBytes(keyword);
- cs.writeByte(0);
-
- int flag = ((Integer)flagIter.next()).intValue();
- cs.writeByte(flag);
- int method = ((Integer)methodIter.next()).intValue();
- cs.writeByte(method);
-
- String languageTag = (String)languageIter.next();
- cs.writeBytes(languageTag);
- cs.writeByte(0);
-
- String translatedKeyword = (String)translatedKeywordIter.next();
- cs.writeBytes(translatedKeyword);
- cs.writeByte(0);
-
- String text = (String)textIter.next();
- if (flag == 1) {
- cs.write(deflate(text));
- } else {
- cs.writeUTF(text);
- }
- 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 = (String)keywordIter.next();
- cs.writeBytes(keyword);
- cs.writeByte(0);
-
- int compressionMethod = ((Integer)methodIter.next()).intValue();
- cs.writeByte(compressionMethod);
-
- String text = (String)textIter.next();
- cs.write(deflate(text));
- cs.finish();
- }
- }
-
- private void writeUnknownChunks() throws IOException {
- Iterator typeIter = metadata.unknownChunkType.iterator();
- Iterator dataIter = metadata.unknownChunkData.iterator();
-
- while (typeIter.hasNext() && dataIter.hasNext()) {
- String type = (String)typeIter.next();
- ChunkStream cs = new ChunkStream(PNGImageReader.chunkType(type),
- stream);
- byte[] data = (byte[])dataIter.next();
- cs.write(data);
- cs.finish();
- }
- }
-
- 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;
- }
- 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);
- }
-
- // TODO - need to divide out premultiplied alpha?
- ras.getPixels(minX, row, width, 1, 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]];
- }
- } 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*pixelsDonetotalPixels);
-
- // 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);
-
- 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);
- }
-
- 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 = maxInSample2;
- 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 = maxInSample2;
- 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);
- }
- }
- }