org.apache.xmlgraphics.image.codec.png.PNGEncodeParam Maven / Gradle / Ivy
Show all versions of xmlgraphics-commons Show documentation
/*
* 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: PNGEncodeParam.java 1732018 2016-02-24 04:51:06Z gadams $ */
package org.apache.xmlgraphics.image.codec.png;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.xmlgraphics.image.codec.util.ImageEncodeParam;
import org.apache.xmlgraphics.image.codec.util.PropertyUtil;
// CSOFF: MemberName
// CSOFF: MultipleVariableDeclarations
// CSOFF: NeedBraces
// CSOFF: OperatorWrap
// CSOFF: ParameterName
// CSOFF: WhitespaceAround
/**
* An instance of ImageEncodeParam
for encoding images in
* the PNG format.
*
* This class is not a committed part of the JAI API. It may
* be removed or changed in future releases of JAI.
*/
public abstract class PNGEncodeParam implements ImageEncodeParam {
private static final long serialVersionUID = -7851509538552141263L;
/** Constant for use with the sRGB chunk. */
public static final int INTENT_PERCEPTUAL = 0;
/** Constant for use with the sRGB chunk. */
public static final int INTENT_RELATIVE = 1;
/** Constant for use with the sRGB chunk. */
public static final int INTENT_SATURATION = 2;
/** Constant for use with the sRGB chunk. */
public static final int INTENT_ABSOLUTE = 3;
/** Constant for use in filtering. */
public static final int PNG_FILTER_NONE = 0;
/** Constant for use in filtering. */
public static final int PNG_FILTER_SUB = 1;
/** Constant for use in filtering. */
public static final int PNG_FILTER_UP = 2;
/** Constant for use in filtering. */
public static final int PNG_FILTER_AVERAGE = 3;
/** Constant for use in filtering. */
public static final int PNG_FILTER_PAETH = 4;
/**
* Returns an instance of PNGEncodeParam.Palette
,
* PNGEncodeParam.Gray
, or
* PNGEncodeParam.RGB
appropriate for encoding
* the given image.
*
*
If the image has an IndexColorModel
, an
* instance of PNGEncodeParam.Palette
is returned.
* Otherwise, if the image has 1 or 2 bands an instance of
* PNGEncodeParam.Gray
is returned. In all other
* cases an instance of PNGEncodeParam.RGB
is
* returned.
*
*
Note that this method does not provide any guarantee that
* the given image will be successfully encoded by the PNG
* encoder, as it only performs a very superficial analysis of
* the image structure.
*/
public static PNGEncodeParam getDefaultEncodeParam(RenderedImage im) {
ColorModel colorModel = im.getColorModel();
if (colorModel instanceof IndexColorModel) {
return new PNGEncodeParam.Palette();
}
SampleModel sampleModel = im.getSampleModel();
int numBands = sampleModel.getNumBands();
if (numBands == 1 || numBands == 2) {
return new PNGEncodeParam.Gray();
} else {
return new PNGEncodeParam.RGB();
}
}
public static class Palette extends PNGEncodeParam {
private static final long serialVersionUID = -5181545170427733891L;
/** Constructs an instance of PNGEncodeParam.Palette
. */
public Palette() { }
// bKGD chunk
private boolean backgroundSet;
/**
* Suppresses the 'bKGD' chunk from being output.
*/
public void unsetBackground() {
backgroundSet = false;
}
/**
* Returns true if a 'bKGD' chunk will be output.
*/
public boolean isBackgroundSet() {
return backgroundSet;
}
/**
* Sets the desired bit depth for a palette image. The bit
* depth must be one of 1, 2, 4, or 8, or else an
* IllegalArgumentException
will be thrown.
*/
public void setBitDepth(int bitDepth) {
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4
&& bitDepth != 8) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam2"));
}
this.bitDepth = bitDepth;
bitDepthSet = true;
}
// PLTE chunk
private int[] palette;
private boolean paletteSet;
/**
* Sets the RGB palette of the image to be encoded.
* The rgb
parameter contains alternating
* R, G, B values for each color index used in the image.
* The number of elements must be a multiple of 3 between
* 3 and 3*256.
*
*
The 'PLTE' chunk will encode this information.
*
* @param rgb An array of int
s.
*/
public void setPalette(int[] rgb) {
if (rgb.length < 1 * 3 || rgb.length > 256 * 3) {
throw new
IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam0"));
}
if ((rgb.length % 3) != 0) {
throw new
IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam1"));
}
palette = (int[])(rgb.clone());
paletteSet = true;
}
/**
* Returns the current RGB palette.
*
*
If the palette has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the palette is not set.
*
* @return An array of int
s.
*/
public int[] getPalette() {
if (!paletteSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam3"));
}
return (int[])(palette.clone());
}
/**
* Suppresses the 'PLTE' chunk from being output.
*/
public void unsetPalette() {
palette = null;
paletteSet = false;
}
/**
* Returns true if a 'PLTE' chunk will be output.
*/
public boolean isPaletteSet() {
return paletteSet;
}
// bKGD chunk
private int backgroundPaletteIndex;
/**
* Sets the palette index of the suggested background color.
*
*
The 'bKGD' chunk will encode this information.
*/
public void setBackgroundPaletteIndex(int index) {
backgroundPaletteIndex = index;
backgroundSet = true;
}
/**
* Returns the palette index of the suggested background color.
*
*
If the background palette index has not previously been
* set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the palette index is not set.
*/
public int getBackgroundPaletteIndex() {
if (!backgroundSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam4"));
}
return backgroundPaletteIndex;
}
// tRNS chunk
private int[] transparency;
/**
* Sets the alpha values associated with each palette entry.
* The alpha
parameter should have as many entries
* as there are RGB triples in the palette.
*
*
The 'tRNS' chunk will encode this information.
*/
public void setPaletteTransparency(byte[] alpha) {
transparency = new int[alpha.length];
for (int i = 0; i < alpha.length; i++) {
transparency[i] = alpha[i] & 0xff;
}
transparencySet = true;
}
/**
* Returns the alpha values associated with each palette entry.
*
*
If the palette transparency has not previously been
* set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the palette transparency is
* not set.
*/
public byte[] getPaletteTransparency() {
if (!transparencySet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam5"));
}
byte[] alpha = new byte[transparency.length];
for (int i = 0; i < alpha.length; i++) {
alpha[i] = (byte)transparency[i];
}
return alpha;
}
}
public static class Gray extends PNGEncodeParam {
private static final long serialVersionUID = -2055439792025795274L;
/** Constructs an instance of PNGEncodeParam.Gray
. */
public Gray() { }
// bKGD chunk
private boolean backgroundSet;
/**
* Suppresses the 'bKGD' chunk from being output.
*/
public void unsetBackground() {
backgroundSet = false;
}
/**
* Returns true if a 'bKGD' chunk will be output.
*/
public boolean isBackgroundSet() {
return backgroundSet;
}
/**
* Sets the desired bit depth for a grayscale image. The bit
* depth must be one of 1, 2, 4, 8, or 16.
*
*
When encoding a source image of a greater bit depth,
* pixel values will be clamped to the smaller range after
* shifting by the value given by getBitShift()
.
* When encoding a source image of a smaller bit depth, pixel
* values will be shifted and left-filled with zeroes.
*/
public void setBitDepth(int bitDepth) {
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4
&& bitDepth != 8 && bitDepth != 16) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam2"));
}
this.bitDepth = bitDepth;
bitDepthSet = true;
}
// bKGD chunk
private int backgroundPaletteGray;
/**
* Sets the suggested gray level of the background.
*
*
The 'bKGD' chunk will encode this information.
*/
public void setBackgroundGray(int gray) {
backgroundPaletteGray = gray;
backgroundSet = true;
}
/**
* Returns the suggested gray level of the background.
*
*
If the background gray level has not previously been
* set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the background gray level
* is not set.
*/
public int getBackgroundGray() {
if (!backgroundSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam6"));
}
return backgroundPaletteGray;
}
// tRNS chunk
private int[] transparency;
/**
* Sets the gray value to be used to denote transparency.
*
*
Setting this attribute will cause the alpha channel
* of the input image to be ignored.
*
*
The 'tRNS' chunk will encode this information.
*/
public void setTransparentGray(int transparentGray) {
transparency = new int[1];
transparency[0] = transparentGray;
transparencySet = true;
}
/**
* Returns the gray value to be used to denote transparency.
*
*
If the transparent gray value has not previously been
* set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the transparent gray value
* is not set.
*/
public int getTransparentGray() {
if (!transparencySet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam7"));
}
int gray = transparency[0];
return gray;
}
private int bitShift;
private boolean bitShiftSet;
/**
* Sets the desired bit shift for a grayscale image.
* Pixels in the source image will be shifted right by
* the given amount prior to being clamped to the maximum
* value given by the encoded image's bit depth.
*/
public void setBitShift(int bitShift) {
if (bitShift < 0) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam25"));
}
this.bitShift = bitShift;
bitShiftSet = true;
}
/**
* Returns the desired bit shift for a grayscale image.
*
*
If the bit shift has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the bit shift is not set.
*/
public int getBitShift() {
if (!bitShiftSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam8"));
}
return bitShift;
}
/**
* Suppresses the setting of the bit shift of a grayscale image.
* Pixels in the source image will not be shifted prior to encoding.
*/
public void unsetBitShift() {
bitShiftSet = false;
}
/**
* Returns true if the bit shift has been set.
*/
public boolean isBitShiftSet() {
return bitShiftSet;
}
/**
* Returns true if the bit depth has been set.
*/
public boolean isBitDepthSet() {
return bitDepthSet;
}
}
public static class RGB extends PNGEncodeParam {
private static final long serialVersionUID = -8918762026006670891L;
/** Constructs an instance of PNGEncodeParam.RGB
. */
public RGB() { }
// bKGD chunk
private boolean backgroundSet;
/**
* Suppresses the 'bKGD' chunk from being output.
*/
public void unsetBackground() {
backgroundSet = false;
}
/**
* Returns true if a 'bKGD' chunk will be output.
*/
public boolean isBackgroundSet() {
return backgroundSet;
}
/**
* Sets the desired bit depth for an RGB image. The bit
* depth must be 8 or 16.
*/
public void setBitDepth(int bitDepth) {
if (bitDepth != 8 && bitDepth != 16) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam26"));
}
this.bitDepth = bitDepth;
bitDepthSet = true;
}
// bKGD chunk
private int[] backgroundRGB;
/**
* Sets the RGB value of the suggested background color.
* The rgb
parameter should have 3 entries.
*
*
The 'bKGD' chunk will encode this information.
*/
public void setBackgroundRGB(int[] rgb) {
if (rgb.length != 3) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam27"));
}
backgroundRGB = rgb;
backgroundSet = true;
}
/**
* Returns the RGB value of the suggested background color.
*
*
If the background color has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the background color is not set.
*/
public int[] getBackgroundRGB() {
if (!backgroundSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam9"));
}
return backgroundRGB;
}
// tRNS chunk
private int[] transparency;
/**
* Sets the RGB value to be used to denote transparency.
*
*
Setting this attribute will cause the alpha channel
* of the input image to be ignored.
*
*
The 'tRNS' chunk will encode this information.
*/
public void setTransparentRGB(int[] transparentRGB) {
transparency = (int[])(transparentRGB.clone());
transparencySet = true;
}
/**
* Returns the RGB value to be used to denote transparency.
*
*
If the transparent color has not previously been set,
* or has been unset, an IllegalStateException
* will be thrown.
*
* @throws IllegalStateException if the transparent color is not set.
*/
public int[] getTransparentRGB() {
if (!transparencySet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam10"));
}
return (int[])(transparency.clone());
}
}
protected int bitDepth;
protected boolean bitDepthSet;
/**
* Sets the desired bit depth of an image.
*/
public abstract void setBitDepth(int bitDepth);
/**
* Returns the desired bit depth for a grayscale image.
*
*
If the bit depth has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the bit depth is not set.
*/
public int getBitDepth() {
if (!bitDepthSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam11"));
}
return bitDepth;
}
/**
* Suppresses the setting of the bit depth of a grayscale image.
* The depth of the encoded image will be inferred from the source
* image bit depth, rounded up to the next power of 2 between 1
* and 16.
*/
public void unsetBitDepth() {
bitDepthSet = false;
}
private boolean useInterlacing;
/**
* Turns Adam7 interlacing on or off.
*/
public void setInterlacing(boolean useInterlacing) {
this.useInterlacing = useInterlacing;
}
/**
* Returns true
if Adam7 interlacing will be used.
*/
public boolean getInterlacing() {
return useInterlacing;
}
// bKGD chunk - delegate to subclasses
// In JAI 1.0, 'backgroundSet' was private. The JDK 1.2 compiler
// was lenient and incorrectly allowed this variable to be
// accessed from the subclasses. The JDK 1.3 compiler correctly
// flags this as a use of a non-static variable in a static
// context. Changing 'backgroundSet' to protected would have
// solved the problem, but would have introduced a visible API
// change. Thus we are forced to adopt the solution of placing a
// separate private variable in each subclass and providing
// separate implementations of 'unsetBackground' and
// 'isBackgroundSet' in each concrete subclass.
/**
* Suppresses the 'bKGD' chunk from being output.
* For API compatibility with JAI 1.0, the superclass
* defines this method to throw a RuntimeException
;
* accordingly, subclasses must provide their own implementations.
*/
public void unsetBackground() {
throw new RuntimeException(PropertyUtil.getString("PNGEncodeParam23"));
}
/**
* Returns true if a 'bKGD' chunk will be output.
* For API compatibility with JAI 1.0, the superclass
* defines this method to throw a RuntimeException
;
* accordingly, subclasses must provide their own implementations.
*/
public boolean isBackgroundSet() {
throw new RuntimeException(PropertyUtil.getString("PNGEncodeParam24"));
}
// cHRM chunk
private float[] chromaticity;
private boolean chromaticitySet;
/**
* Sets the white point and primary chromaticities in CIE (x, y)
* space.
*
*
The chromaticity
parameter should be a
* float
array of length 8 containing the white point
* X and Y, red X and Y, green X and Y, and blue X and Y values in
* order.
*
*
The 'cHRM' chunk will encode this information.
*/
public void setChromaticity(float[] chromaticity) {
if (chromaticity.length != 8) {
throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam28"));
}
this.chromaticity = (float[])(chromaticity.clone());
chromaticitySet = true;
}
/**
* A convenience method that calls the array version.
*/
public void setChromaticity(float whitePointX, float whitePointY,
float redX, float redY,
float greenX, float greenY,
float blueX, float blueY) {
float[] chroma = new float[8];
chroma[0] = whitePointX;
chroma[1] = whitePointY;
chroma[2] = redX;
chroma[3] = redY;
chroma[4] = greenX;
chroma[5] = greenY;
chroma[6] = blueX;
chroma[7] = blueY;
setChromaticity(chroma);
}
/**
* Returns the white point and primary chromaticities in
* CIE (x, y) space.
*
*
See the documentation for the setChromaticity
* method for the format of the returned data.
*
*
If the chromaticity has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the chromaticity is not set.
*/
public float[] getChromaticity() {
if (!chromaticitySet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam12"));
}
return (float[])(chromaticity.clone());
}
/**
* Suppresses the 'cHRM' chunk from being output.
*/
public void unsetChromaticity() {
chromaticity = null;
chromaticitySet = false;
}
/**
* Returns true if a 'cHRM' chunk will be output.
*/
public boolean isChromaticitySet() {
return chromaticitySet;
}
// gAMA chunk
private float gamma;
private boolean gammaSet;
/**
* Sets the file gamma value for the image.
*
*
The 'gAMA' chunk will encode this information.
*/
public void setGamma(float gamma) {
this.gamma = gamma;
gammaSet = true;
}
/**
* Returns the file gamma value for the image.
*
*
If the file gamma has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the gamma is not set.
*/
public float getGamma() {
if (!gammaSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam13"));
}
return gamma;
}
/**
* Suppresses the 'gAMA' chunk from being output.
*/
public void unsetGamma() {
gammaSet = false;
}
/**
* Returns true if a 'gAMA' chunk will be output.
*/
public boolean isGammaSet() {
return gammaSet;
}
// hIST chunk
private int[] paletteHistogram;
private boolean paletteHistogramSet;
/**
* Sets the palette histogram to be stored with this image.
* The histogram consists of an array of integers, one per
* palette entry.
*
*
The 'hIST' chunk will encode this information.
*/
public void setPaletteHistogram(int[] paletteHistogram) {
this.paletteHistogram = (int[])(paletteHistogram.clone());
paletteHistogramSet = true;
}
/**
* Returns the palette histogram to be stored with this image.
*
*
If the histogram has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the histogram is not set.
*/
public int[] getPaletteHistogram() {
if (!paletteHistogramSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam14"));
}
return paletteHistogram;
}
/**
* Suppresses the 'hIST' chunk from being output.
*/
public void unsetPaletteHistogram() {
paletteHistogram = null;
paletteHistogramSet = false;
}
/**
* Returns true if a 'hIST' chunk will be output.
*/
public boolean isPaletteHistogramSet() {
return paletteHistogramSet;
}
// iCCP chunk
private byte[] iccProfileData;
private boolean iccProfileDataSet;
/**
* Sets the ICC profile data to be stored with this image.
* The profile is represented in raw binary form.
*
*
The 'iCCP' chunk will encode this information.
*/
public void setICCProfileData(byte[] iccProfileData) {
this.iccProfileData = (byte[])(iccProfileData.clone());
iccProfileDataSet = true;
}
/**
* Returns the ICC profile data to be stored with this image.
*
*
If the ICC profile has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the ICC profile is not set.
*/
public byte[] getICCProfileData() {
if (!iccProfileDataSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam15"));
}
return (byte[])(iccProfileData.clone());
}
/**
* Suppresses the 'iCCP' chunk from being output.
*/
public void unsetICCProfileData() {
iccProfileData = null;
iccProfileDataSet = false;
}
/**
* Returns true if a 'iCCP' chunk will be output.
*/
public boolean isICCProfileDataSet() {
return iccProfileDataSet;
}
// pHYS chunk
private int[] physicalDimension;
private boolean physicalDimensionSet;
/**
* Sets the physical dimension information to be stored with this
* image. The physicalDimension parameter should be a 3-entry
* array containing the number of pixels per unit in the X
* direction, the number of pixels per unit in the Y direction,
* and the unit specifier (0 = unknown, 1 = meters).
*
*
The 'pHYS' chunk will encode this information.
*/
public void setPhysicalDimension(int[] physicalDimension) {
this.physicalDimension = (int[])(physicalDimension.clone());
physicalDimensionSet = true;
}
/**
* A convenience method that calls the array version.
*/
public void setPhysicalDimension(int xPixelsPerUnit,
int yPixelsPerUnit,
int unitSpecifier) {
int[] pd = new int[3];
pd[0] = xPixelsPerUnit;
pd[1] = yPixelsPerUnit;
pd[2] = unitSpecifier;
setPhysicalDimension(pd);
}
/**
* Returns the physical dimension information to be stored
* with this image.
*
*
If the physical dimension information has not previously
* been set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the physical dimension information
* is not set.
*/
public int[] getPhysicalDimension() {
if (!physicalDimensionSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam16"));
}
return (int[])(physicalDimension.clone());
}
/**
* Suppresses the 'pHYS' chunk from being output.
*/
public void unsetPhysicalDimension() {
physicalDimension = null;
physicalDimensionSet = false;
}
/**
* Returns true if a 'pHYS' chunk will be output.
*/
public boolean isPhysicalDimensionSet() {
return physicalDimensionSet;
}
// sPLT chunk
private PNGSuggestedPaletteEntry[] suggestedPalette;
private boolean suggestedPaletteSet;
/**
* Sets the suggested palette information to be stored with this
* image. The information is passed to this method as an array of
* PNGSuggestedPaletteEntry
objects.
*
*
The 'sPLT' chunk will encode this information.
*/
public void setSuggestedPalette(PNGSuggestedPaletteEntry[] palette) {
suggestedPalette = (PNGSuggestedPaletteEntry[])(palette.clone());
suggestedPaletteSet = true;
}
/**
* Returns the suggested palette information to be stored with this
* image.
*
*
If the suggested palette information has not previously
* been set, or has been unset, an
* IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the suggested palette
* information is not set.
*/
public PNGSuggestedPaletteEntry[] getSuggestedPalette() {
if (!suggestedPaletteSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam17"));
}
return (PNGSuggestedPaletteEntry[])(suggestedPalette.clone());
}
/**
* Suppresses the 'sPLT' chunk from being output.
*/
public void unsetSuggestedPalette() {
suggestedPalette = null;
suggestedPaletteSet = false;
}
/**
* Returns true if a 'sPLT' chunk will be output.
*/
public boolean isSuggestedPaletteSet() {
return suggestedPaletteSet;
}
// sBIT chunk
private int[] significantBits;
private boolean significantBitsSet;
/**
* Sets the number of significant bits for each band of the image.
*
*
The number of entries in the significantBits
* array must be equal to the number of output bands in the image:
* 1 for a gray image, 2 for gray+alpha, 3 for index or truecolor,
* and 4 for truecolor+alpha.
*
*
The 'sBIT' chunk will encode this information.
*/
public void setSignificantBits(int[] significantBits) {
this.significantBits = (int[])(significantBits.clone());
significantBitsSet = true;
}
/**
* Returns the number of significant bits for each band of the image.
*
*
If the significant bits values have not previously been
* set, or have been unset, an IllegalStateException
* will be thrown.
*
* @throws IllegalStateException if the significant bits values are
* not set.
*/
public int[] getSignificantBits() {
if (!significantBitsSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam18"));
}
return (int[])significantBits.clone();
}
/**
* Suppresses the 'sBIT' chunk from being output.
*/
public void unsetSignificantBits() {
significantBits = null;
significantBitsSet = false;
}
/**
* Returns true if an 'sBIT' chunk will be output.
*/
public boolean isSignificantBitsSet() {
return significantBitsSet;
}
// sRGB chunk
private int srgbIntent;
private boolean srgbIntentSet;
/**
* Sets the sRGB rendering intent to be stored with this image.
* The legal values are 0 = Perceptual, 1 = Relative Colorimetric,
* 2 = Saturation, and 3 = Absolute Colorimetric. Refer to the
* PNG specification for information on these values.
*
*
The 'sRGB' chunk will encode this information.
*/
public void setSRGBIntent(int srgbIntent) {
this.srgbIntent = srgbIntent;
srgbIntentSet = true;
}
/**
* Returns the sRGB rendering intent to be stored with this image.
*
*
If the sRGB intent has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the sRGB intent is not set.
*/
public int getSRGBIntent() {
if (!srgbIntentSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam19"));
}
return srgbIntent;
}
/**
* Suppresses the 'sRGB' chunk from being output.
*/
public void unsetSRGBIntent() {
srgbIntentSet = false;
}
/**
* Returns true if an 'sRGB' chunk will be output.
*/
public boolean isSRGBIntentSet() {
return srgbIntentSet;
}
// tEXt chunk
private String[] text;
private boolean textSet;
/**
* Sets the textual data to be stored in uncompressed form with this
* image. The data is passed to this method as an array of
* String
s.
*
*
The 'tEXt' chunk will encode this information.
*/
public void setText(String[] text) {
this.text = text;
textSet = true;
}
/**
* Returns the text strings to be stored in uncompressed form with this
* image as an array of String
s.
*
*
If the text strings have not previously been set, or have been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the text strings are not set.
*/
public String[] getText() {
if (!textSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam20"));
}
return text;
}
/**
* Suppresses the 'tEXt' chunk from being output.
*/
public void unsetText() {
text = null;
textSet = false;
}
/**
* Returns true if a 'tEXt' chunk will be output.
*/
public boolean isTextSet() {
return textSet;
}
// tIME chunk
private Date modificationTime;
private boolean modificationTimeSet;
/**
* Sets the modification time, as a Date
, to be
* stored with this image. The internal storage format will use
* UTC regardless of how the modificationTime
* parameter was created.
*
*
The 'tIME' chunk will encode this information.
*/
public void setModificationTime(Date modificationTime) {
this.modificationTime = modificationTime;
modificationTimeSet = true;
}
/**
* Returns the modification time to be stored with this image.
*
*
If the bit depth has not previously been set, or has been
* unset, an IllegalStateException
will be thrown.
*
* @throws IllegalStateException if the bit depth is not set.
*/
public Date getModificationTime() {
if (!modificationTimeSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam21"));
}
return modificationTime;
}
/**
* Suppresses the 'tIME' chunk from being output.
*/
public void unsetModificationTime() {
modificationTime = null;
modificationTimeSet = false;
}
/**
* Returns true if a 'tIME' chunk will be output.
*/
public boolean isModificationTimeSet() {
return modificationTimeSet;
}
// tRNS chunk
boolean transparencySet;
/**
* Suppresses the 'tRNS' chunk from being output.
*/
public void unsetTransparency() {
transparencySet = false;
}
/**
* Returns true if a 'tRNS' chunk will be output.
*/
public boolean isTransparencySet() {
return transparencySet;
}
// zTXT chunk
private String[] zText;
private boolean zTextSet;
/**
* Sets the text strings to be stored in compressed form with this
* image. The data is passed to this method as an array of
* String
s.
*
*
The 'zTXt' chunk will encode this information.
*/
public void setCompressedText(String[] text) {
this.zText = text;
zTextSet = true;
}
/**
* Returns the text strings to be stored in compressed form with
* this image as an array of String
s.
*
*
If the compressed text strings have not previously been
* set, or have been unset, an IllegalStateException
* will be thrown.
*
* @throws IllegalStateException if the compressed text strings are
* not set.
*/
public String[] getCompressedText() {
if (!zTextSet) {
throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam22"));
}
return zText;
}
/**
* Suppresses the 'zTXt' chunk from being output.
*/
public void unsetCompressedText() {
zText = null;
zTextSet = false;
}
/**
* Returns true if a 'zTXT' chunk will be output.
*/
public boolean isCompressedTextSet() {
return zTextSet;
}
// Other chunk types
List chunkType = new ArrayList();
List chunkData = new ArrayList();
/**
* Adds a private chunk, in binary form, to the list of chunks to
* be stored with this image.
*
* @param type a 4-character String giving the chunk type name.
* @param data an array of byte
s containing the
* chunk data.
*/
public synchronized void addPrivateChunk(String type, byte[] data) {
chunkType.add(type);
chunkData.add(data.clone());
}
/**
* Returns the number of private chunks to be written to the
* output file.
*/
public synchronized int getNumPrivateChunks() {
return chunkType.size();
}
/**
* Returns the type of the private chunk at a given index, as a
* 4-character String
. The index must be smaller
* than the return value of getNumPrivateChunks
.
*/
public synchronized String getPrivateChunkType(int index) {
return (String)chunkType.get(index);
}
/**
* Returns the data associated of the private chunk at a given
* index, as an array of byte
s. The index must be
* smaller than the return value of
* getNumPrivateChunks
.
*/
public synchronized byte[] getPrivateChunkData(int index) {
return (byte[])chunkData.get(index);
}
/**
* Remove all private chunks associated with this parameter instance
* whose 'safe-to-copy' bit is not set. This may be advisable when
* transcoding PNG images.
*/
public synchronized void removeUnsafeToCopyPrivateChunks() {
List newChunkType = new ArrayList();
List newChunkData = new ArrayList();
int len = getNumPrivateChunks();
for (int i = 0; i < len; i++) {
String type = getPrivateChunkType(i);
char lastChar = type.charAt(3);
if (lastChar >= 'a' && lastChar <= 'z') {
newChunkType.add(type);
newChunkData.add(getPrivateChunkData(i));
}
}
chunkType = newChunkType;
chunkData = newChunkData;
}
/**
* Remove all private chunks associated with this parameter instance.
*/
public synchronized void removeAllPrivateChunks() {
chunkType = new ArrayList();
chunkData = new ArrayList();
}
/**
* An abs() function for use by the Paeth predictor.
*/
private static int abs(int x) {
return (x < 0) ? -x : x;
}
/**
* The Paeth predictor routine used in PNG encoding. This routine
* is included as a convenience to subclasses that override the
* filterRow
method.
*/
public static int paethPredictor(int a, int b, int c) {
int p = a + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c);
if ((pa <= pb) && (pa <= pc)) {
return a;
} else if (pb <= pc) {
return b;
} else {
return c;
}
}
/**
* Performs filtering on a row of an image. This method may be
* overridden in order to provide a custom algorithm for choosing
* the filter type for a given row.
*
*
The method is supplied with the current and previous rows
* of the image. For the first row of the image, or of an
* interlacing pass, the previous row array will be filled with
* zeros as required by the PNG specification.
*
*
The method is also supplied with five scratch arrays.
* These arrays may be used within the method for any purpose.
* At method exit, the array at the index given by the return
* value of the method should contain the filtered data. The
* return value will also be used as the filter type.
*
*
The default implementation of the method performs a trial
* encoding with each of the filter types, and computes the sum of
* absolute values of the differences between the raw bytes of the
* current row and the predicted values. The index of the filter
* producing the smallest result is returned.
*
*
As an example, to perform only 'sub' filtering, this method
* could be implemented (non-optimally) as follows:
*
*
* for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) {
* int curr = currRow[i] & 0xff;
* int left = currRow[i - bytesPerPixel] & 0xff;
* scratchRow[PNG_FILTER_SUB][i] = (byte)(curr - left);
* }
* return PNG_FILTER_SUB;
*
*
* @param currRow The current row as an array of byte
s
* of length at least bytesPerRow + bytesPerPixel
.
* The pixel data starts at index bytesPerPixel
;
* the initial bytesPerPixel
bytes are zero.
* @param prevRow The current row as an array of byte
s
* The pixel data starts at index bytesPerPixel
;
* the initial bytesPerPixel
bytes are zero.
* @param scratchRows An array of 5 byte
arrays of
* length at least bytesPerRow +
* bytesPerPixel
, useable to hold temporary results.
* The filtered row will be returned as one of the entries
* of this array. The returned filtered data should start
* at index bytesPerPixel
; The initial
* bytesPerPixel
bytes are not used.
* @param bytesPerRow The number of bytes in the image row.
* This value will always be greater than 0.
* @param bytesPerPixel The number of bytes representing a single
* pixel, rounded up to an integer. This is the 'bpp' parameter
* described in the PNG specification.
*
* @return The filter type to be used. The entry of
* scratchRows[]
at this index holds the
* filtered data. */
public int filterRow(byte[] currRow,
byte[] prevRow,
byte[][] scratchRows,
int bytesPerRow,
int bytesPerPixel) {
int [] badness = {0, 0, 0, 0, 0};
int curr;
int left;
int up;
int upleft;
int diff;
int pa;
int pb;
int pc;
for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) {
curr = currRow[i] & 0xff;
left = currRow[i - bytesPerPixel] & 0xff;
up = prevRow[i] & 0xff;
upleft = prevRow[i - bytesPerPixel] & 0xff;
// no filter
badness[0] += curr;
// sub filter
diff = curr - left;
scratchRows[1][i] = (byte)diff;
badness [1] += (diff > 0) ? diff : -diff;
// up filter
diff = curr - up;
scratchRows[2][i] = (byte)diff;
badness [2] += (diff >= 0) ? diff : -diff;
// average filter
diff = curr - ((left + up) >> 1);
scratchRows[3][i] = (byte)diff;
badness [3] += (diff >= 0) ? diff : -diff;
// paeth filter
// Original code much simplier but doesn't take full
// advantage of relationship between pa/b/c and
// information gleaned in abs operations.
/// pa = up -upleft;
/// pb = left-upleft;
/// pc = pa+pb;
/// pa = abs(pa);
/// pb = abs(pb);
/// pc = abs(pc);
/// if ((pa <= pb) && (pa <= pc))
/// diff = curr-left;
/// else if (pb <= pc)
/// diff = curr-up;
/// else
/// diff = curr-upleft;
pa = up - upleft;
pb = left - upleft;
if (pa < 0) {
if (pb < 0) {
// both pa & pb neg so pc is always greater than or
// equal to pa or pb;
if (pa >= pb) { // since pa & pb neg check sense is reversed.
diff = curr - left;
} else {
diff = curr - up;
}
} else {
// pa neg pb pos so we must compute pc...
pc = pa + pb;
pa = -pa;
if (pa <= pb) { // pc is positive and less than pb
if (pa <= pc) {
diff = curr - left;
} else {
diff = curr - upleft;
}
} else {
// pc is negative and less than or equal to pa,
// but since pa is greater than pb this isn't an issue...
if (pb <= -pc) {
diff = curr - up;
} else {
diff = curr - upleft;
}
}
}
} else {
if (pb < 0) {
pb = -pb; // make it positive...
if (pa <= pb) {
// pc would be negative and less than or equal to pb
pc = pb - pa;
if (pa <= pc) {
diff = curr - left;
} else if (pb == pc) {
// if pa is zero then pc==pb otherwise
// pc must be less than pb.
diff = curr - up;
} else {
diff = curr - upleft;
}
} else {
// pc would be positive and less than pa.
pc = pa - pb;
if (pb <= pc) {
diff = curr - up;
} else {
diff = curr - upleft;
}
}
} else {
// both pos so pa+pb is always greater than pa/pb
if (pa <= pb) {
diff = curr - left;
} else {
diff = curr - up;
}
}
}
scratchRows[4][i] = (byte)diff;
badness [4] += (diff >= 0) ? diff : -diff;
}
int filterType = 0;
int minBadness = badness[0];
for (int i = 1; i < 5; i++) {
if (badness[i] < minBadness) {
minBadness = badness[i];
filterType = i;
}
}
if (filterType == 0) {
System.arraycopy(currRow, bytesPerPixel,
scratchRows[0], bytesPerPixel,
bytesPerRow);
}
return filterType;
}
}