0001: /*
0002: * $RCSfile: CLibPNGMetadata.java,v $
0003: *
0004: *
0005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * - Redistribution of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * - Redistribution in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * Neither the name of Sun Microsystems, Inc. or the names of
0020: * contributors may be used to endorse or promote products derived
0021: * from this software without specific prior written permission.
0022: *
0023: * This software is provided "AS IS," without a warranty of any
0024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0035: * POSSIBILITY OF SUCH DAMAGES.
0036: *
0037: * You acknowledge that this software is not designed or intended for
0038: * use in the design, construction, operation or maintenance of any
0039: * nuclear facility.
0040: *
0041: * $Revision: 1.3 $
0042: * $Date: 2006/02/27 17:25:04 $
0043: * $State: Exp $
0044: */
0045:
0046: package com.sun.media.imageioimpl.plugins.png;
0047:
0048: import java.awt.image.ColorModel;
0049: import java.awt.image.IndexColorModel;
0050: import java.awt.image.SampleModel;
0051: import java.io.ByteArrayInputStream;
0052: import java.io.InputStream;
0053: import java.io.IOException;
0054: import java.io.UnsupportedEncodingException;
0055: import java.util.ArrayList;
0056: import java.util.Arrays;
0057: import java.util.Calendar;
0058: import java.util.GregorianCalendar;
0059: import java.util.Iterator;
0060: import java.util.List;
0061: import java.util.StringTokenizer;
0062: import java.util.zip.DataFormatException;
0063: import java.util.zip.Deflater;
0064: import java.util.zip.Inflater;
0065: import javax.imageio.IIOException;
0066: import javax.imageio.ImageTypeSpecifier;
0067: import javax.imageio.ImageWriteParam;
0068: import javax.imageio.metadata.IIOInvalidTreeException;
0069: import javax.imageio.metadata.IIOMetadata;
0070: import javax.imageio.metadata.IIOMetadataFormat;
0071: import javax.imageio.metadata.IIOMetadataFormatImpl;
0072: import javax.imageio.metadata.IIOMetadataNode;
0073: import javax.imageio.stream.ImageInputStream;
0074: import javax.imageio.stream.MemoryCacheImageInputStream;
0075: import org.w3c.dom.Node;
0076: import com.sun.medialib.codec.png.Decoder;
0077: import com.sun.medialib.codec.png.Encoder;
0078: import com.sun.medialib.codec.png.PNGChunk;
0079: import com.sun.medialib.codec.png.PNGTextualData;
0080: import com.sun.medialib.codec.jiio.mediaLibImage;
0081:
0082: //
0083: // Core J2SE problems fixed in this package:
0084: // 5109146:
0085: // PNG: Background color initialization from standard metadata is incomplete
0086: // 5109114:
0087: // PNG: Cannot set IHDR_bitDepth from standard metadata /Data/BitsPerSample
0088: // 5106305:
0089: // PNG standard to native image metadata conversion incorrect for pixel size
0090: // 5106550:
0091: // PNG writer merge standard metadata fails for TextEntry sans #IMPLIED
0092: // attributes
0093: // 5082756:
0094: // Image I/O plug-ins set metadata boolean attributes to "true" or "false"
0095: // 5105068:
0096: // PNGImageWriter.convertImageMetadata() broken for non-PNGMetadata
0097: //
0098:
0099: /**
0100: */
0101: public class CLibPNGMetadata extends IIOMetadata implements Cloneable {
0102:
0103: // package scope
0104: public static final String nativeMetadataFormatName = "javax_imageio_png_1.0";
0105:
0106: protected static final String nativeMetadataFormatClassName = "com.sun.media.imageioimpl.plugins.png.CLibPNGMetadataFormat";
0107:
0108: // Color types for IHDR chunk
0109: public static final String[] IHDR_colorTypeNames = { "Grayscale",
0110: null, "RGB", "Palette", "GrayAlpha", null, "RGBAlpha" };
0111:
0112: public static final int[] IHDR_numChannels = { 1, 0, 3, 3, 2, 0, 4 };
0113:
0114: // Bit depths for IHDR chunk
0115: public static final String[] IHDR_bitDepths = { "1", "2", "4", "8",
0116: "16" };
0117:
0118: // Compression methods for IHDR chunk
0119: public static final String[] IHDR_compressionMethodNames = { "deflate" };
0120:
0121: // Filter methods for IHDR chunk
0122: public static final String[] IHDR_filterMethodNames = { "adaptive" };
0123:
0124: // Interlace methods for IHDR chunk
0125: public static final String[] IHDR_interlaceMethodNames = { "none",
0126: "adam7" };
0127:
0128: // Compression methods for iCCP chunk
0129: public static final String[] iCCP_compressionMethodNames = { "deflate" };
0130:
0131: // Compression methods for zTXt chunk
0132: public static final String[] zTXt_compressionMethodNames = { "deflate" };
0133:
0134: // "Unknown" unit for pHYs chunk
0135: public static final int PHYS_UNIT_UNKNOWN = 0;
0136:
0137: // "Meter" unit for pHYs chunk
0138: public static final int PHYS_UNIT_METER = 1;
0139:
0140: // Unit specifiers for pHYs chunk
0141: public static final String[] unitSpecifierNames = { "unknown",
0142: "meter" };
0143:
0144: // Rendering intents for sRGB chunk
0145: public static final String[] renderingIntentNames = { "Perceptual", // 0
0146: "Relative colorimetric", // 1
0147: "Saturation", // 2
0148: "Absolute colorimetric" // 3
0149:
0150: };
0151:
0152: // Color space types for Chroma->ColorSpaceType node
0153: public static final String[] colorSpaceTypeNames = { "GRAY", null,
0154: "RGB", "RGB", "GRAY", null, "RGB" };
0155:
0156: // BEGIN Definitions required for reading.
0157:
0158: // Critical chunks
0159: static final int IHDR_TYPE = chunkType("IHDR");
0160: static final int PLTE_TYPE = chunkType("PLTE");
0161: static final int IDAT_TYPE = chunkType("IDAT");
0162: static final int IEND_TYPE = chunkType("IEND");
0163:
0164: // Ancillary chunks
0165: static final int bKGD_TYPE = chunkType("bKGD");
0166: static final int cHRM_TYPE = chunkType("cHRM");
0167: static final int gAMA_TYPE = chunkType("gAMA");
0168: static final int hIST_TYPE = chunkType("hIST");
0169: static final int iCCP_TYPE = chunkType("iCCP");
0170: static final int iTXt_TYPE = chunkType("iTXt");
0171: static final int pHYs_TYPE = chunkType("pHYs");
0172: static final int sBIT_TYPE = chunkType("sBIT");
0173: static final int sPLT_TYPE = chunkType("sPLT");
0174: static final int sRGB_TYPE = chunkType("sRGB");
0175: static final int tEXt_TYPE = chunkType("tEXt");
0176: static final int tIME_TYPE = chunkType("tIME");
0177: static final int tRNS_TYPE = chunkType("tRNS");
0178: static final int zTXt_TYPE = chunkType("zTXt");
0179:
0180: static final int PNG_COLOR_GRAY = 0;
0181: static final int PNG_COLOR_RGB = 2;
0182: static final int PNG_COLOR_PALETTE = 3;
0183: static final int PNG_COLOR_GRAY_ALPHA = 4;
0184: static final int PNG_COLOR_RGB_ALPHA = 6;
0185:
0186: // END Definitions required for reading.
0187:
0188: // IHDR chunk
0189: public boolean IHDR_present;
0190: public int IHDR_width;
0191: public int IHDR_height;
0192: public int IHDR_bitDepth;
0193: public int IHDR_colorType;
0194: public int IHDR_compressionMethod;
0195: public int IHDR_filterMethod;
0196: public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
0197:
0198: // PLTE chunk
0199: public boolean PLTE_present;
0200: public byte[] PLTE_red;
0201: public byte[] PLTE_green;
0202: public byte[] PLTE_blue;
0203:
0204: // bKGD chunk
0205: // If external (non-PNG sourced) data has red = green = blue,
0206: // always store it as gray and promote when writing
0207: public boolean bKGD_present;
0208: public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
0209: public int bKGD_index;
0210: public int bKGD_gray;
0211: public int bKGD_red;
0212: public int bKGD_green;
0213: public int bKGD_blue;
0214:
0215: // cHRM chunk
0216: public boolean cHRM_present;
0217: public int cHRM_whitePointX;
0218: public int cHRM_whitePointY;
0219: public int cHRM_redX;
0220: public int cHRM_redY;
0221: public int cHRM_greenX;
0222: public int cHRM_greenY;
0223: public int cHRM_blueX;
0224: public int cHRM_blueY;
0225:
0226: // gAMA chunk
0227: public boolean gAMA_present;
0228: public int gAMA_gamma;
0229:
0230: // hIST chunk
0231: public boolean hIST_present;
0232: public char[] hIST_histogram;
0233:
0234: // iCCP chunk
0235: public boolean iCCP_present;
0236: public String iCCP_profileName;
0237: public int iCCP_compressionMethod;
0238: public byte[] iCCP_compressedProfile;
0239:
0240: // iTXt chunk
0241: public ArrayList iTXt_keyword = new ArrayList(); // Strings
0242: public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers
0243: public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers
0244: public ArrayList iTXt_languageTag = new ArrayList(); // Strings
0245: public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings
0246: public ArrayList iTXt_text = new ArrayList(); // Strings
0247:
0248: // pHYs chunk
0249: public boolean pHYs_present;
0250: public int pHYs_pixelsPerUnitXAxis;
0251: public int pHYs_pixelsPerUnitYAxis;
0252: public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
0253:
0254: // sBIT chunk
0255: public boolean sBIT_present;
0256: public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
0257: public int sBIT_grayBits;
0258: public int sBIT_redBits;
0259: public int sBIT_greenBits;
0260: public int sBIT_blueBits;
0261: public int sBIT_alphaBits;
0262:
0263: // sPLT chunk
0264: public boolean sPLT_present;
0265: public String sPLT_paletteName; // 1-79 characters
0266: public int sPLT_sampleDepth; // 8 or 16
0267: public int[] sPLT_red;
0268: public int[] sPLT_green;
0269: public int[] sPLT_blue;
0270: public int[] sPLT_alpha;
0271: public int[] sPLT_frequency;
0272:
0273: // sRGB chunk
0274: public boolean sRGB_present;
0275: public int sRGB_renderingIntent;
0276:
0277: // tEXt chunk
0278: public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings
0279: public ArrayList tEXt_text = new ArrayList(); // Strings
0280:
0281: // tIME chunk
0282: public boolean tIME_present;
0283: public int tIME_year;
0284: public int tIME_month;
0285: public int tIME_day;
0286: public int tIME_hour;
0287: public int tIME_minute;
0288: public int tIME_second;
0289:
0290: // tRNS chunk
0291: // If external (non-PNG sourced) data has red = green = blue,
0292: // always store it as gray and promote when writing
0293: public boolean tRNS_present;
0294: public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
0295: public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
0296: public int tRNS_gray;
0297: public int tRNS_red;
0298: public int tRNS_green;
0299: public int tRNS_blue;
0300:
0301: // zTXt chunk
0302: public ArrayList zTXt_keyword = new ArrayList(); // Strings
0303: public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers
0304: public ArrayList zTXt_text = new ArrayList(); // Strings
0305:
0306: // Unknown chunks
0307: public ArrayList unknownChunkType = new ArrayList(); // Strings
0308: public ArrayList unknownChunkData = new ArrayList(); // byte arrays
0309:
0310: /**
0311: * Converts its parameter to another <code>String</code> which contains
0312: * only printable Latin-1 characters but not leading, trailing, or
0313: * consecutive spaces.
0314: *
0315: * @param s the <code>String</code> to convert.
0316: * @return a printable Latin-1 <code>String</code> sans superfluous spaces.
0317: */
0318: static String toPrintableLatin1(String s) {
0319: // Pass a null right back.
0320: if (s == null)
0321: return null;
0322:
0323: // Get Latin-1 characters.
0324: byte[] data = null;
0325: try {
0326: data = s.getBytes("ISO-8859-1");
0327: } catch (UnsupportedEncodingException e) {
0328: // In theory this should not happen (assert).
0329: data = s.getBytes();
0330: }
0331:
0332: // Copy printable characters omitting leading spaces and
0333: // all but first trailing space.
0334: int len = 0;
0335: int prev = 0;
0336: for (int i = 0; i < data.length; i++) {
0337: int d = data[i] & 0xFF;
0338: if (prev == 32 && d == 32)
0339: continue;
0340: if ((d > 32 && d <= 126) || (d >= 161 && d <= 255)
0341: || (d == 32 && len != 0))
0342: data[len++] = (byte) d;
0343: prev = d;
0344: }
0345:
0346: // Return an empty string if no acceptable characters.
0347: if (len == 0)
0348: return "";
0349:
0350: // Omit trailing space, if any.
0351: if (data[len - 1] == 32)
0352: len--;
0353:
0354: return new String(data, 0, len);
0355: }
0356:
0357: public CLibPNGMetadata() {
0358: super (true, nativeMetadataFormatName,
0359: nativeMetadataFormatClassName, null, null);
0360: }
0361:
0362: public CLibPNGMetadata(IIOMetadata metadata)
0363: throws IIOInvalidTreeException {
0364:
0365: this ();
0366:
0367: if (metadata != null) {
0368: List formats = Arrays.asList(metadata
0369: .getMetadataFormatNames());
0370:
0371: if (formats.contains(nativeMetadataFormatName)) {
0372: // Initialize from native image metadata format.
0373: String format = nativeMetadataFormatName;
0374: setFromTree(format, metadata.getAsTree(format));
0375: } else if (metadata.isStandardMetadataFormatSupported()) {
0376: // Initialize from standard metadata form of the input tree.
0377: String format = IIOMetadataFormatImpl.standardMetadataFormatName;
0378: setFromTree(format, metadata.getAsTree(format));
0379: }
0380: }
0381: }
0382:
0383: /**
0384: * Sets the instance variables of the IHDR and if necessary PLTE and
0385: * tRNS chunks. The <code>numBands</code> parameter is necessary since
0386: * we may only be writing a subset of the image bands.
0387: */
0388: public void initialize(ImageTypeSpecifier imageType, int numBands,
0389: ImageWriteParam param, int interlaceMethod) {
0390: ColorModel colorModel = imageType.getColorModel();
0391: SampleModel sampleModel = imageType.getSampleModel();
0392:
0393: // Intialize IHDR_width and IHDR_height
0394: IHDR_width = sampleModel.getWidth();
0395: IHDR_height = sampleModel.getHeight();
0396:
0397: // Initialize IHDR_bitDepth
0398: int[] sampleSize = sampleModel.getSampleSize();
0399: int bitDepth = sampleSize[0];
0400: // Choose max bit depth over all channels
0401: // Fixes bug 4413109
0402: for (int i = 1; i < sampleSize.length; i++) {
0403: if (sampleSize[i] > bitDepth) {
0404: bitDepth = sampleSize[i];
0405: }
0406: }
0407: // Multi-channel images must have a bit depth of 8 or 16
0408: if (sampleSize.length > 1 && bitDepth < 8) {
0409: bitDepth = 8;
0410: }
0411:
0412: // Round bit depth up to a power of 2
0413: if (bitDepth > 2 && bitDepth < 4) {
0414: bitDepth = 4;
0415: } else if (bitDepth > 4 && bitDepth < 8) {
0416: bitDepth = 8;
0417: } else if (bitDepth > 8 && bitDepth < 16) {
0418: bitDepth = 16;
0419: } else if (bitDepth > 16) {
0420: throw new RuntimeException("bitDepth > 16!");
0421: }
0422: IHDR_bitDepth = bitDepth;
0423:
0424: // Initialize IHDR_colorType
0425: if (colorModel instanceof IndexColorModel) {
0426: IndexColorModel icm = (IndexColorModel) colorModel;
0427: int size = icm.getMapSize();
0428:
0429: byte[] reds = new byte[size];
0430: icm.getReds(reds);
0431: byte[] greens = new byte[size];
0432: icm.getGreens(greens);
0433: byte[] blues = new byte[size];
0434: icm.getBlues(blues);
0435:
0436: // Determine whether the color tables are actually a gray ramp
0437: // if the color type has not been set previously
0438: boolean isGray = false;
0439: if (!IHDR_present || (IHDR_colorType != PNG_COLOR_PALETTE)) {
0440: isGray = true;
0441: int scale = 255 / ((1 << IHDR_bitDepth) - 1);
0442: for (int i = 0; i < size; i++) {
0443: byte red = reds[i];
0444: if ((red != (byte) (i * scale))
0445: || (red != greens[i]) || (red != blues[i])) {
0446: isGray = false;
0447: break;
0448: }
0449: }
0450: }
0451:
0452: // Determine whether transparency exists
0453: boolean hasAlpha = colorModel.hasAlpha();
0454:
0455: byte[] alpha = null;
0456: if (hasAlpha) {
0457: alpha = new byte[size];
0458: icm.getAlphas(alpha);
0459: }
0460:
0461: if (isGray && hasAlpha) {
0462: IHDR_colorType = PNG_COLOR_GRAY_ALPHA;
0463: } else if (isGray) {
0464: IHDR_colorType = PNG_COLOR_GRAY;
0465: } else {
0466: IHDR_colorType = PNG_COLOR_PALETTE;
0467:
0468: // Initialize PLTE chunk
0469: PLTE_present = true;
0470: PLTE_red = (byte[]) reds.clone();
0471: PLTE_green = (byte[]) greens.clone();
0472: PLTE_blue = (byte[]) blues.clone();
0473:
0474: if (hasAlpha) {
0475: // Initialize tRNS chunk
0476: tRNS_present = true;
0477: tRNS_colorType = PNG_COLOR_PALETTE;
0478: tRNS_alpha = (byte[]) alpha.clone();
0479: }
0480: }
0481: } else {
0482: if (numBands == 1) {
0483: IHDR_colorType = PNG_COLOR_GRAY;
0484: } else if (numBands == 2) {
0485: IHDR_colorType = PNG_COLOR_GRAY_ALPHA;
0486: } else if (numBands == 3) {
0487: IHDR_colorType = PNG_COLOR_RGB;
0488: } else if (numBands == 4) {
0489: IHDR_colorType = PNG_COLOR_RGB_ALPHA;
0490: } else {
0491: throw new RuntimeException("Number of bands not 1-4!");
0492: }
0493: }
0494:
0495: // Initialize IHDR_compressionMethod and IHDR_filterMethod
0496: IHDR_compressionMethod = IHDR_filterMethod = 0; // Only supported value
0497:
0498: // Initialize IHDR_interlaceMethod
0499: if (param != null
0500: && param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) {
0501: IHDR_interlaceMethod = 0; // No interlacing.
0502: } else if (param != null
0503: && param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
0504: IHDR_interlaceMethod = 1; // Adam7
0505: } else {
0506: // param == null ||
0507: // param.getProgressiveMode() ==
0508: // ImageWriteParam.MODE_COPY_FROM_METADATA
0509: IHDR_interlaceMethod = interlaceMethod;
0510: }
0511:
0512: IHDR_present = true;
0513: }
0514:
0515: public boolean isReadOnly() {
0516: return false;
0517: }
0518:
0519: private ArrayList cloneBytesArrayList(ArrayList in) {
0520: if (in == null) {
0521: return null;
0522: } else {
0523: ArrayList list = new ArrayList(in.size());
0524: Iterator iter = in.iterator();
0525: while (iter.hasNext()) {
0526: Object o = iter.next();
0527: if (o == null) {
0528: list.add(null);
0529: } else {
0530: list.add(((byte[]) o).clone());
0531: }
0532: }
0533:
0534: return list;
0535: }
0536: }
0537:
0538: // Deep clone
0539: public Object clone() {
0540: CLibPNGMetadata metadata;
0541: try {
0542: metadata = (CLibPNGMetadata) super .clone();
0543: } catch (CloneNotSupportedException e) {
0544: return null;
0545: }
0546:
0547: // unknownChunkData needs deep clone
0548: metadata.unknownChunkData = cloneBytesArrayList(this .unknownChunkData);
0549:
0550: return metadata;
0551: }
0552:
0553: public Node getAsTree(String formatName) {
0554: if (formatName.equals(nativeMetadataFormatName)) {
0555: return getNativeTree();
0556: } else if (formatName
0557: .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
0558: return getStandardTree();
0559: } else {
0560: throw new IllegalArgumentException(
0561: "Not a recognized format!");
0562: }
0563: }
0564:
0565: private Node getNativeTree() {
0566: IIOMetadataNode node = null; // scratch node
0567: IIOMetadataNode root = new IIOMetadataNode(
0568: nativeMetadataFormatName);
0569:
0570: // IHDR
0571: if (IHDR_present) {
0572: IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
0573: IHDR_node.setAttribute("width", Integer
0574: .toString(IHDR_width));
0575: IHDR_node.setAttribute("height", Integer
0576: .toString(IHDR_height));
0577: IHDR_node.setAttribute("bitDepth", Integer
0578: .toString(IHDR_bitDepth));
0579: IHDR_node.setAttribute("colorType",
0580: IHDR_colorTypeNames[IHDR_colorType]);
0581: // IHDR_compressionMethod must be 0 in PNG 1.1
0582: IHDR_node
0583: .setAttribute(
0584: "compressionMethod",
0585: IHDR_compressionMethodNames[IHDR_compressionMethod]);
0586: // IHDR_filterMethod must be 0 in PNG 1.1
0587: IHDR_node.setAttribute("filterMethod",
0588: IHDR_filterMethodNames[IHDR_filterMethod]);
0589: IHDR_node.setAttribute("interlaceMethod",
0590: IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
0591: root.appendChild(IHDR_node);
0592: }
0593:
0594: // PLTE
0595: if (PLTE_present) {
0596: IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
0597: int numEntries = PLTE_red.length;
0598: for (int i = 0; i < numEntries; i++) {
0599: IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
0600: entry.setAttribute("index", Integer.toString(i));
0601: entry.setAttribute("red", Integer
0602: .toString(PLTE_red[i] & 0xff));
0603: entry.setAttribute("green", Integer
0604: .toString(PLTE_green[i] & 0xff));
0605: entry.setAttribute("blue", Integer
0606: .toString(PLTE_blue[i] & 0xff));
0607: PLTE_node.appendChild(entry);
0608: }
0609:
0610: root.appendChild(PLTE_node);
0611: }
0612:
0613: // bKGD
0614: if (bKGD_present) {
0615: IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
0616:
0617: if (bKGD_colorType == PNG_COLOR_PALETTE) {
0618: node = new IIOMetadataNode("bKGD_Palette");
0619: node
0620: .setAttribute("index", Integer
0621: .toString(bKGD_index));
0622: } else if (bKGD_colorType == PNG_COLOR_GRAY) {
0623: node = new IIOMetadataNode("bKGD_Grayscale");
0624: node.setAttribute("gray", Integer.toString(bKGD_gray));
0625: } else if (bKGD_colorType == PNG_COLOR_RGB) {
0626: node = new IIOMetadataNode("bKGD_RGB");
0627: node.setAttribute("red", Integer.toString(bKGD_red));
0628: node
0629: .setAttribute("green", Integer
0630: .toString(bKGD_green));
0631: node.setAttribute("blue", Integer.toString(bKGD_blue));
0632: }
0633: bKGD_node.appendChild(node);
0634:
0635: root.appendChild(bKGD_node);
0636: }
0637:
0638: // cHRM
0639: if (cHRM_present) {
0640: IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
0641: cHRM_node.setAttribute("whitePointX", Integer
0642: .toString(cHRM_whitePointX));
0643: cHRM_node.setAttribute("whitePointY", Integer
0644: .toString(cHRM_whitePointY));
0645: cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
0646: cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
0647: cHRM_node.setAttribute("greenX", Integer
0648: .toString(cHRM_greenX));
0649: cHRM_node.setAttribute("greenY", Integer
0650: .toString(cHRM_greenY));
0651: cHRM_node.setAttribute("blueX", Integer
0652: .toString(cHRM_blueX));
0653: cHRM_node.setAttribute("blueY", Integer
0654: .toString(cHRM_blueY));
0655:
0656: root.appendChild(cHRM_node);
0657: }
0658:
0659: // gAMA
0660: if (gAMA_present) {
0661: IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
0662: gAMA_node.setAttribute("value", Integer
0663: .toString(gAMA_gamma));
0664:
0665: root.appendChild(gAMA_node);
0666: }
0667:
0668: // hIST
0669: if (hIST_present) {
0670: IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
0671:
0672: for (int i = 0; i < hIST_histogram.length; i++) {
0673: IIOMetadataNode hist = new IIOMetadataNode("hISTEntry");
0674: hist.setAttribute("index", Integer.toString(i));
0675: hist.setAttribute("value", Integer
0676: .toString(hIST_histogram[i]));
0677: hIST_node.appendChild(hist);
0678: }
0679:
0680: root.appendChild(hIST_node);
0681: }
0682:
0683: // iCCP
0684: if (iCCP_present) {
0685: IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
0686: iCCP_node.setAttribute("profileName", iCCP_profileName);
0687: iCCP_node
0688: .setAttribute(
0689: "compressionMethod",
0690: iCCP_compressionMethodNames[iCCP_compressionMethod]);
0691:
0692: Object profile = iCCP_compressedProfile;
0693: if (profile != null) {
0694: profile = ((byte[]) profile).clone();
0695: }
0696: iCCP_node.setUserObject(profile);
0697:
0698: root.appendChild(iCCP_node);
0699: }
0700:
0701: // iTXt
0702: if (iTXt_keyword.size() > 0) {
0703: IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
0704: for (int i = 0; i < iTXt_keyword.size(); i++) {
0705: Integer val;
0706:
0707: IIOMetadataNode iTXt_node = new IIOMetadataNode(
0708: "iTXtEntry");
0709: iTXt_node.setAttribute("keyword", (String) iTXt_keyword
0710: .get(i));
0711: val = (Integer) iTXt_compressionFlag.get(i);
0712: iTXt_node.setAttribute("compressionFlag", val
0713: .toString());
0714: val = (Integer) iTXt_compressionMethod.get(i);
0715: iTXt_node.setAttribute("compressionMethod", val
0716: .toString());
0717: iTXt_node.setAttribute("languageTag",
0718: (String) iTXt_languageTag.get(i));
0719: iTXt_node.setAttribute("translatedKeyword",
0720: (String) iTXt_translatedKeyword.get(i));
0721: iTXt_node.setAttribute("text", (String) iTXt_text
0722: .get(i));
0723:
0724: iTXt_parent.appendChild(iTXt_node);
0725: }
0726:
0727: root.appendChild(iTXt_parent);
0728: }
0729:
0730: // pHYs
0731: if (pHYs_present) {
0732: IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
0733: pHYs_node.setAttribute("pixelsPerUnitXAxis", Integer
0734: .toString(pHYs_pixelsPerUnitXAxis));
0735: pHYs_node.setAttribute("pixelsPerUnitYAxis", Integer
0736: .toString(pHYs_pixelsPerUnitYAxis));
0737: pHYs_node.setAttribute("unitSpecifier",
0738: unitSpecifierNames[pHYs_unitSpecifier]);
0739:
0740: root.appendChild(pHYs_node);
0741: }
0742:
0743: // sBIT
0744: if (sBIT_present) {
0745: IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
0746:
0747: if (sBIT_colorType == PNG_COLOR_GRAY) {
0748: node = new IIOMetadataNode("sBIT_Grayscale");
0749: node.setAttribute("gray", Integer
0750: .toString(sBIT_grayBits));
0751: } else if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA) {
0752: node = new IIOMetadataNode("sBIT_GrayAlpha");
0753: node.setAttribute("gray", Integer
0754: .toString(sBIT_grayBits));
0755: node.setAttribute("alpha", Integer
0756: .toString(sBIT_alphaBits));
0757: } else if (sBIT_colorType == PNG_COLOR_RGB) {
0758: node = new IIOMetadataNode("sBIT_RGB");
0759: node
0760: .setAttribute("red", Integer
0761: .toString(sBIT_redBits));
0762: node.setAttribute("green", Integer
0763: .toString(sBIT_greenBits));
0764: node.setAttribute("blue", Integer
0765: .toString(sBIT_blueBits));
0766: } else if (sBIT_colorType == PNG_COLOR_RGB_ALPHA) {
0767: node = new IIOMetadataNode("sBIT_RGBAlpha");
0768: node
0769: .setAttribute("red", Integer
0770: .toString(sBIT_redBits));
0771: node.setAttribute("green", Integer
0772: .toString(sBIT_greenBits));
0773: node.setAttribute("blue", Integer
0774: .toString(sBIT_blueBits));
0775: node.setAttribute("alpha", Integer
0776: .toString(sBIT_alphaBits));
0777: } else if (sBIT_colorType == PNG_COLOR_PALETTE) {
0778: node = new IIOMetadataNode("sBIT_Palette");
0779: node
0780: .setAttribute("red", Integer
0781: .toString(sBIT_redBits));
0782: node.setAttribute("green", Integer
0783: .toString(sBIT_greenBits));
0784: node.setAttribute("blue", Integer
0785: .toString(sBIT_blueBits));
0786: }
0787: sBIT_node.appendChild(node);
0788:
0789: root.appendChild(sBIT_node);
0790: }
0791:
0792: // sPLT
0793: if (sPLT_present) {
0794: IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
0795:
0796: sPLT_node.setAttribute("name", sPLT_paletteName);
0797: sPLT_node.setAttribute("sampleDepth", Integer
0798: .toString(sPLT_sampleDepth));
0799:
0800: int numEntries = sPLT_red.length;
0801: for (int i = 0; i < numEntries; i++) {
0802: IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
0803: entry.setAttribute("index", Integer.toString(i));
0804: entry
0805: .setAttribute("red", Integer
0806: .toString(sPLT_red[i]));
0807: entry.setAttribute("green", Integer
0808: .toString(sPLT_green[i]));
0809: entry.setAttribute("blue", Integer
0810: .toString(sPLT_blue[i]));
0811: entry.setAttribute("alpha", Integer
0812: .toString(sPLT_alpha[i]));
0813: entry.setAttribute("frequency", Integer
0814: .toString(sPLT_frequency[i]));
0815: sPLT_node.appendChild(entry);
0816: }
0817:
0818: root.appendChild(sPLT_node);
0819: }
0820:
0821: // sRGB
0822: if (sRGB_present) {
0823: IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
0824: sRGB_node.setAttribute("renderingIntent",
0825: renderingIntentNames[sRGB_renderingIntent]);
0826:
0827: root.appendChild(sRGB_node);
0828: }
0829:
0830: // tEXt
0831: if (tEXt_keyword.size() > 0) {
0832: IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
0833: for (int i = 0; i < tEXt_keyword.size(); i++) {
0834: IIOMetadataNode tEXt_node = new IIOMetadataNode(
0835: "tEXtEntry");
0836: tEXt_node.setAttribute("keyword", (String) tEXt_keyword
0837: .get(i));
0838: tEXt_node.setAttribute("value", (String) tEXt_text
0839: .get(i));
0840:
0841: tEXt_parent.appendChild(tEXt_node);
0842: }
0843:
0844: root.appendChild(tEXt_parent);
0845: }
0846:
0847: // tIME
0848: if (tIME_present) {
0849: IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
0850: tIME_node.setAttribute("year", Integer.toString(tIME_year));
0851: tIME_node.setAttribute("month", Integer
0852: .toString(tIME_month));
0853: tIME_node.setAttribute("day", Integer.toString(tIME_day));
0854: tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
0855: tIME_node.setAttribute("minute", Integer
0856: .toString(tIME_minute));
0857: tIME_node.setAttribute("second", Integer
0858: .toString(tIME_second));
0859:
0860: root.appendChild(tIME_node);
0861: }
0862:
0863: // tRNS
0864: if (tRNS_present) {
0865: IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
0866:
0867: if (tRNS_colorType == PNG_COLOR_PALETTE) {
0868: node = new IIOMetadataNode("tRNS_Palette");
0869:
0870: for (int i = 0; i < tRNS_alpha.length; i++) {
0871: IIOMetadataNode entry = new IIOMetadataNode(
0872: "tRNS_PaletteEntry");
0873: entry.setAttribute("index", Integer.toString(i));
0874: entry.setAttribute("alpha", Integer
0875: .toString(tRNS_alpha[i] & 0xff));
0876: node.appendChild(entry);
0877: }
0878: } else if (tRNS_colorType == PNG_COLOR_GRAY) {
0879: node = new IIOMetadataNode("tRNS_Grayscale");
0880: node.setAttribute("gray", Integer.toString(tRNS_gray));
0881: } else if (tRNS_colorType == PNG_COLOR_RGB) {
0882: node = new IIOMetadataNode("tRNS_RGB");
0883: node.setAttribute("red", Integer.toString(tRNS_red));
0884: node
0885: .setAttribute("green", Integer
0886: .toString(tRNS_green));
0887: node.setAttribute("blue", Integer.toString(tRNS_blue));
0888: }
0889: tRNS_node.appendChild(node);
0890:
0891: root.appendChild(tRNS_node);
0892: }
0893:
0894: // zTXt
0895: if (zTXt_keyword.size() > 0) {
0896: IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
0897: for (int i = 0; i < zTXt_keyword.size(); i++) {
0898: IIOMetadataNode zTXt_node = new IIOMetadataNode(
0899: "zTXtEntry");
0900: zTXt_node.setAttribute("keyword", (String) zTXt_keyword
0901: .get(i));
0902:
0903: int cm = ((Integer) zTXt_compressionMethod.get(i))
0904: .intValue();
0905: zTXt_node.setAttribute("compressionMethod",
0906: zTXt_compressionMethodNames[cm]);
0907:
0908: zTXt_node.setAttribute("text", (String) zTXt_text
0909: .get(i));
0910:
0911: zTXt_parent.appendChild(zTXt_node);
0912: }
0913:
0914: root.appendChild(zTXt_parent);
0915: }
0916:
0917: // Unknown chunks
0918: if (unknownChunkType.size() > 0) {
0919: IIOMetadataNode unknown_parent = new IIOMetadataNode(
0920: "UnknownChunks");
0921: for (int i = 0; i < unknownChunkType.size(); i++) {
0922: IIOMetadataNode unknown_node = new IIOMetadataNode(
0923: "UnknownChunk");
0924: unknown_node.setAttribute("type",
0925: (String) unknownChunkType.get(i));
0926: unknown_node.setUserObject((byte[]) unknownChunkData
0927: .get(i));
0928:
0929: unknown_parent.appendChild(unknown_node);
0930: }
0931:
0932: root.appendChild(unknown_parent);
0933: }
0934:
0935: return root;
0936: }
0937:
0938: private int getNumChannels() {
0939: // Determine number of channels
0940: // Be careful about palette color with transparency
0941: int numChannels = IHDR_numChannels[IHDR_colorType];
0942: if (IHDR_colorType == PNG_COLOR_PALETTE && tRNS_present
0943: && tRNS_colorType == IHDR_colorType) {
0944: numChannels = 4;
0945: }
0946: return numChannels;
0947: }
0948:
0949: public IIOMetadataNode getStandardChromaNode() {
0950: IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
0951: IIOMetadataNode node = null; // scratch node
0952:
0953: node = new IIOMetadataNode("ColorSpaceType");
0954: node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
0955: chroma_node.appendChild(node);
0956:
0957: node = new IIOMetadataNode("NumChannels");
0958: node.setAttribute("value", Integer.toString(getNumChannels()));
0959: chroma_node.appendChild(node);
0960:
0961: if (gAMA_present) {
0962: node = new IIOMetadataNode("Gamma");
0963: node.setAttribute("value", Float
0964: .toString(gAMA_gamma * 1.0e-5F));
0965: chroma_node.appendChild(node);
0966: }
0967:
0968: node = new IIOMetadataNode("BlackIsZero");
0969: node.setAttribute("value", "TRUE");
0970: chroma_node.appendChild(node);
0971:
0972: if (PLTE_present) {
0973: boolean hasAlpha = tRNS_present
0974: && (tRNS_colorType == PNG_COLOR_PALETTE);
0975:
0976: node = new IIOMetadataNode("Palette");
0977: for (int i = 0; i < PLTE_red.length; i++) {
0978: IIOMetadataNode entry = new IIOMetadataNode(
0979: "PaletteEntry");
0980: entry.setAttribute("index", Integer.toString(i));
0981: entry.setAttribute("red", Integer
0982: .toString(PLTE_red[i] & 0xff));
0983: entry.setAttribute("green", Integer
0984: .toString(PLTE_green[i] & 0xff));
0985: entry.setAttribute("blue", Integer
0986: .toString(PLTE_blue[i] & 0xff));
0987: if (hasAlpha) {
0988: int alpha = (i < tRNS_alpha.length) ? (tRNS_alpha[i] & 0xff)
0989: : 255;
0990: entry
0991: .setAttribute("alpha", Integer
0992: .toString(alpha));
0993: }
0994: node.appendChild(entry);
0995: }
0996: chroma_node.appendChild(node);
0997: }
0998:
0999: if (bKGD_present) {
1000: if (bKGD_colorType == PNG_COLOR_PALETTE) {
1001: node = new IIOMetadataNode("BackgroundIndex");
1002: node
1003: .setAttribute("value", Integer
1004: .toString(bKGD_index));
1005: } else {
1006: node = new IIOMetadataNode("BackgroundColor");
1007: int r, g, b;
1008:
1009: if (bKGD_colorType == PNG_COLOR_GRAY) {
1010: r = g = b = bKGD_gray;
1011: } else {
1012: r = bKGD_red;
1013: g = bKGD_green;
1014: b = bKGD_blue;
1015: }
1016: node.setAttribute("red", Integer.toString(r));
1017: node.setAttribute("green", Integer.toString(g));
1018: node.setAttribute("blue", Integer.toString(b));
1019: }
1020: chroma_node.appendChild(node);
1021: }
1022:
1023: return chroma_node;
1024: }
1025:
1026: public IIOMetadataNode getStandardCompressionNode() {
1027: IIOMetadataNode compression_node = new IIOMetadataNode(
1028: "Compression");
1029: IIOMetadataNode node = null; // scratch node
1030:
1031: node = new IIOMetadataNode("CompressionTypeName");
1032: node.setAttribute("value", "deflate");
1033: compression_node.appendChild(node);
1034:
1035: node = new IIOMetadataNode("Lossless");
1036: node.setAttribute("value", "TRUE");
1037: compression_node.appendChild(node);
1038:
1039: node = new IIOMetadataNode("NumProgressiveScans");
1040: node.setAttribute("value", (IHDR_interlaceMethod == 0) ? "1"
1041: : "7");
1042: compression_node.appendChild(node);
1043:
1044: return compression_node;
1045: }
1046:
1047: private String repeat(String s, int times) {
1048: if (times == 1) {
1049: return s;
1050: }
1051: StringBuffer sb = new StringBuffer((s.length() + 1) * times - 1);
1052: sb.append(s);
1053: for (int i = 1; i < times; i++) {
1054: sb.append(" ");
1055: sb.append(s);
1056: }
1057: return sb.toString();
1058: }
1059:
1060: public IIOMetadataNode getStandardDataNode() {
1061: IIOMetadataNode data_node = new IIOMetadataNode("Data");
1062: IIOMetadataNode node = null; // scratch node
1063:
1064: node = new IIOMetadataNode("PlanarConfiguration");
1065: node.setAttribute("value", "PixelInterleaved");
1066: data_node.appendChild(node);
1067:
1068: node = new IIOMetadataNode("SampleFormat");
1069: node.setAttribute("value",
1070: IHDR_colorType == PNG_COLOR_PALETTE ? "Index"
1071: : "UnsignedIntegral");
1072: data_node.appendChild(node);
1073:
1074: String bitDepth = Integer.toString(IHDR_bitDepth);
1075: node = new IIOMetadataNode("BitsPerSample");
1076: node.setAttribute("value", repeat(bitDepth, getNumChannels()));
1077: data_node.appendChild(node);
1078:
1079: if (sBIT_present) {
1080: node = new IIOMetadataNode("SignificantBitsPerSample");
1081: String sbits;
1082: if (sBIT_colorType == PNG_COLOR_GRAY
1083: || sBIT_colorType == PNG_COLOR_GRAY_ALPHA) {
1084: sbits = Integer.toString(sBIT_grayBits);
1085: } else { // sBIT_colorType == PNG_COLOR_RGB ||
1086: // sBIT_colorType == PNG_COLOR_RGB_ALPHA
1087: sbits = Integer.toString(sBIT_redBits) + " "
1088: + Integer.toString(sBIT_greenBits) + " "
1089: + Integer.toString(sBIT_blueBits);
1090: }
1091:
1092: if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA
1093: || sBIT_colorType == PNG_COLOR_RGB_ALPHA) {
1094: sbits += " " + Integer.toString(sBIT_alphaBits);
1095: }
1096:
1097: node.setAttribute("value", sbits);
1098: data_node.appendChild(node);
1099: }
1100:
1101: // SampleMSB
1102:
1103: return data_node;
1104: }
1105:
1106: public IIOMetadataNode getStandardDimensionNode() {
1107: IIOMetadataNode dimension_node = new IIOMetadataNode(
1108: "Dimension");
1109: IIOMetadataNode node = null; // scratch node
1110:
1111: node = new IIOMetadataNode("PixelAspectRatio");
1112: // aspect ratio is pixel width/height which is the ratio of the
1113: // inverses of pixels per unit length.
1114: float ratio = pHYs_present ? (float) pHYs_pixelsPerUnitYAxis
1115: / pHYs_pixelsPerUnitXAxis : 1.0F;
1116: node.setAttribute("value", Float.toString(ratio));
1117: dimension_node.appendChild(node);
1118:
1119: node = new IIOMetadataNode("ImageOrientation");
1120: node.setAttribute("value", "Normal");
1121: dimension_node.appendChild(node);
1122:
1123: if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
1124: node = new IIOMetadataNode("HorizontalPixelSize");
1125: node.setAttribute("value", Float
1126: .toString(1000.0F / pHYs_pixelsPerUnitXAxis));
1127: dimension_node.appendChild(node);
1128:
1129: node = new IIOMetadataNode("VerticalPixelSize");
1130: node.setAttribute("value", Float
1131: .toString(1000.0F / pHYs_pixelsPerUnitYAxis));
1132: dimension_node.appendChild(node);
1133: }
1134:
1135: return dimension_node;
1136: }
1137:
1138: public IIOMetadataNode getStandardDocumentNode() {
1139: if (!tIME_present) {
1140: return null;
1141: }
1142:
1143: IIOMetadataNode document_node = new IIOMetadataNode("Document");
1144: IIOMetadataNode node = null; // scratch node
1145:
1146: node = new IIOMetadataNode("ImageModificationTime");
1147: node.setAttribute("year", Integer.toString(tIME_year));
1148: node.setAttribute("month", Integer.toString(tIME_month));
1149: node.setAttribute("day", Integer.toString(tIME_day));
1150: node.setAttribute("hour", Integer.toString(tIME_hour));
1151: node.setAttribute("minute", Integer.toString(tIME_minute));
1152: node.setAttribute("second", Integer.toString(tIME_second));
1153: document_node.appendChild(node);
1154:
1155: return document_node;
1156: }
1157:
1158: public IIOMetadataNode getStandardTextNode() {
1159: int numEntries = tEXt_keyword.size() + iTXt_keyword.size()
1160: + zTXt_keyword.size();
1161: if (numEntries == 0) {
1162: return null;
1163: }
1164:
1165: IIOMetadataNode text_node = new IIOMetadataNode("Text");
1166: IIOMetadataNode node = null; // scratch node
1167:
1168: for (int i = 0; i < tEXt_keyword.size(); i++) {
1169: node = new IIOMetadataNode("TextEntry");
1170: node.setAttribute("keyword", (String) tEXt_keyword.get(i));
1171: node.setAttribute("value", (String) tEXt_text.get(i));
1172: node.setAttribute("encoding", "ISO-8859-1");
1173: node.setAttribute("compression", "none");
1174:
1175: text_node.appendChild(node);
1176: }
1177:
1178: for (int i = 0; i < iTXt_keyword.size(); i++) {
1179: node = new IIOMetadataNode("TextEntry");
1180: node.setAttribute("keyword", (String) iTXt_keyword.get(i));
1181: node.setAttribute("value", (String) iTXt_text.get(i));
1182: node.setAttribute("language", (String) iTXt_languageTag
1183: .get(i));
1184: if (((Integer) iTXt_compressionFlag.get(i)).intValue() == 1) {
1185: node.setAttribute("compression", "deflate");
1186: } else {
1187: node.setAttribute("compression", "none");
1188: }
1189:
1190: text_node.appendChild(node);
1191: }
1192:
1193: for (int i = 0; i < zTXt_keyword.size(); i++) {
1194: node = new IIOMetadataNode("TextEntry");
1195: node.setAttribute("keyword", (String) zTXt_keyword.get(i));
1196: node.setAttribute("value", (String) zTXt_text.get(i));
1197: node.setAttribute("compression", "deflate");
1198:
1199: text_node.appendChild(node);
1200: }
1201:
1202: return text_node;
1203: }
1204:
1205: public IIOMetadataNode getStandardTransparencyNode() {
1206: IIOMetadataNode transparency_node = new IIOMetadataNode(
1207: "Transparency");
1208: IIOMetadataNode node = null; // scratch node
1209:
1210: node = new IIOMetadataNode("Alpha");
1211: boolean hasAlpha = (IHDR_colorType == PNG_COLOR_RGB_ALPHA)
1212: || (IHDR_colorType == PNG_COLOR_GRAY_ALPHA)
1213: || (IHDR_colorType == PNG_COLOR_PALETTE && tRNS_present
1214: && (tRNS_colorType == IHDR_colorType) && (tRNS_alpha != null));
1215: node.setAttribute("value", hasAlpha ? "nonpremultiplied"
1216: : "none");
1217: transparency_node.appendChild(node);
1218:
1219: if (tRNS_present) {
1220: if (tRNS_colorType == PNG_COLOR_RGB
1221: || tRNS_colorType == PNG_COLOR_GRAY) {
1222: node = new IIOMetadataNode("TransparentColor");
1223: if (tRNS_colorType == PNG_COLOR_RGB) {
1224: node.setAttribute("value", Integer
1225: .toString(tRNS_red)
1226: + " "
1227: + Integer.toString(tRNS_green)
1228: + " "
1229: + Integer.toString(tRNS_blue));
1230: } else if (tRNS_colorType == PNG_COLOR_GRAY) {
1231: node.setAttribute("value", Integer
1232: .toString(tRNS_gray));
1233: }
1234: transparency_node.appendChild(node);
1235: }
1236: }
1237:
1238: return transparency_node;
1239: }
1240:
1241: // Shorthand for throwing an IIOInvalidTreeException
1242: private void fatal(Node node, String reason)
1243: throws IIOInvalidTreeException {
1244: throw new IIOInvalidTreeException(reason, node);
1245: }
1246:
1247: // Get an integer-valued attribute
1248: private int getIntAttribute(Node node, String name,
1249: int defaultValue, boolean required)
1250: throws IIOInvalidTreeException {
1251: String value = getAttribute(node, name, null, required);
1252: if (value == null) {
1253: return defaultValue;
1254: }
1255: return Integer.parseInt(value);
1256: }
1257:
1258: // Get a float-valued attribute
1259: private float getFloatAttribute(Node node, String name,
1260: float defaultValue, boolean required)
1261: throws IIOInvalidTreeException {
1262: String value = getAttribute(node, name, null, required);
1263: if (value == null) {
1264: return defaultValue;
1265: }
1266: return Float.parseFloat(value);
1267: }
1268:
1269: // Get a required integer-valued attribute
1270: private int getIntAttribute(Node node, String name)
1271: throws IIOInvalidTreeException {
1272: return getIntAttribute(node, name, -1, true);
1273: }
1274:
1275: // Get a required float-valued attribute
1276: private float getFloatAttribute(Node node, String name)
1277: throws IIOInvalidTreeException {
1278: return getFloatAttribute(node, name, -1.0F, true);
1279: }
1280:
1281: // Get a boolean-valued attribute
1282: private boolean getBooleanAttribute(Node node, String name,
1283: boolean defaultValue, boolean required)
1284: throws IIOInvalidTreeException {
1285: Node attr = node.getAttributes().getNamedItem(name);
1286: if (attr == null) {
1287: if (!required) {
1288: return defaultValue;
1289: } else {
1290: fatal(node, "Required attribute " + name
1291: + " not present!");
1292: }
1293: }
1294:
1295: String value = attr.getNodeValue();
1296:
1297: if (value.equalsIgnoreCase("true")) {
1298: return true;
1299: } else if (value.equalsIgnoreCase("false")) {
1300: return false;
1301: } else {
1302: fatal(node, "Attribute " + name
1303: + " must be 'true' or 'false'!");
1304: return false;
1305: }
1306: }
1307:
1308: // Get a required boolean-valued attribute
1309: private boolean getBooleanAttribute(Node node, String name)
1310: throws IIOInvalidTreeException {
1311: return getBooleanAttribute(node, name, false, true);
1312: }
1313:
1314: // Get an enumerated attribute as an index into a String array
1315: private int getEnumeratedAttribute(Node node, String name,
1316: String[] legalNames, int defaultValue, boolean required)
1317: throws IIOInvalidTreeException {
1318: Node attr = node.getAttributes().getNamedItem(name);
1319: if (attr == null) {
1320: if (!required) {
1321: return defaultValue;
1322: } else {
1323: fatal(node, "Required attribute " + name
1324: + " not present!");
1325: }
1326: }
1327:
1328: String value = attr.getNodeValue();
1329:
1330: for (int i = 0; i < legalNames.length; i++) {
1331: if (value.equals(legalNames[i])) {
1332: return i;
1333: }
1334: }
1335:
1336: fatal(node, "Illegal value for attribute " + name + "!");
1337: return -1;
1338: }
1339:
1340: // Get a required enumerated attribute as an index into a String array
1341: private int getEnumeratedAttribute(Node node, String name,
1342: String[] legalNames) throws IIOInvalidTreeException {
1343: return getEnumeratedAttribute(node, name, legalNames, -1, true);
1344: }
1345:
1346: // Get a String-valued attribute
1347: private String getAttribute(Node node, String name,
1348: String defaultValue, boolean required)
1349: throws IIOInvalidTreeException {
1350: Node attr = node.getAttributes().getNamedItem(name);
1351: if (attr == null) {
1352: if (!required) {
1353: return defaultValue;
1354: } else {
1355: fatal(node, "Required attribute " + name
1356: + " not present!");
1357: }
1358: }
1359: return attr.getNodeValue();
1360: }
1361:
1362: // Get a required String-valued attribute
1363: private String getAttribute(Node node, String name)
1364: throws IIOInvalidTreeException {
1365: return getAttribute(node, name, null, true);
1366: }
1367:
1368: public void mergeTree(String formatName, Node root)
1369: throws IIOInvalidTreeException {
1370: if (formatName.equals(nativeMetadataFormatName)) {
1371: if (root == null) {
1372: throw new IllegalArgumentException("root == null!");
1373: }
1374: mergeNativeTree(root);
1375: } else if (formatName
1376: .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
1377: if (root == null) {
1378: throw new IllegalArgumentException("root == null!");
1379: }
1380: mergeStandardTree(root);
1381: } else {
1382: throw new IllegalArgumentException(
1383: "Not a recognized format!");
1384: }
1385: }
1386:
1387: private void mergeNativeTree(Node root)
1388: throws IIOInvalidTreeException {
1389: Node node = root;
1390: if (!node.getNodeName().equals(nativeMetadataFormatName)) {
1391: fatal(node, "Root must be " + nativeMetadataFormatName);
1392: }
1393:
1394: node = node.getFirstChild();
1395: while (node != null) {
1396: String name = node.getNodeName();
1397:
1398: if (name.equals("IHDR")) {
1399: IHDR_width = getIntAttribute(node, "width");
1400: IHDR_height = getIntAttribute(node, "height");
1401: IHDR_bitDepth = getEnumeratedAttribute(node,
1402: "bitDepth", IHDR_bitDepths);
1403: IHDR_colorType = getEnumeratedAttribute(node,
1404: "colorType", IHDR_colorTypeNames);
1405: IHDR_compressionMethod = getEnumeratedAttribute(node,
1406: "compressionMethod",
1407: IHDR_compressionMethodNames);
1408: IHDR_filterMethod = getEnumeratedAttribute(node,
1409: "filterMethod", IHDR_filterMethodNames);
1410: IHDR_interlaceMethod = getEnumeratedAttribute(node,
1411: "interlaceMethod", IHDR_interlaceMethodNames);
1412: IHDR_present = true;
1413: } else if (name.equals("PLTE")) {
1414: byte[] red = new byte[256];
1415: byte[] green = new byte[256];
1416: byte[] blue = new byte[256];
1417: int maxindex = -1;
1418:
1419: Node PLTE_entry = node.getFirstChild();
1420: if (PLTE_entry == null) {
1421: fatal(node, "Palette has no entries!");
1422: }
1423:
1424: while (PLTE_entry != null) {
1425: if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
1426: fatal(node,
1427: "Only a PLTEEntry may be a child of a PLTE!");
1428: }
1429:
1430: int index = getIntAttribute(PLTE_entry, "index");
1431: if (index < 0 || index > 255) {
1432: fatal(node,
1433: "Bad value for PLTEEntry attribute index!");
1434: }
1435: if (index > maxindex) {
1436: maxindex = index;
1437: }
1438: red[index] = (byte) getIntAttribute(PLTE_entry,
1439: "red");
1440: green[index] = (byte) getIntAttribute(PLTE_entry,
1441: "green");
1442: blue[index] = (byte) getIntAttribute(PLTE_entry,
1443: "blue");
1444:
1445: PLTE_entry = PLTE_entry.getNextSibling();
1446: }
1447:
1448: int numEntries = maxindex + 1;
1449: PLTE_red = new byte[numEntries];
1450: PLTE_green = new byte[numEntries];
1451: PLTE_blue = new byte[numEntries];
1452: System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1453: System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1454: System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1455: PLTE_present = true;
1456: } else if (name.equals("bKGD")) {
1457: bKGD_present = false; // Guard against partial overwrite
1458: Node bKGD_node = node.getFirstChild();
1459: if (bKGD_node == null) {
1460: fatal(node, "bKGD node has no children!");
1461: }
1462: String bKGD_name = bKGD_node.getNodeName();
1463: if (bKGD_name.equals("bKGD_Palette")) {
1464: bKGD_index = getIntAttribute(bKGD_node, "index");
1465: bKGD_colorType = PNG_COLOR_PALETTE;
1466: } else if (bKGD_name.equals("bKGD_Grayscale")) {
1467: bKGD_gray = getIntAttribute(bKGD_node, "gray");
1468: bKGD_colorType = PNG_COLOR_GRAY;
1469: } else if (bKGD_name.equals("bKGD_RGB")) {
1470: bKGD_red = getIntAttribute(bKGD_node, "red");
1471: bKGD_green = getIntAttribute(bKGD_node, "green");
1472: bKGD_blue = getIntAttribute(bKGD_node, "blue");
1473: bKGD_colorType = PNG_COLOR_RGB;
1474: } else {
1475: fatal(node, "Bad child of a bKGD node!");
1476: }
1477: if (bKGD_node.getNextSibling() != null) {
1478: fatal(node, "bKGD node has more than one child!");
1479: }
1480:
1481: bKGD_present = true;
1482: } else if (name.equals("cHRM")) {
1483: cHRM_whitePointX = getIntAttribute(node, "whitePointX");
1484: cHRM_whitePointY = getIntAttribute(node, "whitePointY");
1485: cHRM_redX = getIntAttribute(node, "redX");
1486: cHRM_redY = getIntAttribute(node, "redY");
1487: cHRM_greenX = getIntAttribute(node, "greenX");
1488: cHRM_greenY = getIntAttribute(node, "greenY");
1489: cHRM_blueX = getIntAttribute(node, "blueX");
1490: cHRM_blueY = getIntAttribute(node, "blueY");
1491:
1492: cHRM_present = true;
1493: } else if (name.equals("gAMA")) {
1494: gAMA_gamma = getIntAttribute(node, "value");
1495: gAMA_present = true;
1496: } else if (name.equals("hIST")) {
1497: char[] hist = new char[256];
1498: int maxindex = -1;
1499:
1500: Node hIST_entry = node.getFirstChild();
1501: if (hIST_entry == null) {
1502: fatal(node, "hIST node has no children!");
1503: }
1504:
1505: while (hIST_entry != null) {
1506: if (!hIST_entry.getNodeName().equals("hISTEntry")) {
1507: fatal(node,
1508: "Only a hISTEntry may be a child of a hIST!");
1509: }
1510:
1511: int index = getIntAttribute(hIST_entry, "index");
1512: if (index < 0 || index > 255) {
1513: fatal(node,
1514: "Bad value for histEntry attribute index!");
1515: }
1516: if (index > maxindex) {
1517: maxindex = index;
1518: }
1519: hist[index] = (char) getIntAttribute(hIST_entry,
1520: "value");
1521:
1522: hIST_entry = hIST_entry.getNextSibling();
1523: }
1524:
1525: int numEntries = maxindex + 1;
1526: hIST_histogram = new char[numEntries];
1527: System
1528: .arraycopy(hist, 0, hIST_histogram, 0,
1529: numEntries);
1530:
1531: hIST_present = true;
1532: } else if (name.equals("iCCP")) {
1533: iCCP_profileName = toPrintableLatin1(getAttribute(node,
1534: "profileName"));
1535: iCCP_compressionMethod = getEnumeratedAttribute(node,
1536: "compressionMethod",
1537: iCCP_compressionMethodNames);
1538: Object compressedProfile = ((IIOMetadataNode) node)
1539: .getUserObject();
1540: if (compressedProfile == null) {
1541: fatal(node,
1542: "No ICCP profile present in user object!");
1543: }
1544: if (!(compressedProfile instanceof byte[])) {
1545: fatal(node, "User object not a byte array!");
1546: }
1547:
1548: iCCP_compressedProfile = (byte[]) ((byte[]) compressedProfile)
1549: .clone();
1550:
1551: iCCP_present = true;
1552: } else if (name.equals("iTXt")) {
1553: Node iTXt_node = node.getFirstChild();
1554: while (iTXt_node != null) {
1555: if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
1556: fatal(node,
1557: "Only an iTXtEntry may be a child of an iTXt!");
1558: }
1559:
1560: String keyword = toPrintableLatin1(getAttribute(
1561: iTXt_node, "keyword"));
1562: iTXt_keyword.add(keyword);
1563:
1564: boolean compressionFlag = getBooleanAttribute(
1565: iTXt_node, "compressionFlag");
1566: iTXt_compressionFlag.add(new Boolean(
1567: compressionFlag));
1568:
1569: String compressionMethod = getAttribute(iTXt_node,
1570: "compressionMethod");
1571: iTXt_compressionMethod.add(compressionMethod);
1572:
1573: String languageTag = getAttribute(iTXt_node,
1574: "languageTag");
1575: iTXt_languageTag.add(languageTag);
1576:
1577: String translatedKeyword = getAttribute(iTXt_node,
1578: "translatedKeyword");
1579: iTXt_translatedKeyword.add(translatedKeyword);
1580:
1581: String text = getAttribute(iTXt_node, "text");
1582: iTXt_text.add(text);
1583:
1584: iTXt_node = iTXt_node.getNextSibling();
1585: }
1586: } else if (name.equals("pHYs")) {
1587: pHYs_pixelsPerUnitXAxis = getIntAttribute(node,
1588: "pixelsPerUnitXAxis");
1589: pHYs_pixelsPerUnitYAxis = getIntAttribute(node,
1590: "pixelsPerUnitYAxis");
1591: pHYs_unitSpecifier = getEnumeratedAttribute(node,
1592: "unitSpecifier", unitSpecifierNames);
1593:
1594: pHYs_present = true;
1595: } else if (name.equals("sBIT")) {
1596: sBIT_present = false; // Guard against partial overwrite
1597: Node sBIT_node = node.getFirstChild();
1598: if (sBIT_node == null) {
1599: fatal(node, "sBIT node has no children!");
1600: }
1601: String sBIT_name = sBIT_node.getNodeName();
1602: if (sBIT_name.equals("sBIT_Grayscale")) {
1603: sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1604: sBIT_colorType = PNG_COLOR_GRAY;
1605: } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
1606: sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1607: sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1608: sBIT_colorType = PNG_COLOR_GRAY_ALPHA;
1609: } else if (sBIT_name.equals("sBIT_RGB")) {
1610: sBIT_redBits = getIntAttribute(sBIT_node, "red");
1611: sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1612: sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1613: sBIT_colorType = PNG_COLOR_RGB;
1614: } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
1615: sBIT_redBits = getIntAttribute(sBIT_node, "red");
1616: sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1617: sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1618: sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1619: sBIT_colorType = PNG_COLOR_RGB_ALPHA;
1620: } else if (sBIT_name.equals("sBIT_Palette")) {
1621: sBIT_redBits = getIntAttribute(sBIT_node, "red");
1622: sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1623: sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1624: sBIT_colorType = PNG_COLOR_PALETTE;
1625: } else {
1626: fatal(node, "Bad child of an sBIT node!");
1627: }
1628: if (sBIT_node.getNextSibling() != null) {
1629: fatal(node, "sBIT node has more than one child!");
1630: }
1631:
1632: sBIT_present = true;
1633: } else if (name.equals("sPLT")) {
1634: sPLT_paletteName = toPrintableLatin1(getAttribute(node,
1635: "name"));
1636: sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
1637:
1638: int[] red = new int[256];
1639: int[] green = new int[256];
1640: int[] blue = new int[256];
1641: int[] alpha = new int[256];
1642: int[] frequency = new int[256];
1643: int maxindex = -1;
1644:
1645: Node sPLT_entry = node.getFirstChild();
1646: if (sPLT_entry == null) {
1647: fatal(node, "sPLT node has no children!");
1648: }
1649:
1650: while (sPLT_entry != null) {
1651: if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
1652: fatal(node,
1653: "Only an sPLTEntry may be a child of an sPLT!");
1654: }
1655:
1656: int index = getIntAttribute(sPLT_entry, "index");
1657: if (index < 0 || index > 255) {
1658: fatal(node,
1659: "Bad value for PLTEEntry attribute index!");
1660: }
1661: if (index > maxindex) {
1662: maxindex = index;
1663: }
1664: red[index] = getIntAttribute(sPLT_entry, "red");
1665: green[index] = getIntAttribute(sPLT_entry, "green");
1666: blue[index] = getIntAttribute(sPLT_entry, "blue");
1667: alpha[index] = getIntAttribute(sPLT_entry, "alpha");
1668: frequency[index] = getIntAttribute(sPLT_entry,
1669: "frequency");
1670:
1671: sPLT_entry = sPLT_entry.getNextSibling();
1672: }
1673:
1674: int numEntries = maxindex + 1;
1675: sPLT_red = new int[numEntries];
1676: sPLT_green = new int[numEntries];
1677: sPLT_blue = new int[numEntries];
1678: sPLT_alpha = new int[numEntries];
1679: sPLT_frequency = new int[numEntries];
1680: System.arraycopy(red, 0, sPLT_red, 0, numEntries);
1681: System.arraycopy(green, 0, sPLT_green, 0, numEntries);
1682: System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
1683: System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
1684: System.arraycopy(frequency, 0, sPLT_frequency, 0,
1685: numEntries);
1686:
1687: sPLT_present = true;
1688: } else if (name.equals("sRGB")) {
1689: sRGB_renderingIntent = getEnumeratedAttribute(node,
1690: "renderingIntent", renderingIntentNames);
1691:
1692: sRGB_present = true;
1693: } else if (name.equals("tEXt")) {
1694: Node tEXt_node = node.getFirstChild();
1695: while (tEXt_node != null) {
1696: if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
1697: fatal(node,
1698: "Only an tEXtEntry may be a child of an tEXt!");
1699: }
1700:
1701: String keyword = toPrintableLatin1(getAttribute(
1702: tEXt_node, "keyword"));
1703: tEXt_keyword.add(keyword);
1704:
1705: String text = getAttribute(tEXt_node, "value");
1706: tEXt_text.add(text);
1707:
1708: tEXt_node = tEXt_node.getNextSibling();
1709: }
1710: } else if (name.equals("tIME")) {
1711: tIME_year = getIntAttribute(node, "year");
1712: tIME_month = getIntAttribute(node, "month");
1713: tIME_day = getIntAttribute(node, "day");
1714: tIME_hour = getIntAttribute(node, "hour");
1715: tIME_minute = getIntAttribute(node, "minute");
1716: tIME_second = getIntAttribute(node, "second");
1717:
1718: tIME_present = true;
1719: } else if (name.equals("tRNS")) {
1720: tRNS_present = false; // Guard against partial overwrite
1721: Node tRNS_node = node.getFirstChild();
1722: if (tRNS_node == null) {
1723: fatal(node, "tRNS node has no children!");
1724: }
1725: String tRNS_name = tRNS_node.getNodeName();
1726: if (tRNS_name.equals("tRNS_Palette")) {
1727: byte[] alpha = new byte[256];
1728: int maxindex = -1;
1729:
1730: Node tRNS_paletteEntry = tRNS_node.getFirstChild();
1731: if (tRNS_paletteEntry == null) {
1732: fatal(node,
1733: "tRNS_Palette node has no children!");
1734: }
1735: while (tRNS_paletteEntry != null) {
1736: if (!tRNS_paletteEntry.getNodeName().equals(
1737: "tRNS_PaletteEntry")) {
1738: fatal(node,
1739: "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
1740: }
1741: int index = getIntAttribute(tRNS_paletteEntry,
1742: "index");
1743: if (index < 0 || index > 255) {
1744: fatal(node,
1745: "Bad value for tRNS_PaletteEntry attribute index!");
1746: }
1747: if (index > maxindex) {
1748: maxindex = index;
1749: }
1750: alpha[index] = (byte) getIntAttribute(
1751: tRNS_paletteEntry, "alpha");
1752:
1753: tRNS_paletteEntry = tRNS_paletteEntry
1754: .getNextSibling();
1755: }
1756:
1757: int numEntries = maxindex + 1;
1758: tRNS_alpha = new byte[numEntries];
1759: tRNS_colorType = PNG_COLOR_PALETTE;
1760: System.arraycopy(alpha, 0, tRNS_alpha, 0,
1761: numEntries);
1762: } else if (tRNS_name.equals("tRNS_Grayscale")) {
1763: tRNS_gray = getIntAttribute(tRNS_node, "gray");
1764: tRNS_colorType = PNG_COLOR_GRAY;
1765: } else if (tRNS_name.equals("tRNS_RGB")) {
1766: tRNS_red = getIntAttribute(tRNS_node, "red");
1767: tRNS_green = getIntAttribute(tRNS_node, "green");
1768: tRNS_blue = getIntAttribute(tRNS_node, "blue");
1769: tRNS_colorType = PNG_COLOR_RGB;
1770: } else {
1771: fatal(node, "Bad child of a tRNS node!");
1772: }
1773: if (tRNS_node.getNextSibling() != null) {
1774: fatal(node, "tRNS node has more than one child!");
1775: }
1776:
1777: tRNS_present = true;
1778: } else if (name.equals("zTXt")) {
1779: Node zTXt_node = node.getFirstChild();
1780: while (zTXt_node != null) {
1781: if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
1782: fatal(node,
1783: "Only an zTXtEntry may be a child of an zTXt!");
1784: }
1785:
1786: String keyword = toPrintableLatin1(getAttribute(
1787: zTXt_node, "keyword"));
1788: zTXt_keyword.add(keyword);
1789:
1790: int compressionMethod = getEnumeratedAttribute(
1791: zTXt_node, "compressionMethod",
1792: zTXt_compressionMethodNames);
1793: zTXt_compressionMethod.add(new Integer(
1794: compressionMethod));
1795:
1796: String text = getAttribute(zTXt_node, "text");
1797: zTXt_text.add(text);
1798:
1799: zTXt_node = zTXt_node.getNextSibling();
1800: }
1801: } else if (name.equals("UnknownChunks")) {
1802: Node unknown_node = node.getFirstChild();
1803: while (unknown_node != null) {
1804: if (!unknown_node.getNodeName().equals(
1805: "UnknownChunk")) {
1806: fatal(node,
1807: "Only an UnknownChunk may be a child of an UnknownChunks!");
1808: }
1809: String chunkType = getAttribute(unknown_node,
1810: "type");
1811: Object chunkData = ((IIOMetadataNode) unknown_node)
1812: .getUserObject();
1813:
1814: if (chunkType.length() != 4) {
1815: fatal(unknown_node,
1816: "Chunk type must be 4 characters!");
1817: }
1818: if (chunkData == null) {
1819: fatal(unknown_node,
1820: "No chunk data present in user object!");
1821: }
1822: if (!(chunkData instanceof byte[])) {
1823: fatal(unknown_node,
1824: "User object not a byte array!");
1825: }
1826: unknownChunkType.add(chunkType);
1827: unknownChunkData.add(((byte[]) chunkData).clone());
1828:
1829: unknown_node = unknown_node.getNextSibling();
1830: }
1831: } else {
1832: fatal(node, "Unknown child of root node!");
1833: }
1834:
1835: node = node.getNextSibling();
1836: }
1837: }
1838:
1839: private boolean isISOLatin(String s) {
1840: int len = s.length();
1841: for (int i = 0; i < len; i++) {
1842: if (s.charAt(i) > 255) {
1843: return false;
1844: }
1845: }
1846: return true;
1847: }
1848:
1849: private void mergeStandardTree(Node root)
1850: throws IIOInvalidTreeException {
1851: Node node = root;
1852: if (!node.getNodeName().equals(
1853: IIOMetadataFormatImpl.standardMetadataFormatName)) {
1854: fatal(node, "Root must be "
1855: + IIOMetadataFormatImpl.standardMetadataFormatName);
1856: }
1857:
1858: node = node.getFirstChild();
1859: while (node != null) {
1860: String name = node.getNodeName();
1861:
1862: if (name.equals("Chroma")) {
1863: Node child = node.getFirstChild();
1864: while (child != null) {
1865: String childName = child.getNodeName();
1866: if (childName.equals("Gamma")) {
1867: float gamma = getFloatAttribute(child, "value");
1868: gAMA_present = true;
1869: gAMA_gamma = (int) (gamma * 100000 + 0.5);
1870: } else if (childName.equals("Palette")) {
1871: byte[] red = new byte[256];
1872: byte[] green = new byte[256];
1873: byte[] blue = new byte[256];
1874: int maxindex = -1;
1875:
1876: Node entry = child.getFirstChild();
1877: while (entry != null) {
1878: String entryName = entry.getNodeName();
1879: if (entryName.equals("PaletteEntry")) {
1880: int index = getIntAttribute(entry,
1881: "index");
1882: if (index >= 0 && index <= 255) {
1883: red[index] = (byte) getIntAttribute(
1884: entry, "red");
1885: green[index] = (byte) getIntAttribute(
1886: entry, "green");
1887: blue[index] = (byte) getIntAttribute(
1888: entry, "blue");
1889: if (index > maxindex) {
1890: maxindex = index;
1891: }
1892: }
1893: }
1894: entry = entry.getNextSibling();
1895: }
1896:
1897: int numEntries = maxindex + 1;
1898: PLTE_red = new byte[numEntries];
1899: PLTE_green = new byte[numEntries];
1900: PLTE_blue = new byte[numEntries];
1901: System.arraycopy(red, 0, PLTE_red, 0,
1902: numEntries);
1903: System.arraycopy(green, 0, PLTE_green, 0,
1904: numEntries);
1905: System.arraycopy(blue, 0, PLTE_blue, 0,
1906: numEntries);
1907: PLTE_present = true;
1908: } else if (childName.equals("BackgroundIndex")) {
1909: bKGD_present = true;
1910: bKGD_colorType = PNG_COLOR_PALETTE;
1911: bKGD_index = getIntAttribute(child, "value");
1912: } else if (childName.equals("BackgroundColor")) {
1913: int red = getIntAttribute(child, "red");
1914: int green = getIntAttribute(child, "green");
1915: int blue = getIntAttribute(child, "blue");
1916: if (red == green && red == blue) {
1917: bKGD_colorType = PNG_COLOR_GRAY;
1918: bKGD_gray = red;
1919: } else {
1920: bKGD_colorType = PNG_COLOR_RGB;
1921: bKGD_red = red;
1922: bKGD_green = green;
1923: bKGD_blue = blue;
1924: }
1925: bKGD_present = true;
1926: }
1927: // } else if (childName.equals("ColorSpaceType")) {
1928: // } else if (childName.equals("NumChannels")) {
1929:
1930: child = child.getNextSibling();
1931: }
1932: } else if (name.equals("Compression")) {
1933: Node child = node.getFirstChild();
1934: while (child != null) {
1935: String childName = child.getNodeName();
1936: if (childName.equals("NumProgressiveScans")) {
1937: // Use Adam7 if NumProgressiveScans > 1
1938: int scans = getIntAttribute(child, "value");
1939: IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
1940: // } else if (childName.equals("CompressionTypeName")) {
1941: // } else if (childName.equals("Lossless")) {
1942: // } else if (childName.equals("BitRate")) {
1943: }
1944: child = child.getNextSibling();
1945: }
1946: } else if (name.equals("Data")) {
1947: Node child = node.getFirstChild();
1948: while (child != null) {
1949: String childName = child.getNodeName();
1950: if (childName.equals("BitsPerSample")) {
1951: String s = getAttribute(child, "value");
1952: StringTokenizer t = new StringTokenizer(s);
1953: int maxBits = -1;
1954: while (t.hasMoreTokens()) {
1955: int bits = Integer.parseInt(t.nextToken());
1956: if (bits > maxBits) {
1957: maxBits = bits;
1958: }
1959: }
1960: if (maxBits < 1) {
1961: maxBits = 1;
1962: } else if (maxBits == 3) {
1963: maxBits = 4;
1964: } else if (maxBits > 4 && maxBits < 8) {
1965: maxBits = 8;
1966: } else if (maxBits > 8) {
1967: maxBits = 16;
1968: }
1969: IHDR_bitDepth = maxBits;
1970: } else if (childName
1971: .equals("SignificantBitsPerSample")) {
1972: String s = getAttribute(child, "value");
1973: StringTokenizer t = new StringTokenizer(s);
1974: int numTokens = t.countTokens();
1975: if (numTokens == 1) {
1976: sBIT_colorType = PNG_COLOR_GRAY;
1977: sBIT_grayBits = Integer.parseInt(t
1978: .nextToken());
1979: } else if (numTokens == 2) {
1980: sBIT_colorType = PNG_COLOR_GRAY_ALPHA;
1981: sBIT_grayBits = Integer.parseInt(t
1982: .nextToken());
1983: sBIT_alphaBits = Integer.parseInt(t
1984: .nextToken());
1985: } else if (numTokens == 3) {
1986: sBIT_colorType = PNG_COLOR_RGB;
1987: sBIT_redBits = Integer.parseInt(t
1988: .nextToken());
1989: sBIT_greenBits = Integer.parseInt(t
1990: .nextToken());
1991: sBIT_blueBits = Integer.parseInt(t
1992: .nextToken());
1993: } else if (numTokens == 4) {
1994: sBIT_colorType = PNG_COLOR_RGB_ALPHA;
1995: sBIT_redBits = Integer.parseInt(t
1996: .nextToken());
1997: sBIT_greenBits = Integer.parseInt(t
1998: .nextToken());
1999: sBIT_blueBits = Integer.parseInt(t
2000: .nextToken());
2001: sBIT_alphaBits = Integer.parseInt(t
2002: .nextToken());
2003: }
2004: if (numTokens >= 1 && numTokens <= 4) {
2005: sBIT_present = true;
2006: }
2007: // } else if (childName.equals("PlanarConfiguration")) {
2008: // } else if (childName.equals("SampleFormat")) {
2009: // } else if (childName.equals("SampleMSB")) {
2010: }
2011: child = child.getNextSibling();
2012: }
2013: } else if (name.equals("Dimension")) {
2014: boolean gotWidth = false;
2015: boolean gotHeight = false;
2016: boolean gotAspectRatio = false;
2017:
2018: float width = -1.0F;
2019: float height = -1.0F;
2020: float aspectRatio = -1.0F;
2021:
2022: Node child = node.getFirstChild();
2023: while (child != null) {
2024: String childName = child.getNodeName();
2025: if (childName.equals("PixelAspectRatio")) {
2026: aspectRatio = getFloatAttribute(child, "value");
2027: gotAspectRatio = true;
2028: } else if (childName.equals("HorizontalPixelSize")) {
2029: width = getFloatAttribute(child, "value");
2030: gotWidth = true;
2031: } else if (childName.equals("VerticalPixelSize")) {
2032: height = getFloatAttribute(child, "value");
2033: gotHeight = true;
2034: // } else if (childName.equals("ImageOrientation")) {
2035: // } else if
2036: // (childName.equals("HorizontalPhysicalPixelSpacing")) {
2037: // } else if
2038: // (childName.equals("VerticalPhysicalPixelSpacing")) {
2039: // } else if (childName.equals("HorizontalPosition")) {
2040: // } else if (childName.equals("VerticalPosition")) {
2041: // } else if (childName.equals("HorizontalPixelOffset")) {
2042: // } else if (childName.equals("VerticalPixelOffset")) {
2043: }
2044: child = child.getNextSibling();
2045: }
2046:
2047: if (gotWidth && gotHeight) {
2048: pHYs_present = true;
2049: pHYs_unitSpecifier = 1;
2050: pHYs_pixelsPerUnitXAxis = (int) (1000.0F / width + 0.5F);
2051: pHYs_pixelsPerUnitYAxis = (int) (1000.0F / height + 0.5F);
2052: } else if (gotAspectRatio) {
2053: pHYs_present = true;
2054: pHYs_unitSpecifier = 0;
2055:
2056: // Find a reasonable rational approximation
2057: int denom = 1;
2058: for (; denom < 100; denom++) {
2059: int num = (int) (aspectRatio * denom);
2060: if (Math.abs(num / denom - aspectRatio) < 0.001) {
2061: break;
2062: }
2063: }
2064: pHYs_pixelsPerUnitXAxis = (int) (aspectRatio * denom);
2065: pHYs_pixelsPerUnitYAxis = denom;
2066: }
2067: } else if (name.equals("Document")) {
2068: Node child = node.getFirstChild();
2069: while (child != null) {
2070: String childName = child.getNodeName();
2071: if (childName.equals("ImageModificationTime")) {
2072: tIME_present = true;
2073: tIME_year = getIntAttribute(child, "year");
2074: tIME_month = getIntAttribute(child, "month");
2075: tIME_day = getIntAttribute(child, "day");
2076: tIME_hour = getIntAttribute(child, "hour", 0,
2077: false);
2078: tIME_minute = getIntAttribute(child, "minute",
2079: 0, false);
2080: tIME_second = getIntAttribute(child, "second",
2081: 0, false);
2082: // } else if (childName.equals("SubimageInterpretation")) {
2083: // } else if (childName.equals("ImageCreationTime")) {
2084: }
2085: child = child.getNextSibling();
2086: }
2087: } else if (name.equals("Text")) {
2088: Node child = node.getFirstChild();
2089: while (child != null) {
2090: String childName = child.getNodeName();
2091: if (childName.equals("TextEntry")) {
2092: String keyword = getAttribute(child, "keyword",
2093: "text", false);
2094: String value = getAttribute(child, "value");
2095: String encoding = getAttribute(child,
2096: "encoding", "unknown", false);
2097: String language = getAttribute(child,
2098: "language", "unknown", false);
2099: String compression = getAttribute(child,
2100: "compression", "other", false);
2101:
2102: if (isISOLatin(value)) {
2103: if (compression.equals("zip")) {
2104: // Use a zTXt node
2105: zTXt_keyword
2106: .add(toPrintableLatin1(keyword));
2107: zTXt_text.add(value);
2108: zTXt_compressionMethod.add(new Integer(
2109: 0));
2110: } else {
2111: // Use a tEXt node
2112: tEXt_keyword
2113: .add(toPrintableLatin1(keyword));
2114: tEXt_text.add(value);
2115: }
2116: } else {
2117: int flag = compression.equals("zip") ? 1
2118: : 0;
2119:
2120: // Use an iTXt node
2121: iTXt_keyword
2122: .add(toPrintableLatin1(keyword));
2123: iTXt_compressionFlag.add(new Integer(flag));
2124: iTXt_compressionMethod.add(new Integer(0));
2125: iTXt_languageTag.add(language);
2126: iTXt_translatedKeyword.add(keyword); // fake it
2127: iTXt_text.add(value);
2128: }
2129: }
2130: child = child.getNextSibling();
2131: }
2132: // } else if (name.equals("Transparency")) {
2133: // Node child = node.getFirstChild();
2134: // while (child != null) {
2135: // String childName = child.getNodeName();
2136: // if (childName.equals("Alpha")) {
2137: // } else if (childName.equals("TransparentIndex")) {
2138: // } else if (childName.equals("TransparentColor")) {
2139: // } else if (childName.equals("TileTransparencies")) {
2140: // } else if (childName.equals("TileOpacities")) {
2141: // }
2142: // child = child.getNextSibling();
2143: // }
2144: // } else {
2145: // // fatal(node, "Unknown child of root node!");
2146: }
2147:
2148: node = node.getNextSibling();
2149: }
2150: }
2151:
2152: // Reset all instance variables to their initial state
2153: public void reset() {
2154: IHDR_present = false;
2155: PLTE_present = false;
2156: bKGD_present = false;
2157: cHRM_present = false;
2158: gAMA_present = false;
2159: hIST_present = false;
2160: iCCP_present = false;
2161: iTXt_keyword = new ArrayList();
2162: iTXt_compressionFlag = new ArrayList();
2163: iTXt_compressionMethod = new ArrayList();
2164: iTXt_languageTag = new ArrayList();
2165: iTXt_translatedKeyword = new ArrayList();
2166: iTXt_text = new ArrayList();
2167: pHYs_present = false;
2168: sBIT_present = false;
2169: sPLT_present = false;
2170: sRGB_present = false;
2171: tEXt_keyword = new ArrayList();
2172: tEXt_text = new ArrayList();
2173: tIME_present = false;
2174: tRNS_present = false;
2175: zTXt_keyword = new ArrayList();
2176: zTXt_compressionMethod = new ArrayList();
2177: zTXt_text = new ArrayList();
2178: unknownChunkType = new ArrayList();
2179: unknownChunkData = new ArrayList();
2180: }
2181:
2182: // BEGIN metadata reading section.
2183:
2184: private boolean gotHeader = false;
2185: private boolean gotMetadata = false;
2186:
2187: private Decoder decoder = null;
2188: private CLibPNGImageReader reader = null;
2189:
2190: private static int chunkType(String typeString) {
2191: char c0 = typeString.charAt(0);
2192: char c1 = typeString.charAt(1);
2193: char c2 = typeString.charAt(2);
2194: char c3 = typeString.charAt(3);
2195:
2196: int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
2197: return type;
2198: }
2199:
2200: private String readNullTerminatedString(ImageInputStream stream)
2201: throws IOException {
2202: StringBuffer b = new StringBuffer();
2203: int c;
2204:
2205: while ((c = stream.read()) != 0) {
2206: b.append((char) c);
2207: }
2208: return b.toString();
2209: }
2210:
2211: private void readHeader() throws IIOException {
2212: if (gotHeader) {
2213: return;
2214: }
2215:
2216: try {
2217: mediaLibImage mlibImage = decoder.getImage();
2218: int width = mlibImage.getWidth();
2219: int height = mlibImage.getHeight();
2220: int bitDepth = decoder.getBitDepth();
2221: int colorType;
2222: switch (mlibImage.getChannels()) {
2223: case 1:
2224: colorType = decoder.getPalette() == null ? PNG_COLOR_GRAY
2225: : PNG_COLOR_PALETTE;
2226: break;
2227: case 2:
2228: colorType = PNG_COLOR_GRAY_ALPHA;
2229: break;
2230: case 3:
2231: colorType = PNG_COLOR_RGB;
2232: break;
2233: case 4:
2234: colorType = PNG_COLOR_RGB_ALPHA;
2235: break;
2236: default:
2237: throw new IIOException("Unsupported image type.");
2238: }
2239:
2240: // Compression method 0 (deflate/inflate) is only supported type.
2241: int compressionMethod = 0;
2242:
2243: // Filter method 0 (adaptive filtering) is only supported type.
2244: int filterMethod = 0;
2245:
2246: int interlaceMethod = decoder.getInterlaceMethod();
2247:
2248: if (width == 0) {
2249: throw new IIOException("Image width == 0!");
2250: }
2251: if (height == 0) {
2252: throw new IIOException("Image height == 0!");
2253: }
2254: if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4
2255: && bitDepth != 8 && bitDepth != 16) {
2256: throw new IIOException(
2257: "Bit depth must be 1, 2, 4, 8, or 16!");
2258: }
2259: if (colorType != 0 && colorType != 2 && colorType != 3
2260: && colorType != 4 && colorType != 6) {
2261: throw new IIOException(
2262: "Color type must be 0, 2, 3, 4, or 6!");
2263: }
2264: if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) {
2265: throw new IIOException(
2266: "Bad color type/bit depth combination!");
2267: }
2268: if ((colorType == PNG_COLOR_RGB
2269: || colorType == PNG_COLOR_RGB_ALPHA || colorType == PNG_COLOR_GRAY_ALPHA)
2270: && (bitDepth != 8 && bitDepth != 16)) {
2271: throw new IIOException(
2272: "Bad color type/bit depth combination!");
2273: }
2274: if (compressionMethod != 0) {
2275: throw new IIOException(
2276: "Unknown compression method (not 0)!");
2277: }
2278: if (filterMethod != 0) {
2279: throw new IIOException("Unknown filter method (not 0)!");
2280: }
2281: if (interlaceMethod != 0 && interlaceMethod != 1) {
2282: throw new IIOException(
2283: "Unknown interlace method (not 0 or 1)!");
2284: }
2285:
2286: IHDR_present = true;
2287: IHDR_width = width;
2288: IHDR_height = height;
2289: IHDR_bitDepth = bitDepth;
2290: IHDR_colorType = colorType;
2291: IHDR_compressionMethod = compressionMethod;
2292: IHDR_filterMethod = filterMethod;
2293: IHDR_interlaceMethod = interlaceMethod;
2294: gotHeader = true;
2295: } catch (IOException e) {
2296: throw new IIOException("I/O error reading PNG header!", e);
2297: }
2298: }
2299:
2300: private void parse_PLTE_chunk() throws IOException {
2301: if (PLTE_present) {
2302: processWarningOccurred("A PNG image may not contain more than one PLTE chunk.\n"
2303: + "The chunk will be ignored.");
2304: return;
2305: } else if (IHDR_colorType == PNG_COLOR_GRAY
2306: || IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
2307: processWarningOccurred("A PNG gray or gray alpha image cannot have a PLTE chunk.\n"
2308: + "The chunk will be ignored.");
2309: return;
2310: }
2311:
2312: byte[] palette = decoder.getPalette();
2313:
2314: if (palette != null) {
2315: int numEntries = palette.length / 3;
2316: if (IHDR_colorType == PNG_COLOR_PALETTE) {
2317: int maxEntries = 1 << IHDR_bitDepth;
2318: if (numEntries > maxEntries) {
2319: processWarningOccurred("PLTE chunk contains too many entries for bit depth, ignoring extras.");
2320: numEntries = maxEntries;
2321: }
2322: }
2323:
2324: // Round array sizes up to 2^2^n
2325: int paletteEntries;
2326: if (numEntries > 16) {
2327: paletteEntries = 256;
2328: } else if (numEntries > 4) {
2329: paletteEntries = 16;
2330: } else if (numEntries > 2) {
2331: paletteEntries = 4;
2332: } else {
2333: paletteEntries = 2;
2334: }
2335:
2336: PLTE_present = true;
2337: PLTE_red = new byte[paletteEntries];
2338: PLTE_green = new byte[paletteEntries];
2339: PLTE_blue = new byte[paletteEntries];
2340:
2341: int index = 0;
2342: for (int i = 0; i < numEntries; i++) {
2343: PLTE_red[i] = palette[index++];
2344: PLTE_green[i] = palette[index++];
2345: PLTE_blue[i] = palette[index++];
2346: }
2347: }
2348: }
2349:
2350: private void parse_bKGD_chunk() throws IOException {
2351: int[] background = decoder.getBackground();
2352: if (background != null) {
2353: if (IHDR_colorType == PNG_COLOR_PALETTE) {
2354: bKGD_colorType = PNG_COLOR_PALETTE;
2355: bKGD_index = background[0];
2356: } else if (IHDR_colorType == PNG_COLOR_GRAY
2357: || IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
2358: bKGD_colorType = PNG_COLOR_GRAY;
2359: bKGD_gray = background[0];
2360: } else { // RGB or RGB_ALPHA
2361: bKGD_colorType = PNG_COLOR_RGB;
2362: bKGD_red = background[0];
2363: bKGD_green = background[1];
2364: bKGD_blue = background[2];
2365: }
2366:
2367: bKGD_present = true;
2368: }
2369: }
2370:
2371: private void parse_cHRM_chunk() throws IOException {
2372: int[] chrm = decoder.getAllPrimaryChromaticities();
2373: if (chrm != null) {
2374: int i = 0;
2375: cHRM_whitePointX = chrm[i++];
2376: cHRM_whitePointY = chrm[i++];
2377: cHRM_redX = chrm[i++];
2378: cHRM_redY = chrm[i++];
2379: cHRM_greenX = chrm[i++];
2380: cHRM_greenY = chrm[i++];
2381: cHRM_blueX = chrm[i++];
2382: cHRM_blueY = chrm[i++];
2383:
2384: cHRM_present = true;
2385: }
2386: }
2387:
2388: private void parse_gAMA_chunk() throws IOException {
2389: int gamma = decoder.getImageGamma();
2390: if (gamma != decoder.PNG_gAMA_DEFAULT) {
2391: gAMA_gamma = gamma;
2392:
2393: gAMA_present = true;
2394: }
2395: }
2396:
2397: private void parse_hIST_chunk() throws IOException, IIOException {
2398: short[] histogram = decoder.getHistogram();
2399: if (histogram != null) {
2400: if (!PLTE_present) {
2401: throw new IIOException(
2402: "hIST chunk without prior PLTE chunk!");
2403: }
2404:
2405: int length = Math.min(PLTE_red.length, histogram.length);
2406: hIST_histogram = new char[length];
2407: for (int i = 0; i < length; i++) {
2408: hIST_histogram[i] = (char) histogram[i];
2409: }
2410:
2411: hIST_present = true;
2412: }
2413: }
2414:
2415: private void parse_iCCP_chunk() throws IOException {
2416: String profileName = decoder.getEmbeddedICCProfileName();
2417:
2418: if (profileName != null) {
2419: iCCP_profileName = profileName;
2420:
2421: byte[] uncompressedProfile = decoder
2422: .getEmbeddedICCProfile();
2423:
2424: // Need to compress this profile to match metadata specification.
2425: Deflater compressor = new Deflater(
2426: Deflater.BEST_COMPRESSION);
2427: compressor.setInput(uncompressedProfile);
2428: compressor.finish();
2429:
2430: int off = 0;
2431: int len = uncompressedProfile.length;
2432: byte[] compressedProfile = new byte[uncompressedProfile.length];
2433: do {
2434: int count = compressor.deflate(compressedProfile, off,
2435: len);
2436: off += count;
2437: len -= count;
2438: } while (!compressor.finished());
2439:
2440: int compressedDataLength = off;
2441:
2442: iCCP_compressedProfile = new byte[compressedDataLength];
2443: System.arraycopy(compressedProfile, 0,
2444: iCCP_compressedProfile, 0, compressedDataLength);
2445:
2446: iCCP_present = true;
2447: }
2448: }
2449:
2450: private void parse_pHYs_chunk() throws IOException {
2451: int unitSpecifier = decoder
2452: .getPhysicalPixelDimensions(decoder.PNG_PIXELS_UNIT_SPECIFIER);
2453: if (unitSpecifier != decoder.PNG_pHYs_NOT_DEFINED) {
2454: pHYs_pixelsPerUnitXAxis = decoder
2455: .getPhysicalPixelDimensions(decoder.PNG_PIXELS_UNIT_X);
2456: pHYs_pixelsPerUnitYAxis = decoder
2457: .getPhysicalPixelDimensions(decoder.PNG_PIXELS_UNIT_Y);
2458: pHYs_unitSpecifier = unitSpecifier;
2459:
2460: pHYs_present = true;
2461: }
2462: }
2463:
2464: private void parse_sBIT_chunk() throws IOException {
2465: byte[] sBits = decoder.getSignificantBits();
2466: if (sBits != null) {
2467: int i = 0;
2468: int colorType = IHDR_colorType;
2469: if (colorType == PNG_COLOR_GRAY
2470: || colorType == PNG_COLOR_GRAY_ALPHA) {
2471: sBIT_grayBits = sBits[i++];
2472: } else if (colorType == PNG_COLOR_RGB
2473: || colorType == PNG_COLOR_PALETTE
2474: || colorType == PNG_COLOR_RGB_ALPHA) {
2475: sBIT_redBits = sBits[i++];
2476: sBIT_greenBits = sBits[i++];
2477: sBIT_blueBits = sBits[i++];
2478: }
2479:
2480: if (colorType == PNG_COLOR_GRAY_ALPHA
2481: || colorType == PNG_COLOR_RGB_ALPHA) {
2482: sBIT_alphaBits = sBits[i++];
2483: }
2484:
2485: sBIT_colorType = colorType;
2486: sBIT_present = true;
2487: }
2488: }
2489:
2490: private void parse_sPLT_chunk() throws IOException, IIOException {
2491:
2492: PNGChunk[] sPLTChunks = decoder.getSuggestedPalette();
2493:
2494: if (sPLTChunks != null && sPLTChunks.length > 0
2495: && sPLTChunks[0] != null) {
2496: PNGChunk sPLTChunk = sPLTChunks[0];
2497: byte[] chunkData = sPLTChunk.getData();
2498: int chunkLength = chunkData.length;
2499:
2500: InputStream is = new ByteArrayInputStream(sPLTChunk
2501: .getData());
2502: ImageInputStream stream = new MemoryCacheImageInputStream(
2503: is);
2504:
2505: sPLT_paletteName = readNullTerminatedString(stream);
2506: chunkLength -= sPLT_paletteName.length() + 1;
2507:
2508: int sampleDepth = stream.readUnsignedByte();
2509: sPLT_sampleDepth = sampleDepth;
2510:
2511: int numEntries = chunkLength / (4 * (sampleDepth / 8) + 2);
2512: sPLT_red = new int[numEntries];
2513: sPLT_green = new int[numEntries];
2514: sPLT_blue = new int[numEntries];
2515: sPLT_alpha = new int[numEntries];
2516: sPLT_frequency = new int[numEntries];
2517:
2518: if (sampleDepth == 8) {
2519: for (int i = 0; i < numEntries; i++) {
2520: sPLT_red[i] = stream.readUnsignedByte();
2521: sPLT_green[i] = stream.readUnsignedByte();
2522: sPLT_blue[i] = stream.readUnsignedByte();
2523: sPLT_alpha[i] = stream.readUnsignedByte();
2524: sPLT_frequency[i] = stream.readUnsignedShort();
2525: }
2526: } else if (sampleDepth == 16) {
2527: for (int i = 0; i < numEntries; i++) {
2528: sPLT_red[i] = stream.readUnsignedShort();
2529: sPLT_green[i] = stream.readUnsignedShort();
2530: sPLT_blue[i] = stream.readUnsignedShort();
2531: sPLT_alpha[i] = stream.readUnsignedShort();
2532: sPLT_frequency[i] = stream.readUnsignedShort();
2533: }
2534: } else {
2535: throw new IIOException("sPLT sample depth not 8 or 16!");
2536: }
2537:
2538: sPLT_present = true;
2539: }
2540: }
2541:
2542: private void parse_sRGB_chunk() throws IOException {
2543: int renderingIntent = decoder.getStandardRGB();
2544: if (renderingIntent != decoder.PNG_sRGB_NOT_DEFINED) {
2545: sRGB_renderingIntent = renderingIntent;
2546: sRGB_present = true;
2547: }
2548: }
2549:
2550: private void parse_tIME_chunk() throws IOException {
2551: Calendar cal = decoder.getLastModificationTime();
2552: if (cal != null) {
2553: tIME_year = cal.get(Calendar.YEAR);
2554: tIME_month = cal.get(Calendar.MONTH) + 1;
2555: tIME_day = cal.get(Calendar.DAY_OF_MONTH);
2556: tIME_hour = cal.get(Calendar.HOUR_OF_DAY);
2557: tIME_minute = cal.get(Calendar.MINUTE);
2558: tIME_second = cal.get(Calendar.SECOND);
2559:
2560: tIME_present = true;
2561: }
2562: }
2563:
2564: private void parse_tRNS_chunk() throws IOException {
2565: int[] transparency = decoder.getTransparency();
2566:
2567: if (transparency == null) {
2568: return;
2569: }
2570:
2571: int colorType = IHDR_colorType;
2572: if (colorType == PNG_COLOR_PALETTE) {
2573: if (!PLTE_present) {
2574: processWarningOccurred("tRNS chunk without prior PLTE chunk, ignoring it.");
2575: return;
2576: }
2577:
2578: // Alpha table may have fewer entries than RGB palette
2579: int maxEntries = PLTE_red.length;
2580: int numEntries = transparency.length;
2581: if (numEntries > maxEntries) {
2582: processWarningOccurred("tRNS chunk has more entries than prior PLTE chunk, ignoring extras.");
2583: numEntries = maxEntries;
2584: }
2585: tRNS_alpha = new byte[numEntries];
2586: tRNS_colorType = PNG_COLOR_PALETTE;
2587: for (int i = 0; i < numEntries; i++) {
2588: tRNS_alpha[i] = (byte) transparency[i];
2589: }
2590: } else if (colorType == PNG_COLOR_GRAY) {
2591: if (transparency.length != 1) {
2592: processWarningOccurred("tRNS chunk for gray image must have length 2, ignoring chunk.");
2593: return;
2594: }
2595: tRNS_gray = transparency[0];
2596: tRNS_colorType = PNG_COLOR_GRAY;
2597: } else if (colorType == PNG_COLOR_RGB) {
2598: if (transparency.length != 3) {
2599: processWarningOccurred("tRNS chunk for RGB image must have length 6, ignoring chunk.");
2600: return;
2601: }
2602: tRNS_red = transparency[0];
2603: tRNS_green = transparency[1];
2604: tRNS_blue = transparency[2];
2605: tRNS_colorType = PNG_COLOR_RGB;
2606: } else {
2607: processWarningOccurred("Gray+Alpha and RGBA images may not have a tRNS chunk, ignoring it.");
2608: return;
2609: }
2610:
2611: tRNS_present = true;
2612: }
2613:
2614: // Parse all iTXt, tEXt, and zTXt chunks.
2615: private void parseTextChunk() throws IOException {
2616: PNGTextualData[] textualData = decoder.getTextualData();
2617:
2618: if (textualData != null) {
2619: for (int i = 0; i < textualData.length; i++) {
2620: PNGTextualData textData = textualData[i];
2621: String keyword = textData.getKeyword();
2622: String text = textData.getText();
2623: String translatedKeyword = textData
2624: .getTranslatedKeyword();
2625:
2626: // No way to detect a zTXt chunk to use tEXt for zTXt.
2627: // Also, all text is already decompressed.
2628: if (keyword.equals(translatedKeyword)) { // tEXt and zTXt
2629: tEXt_keyword.add(keyword);
2630: tEXt_text.add(text);
2631: } else { // iTXt
2632: iTXt_keyword.add(keyword);
2633: iTXt_text.add(text);
2634: iTXt_translatedKeyword.add(translatedKeyword);
2635:
2636: // XXX No access to compression flag so set to 'false'
2637: // as text is decompressed by codecLib.
2638: int compressionFlag = 0;
2639: iTXt_compressionFlag.add(new Integer(
2640: compressionFlag));
2641:
2642: // No access to compression method but only specified
2643: // one is '0' (deflate compression with ZLib data stream).
2644: int compressionMethod = 0;
2645: iTXt_compressionMethod.add(new Integer(
2646: compressionMethod));
2647:
2648: String languageTag = textData.getEncoding();
2649: iTXt_languageTag.add(languageTag);
2650: }
2651: }
2652: }
2653: }
2654:
2655: synchronized void readMetadata(CLibPNGImageReader reader,
2656: Decoder decoder) throws IIOException {
2657: if (gotMetadata) {
2658: return;
2659: }
2660:
2661: this .reader = reader;
2662: this .decoder = decoder;
2663:
2664: readHeader();
2665:
2666: try {
2667: parse_PLTE_chunk();
2668: parse_bKGD_chunk();
2669: parse_cHRM_chunk();
2670: parse_gAMA_chunk();
2671: parse_hIST_chunk();
2672: parse_iCCP_chunk();
2673: parse_pHYs_chunk();
2674: parse_sBIT_chunk();
2675: parse_sPLT_chunk();
2676: parse_sRGB_chunk();
2677: parse_tIME_chunk();
2678: parse_tRNS_chunk();
2679:
2680: parseTextChunk();
2681:
2682: PNGChunk[] userChunks = decoder.getUserData();
2683: if (userChunks != null) {
2684: for (int i = 0; i < userChunks.length; i++) {
2685: // Read an unknown chunk
2686: PNGChunk userChunk = userChunks[i];
2687:
2688: int chunkType = userChunk.getID();
2689: byte[] b = userChunk.getData();
2690:
2691: StringBuffer chunkName = new StringBuffer(4);
2692: chunkName.append((char) (chunkType >>> 24));
2693: chunkName.append((char) ((chunkType >> 16) & 0xff));
2694: chunkName.append((char) ((chunkType >> 8) & 0xff));
2695: chunkName.append((char) (chunkType & 0xff));
2696:
2697: int ancillaryBit = chunkType >>> 28;
2698: if (ancillaryBit == 0) {
2699: processWarningOccurred("Encountered unknown chunk with critical bit set!");
2700: }
2701:
2702: unknownChunkType.add(chunkName.toString());
2703: unknownChunkData.add(b);
2704: }
2705: }
2706: } catch (IOException e) {
2707: throw new IIOException("Error reading PNG metadata", e);
2708: } finally {
2709: this .reader = null;
2710: this .decoder = null;
2711: }
2712:
2713: gotMetadata = true;
2714: }
2715:
2716: void processWarningOccurred(String warning) {
2717: if (reader != null) {
2718: reader.forwardWarningMessage(warning);
2719: }
2720: }
2721:
2722: // END metadata reading methods.
2723:
2724: // BEGIN metadata writing methods.
2725:
2726: synchronized void writeMetadata(Encoder encoder)
2727: throws IIOException {
2728: if (IHDR_present) {
2729: encoder.setBitDepth(IHDR_bitDepth);
2730: encoder
2731: .setInterlaceMethod(IHDR_interlaceMethod == 0 ? Encoder.PNG_INTERLACE_METHOD_DEFAULT
2732: : Encoder.PNG_INTERLACE_METHOD_ADAM7);
2733: }
2734:
2735: if (PLTE_present) {
2736: int paletteLength = PLTE_red.length;
2737: byte[] palette = new byte[3 * paletteLength];
2738: for (int i = 0, j = 0; i < paletteLength; i++) {
2739: palette[j++] = PLTE_red[i];
2740: palette[j++] = PLTE_green[i];
2741: palette[j++] = PLTE_blue[i];
2742: }
2743: encoder.setPalette(palette);
2744: }
2745:
2746: if (bKGD_present) {
2747: int[] color;
2748: switch (bKGD_colorType) {
2749: case PNG_COLOR_GRAY:
2750: color = new int[] { bKGD_gray };
2751: break;
2752: case PNG_COLOR_PALETTE:
2753: color = new int[] { bKGD_index };
2754: break;
2755: default:
2756: color = new int[] { bKGD_red, bKGD_green, bKGD_blue };
2757: }
2758: encoder.setBackground(color);
2759: }
2760:
2761: if (cHRM_present) {
2762: encoder.setPrimaryChromaticities(cHRM_whitePointX,
2763: cHRM_whitePointY, cHRM_redX, cHRM_redY,
2764: cHRM_greenX, cHRM_greenY, cHRM_blueX, cHRM_blueY);
2765: }
2766:
2767: if (gAMA_present) {
2768: encoder.setImageGamma(gAMA_gamma);
2769: }
2770:
2771: if (hIST_present) {
2772: int histogramLength = hIST_histogram.length;
2773: short[] histogram = new short[histogramLength];
2774: for (int i = 0; i < histogramLength; i++) {
2775: histogram[i] = (short) hIST_histogram[i];
2776: }
2777: encoder.setHistogram(histogram);
2778: }
2779:
2780: if (iCCP_present) {
2781: // Encoder expects an uncompressed profile so decompress.
2782: Inflater decompresser = new Inflater();
2783: decompresser.setInput(iCCP_compressedProfile);
2784: byte[] result = new byte[2 * decompresser.getRemaining()];
2785:
2786: int off = 0;
2787: try {
2788: do {
2789: off += decompresser.inflate(result, off,
2790: result.length - off);
2791: if (off == result.length
2792: && !decompresser.finished()) {
2793: byte[] tmpbuf = new byte[2 * result.length];
2794: System.arraycopy(result, 0, tmpbuf, 0,
2795: result.length);
2796: result = tmpbuf;
2797: }
2798: } while (!decompresser.finished());
2799: decompresser.end();
2800:
2801: byte[] uncompressedProfile;
2802: if (off == result.length) {
2803: uncompressedProfile = result;
2804: } else {
2805: uncompressedProfile = new byte[off];
2806: System.arraycopy(result, 0, uncompressedProfile, 0,
2807: off);
2808: }
2809:
2810: String iCCPName = toPrintableLatin1(iCCP_profileName);
2811: encoder.setEmbeddedICCProfile(iCCPName,
2812: uncompressedProfile);
2813: } catch (DataFormatException e) {
2814: // XXX warning message?
2815: }
2816: }
2817:
2818: if (iTXt_keyword.size() > 0) {
2819: int numChunks = iTXt_keyword.size();
2820: for (int i = 0; i < numChunks; i++) {
2821: Integer compressionFlag = Integer
2822: .valueOf((String) iTXt_compressionFlag.get(i));
2823: encoder.setUnicodeTextualData((String) iTXt_keyword
2824: .get(i),
2825: (String) iTXt_translatedKeyword.get(i),
2826: (String) iTXt_languageTag.get(i),
2827: (String) iTXt_text.get(i), compressionFlag
2828: .intValue() == 1);
2829: }
2830: }
2831:
2832: if (pHYs_present) {
2833: encoder.setPhysicalPixelDimensions(pHYs_pixelsPerUnitXAxis,
2834: pHYs_pixelsPerUnitYAxis, pHYs_unitSpecifier);
2835: }
2836:
2837: if (sBIT_present) {
2838: byte[] bits;
2839: switch (sBIT_colorType) {
2840: case PNG_COLOR_GRAY:
2841: bits = new byte[] { (byte) (sBIT_grayBits & 0xff) };
2842: break;
2843: case PNG_COLOR_GRAY_ALPHA:
2844: bits = new byte[] { (byte) (sBIT_grayBits & 0xff),
2845: (byte) (sBIT_alphaBits & 0xff) };
2846: break;
2847: case PNG_COLOR_RGB_ALPHA:
2848: bits = new byte[] { (byte) (sBIT_redBits & 0xff),
2849: (byte) (sBIT_greenBits & 0xff),
2850: (byte) (sBIT_blueBits & 0xff),
2851: (byte) (sBIT_alphaBits & 0xff) };
2852: break;
2853: default: // RGB and PALETTE
2854: bits = new byte[] { (byte) (sBIT_redBits & 0xff),
2855: (byte) (sBIT_greenBits & 0xff),
2856: (byte) (sBIT_blueBits & 0xff) };
2857: break;
2858: }
2859: encoder.setSignificantBits(bits);
2860: }
2861:
2862: if (sPLT_present) {
2863: if (sPLT_sampleDepth == 8) {
2864: byte[] red = new byte[sPLT_red.length];
2865: byte[] green = new byte[sPLT_green.length];
2866: byte[] blue = new byte[sPLT_blue.length];
2867: byte[] alpha = new byte[sPLT_alpha.length];
2868: short[] frequency = new short[sPLT_frequency.length];
2869:
2870: int length = red.length;
2871: for (int i = 0; i < length; i++) {
2872: red[i] = (byte) (sPLT_red[i] & 0xff);
2873: green[i] = (byte) (sPLT_green[i] & 0xff);
2874: blue[i] = (byte) (sPLT_blue[i] & 0xff);
2875: alpha[i] = (byte) (sPLT_alpha[i] & 0xff);
2876: frequency[i] = (short) (sPLT_frequency[i] & 0xffff);
2877: }
2878:
2879: String sPLTName = toPrintableLatin1(sPLT_paletteName);
2880: encoder.setSuggestedPalette(sPLTName, red, green, blue,
2881: alpha, frequency);
2882: } else {
2883: short[] red = new short[sPLT_red.length];
2884: short[] green = new short[sPLT_green.length];
2885: short[] blue = new short[sPLT_blue.length];
2886: short[] alpha = new short[sPLT_alpha.length];
2887: short[] frequency = new short[sPLT_frequency.length];
2888:
2889: int length = red.length;
2890: for (int i = 0; i < length; i++) {
2891: red[i] = (short) (sPLT_red[i] & 0xffff);
2892: green[i] = (short) (sPLT_green[i] & 0xffff);
2893: blue[i] = (short) (sPLT_blue[i] & 0xffff);
2894: alpha[i] = (short) (sPLT_alpha[i] & 0xffff);
2895: frequency[i] = (short) (sPLT_frequency[i] & 0xffff);
2896: }
2897:
2898: String sPLTName = toPrintableLatin1(sPLT_paletteName);
2899: encoder.setSuggestedPalette(sPLTName, red, green, blue,
2900: alpha, frequency);
2901: }
2902: }
2903:
2904: if (sRGB_present) {
2905: encoder.setStandardRGB(sRGB_renderingIntent);
2906: }
2907:
2908: if (tEXt_keyword.size() > 0) {
2909: int numChunks = tEXt_keyword.size();
2910: for (int i = 0; i < numChunks; i++) {
2911: encoder.setTextualData((String) tEXt_keyword.get(i),
2912: (String) tEXt_text.get(i), false);
2913: }
2914: }
2915:
2916: if (tIME_present) {
2917: encoder.setLastModificationTime(new GregorianCalendar(
2918: tIME_year, tIME_month - 1, tIME_day, tIME_hour,
2919: tIME_minute, tIME_second));
2920: }
2921:
2922: if (tRNS_present) {
2923: if (tRNS_colorType == PNG_COLOR_GRAY) {
2924: encoder
2925: .setTransparency(tRNS_gray, tRNS_gray,
2926: tRNS_gray);
2927: } else if (tRNS_colorType == PNG_COLOR_PALETTE) {
2928: int length = tRNS_alpha.length;
2929: int[] color = new int[length];
2930: for (int i = 0; i < length; i++) {
2931: color[i] = tRNS_alpha[i] & 0xff;
2932: }
2933: encoder.setTransparency(color);
2934: } else {
2935: encoder
2936: .setTransparency(tRNS_red, tRNS_green,
2937: tRNS_blue);
2938: }
2939: }
2940:
2941: if (zTXt_keyword.size() > 0) {
2942: int numChunks = zTXt_keyword.size();
2943: for (int i = 0; i < numChunks; i++) {
2944: encoder.setTextualData((String) zTXt_keyword.get(i),
2945: (String) zTXt_text.get(i), true);
2946: }
2947: }
2948:
2949: if (unknownChunkType.size() > 0) {
2950: int numChunks = unknownChunkType.size();
2951: for (int i = 0; i < numChunks; i++) {
2952: encoder.setUserData((String) unknownChunkType.get(i),
2953: (byte[]) unknownChunkData.get(i),
2954: Encoder.PNG_SAVE_BEFORE_IMAGE_DATA);
2955: }
2956: }
2957: }
2958:
2959: // END metadata writing methods.
2960: }
|