001/*
002 * $RCSfile: CLibPNGMetadata.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.3 $
042 * $Date: 2006/02/27 17:25:04 $
043 * $State: Exp $
044 */
045
046package com.github.jaiimageio.impl.plugins.png;
047
048import java.awt.image.ColorModel;
049import java.awt.image.IndexColorModel;
050import java.awt.image.SampleModel;
051import java.io.IOException;
052import java.io.UnsupportedEncodingException;
053import java.util.ArrayList;
054import java.util.Arrays;
055import java.util.Iterator;
056import java.util.List;
057import java.util.StringTokenizer;
058
059import javax.imageio.ImageTypeSpecifier;
060import javax.imageio.ImageWriteParam;
061import javax.imageio.metadata.IIOInvalidTreeException;
062import javax.imageio.metadata.IIOMetadata;
063import javax.imageio.metadata.IIOMetadataFormatImpl;
064import javax.imageio.metadata.IIOMetadataNode;
065import javax.imageio.stream.ImageInputStream;
066
067import org.w3c.dom.Node;
068
069//
070// Core J2SE problems fixed in this package:
071// 5109146:
072// PNG: Background color initialization from standard metadata is incomplete
073// 5109114:
074// PNG: Cannot set IHDR_bitDepth from standard metadata /Data/BitsPerSample
075// 5106305:
076// PNG standard to native image metadata conversion incorrect for pixel size
077// 5106550:
078// PNG writer merge standard metadata fails for TextEntry sans #IMPLIED
079// attributes
080// 5082756:
081// Image I/O plug-ins set metadata boolean attributes to "true" or "false"
082// 5105068:
083// PNGImageWriter.convertImageMetadata() broken for non-PNGMetadata
084//
085
086/**
087 */
088public class CLibPNGMetadata extends IIOMetadata implements Cloneable {
089
090    // package scope
091    public static final String
092        nativeMetadataFormatName = "javax_imageio_png_1.0";
093
094    protected static final String nativeMetadataFormatClassName 
095        = "com.github.jaiimageio.impl.plugins.png.CLibPNGMetadataFormat";
096
097    // Color types for IHDR chunk
098    public static final String[] IHDR_colorTypeNames = {
099        "Grayscale", null, "RGB", "Palette",
100        "GrayAlpha", null, "RGBAlpha"
101    };
102
103    public static final int[] IHDR_numChannels = {
104        1, 0, 3, 3, 2, 0, 4
105    };
106
107    // Bit depths for IHDR chunk
108    public static final String[] IHDR_bitDepths = {
109        "1", "2", "4", "8", "16"
110    };
111
112    // Compression methods for IHDR chunk
113    public static final String[] IHDR_compressionMethodNames = {
114        "deflate"
115    };
116
117    // Filter methods for IHDR chunk
118    public static final String[] IHDR_filterMethodNames = {
119        "adaptive"
120    };
121
122    // Interlace methods for IHDR chunk
123    public static final String[] IHDR_interlaceMethodNames = {
124        "none", "adam7"
125    };
126
127    // Compression methods for iCCP chunk
128    public static final String[] iCCP_compressionMethodNames = {
129        "deflate"
130    };
131
132    // Compression methods for zTXt chunk
133    public static final String[] zTXt_compressionMethodNames = {
134        "deflate"
135    };
136
137    // "Unknown" unit for pHYs chunk
138    public static final int PHYS_UNIT_UNKNOWN = 0;
139
140    // "Meter" unit for pHYs chunk
141    public static final int PHYS_UNIT_METER = 1;
142
143    // Unit specifiers for pHYs chunk
144    public static final String[] unitSpecifierNames = {
145        "unknown", "meter"
146    };
147
148    // Rendering intents for sRGB chunk
149    public static final String[] renderingIntentNames = {
150        "Perceptual", // 0
151        "Relative colorimetric", // 1
152        "Saturation", // 2
153        "Absolute colorimetric" // 3
154
155    };
156
157    // Color space types for Chroma->ColorSpaceType node
158    public static final String[] colorSpaceTypeNames = {
159        "GRAY", null, "RGB", "RGB",
160        "GRAY", null, "RGB"
161    };
162
163    // BEGIN Definitions required for reading.
164
165    // Critical chunks
166    static final int IHDR_TYPE = chunkType("IHDR");
167    static final int PLTE_TYPE = chunkType("PLTE");
168    static final int IDAT_TYPE = chunkType("IDAT");
169    static final int IEND_TYPE = chunkType("IEND");
170    
171    // Ancillary chunks
172    static final int bKGD_TYPE = chunkType("bKGD");
173    static final int cHRM_TYPE = chunkType("cHRM");
174    static final int gAMA_TYPE = chunkType("gAMA");
175    static final int hIST_TYPE = chunkType("hIST");
176    static final int iCCP_TYPE = chunkType("iCCP");
177    static final int iTXt_TYPE = chunkType("iTXt");
178    static final int pHYs_TYPE = chunkType("pHYs");
179    static final int sBIT_TYPE = chunkType("sBIT");
180    static final int sPLT_TYPE = chunkType("sPLT");
181    static final int sRGB_TYPE = chunkType("sRGB");
182    static final int tEXt_TYPE = chunkType("tEXt");
183    static final int tIME_TYPE = chunkType("tIME");
184    static final int tRNS_TYPE = chunkType("tRNS");
185    static final int zTXt_TYPE = chunkType("zTXt");
186
187    static final int PNG_COLOR_GRAY = 0;
188    static final int PNG_COLOR_RGB = 2;
189    static final int PNG_COLOR_PALETTE = 3;
190    static final int PNG_COLOR_GRAY_ALPHA = 4;
191    static final int PNG_COLOR_RGB_ALPHA = 6;
192
193    // END Definitions required for reading.
194
195    // IHDR chunk
196    public boolean IHDR_present;
197    public int IHDR_width;
198    public int IHDR_height;
199    public int IHDR_bitDepth;
200    public int IHDR_colorType;
201    public int IHDR_compressionMethod;
202    public int IHDR_filterMethod;
203    public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
204
205    // PLTE chunk
206    public boolean PLTE_present;
207    public byte[] PLTE_red;
208    public byte[] PLTE_green;
209    public byte[] PLTE_blue;
210
211    // bKGD chunk
212    // If external (non-PNG sourced) data has red = green = blue,
213    // always store it as gray and promote when writing
214    public boolean bKGD_present;
215    public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
216    public int bKGD_index;
217    public int bKGD_gray;
218    public int bKGD_red;
219    public int bKGD_green;
220    public int bKGD_blue;
221
222    // cHRM chunk
223    public boolean cHRM_present;
224    public int cHRM_whitePointX;
225    public int cHRM_whitePointY;
226    public int cHRM_redX;
227    public int cHRM_redY;
228    public int cHRM_greenX;
229    public int cHRM_greenY;
230    public int cHRM_blueX;
231    public int cHRM_blueY;
232
233    // gAMA chunk
234    public boolean gAMA_present;
235    public int gAMA_gamma;
236
237    // hIST chunk
238    public boolean hIST_present;
239    public char[] hIST_histogram;
240
241    // iCCP chunk
242    public boolean iCCP_present;
243    public String iCCP_profileName;
244    public int iCCP_compressionMethod;
245    public byte[] iCCP_compressedProfile;
246
247    // iTXt chunk
248    public ArrayList iTXt_keyword = new ArrayList(); // Strings
249    public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers
250    public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers
251    public ArrayList iTXt_languageTag = new ArrayList(); // Strings
252    public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings
253    public ArrayList iTXt_text = new ArrayList(); // Strings
254
255    // pHYs chunk
256    public boolean pHYs_present;
257    public int pHYs_pixelsPerUnitXAxis;
258    public int pHYs_pixelsPerUnitYAxis;
259    public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
260
261    // sBIT chunk
262    public boolean sBIT_present;
263    public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
264    public int sBIT_grayBits;
265    public int sBIT_redBits;
266    public int sBIT_greenBits;
267    public int sBIT_blueBits;
268    public int sBIT_alphaBits;
269    
270    // sPLT chunk
271    public boolean sPLT_present;
272    public String sPLT_paletteName; // 1-79 characters
273    public int sPLT_sampleDepth; // 8 or 16
274    public int[] sPLT_red;
275    public int[] sPLT_green;
276    public int[] sPLT_blue;
277    public int[] sPLT_alpha;
278    public int[] sPLT_frequency;
279
280    // sRGB chunk
281    public boolean sRGB_present;
282    public int sRGB_renderingIntent;
283
284    // tEXt chunk
285    public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings
286    public ArrayList tEXt_text = new ArrayList(); // Strings
287
288    // tIME chunk
289    public boolean tIME_present;
290    public int tIME_year;
291    public int tIME_month;
292    public int tIME_day;
293    public int tIME_hour;
294    public int tIME_minute;
295    public int tIME_second;
296
297    // tRNS chunk
298    // If external (non-PNG sourced) data has red = green = blue,
299    // always store it as gray and promote when writing
300    public boolean tRNS_present;
301    public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
302    public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
303    public int tRNS_gray;
304    public int tRNS_red;
305    public int tRNS_green;
306    public int tRNS_blue;
307
308    // zTXt chunk
309    public ArrayList zTXt_keyword = new ArrayList(); // Strings
310    public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers
311    public ArrayList zTXt_text = new ArrayList(); // Strings
312
313    // Unknown chunks
314    public ArrayList unknownChunkType = new ArrayList(); // Strings
315    public ArrayList unknownChunkData = new ArrayList(); // byte arrays
316
317    /**
318     * Converts its parameter to another <code>String</code> which contains
319     * only printable Latin-1 characters but not leading, trailing, or
320     * consecutive spaces.
321     *
322     * @param s the <code>String</code> to convert.
323     * @return a printable Latin-1 <code>String</code> sans superfluous spaces.
324     */
325    static String toPrintableLatin1(String s) {
326        // Pass a null right back.
327        if(s == null) return null;
328
329        // Get Latin-1 characters.
330        byte[] data = null;
331        try {
332            data = s.getBytes("ISO-8859-1");
333        } catch(UnsupportedEncodingException e) {
334            // In theory this should not happen (assert).
335            data = s.getBytes();
336        }
337
338        // Copy printable characters omitting leading spaces and
339        // all but first trailing space.
340        int len = 0;
341        int prev = 0;
342        for (int i = 0; i < data.length; i++) {
343            int d = data[i] & 0xFF;
344            if (prev == 32 && d == 32)
345                continue; 
346            if ((d > 32 && d <=126) || (d >= 161 && d <=255) ||
347                (d == 32 && len != 0))
348                data[len++] = (byte)d;
349            prev = d;
350        }
351
352        // Return an empty string if no acceptable characters.
353        if(len == 0) return "";
354
355        // Omit trailing space, if any.
356        if(data[len - 1] == 32) len--;
357
358        return new String(data, 0, len);
359    }
360
361    public CLibPNGMetadata() {
362        super(true, 
363              nativeMetadataFormatName,
364              nativeMetadataFormatClassName,
365              null, null);
366    }
367    
368    public CLibPNGMetadata(IIOMetadata metadata)
369        throws IIOInvalidTreeException {
370
371        this();
372
373        if(metadata != null) {
374            List formats = Arrays.asList(metadata.getMetadataFormatNames());
375
376            if(formats.contains(nativeMetadataFormatName)) {
377                // Initialize from native image metadata format.
378                String format = nativeMetadataFormatName;
379                setFromTree(format, metadata.getAsTree(format));
380            } else if(metadata.isStandardMetadataFormatSupported()) {
381                // Initialize from standard metadata form of the input tree.
382                String format =
383                    IIOMetadataFormatImpl.standardMetadataFormatName;
384                setFromTree(format, metadata.getAsTree(format));
385            }
386        }
387    }
388
389    /**
390     * Sets the instance variables of the IHDR and if necessary PLTE and
391     * tRNS chunks. The <code>numBands</code> parameter is necessary since
392     * we may only be writing a subset of the image bands.
393     */
394    public void initialize(ImageTypeSpecifier imageType,
395                           int numBands,
396                           ImageWriteParam param,
397                           int interlaceMethod) {
398        ColorModel colorModel = imageType.getColorModel();
399        SampleModel sampleModel = imageType.getSampleModel();
400
401        // Intialize IHDR_width and IHDR_height
402        IHDR_width = sampleModel.getWidth();
403        IHDR_height = sampleModel.getHeight();
404
405        // Initialize IHDR_bitDepth
406        int[] sampleSize = sampleModel.getSampleSize();
407        int bitDepth = sampleSize[0];
408        // Choose max bit depth over all channels
409        // Fixes bug 4413109
410        for (int i = 1; i < sampleSize.length; i++) {
411            if (sampleSize[i] > bitDepth) {
412                bitDepth = sampleSize[i];
413            }
414        }
415        // Multi-channel images must have a bit depth of 8 or 16
416        if (sampleSize.length > 1 && bitDepth < 8) {
417            bitDepth = 8;
418        }
419        
420        // Round bit depth up to a power of 2
421        if (bitDepth > 2 && bitDepth < 4) {
422            bitDepth = 4;
423        } else if (bitDepth > 4 && bitDepth < 8) {
424            bitDepth = 8;
425        } else if (bitDepth > 8 && bitDepth < 16) {
426            bitDepth = 16;
427        } else if (bitDepth > 16) {
428            throw new RuntimeException("bitDepth > 16!");
429        }
430        IHDR_bitDepth = bitDepth;
431
432        // Initialize IHDR_colorType
433        if (colorModel instanceof IndexColorModel) {
434            IndexColorModel icm = (IndexColorModel)colorModel;
435            int size = icm.getMapSize();
436
437            byte[] reds = new byte[size];
438            icm.getReds(reds);
439            byte[] greens = new byte[size];
440            icm.getGreens(greens);
441            byte[] blues = new byte[size];
442            icm.getBlues(blues);
443
444            // Determine whether the color tables are actually a gray ramp
445            // if the color type has not been set previously
446            boolean isGray = false;
447            if (!IHDR_present ||
448                (IHDR_colorType != PNG_COLOR_PALETTE)) {
449                isGray = true;
450                int scale = 255/((1 << IHDR_bitDepth) - 1);
451                for (int i = 0; i < size; i++) {
452                    byte red = reds[i];
453                    if ((red != (byte)(i*scale)) ||
454                        (red != greens[i]) ||
455                        (red != blues[i])) {
456                        isGray = false;
457                        break;
458                    }
459                }
460            }
461
462            // Determine whether transparency exists
463            boolean hasAlpha = colorModel.hasAlpha();
464
465            byte[] alpha = null;
466            if (hasAlpha) {
467                alpha = new byte[size];
468                icm.getAlphas(alpha);
469            }
470
471            if (isGray && hasAlpha) {
472                IHDR_colorType = PNG_COLOR_GRAY_ALPHA;
473            } else if (isGray) {
474                IHDR_colorType = PNG_COLOR_GRAY;
475            } else {
476                IHDR_colorType = PNG_COLOR_PALETTE;
477
478                // Initialize PLTE chunk
479                PLTE_present = true;
480                PLTE_red = (byte[])reds.clone();
481                PLTE_green = (byte[])greens.clone();
482                PLTE_blue = (byte[])blues.clone();
483
484                if (hasAlpha) {
485                    // Initialize tRNS chunk
486                    tRNS_present = true;
487                    tRNS_colorType = PNG_COLOR_PALETTE;
488                    tRNS_alpha = (byte[])alpha.clone();
489                }
490            }
491        } else {
492            if (numBands == 1) {
493                IHDR_colorType = PNG_COLOR_GRAY;
494            } else if (numBands == 2) {
495                IHDR_colorType = PNG_COLOR_GRAY_ALPHA;
496            } else if (numBands == 3) {
497                IHDR_colorType = PNG_COLOR_RGB;
498            } else if (numBands == 4) {
499                IHDR_colorType = PNG_COLOR_RGB_ALPHA;
500            } else {
501                throw new RuntimeException("Number of bands not 1-4!");
502            }
503        }
504
505        // Initialize IHDR_compressionMethod and IHDR_filterMethod
506        IHDR_compressionMethod = IHDR_filterMethod = 0; // Only supported value
507
508        // Initialize IHDR_interlaceMethod
509        if(param != null &&
510           param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) {
511            IHDR_interlaceMethod = 0; // No interlacing.
512        } else if(param != null &&
513                  param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
514            IHDR_interlaceMethod = 1; // Adam7
515        } else {
516            // param == null ||
517            // param.getProgressiveMode() ==
518            // ImageWriteParam.MODE_COPY_FROM_METADATA
519            IHDR_interlaceMethod = interlaceMethod;
520        }
521
522        IHDR_present = true;
523    }
524
525    public boolean isReadOnly() {
526        return false;
527    }
528
529    private ArrayList cloneBytesArrayList(ArrayList in) {
530        if (in == null) {
531            return null;
532        } else {
533            ArrayList list = new ArrayList(in.size());
534            Iterator iter = in.iterator();
535            while (iter.hasNext()) {
536                Object o = iter.next();
537                if (o == null) {
538                    list.add(null);
539                } else {
540                    list.add(((byte[])o).clone());
541                }
542            }
543
544            return list;
545        }
546    }
547
548    // Deep clone
549    public Object clone() {
550        CLibPNGMetadata metadata;
551        try {
552            metadata = (CLibPNGMetadata)super.clone();
553        } catch (CloneNotSupportedException e) {
554            return null;
555        }
556        
557        // unknownChunkData needs deep clone
558        metadata.unknownChunkData =
559            cloneBytesArrayList(this.unknownChunkData);
560
561        return metadata;
562    }
563
564    public Node getAsTree(String formatName) {
565        if (formatName.equals(nativeMetadataFormatName)) {
566            return getNativeTree();
567        } else if (formatName.equals
568                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
569            return getStandardTree();
570        } else {
571            throw new IllegalArgumentException("Not a recognized format!");
572        }
573    }
574
575    private Node getNativeTree() {
576        IIOMetadataNode node = null; // scratch node
577        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
578        
579        // IHDR
580        if (IHDR_present) {
581            IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
582            IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
583            IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
584            IHDR_node.setAttribute("bitDepth",
585                                   Integer.toString(IHDR_bitDepth));
586            IHDR_node.setAttribute("colorType",
587                                   IHDR_colorTypeNames[IHDR_colorType]);
588            // IHDR_compressionMethod must be 0 in PNG 1.1
589            IHDR_node.setAttribute("compressionMethod",
590                          IHDR_compressionMethodNames[IHDR_compressionMethod]);
591            // IHDR_filterMethod must be 0 in PNG 1.1
592            IHDR_node.setAttribute("filterMethod",
593                                    IHDR_filterMethodNames[IHDR_filterMethod]);
594            IHDR_node.setAttribute("interlaceMethod",
595                              IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
596            root.appendChild(IHDR_node);
597        }
598
599        // PLTE
600        if (PLTE_present) {
601            IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
602            int numEntries = PLTE_red.length;
603            for (int i = 0; i < numEntries; i++) {
604                IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
605                entry.setAttribute("index", Integer.toString(i));
606                entry.setAttribute("red",
607                                   Integer.toString(PLTE_red[i] & 0xff));
608                entry.setAttribute("green",
609                                   Integer.toString(PLTE_green[i] & 0xff));
610                entry.setAttribute("blue",
611                                   Integer.toString(PLTE_blue[i] & 0xff));
612                PLTE_node.appendChild(entry);
613            }
614
615            root.appendChild(PLTE_node);
616        }
617
618        // bKGD
619        if (bKGD_present) {
620            IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
621            
622            if (bKGD_colorType == PNG_COLOR_PALETTE) {
623                node = new IIOMetadataNode("bKGD_Palette");
624                node.setAttribute("index", Integer.toString(bKGD_index));
625            } else if (bKGD_colorType == PNG_COLOR_GRAY) {
626                node = new IIOMetadataNode("bKGD_Grayscale");
627                node.setAttribute("gray", Integer.toString(bKGD_gray));
628            } else if (bKGD_colorType == PNG_COLOR_RGB) {
629                node = new IIOMetadataNode("bKGD_RGB");
630                node.setAttribute("red", Integer.toString(bKGD_red));
631                node.setAttribute("green", Integer.toString(bKGD_green));
632                node.setAttribute("blue", Integer.toString(bKGD_blue));
633            }
634            bKGD_node.appendChild(node);
635
636            root.appendChild(bKGD_node);
637        }
638
639        // cHRM
640        if (cHRM_present) {
641            IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
642            cHRM_node.setAttribute("whitePointX",
643                              Integer.toString(cHRM_whitePointX));
644            cHRM_node.setAttribute("whitePointY",
645                              Integer.toString(cHRM_whitePointY));
646            cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
647            cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
648            cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
649            cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
650            cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
651            cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
652
653            root.appendChild(cHRM_node);
654        }
655
656        // gAMA
657        if (gAMA_present) {
658            IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
659            gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
660
661            root.appendChild(gAMA_node);
662        }
663
664        // hIST
665        if (hIST_present) {
666            IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
667
668            for (int i = 0; i < hIST_histogram.length; i++) {
669                IIOMetadataNode hist =
670                    new IIOMetadataNode("hISTEntry");
671                hist.setAttribute("index", Integer.toString(i));
672                hist.setAttribute("value",
673                                  Integer.toString(hIST_histogram[i]));
674                hIST_node.appendChild(hist);
675            }
676
677            root.appendChild(hIST_node);
678        }
679
680        // iCCP
681        if (iCCP_present) {
682            IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
683            iCCP_node.setAttribute("profileName", iCCP_profileName);
684            iCCP_node.setAttribute("compressionMethod",
685                          iCCP_compressionMethodNames[iCCP_compressionMethod]);
686
687            Object profile = iCCP_compressedProfile;
688            if (profile != null) {
689                profile = ((byte[])profile).clone();
690            }
691            iCCP_node.setUserObject(profile);
692
693            root.appendChild(iCCP_node);
694        }
695
696        // iTXt
697        if (iTXt_keyword.size() > 0) {
698            IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
699            for (int i = 0; i < iTXt_keyword.size(); i++) {
700                Integer val;
701                
702                IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
703                iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i));
704                val = (Integer)iTXt_compressionFlag.get(i);
705                iTXt_node.setAttribute("compressionFlag", val.toString());
706                val = (Integer)iTXt_compressionMethod.get(i);
707                iTXt_node.setAttribute("compressionMethod", val.toString());
708                iTXt_node.setAttribute("languageTag",
709                                       (String)iTXt_languageTag.get(i));
710                iTXt_node.setAttribute("translatedKeyword",
711                                       (String)iTXt_translatedKeyword.get(i));
712                iTXt_node.setAttribute("text", (String)iTXt_text.get(i));
713                
714                iTXt_parent.appendChild(iTXt_node);
715            }
716            
717            root.appendChild(iTXt_parent);
718        }
719
720        // pHYs
721        if (pHYs_present) {
722            IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
723            pHYs_node.setAttribute("pixelsPerUnitXAxis",
724                              Integer.toString(pHYs_pixelsPerUnitXAxis));
725            pHYs_node.setAttribute("pixelsPerUnitYAxis",
726                                   Integer.toString(pHYs_pixelsPerUnitYAxis));
727            pHYs_node.setAttribute("unitSpecifier",
728                                   unitSpecifierNames[pHYs_unitSpecifier]);
729
730            root.appendChild(pHYs_node);
731        }
732
733        // sBIT
734        if (sBIT_present) {
735            IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
736
737            if (sBIT_colorType == PNG_COLOR_GRAY) {
738                node = new IIOMetadataNode("sBIT_Grayscale");
739                node.setAttribute("gray",
740                                  Integer.toString(sBIT_grayBits));
741            } else if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA) {
742                node = new IIOMetadataNode("sBIT_GrayAlpha");
743                node.setAttribute("gray",
744                                  Integer.toString(sBIT_grayBits));
745                node.setAttribute("alpha",
746                                  Integer.toString(sBIT_alphaBits));
747            } else if (sBIT_colorType == PNG_COLOR_RGB) {
748                node = new IIOMetadataNode("sBIT_RGB");
749                node.setAttribute("red",
750                                  Integer.toString(sBIT_redBits));
751                node.setAttribute("green",
752                                  Integer.toString(sBIT_greenBits));
753                node.setAttribute("blue",
754                                  Integer.toString(sBIT_blueBits));
755            } else if (sBIT_colorType == PNG_COLOR_RGB_ALPHA) {
756                node = new IIOMetadataNode("sBIT_RGBAlpha");
757                node.setAttribute("red",
758                                  Integer.toString(sBIT_redBits));
759                node.setAttribute("green",
760                                  Integer.toString(sBIT_greenBits));
761                node.setAttribute("blue",
762                                  Integer.toString(sBIT_blueBits));
763                node.setAttribute("alpha",
764                                  Integer.toString(sBIT_alphaBits));
765            } else if (sBIT_colorType == PNG_COLOR_PALETTE) {
766                node = new IIOMetadataNode("sBIT_Palette");
767                node.setAttribute("red",
768                                  Integer.toString(sBIT_redBits));
769                node.setAttribute("green",
770                                  Integer.toString(sBIT_greenBits));
771                node.setAttribute("blue",
772                                  Integer.toString(sBIT_blueBits));
773            }
774            sBIT_node.appendChild(node);
775                
776            root.appendChild(sBIT_node);
777        }
778
779        // sPLT
780        if (sPLT_present) {
781            IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
782
783            sPLT_node.setAttribute("name", sPLT_paletteName);
784            sPLT_node.setAttribute("sampleDepth",
785                                   Integer.toString(sPLT_sampleDepth));
786
787            int numEntries = sPLT_red.length;
788            for (int i = 0; i < numEntries; i++) {
789                IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
790                entry.setAttribute("index", Integer.toString(i));
791                entry.setAttribute("red", Integer.toString(sPLT_red[i]));
792                entry.setAttribute("green", Integer.toString(sPLT_green[i]));
793                entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
794                entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
795                entry.setAttribute("frequency",
796                                  Integer.toString(sPLT_frequency[i]));
797                sPLT_node.appendChild(entry);
798            }
799
800            root.appendChild(sPLT_node);
801        }
802
803        // sRGB
804        if (sRGB_present) {
805            IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
806            sRGB_node.setAttribute("renderingIntent",
807                                   renderingIntentNames[sRGB_renderingIntent]);
808
809            root.appendChild(sRGB_node);
810        }
811
812        // tEXt
813        if (tEXt_keyword.size() > 0) {
814            IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
815            for (int i = 0; i < tEXt_keyword.size(); i++) {
816                IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
817                tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i));
818                tEXt_node.setAttribute("value" , (String)tEXt_text.get(i));
819                
820                tEXt_parent.appendChild(tEXt_node);
821            }
822                
823            root.appendChild(tEXt_parent);
824        }
825
826        // tIME
827        if (tIME_present) {
828            IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
829            tIME_node.setAttribute("year", Integer.toString(tIME_year));
830            tIME_node.setAttribute("month", Integer.toString(tIME_month));
831            tIME_node.setAttribute("day", Integer.toString(tIME_day));
832            tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
833            tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
834            tIME_node.setAttribute("second", Integer.toString(tIME_second));
835
836            root.appendChild(tIME_node);
837        }
838
839        // tRNS
840        if (tRNS_present) {
841            IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
842
843            if (tRNS_colorType == PNG_COLOR_PALETTE) {
844                node = new IIOMetadataNode("tRNS_Palette");
845                
846                for (int i = 0; i < tRNS_alpha.length; i++) {
847                    IIOMetadataNode entry =
848                        new IIOMetadataNode("tRNS_PaletteEntry");
849                    entry.setAttribute("index", Integer.toString(i));
850                    entry.setAttribute("alpha",
851                                       Integer.toString(tRNS_alpha[i] & 0xff));
852                    node.appendChild(entry);
853                }
854            } else if (tRNS_colorType == PNG_COLOR_GRAY) {
855                node = new IIOMetadataNode("tRNS_Grayscale");
856                node.setAttribute("gray", Integer.toString(tRNS_gray));
857            } else if (tRNS_colorType == PNG_COLOR_RGB) {
858                node = new IIOMetadataNode("tRNS_RGB");
859                node.setAttribute("red", Integer.toString(tRNS_red));
860                node.setAttribute("green", Integer.toString(tRNS_green));
861                node.setAttribute("blue", Integer.toString(tRNS_blue));
862            }
863            tRNS_node.appendChild(node);
864            
865            root.appendChild(tRNS_node);
866        }
867
868        // zTXt
869        if (zTXt_keyword.size() > 0) {
870            IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
871            for (int i = 0; i < zTXt_keyword.size(); i++) {
872                IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
873                zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i));
874
875                int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue();
876                zTXt_node.setAttribute("compressionMethod",
877                                       zTXt_compressionMethodNames[cm]);
878
879                zTXt_node.setAttribute("text", (String)zTXt_text.get(i));
880
881                zTXt_parent.appendChild(zTXt_node);
882            }
883
884            root.appendChild(zTXt_parent);
885        }
886        
887        // Unknown chunks
888        if (unknownChunkType.size() > 0) {
889            IIOMetadataNode unknown_parent =
890                new IIOMetadataNode("UnknownChunks");
891            for (int i = 0; i < unknownChunkType.size(); i++) {
892                IIOMetadataNode unknown_node =
893                    new IIOMetadataNode("UnknownChunk");
894                unknown_node.setAttribute("type",
895                                          (String)unknownChunkType.get(i));
896                unknown_node.setUserObject((byte[])unknownChunkData.get(i));
897                
898                unknown_parent.appendChild(unknown_node);
899            }
900            
901            root.appendChild(unknown_parent);
902        }
903
904        return root;
905    }
906
907    private int getNumChannels() {
908        // Determine number of channels
909        // Be careful about palette color with transparency
910        int numChannels = IHDR_numChannels[IHDR_colorType];
911        if (IHDR_colorType == PNG_COLOR_PALETTE &&
912            tRNS_present && tRNS_colorType == IHDR_colorType) {
913            numChannels = 4;
914        }
915        return numChannels;
916    }
917
918    public IIOMetadataNode getStandardChromaNode() {
919        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
920        IIOMetadataNode node = null; // scratch node
921
922        node = new IIOMetadataNode("ColorSpaceType");
923        node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
924        chroma_node.appendChild(node);
925
926        node = new IIOMetadataNode("NumChannels");
927        node.setAttribute("value", Integer.toString(getNumChannels()));
928        chroma_node.appendChild(node);
929
930        if (gAMA_present) {
931            node = new IIOMetadataNode("Gamma");
932            node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
933            chroma_node.appendChild(node);
934        }
935
936        node = new IIOMetadataNode("BlackIsZero");
937        node.setAttribute("value", "TRUE");
938        chroma_node.appendChild(node);
939
940        if (PLTE_present) {
941            boolean hasAlpha = tRNS_present &&
942                (tRNS_colorType == PNG_COLOR_PALETTE);
943
944            node = new IIOMetadataNode("Palette");
945            for (int i = 0; i < PLTE_red.length; i++) {
946                IIOMetadataNode entry =
947                    new IIOMetadataNode("PaletteEntry");
948                entry.setAttribute("index", Integer.toString(i));
949                entry.setAttribute("red",
950                                   Integer.toString(PLTE_red[i] & 0xff));
951                entry.setAttribute("green",
952                                   Integer.toString(PLTE_green[i] & 0xff));
953                entry.setAttribute("blue",
954                                   Integer.toString(PLTE_blue[i] & 0xff));
955                if (hasAlpha) {
956                    int alpha = (i < tRNS_alpha.length) ?
957                        (tRNS_alpha[i] & 0xff) : 255;
958                    entry.setAttribute("alpha", Integer.toString(alpha));
959                }
960                node.appendChild(entry);
961            }
962            chroma_node.appendChild(node);
963        }
964
965        if (bKGD_present) {
966            if (bKGD_colorType == PNG_COLOR_PALETTE) {
967                node = new IIOMetadataNode("BackgroundIndex");
968                node.setAttribute("value", Integer.toString(bKGD_index));
969            } else {
970                node = new IIOMetadataNode("BackgroundColor");
971                int r, g, b;
972
973                if (bKGD_colorType == PNG_COLOR_GRAY) {
974                    r = g = b = bKGD_gray;
975                } else {
976                    r = bKGD_red;
977                    g = bKGD_green;
978                    b = bKGD_blue;
979                }
980                node.setAttribute("red", Integer.toString(r));
981                node.setAttribute("green", Integer.toString(g));
982                node.setAttribute("blue", Integer.toString(b));
983            }
984            chroma_node.appendChild(node);
985        }
986
987        return chroma_node;
988    }
989
990    public IIOMetadataNode getStandardCompressionNode() {
991        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
992        IIOMetadataNode node = null; // scratch node
993
994        node = new IIOMetadataNode("CompressionTypeName");
995        node.setAttribute("value", "deflate");
996        compression_node.appendChild(node);
997
998        node = new IIOMetadataNode("Lossless");
999        node.setAttribute("value", "TRUE");
1000        compression_node.appendChild(node);
1001
1002        node = new IIOMetadataNode("NumProgressiveScans");
1003        node.setAttribute("value",
1004                          (IHDR_interlaceMethod == 0) ? "1" : "7");
1005        compression_node.appendChild(node);
1006
1007        return compression_node;
1008    }
1009
1010    private String repeat(String s, int times) {
1011        if (times == 1) {
1012            return s;
1013        }
1014        StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
1015        sb.append(s);
1016        for (int i = 1; i < times; i++) {
1017            sb.append(" ");
1018            sb.append(s);
1019        }
1020        return sb.toString();
1021    }
1022
1023    public IIOMetadataNode getStandardDataNode() {
1024        IIOMetadataNode data_node = new IIOMetadataNode("Data");
1025        IIOMetadataNode node = null; // scratch node
1026
1027        node = new IIOMetadataNode("PlanarConfiguration");
1028        node.setAttribute("value", "PixelInterleaved");
1029        data_node.appendChild(node);
1030
1031        node = new IIOMetadataNode("SampleFormat");
1032        node.setAttribute("value",
1033                          IHDR_colorType == PNG_COLOR_PALETTE ?
1034                          "Index" : "UnsignedIntegral");
1035        data_node.appendChild(node);
1036
1037        String bitDepth = Integer.toString(IHDR_bitDepth);
1038        node = new IIOMetadataNode("BitsPerSample");
1039        node.setAttribute("value", repeat(bitDepth, getNumChannels()));
1040        data_node.appendChild(node);
1041
1042        if (sBIT_present) {
1043            node = new IIOMetadataNode("SignificantBitsPerSample");
1044            String sbits;
1045            if (sBIT_colorType == PNG_COLOR_GRAY ||
1046                sBIT_colorType == PNG_COLOR_GRAY_ALPHA) {
1047                sbits = Integer.toString(sBIT_grayBits);
1048            } else { // sBIT_colorType == PNG_COLOR_RGB ||
1049                     // sBIT_colorType == PNG_COLOR_RGB_ALPHA
1050                sbits = Integer.toString(sBIT_redBits) + " " + 
1051                    Integer.toString(sBIT_greenBits) + " " + 
1052                    Integer.toString(sBIT_blueBits);
1053            }
1054
1055            if (sBIT_colorType == PNG_COLOR_GRAY_ALPHA ||
1056                sBIT_colorType == PNG_COLOR_RGB_ALPHA) {
1057                sbits += " " + Integer.toString(sBIT_alphaBits);
1058            }
1059            
1060            node.setAttribute("value", sbits);
1061            data_node.appendChild(node);
1062        }
1063
1064        // SampleMSB
1065
1066        return data_node;
1067    }
1068
1069    public IIOMetadataNode getStandardDimensionNode() {
1070        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
1071        IIOMetadataNode node = null; // scratch node
1072
1073        node = new IIOMetadataNode("PixelAspectRatio");
1074        // aspect ratio is pixel width/height which is the ratio of the
1075        // inverses of pixels per unit length.
1076        float ratio = pHYs_present ?
1077            (float)pHYs_pixelsPerUnitYAxis/pHYs_pixelsPerUnitXAxis : 1.0F;
1078        node.setAttribute("value", Float.toString(ratio));
1079        dimension_node.appendChild(node);
1080        
1081        node = new IIOMetadataNode("ImageOrientation");
1082        node.setAttribute("value", "Normal");
1083        dimension_node.appendChild(node);
1084        
1085        if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
1086            node = new IIOMetadataNode("HorizontalPixelSize");
1087            node.setAttribute("value",
1088                              Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis));
1089            dimension_node.appendChild(node);
1090
1091            node = new IIOMetadataNode("VerticalPixelSize");
1092            node.setAttribute("value",
1093                              Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis));
1094            dimension_node.appendChild(node);
1095        }
1096
1097        return dimension_node;
1098    }
1099
1100    public IIOMetadataNode getStandardDocumentNode() {
1101        if (!tIME_present) {
1102            return null;
1103        }
1104
1105        IIOMetadataNode document_node = new IIOMetadataNode("Document");
1106        IIOMetadataNode node = null; // scratch node
1107
1108        node = new IIOMetadataNode("ImageModificationTime");
1109        node.setAttribute("year", Integer.toString(tIME_year));
1110        node.setAttribute("month", Integer.toString(tIME_month));
1111        node.setAttribute("day", Integer.toString(tIME_day));
1112        node.setAttribute("hour", Integer.toString(tIME_hour));
1113        node.setAttribute("minute", Integer.toString(tIME_minute));
1114        node.setAttribute("second", Integer.toString(tIME_second));
1115        document_node.appendChild(node);
1116
1117        return document_node;
1118    }
1119
1120    public IIOMetadataNode getStandardTextNode() {
1121        int numEntries = tEXt_keyword.size() +
1122            iTXt_keyword.size() + zTXt_keyword.size();
1123        if (numEntries == 0) {
1124            return null;
1125        }
1126
1127        IIOMetadataNode text_node = new IIOMetadataNode("Text");
1128        IIOMetadataNode node = null; // scratch node
1129
1130        for (int i = 0; i < tEXt_keyword.size(); i++) {
1131            node = new IIOMetadataNode("TextEntry");
1132            node.setAttribute("keyword", (String)tEXt_keyword.get(i));
1133            node.setAttribute("value", (String)tEXt_text.get(i));
1134            node.setAttribute("encoding", "ISO-8859-1");
1135            node.setAttribute("compression", "none");
1136            
1137            text_node.appendChild(node);
1138        }
1139
1140        for (int i = 0; i < iTXt_keyword.size(); i++) {
1141            node = new IIOMetadataNode("TextEntry");
1142            node.setAttribute("keyword", (String)iTXt_keyword.get(i));
1143            node.setAttribute("value", (String)iTXt_text.get(i));
1144            node.setAttribute("language",
1145                              (String)iTXt_languageTag.get(i));
1146            if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) {
1147                node.setAttribute("compression", "deflate");
1148            } else {
1149                node.setAttribute("compression", "none");
1150            }
1151            
1152            text_node.appendChild(node);
1153        }
1154
1155        for (int i = 0; i < zTXt_keyword.size(); i++) {
1156            node = new IIOMetadataNode("TextEntry");
1157            node.setAttribute("keyword", (String)zTXt_keyword.get(i));
1158            node.setAttribute("value", (String)zTXt_text.get(i));
1159            node.setAttribute("compression", "deflate");
1160            
1161            text_node.appendChild(node);
1162        }
1163
1164        return text_node;
1165    }
1166
1167    public IIOMetadataNode getStandardTransparencyNode() {
1168        IIOMetadataNode transparency_node =
1169            new IIOMetadataNode("Transparency");
1170        IIOMetadataNode node = null; // scratch node
1171
1172        node = new IIOMetadataNode("Alpha");
1173        boolean hasAlpha = 
1174            (IHDR_colorType == PNG_COLOR_RGB_ALPHA) ||
1175            (IHDR_colorType == PNG_COLOR_GRAY_ALPHA) ||
1176            (IHDR_colorType == PNG_COLOR_PALETTE &&
1177             tRNS_present &&
1178             (tRNS_colorType == IHDR_colorType) &&
1179             (tRNS_alpha != null));
1180        node.setAttribute("value", hasAlpha ? "nonpremultiplied" : "none"); 
1181        transparency_node.appendChild(node);
1182
1183        if (tRNS_present) {
1184            if(tRNS_colorType == PNG_COLOR_RGB ||
1185               tRNS_colorType == PNG_COLOR_GRAY) {
1186                node = new IIOMetadataNode("TransparentColor");
1187                if (tRNS_colorType == PNG_COLOR_RGB) {
1188                    node.setAttribute("value",
1189                                      Integer.toString(tRNS_red) + " " +
1190                                      Integer.toString(tRNS_green) + " " +
1191                                      Integer.toString(tRNS_blue));
1192                } else if (tRNS_colorType == PNG_COLOR_GRAY) {
1193                    node.setAttribute("value", Integer.toString(tRNS_gray));
1194                }
1195                transparency_node.appendChild(node);
1196            }
1197        }
1198
1199        return transparency_node;
1200    }
1201
1202    // Shorthand for throwing an IIOInvalidTreeException
1203    private void fatal(Node node, String reason)
1204        throws IIOInvalidTreeException {
1205        throw new IIOInvalidTreeException(reason, node);
1206    }
1207
1208    // Get an integer-valued attribute
1209    private int getIntAttribute(Node node, String name,
1210                                int defaultValue, boolean required)
1211        throws IIOInvalidTreeException {
1212        String value = getAttribute(node, name, null, required);
1213        if (value == null) {
1214            return defaultValue;
1215        }
1216        return Integer.parseInt(value);
1217    }
1218
1219    // Get a float-valued attribute
1220    private float getFloatAttribute(Node node, String name,
1221                                    float defaultValue, boolean required)
1222        throws IIOInvalidTreeException {
1223        String value = getAttribute(node, name, null, required);
1224        if (value == null) {
1225            return defaultValue;
1226        }
1227        return Float.parseFloat(value);
1228    }
1229
1230    // Get a required integer-valued attribute
1231    private int getIntAttribute(Node node, String name)
1232        throws IIOInvalidTreeException {
1233        return getIntAttribute(node, name, -1, true);
1234    }
1235
1236    // Get a required float-valued attribute
1237    private float getFloatAttribute(Node node, String name)
1238        throws IIOInvalidTreeException {
1239        return getFloatAttribute(node, name, -1.0F, true);
1240    }
1241
1242    // Get a boolean-valued attribute
1243    private boolean getBooleanAttribute(Node node, String name,
1244                                        boolean defaultValue, 
1245                                        boolean required)
1246        throws IIOInvalidTreeException {
1247        Node attr = node.getAttributes().getNamedItem(name);
1248        if (attr == null) {
1249            if (!required) {
1250                return defaultValue;
1251            } else {
1252                fatal(node, "Required attribute " + name + " not present!");
1253            }
1254        }
1255
1256        String value = attr.getNodeValue();
1257
1258        if (value.equalsIgnoreCase("true")) {
1259            return true;
1260        } else if (value.equalsIgnoreCase("false")) {
1261            return false;
1262        } else {
1263            fatal(node, "Attribute " + name + " must be 'true' or 'false'!");
1264            return false;
1265        }
1266    }
1267
1268    // Get a required boolean-valued attribute
1269    private boolean getBooleanAttribute(Node node, String name)
1270        throws IIOInvalidTreeException {
1271        return getBooleanAttribute(node, name, false, true);
1272    }
1273
1274    // Get an enumerated attribute as an index into a String array
1275    private int getEnumeratedAttribute(Node node,
1276                                       String name, String[] legalNames,
1277                                       int defaultValue, boolean required)
1278        throws IIOInvalidTreeException {
1279        Node attr = node.getAttributes().getNamedItem(name);
1280        if (attr == null) {
1281            if (!required) {
1282                return defaultValue;
1283            } else {
1284                fatal(node, "Required attribute " + name + " not present!");
1285            }
1286        }
1287
1288        String value = attr.getNodeValue();
1289
1290        for (int i = 0; i < legalNames.length; i++) {
1291            if (value.equals(legalNames[i])) {
1292                return i;
1293            }
1294        }
1295
1296        fatal(node, "Illegal value for attribute " + name + "!");
1297        return -1;
1298    }
1299
1300    // Get a required enumerated attribute as an index into a String array
1301    private int getEnumeratedAttribute(Node node,
1302                                       String name, String[] legalNames)
1303        throws IIOInvalidTreeException {
1304        return getEnumeratedAttribute(node, name, legalNames, -1, true);
1305    }
1306
1307    // Get a String-valued attribute
1308    private String getAttribute(Node node, String name,
1309                                String defaultValue, boolean required)
1310        throws IIOInvalidTreeException {
1311        Node attr = node.getAttributes().getNamedItem(name);
1312        if (attr == null) {
1313            if (!required) {
1314                return defaultValue;
1315            } else {
1316                fatal(node, "Required attribute " + name + " not present!");
1317            }
1318        }
1319        return attr.getNodeValue();
1320    }
1321
1322    // Get a required String-valued attribute
1323    private String getAttribute(Node node, String name)
1324        throws IIOInvalidTreeException {
1325            return getAttribute(node, name, null, true);
1326    }
1327
1328    public void mergeTree(String formatName, Node root)
1329        throws IIOInvalidTreeException {
1330        if (formatName.equals(nativeMetadataFormatName)) {
1331            if (root == null) {
1332                throw new IllegalArgumentException("root == null!");
1333            }
1334            mergeNativeTree(root);
1335        } else if (formatName.equals
1336                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
1337            if (root == null) {
1338                throw new IllegalArgumentException("root == null!");
1339            }
1340            mergeStandardTree(root);
1341        } else {
1342            throw new IllegalArgumentException("Not a recognized format!");
1343        }
1344    }
1345
1346    private void mergeNativeTree(Node root)
1347        throws IIOInvalidTreeException {
1348        Node node = root;
1349        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
1350            fatal(node, "Root must be " + nativeMetadataFormatName);
1351        }
1352        
1353        node = node.getFirstChild();
1354        while (node != null) {
1355            String name = node.getNodeName();
1356            
1357            if (name.equals("IHDR")) {
1358                IHDR_width = getIntAttribute(node, "width");
1359                IHDR_height = getIntAttribute(node, "height");
1360                IHDR_bitDepth = getEnumeratedAttribute(node, "bitDepth",
1361                                                       IHDR_bitDepths);
1362                IHDR_colorType = getEnumeratedAttribute(node, "colorType",
1363                                                        IHDR_colorTypeNames);
1364                IHDR_compressionMethod =
1365                    getEnumeratedAttribute(node, "compressionMethod",
1366                                           IHDR_compressionMethodNames);
1367                IHDR_filterMethod =
1368                    getEnumeratedAttribute(node,
1369                                           "filterMethod",
1370                                           IHDR_filterMethodNames);
1371                IHDR_interlaceMethod =
1372                    getEnumeratedAttribute(node, "interlaceMethod",
1373                                           IHDR_interlaceMethodNames);
1374                IHDR_present = true;
1375            } else if (name.equals("PLTE")) {
1376                byte[] red = new byte[256];
1377                byte[] green  = new byte[256];
1378                byte[] blue = new byte[256];
1379                int maxindex = -1;
1380                
1381                Node PLTE_entry = node.getFirstChild();
1382                if (PLTE_entry == null) {
1383                    fatal(node, "Palette has no entries!");
1384                }
1385
1386                while (PLTE_entry != null) {
1387                    if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
1388                        fatal(node,
1389                              "Only a PLTEEntry may be a child of a PLTE!");
1390                    }
1391                    
1392                    int index = getIntAttribute(PLTE_entry, "index");
1393                    if (index < 0 || index > 255) {
1394                        fatal(node,
1395                              "Bad value for PLTEEntry attribute index!");
1396                    }
1397                    if (index > maxindex) {
1398                        maxindex = index;
1399                    }
1400                    red[index] =
1401                        (byte)getIntAttribute(PLTE_entry, "red");
1402                    green[index] =
1403                        (byte)getIntAttribute(PLTE_entry, "green");
1404                    blue[index] =
1405                        (byte)getIntAttribute(PLTE_entry, "blue");
1406                    
1407                    PLTE_entry = PLTE_entry.getNextSibling();
1408                }
1409                
1410                int numEntries = maxindex + 1;
1411                PLTE_red = new byte[numEntries];
1412                PLTE_green = new byte[numEntries];
1413                PLTE_blue = new byte[numEntries];
1414                System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1415                System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1416                System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1417                PLTE_present = true;
1418            } else if (name.equals("bKGD")) {
1419                bKGD_present = false; // Guard against partial overwrite
1420                Node bKGD_node = node.getFirstChild();
1421                if (bKGD_node == null) {
1422                    fatal(node, "bKGD node has no children!");
1423                }
1424                String bKGD_name = bKGD_node.getNodeName();
1425                if (bKGD_name.equals("bKGD_Palette")) {
1426                    bKGD_index = getIntAttribute(bKGD_node, "index");
1427                    bKGD_colorType = PNG_COLOR_PALETTE;
1428                } else if (bKGD_name.equals("bKGD_Grayscale")) {
1429                    bKGD_gray = getIntAttribute(bKGD_node, "gray");
1430                    bKGD_colorType = PNG_COLOR_GRAY;
1431                } else if (bKGD_name.equals("bKGD_RGB")) {
1432                    bKGD_red = getIntAttribute(bKGD_node, "red");
1433                    bKGD_green = getIntAttribute(bKGD_node, "green");
1434                    bKGD_blue = getIntAttribute(bKGD_node, "blue");
1435                    bKGD_colorType = PNG_COLOR_RGB;
1436                } else {
1437                    fatal(node, "Bad child of a bKGD node!");
1438                }
1439                if (bKGD_node.getNextSibling() != null) {
1440                    fatal(node, "bKGD node has more than one child!");
1441                }
1442
1443                bKGD_present = true;
1444            } else if (name.equals("cHRM")) {
1445                cHRM_whitePointX = getIntAttribute(node, "whitePointX");
1446                cHRM_whitePointY = getIntAttribute(node, "whitePointY");
1447                cHRM_redX = getIntAttribute(node, "redX");
1448                cHRM_redY = getIntAttribute(node, "redY");
1449                cHRM_greenX = getIntAttribute(node, "greenX");
1450                cHRM_greenY = getIntAttribute(node, "greenY");
1451                cHRM_blueX = getIntAttribute(node, "blueX");
1452                cHRM_blueY = getIntAttribute(node, "blueY");
1453                
1454                cHRM_present = true;
1455            } else if (name.equals("gAMA")) {
1456                gAMA_gamma = getIntAttribute(node, "value");
1457                gAMA_present = true;
1458            } else if (name.equals("hIST")) {
1459                char[] hist = new char[256];
1460                int maxindex = -1;
1461                
1462                Node hIST_entry = node.getFirstChild();
1463                if (hIST_entry == null) {
1464                    fatal(node, "hIST node has no children!");
1465                }
1466
1467                while (hIST_entry != null) {
1468                    if (!hIST_entry.getNodeName().equals("hISTEntry")) {
1469                        fatal(node,
1470                              "Only a hISTEntry may be a child of a hIST!");
1471                    }
1472                    
1473                    int index = getIntAttribute(hIST_entry, "index");
1474                    if (index < 0 || index > 255) {
1475                        fatal(node,
1476                              "Bad value for histEntry attribute index!");
1477                    }
1478                    if (index > maxindex) {
1479                        maxindex = index;
1480                    }
1481                    hist[index] =
1482                        (char)getIntAttribute(hIST_entry, "value");
1483                    
1484                    hIST_entry = hIST_entry.getNextSibling();
1485                }
1486                
1487                int numEntries = maxindex + 1;
1488                hIST_histogram = new char[numEntries];
1489                System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
1490                
1491                hIST_present = true;
1492            } else if (name.equals("iCCP")) {
1493                iCCP_profileName =
1494                    toPrintableLatin1(getAttribute(node, "profileName"));
1495                iCCP_compressionMethod =
1496                    getEnumeratedAttribute(node, "compressionMethod",
1497                                           iCCP_compressionMethodNames);
1498                Object compressedProfile =
1499                    ((IIOMetadataNode)node).getUserObject();
1500                if (compressedProfile == null) {
1501                    fatal(node, "No ICCP profile present in user object!");
1502                }
1503                if (!(compressedProfile instanceof byte[])) {
1504                    fatal(node, "User object not a byte array!");
1505                }
1506                
1507                iCCP_compressedProfile =
1508                    (byte[])((byte[])compressedProfile).clone();
1509                
1510                iCCP_present = true;
1511            } else if (name.equals("iTXt")) {
1512                Node iTXt_node = node.getFirstChild();
1513                while (iTXt_node != null) {
1514                    if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
1515                        fatal(node,
1516                              "Only an iTXtEntry may be a child of an iTXt!");
1517                    }
1518                    
1519                    String keyword =
1520                        toPrintableLatin1(getAttribute(iTXt_node, "keyword"));
1521                    iTXt_keyword.add(keyword);
1522                    
1523                    boolean compressionFlag =
1524                        getBooleanAttribute(iTXt_node, "compressionFlag");
1525                    iTXt_compressionFlag.add(new Boolean(compressionFlag));
1526                    
1527                    String compressionMethod =
1528                        getAttribute(iTXt_node, "compressionMethod");
1529                    iTXt_compressionMethod.add(compressionMethod);
1530                    
1531                    String languageTag =
1532                        getAttribute(iTXt_node, "languageTag");
1533                    iTXt_languageTag.add(languageTag); 
1534                    
1535                    String translatedKeyword =
1536                        getAttribute(iTXt_node, "translatedKeyword");
1537                    iTXt_translatedKeyword.add(translatedKeyword);
1538                    
1539                    String text = getAttribute(iTXt_node, "text");
1540                    iTXt_text.add(text);
1541                    
1542                    iTXt_node = iTXt_node.getNextSibling();
1543                }
1544            } else if (name.equals("pHYs")) {
1545                pHYs_pixelsPerUnitXAxis =
1546                    getIntAttribute(node, "pixelsPerUnitXAxis");
1547                pHYs_pixelsPerUnitYAxis =
1548                    getIntAttribute(node, "pixelsPerUnitYAxis");
1549                pHYs_unitSpecifier =
1550                    getEnumeratedAttribute(node, "unitSpecifier",
1551                                           unitSpecifierNames);
1552                
1553                pHYs_present = true;
1554            } else if (name.equals("sBIT")) {
1555                sBIT_present = false; // Guard against partial overwrite
1556                Node sBIT_node = node.getFirstChild();
1557                if (sBIT_node == null) {
1558                    fatal(node, "sBIT node has no children!");
1559                }
1560                String sBIT_name = sBIT_node.getNodeName();
1561                if (sBIT_name.equals("sBIT_Grayscale")) {
1562                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1563                    sBIT_colorType = PNG_COLOR_GRAY;
1564                } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
1565                    sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1566                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1567                    sBIT_colorType = PNG_COLOR_GRAY_ALPHA;
1568                } else if (sBIT_name.equals("sBIT_RGB")) {
1569                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1570                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1571                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1572                    sBIT_colorType = PNG_COLOR_RGB;
1573                } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
1574                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1575                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1576                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1577                    sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1578                    sBIT_colorType = PNG_COLOR_RGB_ALPHA;
1579                } else if (sBIT_name.equals("sBIT_Palette")) {
1580                    sBIT_redBits = getIntAttribute(sBIT_node, "red");
1581                    sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1582                    sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1583                    sBIT_colorType = PNG_COLOR_PALETTE;
1584                } else {
1585                    fatal(node, "Bad child of an sBIT node!");
1586                }
1587                if (sBIT_node.getNextSibling() != null) {
1588                    fatal(node, "sBIT node has more than one child!");
1589                }
1590
1591                sBIT_present = true;
1592            } else if (name.equals("sPLT")) {
1593                sPLT_paletteName =
1594                    toPrintableLatin1(getAttribute(node, "name"));
1595                sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
1596                
1597                int[] red = new int[256];
1598                int[] green  = new int[256];
1599                int[] blue = new int[256];
1600                int[] alpha = new int[256];
1601                int[] frequency = new int[256];
1602                int maxindex = -1;
1603                
1604                Node sPLT_entry = node.getFirstChild();
1605                if (sPLT_entry == null) {
1606                    fatal(node, "sPLT node has no children!");
1607                }
1608
1609                while (sPLT_entry != null) {
1610                    if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
1611                        fatal(node,
1612                              "Only an sPLTEntry may be a child of an sPLT!");
1613                    }
1614                    
1615                    int index = getIntAttribute(sPLT_entry, "index");
1616                    if (index < 0 || index > 255) {
1617                        fatal(node,
1618                              "Bad value for PLTEEntry attribute index!");
1619                    }
1620                    if (index > maxindex) {
1621                        maxindex = index;
1622                    }
1623                    red[index] = getIntAttribute(sPLT_entry, "red");
1624                    green[index] = getIntAttribute(sPLT_entry, "green");
1625                    blue[index] = getIntAttribute(sPLT_entry, "blue");
1626                    alpha[index] = getIntAttribute(sPLT_entry, "alpha");
1627                    frequency[index] =
1628                        getIntAttribute(sPLT_entry, "frequency");
1629                    
1630                    sPLT_entry = sPLT_entry.getNextSibling();
1631                }
1632                
1633                int numEntries = maxindex + 1;
1634                sPLT_red = new int[numEntries];
1635                sPLT_green = new int[numEntries];
1636                sPLT_blue = new int[numEntries];
1637                sPLT_alpha = new int[numEntries];
1638                sPLT_frequency = new int[numEntries];
1639                System.arraycopy(red, 0, sPLT_red, 0, numEntries);
1640                System.arraycopy(green, 0, sPLT_green, 0, numEntries);
1641                System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
1642                System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
1643                System.arraycopy(frequency, 0,
1644                                 sPLT_frequency, 0, numEntries);
1645                
1646                sPLT_present = true;
1647            } else if (name.equals("sRGB")) {
1648                sRGB_renderingIntent =
1649                    getEnumeratedAttribute(node, "renderingIntent",
1650                                           renderingIntentNames);
1651                
1652                sRGB_present = true;
1653            } else if (name.equals("tEXt")) {
1654                Node tEXt_node = node.getFirstChild();
1655                while (tEXt_node != null) {
1656                    if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
1657                        fatal(node,
1658                              "Only an tEXtEntry may be a child of an tEXt!");
1659                    }
1660                    
1661                    String keyword =
1662                        toPrintableLatin1(getAttribute(tEXt_node, "keyword"));
1663                    tEXt_keyword.add(keyword);
1664                    
1665                    String text = getAttribute(tEXt_node, "value");
1666                    tEXt_text.add(text);
1667                    
1668                    tEXt_node = tEXt_node.getNextSibling();
1669                }
1670            } else if (name.equals("tIME")) {
1671                tIME_year = getIntAttribute(node, "year");
1672                tIME_month = getIntAttribute(node, "month");
1673                tIME_day = getIntAttribute(node, "day");
1674                tIME_hour = getIntAttribute(node, "hour");
1675                tIME_minute = getIntAttribute(node, "minute");
1676                tIME_second = getIntAttribute(node, "second");
1677                
1678                tIME_present = true;
1679            } else if (name.equals("tRNS")) {
1680                tRNS_present = false; // Guard against partial overwrite
1681                Node tRNS_node = node.getFirstChild();
1682                if (tRNS_node == null) {
1683                    fatal(node, "tRNS node has no children!");
1684                }
1685                String tRNS_name = tRNS_node.getNodeName();
1686                if (tRNS_name.equals("tRNS_Palette")) {
1687                    byte[] alpha = new byte[256];
1688                    int maxindex = -1;
1689                    
1690                    Node tRNS_paletteEntry = tRNS_node.getFirstChild();
1691                    if (tRNS_paletteEntry == null) {
1692                        fatal(node, "tRNS_Palette node has no children!");
1693                    }
1694                    while (tRNS_paletteEntry != null) {
1695                        if (!tRNS_paletteEntry.getNodeName().equals(
1696                                                        "tRNS_PaletteEntry")) {
1697                            fatal(node,
1698                 "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
1699                        }
1700                        int index =
1701                            getIntAttribute(tRNS_paletteEntry, "index");
1702                        if (index < 0 || index > 255) {
1703                            fatal(node,
1704                           "Bad value for tRNS_PaletteEntry attribute index!");
1705                        }
1706                        if (index > maxindex) {
1707                            maxindex = index;
1708                        }
1709                        alpha[index] =
1710                            (byte)getIntAttribute(tRNS_paletteEntry,
1711                                                  "alpha");
1712                        
1713                        tRNS_paletteEntry =
1714                            tRNS_paletteEntry.getNextSibling();
1715                    }
1716                    
1717                    int numEntries = maxindex + 1;
1718                    tRNS_alpha = new byte[numEntries];
1719                    tRNS_colorType = PNG_COLOR_PALETTE;
1720                    System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
1721                } else if (tRNS_name.equals("tRNS_Grayscale")) {
1722                    tRNS_gray = getIntAttribute(tRNS_node, "gray");
1723                    tRNS_colorType = PNG_COLOR_GRAY;
1724                } else if (tRNS_name.equals("tRNS_RGB")) {
1725                    tRNS_red = getIntAttribute(tRNS_node, "red");
1726                    tRNS_green = getIntAttribute(tRNS_node, "green");
1727                    tRNS_blue = getIntAttribute(tRNS_node, "blue");
1728                    tRNS_colorType = PNG_COLOR_RGB;
1729                } else {
1730                    fatal(node, "Bad child of a tRNS node!");
1731                }
1732                if (tRNS_node.getNextSibling() != null) {
1733                    fatal(node, "tRNS node has more than one child!");
1734                }
1735                
1736                tRNS_present = true;
1737            } else if (name.equals("zTXt")) {
1738                Node zTXt_node = node.getFirstChild();
1739                while (zTXt_node != null) {
1740                    if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
1741                        fatal(node,
1742                              "Only an zTXtEntry may be a child of an zTXt!");
1743                    }
1744                    
1745                    String keyword =
1746                        toPrintableLatin1(getAttribute(zTXt_node, "keyword"));
1747                    zTXt_keyword.add(keyword);
1748                    
1749                    int compressionMethod =
1750                        getEnumeratedAttribute(zTXt_node, "compressionMethod",
1751                                               zTXt_compressionMethodNames);
1752                    zTXt_compressionMethod.add(new Integer(compressionMethod));
1753                    
1754                    String text = getAttribute(zTXt_node, "text");
1755                    zTXt_text.add(text);
1756                    
1757                    zTXt_node = zTXt_node.getNextSibling();
1758                }
1759            } else if (name.equals("UnknownChunks")) {
1760                Node unknown_node = node.getFirstChild();
1761                while (unknown_node != null) {
1762                    if (!unknown_node.getNodeName().equals("UnknownChunk")) {
1763                        fatal(node,
1764                   "Only an UnknownChunk may be a child of an UnknownChunks!");
1765                    }
1766                    String chunkType = getAttribute(unknown_node, "type");
1767                    Object chunkData =
1768                        ((IIOMetadataNode)unknown_node).getUserObject();
1769                    
1770                    if (chunkType.length() != 4) {
1771                        fatal(unknown_node,
1772                              "Chunk type must be 4 characters!");
1773                    }
1774                    if (chunkData == null) {
1775                        fatal(unknown_node,
1776                              "No chunk data present in user object!");
1777                    }
1778                    if (!(chunkData instanceof byte[])) {
1779                        fatal(unknown_node,
1780                              "User object not a byte array!");
1781                    }
1782                    unknownChunkType.add(chunkType);
1783                    unknownChunkData.add(((byte[])chunkData).clone());
1784                    
1785                    unknown_node = unknown_node.getNextSibling();
1786                }
1787            } else {
1788                fatal(node, "Unknown child of root node!");
1789            }
1790            
1791            node = node.getNextSibling();
1792        }
1793    }
1794
1795    private boolean isISOLatin(String s) {
1796        int len = s.length();
1797        for (int i = 0; i < len; i++) {
1798            if (s.charAt(i) > 255) {
1799                return false;
1800            }
1801        }
1802        return true;
1803    }
1804
1805    private void mergeStandardTree(Node root)
1806        throws IIOInvalidTreeException {
1807        Node node = root;
1808        if (!node.getNodeName()
1809            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
1810            fatal(node, "Root must be " +
1811                  IIOMetadataFormatImpl.standardMetadataFormatName);
1812        }
1813        
1814        node = node.getFirstChild();
1815        while(node != null) {
1816            String name = node.getNodeName();
1817
1818            if (name.equals("Chroma")) {
1819                Node child = node.getFirstChild();
1820                while (child != null) {
1821                    String childName = child.getNodeName();
1822                    if (childName.equals("Gamma")) {
1823                        float gamma = getFloatAttribute(child, "value");
1824                        gAMA_present = true;
1825                        gAMA_gamma = (int)(gamma*100000 + 0.5);
1826                    } else if (childName.equals("Palette")) {
1827                        byte[] red = new byte[256];
1828                        byte[] green = new byte[256];
1829                        byte[] blue = new byte[256];
1830                        int maxindex = -1;
1831                
1832                        Node entry = child.getFirstChild();
1833                        while (entry != null) {
1834                            String entryName = entry.getNodeName();
1835                            if(entryName.equals("PaletteEntry")) {
1836                                int index = getIntAttribute(entry, "index");
1837                                if (index >= 0 && index <= 255) {
1838                                    red[index] =
1839                                        (byte)getIntAttribute(entry, "red");
1840                                    green[index] =
1841                                        (byte)getIntAttribute(entry, "green");
1842                                    blue[index] =
1843                                        (byte)getIntAttribute(entry, "blue");
1844                                    if (index > maxindex) {
1845                                        maxindex = index;
1846                                    }
1847                                }
1848                            }
1849                            entry = entry.getNextSibling();
1850                        }
1851                
1852                        int numEntries = maxindex + 1;
1853                        PLTE_red = new byte[numEntries];
1854                        PLTE_green = new byte[numEntries];
1855                        PLTE_blue = new byte[numEntries];
1856                        System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1857                        System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1858                        System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1859                        PLTE_present = true;
1860                    } else if (childName.equals("BackgroundIndex")) {
1861                        bKGD_present = true;
1862                        bKGD_colorType = PNG_COLOR_PALETTE;
1863                        bKGD_index = getIntAttribute(child, "value");
1864                    } else if (childName.equals("BackgroundColor")) {
1865                        int red = getIntAttribute(child, "red");
1866                        int green = getIntAttribute(child, "green");
1867                        int blue = getIntAttribute(child, "blue");
1868                        if (red == green && red == blue) {
1869                            bKGD_colorType = PNG_COLOR_GRAY;
1870                            bKGD_gray = red;
1871                        } else {
1872                            bKGD_colorType = PNG_COLOR_RGB;
1873                            bKGD_red = red;
1874                            bKGD_green = green;
1875                            bKGD_blue = blue;
1876                        }
1877                        bKGD_present = true;
1878                    }
1879                    // } else if (childName.equals("ColorSpaceType")) {
1880                    // } else if (childName.equals("NumChannels")) {
1881
1882                    child = child.getNextSibling();
1883                }
1884            } else if (name.equals("Compression")) {
1885                Node child = node.getFirstChild();
1886                while (child != null) {
1887                    String childName = child.getNodeName();
1888                    if (childName.equals("NumProgressiveScans")) {
1889                        // Use Adam7 if NumProgressiveScans > 1
1890                        int scans = getIntAttribute(child, "value");
1891                        IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
1892                        //                  } else if (childName.equals("CompressionTypeName")) {
1893                        //                  } else if (childName.equals("Lossless")) {
1894                        //                  } else if (childName.equals("BitRate")) {
1895                    }
1896                    child = child.getNextSibling();
1897                }
1898            } else if (name.equals("Data")) {
1899                Node child = node.getFirstChild();
1900                while (child != null) {
1901                    String childName = child.getNodeName();
1902                    if (childName.equals("BitsPerSample")) {
1903                        String s = getAttribute(child, "value");
1904                        StringTokenizer t = new StringTokenizer(s);
1905                        int maxBits = -1;
1906                        while (t.hasMoreTokens()) {
1907                            int bits = Integer.parseInt(t.nextToken());
1908                            if (bits > maxBits) {
1909                                maxBits = bits;
1910                            }
1911                        }
1912                        if (maxBits < 1) {
1913                            maxBits = 1;
1914                        } else if (maxBits == 3) {
1915                            maxBits = 4;
1916                        } else if (maxBits > 4 && maxBits < 8) {
1917                            maxBits = 8;
1918                        } else if (maxBits > 8) {
1919                            maxBits = 16;
1920                        }
1921                        IHDR_bitDepth = maxBits;
1922                    } else if (childName.equals("SignificantBitsPerSample")) {
1923                        String s = getAttribute(child, "value");
1924                        StringTokenizer t = new StringTokenizer(s);
1925                        int numTokens = t.countTokens();
1926                        if (numTokens == 1) {
1927                            sBIT_colorType = PNG_COLOR_GRAY;
1928                            sBIT_grayBits = Integer.parseInt(t.nextToken());
1929                        } else if (numTokens == 2) {
1930                            sBIT_colorType =
1931                                PNG_COLOR_GRAY_ALPHA;
1932                            sBIT_grayBits = Integer.parseInt(t.nextToken());
1933                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
1934                        } else if (numTokens == 3) {
1935                            sBIT_colorType = PNG_COLOR_RGB;
1936                            sBIT_redBits = Integer.parseInt(t.nextToken());
1937                            sBIT_greenBits = Integer.parseInt(t.nextToken());
1938                            sBIT_blueBits = Integer.parseInt(t.nextToken());
1939                        } else if (numTokens == 4) {
1940                            sBIT_colorType =
1941                                PNG_COLOR_RGB_ALPHA;
1942                            sBIT_redBits = Integer.parseInt(t.nextToken());
1943                            sBIT_greenBits = Integer.parseInt(t.nextToken());
1944                            sBIT_blueBits = Integer.parseInt(t.nextToken());
1945                            sBIT_alphaBits = Integer.parseInt(t.nextToken());
1946                        }
1947                        if (numTokens >= 1 && numTokens <= 4) {
1948                            sBIT_present = true;
1949                        }
1950                        // } else if (childName.equals("PlanarConfiguration")) {
1951                        // } else if (childName.equals("SampleFormat")) {
1952                        // } else if (childName.equals("SampleMSB")) {
1953                    }
1954                    child = child.getNextSibling();
1955                }
1956            } else if (name.equals("Dimension")) {
1957                boolean gotWidth = false;
1958                boolean gotHeight = false;
1959                boolean gotAspectRatio = false;
1960
1961                float width = -1.0F;
1962                float height = -1.0F;
1963                float aspectRatio = -1.0F;
1964                
1965                Node child = node.getFirstChild();
1966                while (child != null) {
1967                    String childName = child.getNodeName();
1968                    if (childName.equals("PixelAspectRatio")) {
1969                        aspectRatio = getFloatAttribute(child, "value");
1970                        gotAspectRatio = true;
1971                    } else if (childName.equals("HorizontalPixelSize")) {
1972                        width = getFloatAttribute(child, "value");
1973                        gotWidth = true;
1974                    } else if (childName.equals("VerticalPixelSize")) {
1975                        height = getFloatAttribute(child, "value");
1976                        gotHeight = true;
1977                        // } else if (childName.equals("ImageOrientation")) {
1978                        // } else if
1979                        //     (childName.equals("HorizontalPhysicalPixelSpacing")) {
1980                        // } else if
1981                        //     (childName.equals("VerticalPhysicalPixelSpacing")) {
1982                        // } else if (childName.equals("HorizontalPosition")) {
1983                        // } else if (childName.equals("VerticalPosition")) {
1984                        // } else if (childName.equals("HorizontalPixelOffset")) {
1985                        // } else if (childName.equals("VerticalPixelOffset")) {
1986                    }
1987                    child = child.getNextSibling();
1988                }
1989
1990                if (gotWidth && gotHeight) {
1991                    pHYs_present = true;
1992                    pHYs_unitSpecifier = 1;
1993                    pHYs_pixelsPerUnitXAxis = (int)(1000.0F/width + 0.5F);
1994                    pHYs_pixelsPerUnitYAxis = (int)(1000.0F/height + 0.5F);
1995                } else if (gotAspectRatio) {
1996                    pHYs_present = true;
1997                    pHYs_unitSpecifier = 0;
1998
1999                    // Find a reasonable rational approximation
2000                    int denom = 1;
2001                    for (; denom < 100; denom++) {
2002                        int num = (int)(aspectRatio*denom);
2003                        if (Math.abs(num/denom - aspectRatio) < 0.001) {
2004                            break;
2005                        }
2006                    }
2007                    pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
2008                    pHYs_pixelsPerUnitYAxis = denom;
2009                }
2010            } else if (name.equals("Document")) {
2011                Node child = node.getFirstChild();
2012                while (child != null) {
2013                    String childName = child.getNodeName();
2014                    if (childName.equals("ImageModificationTime")) {
2015                        tIME_present = true;
2016                        tIME_year = getIntAttribute(child, "year");
2017                        tIME_month = getIntAttribute(child, "month");
2018                        tIME_day = getIntAttribute(child, "day");
2019                        tIME_hour =
2020                            getIntAttribute(child, "hour", 0, false);
2021                        tIME_minute =
2022                            getIntAttribute(child, "minute", 0, false);
2023                        tIME_second =
2024                            getIntAttribute(child, "second", 0, false);
2025                        // } else if (childName.equals("SubimageInterpretation")) {
2026                        // } else if (childName.equals("ImageCreationTime")) {
2027                    }
2028                    child = child.getNextSibling();
2029                }
2030            } else if (name.equals("Text")) {
2031                Node child = node.getFirstChild();
2032                while (child != null) {
2033                    String childName = child.getNodeName();
2034                    if (childName.equals("TextEntry")) {
2035                        String keyword = getAttribute(child, "keyword",
2036                                                      "text", false);
2037                        String value = getAttribute(child, "value");
2038                        String encoding = getAttribute(child, "encoding",
2039                                                       "unknown", false);
2040                        String language = getAttribute(child, "language",
2041                                                       "unknown", false);
2042                        String compression =
2043                            getAttribute(child, "compression",
2044                                         "other", false);
2045
2046                        if (isISOLatin(value)) {
2047                            if (compression.equals("zip")) {
2048                                // Use a zTXt node
2049                                zTXt_keyword.add(toPrintableLatin1(keyword));
2050                                zTXt_text.add(value);
2051                                zTXt_compressionMethod.add(new Integer(0));
2052                            } else {
2053                                // Use a tEXt node
2054                                tEXt_keyword.add(toPrintableLatin1(keyword));
2055                                tEXt_text.add(value);
2056                            }
2057                        } else {
2058                            int flag = compression.equals("zip") ?
2059                                1 : 0;
2060
2061                            // Use an iTXt node
2062                            iTXt_keyword.add(toPrintableLatin1(keyword));
2063                            iTXt_compressionFlag.add(new Integer(flag));
2064                            iTXt_compressionMethod.add(new Integer(0));
2065                            iTXt_languageTag.add(language);
2066                            iTXt_translatedKeyword.add(keyword); // fake it
2067                            iTXt_text.add(value);
2068                        }
2069                    }
2070                    child = child.getNextSibling();
2071                }
2072                //          } else if (name.equals("Transparency")) {
2073                //              Node child = node.getFirstChild();
2074                //              while (child != null) {
2075                //                  String childName = child.getNodeName();
2076                //                  if (childName.equals("Alpha")) {
2077                //                  } else if (childName.equals("TransparentIndex")) {
2078                //                  } else if (childName.equals("TransparentColor")) {
2079                //                  } else if (childName.equals("TileTransparencies")) {
2080                //                  } else if (childName.equals("TileOpacities")) {
2081                //                  }
2082                //                  child = child.getNextSibling();
2083                //              }
2084                //          } else {
2085                //              // fatal(node, "Unknown child of root node!");
2086            }
2087            
2088            node = node.getNextSibling();
2089        }
2090    }
2091
2092    // Reset all instance variables to their initial state
2093    public void reset() {
2094        IHDR_present = false;
2095        PLTE_present = false;
2096        bKGD_present = false;
2097        cHRM_present = false;
2098        gAMA_present = false;
2099        hIST_present = false;
2100        iCCP_present = false;
2101        iTXt_keyword = new ArrayList();
2102        iTXt_compressionFlag = new ArrayList();
2103        iTXt_compressionMethod = new ArrayList();
2104        iTXt_languageTag = new ArrayList();
2105        iTXt_translatedKeyword = new ArrayList();
2106        iTXt_text = new ArrayList();
2107        pHYs_present = false;
2108        sBIT_present = false;
2109        sPLT_present = false;
2110        sRGB_present = false;
2111        tEXt_keyword = new ArrayList();
2112        tEXt_text = new ArrayList();
2113        tIME_present = false;
2114        tRNS_present = false;
2115        zTXt_keyword = new ArrayList();
2116        zTXt_compressionMethod = new ArrayList();
2117        zTXt_text = new ArrayList();
2118        unknownChunkType = new ArrayList();
2119        unknownChunkData = new ArrayList();
2120    }
2121
2122    // BEGIN metadata reading section.
2123
2124    private boolean gotHeader = false;
2125    private boolean gotMetadata = false;
2126
2127    private static int chunkType(String typeString) {
2128        char c0 = typeString.charAt(0);
2129        char c1 = typeString.charAt(1);
2130        char c2 = typeString.charAt(2);
2131        char c3 = typeString.charAt(3);
2132
2133        int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
2134        return type;
2135    }
2136
2137    private String readNullTerminatedString(ImageInputStream stream)
2138        throws IOException {
2139        StringBuffer b = new StringBuffer();
2140        int c;
2141
2142        while ((c = stream.read()) != 0) {
2143            b.append((char)c);
2144        }
2145        return b.toString();
2146    }
2147
2148    // END metadata writing methods.
2149}