001/*
002 * $RCSfile: BMPMetadata.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.2 $
042 * $Date: 2006/04/21 23:14:37 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.bmp;
046
047import java.awt.image.ColorModel;
048import java.awt.image.DirectColorModel;
049import java.awt.image.IndexColorModel;
050import java.awt.image.SampleModel;
051import java.util.ArrayList;
052import java.util.Arrays;
053import java.util.Iterator;
054import java.util.List;
055import java.util.StringTokenizer;
056
057import javax.imageio.ImageWriteParam;
058import javax.imageio.metadata.IIOInvalidTreeException;
059import javax.imageio.metadata.IIOMetadata;
060import javax.imageio.metadata.IIOMetadataFormatImpl;
061import javax.imageio.metadata.IIOMetadataNode;
062
063import org.w3c.dom.Node;
064
065import com.github.jaiimageio.impl.common.ImageUtil;
066
067public class BMPMetadata extends IIOMetadata
068    implements Cloneable, BMPConstants {
069
070    public static final String nativeMetadataFormatName =
071        "com_sun_media_imageio_plugins_bmp_image_1.0";
072
073    // Fields for Image Descriptor
074    public String bmpVersion;
075    public int width ;
076    public int height;
077    public short bitsPerPixel;
078    public int compression;
079    public int imageSize;
080
081    // Fields for PixelsPerMeter
082    public int xPixelsPerMeter;
083    public int yPixelsPerMeter;
084
085    public int colorsUsed;
086    public int colorsImportant;
087
088    // Fields for BI_BITFIELDS compression(Mask)
089    public int redMask;
090    public int greenMask;
091    public int blueMask;
092    public int alphaMask;
093
094    public int colorSpace;
095
096    // Fields for CIE XYZ for the LCS_CALIBRATED_RGB color space
097    public double redX;
098    public double redY;
099    public double redZ;
100    public double greenX;
101    public double greenY;
102    public double greenZ;
103    public double blueX;
104    public double blueY;
105    public double blueZ;
106
107    // Fields for Gamma values for the LCS_CALIBRATED_RGB color space
108    public int gammaRed;
109    public int gammaGreen;
110    public int gammaBlue;
111
112    public int intent;
113
114    // Fields for the Palette and Entries
115    public byte[] palette = null;
116    public int paletteSize;
117    public int red;
118    public int green;
119    public int blue;
120
121    // Fields from CommentExtension
122    // List of String
123    public List comments = null; // new ArrayList();
124
125    public BMPMetadata() {
126        super(true,
127              nativeMetadataFormatName,
128              "com.github.jaiimageio.impl.bmp.BMPMetadataFormat",
129              null, null);
130    }
131
132    public BMPMetadata(IIOMetadata metadata)
133        throws IIOInvalidTreeException {
134
135        this();
136
137        if(metadata != null) {
138            List formats = Arrays.asList(metadata.getMetadataFormatNames());
139
140            if(formats.contains(nativeMetadataFormatName)) {
141                // Initialize from native image metadata format.
142                setFromTree(nativeMetadataFormatName,
143                            metadata.getAsTree(nativeMetadataFormatName));
144            } else if(metadata.isStandardMetadataFormatSupported()) {
145                // Initialize from standard metadata form of the input tree.
146                String format =
147                    IIOMetadataFormatImpl.standardMetadataFormatName;
148                setFromTree(format, metadata.getAsTree(format));
149            }
150        }
151    }
152
153    public boolean isReadOnly() {
154        return false;
155    }
156
157    public Object clone() {
158        BMPMetadata metadata;
159        try {
160            metadata = (BMPMetadata)super.clone();
161        } catch (CloneNotSupportedException e) {
162            return null;
163        }
164        
165        return metadata;
166    }
167
168    public Node getAsTree(String formatName) {
169        if (formatName.equals(nativeMetadataFormatName)) {
170            return getNativeTree();
171        } else if (formatName.equals
172                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
173            return getStandardTree();
174        } else {
175            throw new IllegalArgumentException(I18N.getString("BMPMetadata0"));
176        }
177    }
178
179    private Node getNativeTree() {
180        IIOMetadataNode root =
181            new IIOMetadataNode(nativeMetadataFormatName);
182
183        addChildNode(root, "BMPVersion", bmpVersion);
184        addChildNode(root, "Width", new Integer(width));
185        addChildNode(root, "Height", new Integer(height));
186        addChildNode(root, "BitsPerPixel", new Short(bitsPerPixel));
187        addChildNode(root, "Compression", new Integer(compression));
188        addChildNode(root, "ImageSize", new Integer(imageSize));
189
190        IIOMetadataNode node;
191        if(xPixelsPerMeter > 0 && yPixelsPerMeter > 0) {
192            node = addChildNode(root, "PixelsPerMeter", null);
193            addChildNode(node, "X", new Integer(xPixelsPerMeter));
194            addChildNode(node, "Y", new Integer(yPixelsPerMeter));
195        }
196
197        addChildNode(root, "ColorsUsed", new Integer(colorsUsed));
198        addChildNode(root, "ColorsImportant", new Integer(colorsImportant));
199
200        int version = 0;
201        for (int i = 0; i < bmpVersion.length(); i++)
202            if (Character.isDigit(bmpVersion.charAt(i)))
203                version = bmpVersion.charAt(i) -'0';
204
205        if (version >= 4) {
206            node = addChildNode(root, "Mask", null);
207            addChildNode(node, "Red", new Integer(redMask));
208            addChildNode(node, "Green", new Integer(greenMask));
209            addChildNode(node, "Blue", new Integer(blueMask));
210            addChildNode(node, "Alpha", new Integer(alphaMask));
211
212            addChildNode(root, "ColorSpaceType", new Integer(colorSpace));
213
214            node = addChildNode(root, "CIEXYZEndpoints", null);
215            addXYZPoints(node, "Red", redX, redY, redZ);
216            addXYZPoints(node, "Green", greenX, greenY, greenZ);
217            addXYZPoints(node, "Blue", blueX, blueY, blueZ);
218
219            node = addChildNode(root, "Gamma", null);
220            addChildNode(node, "Red", new Integer(gammaRed));
221            addChildNode(node, "Green", new Integer(gammaGreen));
222            addChildNode(node, "Blue", new Integer(gammaBlue));
223
224            node = addChildNode(root, "Intent", new Integer(intent));
225        }
226
227        // Palette
228        if ((palette != null) && (paletteSize > 0)) {
229            node = addChildNode(root, "Palette", null);
230            boolean isVersion2 =
231                bmpVersion != null && bmpVersion.equals(VERSION_2);
232
233            for (int i = 0, j = 0; i < paletteSize; i++) {
234                IIOMetadataNode entry =
235                    addChildNode(node, "PaletteEntry", null);
236                blue = palette[j++] & 0xff;
237                green = palette[j++] & 0xff;
238                red = palette[j++] & 0xff;
239                addChildNode(entry, "Red", new Integer(red));
240                addChildNode(entry, "Green", new Integer(green));
241                addChildNode(entry, "Blue", new Integer(blue));
242                if(!isVersion2) j++; // skip reserved entry
243            }
244        }
245
246        return root;
247    }
248
249    // Standard tree node methods
250    protected IIOMetadataNode getStandardChromaNode() {
251
252        IIOMetadataNode node = new IIOMetadataNode("Chroma");
253
254        IIOMetadataNode subNode = new IIOMetadataNode("ColorSpaceType");
255        String colorSpaceType;
256        if (((palette != null) && (paletteSize > 0)) ||
257            (redMask != 0 || greenMask != 0 || blueMask != 0) ||
258            bitsPerPixel > 8) {
259            colorSpaceType = "RGB";
260        } else {
261            colorSpaceType = "GRAY";
262        }
263        subNode.setAttribute("name", colorSpaceType);
264        node.appendChild(subNode);
265
266        subNode = new IIOMetadataNode("NumChannels");
267        String numChannels;
268        if (((palette != null) && (paletteSize > 0)) ||
269            (redMask != 0 || greenMask != 0 || blueMask != 0) ||
270            bitsPerPixel > 8) {
271            if(alphaMask != 0) {
272                numChannels = "4";
273            } else {
274                numChannels = "3";
275            }
276        } else {
277            numChannels = "1";
278        }
279        subNode.setAttribute("value", numChannels);
280        node.appendChild(subNode);
281
282        if(gammaRed != 0 && gammaGreen != 0 && gammaBlue != 0) {
283            subNode = new IIOMetadataNode("Gamma");
284            Double gamma = new Double((gammaRed+gammaGreen+gammaBlue)/3.0);
285            subNode.setAttribute("value", gamma.toString());
286            node.appendChild(subNode);
287        }
288
289        if(numChannels.equals("1") &&
290           (palette == null || paletteSize == 0)) {
291            subNode = new IIOMetadataNode("BlackIsZero");
292            subNode.setAttribute("value", "TRUE");
293            node.appendChild(subNode);
294        }
295
296        if ((palette != null) && (paletteSize > 0)) {
297            subNode = new IIOMetadataNode("Palette");
298            boolean isVersion2 =
299                bmpVersion != null && bmpVersion.equals(VERSION_2);
300
301            for (int i = 0, j = 0; i < paletteSize; i++) {
302                IIOMetadataNode subNode1 =
303                    new IIOMetadataNode("PaletteEntry");
304                subNode1.setAttribute("index", ""+i);
305                subNode1.setAttribute("blue", "" + (palette[j++]&0xff));
306                subNode1.setAttribute("green", "" + (palette[j++]&0xff));
307                subNode1.setAttribute("red", "" + (palette[j++]&0xff));
308                if(!isVersion2) j++; // skip reserved entry
309                subNode.appendChild(subNode1);
310            }
311            node.appendChild(subNode);
312        }
313
314        return node;
315    }
316
317    protected IIOMetadataNode getStandardCompressionNode() {
318        IIOMetadataNode node = new IIOMetadataNode("Compression");
319
320        // CompressionTypeName
321        IIOMetadataNode subNode = new IIOMetadataNode("CompressionTypeName");
322        subNode.setAttribute("value", compressionTypeNames[compression]);
323        node.appendChild(subNode);
324
325        subNode = new IIOMetadataNode("Lossless");
326        subNode.setAttribute("value",
327                             compression == BI_JPEG ? "FALSE" : "TRUE");
328        node.appendChild(subNode);
329
330        return node;
331    }
332
333    protected IIOMetadataNode getStandardDataNode() {
334        IIOMetadataNode node = new IIOMetadataNode("Data");
335
336        String sampleFormat = (palette != null) && (paletteSize > 0) ?
337            "Index" : "UnsignedIntegral";
338        IIOMetadataNode subNode = new IIOMetadataNode("SampleFormat");
339        subNode.setAttribute("value", sampleFormat);
340        node.appendChild(subNode);
341
342        String bits = "";
343        if(redMask != 0 || greenMask != 0 || blueMask != 0) {
344            bits =
345                countBits(redMask) + " " +
346                countBits(greenMask) + " " +
347                countBits(blueMask);
348            if(alphaMask != 0) {
349                bits += " " + countBits(alphaMask);
350            }
351        } else if(palette != null && paletteSize > 0) {
352            for(int i = 1; i <= 3; i++) {
353                bits += bitsPerPixel;
354                if(i != 3) {
355                    bits += " ";
356                }
357            }
358        } else {
359            if (bitsPerPixel == 1) {
360                bits = "1";
361            } else if (bitsPerPixel == 4) {
362                bits = "4";
363            } else if (bitsPerPixel == 8) {
364                bits = "8";
365            } else if (bitsPerPixel == 16) {
366                bits = "5 6 5";
367            } else if (bitsPerPixel == 24) {
368                bits = "8 8 8";
369            } else if ( bitsPerPixel == 32) {
370                bits = "8 8 8 8";
371            }
372        }
373
374        if(!bits.equals("")) {
375            subNode = new IIOMetadataNode("BitsPerSample");
376            subNode.setAttribute("value", bits);
377            node.appendChild(subNode);
378        }
379
380        return node;
381    }
382
383    protected IIOMetadataNode getStandardDimensionNode() {
384        if (yPixelsPerMeter > 0 && xPixelsPerMeter > 0) {
385            IIOMetadataNode node = new IIOMetadataNode("Dimension");
386            float ratio = (float)yPixelsPerMeter / (float)xPixelsPerMeter;
387            IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio");
388            subNode.setAttribute("value", "" + ratio);
389            node.appendChild(subNode);
390
391            subNode = new IIOMetadataNode("HorizontalPixelSize");
392            subNode.setAttribute("value", "" + (1000.0F / xPixelsPerMeter));
393            node.appendChild(subNode);
394
395            subNode = new IIOMetadataNode("VerticalPixelSize");
396            subNode.setAttribute("value", "" + (1000.0F / yPixelsPerMeter));
397            node.appendChild(subNode);
398
399            // Emit HorizontalPhysicalPixelSpacing and
400            // VerticalPhysicalPixelSpacing for historical reasonse:
401            // HorizontalPixelSize and VerticalPixelSize should have
402            // been used in the first place.
403            subNode = new IIOMetadataNode("HorizontalPhysicalPixelSpacing");
404            subNode.setAttribute("value", "" + (1000.0F / xPixelsPerMeter));
405            node.appendChild(subNode);
406
407            subNode = new IIOMetadataNode("VerticalPhysicalPixelSpacing");
408            subNode.setAttribute("value", "" + (1000.0F / yPixelsPerMeter));
409            node.appendChild(subNode);
410
411            return node;
412        }
413        return null;
414    }
415
416    protected IIOMetadataNode getStandardDocumentNode() {
417        if(bmpVersion != null) {
418            IIOMetadataNode node = new IIOMetadataNode("Document");
419            IIOMetadataNode subNode = new IIOMetadataNode("FormatVersion");
420            subNode.setAttribute("value", bmpVersion);
421            node.appendChild(subNode);
422            return node;
423        }
424        return null;
425    }
426
427    protected IIOMetadataNode getStandardTextNode() {
428        if(comments != null) {
429            IIOMetadataNode node = new IIOMetadataNode("Text");
430            Iterator iter = comments.iterator();
431            while(iter.hasNext()) {
432                String comment = (String)iter.next();
433                IIOMetadataNode subNode = new IIOMetadataNode("TextEntry");
434                subNode.setAttribute("keyword", "comment");
435                subNode.setAttribute("value", comment);
436                node.appendChild(subNode);
437            }
438            return node;
439        }
440        return null;
441    }
442
443    protected IIOMetadataNode getStandardTransparencyNode() {
444        IIOMetadataNode node = new IIOMetadataNode("Transparency");
445        IIOMetadataNode subNode = new IIOMetadataNode("Alpha");
446        String alpha;
447        if(alphaMask != 0) {
448            alpha = "nonpremultiplied";
449        } else {
450            alpha = "none";
451        }
452
453        subNode.setAttribute("value", alpha);
454        node.appendChild(subNode);
455        return node;
456    }
457
458    // Shorthand for throwing an IIOInvalidTreeException
459    private void fatal(Node node, String reason)
460        throws IIOInvalidTreeException {
461        throw new IIOInvalidTreeException(reason, node);
462    }
463
464    // Get an integer-valued attribute
465    private int getIntAttribute(Node node, String name,
466                                int defaultValue, boolean required)
467        throws IIOInvalidTreeException {
468        String value = getAttribute(node, name, null, required);
469        if (value == null) {
470            return defaultValue;
471        }
472        return Integer.parseInt(value);
473    }
474
475    // Get a double-valued attribute
476    private double getDoubleAttribute(Node node, String name,
477                                      double defaultValue, boolean required)
478        throws IIOInvalidTreeException {
479        String value = getAttribute(node, name, null, required);
480        if (value == null) {
481            return defaultValue;
482        }
483        return Double.parseDouble(value);
484    }
485
486    // Get a required integer-valued attribute
487    private int getIntAttribute(Node node, String name)
488        throws IIOInvalidTreeException {
489        return getIntAttribute(node, name, -1, true);
490    }
491
492    // Get a required double-valued attribute
493    private double getDoubleAttribute(Node node, String name)
494        throws IIOInvalidTreeException {
495        return getDoubleAttribute(node, name, -1.0F, true);
496    }
497
498    // Get a String-valued attribute
499    private String getAttribute(Node node, String name,
500                                String defaultValue, boolean required)
501        throws IIOInvalidTreeException {
502        Node attr = node.getAttributes().getNamedItem(name);
503        if (attr == null) {
504            if (!required) {
505                return defaultValue;
506            } else {
507                fatal(node, "Required attribute " + name + " not present!");
508            }
509        }
510        return attr.getNodeValue();
511    }
512
513    // Get a required String-valued attribute
514    private String getAttribute(Node node, String name)
515        throws IIOInvalidTreeException {
516        return getAttribute(node, name, null, true);
517    }
518
519    void initialize(ColorModel cm, SampleModel sm, ImageWriteParam param) {
520        // bmpVersion and compression.
521        if(param != null) {
522            bmpVersion = BMPConstants.VERSION_3;
523
524            if(param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
525                String compressionType = param.getCompressionType();
526                compression =
527                    BMPImageWriter.getCompressionType(compressionType);
528            }
529        } else {
530            bmpVersion = BMPConstants.VERSION_3;
531            compression = BMPImageWriter.getPreferredCompressionType(cm, sm);
532        }
533
534        // width and height
535        width = sm.getWidth();
536        height = sm.getHeight();
537
538        // bitsPerPixel
539        bitsPerPixel = (short)cm.getPixelSize();
540
541        // mask
542        if(cm instanceof DirectColorModel) {
543            DirectColorModel dcm = (DirectColorModel)cm;
544            redMask = dcm.getRedMask();
545            greenMask = dcm.getGreenMask();
546            blueMask = dcm.getBlueMask();
547            alphaMask = dcm.getAlphaMask();
548        }
549
550        // palette and paletteSize
551        if(cm instanceof IndexColorModel) {
552            IndexColorModel icm = (IndexColorModel)cm;
553            paletteSize = icm.getMapSize();
554
555            byte[] r = new byte[paletteSize];
556            byte[] g = new byte[paletteSize];
557            byte[] b = new byte[paletteSize];
558
559            icm.getReds(r);
560            icm.getGreens(g);
561            icm.getBlues(b);
562
563            boolean isVersion2 =
564                bmpVersion != null && bmpVersion.equals(VERSION_2);
565
566            palette = new byte[(isVersion2 ? 3 : 4)*paletteSize];
567            for(int i = 0, j = 0; i < paletteSize; i++) {
568                palette[j++] = b[i];
569                palette[j++] = g[i];
570                palette[j++] = r[i];
571                if(!isVersion2) j++; // skip reserved entry
572            }
573        }
574    }
575
576    public void mergeTree(String formatName, Node root)
577        throws IIOInvalidTreeException {
578        if (formatName.equals(nativeMetadataFormatName)) {
579            if (root == null) {
580                throw new IllegalArgumentException("root == null!");
581            }
582            mergeNativeTree(root);
583        } else if (formatName.equals
584                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
585            if (root == null) {
586                throw new IllegalArgumentException("root == null!");
587            }
588            mergeStandardTree(root);
589        } else {
590            throw new IllegalArgumentException("Not a recognized format!");
591        }
592    }
593
594    private void mergeNativeTree(Node root)
595        throws IIOInvalidTreeException {
596        Node node = root;
597        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
598            fatal(node, "Root must be " + nativeMetadataFormatName);
599        }
600        
601        byte[] r = null, g = null, b = null;
602        int maxIndex = -1;
603
604        node = node.getFirstChild();
605        while (node != null) {
606            String name = node.getNodeName();
607
608            if (name.equals("BMPVersion")) {
609                String value = getStringValue(node);
610                if(value != null) bmpVersion = value;
611            } else if (name.equals("Width")) {
612                Integer value = getIntegerValue(node);
613                if(value != null) width = value.intValue();
614            } else if (name.equals("Height")) {
615                Integer value = getIntegerValue(node);
616                if(value != null) height = value.intValue();
617            } else if (name.equals("BitsPerPixel")) {
618                Short value = getShortValue(node);
619                if(value != null) bitsPerPixel = value.shortValue();
620            } else if (name.equals("Compression")) {
621                Integer value = getIntegerValue(node);
622                if(value != null) compression = value.intValue();
623            } else if (name.equals("ImageSize")) {
624                Integer value = getIntegerValue(node);
625                if(value != null) imageSize = value.intValue();
626            } else if (name.equals("PixelsPerMeter")) {
627                Node subNode = node.getFirstChild();
628                while (subNode != null) {
629                    String subName = subNode.getNodeName();
630                    if(subName.equals("X")) {
631                        Integer value = getIntegerValue(subNode);
632                        if(value != null)
633                            xPixelsPerMeter = value.intValue();
634                    } else if(subName.equals("Y")) {
635                        Integer value = getIntegerValue(subNode);
636                        if(value != null)
637                            yPixelsPerMeter = value.intValue();
638                    }
639                    subNode = subNode.getNextSibling();
640                }
641            } else if (name.equals("ColorsUsed")) {
642                Integer value = getIntegerValue(node);
643                if(value != null) colorsUsed = value.intValue();
644            } else if (name.equals("ColorsImportant")) {
645                Integer value = getIntegerValue(node);
646                if(value != null) colorsImportant = value.intValue();
647            } else if (name.equals("Mask")) {
648                Node subNode = node.getFirstChild();
649                while (subNode != null) {
650                    String subName = subNode.getNodeName();
651                    if(subName.equals("Red")) {
652                        Integer value = getIntegerValue(subNode);
653                        if(value != null)
654                            redMask = value.intValue();
655                    } else if(subName.equals("Green")) {
656                        Integer value = getIntegerValue(subNode);
657                        if(value != null)
658                            greenMask = value.intValue();
659                    } else if(subName.equals("Blue")) {
660                        Integer value = getIntegerValue(subNode);
661                        if(value != null)
662                            blueMask = value.intValue();
663                    } else if(subName.equals("Alpha")) {
664                        Integer value = getIntegerValue(subNode);
665                        if(value != null)
666                            alphaMask = value.intValue();
667                    }
668                    subNode = subNode.getNextSibling();
669                }
670            } else if (name.equals("ColorSpace")) {
671                Integer value = getIntegerValue(node);
672                if(value != null) colorSpace = value.intValue();
673            } else if (name.equals("CIEXYZEndpoints")) {
674                Node subNode = node.getFirstChild();
675                while (subNode != null) {
676                    String subName = subNode.getNodeName();
677                    if(subName.equals("Red")) {
678                        Node subNode1 = subNode.getFirstChild();
679                        while (subNode1 != null) {
680                            String subName1 = subNode1.getNodeName();
681                            if(subName1.equals("X")) {
682                                Double value = getDoubleValue(subNode1);
683                                if(value != null)
684                                    redX = value.doubleValue();
685                            } else if(subName1.equals("Y")) {
686                                Double value = getDoubleValue(subNode1);
687                                if(value != null)
688                                    redY = value.doubleValue();
689                            } else if(subName1.equals("Z")) {
690                                Double value = getDoubleValue(subNode1);
691                                if(value != null)
692                                    redZ = value.doubleValue();
693                            }
694                            subNode1 = subNode1.getNextSibling();
695                        }
696                    } else if(subName.equals("Green")) {
697                        Node subNode1 = subNode.getFirstChild();
698                        while (subNode1 != null) {
699                            String subName1 = subNode1.getNodeName();
700                            if(subName1.equals("X")) {
701                                Double value = getDoubleValue(subNode1);
702                                if(value != null)
703                                    greenX = value.doubleValue();
704                            } else if(subName1.equals("Y")) {
705                                Double value = getDoubleValue(subNode1);
706                                if(value != null)
707                                    greenY = value.doubleValue();
708                            } else if(subName1.equals("Z")) {
709                                Double value = getDoubleValue(subNode1);
710                                if(value != null)
711                                    greenZ = value.doubleValue();
712                            }
713                            subNode1 = subNode1.getNextSibling();
714                        }
715                    } else if(subName.equals("Blue")) {
716                        Node subNode1 = subNode.getFirstChild();
717                        while (subNode1 != null) {
718                            String subName1 = subNode1.getNodeName();
719                            if(subName1.equals("X")) {
720                                Double value = getDoubleValue(subNode1);
721                                if(value != null)
722                                    blueX = value.doubleValue();
723                            } else if(subName1.equals("Y")) {
724                                Double value = getDoubleValue(subNode1);
725                                if(value != null)
726                                    blueY = value.doubleValue();
727                            } else if(subName1.equals("Z")) {
728                                Double value = getDoubleValue(subNode1);
729                                if(value != null)
730                                    blueZ = value.doubleValue();
731                            }
732                            subNode1 = subNode1.getNextSibling();
733                        }
734                    }
735                    subNode = subNode.getNextSibling();
736                }
737            } else if (name.equals("Gamma")) {
738                Node subNode = node.getFirstChild();
739                while (subNode != null) {
740                    String subName = subNode.getNodeName();
741                    if(subName.equals("Red")) {
742                        Integer value = getIntegerValue(subNode);
743                        if(value != null)
744                            gammaRed = value.intValue();
745                    } else if(subName.equals("Green")) {
746                        Integer value = getIntegerValue(subNode);
747                        if(value != null)
748                            gammaGreen = value.intValue();
749                    } else if(subName.equals("Blue")) {
750                        Integer value = getIntegerValue(subNode);
751                        if(value != null)
752                            gammaBlue = value.intValue();
753                    }
754                    subNode = subNode.getNextSibling();
755                }
756            } else if (name.equals("Intent")) {
757                Integer value = getIntegerValue(node);
758                if(value != null) intent = value.intValue();
759            } else if (name.equals("Palette")) {
760                paletteSize = getIntAttribute(node, "sizeOfPalette");
761
762                r = new byte[paletteSize];
763                g = new byte[paletteSize];
764                b = new byte[paletteSize];
765                maxIndex = -1;
766                
767                Node paletteEntry = node.getFirstChild();
768                if (paletteEntry == null) {
769                    fatal(node, "Palette has no entries!");
770                }
771
772                int numPaletteEntries = 0;
773                while (paletteEntry != null) {
774                    if (!paletteEntry.getNodeName().equals("PaletteEntry")) {
775                        fatal(node,
776                              "Only a PaletteEntry may be a child of a Palette!");
777                    }
778
779                    int index = -1;
780                    Node subNode = paletteEntry.getFirstChild();
781                    while (subNode != null) {
782                        String subName = subNode.getNodeName();
783                        if(subName.equals("Index")) {
784                            Integer value = getIntegerValue(subNode);
785                            if(value != null)
786                                index = value.intValue();
787                            if (index < 0 || index > paletteSize - 1) {
788                                fatal(node,
789                                      "Bad value for PaletteEntry attribute index!");
790                            }
791                        } else if(subName.equals("Red")) {
792                            Integer value = getIntegerValue(subNode);
793                            if(value != null)
794                                red = value.intValue();
795                        } else if(subName.equals("Green")) {
796                            Integer value = getIntegerValue(subNode);
797                            if(value != null)
798                                green = value.intValue();
799                        } else if(subName.equals("Blue")) {
800                            Integer value = getIntegerValue(subNode);
801                            if(value != null)
802                                blue = value.intValue();
803                        }
804                        subNode = subNode.getNextSibling();
805                    }
806
807                    if(index == -1) {
808                        index = numPaletteEntries;
809                    }
810                    if (index > maxIndex) {
811                        maxIndex = index;
812                    }
813
814                    r[index] = (byte)red;
815                    g[index] = (byte)green;
816                    b[index] = (byte)blue;
817
818                    numPaletteEntries++;
819                    paletteEntry = paletteEntry.getNextSibling();
820                }
821            } else if (name.equals("CommentExtensions")) {
822                // CommentExtension
823                Node commentExtension = node.getFirstChild();
824                if (commentExtension == null) {
825                    fatal(node, "CommentExtensions has no entries!");
826                }
827
828                if(comments == null) {
829                    comments = new ArrayList();
830                }
831
832                while (commentExtension != null) {
833                    if (!commentExtension.getNodeName().equals("CommentExtension")) {
834                        fatal(node,
835                              "Only a CommentExtension may be a child of a CommentExtensions!");
836                    }
837
838                    comments.add(getAttribute(commentExtension, "value"));
839
840                    commentExtension = commentExtension.getNextSibling();
841                }
842            } else {
843                fatal(node, "Unknown child of root node!");
844            }
845            
846            node = node.getNextSibling();
847        }
848
849        if(r != null && g != null && b != null) {
850            boolean isVersion2 =
851                bmpVersion != null && bmpVersion.equals(VERSION_2);
852
853            int numEntries = maxIndex + 1;
854            palette = new byte[(isVersion2 ? 3 : 4)*numEntries];
855            for(int i = 0, j = 0; i < numEntries; i++) {
856                palette[j++] = b[i];
857                palette[j++] = g[i];
858                palette[j++] = r[i];
859                if(!isVersion2) j++; // skip reserved entry
860            }
861        }
862    }
863
864    private void mergeStandardTree(Node root)
865        throws IIOInvalidTreeException {
866        Node node = root;
867        if (!node.getNodeName()
868            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
869            fatal(node, "Root must be " +
870                  IIOMetadataFormatImpl.standardMetadataFormatName);
871        }
872
873        String colorSpaceType = null;
874        int numChannels = 0;
875        int[] bitsPerSample = null;
876        boolean hasAlpha = false;
877        
878        byte[] r = null, g = null, b = null;
879        int maxIndex = -1;
880
881        node = node.getFirstChild();
882        while(node != null) {
883            String name = node.getNodeName();
884
885            if (name.equals("Chroma")) {
886                Node child = node.getFirstChild();
887                while (child != null) {
888                    String childName = child.getNodeName();
889                    if (childName.equals("ColorSpaceType")) {
890                        colorSpaceType = getAttribute(child, "name");
891                    } else if (childName.equals("NumChannels")) {
892                        numChannels = getIntAttribute(child, "value");
893                    } else if (childName.equals("Gamma")) {
894                        gammaRed = gammaGreen = gammaBlue =
895                            (int)(getDoubleAttribute(child, "value") + 0.5);
896                    } else if (childName.equals("Palette")) {
897                        r = new byte[256];
898                        g  = new byte[256];
899                        b = new byte[256];
900                        maxIndex = -1;
901                
902                        Node paletteEntry = child.getFirstChild();
903                        if (paletteEntry == null) {
904                            fatal(node, "Palette has no entries!");
905                        }
906
907                        while (paletteEntry != null) {
908                            if (!paletteEntry.getNodeName().equals("PaletteEntry")) {
909                                fatal(node,
910                                      "Only a PaletteEntry may be a child of a Palette!");
911                            }
912                    
913                            int index = getIntAttribute(paletteEntry, "index");
914                            if (index < 0 || index > 255) {
915                                fatal(node,
916                                      "Bad value for PaletteEntry attribute index!");
917                            }
918                            if (index > maxIndex) {
919                                maxIndex = index;
920                            }
921                            r[index] =
922                                (byte)getIntAttribute(paletteEntry, "red");
923                            g[index] =
924                                (byte)getIntAttribute(paletteEntry, "green");
925                            b[index] =
926                                (byte)getIntAttribute(paletteEntry, "blue");
927                        
928                            paletteEntry = paletteEntry.getNextSibling();
929                        }
930                    }
931
932                    child = child.getNextSibling();
933                }
934            } else if (name.equals("Compression")) {
935                Node child = node.getFirstChild();
936                while(child != null) {
937                    String childName = child.getNodeName();
938                    if (childName.equals("CompressionTypeName")) {
939                        String compressionName = getAttribute(child, "value");
940                        compression =
941                            BMPImageWriter.getCompressionType(compressionName);
942                    }
943                    child = child.getNextSibling();
944                }
945            } else if (name.equals("Data")) {
946                Node child = node.getFirstChild();
947                while(child != null) {
948                    String childName = child.getNodeName();
949                    if (childName.equals("BitsPerSample")) {
950                        List bps = new ArrayList(4);
951                        String s = getAttribute(child, "value");
952                        StringTokenizer t = new StringTokenizer(s);
953                        while(t.hasMoreTokens()) {
954                            bps.add(Integer.valueOf(t.nextToken()));
955                        }
956                        bitsPerSample = new int[bps.size()];
957                        for(int i = 0; i < bitsPerSample.length; i++) {
958                            bitsPerSample[i] =
959                                ((Integer)bps.get(i)).intValue();
960                        }
961                        break;
962                    }
963                    child = child.getNextSibling();
964                }
965            } else if (name.equals("Dimension")) {
966                boolean gotWidth = false;
967                boolean gotHeight = false;
968                boolean gotAspectRatio = false;
969                boolean gotSpaceX = false;
970                boolean gotSpaceY = false;
971
972                double width = -1.0F;
973                double height = -1.0F;
974                double aspectRatio = -1.0F;
975                double spaceX = -1.0F;
976                double spaceY = -1.0F;
977                
978                Node child = node.getFirstChild();
979                while (child != null) {
980                    String childName = child.getNodeName();
981                    if (childName.equals("PixelAspectRatio")) {
982                        aspectRatio = getDoubleAttribute(child, "value");
983                        gotAspectRatio = true;
984                    } else if (childName.equals("HorizontalPixelSize")) {
985                        width = getDoubleAttribute(child, "value");
986                        gotWidth = true;
987                    } else if (childName.equals("VerticalPixelSize")) {
988                        height = getDoubleAttribute(child, "value");
989                        gotHeight = true;
990                    } else if (childName.equals("HorizontalPhysicalPixelSpacing")) {
991                        spaceX = getDoubleAttribute(child, "value");
992                        gotSpaceX = true;
993                    } else if (childName.equals("VerticalPhysicalPixelSpacing")) {
994                        spaceY = getDoubleAttribute(child, "value");
995                        gotSpaceY = true;
996                        // } else if (childName.equals("ImageOrientation")) {
997                        // } else if (childName.equals("HorizontalPosition")) {
998                        // } else if (childName.equals("VerticalPosition")) {
999                        // } else if (childName.equals("HorizontalPixelOffset")) {
1000                        // } else if (childName.equals("VerticalPixelOffset")) {
1001                    }
1002                    child = child.getNextSibling();
1003                }
1004
1005                // Use PhysicalPixelSpacing if PixelSize not available.
1006                if(!(gotWidth || gotHeight) && (gotSpaceX || gotSpaceY)) {
1007                    width = spaceX;
1008                    gotWidth = gotSpaceX;
1009                    height = spaceY;
1010                    gotHeight = gotSpaceY;
1011                }
1012
1013                // Round floating point values to obtain int resolution.
1014                if (gotWidth && gotHeight) {
1015                    xPixelsPerMeter = (int)(1000.0/width + 0.5);
1016                    yPixelsPerMeter = (int)(1000.0/height + 0.5);
1017                } else if (gotAspectRatio && aspectRatio != 0.0) {
1018                    if(gotWidth) {
1019                        xPixelsPerMeter = (int)(1000.0/width + 0.5);
1020                        yPixelsPerMeter =
1021                            (int)(aspectRatio*(1000.0/width) + 0.5);
1022                    } else if(gotHeight) {
1023                        xPixelsPerMeter =
1024                            (int)(1000.0/height/aspectRatio + 0.5);
1025                        yPixelsPerMeter = (int)(1000.0/height + 0.5);
1026                    }
1027                }
1028            } else if (name.equals("Document")) {
1029                Node child = node.getFirstChild();
1030                while(child != null) {
1031                    String childName = child.getNodeName();
1032                    if (childName.equals("FormatVersion")) {
1033                        bmpVersion = getAttribute(child, "value");
1034                        break;
1035                    }
1036                    child = child.getNextSibling();
1037                }
1038            } else if (name.equals("Text")) {
1039                Node child = node.getFirstChild();
1040                while(child != null) {
1041                    String childName = child.getNodeName();
1042                    if (childName.equals("TextEntry")) {
1043                        if(comments == null) {
1044                            comments = new ArrayList();
1045                        }
1046                        comments.add(getAttribute(child, "value"));
1047                    }
1048                    child = child.getNextSibling();
1049                }
1050            } else if (name.equals("Transparency")) {
1051                Node child = node.getFirstChild();
1052                while(child != null) {
1053                    String childName = child.getNodeName();
1054                    if (childName.equals("Alpha")) {
1055                        hasAlpha =
1056                            !getAttribute(child, "value").equals("none");
1057                        break;
1058                    }
1059                    child = child.getNextSibling();
1060                }
1061            } else {
1062                // XXX Ignore it.
1063            }
1064            
1065            node = node.getNextSibling();
1066        }
1067
1068        // Set bitsPerPixel.
1069        if(bitsPerSample != null) {
1070            if(palette != null && paletteSize > 0) {
1071                bitsPerPixel = (short)bitsPerSample[0];
1072            } else {
1073                bitsPerPixel = 0;
1074                for(int i = 0; i < bitsPerSample.length; i++) {
1075                    bitsPerPixel += bitsPerSample[i];
1076                }
1077            }
1078        } else if(palette != null) {
1079            bitsPerPixel = 8;
1080        } else if(numChannels == 1) {
1081            bitsPerPixel = 8;
1082        } else if(numChannels == 3) {
1083            bitsPerPixel = 24;
1084        } else if(numChannels == 4) {
1085            bitsPerPixel = 32;
1086        } else if(colorSpaceType.equals("GRAY")) {
1087            bitsPerPixel = 8;
1088        } else if(colorSpaceType.equals("RGB")) {
1089            bitsPerPixel = (short)(hasAlpha ? 32 : 24);
1090        }
1091
1092        // Set RGB masks.
1093        if((bitsPerSample != null && bitsPerSample.length == 4) ||
1094           bitsPerPixel >= 24) {
1095            redMask   = 0x00ff0000;
1096            greenMask = 0x0000ff00;
1097            blueMask  = 0x000000ff;
1098        }
1099
1100        // Set alpha mask.
1101        if((bitsPerSample != null && bitsPerSample.length == 4) ||
1102           bitsPerPixel > 24) {
1103            alphaMask = 0xff000000;
1104        }
1105
1106        // Set palette
1107        if(r != null && g != null && b != null) {
1108            boolean isVersion2 =
1109                bmpVersion != null && bmpVersion.equals(VERSION_2);
1110
1111            paletteSize = maxIndex + 1;
1112            palette = new byte[(isVersion2 ? 3 : 4)*paletteSize];
1113            for(int i = 0, j = 0; i < paletteSize; i++) {
1114                palette[j++] = b[i];
1115                palette[j++] = g[i];
1116                palette[j++] = r[i];
1117                if(!isVersion2) j++; // skip reserved entry
1118            }
1119        }
1120    }
1121
1122    public void reset() {
1123        // Fields for Image Descriptor
1124        bmpVersion = null;
1125        width = 0;
1126        height = 0;
1127        bitsPerPixel = 0;
1128        compression = 0;
1129        imageSize = 0;
1130
1131        // Fields for PixelsPerMeter
1132        xPixelsPerMeter = 0;
1133        yPixelsPerMeter = 0;
1134
1135        colorsUsed = 0;
1136        colorsImportant = 0;
1137
1138        // Fields for BI_BITFIELDS compression(Mask)
1139        redMask = 0;
1140        greenMask = 0;
1141        blueMask = 0;
1142        alphaMask = 0;
1143
1144        colorSpace = 0;
1145
1146        // Fields for CIE XYZ for the LCS_CALIBRATED_RGB color space
1147        redX = 0;
1148        redY = 0;
1149        redZ = 0;
1150        greenX = 0;
1151        greenY = 0;
1152        greenZ = 0;
1153        blueX = 0;
1154        blueY = 0;
1155        blueZ = 0;
1156
1157        // Fields for Gamma values for the LCS_CALIBRATED_RGB color space
1158        gammaRed = 0;
1159        gammaGreen = 0;
1160        gammaBlue = 0;
1161
1162        intent = 0;
1163
1164        // Fields for the Palette and Entries
1165        palette = null;
1166        paletteSize = 0;
1167        red = 0;
1168        green = 0;
1169        blue = 0;
1170
1171        // Fields from CommentExtension
1172        comments = null;
1173    }
1174
1175    private String countBits(int num) {
1176        int count = 0;
1177        while(num != 0) {
1178            if ((num & 1) == 1)
1179                count++;
1180            num >>>= 1;
1181        }
1182
1183        return count == 0 ? "0" : "" + count;
1184    }
1185
1186    private void addXYZPoints(IIOMetadataNode root, String name, double x, double y, double z) {
1187        IIOMetadataNode node = addChildNode(root, name, null);
1188        addChildNode(node, "X", new Double(x));
1189        addChildNode(node, "Y", new Double(y));
1190        addChildNode(node, "Z", new Double(z));
1191    }
1192
1193    private IIOMetadataNode addChildNode(IIOMetadataNode root,
1194                                         String name,
1195                                         Object object) {
1196        IIOMetadataNode child = new IIOMetadataNode(name);
1197        if (object != null) {
1198            child.setUserObject(object);
1199            child.setNodeValue(ImageUtil.convertObjectToString(object));
1200        }
1201        root.appendChild(child);
1202        return child;
1203    }
1204
1205    private Object getObjectValue(Node node) {
1206        Object tmp = node.getNodeValue();
1207
1208        if(tmp == null && node instanceof IIOMetadataNode) {
1209            tmp = ((IIOMetadataNode)node).getUserObject();
1210        }
1211
1212        return tmp;
1213    }
1214
1215    private String getStringValue(Node node) {
1216        Object tmp = getObjectValue(node);
1217        return tmp instanceof String ? (String)tmp : null;
1218    }
1219
1220    private Byte getByteValue(Node node) {
1221        Object tmp = getObjectValue(node);
1222        Byte value = null;
1223        if(tmp instanceof String) {
1224            value = Byte.valueOf((String)tmp);
1225        } else if(tmp instanceof Byte) {
1226            value = (Byte)tmp;
1227        }
1228        return value;
1229    }
1230
1231    private Short getShortValue(Node node) {
1232        Object tmp = getObjectValue(node);
1233        Short value = null;
1234        if(tmp instanceof String) {
1235            value = Short.valueOf((String)tmp);
1236        } else if(tmp instanceof Short) {
1237            value = (Short)tmp;
1238        }
1239        return value;
1240    }
1241
1242    private Integer getIntegerValue(Node node) {
1243        Object tmp = getObjectValue(node);
1244        Integer value = null;
1245        if(tmp instanceof String) {
1246            value = Integer.valueOf((String)tmp);
1247        } else if(tmp instanceof Integer) {
1248            value = (Integer)tmp;
1249        } else if(tmp instanceof Byte) {
1250            value = new Integer(((Byte)tmp).byteValue() & 0xff);
1251        }
1252        return value;
1253    }
1254
1255    private Double getDoubleValue(Node node) {
1256        Object tmp = getObjectValue(node);
1257        Double value = null;
1258        if(tmp instanceof String) {
1259            value = Double.valueOf((String)tmp);
1260        } else if(tmp instanceof Double) {
1261            value = (Double)tmp;
1262        }
1263        return value;
1264    }
1265}