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.
2048 lines
83 KiB
2048 lines
83 KiB
/*
|
|
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package com.sun.imageio.plugins.png;
|
|
|
|
import java.awt.image.ColorModel;
|
|
import java.awt.image.IndexColorModel;
|
|
import java.awt.image.SampleModel;
|
|
import java.util.ArrayList;
|
|
import java.util.StringTokenizer;
|
|
import javax.imageio.ImageTypeSpecifier;
|
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
|
import javax.imageio.metadata.IIOMetadata;
|
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|
import javax.imageio.metadata.IIOMetadataNode;
|
|
import org.w3c.dom.Node;
|
|
|
|
public class PNGMetadata extends IIOMetadata implements Cloneable {
|
|
|
|
// package scope
|
|
public static final String
|
|
nativeMetadataFormatName = "javax_imageio_png_1.0";
|
|
|
|
protected static final String nativeMetadataFormatClassName
|
|
= "com.sun.imageio.plugins.png.PNGMetadataFormat";
|
|
|
|
// Color types for IHDR chunk
|
|
static final String[] IHDR_colorTypeNames = {
|
|
"Grayscale", null, "RGB", "Palette",
|
|
"GrayAlpha", null, "RGBAlpha"
|
|
};
|
|
|
|
static final int[] IHDR_numChannels = {
|
|
1, 0, 3, 3, 2, 0, 4
|
|
};
|
|
|
|
// Bit depths for IHDR chunk
|
|
static final String[] IHDR_bitDepths = {
|
|
"1", "2", "4", "8", "16"
|
|
};
|
|
|
|
// Compression methods for IHDR chunk
|
|
static final String[] IHDR_compressionMethodNames = {
|
|
"deflate"
|
|
};
|
|
|
|
// Filter methods for IHDR chunk
|
|
static final String[] IHDR_filterMethodNames = {
|
|
"adaptive"
|
|
};
|
|
|
|
// Interlace methods for IHDR chunk
|
|
static final String[] IHDR_interlaceMethodNames = {
|
|
"none", "adam7"
|
|
};
|
|
|
|
// Compression methods for iCCP chunk
|
|
static final String[] iCCP_compressionMethodNames = {
|
|
"deflate"
|
|
};
|
|
|
|
// Compression methods for zTXt chunk
|
|
static final String[] zTXt_compressionMethodNames = {
|
|
"deflate"
|
|
};
|
|
|
|
// "Unknown" unit for pHYs chunk
|
|
public static final int PHYS_UNIT_UNKNOWN = 0;
|
|
|
|
// "Meter" unit for pHYs chunk
|
|
public static final int PHYS_UNIT_METER = 1;
|
|
|
|
// Unit specifiers for pHYs chunk
|
|
static final String[] unitSpecifierNames = {
|
|
"unknown", "meter"
|
|
};
|
|
|
|
// Rendering intents for sRGB chunk
|
|
static final String[] renderingIntentNames = {
|
|
"Perceptual", // 0
|
|
"Relative colorimetric", // 1
|
|
"Saturation", // 2
|
|
"Absolute colorimetric" // 3
|
|
|
|
};
|
|
|
|
// Color space types for Chroma->ColorSpaceType node
|
|
static final String[] colorSpaceTypeNames = {
|
|
"GRAY", null, "RGB", "RGB",
|
|
"GRAY", null, "RGB"
|
|
};
|
|
|
|
// IHDR chunk
|
|
public boolean IHDR_present;
|
|
public int IHDR_width;
|
|
public int IHDR_height;
|
|
public int IHDR_bitDepth;
|
|
public int IHDR_colorType;
|
|
public int IHDR_compressionMethod;
|
|
public int IHDR_filterMethod;
|
|
public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
|
|
|
|
// PLTE chunk
|
|
public boolean PLTE_present;
|
|
public byte[] PLTE_red;
|
|
public byte[] PLTE_green;
|
|
public byte[] PLTE_blue;
|
|
|
|
// If non-null, used to reorder palette entries during encoding in
|
|
// order to minimize the size of the tRNS chunk. Thus an index of
|
|
// 'i' in the source should be encoded as index 'PLTE_order[i]'.
|
|
// PLTE_order will be null unless 'initialize' is called with an
|
|
// IndexColorModel image type.
|
|
public int[] PLTE_order = null;
|
|
|
|
// bKGD chunk
|
|
// If external (non-PNG sourced) data has red = green = blue,
|
|
// always store it as gray and promote when writing
|
|
public boolean bKGD_present;
|
|
public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
|
|
public int bKGD_index;
|
|
public int bKGD_gray;
|
|
public int bKGD_red;
|
|
public int bKGD_green;
|
|
public int bKGD_blue;
|
|
|
|
// cHRM chunk
|
|
public boolean cHRM_present;
|
|
public int cHRM_whitePointX;
|
|
public int cHRM_whitePointY;
|
|
public int cHRM_redX;
|
|
public int cHRM_redY;
|
|
public int cHRM_greenX;
|
|
public int cHRM_greenY;
|
|
public int cHRM_blueX;
|
|
public int cHRM_blueY;
|
|
|
|
// gAMA chunk
|
|
public boolean gAMA_present;
|
|
public int gAMA_gamma;
|
|
|
|
// hIST chunk
|
|
public boolean hIST_present;
|
|
public char[] hIST_histogram;
|
|
|
|
// iCCP chunk
|
|
public boolean iCCP_present;
|
|
public String iCCP_profileName;
|
|
public int iCCP_compressionMethod;
|
|
public byte[] iCCP_compressedProfile;
|
|
|
|
// iTXt chunk
|
|
public ArrayList<String> iTXt_keyword = new ArrayList<String>();
|
|
public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>();
|
|
public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>();
|
|
public ArrayList<String> iTXt_languageTag = new ArrayList<String>();
|
|
public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>();
|
|
public ArrayList<String> iTXt_text = new ArrayList<String>();
|
|
|
|
// pHYs chunk
|
|
public boolean pHYs_present;
|
|
public int pHYs_pixelsPerUnitXAxis;
|
|
public int pHYs_pixelsPerUnitYAxis;
|
|
public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
|
|
|
|
// sBIT chunk
|
|
public boolean sBIT_present;
|
|
public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
|
|
public int sBIT_grayBits;
|
|
public int sBIT_redBits;
|
|
public int sBIT_greenBits;
|
|
public int sBIT_blueBits;
|
|
public int sBIT_alphaBits;
|
|
|
|
// sPLT chunk
|
|
public boolean sPLT_present;
|
|
public String sPLT_paletteName; // 1-79 characters
|
|
public int sPLT_sampleDepth; // 8 or 16
|
|
public int[] sPLT_red;
|
|
public int[] sPLT_green;
|
|
public int[] sPLT_blue;
|
|
public int[] sPLT_alpha;
|
|
public int[] sPLT_frequency;
|
|
|
|
// sRGB chunk
|
|
public boolean sRGB_present;
|
|
public int sRGB_renderingIntent;
|
|
|
|
// tEXt chunk
|
|
public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters
|
|
public ArrayList<String> tEXt_text = new ArrayList<String>();
|
|
|
|
// tIME chunk
|
|
public boolean tIME_present;
|
|
public int tIME_year;
|
|
public int tIME_month;
|
|
public int tIME_day;
|
|
public int tIME_hour;
|
|
public int tIME_minute;
|
|
public int tIME_second;
|
|
|
|
// tRNS chunk
|
|
// If external (non-PNG sourced) data has red = green = blue,
|
|
// always store it as gray and promote when writing
|
|
public boolean tRNS_present;
|
|
public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
|
|
public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
|
|
public int tRNS_gray;
|
|
public int tRNS_red;
|
|
public int tRNS_green;
|
|
public int tRNS_blue;
|
|
|
|
// zTXt chunk
|
|
public ArrayList<String> zTXt_keyword = new ArrayList<String>();
|
|
public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>();
|
|
public ArrayList<String> zTXt_text = new ArrayList<String>();
|
|
|
|
// Unknown chunks
|
|
public ArrayList<String> unknownChunkType = new ArrayList<String>();
|
|
public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>();
|
|
|
|
public PNGMetadata() {
|
|
super(true,
|
|
nativeMetadataFormatName,
|
|
nativeMetadataFormatClassName,
|
|
null, null);
|
|
}
|
|
|
|
public PNGMetadata(IIOMetadata metadata) {
|
|
// TODO -- implement
|
|
}
|
|
|
|
/**
|
|
* Sets the IHDR_bitDepth and IHDR_colorType variables.
|
|
* The <code>numBands</code> parameter is necessary since
|
|
* we may only be writing a subset of the image bands.
|
|
*/
|
|
public void initialize(ImageTypeSpecifier imageType, int numBands) {
|
|
ColorModel colorModel = imageType.getColorModel();
|
|
SampleModel sampleModel = imageType.getSampleModel();
|
|
|
|
// Initialize IHDR_bitDepth
|
|
int[] sampleSize = sampleModel.getSampleSize();
|
|
int bitDepth = sampleSize[0];
|
|
// Choose max bit depth over all channels
|
|
// Fixes bug 4413109
|
|
for (int i = 1; i < sampleSize.length; i++) {
|
|
if (sampleSize[i] > bitDepth) {
|
|
bitDepth = sampleSize[i];
|
|
}
|
|
}
|
|
// Multi-channel images must have a bit depth of 8 or 16
|
|
if (sampleSize.length > 1 && bitDepth < 8) {
|
|
bitDepth = 8;
|
|
}
|
|
|
|
// 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("bitDepth > 16!");
|
|
}
|
|
IHDR_bitDepth = bitDepth;
|
|
|
|
// Initialize IHDR_colorType
|
|
if (colorModel instanceof IndexColorModel) {
|
|
IndexColorModel icm = (IndexColorModel)colorModel;
|
|
int size = icm.getMapSize();
|
|
|
|
byte[] reds = new byte[size];
|
|
icm.getReds(reds);
|
|
byte[] greens = new byte[size];
|
|
icm.getGreens(greens);
|
|
byte[] blues = new byte[size];
|
|
icm.getBlues(blues);
|
|
|
|
// Determine whether the color tables are actually a gray ramp
|
|
// if the color type has not been set previously
|
|
boolean isGray = false;
|
|
if (!IHDR_present ||
|
|
(IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) {
|
|
isGray = true;
|
|
int scale = 255/((1 << IHDR_bitDepth) - 1);
|
|
for (int i = 0; i < size; i++) {
|
|
byte red = reds[i];
|
|
if ((red != (byte)(i*scale)) ||
|
|
(red != greens[i]) ||
|
|
(red != blues[i])) {
|
|
isGray = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine whether transparency exists
|
|
boolean hasAlpha = colorModel.hasAlpha();
|
|
|
|
byte[] alpha = null;
|
|
if (hasAlpha) {
|
|
alpha = new byte[size];
|
|
icm.getAlphas(alpha);
|
|
}
|
|
|
|
/*
|
|
* NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images
|
|
* contained more than 1024 pixels (or even than 768 pixels in case of
|
|
* single transparent pixel in palette).
|
|
* For such images alpha samples in raster will occupy more space than
|
|
* it is required to store palette so it could be reasonable to
|
|
* use PNG_COLOR_PALETTE color type for large images.
|
|
*/
|
|
|
|
if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
|
|
} else if (isGray && !hasAlpha) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
} else {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
PLTE_present = true;
|
|
PLTE_order = null;
|
|
PLTE_red = (byte[])reds.clone();
|
|
PLTE_green = (byte[])greens.clone();
|
|
PLTE_blue = (byte[])blues.clone();
|
|
|
|
if (hasAlpha) {
|
|
tRNS_present = true;
|
|
tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
|
|
PLTE_order = new int[alpha.length];
|
|
|
|
// Reorder the palette so that non-opaque entries
|
|
// come first. Since the tRNS chunk does not have
|
|
// to store trailing 255's, this can save a
|
|
// considerable amount of space when encoding
|
|
// images with only one transparent pixel value,
|
|
// e.g., images from GIF sources.
|
|
|
|
byte[] newAlpha = new byte[alpha.length];
|
|
|
|
// Scan for non-opaque entries and assign them
|
|
// positions starting at 0.
|
|
int newIndex = 0;
|
|
for (int i = 0; i < alpha.length; i++) {
|
|
if (alpha[i] != (byte)255) {
|
|
PLTE_order[i] = newIndex;
|
|
newAlpha[newIndex] = alpha[i];
|
|
++newIndex;
|
|
}
|
|
}
|
|
int numTransparent = newIndex;
|
|
|
|
// Scan for opaque entries and assign them
|
|
// positions following the non-opaque entries.
|
|
for (int i = 0; i < alpha.length; i++) {
|
|
if (alpha[i] == (byte)255) {
|
|
PLTE_order[i] = newIndex++;
|
|
}
|
|
}
|
|
|
|
// Reorder the palettes
|
|
byte[] oldRed = PLTE_red;
|
|
byte[] oldGreen = PLTE_green;
|
|
byte[] oldBlue = PLTE_blue;
|
|
int len = oldRed.length; // All have the same length
|
|
PLTE_red = new byte[len];
|
|
PLTE_green = new byte[len];
|
|
PLTE_blue = new byte[len];
|
|
for (int i = 0; i < len; i++) {
|
|
PLTE_red[PLTE_order[i]] = oldRed[i];
|
|
PLTE_green[PLTE_order[i]] = oldGreen[i];
|
|
PLTE_blue[PLTE_order[i]] = oldBlue[i];
|
|
}
|
|
|
|
// Copy only the transparent entries into tRNS_alpha
|
|
tRNS_alpha = new byte[numTransparent];
|
|
System.arraycopy(newAlpha, 0,
|
|
tRNS_alpha, 0, numTransparent);
|
|
}
|
|
}
|
|
} else {
|
|
if (numBands == 1) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
} else if (numBands == 2) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
|
|
} else if (numBands == 3) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_RGB;
|
|
} else if (numBands == 4) {
|
|
IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
|
|
} else {
|
|
throw new RuntimeException("Number of bands not 1-4!");
|
|
}
|
|
}
|
|
|
|
IHDR_present = true;
|
|
}
|
|
|
|
public boolean isReadOnly() {
|
|
return false;
|
|
}
|
|
|
|
private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) {
|
|
if (in == null) {
|
|
return null;
|
|
} else {
|
|
ArrayList<byte[]> list = new ArrayList<byte[]>(in.size());
|
|
for (byte[] b: in) {
|
|
list.add((b == null) ? null : (byte[])b.clone());
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
|
|
// Deep clone
|
|
public Object clone() {
|
|
PNGMetadata metadata;
|
|
try {
|
|
metadata = (PNGMetadata)super.clone();
|
|
} catch (CloneNotSupportedException e) {
|
|
return null;
|
|
}
|
|
|
|
// unknownChunkData needs deep clone
|
|
metadata.unknownChunkData =
|
|
cloneBytesArrayList(this.unknownChunkData);
|
|
|
|
return metadata;
|
|
}
|
|
|
|
public Node getAsTree(String formatName) {
|
|
if (formatName.equals(nativeMetadataFormatName)) {
|
|
return getNativeTree();
|
|
} else if (formatName.equals
|
|
(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
|
return getStandardTree();
|
|
} else {
|
|
throw new IllegalArgumentException("Not a recognized format!");
|
|
}
|
|
}
|
|
|
|
private Node getNativeTree() {
|
|
IIOMetadataNode node = null; // scratch node
|
|
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
|
|
|
|
// IHDR
|
|
if (IHDR_present) {
|
|
IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
|
|
IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
|
|
IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
|
|
IHDR_node.setAttribute("bitDepth",
|
|
Integer.toString(IHDR_bitDepth));
|
|
IHDR_node.setAttribute("colorType",
|
|
IHDR_colorTypeNames[IHDR_colorType]);
|
|
// IHDR_compressionMethod must be 0 in PNG 1.1
|
|
IHDR_node.setAttribute("compressionMethod",
|
|
IHDR_compressionMethodNames[IHDR_compressionMethod]);
|
|
// IHDR_filterMethod must be 0 in PNG 1.1
|
|
IHDR_node.setAttribute("filterMethod",
|
|
IHDR_filterMethodNames[IHDR_filterMethod]);
|
|
IHDR_node.setAttribute("interlaceMethod",
|
|
IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
|
|
root.appendChild(IHDR_node);
|
|
}
|
|
|
|
// PLTE
|
|
if (PLTE_present) {
|
|
IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
|
|
int numEntries = PLTE_red.length;
|
|
for (int i = 0; i < numEntries; i++) {
|
|
IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
|
|
entry.setAttribute("index", Integer.toString(i));
|
|
entry.setAttribute("red",
|
|
Integer.toString(PLTE_red[i] & 0xff));
|
|
entry.setAttribute("green",
|
|
Integer.toString(PLTE_green[i] & 0xff));
|
|
entry.setAttribute("blue",
|
|
Integer.toString(PLTE_blue[i] & 0xff));
|
|
PLTE_node.appendChild(entry);
|
|
}
|
|
|
|
root.appendChild(PLTE_node);
|
|
}
|
|
|
|
// bKGD
|
|
if (bKGD_present) {
|
|
IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
|
|
|
|
if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
|
|
node = new IIOMetadataNode("bKGD_Palette");
|
|
node.setAttribute("index", Integer.toString(bKGD_index));
|
|
} else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
|
node = new IIOMetadataNode("bKGD_Grayscale");
|
|
node.setAttribute("gray", Integer.toString(bKGD_gray));
|
|
} else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) {
|
|
node = new IIOMetadataNode("bKGD_RGB");
|
|
node.setAttribute("red", Integer.toString(bKGD_red));
|
|
node.setAttribute("green", Integer.toString(bKGD_green));
|
|
node.setAttribute("blue", Integer.toString(bKGD_blue));
|
|
}
|
|
bKGD_node.appendChild(node);
|
|
|
|
root.appendChild(bKGD_node);
|
|
}
|
|
|
|
// cHRM
|
|
if (cHRM_present) {
|
|
IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
|
|
cHRM_node.setAttribute("whitePointX",
|
|
Integer.toString(cHRM_whitePointX));
|
|
cHRM_node.setAttribute("whitePointY",
|
|
Integer.toString(cHRM_whitePointY));
|
|
cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
|
|
cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
|
|
cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
|
|
cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
|
|
cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
|
|
cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
|
|
|
|
root.appendChild(cHRM_node);
|
|
}
|
|
|
|
// gAMA
|
|
if (gAMA_present) {
|
|
IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
|
|
gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
|
|
|
|
root.appendChild(gAMA_node);
|
|
}
|
|
|
|
// hIST
|
|
if (hIST_present) {
|
|
IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
|
|
|
|
for (int i = 0; i < hIST_histogram.length; i++) {
|
|
IIOMetadataNode hist =
|
|
new IIOMetadataNode("hISTEntry");
|
|
hist.setAttribute("index", Integer.toString(i));
|
|
hist.setAttribute("value",
|
|
Integer.toString(hIST_histogram[i]));
|
|
hIST_node.appendChild(hist);
|
|
}
|
|
|
|
root.appendChild(hIST_node);
|
|
}
|
|
|
|
// iCCP
|
|
if (iCCP_present) {
|
|
IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
|
|
iCCP_node.setAttribute("profileName", iCCP_profileName);
|
|
iCCP_node.setAttribute("compressionMethod",
|
|
iCCP_compressionMethodNames[iCCP_compressionMethod]);
|
|
|
|
Object profile = iCCP_compressedProfile;
|
|
if (profile != null) {
|
|
profile = ((byte[])profile).clone();
|
|
}
|
|
iCCP_node.setUserObject(profile);
|
|
|
|
root.appendChild(iCCP_node);
|
|
}
|
|
|
|
// iTXt
|
|
if (iTXt_keyword.size() > 0) {
|
|
IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
|
|
for (int i = 0; i < iTXt_keyword.size(); i++) {
|
|
IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
|
|
iTXt_node.setAttribute("keyword", iTXt_keyword.get(i));
|
|
iTXt_node.setAttribute("compressionFlag",
|
|
iTXt_compressionFlag.get(i) ? "TRUE" : "FALSE");
|
|
iTXt_node.setAttribute("compressionMethod",
|
|
iTXt_compressionMethod.get(i).toString());
|
|
iTXt_node.setAttribute("languageTag",
|
|
iTXt_languageTag.get(i));
|
|
iTXt_node.setAttribute("translatedKeyword",
|
|
iTXt_translatedKeyword.get(i));
|
|
iTXt_node.setAttribute("text", iTXt_text.get(i));
|
|
|
|
iTXt_parent.appendChild(iTXt_node);
|
|
}
|
|
|
|
root.appendChild(iTXt_parent);
|
|
}
|
|
|
|
// pHYs
|
|
if (pHYs_present) {
|
|
IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
|
|
pHYs_node.setAttribute("pixelsPerUnitXAxis",
|
|
Integer.toString(pHYs_pixelsPerUnitXAxis));
|
|
pHYs_node.setAttribute("pixelsPerUnitYAxis",
|
|
Integer.toString(pHYs_pixelsPerUnitYAxis));
|
|
pHYs_node.setAttribute("unitSpecifier",
|
|
unitSpecifierNames[pHYs_unitSpecifier]);
|
|
|
|
root.appendChild(pHYs_node);
|
|
}
|
|
|
|
// sBIT
|
|
if (sBIT_present) {
|
|
IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
|
|
|
|
if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
|
node = new IIOMetadataNode("sBIT_Grayscale");
|
|
node.setAttribute("gray",
|
|
Integer.toString(sBIT_grayBits));
|
|
} else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
|
|
node = new IIOMetadataNode("sBIT_GrayAlpha");
|
|
node.setAttribute("gray",
|
|
Integer.toString(sBIT_grayBits));
|
|
node.setAttribute("alpha",
|
|
Integer.toString(sBIT_alphaBits));
|
|
} else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) {
|
|
node = new IIOMetadataNode("sBIT_RGB");
|
|
node.setAttribute("red",
|
|
Integer.toString(sBIT_redBits));
|
|
node.setAttribute("green",
|
|
Integer.toString(sBIT_greenBits));
|
|
node.setAttribute("blue",
|
|
Integer.toString(sBIT_blueBits));
|
|
} else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
|
|
node = new IIOMetadataNode("sBIT_RGBAlpha");
|
|
node.setAttribute("red",
|
|
Integer.toString(sBIT_redBits));
|
|
node.setAttribute("green",
|
|
Integer.toString(sBIT_greenBits));
|
|
node.setAttribute("blue",
|
|
Integer.toString(sBIT_blueBits));
|
|
node.setAttribute("alpha",
|
|
Integer.toString(sBIT_alphaBits));
|
|
} else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
|
|
node = new IIOMetadataNode("sBIT_Palette");
|
|
node.setAttribute("red",
|
|
Integer.toString(sBIT_redBits));
|
|
node.setAttribute("green",
|
|
Integer.toString(sBIT_greenBits));
|
|
node.setAttribute("blue",
|
|
Integer.toString(sBIT_blueBits));
|
|
}
|
|
sBIT_node.appendChild(node);
|
|
|
|
root.appendChild(sBIT_node);
|
|
}
|
|
|
|
// sPLT
|
|
if (sPLT_present) {
|
|
IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
|
|
|
|
sPLT_node.setAttribute("name", sPLT_paletteName);
|
|
sPLT_node.setAttribute("sampleDepth",
|
|
Integer.toString(sPLT_sampleDepth));
|
|
|
|
int numEntries = sPLT_red.length;
|
|
for (int i = 0; i < numEntries; i++) {
|
|
IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
|
|
entry.setAttribute("index", Integer.toString(i));
|
|
entry.setAttribute("red", Integer.toString(sPLT_red[i]));
|
|
entry.setAttribute("green", Integer.toString(sPLT_green[i]));
|
|
entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
|
|
entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
|
|
entry.setAttribute("frequency",
|
|
Integer.toString(sPLT_frequency[i]));
|
|
sPLT_node.appendChild(entry);
|
|
}
|
|
|
|
root.appendChild(sPLT_node);
|
|
}
|
|
|
|
// sRGB
|
|
if (sRGB_present) {
|
|
IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
|
|
sRGB_node.setAttribute("renderingIntent",
|
|
renderingIntentNames[sRGB_renderingIntent]);
|
|
|
|
root.appendChild(sRGB_node);
|
|
}
|
|
|
|
// tEXt
|
|
if (tEXt_keyword.size() > 0) {
|
|
IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
|
|
for (int i = 0; i < tEXt_keyword.size(); i++) {
|
|
IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
|
|
tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i));
|
|
tEXt_node.setAttribute("value" , (String)tEXt_text.get(i));
|
|
|
|
tEXt_parent.appendChild(tEXt_node);
|
|
}
|
|
|
|
root.appendChild(tEXt_parent);
|
|
}
|
|
|
|
// tIME
|
|
if (tIME_present) {
|
|
IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
|
|
tIME_node.setAttribute("year", Integer.toString(tIME_year));
|
|
tIME_node.setAttribute("month", Integer.toString(tIME_month));
|
|
tIME_node.setAttribute("day", Integer.toString(tIME_day));
|
|
tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
|
|
tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
|
|
tIME_node.setAttribute("second", Integer.toString(tIME_second));
|
|
|
|
root.appendChild(tIME_node);
|
|
}
|
|
|
|
// tRNS
|
|
if (tRNS_present) {
|
|
IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
|
|
|
|
if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
|
|
node = new IIOMetadataNode("tRNS_Palette");
|
|
|
|
for (int i = 0; i < tRNS_alpha.length; i++) {
|
|
IIOMetadataNode entry =
|
|
new IIOMetadataNode("tRNS_PaletteEntry");
|
|
entry.setAttribute("index", Integer.toString(i));
|
|
entry.setAttribute("alpha",
|
|
Integer.toString(tRNS_alpha[i] & 0xff));
|
|
node.appendChild(entry);
|
|
}
|
|
} else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
|
node = new IIOMetadataNode("tRNS_Grayscale");
|
|
node.setAttribute("gray", Integer.toString(tRNS_gray));
|
|
} else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
|
|
node = new IIOMetadataNode("tRNS_RGB");
|
|
node.setAttribute("red", Integer.toString(tRNS_red));
|
|
node.setAttribute("green", Integer.toString(tRNS_green));
|
|
node.setAttribute("blue", Integer.toString(tRNS_blue));
|
|
}
|
|
tRNS_node.appendChild(node);
|
|
|
|
root.appendChild(tRNS_node);
|
|
}
|
|
|
|
// zTXt
|
|
if (zTXt_keyword.size() > 0) {
|
|
IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
|
|
for (int i = 0; i < zTXt_keyword.size(); i++) {
|
|
IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
|
|
zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i));
|
|
|
|
int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue();
|
|
zTXt_node.setAttribute("compressionMethod",
|
|
zTXt_compressionMethodNames[cm]);
|
|
|
|
zTXt_node.setAttribute("text", (String)zTXt_text.get(i));
|
|
|
|
zTXt_parent.appendChild(zTXt_node);
|
|
}
|
|
|
|
root.appendChild(zTXt_parent);
|
|
}
|
|
|
|
// Unknown chunks
|
|
if (unknownChunkType.size() > 0) {
|
|
IIOMetadataNode unknown_parent =
|
|
new IIOMetadataNode("UnknownChunks");
|
|
for (int i = 0; i < unknownChunkType.size(); i++) {
|
|
IIOMetadataNode unknown_node =
|
|
new IIOMetadataNode("UnknownChunk");
|
|
unknown_node.setAttribute("type",
|
|
(String)unknownChunkType.get(i));
|
|
unknown_node.setUserObject((byte[])unknownChunkData.get(i));
|
|
|
|
unknown_parent.appendChild(unknown_node);
|
|
}
|
|
|
|
root.appendChild(unknown_parent);
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
private int getNumChannels() {
|
|
// Determine number of channels
|
|
// Be careful about palette color with transparency
|
|
int numChannels = IHDR_numChannels[IHDR_colorType];
|
|
if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
|
|
tRNS_present && tRNS_colorType == IHDR_colorType) {
|
|
numChannels = 4;
|
|
}
|
|
return numChannels;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardChromaNode() {
|
|
IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("ColorSpaceType");
|
|
node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
|
|
chroma_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("NumChannels");
|
|
node.setAttribute("value", Integer.toString(getNumChannels()));
|
|
chroma_node.appendChild(node);
|
|
|
|
if (gAMA_present) {
|
|
node = new IIOMetadataNode("Gamma");
|
|
node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
|
|
chroma_node.appendChild(node);
|
|
}
|
|
|
|
node = new IIOMetadataNode("BlackIsZero");
|
|
node.setAttribute("value", "TRUE");
|
|
chroma_node.appendChild(node);
|
|
|
|
if (PLTE_present) {
|
|
boolean hasAlpha = tRNS_present &&
|
|
(tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE);
|
|
|
|
node = new IIOMetadataNode("Palette");
|
|
for (int i = 0; i < PLTE_red.length; i++) {
|
|
IIOMetadataNode entry =
|
|
new IIOMetadataNode("PaletteEntry");
|
|
entry.setAttribute("index", Integer.toString(i));
|
|
entry.setAttribute("red",
|
|
Integer.toString(PLTE_red[i] & 0xff));
|
|
entry.setAttribute("green",
|
|
Integer.toString(PLTE_green[i] & 0xff));
|
|
entry.setAttribute("blue",
|
|
Integer.toString(PLTE_blue[i] & 0xff));
|
|
if (hasAlpha) {
|
|
int alpha = (i < tRNS_alpha.length) ?
|
|
(tRNS_alpha[i] & 0xff) : 255;
|
|
entry.setAttribute("alpha", Integer.toString(alpha));
|
|
}
|
|
node.appendChild(entry);
|
|
}
|
|
chroma_node.appendChild(node);
|
|
}
|
|
|
|
if (bKGD_present) {
|
|
if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
|
|
node = new IIOMetadataNode("BackgroundIndex");
|
|
node.setAttribute("value", Integer.toString(bKGD_index));
|
|
} else {
|
|
node = new IIOMetadataNode("BackgroundColor");
|
|
int r, g, b;
|
|
|
|
if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
|
r = g = b = bKGD_gray;
|
|
} else {
|
|
r = bKGD_red;
|
|
g = bKGD_green;
|
|
b = bKGD_blue;
|
|
}
|
|
node.setAttribute("red", Integer.toString(r));
|
|
node.setAttribute("green", Integer.toString(g));
|
|
node.setAttribute("blue", Integer.toString(b));
|
|
}
|
|
chroma_node.appendChild(node);
|
|
}
|
|
|
|
return chroma_node;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardCompressionNode() {
|
|
IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("CompressionTypeName");
|
|
node.setAttribute("value", "deflate");
|
|
compression_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("Lossless");
|
|
node.setAttribute("value", "TRUE");
|
|
compression_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("NumProgressiveScans");
|
|
node.setAttribute("value",
|
|
(IHDR_interlaceMethod == 0) ? "1" : "7");
|
|
compression_node.appendChild(node);
|
|
|
|
return compression_node;
|
|
}
|
|
|
|
private String repeat(String s, int times) {
|
|
if (times == 1) {
|
|
return s;
|
|
}
|
|
StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
|
|
sb.append(s);
|
|
for (int i = 1; i < times; i++) {
|
|
sb.append(" ");
|
|
sb.append(s);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public IIOMetadataNode getStandardDataNode() {
|
|
IIOMetadataNode data_node = new IIOMetadataNode("Data");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("PlanarConfiguration");
|
|
node.setAttribute("value", "PixelInterleaved");
|
|
data_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("SampleFormat");
|
|
node.setAttribute("value",
|
|
IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ?
|
|
"Index" : "UnsignedIntegral");
|
|
data_node.appendChild(node);
|
|
|
|
String bitDepth = Integer.toString(IHDR_bitDepth);
|
|
node = new IIOMetadataNode("BitsPerSample");
|
|
node.setAttribute("value", repeat(bitDepth, getNumChannels()));
|
|
data_node.appendChild(node);
|
|
|
|
if (sBIT_present) {
|
|
node = new IIOMetadataNode("SignificantBitsPerSample");
|
|
String sbits;
|
|
if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY ||
|
|
sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
|
|
sbits = Integer.toString(sBIT_grayBits);
|
|
} else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB ||
|
|
// sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
|
|
sbits = Integer.toString(sBIT_redBits) + " " +
|
|
Integer.toString(sBIT_greenBits) + " " +
|
|
Integer.toString(sBIT_blueBits);
|
|
}
|
|
|
|
if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
|
|
sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
|
|
sbits += " " + Integer.toString(sBIT_alphaBits);
|
|
}
|
|
|
|
node.setAttribute("value", sbits);
|
|
data_node.appendChild(node);
|
|
}
|
|
|
|
// SampleMSB
|
|
|
|
return data_node;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardDimensionNode() {
|
|
IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("PixelAspectRatio");
|
|
float ratio = pHYs_present ?
|
|
(float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F;
|
|
node.setAttribute("value", Float.toString(ratio));
|
|
dimension_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("ImageOrientation");
|
|
node.setAttribute("value", "Normal");
|
|
dimension_node.appendChild(node);
|
|
|
|
if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
|
|
node = new IIOMetadataNode("HorizontalPixelSize");
|
|
node.setAttribute("value",
|
|
Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis));
|
|
dimension_node.appendChild(node);
|
|
|
|
node = new IIOMetadataNode("VerticalPixelSize");
|
|
node.setAttribute("value",
|
|
Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis));
|
|
dimension_node.appendChild(node);
|
|
}
|
|
|
|
return dimension_node;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardDocumentNode() {
|
|
if (!tIME_present) {
|
|
return null;
|
|
}
|
|
|
|
IIOMetadataNode document_node = new IIOMetadataNode("Document");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("ImageModificationTime");
|
|
node.setAttribute("year", Integer.toString(tIME_year));
|
|
node.setAttribute("month", Integer.toString(tIME_month));
|
|
node.setAttribute("day", Integer.toString(tIME_day));
|
|
node.setAttribute("hour", Integer.toString(tIME_hour));
|
|
node.setAttribute("minute", Integer.toString(tIME_minute));
|
|
node.setAttribute("second", Integer.toString(tIME_second));
|
|
document_node.appendChild(node);
|
|
|
|
return document_node;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardTextNode() {
|
|
int numEntries = tEXt_keyword.size() +
|
|
iTXt_keyword.size() + zTXt_keyword.size();
|
|
if (numEntries == 0) {
|
|
return null;
|
|
}
|
|
|
|
IIOMetadataNode text_node = new IIOMetadataNode("Text");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
for (int i = 0; i < tEXt_keyword.size(); i++) {
|
|
node = new IIOMetadataNode("TextEntry");
|
|
node.setAttribute("keyword", (String)tEXt_keyword.get(i));
|
|
node.setAttribute("value", (String)tEXt_text.get(i));
|
|
node.setAttribute("encoding", "ISO-8859-1");
|
|
node.setAttribute("compression", "none");
|
|
|
|
text_node.appendChild(node);
|
|
}
|
|
|
|
for (int i = 0; i < iTXt_keyword.size(); i++) {
|
|
node = new IIOMetadataNode("TextEntry");
|
|
node.setAttribute("keyword", iTXt_keyword.get(i));
|
|
node.setAttribute("value", iTXt_text.get(i));
|
|
node.setAttribute("language",
|
|
iTXt_languageTag.get(i));
|
|
if (iTXt_compressionFlag.get(i)) {
|
|
node.setAttribute("compression", "zip");
|
|
} else {
|
|
node.setAttribute("compression", "none");
|
|
}
|
|
|
|
text_node.appendChild(node);
|
|
}
|
|
|
|
for (int i = 0; i < zTXt_keyword.size(); i++) {
|
|
node = new IIOMetadataNode("TextEntry");
|
|
node.setAttribute("keyword", (String)zTXt_keyword.get(i));
|
|
node.setAttribute("value", (String)zTXt_text.get(i));
|
|
node.setAttribute("compression", "zip");
|
|
|
|
text_node.appendChild(node);
|
|
}
|
|
|
|
return text_node;
|
|
}
|
|
|
|
public IIOMetadataNode getStandardTransparencyNode() {
|
|
IIOMetadataNode transparency_node =
|
|
new IIOMetadataNode("Transparency");
|
|
IIOMetadataNode node = null; // scratch node
|
|
|
|
node = new IIOMetadataNode("Alpha");
|
|
boolean hasAlpha =
|
|
(IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) ||
|
|
(IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) ||
|
|
(IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
|
|
tRNS_present &&
|
|
(tRNS_colorType == IHDR_colorType) &&
|
|
(tRNS_alpha != null));
|
|
node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none");
|
|
transparency_node.appendChild(node);
|
|
|
|
if (tRNS_present) {
|
|
node = new IIOMetadataNode("TransparentColor");
|
|
if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
|
|
node.setAttribute("value",
|
|
Integer.toString(tRNS_red) + " " +
|
|
Integer.toString(tRNS_green) + " " +
|
|
Integer.toString(tRNS_blue));
|
|
} else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
|
node.setAttribute("value", Integer.toString(tRNS_gray));
|
|
}
|
|
transparency_node.appendChild(node);
|
|
}
|
|
|
|
return transparency_node;
|
|
}
|
|
|
|
// Shorthand for throwing an IIOInvalidTreeException
|
|
private void fatal(Node node, String reason)
|
|
throws IIOInvalidTreeException {
|
|
throw new IIOInvalidTreeException(reason, node);
|
|
}
|
|
|
|
// Get an integer-valued attribute
|
|
private String getStringAttribute(Node node, String name,
|
|
String defaultValue, boolean required)
|
|
throws IIOInvalidTreeException {
|
|
Node attr = node.getAttributes().getNamedItem(name);
|
|
if (attr == null) {
|
|
if (!required) {
|
|
return defaultValue;
|
|
} else {
|
|
fatal(node, "Required attribute " + name + " not present!");
|
|
}
|
|
}
|
|
return attr.getNodeValue();
|
|
}
|
|
|
|
|
|
// Get an integer-valued attribute
|
|
private int getIntAttribute(Node node, String name,
|
|
int defaultValue, boolean required)
|
|
throws IIOInvalidTreeException {
|
|
String value = getStringAttribute(node, name, null, required);
|
|
if (value == null) {
|
|
return defaultValue;
|
|
}
|
|
return Integer.parseInt(value);
|
|
}
|
|
|
|
// Get a float-valued attribute
|
|
private float getFloatAttribute(Node node, String name,
|
|
float defaultValue, boolean required)
|
|
throws IIOInvalidTreeException {
|
|
String value = getStringAttribute(node, name, null, required);
|
|
if (value == null) {
|
|
return defaultValue;
|
|
}
|
|
return Float.parseFloat(value);
|
|
}
|
|
|
|
// Get a required integer-valued attribute
|
|
private int getIntAttribute(Node node, String name)
|
|
throws IIOInvalidTreeException {
|
|
return getIntAttribute(node, name, -1, true);
|
|
}
|
|
|
|
// Get a required float-valued attribute
|
|
private float getFloatAttribute(Node node, String name)
|
|
throws IIOInvalidTreeException {
|
|
return getFloatAttribute(node, name, -1.0F, true);
|
|
}
|
|
|
|
// Get a boolean-valued attribute
|
|
private boolean getBooleanAttribute(Node node, String name,
|
|
boolean defaultValue,
|
|
boolean required)
|
|
throws IIOInvalidTreeException {
|
|
Node attr = node.getAttributes().getNamedItem(name);
|
|
if (attr == null) {
|
|
if (!required) {
|
|
return defaultValue;
|
|
} else {
|
|
fatal(node, "Required attribute " + name + " not present!");
|
|
}
|
|
}
|
|
String value = attr.getNodeValue();
|
|
// Allow lower case booleans for backward compatibility, #5082756
|
|
if (value.equals("TRUE") || value.equals("true")) {
|
|
return true;
|
|
} else if (value.equals("FALSE") || value.equals("false")) {
|
|
return false;
|
|
} else {
|
|
fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get a required boolean-valued attribute
|
|
private boolean getBooleanAttribute(Node node, String name)
|
|
throws IIOInvalidTreeException {
|
|
return getBooleanAttribute(node, name, false, true);
|
|
}
|
|
|
|
// Get an enumerated attribute as an index into a String array
|
|
private int getEnumeratedAttribute(Node node,
|
|
String name, String[] legalNames,
|
|
int defaultValue, boolean required)
|
|
throws IIOInvalidTreeException {
|
|
Node attr = node.getAttributes().getNamedItem(name);
|
|
if (attr == null) {
|
|
if (!required) {
|
|
return defaultValue;
|
|
} else {
|
|
fatal(node, "Required attribute " + name + " not present!");
|
|
}
|
|
}
|
|
String value = attr.getNodeValue();
|
|
for (int i = 0; i < legalNames.length; i++) {
|
|
if (value.equals(legalNames[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
fatal(node, "Illegal value for attribute " + name + "!");
|
|
return -1;
|
|
}
|
|
|
|
// Get a required enumerated attribute as an index into a String array
|
|
private int getEnumeratedAttribute(Node node,
|
|
String name, String[] legalNames)
|
|
throws IIOInvalidTreeException {
|
|
return getEnumeratedAttribute(node, name, legalNames, -1, true);
|
|
}
|
|
|
|
// Get a String-valued attribute
|
|
private String getAttribute(Node node, String name,
|
|
String defaultValue, boolean required)
|
|
throws IIOInvalidTreeException {
|
|
Node attr = node.getAttributes().getNamedItem(name);
|
|
if (attr == null) {
|
|
if (!required) {
|
|
return defaultValue;
|
|
} else {
|
|
fatal(node, "Required attribute " + name + " not present!");
|
|
}
|
|
}
|
|
return attr.getNodeValue();
|
|
}
|
|
|
|
// Get a required String-valued attribute
|
|
private String getAttribute(Node node, String name)
|
|
throws IIOInvalidTreeException {
|
|
return getAttribute(node, name, null, true);
|
|
}
|
|
|
|
public void mergeTree(String formatName, Node root)
|
|
throws IIOInvalidTreeException {
|
|
if (formatName.equals(nativeMetadataFormatName)) {
|
|
if (root == null) {
|
|
throw new IllegalArgumentException("root == null!");
|
|
}
|
|
mergeNativeTree(root);
|
|
} else if (formatName.equals
|
|
(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
|
if (root == null) {
|
|
throw new IllegalArgumentException("root == null!");
|
|
}
|
|
mergeStandardTree(root);
|
|
} else {
|
|
throw new IllegalArgumentException("Not a recognized format!");
|
|
}
|
|
}
|
|
|
|
private void mergeNativeTree(Node root)
|
|
throws IIOInvalidTreeException {
|
|
Node node = root;
|
|
if (!node.getNodeName().equals(nativeMetadataFormatName)) {
|
|
fatal(node, "Root must be " + nativeMetadataFormatName);
|
|
}
|
|
|
|
node = node.getFirstChild();
|
|
while (node != null) {
|
|
String name = node.getNodeName();
|
|
|
|
if (name.equals("IHDR")) {
|
|
IHDR_width = getIntAttribute(node, "width");
|
|
IHDR_height = getIntAttribute(node, "height");
|
|
IHDR_bitDepth =
|
|
Integer.valueOf(IHDR_bitDepths[
|
|
getEnumeratedAttribute(node,
|
|
"bitDepth",
|
|
IHDR_bitDepths)]);
|
|
IHDR_colorType = getEnumeratedAttribute(node, "colorType",
|
|
IHDR_colorTypeNames);
|
|
IHDR_compressionMethod =
|
|
getEnumeratedAttribute(node, "compressionMethod",
|
|
IHDR_compressionMethodNames);
|
|
IHDR_filterMethod =
|
|
getEnumeratedAttribute(node,
|
|
"filterMethod",
|
|
IHDR_filterMethodNames);
|
|
IHDR_interlaceMethod =
|
|
getEnumeratedAttribute(node, "interlaceMethod",
|
|
IHDR_interlaceMethodNames);
|
|
IHDR_present = true;
|
|
} else if (name.equals("PLTE")) {
|
|
byte[] red = new byte[256];
|
|
byte[] green = new byte[256];
|
|
byte[] blue = new byte[256];
|
|
int maxindex = -1;
|
|
|
|
Node PLTE_entry = node.getFirstChild();
|
|
if (PLTE_entry == null) {
|
|
fatal(node, "Palette has no entries!");
|
|
}
|
|
|
|
while (PLTE_entry != null) {
|
|
if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
|
|
fatal(node,
|
|
"Only a PLTEEntry may be a child of a PLTE!");
|
|
}
|
|
|
|
int index = getIntAttribute(PLTE_entry, "index");
|
|
if (index < 0 || index > 255) {
|
|
fatal(node,
|
|
"Bad value for PLTEEntry attribute index!");
|
|
}
|
|
if (index > maxindex) {
|
|
maxindex = index;
|
|
}
|
|
red[index] =
|
|
(byte)getIntAttribute(PLTE_entry, "red");
|
|
green[index] =
|
|
(byte)getIntAttribute(PLTE_entry, "green");
|
|
blue[index] =
|
|
(byte)getIntAttribute(PLTE_entry, "blue");
|
|
|
|
PLTE_entry = PLTE_entry.getNextSibling();
|
|
}
|
|
|
|
int numEntries = maxindex + 1;
|
|
PLTE_red = new byte[numEntries];
|
|
PLTE_green = new byte[numEntries];
|
|
PLTE_blue = new byte[numEntries];
|
|
System.arraycopy(red, 0, PLTE_red, 0, numEntries);
|
|
System.arraycopy(green, 0, PLTE_green, 0, numEntries);
|
|
System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
|
|
PLTE_present = true;
|
|
} else if (name.equals("bKGD")) {
|
|
bKGD_present = false; // Guard against partial overwrite
|
|
Node bKGD_node = node.getFirstChild();
|
|
if (bKGD_node == null) {
|
|
fatal(node, "bKGD node has no children!");
|
|
}
|
|
String bKGD_name = bKGD_node.getNodeName();
|
|
if (bKGD_name.equals("bKGD_Palette")) {
|
|
bKGD_index = getIntAttribute(bKGD_node, "index");
|
|
bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
} else if (bKGD_name.equals("bKGD_Grayscale")) {
|
|
bKGD_gray = getIntAttribute(bKGD_node, "gray");
|
|
bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
} else if (bKGD_name.equals("bKGD_RGB")) {
|
|
bKGD_red = getIntAttribute(bKGD_node, "red");
|
|
bKGD_green = getIntAttribute(bKGD_node, "green");
|
|
bKGD_blue = getIntAttribute(bKGD_node, "blue");
|
|
bKGD_colorType = PNGImageReader.PNG_COLOR_RGB;
|
|
} else {
|
|
fatal(node, "Bad child of a bKGD node!");
|
|
}
|
|
if (bKGD_node.getNextSibling() != null) {
|
|
fatal(node, "bKGD node has more than one child!");
|
|
}
|
|
|
|
bKGD_present = true;
|
|
} else if (name.equals("cHRM")) {
|
|
cHRM_whitePointX = getIntAttribute(node, "whitePointX");
|
|
cHRM_whitePointY = getIntAttribute(node, "whitePointY");
|
|
cHRM_redX = getIntAttribute(node, "redX");
|
|
cHRM_redY = getIntAttribute(node, "redY");
|
|
cHRM_greenX = getIntAttribute(node, "greenX");
|
|
cHRM_greenY = getIntAttribute(node, "greenY");
|
|
cHRM_blueX = getIntAttribute(node, "blueX");
|
|
cHRM_blueY = getIntAttribute(node, "blueY");
|
|
|
|
cHRM_present = true;
|
|
} else if (name.equals("gAMA")) {
|
|
gAMA_gamma = getIntAttribute(node, "value");
|
|
gAMA_present = true;
|
|
} else if (name.equals("hIST")) {
|
|
char[] hist = new char[256];
|
|
int maxindex = -1;
|
|
|
|
Node hIST_entry = node.getFirstChild();
|
|
if (hIST_entry == null) {
|
|
fatal(node, "hIST node has no children!");
|
|
}
|
|
|
|
while (hIST_entry != null) {
|
|
if (!hIST_entry.getNodeName().equals("hISTEntry")) {
|
|
fatal(node,
|
|
"Only a hISTEntry may be a child of a hIST!");
|
|
}
|
|
|
|
int index = getIntAttribute(hIST_entry, "index");
|
|
if (index < 0 || index > 255) {
|
|
fatal(node,
|
|
"Bad value for histEntry attribute index!");
|
|
}
|
|
if (index > maxindex) {
|
|
maxindex = index;
|
|
}
|
|
hist[index] =
|
|
(char)getIntAttribute(hIST_entry, "value");
|
|
|
|
hIST_entry = hIST_entry.getNextSibling();
|
|
}
|
|
|
|
int numEntries = maxindex + 1;
|
|
hIST_histogram = new char[numEntries];
|
|
System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
|
|
|
|
hIST_present = true;
|
|
} else if (name.equals("iCCP")) {
|
|
iCCP_profileName = getAttribute(node, "profileName");
|
|
iCCP_compressionMethod =
|
|
getEnumeratedAttribute(node, "compressionMethod",
|
|
iCCP_compressionMethodNames);
|
|
Object compressedProfile =
|
|
((IIOMetadataNode)node).getUserObject();
|
|
if (compressedProfile == null) {
|
|
fatal(node, "No ICCP profile present in user object!");
|
|
}
|
|
if (!(compressedProfile instanceof byte[])) {
|
|
fatal(node, "User object not a byte array!");
|
|
}
|
|
|
|
iCCP_compressedProfile =
|
|
(byte[])((byte[])compressedProfile).clone();
|
|
|
|
iCCP_present = true;
|
|
} else if (name.equals("iTXt")) {
|
|
Node iTXt_node = node.getFirstChild();
|
|
while (iTXt_node != null) {
|
|
if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
|
|
fatal(node,
|
|
"Only an iTXtEntry may be a child of an iTXt!");
|
|
}
|
|
|
|
String keyword = getAttribute(iTXt_node, "keyword");
|
|
if (isValidKeyword(keyword)) {
|
|
iTXt_keyword.add(keyword);
|
|
|
|
boolean compressionFlag =
|
|
getBooleanAttribute(iTXt_node, "compressionFlag");
|
|
iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag));
|
|
|
|
String compressionMethod =
|
|
getAttribute(iTXt_node, "compressionMethod");
|
|
iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
|
|
|
|
String languageTag =
|
|
getAttribute(iTXt_node, "languageTag");
|
|
iTXt_languageTag.add(languageTag);
|
|
|
|
String translatedKeyword =
|
|
getAttribute(iTXt_node, "translatedKeyword");
|
|
iTXt_translatedKeyword.add(translatedKeyword);
|
|
|
|
String text = getAttribute(iTXt_node, "text");
|
|
iTXt_text.add(text);
|
|
|
|
}
|
|
// silently skip invalid text entry
|
|
|
|
iTXt_node = iTXt_node.getNextSibling();
|
|
}
|
|
} else if (name.equals("pHYs")) {
|
|
pHYs_pixelsPerUnitXAxis =
|
|
getIntAttribute(node, "pixelsPerUnitXAxis");
|
|
pHYs_pixelsPerUnitYAxis =
|
|
getIntAttribute(node, "pixelsPerUnitYAxis");
|
|
pHYs_unitSpecifier =
|
|
getEnumeratedAttribute(node, "unitSpecifier",
|
|
unitSpecifierNames);
|
|
|
|
pHYs_present = true;
|
|
} else if (name.equals("sBIT")) {
|
|
sBIT_present = false; // Guard against partial overwrite
|
|
Node sBIT_node = node.getFirstChild();
|
|
if (sBIT_node == null) {
|
|
fatal(node, "sBIT node has no children!");
|
|
}
|
|
String sBIT_name = sBIT_node.getNodeName();
|
|
if (sBIT_name.equals("sBIT_Grayscale")) {
|
|
sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
} else if (sBIT_name.equals("sBIT_GrayAlpha")) {
|
|
sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
|
|
sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
|
|
} else if (sBIT_name.equals("sBIT_RGB")) {
|
|
sBIT_redBits = getIntAttribute(sBIT_node, "red");
|
|
sBIT_greenBits = getIntAttribute(sBIT_node, "green");
|
|
sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
|
|
} else if (sBIT_name.equals("sBIT_RGBAlpha")) {
|
|
sBIT_redBits = getIntAttribute(sBIT_node, "red");
|
|
sBIT_greenBits = getIntAttribute(sBIT_node, "green");
|
|
sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
|
|
sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
|
|
} else if (sBIT_name.equals("sBIT_Palette")) {
|
|
sBIT_redBits = getIntAttribute(sBIT_node, "red");
|
|
sBIT_greenBits = getIntAttribute(sBIT_node, "green");
|
|
sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
} else {
|
|
fatal(node, "Bad child of an sBIT node!");
|
|
}
|
|
if (sBIT_node.getNextSibling() != null) {
|
|
fatal(node, "sBIT node has more than one child!");
|
|
}
|
|
|
|
sBIT_present = true;
|
|
} else if (name.equals("sPLT")) {
|
|
sPLT_paletteName = getAttribute(node, "name");
|
|
sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
|
|
|
|
int[] red = new int[256];
|
|
int[] green = new int[256];
|
|
int[] blue = new int[256];
|
|
int[] alpha = new int[256];
|
|
int[] frequency = new int[256];
|
|
int maxindex = -1;
|
|
|
|
Node sPLT_entry = node.getFirstChild();
|
|
if (sPLT_entry == null) {
|
|
fatal(node, "sPLT node has no children!");
|
|
}
|
|
|
|
while (sPLT_entry != null) {
|
|
if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
|
|
fatal(node,
|
|
"Only an sPLTEntry may be a child of an sPLT!");
|
|
}
|
|
|
|
int index = getIntAttribute(sPLT_entry, "index");
|
|
if (index < 0 || index > 255) {
|
|
fatal(node,
|
|
"Bad value for PLTEEntry attribute index!");
|
|
}
|
|
if (index > maxindex) {
|
|
maxindex = index;
|
|
}
|
|
red[index] = getIntAttribute(sPLT_entry, "red");
|
|
green[index] = getIntAttribute(sPLT_entry, "green");
|
|
blue[index] = getIntAttribute(sPLT_entry, "blue");
|
|
alpha[index] = getIntAttribute(sPLT_entry, "alpha");
|
|
frequency[index] =
|
|
getIntAttribute(sPLT_entry, "frequency");
|
|
|
|
sPLT_entry = sPLT_entry.getNextSibling();
|
|
}
|
|
|
|
int numEntries = maxindex + 1;
|
|
sPLT_red = new int[numEntries];
|
|
sPLT_green = new int[numEntries];
|
|
sPLT_blue = new int[numEntries];
|
|
sPLT_alpha = new int[numEntries];
|
|
sPLT_frequency = new int[numEntries];
|
|
System.arraycopy(red, 0, sPLT_red, 0, numEntries);
|
|
System.arraycopy(green, 0, sPLT_green, 0, numEntries);
|
|
System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
|
|
System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
|
|
System.arraycopy(frequency, 0,
|
|
sPLT_frequency, 0, numEntries);
|
|
|
|
sPLT_present = true;
|
|
} else if (name.equals("sRGB")) {
|
|
sRGB_renderingIntent =
|
|
getEnumeratedAttribute(node, "renderingIntent",
|
|
renderingIntentNames);
|
|
|
|
sRGB_present = true;
|
|
} else if (name.equals("tEXt")) {
|
|
Node tEXt_node = node.getFirstChild();
|
|
while (tEXt_node != null) {
|
|
if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
|
|
fatal(node,
|
|
"Only an tEXtEntry may be a child of an tEXt!");
|
|
}
|
|
|
|
String keyword = getAttribute(tEXt_node, "keyword");
|
|
tEXt_keyword.add(keyword);
|
|
|
|
String text = getAttribute(tEXt_node, "value");
|
|
tEXt_text.add(text);
|
|
|
|
tEXt_node = tEXt_node.getNextSibling();
|
|
}
|
|
} else if (name.equals("tIME")) {
|
|
tIME_year = getIntAttribute(node, "year");
|
|
tIME_month = getIntAttribute(node, "month");
|
|
tIME_day = getIntAttribute(node, "day");
|
|
tIME_hour = getIntAttribute(node, "hour");
|
|
tIME_minute = getIntAttribute(node, "minute");
|
|
tIME_second = getIntAttribute(node, "second");
|
|
|
|
tIME_present = true;
|
|
} else if (name.equals("tRNS")) {
|
|
tRNS_present = false; // Guard against partial overwrite
|
|
Node tRNS_node = node.getFirstChild();
|
|
if (tRNS_node == null) {
|
|
fatal(node, "tRNS node has no children!");
|
|
}
|
|
String tRNS_name = tRNS_node.getNodeName();
|
|
if (tRNS_name.equals("tRNS_Palette")) {
|
|
byte[] alpha = new byte[256];
|
|
int maxindex = -1;
|
|
|
|
Node tRNS_paletteEntry = tRNS_node.getFirstChild();
|
|
if (tRNS_paletteEntry == null) {
|
|
fatal(node, "tRNS_Palette node has no children!");
|
|
}
|
|
while (tRNS_paletteEntry != null) {
|
|
if (!tRNS_paletteEntry.getNodeName().equals(
|
|
"tRNS_PaletteEntry")) {
|
|
fatal(node,
|
|
"Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
|
|
}
|
|
int index =
|
|
getIntAttribute(tRNS_paletteEntry, "index");
|
|
if (index < 0 || index > 255) {
|
|
fatal(node,
|
|
"Bad value for tRNS_PaletteEntry attribute index!");
|
|
}
|
|
if (index > maxindex) {
|
|
maxindex = index;
|
|
}
|
|
alpha[index] =
|
|
(byte)getIntAttribute(tRNS_paletteEntry,
|
|
"alpha");
|
|
|
|
tRNS_paletteEntry =
|
|
tRNS_paletteEntry.getNextSibling();
|
|
}
|
|
|
|
int numEntries = maxindex + 1;
|
|
tRNS_alpha = new byte[numEntries];
|
|
tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
|
|
} else if (tRNS_name.equals("tRNS_Grayscale")) {
|
|
tRNS_gray = getIntAttribute(tRNS_node, "gray");
|
|
tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
} else if (tRNS_name.equals("tRNS_RGB")) {
|
|
tRNS_red = getIntAttribute(tRNS_node, "red");
|
|
tRNS_green = getIntAttribute(tRNS_node, "green");
|
|
tRNS_blue = getIntAttribute(tRNS_node, "blue");
|
|
tRNS_colorType = PNGImageReader.PNG_COLOR_RGB;
|
|
} else {
|
|
fatal(node, "Bad child of a tRNS node!");
|
|
}
|
|
if (tRNS_node.getNextSibling() != null) {
|
|
fatal(node, "tRNS node has more than one child!");
|
|
}
|
|
|
|
tRNS_present = true;
|
|
} else if (name.equals("zTXt")) {
|
|
Node zTXt_node = node.getFirstChild();
|
|
while (zTXt_node != null) {
|
|
if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
|
|
fatal(node,
|
|
"Only an zTXtEntry may be a child of an zTXt!");
|
|
}
|
|
|
|
String keyword = getAttribute(zTXt_node, "keyword");
|
|
zTXt_keyword.add(keyword);
|
|
|
|
int compressionMethod =
|
|
getEnumeratedAttribute(zTXt_node, "compressionMethod",
|
|
zTXt_compressionMethodNames);
|
|
zTXt_compressionMethod.add(new Integer(compressionMethod));
|
|
|
|
String text = getAttribute(zTXt_node, "text");
|
|
zTXt_text.add(text);
|
|
|
|
zTXt_node = zTXt_node.getNextSibling();
|
|
}
|
|
} else if (name.equals("UnknownChunks")) {
|
|
Node unknown_node = node.getFirstChild();
|
|
while (unknown_node != null) {
|
|
if (!unknown_node.getNodeName().equals("UnknownChunk")) {
|
|
fatal(node,
|
|
"Only an UnknownChunk may be a child of an UnknownChunks!");
|
|
}
|
|
String chunkType = getAttribute(unknown_node, "type");
|
|
Object chunkData =
|
|
((IIOMetadataNode)unknown_node).getUserObject();
|
|
|
|
if (chunkType.length() != 4) {
|
|
fatal(unknown_node,
|
|
"Chunk type must be 4 characters!");
|
|
}
|
|
if (chunkData == null) {
|
|
fatal(unknown_node,
|
|
"No chunk data present in user object!");
|
|
}
|
|
if (!(chunkData instanceof byte[])) {
|
|
fatal(unknown_node,
|
|
"User object not a byte array!");
|
|
}
|
|
unknownChunkType.add(chunkType);
|
|
unknownChunkData.add(((byte[])chunkData).clone());
|
|
|
|
unknown_node = unknown_node.getNextSibling();
|
|
}
|
|
} else {
|
|
fatal(node, "Unknown child of root node!");
|
|
}
|
|
|
|
node = node.getNextSibling();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Accrding to PNG spec, keywords are restricted to 1 to 79 bytes
|
|
* in length. Keywords shall contain only printable Latin-1 characters
|
|
* and spaces; To reduce the chances for human misreading of a keyword,
|
|
* leading spaces, trailing spaces, and consecutive spaces are not
|
|
* permitted in keywords.
|
|
*
|
|
* See: http://www.w3.org/TR/PNG/#11keywords
|
|
*/
|
|
private boolean isValidKeyword(String s) {
|
|
int len = s.length();
|
|
if (len < 1 || len >= 80) {
|
|
return false;
|
|
}
|
|
if (s.startsWith(" ") || s.endsWith(" ") || s.contains(" ")) {
|
|
return false;
|
|
}
|
|
return isISOLatin(s, false);
|
|
}
|
|
|
|
/*
|
|
* According to PNG spec, keyword shall contain only printable
|
|
* Latin-1 [ISO-8859-1] characters and spaces; that is, only
|
|
* character codes 32-126 and 161-255 decimal are allowed.
|
|
* For Latin-1 value fields the 0x10 (linefeed) control
|
|
* character is aloowed too.
|
|
*
|
|
* See: http://www.w3.org/TR/PNG/#11keywords
|
|
*/
|
|
private boolean isISOLatin(String s, boolean isLineFeedAllowed) {
|
|
int len = s.length();
|
|
for (int i = 0; i < len; i++) {
|
|
char c = s.charAt(i);
|
|
if (c < 32 || c > 255 || (c > 126 && c < 161)) {
|
|
// not printable. Check whether this is an allowed
|
|
// control char
|
|
if (!isLineFeedAllowed || c != 0x10) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void mergeStandardTree(Node root)
|
|
throws IIOInvalidTreeException {
|
|
Node node = root;
|
|
if (!node.getNodeName()
|
|
.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
|
fatal(node, "Root must be " +
|
|
IIOMetadataFormatImpl.standardMetadataFormatName);
|
|
}
|
|
|
|
node = node.getFirstChild();
|
|
while (node != null) {
|
|
String name = node.getNodeName();
|
|
|
|
if (name.equals("Chroma")) {
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("Gamma")) {
|
|
float gamma = getFloatAttribute(child, "value");
|
|
gAMA_present = true;
|
|
gAMA_gamma = (int)(gamma*100000 + 0.5);
|
|
} else if (childName.equals("Palette")) {
|
|
byte[] red = new byte[256];
|
|
byte[] green = new byte[256];
|
|
byte[] blue = new byte[256];
|
|
int maxindex = -1;
|
|
|
|
Node entry = child.getFirstChild();
|
|
while (entry != null) {
|
|
int index = getIntAttribute(entry, "index");
|
|
if (index >= 0 && index <= 255) {
|
|
red[index] =
|
|
(byte)getIntAttribute(entry, "red");
|
|
green[index] =
|
|
(byte)getIntAttribute(entry, "green");
|
|
blue[index] =
|
|
(byte)getIntAttribute(entry, "blue");
|
|
if (index > maxindex) {
|
|
maxindex = index;
|
|
}
|
|
}
|
|
entry = entry.getNextSibling();
|
|
}
|
|
|
|
int numEntries = maxindex + 1;
|
|
PLTE_red = new byte[numEntries];
|
|
PLTE_green = new byte[numEntries];
|
|
PLTE_blue = new byte[numEntries];
|
|
System.arraycopy(red, 0, PLTE_red, 0, numEntries);
|
|
System.arraycopy(green, 0, PLTE_green, 0, numEntries);
|
|
System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
|
|
PLTE_present = true;
|
|
} else if (childName.equals("BackgroundIndex")) {
|
|
bKGD_present = true;
|
|
bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
|
|
bKGD_index = getIntAttribute(child, "value");
|
|
} else if (childName.equals("BackgroundColor")) {
|
|
int red = getIntAttribute(child, "red");
|
|
int green = getIntAttribute(child, "green");
|
|
int blue = getIntAttribute(child, "blue");
|
|
if (red == green && red == blue) {
|
|
bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
bKGD_gray = red;
|
|
} else {
|
|
bKGD_red = red;
|
|
bKGD_green = green;
|
|
bKGD_blue = blue;
|
|
}
|
|
bKGD_present = true;
|
|
}
|
|
// } else if (childName.equals("ColorSpaceType")) {
|
|
// } else if (childName.equals("NumChannels")) {
|
|
|
|
child = child.getNextSibling();
|
|
}
|
|
} else if (name.equals("Compression")) {
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("NumProgressiveScans")) {
|
|
// Use Adam7 if NumProgressiveScans > 1
|
|
int scans = getIntAttribute(child, "value");
|
|
IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
|
|
// } else if (childName.equals("CompressionTypeName")) {
|
|
// } else if (childName.equals("Lossless")) {
|
|
// } else if (childName.equals("BitRate")) {
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
} else if (name.equals("Data")) {
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("BitsPerSample")) {
|
|
String s = getAttribute(child, "value");
|
|
StringTokenizer t = new StringTokenizer(s);
|
|
int maxBits = -1;
|
|
while (t.hasMoreTokens()) {
|
|
int bits = Integer.parseInt(t.nextToken());
|
|
if (bits > maxBits) {
|
|
maxBits = bits;
|
|
}
|
|
}
|
|
if (maxBits < 1) {
|
|
maxBits = 1;
|
|
}
|
|
if (maxBits == 3) maxBits = 4;
|
|
if (maxBits > 4 || maxBits < 8) {
|
|
maxBits = 8;
|
|
}
|
|
if (maxBits > 8) {
|
|
maxBits = 16;
|
|
}
|
|
IHDR_bitDepth = maxBits;
|
|
} else if (childName.equals("SignificantBitsPerSample")) {
|
|
String s = getAttribute(child, "value");
|
|
StringTokenizer t = new StringTokenizer(s);
|
|
int numTokens = t.countTokens();
|
|
if (numTokens == 1) {
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
|
|
sBIT_grayBits = Integer.parseInt(t.nextToken());
|
|
} else if (numTokens == 2) {
|
|
sBIT_colorType =
|
|
PNGImageReader.PNG_COLOR_GRAY_ALPHA;
|
|
sBIT_grayBits = Integer.parseInt(t.nextToken());
|
|
sBIT_alphaBits = Integer.parseInt(t.nextToken());
|
|
} else if (numTokens == 3) {
|
|
sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
|
|
sBIT_redBits = Integer.parseInt(t.nextToken());
|
|
sBIT_greenBits = Integer.parseInt(t.nextToken());
|
|
sBIT_blueBits = Integer.parseInt(t.nextToken());
|
|
} else if (numTokens == 4) {
|
|
sBIT_colorType =
|
|
PNGImageReader.PNG_COLOR_RGB_ALPHA;
|
|
sBIT_redBits = Integer.parseInt(t.nextToken());
|
|
sBIT_greenBits = Integer.parseInt(t.nextToken());
|
|
sBIT_blueBits = Integer.parseInt(t.nextToken());
|
|
sBIT_alphaBits = Integer.parseInt(t.nextToken());
|
|
}
|
|
if (numTokens >= 1 && numTokens <= 4) {
|
|
sBIT_present = true;
|
|
}
|
|
// } else if (childName.equals("PlanarConfiguration")) {
|
|
// } else if (childName.equals("SampleFormat")) {
|
|
// } else if (childName.equals("SampleMSB")) {
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
} else if (name.equals("Dimension")) {
|
|
boolean gotWidth = false;
|
|
boolean gotHeight = false;
|
|
boolean gotAspectRatio = false;
|
|
|
|
float width = -1.0F;
|
|
float height = -1.0F;
|
|
float aspectRatio = -1.0F;
|
|
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("PixelAspectRatio")) {
|
|
aspectRatio = getFloatAttribute(child, "value");
|
|
gotAspectRatio = true;
|
|
} else if (childName.equals("HorizontalPixelSize")) {
|
|
width = getFloatAttribute(child, "value");
|
|
gotWidth = true;
|
|
} else if (childName.equals("VerticalPixelSize")) {
|
|
height = getFloatAttribute(child, "value");
|
|
gotHeight = true;
|
|
// } else if (childName.equals("ImageOrientation")) {
|
|
// } else if
|
|
// (childName.equals("HorizontalPhysicalPixelSpacing")) {
|
|
// } else if
|
|
// (childName.equals("VerticalPhysicalPixelSpacing")) {
|
|
// } else if (childName.equals("HorizontalPosition")) {
|
|
// } else if (childName.equals("VerticalPosition")) {
|
|
// } else if (childName.equals("HorizontalPixelOffset")) {
|
|
// } else if (childName.equals("VerticalPixelOffset")) {
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
|
|
if (gotWidth && gotHeight) {
|
|
pHYs_present = true;
|
|
pHYs_unitSpecifier = 1;
|
|
pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F);
|
|
pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F);
|
|
} else if (gotAspectRatio) {
|
|
pHYs_present = true;
|
|
pHYs_unitSpecifier = 0;
|
|
|
|
// Find a reasonable rational approximation
|
|
int denom = 1;
|
|
for (; denom < 100; denom++) {
|
|
int num = (int)(aspectRatio*denom);
|
|
if (Math.abs(num/denom - aspectRatio) < 0.001) {
|
|
break;
|
|
}
|
|
}
|
|
pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
|
|
pHYs_pixelsPerUnitYAxis = denom;
|
|
}
|
|
} else if (name.equals("Document")) {
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("ImageModificationTime")) {
|
|
tIME_present = true;
|
|
tIME_year = getIntAttribute(child, "year");
|
|
tIME_month = getIntAttribute(child, "month");
|
|
tIME_day = getIntAttribute(child, "day");
|
|
tIME_hour =
|
|
getIntAttribute(child, "hour", 0, false);
|
|
tIME_minute =
|
|
getIntAttribute(child, "minute", 0, false);
|
|
tIME_second =
|
|
getIntAttribute(child, "second", 0, false);
|
|
// } else if (childName.equals("SubimageInterpretation")) {
|
|
// } else if (childName.equals("ImageCreationTime")) {
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
} else if (name.equals("Text")) {
|
|
Node child = node.getFirstChild();
|
|
while (child != null) {
|
|
String childName = child.getNodeName();
|
|
if (childName.equals("TextEntry")) {
|
|
String keyword =
|
|
getAttribute(child, "keyword", "", false);
|
|
String value = getAttribute(child, "value");
|
|
String language =
|
|
getAttribute(child, "language", "", false);
|
|
String compression =
|
|
getAttribute(child, "compression", "none", false);
|
|
|
|
if (!isValidKeyword(keyword)) {
|
|
// Just ignore this node, PNG requires keywords
|
|
} else if (isISOLatin(value, true)) {
|
|
if (compression.equals("zip")) {
|
|
// Use a zTXt node
|
|
zTXt_keyword.add(keyword);
|
|
zTXt_text.add(value);
|
|
zTXt_compressionMethod.add(Integer.valueOf(0));
|
|
} else {
|
|
// Use a tEXt node
|
|
tEXt_keyword.add(keyword);
|
|
tEXt_text.add(value);
|
|
}
|
|
} else {
|
|
// Use an iTXt node
|
|
iTXt_keyword.add(keyword);
|
|
iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip")));
|
|
iTXt_compressionMethod.add(Integer.valueOf(0));
|
|
iTXt_languageTag.add(language);
|
|
iTXt_translatedKeyword.add(keyword); // fake it
|
|
iTXt_text.add(value);
|
|
}
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
// } else if (name.equals("Transparency")) {
|
|
// Node child = node.getFirstChild();
|
|
// while (child != null) {
|
|
// String childName = child.getNodeName();
|
|
// if (childName.equals("Alpha")) {
|
|
// } else if (childName.equals("TransparentIndex")) {
|
|
// } else if (childName.equals("TransparentColor")) {
|
|
// } else if (childName.equals("TileTransparencies")) {
|
|
// } else if (childName.equals("TileOpacities")) {
|
|
// }
|
|
// child = child.getNextSibling();
|
|
// }
|
|
// } else {
|
|
// // fatal(node, "Unknown child of root node!");
|
|
}
|
|
|
|
node = node.getNextSibling();
|
|
}
|
|
}
|
|
|
|
// Reset all instance variables to their initial state
|
|
public void reset() {
|
|
IHDR_present = false;
|
|
PLTE_present = false;
|
|
bKGD_present = false;
|
|
cHRM_present = false;
|
|
gAMA_present = false;
|
|
hIST_present = false;
|
|
iCCP_present = false;
|
|
iTXt_keyword = new ArrayList<String>();
|
|
iTXt_compressionFlag = new ArrayList<Boolean>();
|
|
iTXt_compressionMethod = new ArrayList<Integer>();
|
|
iTXt_languageTag = new ArrayList<String>();
|
|
iTXt_translatedKeyword = new ArrayList<String>();
|
|
iTXt_text = new ArrayList<String>();
|
|
pHYs_present = false;
|
|
sBIT_present = false;
|
|
sPLT_present = false;
|
|
sRGB_present = false;
|
|
tEXt_keyword = new ArrayList<String>();
|
|
tEXt_text = new ArrayList<String>();
|
|
tIME_present = false;
|
|
tRNS_present = false;
|
|
zTXt_keyword = new ArrayList<String>();
|
|
zTXt_compressionMethod = new ArrayList<Integer>();
|
|
zTXt_text = new ArrayList<String>();
|
|
unknownChunkType = new ArrayList<String>();
|
|
unknownChunkData = new ArrayList<byte[]>();
|
|
}
|
|
}
|