You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1045 lines
32 KiB
1045 lines
32 KiB
5 years ago
|
package com.fr.third.JAI;
|
||
|
/*
|
||
|
* Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
*
|
||
|
* -Redistributions of source code must retain the above copyright notice, this
|
||
|
* list of conditions and the following disclaimer.
|
||
|
*
|
||
|
* -Redistribution in binary form must reproduct the above copyright notice,
|
||
|
* this list of conditions and the following disclaimer in the documentation
|
||
|
* and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
|
||
|
* be used to endorse or promote products derived from this software without
|
||
|
* specific prior written permission.
|
||
|
*
|
||
|
* This software is provided "AS IS," without a warranty of any kind. ALL
|
||
|
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
|
||
|
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
|
||
|
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
|
||
|
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
|
||
|
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
|
||
|
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
|
||
|
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
|
||
|
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
|
||
|
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
|
||
|
* POSSIBILITY OF SUCH DAMAGES.
|
||
|
*
|
||
|
* You acknowledge that Software is not designed,licensed or intended for use in
|
||
|
* the design, construction, operation or maintenance of any nuclear facility.
|
||
|
*/
|
||
|
import java.awt.image.IndexColorModel;
|
||
|
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.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;
|
||
|
|
||
|
class 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;
|
||
|
|
||
|
public ChunkStream(String type) throws IOException {
|
||
|
this.type = type;
|
||
|
|
||
|
this.baos = new ByteArrayOutputStream();
|
||
|
this.dos = new DataOutputStream(baos);
|
||
|
}
|
||
|
|
||
|
public void write(byte[] b) throws IOException {
|
||
|
dos.write(b);
|
||
|
}
|
||
|
|
||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||
|
dos.write(b, off, len);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
class IDATOutputStream extends FilterOutputStream {
|
||
|
|
||
|
private static final byte[] typeSignature =
|
||
|
{(byte)'I', (byte)'D', (byte)'A', (byte)'T'};
|
||
|
|
||
|
private int bytesWritten = 0;
|
||
|
private int segmentLength;
|
||
|
byte[] buffer;
|
||
|
|
||
|
public IDATOutputStream(OutputStream output,
|
||
|
int segmentLength) {
|
||
|
super(output);
|
||
|
this.segmentLength = segmentLength;
|
||
|
this.buffer = new byte[segmentLength];
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
public void flush() throws IOException {
|
||
|
// Length
|
||
|
writeInt(bytesWritten);
|
||
|
// 'IDAT' signature
|
||
|
out.write(typeSignature);
|
||
|
// Data
|
||
|
out.write(buffer, 0, bytesWritten);
|
||
|
|
||
|
int crc = 0xffffffff;
|
||
|
crc = CRC.updateCRC(crc, typeSignature, 0, 4);
|
||
|
crc = CRC.updateCRC(crc, buffer, 0, bytesWritten);
|
||
|
|
||
|
// CRC
|
||
|
writeInt(crc ^ 0xffffffff);
|
||
|
|
||
|
// Reset buffer
|
||
|
bytesWritten = 0;
|
||
|
}
|
||
|
|
||
|
public void write(byte[] b) throws IOException {
|
||
|
this.write(b, 0, b.length);
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void write(int b) throws IOException {
|
||
|
buffer[bytesWritten++] = (byte)b;
|
||
|
if (bytesWritten == segmentLength) {
|
||
|
flush();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An ImageEncoder for the PNG file format.
|
||
|
*
|
||
|
*/
|
||
|
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 = false;
|
||
|
private boolean compressGray = false;
|
||
|
|
||
|
private boolean interlace;
|
||
|
|
||
|
private byte[] redPalette = null;
|
||
|
private byte[] greenPalette = null;
|
||
|
private byte[] bluePalette = null;
|
||
|
private byte[] alphaPalette = null;
|
||
|
|
||
|
private DataOutputStream dataOutput;
|
||
|
|
||
|
public PNGImageEncoder(OutputStream output,
|
||
|
PNGEncodeParam param) {
|
||
|
super(output, param);
|
||
|
|
||
|
if (param != null) {
|
||
|
this.param = (PNGEncodeParam)param;
|
||
|
}
|
||
|
this.dataOutput = new DataOutputStream(output);
|
||
|
}
|
||
|
|
||
|
private void writeMagic() throws IOException {
|
||
|
dataOutput.write(magic);
|
||
|
}
|
||
|
|
||
|
private void writeIHDR() throws IOException {
|
||
|
ChunkStream cs = new ChunkStream("IHDR");
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
private byte[] prevRow = null;
|
||
|
private byte[] currRow = null;
|
||
|
|
||
|
private byte[][] filteredRows = null;
|
||
|
|
||
|
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) == mask) {
|
||
|
currRow[count++] = (byte)tmp;
|
||
|
tmp = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Left shift the last byte
|
||
|
if ((pos & mask) != 0) {
|
||
|
tmp <<= ((8/bitDepth) - 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
|
||
|
Raster ras = image.getData();
|
||
|
|
||
|
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();
|
||
|
ios.flush();
|
||
|
}
|
||
|
|
||
|
private void writeIEND() throws IOException {
|
||
|
ChunkStream cs = new ChunkStream("IEND");
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
|
||
|
private static final float[] srgbChroma = {
|
||
|
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");
|
||
|
|
||
|
float[] chroma;
|
||
|
if (!param.isSRGBIntentSet()) {
|
||
|
chroma = param.getChromaticity();
|
||
|
} else {
|
||
|
chroma = srgbChroma; // SRGB chromaticities
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < 8; i++) {
|
||
|
cs.writeInt((int)(chroma[i]*100000));
|
||
|
}
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeGAMA() throws IOException {
|
||
|
if (param.isGammaSet() || param.isSRGBIntentSet()) {
|
||
|
ChunkStream cs = new ChunkStream("gAMA");
|
||
|
|
||
|
float gamma;
|
||
|
if (!param.isSRGBIntentSet()) {
|
||
|
gamma = param.getGamma();
|
||
|
} else {
|
||
|
gamma = 1.0F/2.2F; // SRGB gamma
|
||
|
}
|
||
|
|
||
|
cs.writeInt((int)(gamma*100000));
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeICCP() throws IOException {
|
||
|
if (param.isICCProfileDataSet()) {
|
||
|
ChunkStream cs = new ChunkStream("iCCP");
|
||
|
byte[] ICCProfileData = param.getICCProfileData();
|
||
|
cs.write(ICCProfileData);
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeSBIT() throws IOException {
|
||
|
if (param.isSignificantBitsSet()) {
|
||
|
ChunkStream cs = new ChunkStream("sBIT");
|
||
|
int[] significantBits = param.getSignificantBits();
|
||
|
int len = significantBits.length;
|
||
|
for (int i = 0; i < len; i++) {
|
||
|
cs.writeByte(significantBits[i]);
|
||
|
}
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeSRGB() throws IOException {
|
||
|
if (param.isSRGBIntentSet()) {
|
||
|
ChunkStream cs = new ChunkStream("sRGB");
|
||
|
|
||
|
int intent = param.getSRGBIntent();
|
||
|
cs.write(intent);
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writePLTE() throws IOException {
|
||
|
if (redPalette == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ChunkStream cs = new ChunkStream("PLTE");
|
||
|
for (int i = 0; i < redPalette.length; i++) {
|
||
|
cs.writeByte(redPalette[i]);
|
||
|
cs.writeByte(greenPalette[i]);
|
||
|
cs.writeByte(bluePalette[i]);
|
||
|
}
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
|
||
|
private void writeBKGD() throws IOException {
|
||
|
if (param.isBackgroundSet()) {
|
||
|
ChunkStream cs = new ChunkStream("bKGD");
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeHIST() throws IOException {
|
||
|
if (param.isPaletteHistogramSet()) {
|
||
|
ChunkStream cs = new ChunkStream("hIST");
|
||
|
|
||
|
int[] hist = param.getPaletteHistogram();
|
||
|
for (int i = 0; i < hist.length; i++) {
|
||
|
cs.writeShort(hist[i]);
|
||
|
}
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeTRNS() throws IOException {
|
||
|
if (param.isTransparencySet() &&
|
||
|
(colorType != PNG_COLOR_GRAY_ALPHA) &&
|
||
|
(colorType != PNG_COLOR_RGB_ALPHA)) {
|
||
|
ChunkStream cs = new ChunkStream("tRNS");
|
||
|
|
||
|
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);
|
||
|
} 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");
|
||
|
for (int i = 0; i <= nonOpaque; i++) {
|
||
|
cs.writeByte(alphaPalette[i]);
|
||
|
}
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writePHYS() throws IOException {
|
||
|
if (param.isPhysicalDimensionSet()) {
|
||
|
ChunkStream cs = new ChunkStream("pHYs");
|
||
|
|
||
|
int[] dims = param.getPhysicalDimension();
|
||
|
cs.writeInt(dims[0]);
|
||
|
cs.writeInt(dims[1]);
|
||
|
cs.writeByte((byte)dims[2]);
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeSPLT() throws IOException {
|
||
|
if (param.isSuggestedPaletteSet()) {
|
||
|
ChunkStream cs = new ChunkStream("sPLT");
|
||
|
|
||
|
System.out.println("sPLT not supported yet.");
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writeTIME() throws IOException {
|
||
|
if (param.isModificationTimeSet()) {
|
||
|
ChunkStream cs = new ChunkStream("tIME");
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
byte[] value = text[2*i + 1].getBytes();
|
||
|
|
||
|
ChunkStream cs = new ChunkStream("tEXt");
|
||
|
|
||
|
cs.write(keyword, 0, Math.min(keyword.length, 79));
|
||
|
cs.write(0);
|
||
|
cs.write(value);
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
byte[] value = text[2*i + 1].getBytes();
|
||
|
|
||
|
ChunkStream cs = new ChunkStream("zTXt");
|
||
|
|
||
|
cs.write(keyword, 0, Math.min(keyword.length, 79));
|
||
|
cs.write(0);
|
||
|
cs.write(0);
|
||
|
|
||
|
DeflaterOutputStream dos = new DeflaterOutputStream(cs);
|
||
|
dos.write(value);
|
||
|
dos.finish();
|
||
|
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void writePrivateChunks() throws IOException {
|
||
|
int numChunks = param.getNumPrivateChunks();
|
||
|
for (int i = 0; i < numChunks; i++) {
|
||
|
String type = param.getPrivateChunkType(i);
|
||
|
char char3 = type.charAt(3);
|
||
|
|
||
|
byte[] data = param.getPrivateChunkData(i);
|
||
|
|
||
|
ChunkStream cs = new ChunkStream(type);
|
||
|
cs.write(data);
|
||
|
cs.writeToStream(dataOutput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
if (sampleModel.getNumBands() != 1) {
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
} 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();
|
||
|
}
|
||
|
|
||
|
// public static void main(String[] args) {
|
||
|
// try {
|
||
|
// SeekableStream stream = new FileSeekableStream(args[0]);
|
||
|
// String[] names = ImageCodec.getDecoderNames(stream);
|
||
|
|
||
|
// ImageDecoder dec =
|
||
|
// ImageCodec.createImageDecoder(names[0], stream, null);
|
||
|
// RenderedImage im = dec.decodeAsRenderedImage();
|
||
|
|
||
|
// OutputStream output = new FileOutputStream(args[1]);
|
||
|
|
||
|
// PNGEncodeParam param = null;
|
||
|
// Object o = im.getProperty("encode_param");
|
||
|
// if ((o != null) && (o instanceof PNGEncodeParam)) {
|
||
|
// param = (PNGEncodeParam)o;
|
||
|
// } else {
|
||
|
// param = PNGEncodeParam.getDefaultEncodeParam(im);
|
||
|
// }
|
||
|
|
||
|
// if (param instanceof PNGEncodeParam.RGB) {
|
||
|
// int[] rgb = { 50, 100, 150 };
|
||
|
// ((PNGEncodeParam.RGB)param).setBackgroundRGB(rgb);
|
||
|
// }
|
||
|
|
||
|
// param.setChromaticity(0.32270F, 0.319F,
|
||
|
// 0.65F, 0.32F,
|
||
|
// 0.31F, 0.58F,
|
||
|
// 0.16F, 0.04F);
|
||
|
|
||
|
// param.setGamma(3.5F);
|
||
|
|
||
|
// int[] sbits = { 8, 8, 8 };
|
||
|
// param.setSignificantBits(sbits);
|
||
|
|
||
|
// param.setSRGBIntent(0);
|
||
|
|
||
|
// String[] text = new String[4];
|
||
|
// text[0] = "Title";
|
||
|
// text[1] = "PNG Test Image";
|
||
|
// text[2] = "Author";
|
||
|
// text[3] = "Daniel Rice";
|
||
|
// param.setText(text);
|
||
|
|
||
|
// String[] ztext = new String[2];
|
||
|
// ztext[0] = "Description";
|
||
|
// ztext[1] = "A really incredibly long-winded description of extremely little if any substance whatsoever.";
|
||
|
// param.setCompressedText(ztext);
|
||
|
|
||
|
// ImageEncoder enc = new PNGImageEncoder(output, param);
|
||
|
// enc.encode(im);
|
||
|
// } catch (Exception e) {
|
||
|
// e.printStackTrace();
|
||
|
// System.exit(1);
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
}
|
||
|
|