1/*
2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.imageio.plugins.png;
27
28import java.awt.image.ColorModel;
29import java.awt.image.IndexColorModel;
30import java.awt.image.SampleModel;
31import java.util.ArrayList;
32import java.util.StringTokenizer;
33import javax.imageio.ImageTypeSpecifier;
34import javax.imageio.metadata.IIOInvalidTreeException;
35import javax.imageio.metadata.IIOMetadata;
36import javax.imageio.metadata.IIOMetadataFormatImpl;
37import javax.imageio.metadata.IIOMetadataNode;
38import org.w3c.dom.Node;
39
40public class PNGMetadata extends IIOMetadata implements Cloneable {
41
42    // package scope
43    public static final String
44        nativeMetadataFormatName = "javax_imageio_png_1.0";
45
46    protected static final String nativeMetadataFormatClassName
47        = "com.sun.imageio.plugins.png.PNGMetadataFormat";
48
49    // Color types for IHDR chunk
50    static final String[] IHDR_colorTypeNames = {
51        "Grayscale", null, "RGB", "Palette",
52        "GrayAlpha", null, "RGBAlpha"
53    };
54
55    static final int[] IHDR_numChannels = {
56        1, 0, 3, 3, 2, 0, 4
57    };
58
59    // Bit depths for IHDR chunk
60    static final String[] IHDR_bitDepths = {
61        "1", "2", "4", "8", "16"
62    };
63
64    // Compression methods for IHDR chunk
65    static final String[] IHDR_compressionMethodNames = {
66        "deflate"
67    };
68
69    // Filter methods for IHDR chunk
70    static final String[] IHDR_filterMethodNames = {
71        "adaptive"
72    };
73
74    // Interlace methods for IHDR chunk
75    static final String[] IHDR_interlaceMethodNames = {
76        "none", "adam7"
77    };
78
79    // Compression methods for iCCP chunk
80    static final String[] iCCP_compressionMethodNames = {
81        "deflate"
82    };
83
84    // Compression methods for zTXt chunk
85    static final String[] zTXt_compressionMethodNames = {
86        "deflate"
87    };
88
89    // "Unknown" unit for pHYs chunk
90    public static final int PHYS_UNIT_UNKNOWN = 0;
91
92    // "Meter" unit for pHYs chunk
93    public static final int PHYS_UNIT_METER = 1;
94
95    // Unit specifiers for pHYs chunk
96    static final String[] unitSpecifierNames = {
97        "unknown", "meter"
98    };
99
100    // Rendering intents for sRGB chunk
101    static final String[] renderingIntentNames = {
102        "Perceptual", // 0
103        "Relative colorimetric", // 1
104        "Saturation", // 2
105        "Absolute colorimetric" // 3
106
107    };
108
109    // Color space types for Chroma->ColorSpaceType node
110    static final String[] colorSpaceTypeNames = {
111        "GRAY", null, "RGB", "RGB",
112        "GRAY", null, "RGB"
113    };
114
115    // IHDR chunk
116    public boolean IHDR_present;
117    public int IHDR_width;
118    public int IHDR_height;
119    public int IHDR_bitDepth;
120    public int IHDR_colorType;
121    public int IHDR_compressionMethod;
122    public int IHDR_filterMethod;
123    public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
124
125    // PLTE chunk
126    public boolean PLTE_present;
127    public byte[] PLTE_red;
128    public byte[] PLTE_green;
129    public byte[] PLTE_blue;
130
131    // If non-null, used to reorder palette entries during encoding in
132    // order to minimize the size of the tRNS chunk.  Thus an index of
133    // 'i' in the source should be encoded as index 'PLTE_order[i]'.
134    // PLTE_order will be null unless 'initialize' is called with an
135    // IndexColorModel image type.
136    public int[] PLTE_order = null;
137
138    // bKGD chunk
139    // If external (non-PNG sourced) data has red = green = blue,
140    // always store it as gray and promote when writing
141    public boolean bKGD_present;
142    public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
143    public int bKGD_index;
144    public int bKGD_gray;
145    public int bKGD_red;
146    public int bKGD_green;
147    public int bKGD_blue;
148
149    // cHRM chunk
150    public boolean cHRM_present;
151    public int cHRM_whitePointX;
152    public int cHRM_whitePointY;
153    public int cHRM_redX;
154    public int cHRM_redY;
155    public int cHRM_greenX;
156    public int cHRM_greenY;
157    public int cHRM_blueX;
158    public int cHRM_blueY;
159
160    // gAMA chunk
161    public boolean gAMA_present;
162    public int gAMA_gamma;
163
164    // hIST chunk
165    public boolean hIST_present;
166    public char[] hIST_histogram;
167
168    // iCCP chunk
169    public boolean iCCP_present;
170    public String iCCP_profileName;
171    public int iCCP_compressionMethod;
172    public byte[] iCCP_compressedProfile;
173
174    // iTXt chunk
175    public ArrayList<String> iTXt_keyword = new ArrayList<String>();
176    public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>();
177    public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>();
178    public ArrayList<String> iTXt_languageTag = new ArrayList<String>();
179    public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>();
180    public ArrayList<String> iTXt_text = new ArrayList<String>();
181
182    // pHYs chunk
183    public boolean pHYs_present;
184    public int pHYs_pixelsPerUnitXAxis;
185    public int pHYs_pixelsPerUnitYAxis;
186    public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
187
188    // sBIT chunk
189    public boolean sBIT_present;
190    public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
191    public int sBIT_grayBits;
192    public int sBIT_redBits;
193    public int sBIT_greenBits;
194    public int sBIT_blueBits;
195    public int sBIT_alphaBits;
196
197    // sPLT chunk
198    public boolean sPLT_present;
199    public String sPLT_paletteName; // 1-79 characters
200    public int sPLT_sampleDepth; // 8 or 16
201    public int[] sPLT_red;
202    public int[] sPLT_green;
203    public int[] sPLT_blue;
204    public int[] sPLT_alpha;
205    public int[] sPLT_frequency;
206
207    // sRGB chunk
208    public boolean sRGB_present;
209    public int sRGB_renderingIntent;
210
211    // tEXt chunk
212    public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters
213    public ArrayList<String> tEXt_text = new ArrayList<String>();
214
215    // tIME chunk
216    public boolean tIME_present;
217    public int tIME_year;
218    public int tIME_month;
219    public int tIME_day;
220    public int tIME_hour;
221    public int tIME_minute;
222    public int tIME_second;
223
224    // tRNS chunk
225    // If external (non-PNG sourced) data has red = green = blue,
226    // always store it as gray and promote when writing
227    public boolean tRNS_present;
228    public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
229    public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
230    public int tRNS_gray;
231    public int tRNS_red;
232    public int tRNS_green;
233    public int tRNS_blue;
234
235    // zTXt chunk
236    public ArrayList<String> zTXt_keyword = new ArrayList<String>();
237    public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>();
238    public ArrayList<String> zTXt_text = new ArrayList<String>();
239
240    // Unknown chunks
241    public ArrayList<String> unknownChunkType = new ArrayList<String>();
242    public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>();
243
244    public PNGMetadata() {
245        super(true,
246              nativeMetadataFormatName,
247              nativeMetadataFormatClassName,
248              null, null);
249    }
250
251    public PNGMetadata(IIOMetadata metadata) {
252        // TODO -- implement
253    }
254
255    /**
256     * Sets the IHDR_bitDepth and IHDR_colorType variables.
257     * The {@code numBands} parameter is necessary since
258     * we may only be writing a subset of the image bands.
259     */
260    public void initialize(ImageTypeSpecifier imageType, int numBands) {
261        ColorModel colorModel = imageType.getColorModel();
262        SampleModel sampleModel = imageType.getSampleModel();
263
264        // Initialize IHDR_bitDepth
265        int[] sampleSize = sampleModel.getSampleSize();
266        int bitDepth = sampleSize[0];
267        // Choose max bit depth over all channels
268        // Fixes bug 4413109
269        for (int i = 1; i < sampleSize.length; i++) {
270            if (sampleSize[i] > bitDepth) {
271                bitDepth = sampleSize[i];
272            }
273        }
274        // Multi-channel images must have a bit depth of 8 or 16
275        if (sampleSize.length > 1 && bitDepth < 8) {
276            bitDepth = 8;
277        }
278
279        // Round bit depth up to a power of 2
280        if (bitDepth > 2 && bitDepth < 4) {
281            bitDepth = 4;
282        } else if (bitDepth > 4 && bitDepth < 8) {
283            bitDepth = 8;
284        } else if (bitDepth > 8 && bitDepth < 16) {
285            bitDepth = 16;
286        } else if (bitDepth > 16) {
287            throw new RuntimeException("bitDepth > 16!");
288        }
289        IHDR_bitDepth = bitDepth;
290
291        // Initialize IHDR_colorType
292        if (colorModel instanceof IndexColorModel) {
293            IndexColorModel icm = (IndexColorModel)colorModel;
294            int size = icm.getMapSize();
295
296            byte[] reds = new byte[size];
297            icm.getReds(reds);
298            byte[] greens = new byte[size];
299            icm.getGreens(greens);
300            byte[] blues = new byte[size];
301            icm.getBlues(blues);
302
303            // Determine whether the color tables are actually a gray ramp
304            // if the color type has not been set previously
305            boolean isGray = false;
306            if (!IHDR_present ||
307                (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) {
308                isGray = true;
309                int scale = 255/((1 << IHDR_bitDepth) - 1);
310                for (int i = 0; i < size; i++) {
311                    byte red = reds[i];
312                    if ((red != (byte)(i*scale)) ||
313                        (red != greens[i]) ||
314                        (red != blues[i])) {
315                        isGray = false;
316                        break;
317                    }
318                }
319            }
320
321            // Determine whether transparency exists
322            boolean hasAlpha = colorModel.hasAlpha();
323
324            byte[] alpha = null;
325            if (hasAlpha) {
326                alpha = new byte[size];
327                icm.getAlphas(alpha);
328            }
329
330            /*
331             * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images
332             * contained more than 1024 pixels (or even than 768 pixels in case of
333             * single transparent pixel in palette).
334             * For such images alpha samples in raster will occupy more space than
335             * it is required to store palette so it could be reasonable to
336             * use PNG_COLOR_PALETTE color type for large images.
337             */
338
339            if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) {
340                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
341            } else if (isGray && !hasAlpha) {
342                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
343            } else {
344                IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE;
345                PLTE_present = true;
346                PLTE_order = null;
347                PLTE_red = reds.clone();
348                PLTE_green = greens.clone();
349                PLTE_blue = blues.clone();
350
351                if (hasAlpha) {
352                    tRNS_present = true;
353                    tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
354
355                    PLTE_order = new int[alpha.length];
356
357                    // Reorder the palette so that non-opaque entries
358                    // come first.  Since the tRNS chunk does not have
359                    // to store trailing 255's, this can save a
360                    // considerable amount of space when encoding
361                    // images with only one transparent pixel value,
362                    // e.g., images from GIF sources.
363
364                    byte[] newAlpha = new byte[alpha.length];
365
366                    // Scan for non-opaque entries and assign them
367                    // positions starting at 0.
368                    int newIndex = 0;
369                    for (int i = 0; i < alpha.length; i++) {
370                        if (alpha[i] != (byte)255) {
371                            PLTE_order[i] = newIndex;
372                            newAlpha[newIndex] = alpha[i];
373                            ++newIndex;
374                        }
375                    }
376                    int numTransparent = newIndex;
377
378                    // Scan for opaque entries and assign them
379                    // positions following the non-opaque entries.
380                    for (int i = 0; i < alpha.length; i++) {
381                        if (alpha[i] == (byte)255) {
382                            PLTE_order[i] = newIndex++;
383                        }
384                    }
385
386                    // Reorder the palettes
387                    byte[] oldRed = PLTE_red;
388                    byte[] oldGreen = PLTE_green;
389                    byte[] oldBlue = PLTE_blue;
390                    int len = oldRed.length; // All have the same length
391                    PLTE_red = new byte[len];
392                    PLTE_green = new byte[len];
393                    PLTE_blue = new byte[len];
394                    for (int i = 0; i < len; i++) {
395                        PLTE_red[PLTE_order[i]] = oldRed[i];
396                        PLTE_green[PLTE_order[i]] = oldGreen[i];
397                        PLTE_blue[PLTE_order[i]] = oldBlue[i];
398                    }
399
400                    // Copy only the transparent entries into tRNS_alpha
401                    tRNS_alpha = new byte[numTransparent];
402                    System.arraycopy(newAlpha, 0,
403                                     tRNS_alpha, 0, numTransparent);
404                }
405            }
406        } else {
407            if (numBands == 1) {
408                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
409            } else if (numBands == 2) {
410                IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
411            } else if (numBands == 3) {
412                IHDR_colorType = PNGImageReader.PNG_COLOR_RGB;
413            } else if (numBands == 4) {
414                IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
415            } else {
416                throw new RuntimeException("Number of bands not 1-4!");
417            }
418        }
419
420        IHDR_present = true;
421    }
422
423    public boolean isReadOnly() {
424        return false;
425    }
426
427    private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) {
428        if (in == null) {
429            return null;
430        } else {
431            ArrayList<byte[]> list = new ArrayList<byte[]>(in.size());
432            for (byte[] b: in) {
433                list.add((b == null) ? null : b.clone());
434            }
435            return list;
436        }
437    }
438
439    // Deep clone
440    public Object clone() {
441        PNGMetadata metadata;
442        try {
443            metadata = (PNGMetadata)super.clone();
444        } catch (CloneNotSupportedException e) {
445            return null;
446        }
447
448        // unknownChunkData needs deep clone
449        metadata.unknownChunkData =
450            cloneBytesArrayList(this.unknownChunkData);
451
452        return metadata;
453    }
454
455    public Node getAsTree(String formatName) {
456        if (formatName.equals(nativeMetadataFormatName)) {
457            return getNativeTree();
458        } else if (formatName.equals
459                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
460            return getStandardTree();
461        } else {
462            throw new IllegalArgumentException("Not a recognized format!");
463        }
464    }
465
466    private Node getNativeTree() {
467        IIOMetadataNode node = null; // scratch node
468        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
469
470        // IHDR
471        if (IHDR_present) {
472            IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
473            IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
474            IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
475            IHDR_node.setAttribute("bitDepth",
476                                   Integer.toString(IHDR_bitDepth));
477            IHDR_node.setAttribute("colorType",
478                                   IHDR_colorTypeNames[IHDR_colorType]);
479            // IHDR_compressionMethod must be 0 in PNG 1.1
480            IHDR_node.setAttribute("compressionMethod",
481                          IHDR_compressionMethodNames[IHDR_compressionMethod]);
482            // IHDR_filterMethod must be 0 in PNG 1.1
483            IHDR_node.setAttribute("filterMethod",
484                                    IHDR_filterMethodNames[IHDR_filterMethod]);
485            IHDR_node.setAttribute("interlaceMethod",
486                              IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
487            root.appendChild(IHDR_node);
488        }
489
490        // PLTE
491        if (PLTE_present) {
492            IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
493            int numEntries = PLTE_red.length;
494            for (int i = 0; i < numEntries; i++) {
495                IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
496                entry.setAttribute("index", Integer.toString(i));
497                entry.setAttribute("red",
498                                   Integer.toString(PLTE_red[i] & 0xff));
499                entry.setAttribute("green",
500                                   Integer.toString(PLTE_green[i] & 0xff));
501                entry.setAttribute("blue",
502                                   Integer.toString(PLTE_blue[i] & 0xff));
503                PLTE_node.appendChild(entry);
504            }
505
506            root.appendChild(PLTE_node);
507        }
508
509        // bKGD
510        if (bKGD_present) {
511            IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
512
513            if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
514                node = new IIOMetadataNode("bKGD_Palette");
515                node.setAttribute("index", Integer.toString(bKGD_index));
516            } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
517                node = new IIOMetadataNode("bKGD_Grayscale");
518                node.setAttribute("gray", Integer.toString(bKGD_gray));
519            } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) {
520                node = new IIOMetadataNode("bKGD_RGB");
521                node.setAttribute("red", Integer.toString(bKGD_red));
522                node.setAttribute("green", Integer.toString(bKGD_green));
523                node.setAttribute("blue", Integer.toString(bKGD_blue));
524            }
525            bKGD_node.appendChild(node);
526
527            root.appendChild(bKGD_node);
528        }
529
530        // cHRM
531        if (cHRM_present) {
532            IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
533            cHRM_node.setAttribute("whitePointX",
534                              Integer.toString(cHRM_whitePointX));
535            cHRM_node.setAttribute("whitePointY",
536                              Integer.toString(cHRM_whitePointY));
537            cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
538            cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
539            cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
540            cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
541            cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
542            cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
543
544            root.appendChild(cHRM_node);
545        }
546
547        // gAMA
548        if (gAMA_present) {
549            IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
550            gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
551
552            root.appendChild(gAMA_node);
553        }
554
555        // hIST
556        if (hIST_present) {
557            IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
558
559            for (int i = 0; i < hIST_histogram.length; i++) {
560                IIOMetadataNode hist =
561                    new IIOMetadataNode("hISTEntry");
562                hist.setAttribute("index", Integer.toString(i));
563                hist.setAttribute("value",
564                                  Integer.toString(hIST_histogram[i]));
565                hIST_node.appendChild(hist);
566            }
567
568            root.appendChild(hIST_node);
569        }
570
571        // iCCP
572        if (iCCP_present) {
573            IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
574            iCCP_node.setAttribute("profileName", iCCP_profileName);
575            iCCP_node.setAttribute("compressionMethod",
576                          iCCP_compressionMethodNames[iCCP_compressionMethod]);
577
578            Object profile = iCCP_compressedProfile;
579            if (profile != null) {
580                profile = ((byte[])profile).clone();
581            }
582            iCCP_node.setUserObject(profile);
583
584            root.appendChild(iCCP_node);
585        }
586
587        // iTXt
588        if (iTXt_keyword.size() > 0) {
589            IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
590            for (int i = 0; i < iTXt_keyword.size(); i++) {
591                IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
592                iTXt_node.setAttribute("keyword", iTXt_keyword.get(i));
593                iTXt_node.setAttribute("compressionFlag",
594                        iTXt_compressionFlag.get(i) ? "TRUE" : "FALSE");
595                iTXt_node.setAttribute("compressionMethod",
596                        iTXt_compressionMethod.get(i).toString());
597                iTXt_node.setAttribute("languageTag",
598                                       iTXt_languageTag.get(i));
599                iTXt_node.setAttribute("translatedKeyword",
600                                       iTXt_translatedKeyword.get(i));
601                iTXt_node.setAttribute("text", iTXt_text.get(i));
602
603                iTXt_parent.appendChild(iTXt_node);
604            }
605
606            root.appendChild(iTXt_parent);
607        }
608
609        // pHYs
610        if (pHYs_present) {
611            IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
612            pHYs_node.setAttribute("pixelsPerUnitXAxis",
613                              Integer.toString(pHYs_pixelsPerUnitXAxis));
614            pHYs_node.setAttribute("pixelsPerUnitYAxis",
615                                   Integer.toString(pHYs_pixelsPerUnitYAxis));
616            pHYs_node.setAttribute("unitSpecifier",
617                                   unitSpecifierNames[pHYs_unitSpecifier]);
618
619            root.appendChild(pHYs_node);
620        }
621
622        // sBIT
623        if (sBIT_present) {
624            IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
625
626            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) {
627                node = new IIOMetadataNode("sBIT_Grayscale");
628                node.setAttribute("gray",
629                                  Integer.toString(sBIT_grayBits));
630            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
631                node = new IIOMetadataNode("sBIT_GrayAlpha");
632                node.setAttribute("gray",
633                                  Integer.toString(sBIT_grayBits));
634                node.setAttribute("alpha",
635                                  Integer.toString(sBIT_alphaBits));
636            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) {
637                node = new IIOMetadataNode("sBIT_RGB");
638                node.setAttribute("red",
639                                  Integer.toString(sBIT_redBits));
640                node.setAttribute("green",
641                                  Integer.toString(sBIT_greenBits));
642                node.setAttribute("blue",
643                                  Integer.toString(sBIT_blueBits));
644            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
645                node = new IIOMetadataNode("sBIT_RGBAlpha");
646                node.setAttribute("red",
647                                  Integer.toString(sBIT_redBits));
648                node.setAttribute("green",
649                                  Integer.toString(sBIT_greenBits));
650                node.setAttribute("blue",
651                                  Integer.toString(sBIT_blueBits));
652                node.setAttribute("alpha",
653                                  Integer.toString(sBIT_alphaBits));
654            } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
655                node = new IIOMetadataNode("sBIT_Palette");
656                node.setAttribute("red",
657                                  Integer.toString(sBIT_redBits));
658                node.setAttribute("green",
659                                  Integer.toString(sBIT_greenBits));
660                node.setAttribute("blue",
661                                  Integer.toString(sBIT_blueBits));
662            }
663            sBIT_node.appendChild(node);
664
665            root.appendChild(sBIT_node);
666        }
667
668        // sPLT
669        if (sPLT_present) {
670            IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
671
672            sPLT_node.setAttribute("name", sPLT_paletteName);
673            sPLT_node.setAttribute("sampleDepth",
674                                   Integer.toString(sPLT_sampleDepth));
675
676            int numEntries = sPLT_red.length;
677            for (int i = 0; i < numEntries; i++) {
678                IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
679                entry.setAttribute("index", Integer.toString(i));
680                entry.setAttribute("red", Integer.toString(sPLT_red[i]));
681                entry.setAttribute("green", Integer.toString(sPLT_green[i]));
682                entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
683                entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
684                entry.setAttribute("frequency",
685                                  Integer.toString(sPLT_frequency[i]));
686                sPLT_node.appendChild(entry);
687            }
688
689            root.appendChild(sPLT_node);
690        }
691
692        // sRGB
693        if (sRGB_present) {
694            IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
695            sRGB_node.setAttribute("renderingIntent",
696                                   renderingIntentNames[sRGB_renderingIntent]);
697
698            root.appendChild(sRGB_node);
699        }
700
701        // tEXt
702        if (tEXt_keyword.size() > 0) {
703            IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
704            for (int i = 0; i < tEXt_keyword.size(); i++) {
705                IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
706                tEXt_node.setAttribute("keyword" , tEXt_keyword.get(i));
707                tEXt_node.setAttribute("value" , tEXt_text.get(i));
708
709                tEXt_parent.appendChild(tEXt_node);
710            }
711
712            root.appendChild(tEXt_parent);
713        }
714
715        // tIME
716        if (tIME_present) {
717            IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
718            tIME_node.setAttribute("year", Integer.toString(tIME_year));
719            tIME_node.setAttribute("month", Integer.toString(tIME_month));
720            tIME_node.setAttribute("day", Integer.toString(tIME_day));
721            tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
722            tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
723            tIME_node.setAttribute("second", Integer.toString(tIME_second));
724
725            root.appendChild(tIME_node);
726        }
727
728        // tRNS
729        if (tRNS_present) {
730            IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
731
732            if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
733                node = new IIOMetadataNode("tRNS_Palette");
734
735                for (int i = 0; i < tRNS_alpha.length; i++) {
736                    IIOMetadataNode entry =
737                        new IIOMetadataNode("tRNS_PaletteEntry");
738                    entry.setAttribute("index", Integer.toString(i));
739                    entry.setAttribute("alpha",
740                                       Integer.toString(tRNS_alpha[i] & 0xff));
741                    node.appendChild(entry);
742                }
743            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
744                node = new IIOMetadataNode("tRNS_Grayscale");
745                node.setAttribute("gray", Integer.toString(tRNS_gray));
746            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
747                node = new IIOMetadataNode("tRNS_RGB");
748                node.setAttribute("red", Integer.toString(tRNS_red));
749                node.setAttribute("green", Integer.toString(tRNS_green));
750                node.setAttribute("blue", Integer.toString(tRNS_blue));
751            }
752            tRNS_node.appendChild(node);
753
754            root.appendChild(tRNS_node);
755        }
756
757        // zTXt
758        if (zTXt_keyword.size() > 0) {
759            IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
760            for (int i = 0; i < zTXt_keyword.size(); i++) {
761                IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
762                zTXt_node.setAttribute("keyword", zTXt_keyword.get(i));
763
764                int cm = (zTXt_compressionMethod.get(i)).intValue();
765                zTXt_node.setAttribute("compressionMethod",
766                                       zTXt_compressionMethodNames[cm]);
767
768                zTXt_node.setAttribute("text", zTXt_text.get(i));
769
770                zTXt_parent.appendChild(zTXt_node);
771            }
772
773            root.appendChild(zTXt_parent);
774        }
775
776        // Unknown chunks
777        if (unknownChunkType.size() > 0) {
778            IIOMetadataNode unknown_parent =
779                new IIOMetadataNode("UnknownChunks");
780            for (int i = 0; i < unknownChunkType.size(); i++) {
781                IIOMetadataNode unknown_node =
782                    new IIOMetadataNode("UnknownChunk");
783                unknown_node.setAttribute("type",
784                                          unknownChunkType.get(i));
785                unknown_node.setUserObject(unknownChunkData.get(i));
786
787                unknown_parent.appendChild(unknown_node);
788            }
789
790            root.appendChild(unknown_parent);
791        }
792
793        return root;
794    }
795
796    private int getNumChannels() {
797        // Determine number of channels
798        // Be careful about palette color with transparency
799        int numChannels = IHDR_numChannels[IHDR_colorType];
800        if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
801            tRNS_present && tRNS_colorType == IHDR_colorType) {
802            numChannels = 4;
803        }
804        return numChannels;
805    }
806
807    public IIOMetadataNode getStandardChromaNode() {
808        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
809        IIOMetadataNode node = null; // scratch node
810
811        node = new IIOMetadataNode("ColorSpaceType");
812        node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
813        chroma_node.appendChild(node);
814
815        node = new IIOMetadataNode("NumChannels");
816        node.setAttribute("value", Integer.toString(getNumChannels()));
817        chroma_node.appendChild(node);
818
819        if (gAMA_present) {
820            node = new IIOMetadataNode("Gamma");
821            node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
822            chroma_node.appendChild(node);
823        }
824
825        node = new IIOMetadataNode("BlackIsZero");
826        node.setAttribute("value", "TRUE");
827        chroma_node.appendChild(node);
828
829        if (PLTE_present) {
830            boolean hasAlpha = tRNS_present &&
831                (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE);
832
833            node = new IIOMetadataNode("Palette");
834            for (int i = 0; i < PLTE_red.length; i++) {
835                IIOMetadataNode entry =
836                    new IIOMetadataNode("PaletteEntry");
837                entry.setAttribute("index", Integer.toString(i));
838                entry.setAttribute("red",
839                                   Integer.toString(PLTE_red[i] & 0xff));
840                entry.setAttribute("green",
841                                   Integer.toString(PLTE_green[i] & 0xff));
842                entry.setAttribute("blue",
843                                   Integer.toString(PLTE_blue[i] & 0xff));
844                if (hasAlpha) {
845                    int alpha = (i < tRNS_alpha.length) ?
846                        (tRNS_alpha[i] & 0xff) : 255;
847                    entry.setAttribute("alpha", Integer.toString(alpha));
848                }
849                node.appendChild(entry);
850            }
851            chroma_node.appendChild(node);
852        }
853
854        if (bKGD_present) {
855            if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
856                node = new IIOMetadataNode("BackgroundIndex");
857                node.setAttribute("value", Integer.toString(bKGD_index));
858            } else {
859                node = new IIOMetadataNode("BackgroundColor");
860                int r, g, b;
861
862                if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
863                    r = g = b = bKGD_gray;
864                } else {
865                    r = bKGD_red;
866                    g = bKGD_green;
867                    b = bKGD_blue;
868                }
869                node.setAttribute("red", Integer.toString(r));
870                node.setAttribute("green", Integer.toString(g));
871                node.setAttribute("blue", Integer.toString(b));
872            }
873            chroma_node.appendChild(node);
874        }
875
876        return chroma_node;
877    }
878
879    public IIOMetadataNode getStandardCompressionNode() {
880        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
881        IIOMetadataNode node = null; // scratch node
882
883        node = new IIOMetadataNode("CompressionTypeName");
884        node.setAttribute("value", "deflate");
885        compression_node.appendChild(node);
886
887        node = new IIOMetadataNode("Lossless");
888        node.setAttribute("value", "TRUE");
889        compression_node.appendChild(node);
890
891        node = new IIOMetadataNode("NumProgressiveScans");
892        node.setAttribute("value",
893                          (IHDR_interlaceMethod == 0) ? "1" : "7");
894        compression_node.appendChild(node);
895
896        return compression_node;
897    }
898
899    private String repeat(String s, int times) {
900        if (times == 1) {
901            return s;
902        }
903        StringBuilder sb = new StringBuilder((s.length() + 1)*times - 1);
904        sb.append(s);
905        for (int i = 1; i < times; i++) {
906            sb.append(" ");
907            sb.append(s);
908        }
909        return sb.toString();
910    }
911
912    public IIOMetadataNode getStandardDataNode() {
913        IIOMetadataNode data_node = new IIOMetadataNode("Data");
914        IIOMetadataNode node = null; // scratch node
915
916        node = new IIOMetadataNode("PlanarConfiguration");
917        node.setAttribute("value", "PixelInterleaved");
918        data_node.appendChild(node);
919
920        node = new IIOMetadataNode("SampleFormat");
921        node.setAttribute("value",
922                          IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ?
923                          "Index" : "UnsignedIntegral");
924        data_node.appendChild(node);
925
926        String bitDepth = Integer.toString(IHDR_bitDepth);
927        node = new IIOMetadataNode("BitsPerSample");
928        node.setAttribute("value", repeat(bitDepth, getNumChannels()));
929        data_node.appendChild(node);
930
931        if (sBIT_present) {
932            node = new IIOMetadataNode("SignificantBitsPerSample");
933            String sbits;
934            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY ||
935                sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
936                sbits = Integer.toString(sBIT_grayBits);
937            } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB ||
938                     // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
939                sbits = Integer.toString(sBIT_redBits) + " " +
940                    Integer.toString(sBIT_greenBits) + " " +
941                    Integer.toString(sBIT_blueBits);
942            }
943
944            if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
945                sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
946                sbits += " " + Integer.toString(sBIT_alphaBits);
947            }
948
949            node.setAttribute("value", sbits);
950            data_node.appendChild(node);
951        }
952
953        // SampleMSB
954
955        return data_node;
956    }
957
958    public IIOMetadataNode getStandardDimensionNode() {
959        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
960        IIOMetadataNode node = null; // scratch node
961
962        node = new IIOMetadataNode("PixelAspectRatio");
963        float ratio = pHYs_present ?
964            (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F;
965        node.setAttribute("value", Float.toString(ratio));
966        dimension_node.appendChild(node);
967
968        node = new IIOMetadataNode("ImageOrientation");
969        node.setAttribute("value", "Normal");
970        dimension_node.appendChild(node);
971
972        if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
973            node = new IIOMetadataNode("HorizontalPixelSize");
974            node.setAttribute("value",
975                              Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis));
976            dimension_node.appendChild(node);
977
978            node = new IIOMetadataNode("VerticalPixelSize");
979            node.setAttribute("value",
980                              Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis));
981            dimension_node.appendChild(node);
982        }
983
984        return dimension_node;
985    }
986
987    public IIOMetadataNode getStandardDocumentNode() {
988        if (!tIME_present) {
989            return null;
990        }
991
992        IIOMetadataNode document_node = new IIOMetadataNode("Document");
993        IIOMetadataNode node = null; // scratch node
994
995        node = new IIOMetadataNode("ImageModificationTime");
996        node.setAttribute("year", Integer.toString(tIME_year));
997        node.setAttribute("month", Integer.toString(tIME_month));
998        node.setAttribute("day", Integer.toString(tIME_day));
999        node.setAttribute("hour", Integer.toString(tIME_hour));
1000        node.setAttribute("minute", Integer.toString(tIME_minute));
1001        node.setAttribute("second", Integer.toString(tIME_second));
1002        document_node.appendChild(node);
1003
1004        return document_node;
1005    }
1006
1007    public IIOMetadataNode getStandardTextNode() {
1008        int numEntries = tEXt_keyword.size() +
1009            iTXt_keyword.size() + zTXt_keyword.size();
1010        if (numEntries == 0) {
1011            return null;
1012        }
1013
1014        IIOMetadataNode text_node = new IIOMetadataNode("Text");
1015        IIOMetadataNode node = null; // scratch node
1016
1017        for (int i = 0; i < tEXt_keyword.size(); i++) {
1018            node = new IIOMetadataNode("TextEntry");
1019            node.setAttribute("keyword", tEXt_keyword.get(i));
1020            node.setAttribute("value", tEXt_text.get(i));
1021            node.setAttribute("encoding", "ISO-8859-1");
1022            node.setAttribute("compression", "none");
1023
1024            text_node.appendChild(node);
1025        }
1026
1027        for (int i = 0; i < iTXt_keyword.size(); i++) {
1028            node = new IIOMetadataNode("TextEntry");
1029            node.setAttribute("keyword", iTXt_keyword.get(i));
1030            node.setAttribute("value", iTXt_text.get(i));
1031            node.setAttribute("language",
1032                              iTXt_languageTag.get(i));
1033            if (iTXt_compressionFlag.get(i)) {
1034                node.setAttribute("compression", "zip");
1035            } else {
1036                node.setAttribute("compression", "none");
1037            }
1038
1039            text_node.appendChild(node);
1040        }
1041
1042        for (int i = 0; i < zTXt_keyword.size(); i++) {
1043            node = new IIOMetadataNode("TextEntry");
1044            node.setAttribute("keyword", zTXt_keyword.get(i));
1045            node.setAttribute("value", zTXt_text.get(i));
1046            node.setAttribute("compression", "zip");
1047
1048            text_node.appendChild(node);
1049        }
1050
1051        return text_node;
1052    }
1053
1054    public IIOMetadataNode getStandardTransparencyNode() {
1055        IIOMetadataNode transparency_node =
1056            new IIOMetadataNode("Transparency");
1057        IIOMetadataNode node = null; // scratch node
1058
1059        node = new IIOMetadataNode("Alpha");
1060        boolean hasAlpha =
1061            (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) ||
1062            (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) ||
1063            (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
1064             tRNS_present &&
1065             (tRNS_colorType == IHDR_colorType) &&
1066             (tRNS_alpha != null));
1067        node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none");
1068        transparency_node.appendChild(node);
1069
1070        if (tRNS_present) {
1071            node = new IIOMetadataNode("TransparentColor");
1072            if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
1073                node.setAttribute("value",
1074                                  Integer.toString(tRNS_red) + " " +
1075                                  Integer.toString(tRNS_green) + " " +
1076                                  Integer.toString(tRNS_blue));
1077            } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
1078                node.setAttribute("value", Integer.toString(tRNS_gray));
1079            }
1080            transparency_node.appendChild(node);
1081        }
1082
1083        return transparency_node;
1084    }
1085
1086    // Shorthand for throwing an IIOInvalidTreeException
1087    private void fatal(Node node, String reason)
1088        throws IIOInvalidTreeException {
1089        throw new IIOInvalidTreeException(reason, node);
1090    }
1091
1092    // Get an integer-valued attribute
1093    private String getStringAttribute(Node node, String name,
1094                                      String defaultValue, boolean required)
1095        throws IIOInvalidTreeException {
1096        Node attr = node.getAttributes().getNamedItem(name);
1097        if (attr == null) {
1098            if (!required) {
1099                return defaultValue;
1100            } else {
1101                fatal(node, "Required attribute " + name + " not present!");
1102            }
1103        }
1104        return attr.getNodeValue();
1105    }
1106
1107
1108    // Get an integer-valued attribute
1109    private int getIntAttribute(Node node, String name,
1110                                int defaultValue, boolean required)
1111        throws IIOInvalidTreeException {
1112        String value = getStringAttribute(node, name, null, required);
1113        if (value == null) {
1114            return defaultValue;
1115        }
1116        return Integer.parseInt(value);
1117    }
1118
1119    // Get a float-valued attribute
1120    private float getFloatAttribute(Node node, String name,
1121                                    float defaultValue, boolean required)
1122        throws IIOInvalidTreeException {
1123        String value = getStringAttribute(node, name, null, required);
1124        if (value == null) {
1125            return defaultValue;
1126        }
1127        return Float.parseFloat(value);
1128    }
1129
1130    // Get a required integer-valued attribute
1131    private int getIntAttribute(Node node, String name)
1132        throws IIOInvalidTreeException {
1133        return getIntAttribute(node, name, -1, true);
1134    }
1135
1136    // Get a required float-valued attribute
1137    private float getFloatAttribute(Node node, String name)
1138        throws IIOInvalidTreeException {
1139        return getFloatAttribute(node, name, -1.0F, true);
1140    }
1141
1142    // Get a boolean-valued attribute
1143    private boolean getBooleanAttribute(Node node, String name,
1144                                        boolean defaultValue,
1145                                        boolean required)
1146        throws IIOInvalidTreeException {
1147        Node attr = node.getAttributes().getNamedItem(name);
1148        if (attr == null) {
1149            if (!required) {
1150                return defaultValue;
1151            } else {
1152                fatal(node, "Required attribute " + name + " not present!");
1153            }
1154        }
1155        String value = attr.getNodeValue();
1156        // Allow lower case booleans for backward compatibility, #5082756
1157        if (value.equals("TRUE") || value.equals("true")) {
1158            return true;
1159        } else if (value.equals("FALSE") || value.equals("false")) {
1160            return false;
1161        } else {
1162            fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!");
1163            return false;
1164        }
1165    }
1166
1167    // Get a required boolean-valued attribute
1168    private boolean getBooleanAttribute(Node node, String name)
1169        throws IIOInvalidTreeException {
1170        return getBooleanAttribute(node, name, false, true);
1171    }
1172
1173    // Get an enumerated attribute as an index into a String array
1174    private int getEnumeratedAttribute(Node node,
1175                                       String name, String[] legalNames,
1176                                       int defaultValue, boolean required)
1177        throws IIOInvalidTreeException {
1178        Node attr = node.getAttributes().getNamedItem(name);
1179        if (attr == null) {
1180            if (!required) {
1181                return defaultValue;
1182            } else {
1183                fatal(node, "Required attribute " + name + " not present!");
1184            }
1185        }
1186        String value = attr.getNodeValue();
1187        for (int i = 0; i < legalNames.length; i++) {
1188            if (value.equals(legalNames[i])) {
1189                return i;
1190            }
1191        }
1192
1193        fatal(node, "Illegal value for attribute " + name + "!");
1194        return -1;
1195    }
1196
1197    // Get a required enumerated attribute as an index into a String array
1198    private int getEnumeratedAttribute(Node node,
1199                                       String name, String[] legalNames)
1200        throws IIOInvalidTreeException {
1201        return getEnumeratedAttribute(node, name, legalNames, -1, true);
1202    }
1203
1204    // Get a String-valued attribute
1205    private String getAttribute(Node node, String name,
1206                                String defaultValue, boolean required)
1207        throws IIOInvalidTreeException {
1208        Node attr = node.getAttributes().getNamedItem(name);
1209        if (attr == null) {
1210            if (!required) {
1211                return defaultValue;
1212            } else {
1213                fatal(node, "Required attribute " + name + " not present!");
1214            }
1215        }
1216        return attr.getNodeValue();
1217    }
1218
1219    // Get a required String-valued attribute
1220    private String getAttribute(Node node, String name)
1221        throws IIOInvalidTreeException {
1222            return getAttribute(node, name, null, true);
1223    }
1224
1225    public void mergeTree(String formatName, Node root)
1226        throws IIOInvalidTreeException {
1227        if (formatName.equals(nativeMetadataFormatName)) {
1228            if (root == null) {
1229                throw new IllegalArgumentException("root == null!");
1230            }
1231            mergeNativeTree(root);
1232        } else if (formatName.equals
1233                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
1234            if (root == null) {
1235                throw new IllegalArgumentException("root == null!");
1236            }
1237            mergeStandardTree(root);
1238        } else {
1239            throw new IllegalArgumentException("Not a recognized format!");
1240        }
1241    }
1242
1243    private void mergeNativeTree(Node root)
1244        throws IIOInvalidTreeException {
1245        Node node = root;
1246        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
1247            fatal(node, "Root must be " + nativeMetadataFormatName);
1248        }
1249
1250        node = node.getFirstChild();
1251        while (node != null) {
1252            String name = node.getNodeName();
1253
1254            if (name.equals("IHDR")) {
1255                IHDR_width = getIntAttribute(node, "width");
1256                IHDR_height = getIntAttribute(node, "height");
1257                IHDR_bitDepth =
1258                        Integer.valueOf(IHDR_bitDepths[
1259                                getEnumeratedAttribute(node,
1260                                                    "bitDepth",
1261                                                    IHDR_bitDepths)]);
1262                IHDR_colorType = getEnumeratedAttribute(node, "colorType",
1263                                                        IHDR_colorTypeNames);
1264                IHDR_compressionMethod =
1265                    getEnumeratedAttribute(node, "compressionMethod",
1266                                           IHDR_compressionMethodNames);
1267                IHDR_filterMethod =
1268                    getEnumeratedAttribute(node,
1269                                           "filterMethod",
1270                                           IHDR_filterMethodNames);
1271                IHDR_interlaceMethod =
1272                    getEnumeratedAttribute(node, "interlaceMethod",
1273                                           IHDR_interlaceMethodNames);
1274                IHDR_present = true;
1275            } else if (name.equals("PLTE")) {
1276                byte[] red = new byte[256];
1277                byte[] green  = new byte[256];
1278                byte[] blue = new byte[256];
1279                int maxindex = -1;
1280
1281                Node PLTE_entry = node.getFirstChild();
1282                if (PLTE_entry == null) {
1283                    fatal(node, "Palette has no entries!");
1284                }
1285
1286                while (PLTE_entry != null) {
1287                    if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
1288                        fatal(node,
1289                              "Only a PLTEEntry may be a child of a PLTE!");
1290                    }
1291
1292                    int index = getIntAttribute(PLTE_entry, "index");
1293                    if (index < 0 || index > 255) {
1294                        fatal(node,
1295                              "Bad value for PLTEEntry attribute index!");
1296                    }
1297                    if (index > maxindex) {
1298                        maxindex = index;
1299                    }
1300                    red[index] =
1301                        (byte)getIntAttribute(PLTE_entry, "red");
1302                    green[index] =
1303                        (byte)getIntAttribute(PLTE_entry, "green");
1304                    blue[index] =
1305                        (byte)getIntAttribute(PLTE_entry, "blue");
1306
1307                    PLTE_entry = PLTE_entry.getNextSibling();
1308                }
1309
1310                int numEntries = maxindex + 1;
1311                PLTE_red = new byte[numEntries];
1312                PLTE_green = new byte[numEntries];
1313                PLTE_blue = new byte[numEntries];
1314                System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1315                System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1316                System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1317                PLTE_present = true;
1318            } else if (name.equals("bKGD")) {
1319                bKGD_present = false; // Guard against partial overwrite
1320                Node bKGD_node = node.getFirstChild();
1321                if (bKGD_node == null) {
1322                    fatal(node, "bKGD node has no children!");
1323                }
1324                String bKGD_name = bKGD_node.getNodeName();
1325                if (bKGD_name.equals("bKGD_Palette")) {
1326                    bKGD_index = getIntAttribute(bKGD_node, "index");
1327                    bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1328                } else if (bKGD_name.equals("bKGD_Grayscale")) {
1329                    bKGD_gray = getIntAttribute(bKGD_node, "gray");
1330                    bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1331                } else if (bKGD_name.equals("bKGD_RGB")) {
1332                    bKGD_red = getIntAttribute(bKGD_node, "red");
1333                    bKGD_green = getIntAttribute(bKGD_node, "green");
1334                    bKGD_blue = getIntAttribute(bKGD_node, "blue");
1335                    bKGD_colorType = PNGImageReader.PNG_COLOR_RGB;
1336                } else {
1337                    fatal(node, "Bad child of a bKGD node!");
1338                }
1339                if (bKGD_node.getNextSibling() != null) {
1340                    fatal(node, "bKGD node has more than one child!");
1341                }
1342
1343                bKGD_present = true;
1344            } else if (name.equals("cHRM")) {
1345                cHRM_whitePointX = getIntAttribute(node, "whitePointX");
1346                cHRM_whitePointY = getIntAttribute(node, "whitePointY");
1347                cHRM_redX = getIntAttribute(node, "redX");
1348                cHRM_redY = getIntAttribute(node, "redY");
1349                cHRM_greenX = getIntAttribute(node, "greenX");
1350                cHRM_greenY = getIntAttribute(node, "greenY");
1351                cHRM_blueX = getIntAttribute(node, "blueX");
1352                cHRM_blueY = getIntAttribute(node, "blueY");
1353
1354                cHRM_present = true;
1355            } else if (name.equals("gAMA")) {
1356                gAMA_gamma = getIntAttribute(node, "value");
1357                gAMA_present = true;
1358            } else if (name.equals("hIST")) {
1359                char[] hist = new char[256];
1360                int maxindex = -1;
1361
1362                Node hIST_entry = node.getFirstChild();
1363                if (hIST_entry == null) {
1364                    fatal(node, "hIST node has no children!");
1365                }
1366
1367                while (hIST_entry != null) {
1368                    if (!hIST_entry.getNodeName().equals("hISTEntry")) {
1369                        fatal(node,
1370                              "Only a hISTEntry may be a child of a hIST!");
1371                    }
1372
1373                    int index = getIntAttribute(hIST_entry, "index");
1374                    if (index < 0 || index > 255) {
1375                        fatal(node,
1376                              "Bad value for histEntry attribute index!");
1377                    }
1378                    if (index > maxindex) {
1379                        maxindex = index;
1380                    }
1381                    hist[index] =
1382                        (char)getIntAttribute(hIST_entry, "value");
1383
1384                    hIST_entry = hIST_entry.getNextSibling();
1385                }
1386
1387                int numEntries = maxindex + 1;
1388                hIST_histogram = new char[numEntries];
1389                System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
1390
1391                hIST_present = true;
1392            } else if (name.equals("iCCP")) {
1393                iCCP_profileName = getAttribute(node, "profileName");
1394                iCCP_compressionMethod =
1395                    getEnumeratedAttribute(node, "compressionMethod",
1396                                           iCCP_compressionMethodNames);
1397                Object compressedProfile =
1398                    ((IIOMetadataNode)node).getUserObject();
1399                if (compressedProfile == null) {
1400                    fatal(node, "No ICCP profile present in user object!");
1401                }
1402                if (!(compressedProfile instanceof byte[])) {
1403                    fatal(node, "User object not a byte array!");
1404                }
1405
1406                iCCP_compressedProfile = ((byte[])compressedProfile).clone();
1407
1408                iCCP_present = true;
1409            } else if (name.equals("iTXt")) {
1410                Node iTXt_node = node.getFirstChild();
1411                while (iTXt_node != null) {
1412                    if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
1413                        fatal(node,
1414                              "Only an iTXtEntry may be a child of an iTXt!");
1415                    }
1416
1417                    String keyword = getAttribute(iTXt_node, "keyword");
1418                    if (isValidKeyword(keyword)) {
1419                        iTXt_keyword.add(keyword);
1420
1421                        boolean compressionFlag =
1422                            getBooleanAttribute(iTXt_node, "compressionFlag");
1423                        iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag));
1424
1425                        String compressionMethod =
1426                            getAttribute(iTXt_node, "compressionMethod");
1427                        iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
1428
1429                        String languageTag =
1430                            getAttribute(iTXt_node, "languageTag");
1431                        iTXt_languageTag.add(languageTag);
1432
1433                        String translatedKeyword =
1434                            getAttribute(iTXt_node, "translatedKeyword");
1435                        iTXt_translatedKeyword.add(translatedKeyword);
1436
1437                        String text = getAttribute(iTXt_node, "text");
1438                        iTXt_text.add(text);
1439
1440                    }
1441                    // silently skip invalid text entry
1442
1443                    iTXt_node = iTXt_node.getNextSibling();
1444                }
1445            } else if (name.equals("pHYs")) {
1446                pHYs_pixelsPerUnitXAxis =
1447                    getIntAttribute(node, "pixelsPerUnitXAxis");
1448                pHYs_pixelsPerUnitYAxis =
1449                    getIntAttribute(node, "pixelsPerUnitYAxis");
1450                pHYs_unitSpecifier =
1451                    getEnumeratedAttribute(node, "unitSpecifier",
1452                                           unitSpecifierNames);
1453
1454                pHYs_present = true;
1455            } else if (name.equals("sBIT")) {
1456                sBIT_present = false; // Guard against partial overwrite
1457                Node sBIT_node = node.getFirstChild();
1458                if (sBIT_node == null) {
1459                    fatal(node, "sBIT node has no children!");
1460                }
1461                String sBIT_name = sBIT_node.getNodeName();
1462                if (sBIT_name.equals("sBIT_Grayscale")) {
1463                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1464                    sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1465                } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
1466                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1467                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1468                    sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1469                } else if (sBIT_name.equals("sBIT_RGB")) {
1470                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1471                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1472                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1473                    sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1474                } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
1475                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1476                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1477                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1478                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1479                    sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
1480                } else if (sBIT_name.equals("sBIT_Palette")) {
1481                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1482                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1483                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1484                    sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1485                } else {
1486                    fatal(node, "Bad child of an sBIT node!");
1487                }
1488                if (sBIT_node.getNextSibling() != null) {
1489                    fatal(node, "sBIT node has more than one child!");
1490                }
1491
1492                sBIT_present = true;
1493            } else if (name.equals("sPLT")) {
1494                sPLT_paletteName = getAttribute(node, "name");
1495                sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
1496
1497                int[] red = new int[256];
1498                int[] green  = new int[256];
1499                int[] blue = new int[256];
1500                int[] alpha = new int[256];
1501                int[] frequency = new int[256];
1502                int maxindex = -1;
1503
1504                Node sPLT_entry = node.getFirstChild();
1505                if (sPLT_entry == null) {
1506                    fatal(node, "sPLT node has no children!");
1507                }
1508
1509                while (sPLT_entry != null) {
1510                    if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
1511                        fatal(node,
1512                              "Only an sPLTEntry may be a child of an sPLT!");
1513                    }
1514
1515                    int index = getIntAttribute(sPLT_entry, "index");
1516                    if (index < 0 || index > 255) {
1517                        fatal(node,
1518                              "Bad value for PLTEEntry attribute index!");
1519                    }
1520                    if (index > maxindex) {
1521                        maxindex = index;
1522                    }
1523                    red[index] = getIntAttribute(sPLT_entry, "red");
1524                    green[index] = getIntAttribute(sPLT_entry, "green");
1525                    blue[index] = getIntAttribute(sPLT_entry, "blue");
1526                    alpha[index] = getIntAttribute(sPLT_entry, "alpha");
1527                    frequency[index] =
1528                        getIntAttribute(sPLT_entry, "frequency");
1529
1530                    sPLT_entry = sPLT_entry.getNextSibling();
1531                }
1532
1533                int numEntries = maxindex + 1;
1534                sPLT_red = new int[numEntries];
1535                sPLT_green = new int[numEntries];
1536                sPLT_blue = new int[numEntries];
1537                sPLT_alpha = new int[numEntries];
1538                sPLT_frequency = new int[numEntries];
1539                System.arraycopy(red, 0, sPLT_red, 0, numEntries);
1540                System.arraycopy(green, 0, sPLT_green, 0, numEntries);
1541                System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
1542                System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
1543                System.arraycopy(frequency, 0,
1544                                 sPLT_frequency, 0, numEntries);
1545
1546                sPLT_present = true;
1547            } else if (name.equals("sRGB")) {
1548                sRGB_renderingIntent =
1549                    getEnumeratedAttribute(node, "renderingIntent",
1550                                           renderingIntentNames);
1551
1552                sRGB_present = true;
1553            } else if (name.equals("tEXt")) {
1554                Node tEXt_node = node.getFirstChild();
1555                while (tEXt_node != null) {
1556                    if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
1557                        fatal(node,
1558                              "Only an tEXtEntry may be a child of an tEXt!");
1559                    }
1560
1561                    String keyword = getAttribute(tEXt_node, "keyword");
1562                    tEXt_keyword.add(keyword);
1563
1564                    String text = getAttribute(tEXt_node, "value");
1565                    tEXt_text.add(text);
1566
1567                    tEXt_node = tEXt_node.getNextSibling();
1568                }
1569            } else if (name.equals("tIME")) {
1570                tIME_year = getIntAttribute(node, "year");
1571                tIME_month = getIntAttribute(node, "month");
1572                tIME_day = getIntAttribute(node, "day");
1573                tIME_hour = getIntAttribute(node, "hour");
1574                tIME_minute = getIntAttribute(node, "minute");
1575                tIME_second = getIntAttribute(node, "second");
1576
1577                tIME_present = true;
1578            } else if (name.equals("tRNS")) {
1579                tRNS_present = false; // Guard against partial overwrite
1580                Node tRNS_node = node.getFirstChild();
1581                if (tRNS_node == null) {
1582                    fatal(node, "tRNS node has no children!");
1583                }
1584                String tRNS_name = tRNS_node.getNodeName();
1585                if (tRNS_name.equals("tRNS_Palette")) {
1586                    byte[] alpha = new byte[256];
1587                    int maxindex = -1;
1588
1589                    Node tRNS_paletteEntry = tRNS_node.getFirstChild();
1590                    if (tRNS_paletteEntry == null) {
1591                        fatal(node, "tRNS_Palette node has no children!");
1592                    }
1593                    while (tRNS_paletteEntry != null) {
1594                        if (!tRNS_paletteEntry.getNodeName().equals(
1595                                                        "tRNS_PaletteEntry")) {
1596                            fatal(node,
1597                 "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
1598                        }
1599                        int index =
1600                            getIntAttribute(tRNS_paletteEntry, "index");
1601                        if (index < 0 || index > 255) {
1602                            fatal(node,
1603                           "Bad value for tRNS_PaletteEntry attribute index!");
1604                        }
1605                        if (index > maxindex) {
1606                            maxindex = index;
1607                        }
1608                        alpha[index] =
1609                            (byte)getIntAttribute(tRNS_paletteEntry,
1610                                                  "alpha");
1611
1612                        tRNS_paletteEntry =
1613                            tRNS_paletteEntry.getNextSibling();
1614                    }
1615
1616                    int numEntries = maxindex + 1;
1617                    tRNS_alpha = new byte[numEntries];
1618                    tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1619                    System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
1620                } else if (tRNS_name.equals("tRNS_Grayscale")) {
1621                    tRNS_gray = getIntAttribute(tRNS_node, "gray");
1622                    tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY;
1623                } else if (tRNS_name.equals("tRNS_RGB")) {
1624                    tRNS_red = getIntAttribute(tRNS_node, "red");
1625                    tRNS_green = getIntAttribute(tRNS_node, "green");
1626                    tRNS_blue = getIntAttribute(tRNS_node, "blue");
1627                    tRNS_colorType = PNGImageReader.PNG_COLOR_RGB;
1628                } else {
1629                    fatal(node, "Bad child of a tRNS node!");
1630                }
1631                if (tRNS_node.getNextSibling() != null) {
1632                    fatal(node, "tRNS node has more than one child!");
1633                }
1634
1635                tRNS_present = true;
1636            } else if (name.equals("zTXt")) {
1637                Node zTXt_node = node.getFirstChild();
1638                while (zTXt_node != null) {
1639                    if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
1640                        fatal(node,
1641                              "Only an zTXtEntry may be a child of an zTXt!");
1642                    }
1643
1644                    String keyword = getAttribute(zTXt_node, "keyword");
1645                    zTXt_keyword.add(keyword);
1646
1647                    int compressionMethod =
1648                        getEnumeratedAttribute(zTXt_node, "compressionMethod",
1649                                               zTXt_compressionMethodNames);
1650                    zTXt_compressionMethod.add(compressionMethod);
1651
1652                    String text = getAttribute(zTXt_node, "text");
1653                    zTXt_text.add(text);
1654
1655                    zTXt_node = zTXt_node.getNextSibling();
1656                }
1657            } else if (name.equals("UnknownChunks")) {
1658                Node unknown_node = node.getFirstChild();
1659                while (unknown_node != null) {
1660                    if (!unknown_node.getNodeName().equals("UnknownChunk")) {
1661                        fatal(node,
1662                   "Only an UnknownChunk may be a child of an UnknownChunks!");
1663                    }
1664                    String chunkType = getAttribute(unknown_node, "type");
1665                    Object chunkData =
1666                        ((IIOMetadataNode)unknown_node).getUserObject();
1667
1668                    if (chunkType.length() != 4) {
1669                        fatal(unknown_node,
1670                              "Chunk type must be 4 characters!");
1671                    }
1672                    if (chunkData == null) {
1673                        fatal(unknown_node,
1674                              "No chunk data present in user object!");
1675                    }
1676                    if (!(chunkData instanceof byte[])) {
1677                        fatal(unknown_node,
1678                              "User object not a byte array!");
1679                    }
1680                    unknownChunkType.add(chunkType);
1681                    unknownChunkData.add(((byte[])chunkData).clone());
1682
1683                    unknown_node = unknown_node.getNextSibling();
1684                }
1685            } else {
1686                fatal(node, "Unknown child of root node!");
1687            }
1688
1689            node = node.getNextSibling();
1690        }
1691    }
1692
1693    /*
1694     * Accrding to PNG spec, keywords are restricted to 1 to 79 bytes
1695     * in length. Keywords shall contain only printable Latin-1 characters
1696     * and spaces; To reduce the chances for human misreading of a keyword,
1697     * leading spaces, trailing spaces, and consecutive spaces are not
1698     * permitted in keywords.
1699     *
1700     * See: http://www.w3.org/TR/PNG/#11keywords
1701     */
1702    private boolean isValidKeyword(String s) {
1703        int len = s.length();
1704        if (len < 1 || len >= 80) {
1705            return false;
1706        }
1707        if (s.startsWith(" ") || s.endsWith(" ") || s.contains("  ")) {
1708            return false;
1709        }
1710        return isISOLatin(s, false);
1711    }
1712
1713    /*
1714     * According to PNG spec, keyword shall contain only printable
1715     * Latin-1 [ISO-8859-1] characters and spaces; that is, only
1716     * character codes 32-126 and 161-255 decimal are allowed.
1717     * For Latin-1 value fields the 0x10 (linefeed) control
1718     * character is aloowed too.
1719     *
1720     * See: http://www.w3.org/TR/PNG/#11keywords
1721     */
1722    private boolean isISOLatin(String s, boolean isLineFeedAllowed) {
1723        int len = s.length();
1724        for (int i = 0; i < len; i++) {
1725            char c = s.charAt(i);
1726            if (c < 32 || c > 255 || (c > 126 && c < 161)) {
1727                // not printable. Check whether this is an allowed
1728                // control char
1729                if (!isLineFeedAllowed || c != 0x10) {
1730                    return false;
1731                }
1732            }
1733        }
1734        return true;
1735    }
1736
1737    private void mergeStandardTree(Node root)
1738        throws IIOInvalidTreeException {
1739        Node node = root;
1740        if (!node.getNodeName()
1741            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
1742            fatal(node, "Root must be " +
1743                  IIOMetadataFormatImpl.standardMetadataFormatName);
1744        }
1745
1746        node = node.getFirstChild();
1747        while (node != null) {
1748            String name = node.getNodeName();
1749
1750            if (name.equals("Chroma")) {
1751                Node child = node.getFirstChild();
1752                while (child != null) {
1753                    String childName = child.getNodeName();
1754                    if (childName.equals("Gamma")) {
1755                        float gamma = getFloatAttribute(child, "value");
1756                        gAMA_present = true;
1757                        gAMA_gamma = (int)(gamma*100000 + 0.5);
1758                    } else if (childName.equals("Palette")) {
1759                        byte[] red = new byte[256];
1760                        byte[] green = new byte[256];
1761                        byte[] blue = new byte[256];
1762                        int maxindex = -1;
1763
1764                        Node entry = child.getFirstChild();
1765                        while (entry != null) {
1766                            int index = getIntAttribute(entry, "index");
1767                            if (index >= 0 && index <= 255) {
1768                                red[index] =
1769                                    (byte)getIntAttribute(entry, "red");
1770                                green[index] =
1771                                    (byte)getIntAttribute(entry, "green");
1772                                blue[index] =
1773                                    (byte)getIntAttribute(entry, "blue");
1774                                if (index > maxindex) {
1775                                    maxindex = index;
1776                                }
1777                            }
1778                            entry = entry.getNextSibling();
1779                        }
1780
1781                        int numEntries = maxindex + 1;
1782                        PLTE_red = new byte[numEntries];
1783                        PLTE_green = new byte[numEntries];
1784                        PLTE_blue = new byte[numEntries];
1785                        System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1786                        System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1787                        System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1788                        PLTE_present = true;
1789                    } else if (childName.equals("BackgroundIndex")) {
1790                        bKGD_present = true;
1791                        bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1792                        bKGD_index = getIntAttribute(child, "value");
1793                    } else if (childName.equals("BackgroundColor")) {
1794                        int red = getIntAttribute(child, "red");
1795                        int green = getIntAttribute(child, "green");
1796                        int blue = getIntAttribute(child, "blue");
1797                        if (red == green && red == blue) {
1798                            bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1799                            bKGD_gray = red;
1800                        } else {
1801                            bKGD_red = red;
1802                            bKGD_green = green;
1803                            bKGD_blue = blue;
1804                        }
1805                        bKGD_present = true;
1806                    }
1807//                  } else if (childName.equals("ColorSpaceType")) {
1808//                  } else if (childName.equals("NumChannels")) {
1809
1810                    child = child.getNextSibling();
1811                }
1812            } else if (name.equals("Compression")) {
1813                Node child = node.getFirstChild();
1814                while (child != null) {
1815                    String childName = child.getNodeName();
1816                    if (childName.equals("NumProgressiveScans")) {
1817                        // Use Adam7 if NumProgressiveScans > 1
1818                        int scans = getIntAttribute(child, "value");
1819                        IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
1820//                  } else if (childName.equals("CompressionTypeName")) {
1821//                  } else if (childName.equals("Lossless")) {
1822//                  } else if (childName.equals("BitRate")) {
1823                    }
1824                    child = child.getNextSibling();
1825                }
1826            } else if (name.equals("Data")) {
1827                Node child = node.getFirstChild();
1828                while (child != null) {
1829                    String childName = child.getNodeName();
1830                    if (childName.equals("BitsPerSample")) {
1831                        String s = getAttribute(child, "value");
1832                        StringTokenizer t = new StringTokenizer(s);
1833                        int maxBits = -1;
1834                        while (t.hasMoreTokens()) {
1835                            int bits = Integer.parseInt(t.nextToken());
1836                            if (bits > maxBits) {
1837                                maxBits = bits;
1838                            }
1839                        }
1840                        if (maxBits < 1) {
1841                            maxBits = 1;
1842                        }
1843                        if (maxBits == 3) maxBits = 4;
1844                        if (maxBits > 4 || maxBits < 8) {
1845                            maxBits = 8;
1846                        }
1847                        if (maxBits > 8) {
1848                            maxBits = 16;
1849                        }
1850                        IHDR_bitDepth = maxBits;
1851                    } else if (childName.equals("SignificantBitsPerSample")) {
1852                        String s = getAttribute(child, "value");
1853                        StringTokenizer t = new StringTokenizer(s);
1854                        int numTokens = t.countTokens();
1855                        if (numTokens == 1) {
1856                            sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1857                            sBIT_grayBits = Integer.parseInt(t.nextToken());
1858                        } else if (numTokens == 2) {
1859                            sBIT_colorType =
1860                              PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1861                            sBIT_grayBits = Integer.parseInt(t.nextToken());
1862                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
1863                        } else if (numTokens == 3) {
1864                            sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1865                            sBIT_redBits = Integer.parseInt(t.nextToken());
1866                            sBIT_greenBits = Integer.parseInt(t.nextToken());
1867                            sBIT_blueBits = Integer.parseInt(t.nextToken());
1868                        } else if (numTokens == 4) {
1869                            sBIT_colorType =
1870                              PNGImageReader.PNG_COLOR_RGB_ALPHA;
1871                            sBIT_redBits = Integer.parseInt(t.nextToken());
1872                            sBIT_greenBits = Integer.parseInt(t.nextToken());
1873                            sBIT_blueBits = Integer.parseInt(t.nextToken());
1874                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
1875                        }
1876                        if (numTokens >= 1 && numTokens <= 4) {
1877                            sBIT_present = true;
1878                        }
1879//                      } else if (childName.equals("PlanarConfiguration")) {
1880//                      } else if (childName.equals("SampleFormat")) {
1881//                      } else if (childName.equals("SampleMSB")) {
1882                    }
1883                    child = child.getNextSibling();
1884                }
1885            } else if (name.equals("Dimension")) {
1886                boolean gotWidth = false;
1887                boolean gotHeight = false;
1888                boolean gotAspectRatio = false;
1889
1890                float width = -1.0F;
1891                float height = -1.0F;
1892                float aspectRatio = -1.0F;
1893
1894                Node child = node.getFirstChild();
1895                while (child != null) {
1896                    String childName = child.getNodeName();
1897                    if (childName.equals("PixelAspectRatio")) {
1898                        aspectRatio = getFloatAttribute(child, "value");
1899                        gotAspectRatio = true;
1900                    } else if (childName.equals("HorizontalPixelSize")) {
1901                        width = getFloatAttribute(child, "value");
1902                        gotWidth = true;
1903                    } else if (childName.equals("VerticalPixelSize")) {
1904                        height = getFloatAttribute(child, "value");
1905                        gotHeight = true;
1906//                  } else if (childName.equals("ImageOrientation")) {
1907//                  } else if
1908//                      (childName.equals("HorizontalPhysicalPixelSpacing")) {
1909//                  } else if
1910//                      (childName.equals("VerticalPhysicalPixelSpacing")) {
1911//                  } else if (childName.equals("HorizontalPosition")) {
1912//                  } else if (childName.equals("VerticalPosition")) {
1913//                  } else if (childName.equals("HorizontalPixelOffset")) {
1914//                  } else if (childName.equals("VerticalPixelOffset")) {
1915                    }
1916                    child = child.getNextSibling();
1917                }
1918
1919                if (gotWidth && gotHeight) {
1920                    pHYs_present = true;
1921                    pHYs_unitSpecifier = 1;
1922                    pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F);
1923                    pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F);
1924                } else if (gotAspectRatio) {
1925                    pHYs_present = true;
1926                    pHYs_unitSpecifier = 0;
1927
1928                    // Find a reasonable rational approximation
1929                    int denom = 1;
1930                    for (; denom < 100; denom++) {
1931                        int num = (int)(aspectRatio*denom);
1932                        if (Math.abs(num/denom - aspectRatio) < 0.001) {
1933                            break;
1934                        }
1935                    }
1936                    pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
1937                    pHYs_pixelsPerUnitYAxis = denom;
1938                }
1939            } else if (name.equals("Document")) {
1940                Node child = node.getFirstChild();
1941                while (child != null) {
1942                    String childName = child.getNodeName();
1943                    if (childName.equals("ImageModificationTime")) {
1944                        tIME_present = true;
1945                        tIME_year = getIntAttribute(child, "year");
1946                        tIME_month = getIntAttribute(child, "month");
1947                        tIME_day = getIntAttribute(child, "day");
1948                        tIME_hour =
1949                            getIntAttribute(child, "hour", 0, false);
1950                        tIME_minute =
1951                            getIntAttribute(child, "minute", 0, false);
1952                        tIME_second =
1953                            getIntAttribute(child, "second", 0, false);
1954//                  } else if (childName.equals("SubimageInterpretation")) {
1955//                  } else if (childName.equals("ImageCreationTime")) {
1956                    }
1957                    child = child.getNextSibling();
1958                }
1959            } else if (name.equals("Text")) {
1960                Node child = node.getFirstChild();
1961                while (child != null) {
1962                    String childName = child.getNodeName();
1963                    if (childName.equals("TextEntry")) {
1964                        String keyword =
1965                            getAttribute(child, "keyword", "", false);
1966                        String value = getAttribute(child, "value");
1967                        String language =
1968                            getAttribute(child, "language", "", false);
1969                        String compression =
1970                            getAttribute(child, "compression", "none", false);
1971
1972                        if (!isValidKeyword(keyword)) {
1973                            // Just ignore this node, PNG requires keywords
1974                        } else if (isISOLatin(value, true)) {
1975                            if (compression.equals("zip")) {
1976                                // Use a zTXt node
1977                                zTXt_keyword.add(keyword);
1978                                zTXt_text.add(value);
1979                                zTXt_compressionMethod.add(Integer.valueOf(0));
1980                            } else {
1981                                // Use a tEXt node
1982                                tEXt_keyword.add(keyword);
1983                                tEXt_text.add(value);
1984                            }
1985                        } else {
1986                            // Use an iTXt node
1987                            iTXt_keyword.add(keyword);
1988                            iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip")));
1989                            iTXt_compressionMethod.add(Integer.valueOf(0));
1990                            iTXt_languageTag.add(language);
1991                            iTXt_translatedKeyword.add(keyword); // fake it
1992                            iTXt_text.add(value);
1993                        }
1994                    }
1995                    child = child.getNextSibling();
1996                }
1997//          } else if (name.equals("Transparency")) {
1998//              Node child = node.getFirstChild();
1999//              while (child != null) {
2000//                  String childName = child.getNodeName();
2001//                  if (childName.equals("Alpha")) {
2002//                  } else if (childName.equals("TransparentIndex")) {
2003//                  } else if (childName.equals("TransparentColor")) {
2004//                  } else if (childName.equals("TileTransparencies")) {
2005//                  } else if (childName.equals("TileOpacities")) {
2006//                  }
2007//                  child = child.getNextSibling();
2008//              }
2009//          } else {
2010//              // fatal(node, "Unknown child of root node!");
2011            }
2012
2013            node = node.getNextSibling();
2014        }
2015    }
2016
2017    // Reset all instance variables to their initial state
2018    public void reset() {
2019        IHDR_present = false;
2020        PLTE_present = false;
2021        bKGD_present = false;
2022        cHRM_present = false;
2023        gAMA_present = false;
2024        hIST_present = false;
2025        iCCP_present = false;
2026        iTXt_keyword = new ArrayList<String>();
2027        iTXt_compressionFlag = new ArrayList<Boolean>();
2028        iTXt_compressionMethod = new ArrayList<Integer>();
2029        iTXt_languageTag = new ArrayList<String>();
2030        iTXt_translatedKeyword = new ArrayList<String>();
2031        iTXt_text = new ArrayList<String>();
2032        pHYs_present = false;
2033        sBIT_present = false;
2034        sPLT_present = false;
2035        sRGB_present = false;
2036        tEXt_keyword = new ArrayList<String>();
2037        tEXt_text = new ArrayList<String>();
2038        tIME_present = false;
2039        tRNS_present = false;
2040        zTXt_keyword = new ArrayList<String>();
2041        zTXt_compressionMethod = new ArrayList<Integer>();
2042        zTXt_text = new ArrayList<String>();
2043        unknownChunkType = new ArrayList<String>();
2044        unknownChunkData = new ArrayList<byte[]>();
2045    }
2046}
2047