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