org.apache.xmlgraphics.image.codec.png.PNGImageEncoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xmlgraphics-commons Show documentation
Show all versions of xmlgraphics-commons Show documentation
Apache XML Graphics Commons is a library that consists of several reusable
components used by Apache Batik and Apache FOP. Many of these components
can easily be used separately outside the domains of SVG and XSL-FO.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id: PNGImageEncoder.java 1732018 2016-02-24 04:51:06Z gadams $ */
package org.apache.xmlgraphics.image.codec.png;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
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.DataOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import org.apache.xmlgraphics.image.codec.util.ImageEncoderImpl;
import org.apache.xmlgraphics.image.codec.util.PropertyUtil;
// CSOFF: ConstantName
// CSOFF: InnerAssignment
// CSOFF: LocalVariableName
// CSOFF: MissingSwitchDefault
// CSOFF: OperatorWrap
// CSOFF: WhitespaceAround
final class CRC {
private CRC() {
}
private static int[] crcTable = new int[256];
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 static int updateCRC(int crc, byte[] data, int off, int len) {
int c = crc;
for (int n = 0; n < len; n++) {
c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
}
return c;
}
}
class ChunkStream extends OutputStream implements DataOutput {
private String type;
private ByteArrayOutputStream baos;
private DataOutputStream dos;
ChunkStream(String type) throws IOException {
this.type = type;
this.baos = new ByteArrayOutputStream();
this.dos = new DataOutputStream(baos);
}
@Override
public void write(byte[] b) throws IOException {
dos.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
dos.write(b, off, len);
}
@Override
public void write(int b) throws IOException {
dos.write(b);
}
public void writeBoolean(boolean v) throws IOException {
dos.writeBoolean(v);
}
public void writeByte(int v) throws IOException {
dos.writeByte(v);
}
public void writeBytes(String s) throws IOException {
dos.writeBytes(s);
}
public void writeChar(int v) throws IOException {
dos.writeChar(v);
}
public void writeChars(String s) throws IOException {
dos.writeChars(s);
}
public void writeDouble(double v) throws IOException {
dos.writeDouble(v);
}
public void writeFloat(float v) throws IOException {
dos.writeFloat(v);
}
public void writeInt(int v) throws IOException {
dos.writeInt(v);
}
public void writeLong(long v) throws IOException {
dos.writeLong(v);
}
public void writeShort(int v) throws IOException {
dos.writeShort(v);
}
public void writeUTF(String str) throws IOException {
dos.writeUTF(str);
}
public void writeToStream(DataOutputStream output) throws IOException {
byte[] typeSignature = new byte[4];
typeSignature[0] = (byte)type.charAt(0);
typeSignature[1] = (byte)type.charAt(1);
typeSignature[2] = (byte)type.charAt(2);
typeSignature[3] = (byte)type.charAt(3);
dos.flush();
baos.flush();
byte[] data = baos.toByteArray();
int len = data.length;
output.writeInt(len);
output.write(typeSignature);
output.write(data, 0, len);
int crc = 0xffffffff;
crc = CRC.updateCRC(crc, typeSignature, 0, 4);
crc = CRC.updateCRC(crc, data, 0, len);
output.writeInt(crc ^ 0xffffffff);
}
/** {@inheritDoc} */
@Override
public void close() throws IOException {
if (baos != null) {
baos.close();
baos = null;
}
if (dos != null) {
dos.close();
dos = null;
}
}
}
class IDATOutputStream extends FilterOutputStream {
private static final byte[] TYPE_SIGNATURE
= {(byte)'I', (byte)'D', (byte)'A', (byte)'T'};
private int bytesWritten;
private int segmentLength;
private byte[] buffer;
public IDATOutputStream(OutputStream output,
int segmentLength) {
super(output);
this.segmentLength = segmentLength;
this.buffer = new byte[segmentLength];
}
@Override
public void close() throws IOException {
flush();
}
private void writeInt(int x) throws IOException {
out.write(x >> 24);
out.write((x >> 16) & 0xff);
out.write((x >> 8) & 0xff);
out.write(x & 0xff);
}
@Override
public void flush() throws IOException {
if (bytesWritten == 0) {
return;
}
// Length
writeInt(bytesWritten);
// 'IDAT' signature
out.write(TYPE_SIGNATURE);
// Data
out.write(buffer, 0, bytesWritten);
int crc = 0xffffffff;
crc = CRC.updateCRC(crc, TYPE_SIGNATURE, 0, 4);
crc = CRC.updateCRC(crc, buffer, 0, bytesWritten);
// CRC
writeInt(crc ^ 0xffffffff);
// Reset buffer
bytesWritten = 0;
}
@Override
public void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
int bytes = Math.min(segmentLength - bytesWritten, len);
System.arraycopy(b, off, buffer, bytesWritten, bytes);
off += bytes;
len -= bytes;
bytesWritten += bytes;
if (bytesWritten == segmentLength) {
flush();
}
}
}
@Override
public void write(int b) throws IOException {
buffer[bytesWritten++] = (byte)b;
if (bytesWritten == segmentLength) {
flush();
}
}
}
/**
* An ImageEncoder for the PNG file format.
*
* @since EA4
*/
public class PNGImageEncoder extends ImageEncoderImpl {
private static final int PNG_COLOR_GRAY = 0;
private static final int PNG_COLOR_RGB = 2;
private static final int PNG_COLOR_PALETTE = 3;
private static final int PNG_COLOR_GRAY_ALPHA = 4;
private static final int PNG_COLOR_RGB_ALPHA = 6;
private static final byte[] MAGIC = {
(byte)137, (byte) 80, (byte) 78, (byte) 71,
(byte) 13, (byte) 10, (byte) 26, (byte) 10
};
private PNGEncodeParam param;
private RenderedImage image;
private int width;
private int height;
private int bitDepth;
private int bitShift;
private int numBands;
private int colorType;
private int bpp; // bytes per pixel, rounded up
private boolean skipAlpha;
private boolean compressGray;
private boolean interlace;
private byte[] redPalette;
private byte[] greenPalette;
private byte[] bluePalette;
private byte[] alphaPalette;
private DataOutputStream dataOutput;
public PNGImageEncoder(OutputStream output,
PNGEncodeParam param) {
super(output, param);
if (param != null) {
this.param = param;
}
this.dataOutput = new DataOutputStream(output);
}
private void writeMagic() throws IOException {
dataOutput.write(MAGIC);
}
private void writeIHDR() throws IOException {
ChunkStream cs = new ChunkStream("IHDR");
try {
cs.writeInt(width);
cs.writeInt(height);
cs.writeByte((byte)bitDepth);
cs.writeByte((byte)colorType);
cs.writeByte((byte)0);
cs.writeByte((byte)0);
cs.writeByte(interlace ? (byte)1 : (byte)0);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
private byte[] prevRow;
private byte[] currRow;
private byte[][] filteredRows;
private static int clamp(int val, int maxValue) {
return (val > maxValue) ? maxValue : val;
}
private void encodePass(OutputStream os, Raster ras,
int xOffset, int yOffset,
int xSkip, int ySkip)
throws IOException {
int minX = ras.getMinX();
int minY = ras.getMinY();
int width = ras.getWidth();
int height = ras.getHeight();
xOffset *= numBands;
xSkip *= numBands;
int samplesPerByte = 8 / bitDepth;
int numSamples = width * numBands;
int[] samples = new int[numSamples];
int pixels = (numSamples - xOffset + xSkip - 1) / xSkip;
int bytesPerRow = pixels * numBands;
if (bitDepth < 8) {
bytesPerRow = (bytesPerRow + samplesPerByte - 1) / samplesPerByte;
} else if (bitDepth == 16) {
bytesPerRow *= 2;
}
if (bytesPerRow == 0) {
return;
}
currRow = new byte[bytesPerRow + bpp];
prevRow = new byte[bytesPerRow + bpp];
filteredRows = new byte[5][bytesPerRow + bpp];
int maxValue = (1 << bitDepth) - 1;
for (int row = minY + yOffset; row < minY + height; row += ySkip) {
ras.getPixels(minX, row, width, 1, samples);
if (compressGray) {
int shift = 8 - bitDepth;
for (int i = 0; i < width; i++) {
samples[i] >>= shift;
}
}
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) {
int val = clamp(samples[s] >> bitShift, maxValue);
tmp = (tmp << bitDepth) | val;
if (pos++ == mask) {
currRow[count++] = (byte)tmp;
tmp = 0;
pos = 0;
}
}
// Left shift the last byte
if (pos != 0) {
tmp <<= (samplesPerByte - pos) * bitDepth;
currRow[count++] = (byte)tmp;
}
break;
case 8:
for (int s = xOffset; s < numSamples; s += xSkip) {
for (int b = 0; b < numBands; b++) {
currRow[count++] =
(byte)clamp(samples[s + b] >> bitShift, maxValue);
}
}
break;
case 16:
for (int s = xOffset; s < numSamples; s += xSkip) {
for (int b = 0; b < numBands; b++) {
int val = clamp(samples[s + b] >> bitShift, maxValue);
currRow[count++] = (byte)(val >> 8);
currRow[count++] = (byte)(val & 0xff);
}
}
break;
}
// Perform filtering
int filterType = param.filterRow(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;
}
}
private void writeIDAT() throws IOException {
IDATOutputStream ios = new IDATOutputStream(dataOutput, 8192);
DeflaterOutputStream dos =
new DeflaterOutputStream(ios, new Deflater(9));
// Future work - don't convert entire image to a Raster It
// might seem that you could just call image.getData() but
// 'BufferedImage.subImage' doesn't appear to set the Width
// and height properly of the Child Raster, so the Raster
// you get back here appears larger than it should.
// This solves that problem by bounding the raster to the
// image's bounds...
Raster ras = image.getData(new Rectangle(image.getMinX(),
image.getMinY(),
image.getWidth(),
image.getHeight()));
// System.out.println("Image: [" +
// image.getMinY() + ", " +
// image.getMinX() + ", " +
// image.getWidth() + ", " +
// image.getHeight() + "]");
// System.out.println("Ras: [" +
// ras.getMinX() + ", " +
// ras.getMinY() + ", " +
// ras.getWidth() + ", " +
// ras.getHeight() + "]");
if (skipAlpha) {
int numBands = ras.getNumBands() - 1;
int[] bandList = new int[numBands];
for (int i = 0; i < numBands; i++) {
bandList[i] = i;
}
ras = ras.createChild(0, 0,
ras.getWidth(), ras.getHeight(),
0, 0,
bandList);
}
if (interlace) {
// Interlacing pass 1
encodePass(dos, ras, 0, 0, 8, 8);
// Interlacing pass 2
encodePass(dos, ras, 4, 0, 8, 8);
// Interlacing pass 3
encodePass(dos, ras, 0, 4, 4, 8);
// Interlacing pass 4
encodePass(dos, ras, 2, 0, 4, 4);
// Interlacing pass 5
encodePass(dos, ras, 0, 2, 2, 4);
// Interlacing pass 6
encodePass(dos, ras, 1, 0, 2, 2);
// Interlacing pass 7
encodePass(dos, ras, 0, 1, 1, 2);
} else {
encodePass(dos, ras, 0, 0, 1, 1);
}
dos.finish();
dos.close();
ios.flush();
ios.close();
}
private void writeIEND() throws IOException {
ChunkStream cs = new ChunkStream("IEND");
try {
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
private static final float[] SRGB_CHROMA = {
0.31270F, 0.329F, 0.64F, 0.33F, 0.3F, 0.6F, 0.15F, 0.06F
};
private void writeCHRM() throws IOException {
if (param.isChromaticitySet() || param.isSRGBIntentSet()) {
ChunkStream cs = new ChunkStream("cHRM");
try {
float[] chroma;
if (!param.isSRGBIntentSet()) {
chroma = param.getChromaticity();
} else {
chroma = SRGB_CHROMA; // SRGB chromaticities
}
for (int i = 0; i < 8; i++) {
cs.writeInt((int)(chroma[i] * 100000));
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeGAMA() throws IOException {
if (param.isGammaSet() || param.isSRGBIntentSet()) {
ChunkStream cs = new ChunkStream("gAMA");
try {
float gamma;
if (!param.isSRGBIntentSet()) {
gamma = param.getGamma();
} else {
gamma = 1.0F / 2.2F; // SRGB gamma
}
// TD should include the .5 but causes regard to say
// everything is different.
cs.writeInt((int)(gamma * 100000/*+0.5*/));
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeICCP() throws IOException {
if (param.isICCProfileDataSet()) {
ChunkStream cs = new ChunkStream("iCCP");
try {
byte[] iccProfileData = param.getICCProfileData();
cs.write(iccProfileData);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeSBIT() throws IOException {
if (param.isSignificantBitsSet()) {
ChunkStream cs = new ChunkStream("sBIT");
try {
int[] significantBits = param.getSignificantBits();
int len = significantBits.length;
for (int i = 0; i < len; i++) {
cs.writeByte(significantBits[i]);
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeSRGB() throws IOException {
if (param.isSRGBIntentSet()) {
ChunkStream cs = new ChunkStream("sRGB");
try {
int intent = param.getSRGBIntent();
cs.write(intent);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writePLTE() throws IOException {
if (redPalette == null) {
return;
}
ChunkStream cs = new ChunkStream("PLTE");
try {
for (int i = 0; i < redPalette.length; i++) {
cs.writeByte(redPalette[i]);
cs.writeByte(greenPalette[i]);
cs.writeByte(bluePalette[i]);
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
private void writeBKGD() throws IOException {
if (param.isBackgroundSet()) {
ChunkStream cs = new ChunkStream("bKGD");
try {
switch (colorType) {
case PNG_COLOR_GRAY:
case PNG_COLOR_GRAY_ALPHA:
int gray = ((PNGEncodeParam.Gray)param).getBackgroundGray();
cs.writeShort(gray);
break;
case PNG_COLOR_PALETTE:
int index =
((PNGEncodeParam.Palette)param).getBackgroundPaletteIndex();
cs.writeByte(index);
break;
case PNG_COLOR_RGB:
case PNG_COLOR_RGB_ALPHA:
int[] rgb = ((PNGEncodeParam.RGB)param).getBackgroundRGB();
cs.writeShort(rgb[0]);
cs.writeShort(rgb[1]);
cs.writeShort(rgb[2]);
break;
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeHIST() throws IOException {
if (param.isPaletteHistogramSet()) {
ChunkStream cs = new ChunkStream("hIST");
try {
int[] hist = param.getPaletteHistogram();
for (int i = 0; i < hist.length; i++) {
cs.writeShort(hist[i]);
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeTRNS() throws IOException {
if (param.isTransparencySet()
&& (colorType != PNG_COLOR_GRAY_ALPHA)
&& (colorType != PNG_COLOR_RGB_ALPHA)) {
ChunkStream cs = new ChunkStream("tRNS");
try {
if (param instanceof PNGEncodeParam.Palette) {
byte[] t =
((PNGEncodeParam.Palette)param).getPaletteTransparency();
for (int i = 0; i < t.length; i++) {
cs.writeByte(t[i]);
}
} else if (param instanceof PNGEncodeParam.Gray) {
int t = ((PNGEncodeParam.Gray)param).getTransparentGray();
cs.writeShort(t);
} else if (param instanceof PNGEncodeParam.RGB) {
int[] t = ((PNGEncodeParam.RGB)param).getTransparentRGB();
cs.writeShort(t[0]);
cs.writeShort(t[1]);
cs.writeShort(t[2]);
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
} else if (colorType == PNG_COLOR_PALETTE) {
int lastEntry = Math.min(255, alphaPalette.length - 1);
int nonOpaque;
for (nonOpaque = lastEntry; nonOpaque >= 0; nonOpaque--) {
if (alphaPalette[nonOpaque] != (byte)255) {
break;
}
}
if (nonOpaque >= 0) {
ChunkStream cs = new ChunkStream("tRNS");
try {
for (int i = 0; i <= nonOpaque; i++) {
cs.writeByte(alphaPalette[i]);
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
}
private void writePHYS() throws IOException {
if (param.isPhysicalDimensionSet()) {
ChunkStream cs = new ChunkStream("pHYs");
try {
int[] dims = param.getPhysicalDimension();
cs.writeInt(dims[0]);
cs.writeInt(dims[1]);
cs.writeByte((byte)dims[2]);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeSPLT() throws IOException {
if (param.isSuggestedPaletteSet()) {
ChunkStream cs = new ChunkStream("sPLT");
try {
System.out.println("sPLT not supported yet.");
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeTIME() throws IOException {
if (param.isModificationTimeSet()) {
ChunkStream cs = new ChunkStream("tIME");
try {
Date date = param.getModificationTime();
TimeZone gmt = TimeZone.getTimeZone("GMT");
GregorianCalendar cal = new GregorianCalendar(gmt);
cal.setTime(date);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
cs.writeShort(year);
cs.writeByte(month + 1);
cs.writeByte(day);
cs.writeByte(hour);
cs.writeByte(minute);
cs.writeByte(second);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
private void writeTEXT() throws IOException {
if (param.isTextSet()) {
String[] text = param.getText();
for (int i = 0; i < text.length / 2; i++) {
byte[] keyword = text[2 * i].getBytes("UTF-8");
byte[] value = text[2 * i + 1].getBytes("UTF-8");
ChunkStream cs = new ChunkStream("tEXt");
try {
cs.write(keyword, 0, Math.min(keyword.length, 79));
cs.write(0);
cs.write(value);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
}
private void writeZTXT() throws IOException {
if (param.isCompressedTextSet()) {
String[] text = param.getCompressedText();
for (int i = 0; i < text.length / 2; i++) {
byte[] keyword = text[2 * i].getBytes("UTF-8");
byte[] value = text[2 * i + 1].getBytes("UTF-8");
ChunkStream cs = new ChunkStream("zTXt");
try {
cs.write(keyword, 0, Math.min(keyword.length, 79));
cs.write(0);
cs.write(0);
DeflaterOutputStream dos = new DeflaterOutputStream(cs);
try {
dos.write(value);
dos.finish();
} finally {
dos.close();
}
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
}
private void writePrivateChunks() throws IOException {
int numChunks = param.getNumPrivateChunks();
for (int i = 0; i < numChunks; i++) {
String type = param.getPrivateChunkType(i);
byte[] data = param.getPrivateChunkData(i);
ChunkStream cs = new ChunkStream(type);
try {
cs.write(data);
cs.writeToStream(dataOutput);
} finally {
cs.close();
}
}
}
/**
* Analyzes a set of palettes and determines if it can be expressed
* as a standard set of gray values, with zero or one values being
* fully transparent and the rest being fully opaque. If it
* is possible to express the data thusly, the method returns
* a suitable instance of PNGEncodeParam.Gray; otherwise it
* returns null.
*/
private PNGEncodeParam.Gray createGrayParam(byte[] redPalette,
byte[] greenPalette,
byte[] bluePalette,
byte[] alphaPalette) {
PNGEncodeParam.Gray param = new PNGEncodeParam.Gray();
int numTransparent = 0;
int grayFactor = 255 / ((1 << bitDepth) - 1);
int entries = 1 << bitDepth;
for (int i = 0; i < entries; i++) {
byte red = redPalette[i];
if ((red != i * grayFactor)
|| (red != greenPalette[i])
|| (red != bluePalette[i])) {
return null;
}
// All alphas must be 255 except at most 1 can be 0
byte alpha = alphaPalette[i];
if (alpha == (byte)0) {
param.setTransparentGray(i);
++numTransparent;
if (numTransparent > 1) {
return null;
}
} else if (alpha != (byte)255) {
return null;
}
}
return param;
}
/**
* This method encodes a RenderedImage
into PNG.
* The stream into which the PNG is dumped is not closed at
* the end of the operation, this should be done if needed
* by the caller of this method.
*/
@Override
public void encode(RenderedImage im) throws IOException {
this.image = im;
this.width = image.getWidth();
this.height = image.getHeight();
SampleModel sampleModel = image.getSampleModel();
int[] sampleSize = sampleModel.getSampleSize();
// Set bitDepth to a sentinel value
this.bitDepth = -1;
this.bitShift = 0;
// Allow user to override the bit depth of gray images
if (param instanceof PNGEncodeParam.Gray) {
PNGEncodeParam.Gray paramg = (PNGEncodeParam.Gray)param;
if (paramg.isBitDepthSet()) {
this.bitDepth = paramg.getBitDepth();
}
if (paramg.isBitShiftSet()) {
this.bitShift = paramg.getBitShift();
}
}
// Get bit depth from image if not set in param
if (this.bitDepth == -1) {
// Get bit depth from channel 0 of the image
this.bitDepth = sampleSize[0];
// Ensure all channels have the same bit depth
for (int i = 1; i < sampleSize.length; i++) {
if (sampleSize[i] != bitDepth) {
throw new RuntimeException(PropertyUtil.getString("PNGImageEncoder0"));
}
}
// Round bit depth up to a power of 2
if (bitDepth > 2 && bitDepth < 4) {
bitDepth = 4;
} else if (bitDepth > 4 && bitDepth < 8) {
bitDepth = 8;
} else if (bitDepth > 8 && bitDepth < 16) {
bitDepth = 16;
} else if (bitDepth > 16) {
throw new RuntimeException(PropertyUtil.getString("PNGImageEncoder1"));
}
}
this.numBands = sampleModel.getNumBands();
this.bpp = numBands * ((bitDepth == 16) ? 2 : 1);
ColorModel colorModel = image.getColorModel();
if (colorModel instanceof IndexColorModel) {
if (bitDepth < 1 || bitDepth > 8) {
throw new RuntimeException(PropertyUtil.getString("PNGImageEncoder2"));
}
if (sampleModel.getNumBands() != 1) {
throw new RuntimeException(PropertyUtil.getString("PNGImageEncoder3"));
}
IndexColorModel icm = (IndexColorModel)colorModel;
int size = icm.getMapSize();
redPalette = new byte[size];
greenPalette = new byte[size];
bluePalette = new byte[size];
alphaPalette = new byte[size];
icm.getReds(redPalette);
icm.getGreens(greenPalette);
icm.getBlues(bluePalette);
icm.getAlphas(alphaPalette);
this.bpp = 1;
if (param == null) {
param = createGrayParam(redPalette,
greenPalette,
bluePalette,
alphaPalette);
}
// If param is still null, it can't be expressed as gray
if (param == null) {
param = new PNGEncodeParam.Palette();
}
if (param instanceof PNGEncodeParam.Palette) {
// If palette not set in param, create one from the ColorModel.
PNGEncodeParam.Palette parami = (PNGEncodeParam.Palette)param;
if (parami.isPaletteSet()) {
int[] palette = parami.getPalette();
size = palette.length / 3;
int index = 0;
for (int i = 0; i < size; i++) {
redPalette[i] = (byte)palette[index++];
greenPalette[i] = (byte)palette[index++];
bluePalette[i] = (byte)palette[index++];
alphaPalette[i] = (byte)255;
}
}
this.colorType = PNG_COLOR_PALETTE;
} else if (param instanceof PNGEncodeParam.Gray) {
redPalette = greenPalette = bluePalette = alphaPalette = null;
this.colorType = PNG_COLOR_GRAY;
} else {
throw new RuntimeException(PropertyUtil.getString("PNGImageEncoder4"));
}
} else if (numBands == 1) {
if (param == null) {
param = new PNGEncodeParam.Gray();
}
this.colorType = PNG_COLOR_GRAY;
} else if (numBands == 2) {
if (param == null) {
param = new PNGEncodeParam.Gray();
}
if (param.isTransparencySet()) {
skipAlpha = true;
numBands = 1;
if ((sampleSize[0] == 8) && (bitDepth < 8)) {
compressGray = true;
}
bpp = (bitDepth == 16) ? 2 : 1;
this.colorType = PNG_COLOR_GRAY;
} else {
if (this.bitDepth < 8) {
this.bitDepth = 8;
}
this.colorType = PNG_COLOR_GRAY_ALPHA;
}
} else if (numBands == 3) {
if (param == null) {
param = new PNGEncodeParam.RGB();
}
this.colorType = PNG_COLOR_RGB;
} else if (numBands == 4) {
if (param == null) {
param = new PNGEncodeParam.RGB();
}
if (param.isTransparencySet()) {
skipAlpha = true;
numBands = 3;
bpp = (bitDepth == 16) ? 6 : 3;
this.colorType = PNG_COLOR_RGB;
} else {
this.colorType = PNG_COLOR_RGB_ALPHA;
}
}
interlace = param.getInterlacing();
writeMagic();
writeIHDR();
writeCHRM();
writeGAMA();
writeICCP();
writeSBIT();
writeSRGB();
writePLTE();
writeHIST();
writeTRNS();
writeBKGD();
writePHYS();
writeSPLT();
writeTIME();
writeTEXT();
writeZTXT();
writePrivateChunks();
writeIDAT();
writeIEND();
dataOutput.flush();
dataOutput.close();
}
}