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