001/*
002 * $RCSfile: J2KMetadata.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.4 $
042 * $Date: 2006/09/22 23:07:25 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.jpeg2000.impl;
046
047import java.awt.color.ColorSpace;
048import java.awt.color.ICC_ColorSpace;
049import java.awt.image.ColorModel;
050import java.awt.image.DataBuffer;
051import java.awt.image.IndexColorModel;
052import java.awt.image.SampleModel;
053import java.io.IOException;
054import java.util.ArrayList;
055import java.util.Iterator;
056
057import javax.imageio.ImageTypeSpecifier;
058import javax.imageio.ImageWriteParam;
059import javax.imageio.ImageWriter;
060import javax.imageio.metadata.IIOInvalidTreeException;
061import javax.imageio.metadata.IIOMetadata;
062import javax.imageio.metadata.IIOMetadataFormatImpl;
063import javax.imageio.metadata.IIOMetadataNode;
064import javax.imageio.stream.ImageInputStream;
065
066import jj2000.j2k.fileformat.FileFormatBoxes;
067import jj2000.j2k.fileformat.reader.FileFormatReader;
068import jj2000.j2k.io.RandomAccessIO;
069
070import org.w3c.dom.NamedNodeMap;
071import org.w3c.dom.Node;
072import org.w3c.dom.NodeList;
073
074/**
075 * Metadata for the J2K plug-in.
076 */
077public class J2KMetadata extends IIOMetadata implements Cloneable {
078    static final String nativeMetadataFormatName =
079        "com_sun_media_imageio_plugins_jpeg2000_image_1.0";
080
081    /** cache the metadata format */
082    private J2KMetadataFormat format;
083
084    /** The boxes of JP2 file used as meta data, i. e., all the boxes
085     *  except the data stream box
086     */
087    private ArrayList boxes = new ArrayList();
088
089    /**
090     * Constructor containing code shared by other constructors.
091     */
092    public J2KMetadata() {
093        super(true,  // Supports standard format
094              nativeMetadataFormatName,  // and a native format
095              "com.github.jaiimageio.jpeg2000.impl.J2KMetadataFormat",
096              null, null);  // No other formats
097
098        format = (J2KMetadataFormat)getMetadataFormat(nativeMetadataFormatName);
099    }
100
101    /*
102     * Constructs a <code>J2KMetadata</code> object by reading the
103     * contents of an <code>ImageInputStream</code>.  Has package-only
104     * access.
105     *
106     * @param iis An <code>ImageInputStream</code> from which to read
107     * the metadata.
108     * @param reader The <code>J2KImageReader</code> calling this
109     * constructor, to which warnings should be sent.
110     */
111    public J2KMetadata(ImageInputStream iis,
112                       J2KImageReader reader) throws IOException {
113        this();
114        RandomAccessIO in = new IISRandomAccessIO(iis);
115
116        iis.mark();
117        // **** File Format ****
118        // If the codestream is wrapped in the jp2 fileformat, Read the
119        // file format wrapper
120        FileFormatReader ff = new FileFormatReader(in, this);
121        ff.readFileFormat();
122        iis.reset();
123    }
124
125    /**
126     * Constructs a default stream <code>J2KMetadata</code> object appropriate
127     * for the given write parameters.
128     */
129    public J2KMetadata(ImageWriteParam param, ImageWriter writer) {
130        this(null, param, writer);
131    }
132
133    /**
134     * Constructs a default image <code>J2KMetadata</code> object appropriate
135     * for the given image type and write parameters.
136     */
137    public J2KMetadata(ImageTypeSpecifier imageType,
138                       ImageWriteParam param,
139                       ImageWriter writer) {
140        this(imageType != null ? imageType.getColorModel() : null,
141             imageType != null ? imageType.getSampleModel() : null,
142             0, 0,
143             param, writer);
144    }
145
146    /**
147     * Constructs a default image <code>J2KMetadata</code> object appropriate
148     * for the given image type and write parameters.
149     */
150    public J2KMetadata(ColorModel colorModel,
151                       SampleModel sampleModel,
152                       int width,
153                       int height,
154                       ImageWriteParam param,
155                       ImageWriter writer) {
156        this();
157        addNode(new SignatureBox());
158        addNode(new FileTypeBox(0x6A703220, 0, new int[]{0x6A703220}));
159
160        ImageTypeSpecifier destType = null;
161
162        if (param != null) {
163            destType = param.getDestinationType();
164            if (colorModel == null && sampleModel == null) {
165                colorModel = destType == null ? null : destType.getColorModel();
166                sampleModel =
167                    destType == null ? null : destType.getSampleModel();
168            }
169        }
170
171        int[] bitDepths = null;
172        if(colorModel != null) {
173            bitDepths = colorModel.getComponentSize();
174        } else if(sampleModel != null) {
175            bitDepths = sampleModel.getSampleSize();
176        }
177
178        int bitsPerComponent = 0xff;
179        if(bitDepths != null) {
180            bitsPerComponent = bitDepths[0];
181            int numComponents = bitDepths.length;
182            for(int i = 1; i < numComponents; i++) {
183                /* XXX: This statement should be removed when BPC behavior
184                   is corrected as derscribed below. */
185                if(bitDepths[i] > bitsPerComponent) {
186                    bitsPerComponent = bitDepths[i];
187                }
188                /* XXX: When the number of bits per component is not the
189                   same for all components the BPC parameter of the Image
190                   Header box should be set to 0xff and the actual number of
191                   bits per component written in the Bits Per Component box.
192                if(bitDepths[i] != bitsPerComponent) {
193                    bitsPerComponent = 0xff;
194                    break;
195                }
196                */
197            }
198        }
199
200        if (colorModel != null) {
201            ColorSpace cs = colorModel.getColorSpace();
202            boolean iccColor = (cs instanceof ICC_ColorSpace);
203            int type = cs.getType();
204
205            if (type == ColorSpace.TYPE_RGB) {
206                addNode(new ColorSpecificationBox((byte)1,
207                                                  (byte)0, (byte)0,
208                                                  ColorSpecificationBox.ECS_sRGB,
209                                                  null));
210            } else if (type == ColorSpace.TYPE_GRAY)
211                addNode(new ColorSpecificationBox((byte)1,
212                                                  (byte)0, (byte)0,
213                                                  ColorSpecificationBox.ECS_GRAY,
214                                                  null));
215            else if (cs instanceof ICC_ColorSpace)
216                addNode(new ColorSpecificationBox((byte)2,
217                                                  (byte)0, (byte)0,
218                                                  0,
219                                                  ((ICC_ColorSpace)cs).getProfile()));
220
221            if (colorModel.hasAlpha()) {
222                addNode(new ChannelDefinitionBox(colorModel));
223            }
224
225            if (colorModel instanceof IndexColorModel) {
226                addNode(new PaletteBox((IndexColorModel)colorModel));
227                int numComp = colorModel.getComponentSize().length;
228                short[] channels = new short[numComp];
229                byte[] types = new byte[numComp];
230                byte[] maps = new byte[numComp];
231                for (int i = 0; i < numComp; i++) {
232                    channels[i] = 0;
233                    types[i] = 1;
234                    maps[i] = (byte)i;
235                }
236                addNode(new ComponentMappingBox(channels, types, maps));
237            }
238        }
239
240        if (sampleModel != null) {
241            if (width <= 0)
242                width = sampleModel.getWidth();
243            if (height <= 0)
244                height = sampleModel.getHeight();
245            int bpc = bitsPerComponent == 0xff ?
246                0xff : ((bitsPerComponent - 1) |
247                        (isOriginalSigned(sampleModel) ? 0x80 : 0));
248            addNode(new HeaderBox(height,
249                                  width,
250                                  sampleModel.getNumBands(),
251                                  bpc,
252                                  7,
253                                  colorModel == null ? 1 : 0,
254                                  getElement("JPEG2000IntellectualPropertyRightsBox")==null ? 0 : 1));
255        }
256    }
257
258    public Object clone() {
259        J2KMetadata theClone = null;
260
261        try {
262            theClone = (J2KMetadata) super.clone();
263        } catch (CloneNotSupportedException e) {} // won't happen
264
265        if (boxes != null) {
266            int numBoxes = boxes.size();
267            for(int i = 0; i < numBoxes; i++) {
268                theClone.addNode((Box)boxes.get(i));
269            }
270        }
271        return theClone;
272    }
273
274    public Node getAsTree(String formatName) {
275        if (formatName == null) {
276            throw new IllegalArgumentException(I18N.getString("J2KMetadata0"));
277        }
278
279        if (formatName.equals(nativeMetadataFormatName)) {
280            return getNativeTree();
281        }
282
283        if (formatName.equals
284            (IIOMetadataFormatImpl.standardMetadataFormatName)) {
285            return getStandardTree();
286        }
287
288        throw  new IllegalArgumentException(I18N.getString("J2KMetadata1")
289                                            + " " + formatName);
290    }
291
292    IIOMetadataNode getNativeTree() {
293        IIOMetadataNode root =
294            new IIOMetadataNode(nativeMetadataFormatName);
295
296        Box signatureBox = null, fileTypeBox = null, headerBox = null;
297        int signatureIndex = -1, fileTypeIndex = -1, headerIndex = -1;
298
299        int numBoxes = boxes.size();
300
301        int found = 0;
302        for(int i = 0; i < numBoxes && found < 3; i++) {
303            Box box = (Box)boxes.get(i);
304            if(Box.getName(box.getType()).equals("JPEG2000SignatureBox")) {
305                signatureBox = box;
306                signatureIndex = i;
307                found++;
308            } else if(Box.getName(box.getType()).equals("JPEG2000FileTypeBox")) {
309                fileTypeBox = box;
310                fileTypeIndex = i;
311                found++;
312            } else if(Box.getName(box.getType()).equals("JPEG2000HeaderBox")) {
313                headerBox = box;
314                headerIndex = i;
315                found++;
316            }
317        }
318
319        if(signatureBox != null) {
320            insertNodeIntoTree(root, signatureBox.getNativeNode());
321        }
322
323        if(fileTypeBox != null) {
324            insertNodeIntoTree(root, fileTypeBox.getNativeNode());
325        }
326
327        if(headerBox != null) {
328            insertNodeIntoTree(root, headerBox.getNativeNode());
329        }
330
331        for(int i = 0; i < numBoxes; i++) {
332            if(i == signatureIndex ||
333               i == fileTypeIndex  ||
334               i == headerIndex) continue;
335            Box box = (Box)boxes.get(i);
336            IIOMetadataNode node = box.getNativeNode();
337            insertNodeIntoTree(root, node);
338        }
339        return root;
340    }
341
342    // Standard tree node methods
343    protected IIOMetadataNode getStandardChromaNode() {
344        HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox");
345        PaletteBox palette = (PaletteBox)getElement("JPEG2000PaletteBox");
346        ColorSpecificationBox color =
347            (ColorSpecificationBox)getElement("JPEG2000ColorSpecificationBox");
348
349        IIOMetadataNode node = new IIOMetadataNode("Chroma");
350        IIOMetadataNode subNode = null;
351        if (header != null) {
352            if (header.getUnknownColorspace() == 0) {
353                if (color != null && color.getMethod() == 1) {
354                    subNode = new IIOMetadataNode("ColorSpaceType");
355                    int ecs = color.getEnumeratedColorSpace();
356                    if (ecs == FileFormatBoxes.CSB_ENUM_SRGB)
357                        subNode.setAttribute("name", "RGB");
358                    if (ecs == FileFormatBoxes.CSB_ENUM_GREY)
359                        subNode.setAttribute("name", "GRAY");
360                    node.appendChild(subNode);
361                }
362            }
363
364            subNode = new IIOMetadataNode("NumChannels");
365            subNode.setAttribute("value", "" + header.getNumComponents());
366            node.appendChild(subNode);
367
368            if (palette != null) {
369                subNode.setAttribute("value", "" + palette.getNumComp());
370                subNode = new IIOMetadataNode("Palette");
371                byte[][] lut = palette.getLUT();
372
373                int size = lut[0].length;
374                int numComp = lut.length;
375
376                for (int i = 0; i < size; i++) {
377                    IIOMetadataNode subNode1 =
378                        new IIOMetadataNode("PaletteEntry");
379                    subNode1.setAttribute("index", ""+i);
380                    subNode1.setAttribute("red", "" + (lut[0][i]&0xff));
381                    subNode1.setAttribute("green", "" + (lut[1][i]&0xff));
382                    subNode1.setAttribute("blue", "" + (lut[2][i]&0xff));
383                    if (numComp == 4)
384                        subNode1.setAttribute("alpha", "" + (lut[3][i]&0xff));
385                    subNode.appendChild(subNode1);
386                }
387                node.appendChild(subNode);
388            }
389        }
390        return node;
391    }
392
393    protected IIOMetadataNode getStandardCompressionNode() {
394        IIOMetadataNode node = new IIOMetadataNode("Compression");
395
396        // CompressionTypeName
397        IIOMetadataNode subNode = new IIOMetadataNode("CompressionTypeName");
398        subNode.setAttribute("value", "JPEG2000");
399        node.appendChild(subNode);
400        return node;
401    }
402
403    protected IIOMetadataNode getStandardDataNode() {
404        IIOMetadataNode node = new IIOMetadataNode("Data");
405        PaletteBox palette = (PaletteBox)getElement("JPEG2000PaletteBox");
406        boolean sampleFormat = false;
407
408        if (palette != null) {
409            IIOMetadataNode subNode = new IIOMetadataNode("SampleFormat");
410            subNode.setAttribute("value", "Index");
411            node.appendChild(subNode);
412            sampleFormat = true;
413        }
414
415        BitsPerComponentBox bitDepth =
416            (BitsPerComponentBox)getElement("JPEG2000BitsPerComponentBox");
417        String value = "";
418        boolean signed = false;
419        boolean gotSampleInfo = false;
420
421        // JPEG 2000 "B" parameter represents "bitDepth - 1" in the
422        // right 7 least significant bits with the most significant
423        // bit indicating signed if set and unsigned if not.
424        if (bitDepth != null) {
425            byte[] bits = bitDepth.getBitDepth();
426            if ((bits[0] & 0x80) == 0x80)
427                signed = true;
428
429            int numComp = bits.length;
430            for (int i = 0; i < numComp; i++) {
431                value += (bits[i] & 0x7f) + 1;
432                if(i != numComp - 1) value += " ";
433            }
434
435            gotSampleInfo = true;
436        } else {
437            HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox");
438            if(header != null) {
439                int bits = header.getBitDepth();
440                if ((bits & 0x80) == 0x80)
441                    signed = true;
442                bits = (bits & 0x7f) + 1;
443                int numComp = header.getNumComponents();
444                for (int i = 0; i < numComp; i++) {
445                    value += bits;
446                    if(i != numComp - 1) value += " ";
447                }
448
449                gotSampleInfo = true;
450            }
451        }
452
453        IIOMetadataNode subNode = null;
454
455        if(gotSampleInfo) {
456            subNode = new IIOMetadataNode("BitsPerSample");
457            subNode.setAttribute("value", value);
458            node.appendChild(subNode);
459        }
460
461        subNode = new IIOMetadataNode("PlanarConfiguration");
462        subNode.setAttribute("value", "TileInterleaved");
463        node.appendChild(subNode);
464
465        if (!sampleFormat && gotSampleInfo) {
466            subNode = new IIOMetadataNode("SampleFormat");
467            subNode.setAttribute("value",
468                             signed ? "SignedIntegral": "UnsignedIntegral");
469            node.appendChild(subNode);
470        }
471
472        return node;
473    }
474
475    protected IIOMetadataNode getStandardDimensionNode() {
476        ResolutionBox box =
477            (ResolutionBox)getElement("JPEG2000CaptureResolutionBox");
478        if (box != null) {
479            IIOMetadataNode node = new IIOMetadataNode("Dimension");
480            float hRes = box.getHorizontalResolution();
481            float vRes = box.getVerticalResolution();
482            float ratio = vRes / hRes;
483            IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio");
484            subNode.setAttribute("value", "" + ratio);
485            node.appendChild(subNode);
486
487            subNode = new IIOMetadataNode("HorizontalPixelSize");
488            subNode.setAttribute("value", "" + (1000 / hRes));
489            node.appendChild(subNode);
490
491            subNode = new IIOMetadataNode("VerticalPixelSize");
492            subNode.setAttribute("value", "" + (1000 / vRes));
493            node.appendChild(subNode);
494
495            return node;
496        }
497
498        return null;
499    }
500
501    protected IIOMetadataNode getStandardTransparencyNode() {
502        ChannelDefinitionBox channel =
503            (ChannelDefinitionBox)getElement("JPEG2000ChannelDefinitionBox");
504        if (channel != null) {
505            IIOMetadataNode node = new IIOMetadataNode("Transparency");
506
507            boolean hasAlpha = false;
508            boolean isPremultiplied = false;
509            short[] type = channel.getTypes();
510
511            for (int i = 0; i < type.length; i++) {
512                if (type[i] == 1)
513                    hasAlpha = true;
514                if (type[i] == 2)
515                    isPremultiplied = true;
516            }
517
518            String value = "none";
519            if (isPremultiplied)
520                value = "premultiplied";
521            else if (hasAlpha)
522                value = "nonpremultiplied";
523
524            IIOMetadataNode subNode = new IIOMetadataNode("Alpha");
525            subNode.setAttribute("value", value);
526            node.appendChild(subNode);
527
528            return node;
529        }
530
531        IIOMetadataNode node = new IIOMetadataNode("Transparency");
532        IIOMetadataNode subNode = new IIOMetadataNode("Alpha");
533        subNode.setAttribute("value", "none");
534        node.appendChild(subNode);
535
536        return null;
537    }
538
539    protected IIOMetadataNode getStandardTextNode() {
540        if (boxes == null)
541            return null;
542        IIOMetadataNode text = null;
543
544        Iterator iterator = boxes.iterator();
545
546        while(iterator.hasNext()) {
547            Box box = (Box)iterator.next();
548            if (box instanceof XMLBox) {
549                if (text == null)
550                    text = new IIOMetadataNode("Text");
551                IIOMetadataNode subNode = new IIOMetadataNode("TextEntry");
552                String content = new String(box.getContent());
553                subNode.setAttribute("value", content);
554                text.appendChild(subNode);
555            }
556        }
557        return text;
558    }
559
560    public boolean isReadOnly() {
561        return false;
562    }
563
564    public void mergeTree(String formatName, Node root)
565        throws IIOInvalidTreeException {
566        if (formatName == null) {
567            throw new IllegalArgumentException(I18N.getString("J2KMetadata0"));
568        }
569
570        if (root == null) {
571            throw new IllegalArgumentException(I18N.getString("J2KMetadata2"));
572        }
573
574        if (formatName.equals(nativeMetadataFormatName) &&
575            root.getNodeName().equals(nativeMetadataFormatName)) {
576            mergeNativeTree(root);
577        } else if (formatName.equals
578                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
579            mergeStandardTree(root);
580        } else {
581            throw  new IllegalArgumentException(I18N.getString("J2KMetadata1")
582                                                + " " + formatName);
583        }
584    }
585
586    public void setFromTree(String formatName, Node root)
587        throws IIOInvalidTreeException {
588        if (formatName == null) {
589            throw new IllegalArgumentException(I18N.getString("J2KMetadata0"));
590        }
591
592        if (root == null) {
593            throw new IllegalArgumentException(I18N.getString("J2KMetadata2"));
594        }
595
596        if (formatName.equals(nativeMetadataFormatName) &&
597            root.getNodeName().equals(nativeMetadataFormatName)) {
598            boxes = new ArrayList();
599            mergeNativeTree(root);
600        } else if (formatName.equals
601                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
602            boxes = new ArrayList();
603            mergeStandardTree(root);
604        } else {
605            throw  new IllegalArgumentException(I18N.getString("J2KMetadata1")
606                                                + " " + formatName);
607        }
608    }
609
610    public void reset() {
611        boxes.clear();
612    }
613
614    public void addNode(Box node) {
615        if (boxes == null)
616            boxes = new ArrayList();
617        replace(Box.getName(node.getType()), node);
618    }
619
620    public Box getElement(String name) {
621        for (int i = boxes.size() - 1; i >= 0; i--) {
622            Box box = (Box)boxes.get(i);
623            if (name.equals(Box.getName(box.getType())))
624                return box;
625        }
626        return null;
627    }
628
629    private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
630        NodeList list = root.getChildNodes();
631        for (int i = list.getLength() - 1; i >= 0; i--) {
632            Node node = list.item(i);
633            String name = node.getNodeName();
634            if (format.getParent(name) != null) {
635                if (format.isLeaf(name)) {
636                    String s = (String)Box.getAttribute(node, "Type");
637                    Box box = Box.createBox(Box.getTypeInt(s), node);
638                    if (format.singleInstance(name)&&getElement(name) != null) {
639                        replace(name, box);
640                    } else
641                        boxes.add(box);
642                } else {
643                    mergeNativeTree(node);
644                }
645            }
646        }
647    }
648
649    private void mergeStandardTree(Node root) throws IIOInvalidTreeException {
650        NodeList children = root.getChildNodes();
651        int numComps = 0;
652
653        for (int i = 0; i < children.getLength(); i++) {
654            Node node = children.item(i);
655            String name = node.getNodeName();
656            if (name.equals("Chroma")) {
657                NodeList children1 = node.getChildNodes();
658                for (int j = 0; j < children1.getLength(); j++) {
659                    Node child = children1.item(j);
660                    String name1 = child.getNodeName();
661
662                    if (name1.equals("NumChannels")) {
663                        String s = (String)Box.getAttribute(child, "value");
664                        numComps = new Integer(s).intValue();
665                    }
666
667                    if (name1.equals("ColorSpaceType"))
668                        createColorSpecificationBoxFromStandardNode(child);
669
670                    if (name1.equals("Palette")) {
671                        createPaletteBoxFromStandardNode(child);
672                    }
673                }
674            } else if (name.equals("Compression")) {
675                // Intentionally do nothing: just prevent entry into
676                // the default "else" block and an ensuing
677                // IIOInvalidTreeException; fixes 5110389.
678            } else if (name.equals("Data")) {
679                createBitsPerComponentBoxFromStandardNode(node);
680                createHeaderBoxFromStandardNode(node, numComps);
681            } else if (name.equals("Dimension")) {
682                createResolutionBoxFromStandardNode(node);
683            } else if (name.equals("Document")) {
684                createXMLBoxFromStandardNode(node);
685            } else if (name.equals("Text")) {
686                createXMLBoxFromStandardNode(node);
687            } else if (name.equals("Transparency")) {
688                createChannelDefinitionFromStandardNode(node);
689            } else {
690                throw new IIOInvalidTreeException(I18N.getString("J2KMetadata3")
691                                        + " " + name, node);
692            }
693        }
694    }
695
696    private void createColorSpecificationBoxFromStandardNode(Node node) {
697        if (node.getNodeName() != "ColorSpaceType")
698            throw new IllegalArgumentException(I18N.getString("J2KMetadata4"));
699        String name = (String)Box.getAttribute(node, "name");
700        int ecs = name.equals("RGB") ? ColorSpecificationBox.ECS_sRGB :
701                  (name.equals("Gray") ? ColorSpecificationBox.ECS_GRAY : 0);
702
703        if (ecs == ColorSpecificationBox.ECS_sRGB ||
704            ecs ==ColorSpecificationBox.ECS_GRAY) {
705            replace ("JPEG2000ColorSpecificationBox",
706                     new ColorSpecificationBox((byte)1, (byte)0, (byte)0,
707                                               ecs, null));
708        }
709    }
710
711    private void createPaletteBoxFromStandardNode(Node node) {
712        if (node.getNodeName() != "Palette")
713            throw new IllegalArgumentException(I18N.getString("J2KMetadata5"));
714        NodeList children = node.getChildNodes();
715        int maxIndex = -1;
716        boolean hasAlpha = false;
717        for (int i = 0; i < children.getLength(); i++) {
718            Node child = children.item(i);
719            String name = child.getNodeName();
720
721            if (name.equals("PaletteEntry")) {
722                String s = (String)Box.getAttribute(child, "index");
723                int index = new Integer(s).intValue();
724                if(index > maxIndex) {
725                    maxIndex = index;
726                }
727                if(Box.getAttribute(child, "alpha") != null) {
728                    hasAlpha = true;
729                }
730            }
731        }
732
733        // Determine palette size.
734        int numBits = 32;
735        int mask = 0x80000000;
736        while(mask != 0 && (maxIndex & mask) == 0) {
737            numBits--;
738            mask >>>= 1;
739        }
740        int size = 1 << numBits;
741
742        byte[] red = new byte[size];
743        byte[] green = new byte[size];
744        byte[] blue = new byte[size];
745        byte[] alpha = hasAlpha ? new byte[size]: null;
746
747        for (int i = 0; i < children.getLength(); i++) {
748            Node child = children.item(i);
749            String name = child.getNodeName();
750
751            if (name.equals("PaletteEntry")) {
752                String s = (String)Box.getAttribute(child, "index");
753                int index = new Integer(s).intValue();
754                s = (String)Box.getAttribute(child, "red");
755                red[index] = (byte)(new Integer(s).intValue());
756                s = (String)Box.getAttribute(child, "green");
757                green[index] = (byte)(new Integer(s).intValue());
758                s = (String)Box.getAttribute(child, "blue");
759                blue[index] = (byte)(new Integer(s).intValue());
760
761                byte t = (byte)255;
762                s = (String)Box.getAttribute(child, "alpha");
763                if(s != null) {
764                    t = (byte)(new Integer(s).intValue());
765                }
766
767                if(alpha != null) {
768                    alpha[index] = t;
769                }
770            }
771        }
772
773        IndexColorModel icm;
774        if (alpha == null)
775            icm = new IndexColorModel(numBits, size, red, green, blue);
776        else
777            icm = new IndexColorModel(numBits, size, red, green, blue, alpha);
778
779        replace("JPEG2000PaletteBox", new PaletteBox(icm));
780    }
781
782    private void createBitsPerComponentBoxFromStandardNode(Node node) {
783        if (node.getNodeName() != "Data")
784            throw new IllegalArgumentException(I18N.getString("J2KMetadata6"));
785
786        NodeList children = node.getChildNodes();
787
788        byte[] bits = null;
789        boolean isSigned = false;
790        for (int i = 0; i < children.getLength(); i++) {
791            Node child = children.item(i);
792            String name = child.getNodeName();
793
794            if (name.equals("BitsPerSample")) {
795                String s = (String)Box.getAttribute(child, "value");
796                bits = (byte[])Box.parseByteArray(s).clone();
797            } else if(name.equals("SampleFormat")) {
798                String s = (String)Box.getAttribute(child, "value");
799                isSigned = s.equals("SignedIntegral");
800            }
801        }
802
803        if(bits != null) {
804            // JPEG 2000 "B" parameter represents "bitDepth - 1" in the
805            // right 7 least significant bits with the most significant
806            // bit indicating signed if set and unsigned if not.
807            for (int i = 0; i < bits.length; i++) {
808                bits[i] = (byte)((bits[i]&0xff) - 1);
809                if(isSigned) {
810                    bits[i] |= 0x80;
811                }
812            }
813
814            replace("JPEG2000BitsPerComponent",
815                    new BitsPerComponentBox(bits));
816        }
817    }
818
819    private void createResolutionBoxFromStandardNode(Node node) {
820        if (node.getNodeName() != "Dimension")
821            throw new IllegalArgumentException(I18N.getString("J2KMetadata7"));
822        NodeList children = node.getChildNodes();
823        float hRes = 0.0f;
824        float vRes = 0.0f;
825
826        boolean gotH = false, gotV = false;
827
828        for (int i = 0; i < children.getLength(); i++) {
829            Node child = children.item(i);
830            String name = child.getNodeName();
831
832            if (name.equals("HorizontalPixelSize")) {
833                String s = (String)Box.getAttribute(child, "value");
834                hRes = new Float(s).floatValue();
835                hRes = 1000 / hRes;
836                gotH = true;
837            }
838
839            if (name.equals("VerticalPixelSize")) {
840                String s = (String)Box.getAttribute(child, "value");
841                vRes = new Float(s).floatValue();
842                vRes = 1000 / vRes;
843                gotV = true;
844            }
845        }
846
847        if(gotH && !gotV) {
848            vRes = hRes;
849        } else if(gotV && !gotH) {
850            hRes = vRes;
851        }
852
853        if(gotH || gotV) {
854            replace("JPEG2000CaptureResolutionBox",
855                    new ResolutionBox(0x72657363, hRes, vRes));
856        }
857    }
858
859    private void createXMLBoxFromStandardNode(Node node) {
860        NodeList children = node.getChildNodes();
861        String value = "<" + node.getNodeName() + ">";
862
863        for (int i = 0; i < children.getLength(); i++) {
864            Node child = children.item(i);
865            String name = child.getNodeName();
866            value += "<" + name + " ";
867
868            NamedNodeMap map = child.getAttributes();
869
870            for (int j = 0; j < map.getLength(); j++) {
871                Node att = map.item(j);
872                value += att.getNodeName() + "=\"" +
873                    att.getNodeValue() + "\" ";
874            }
875
876            value += " />";
877        }
878
879        value += "</" + node.getNodeName() + ">";
880
881        boxes.add(new XMLBox(value.getBytes()));
882    }
883
884    private void createHeaderBoxFromStandardNode(Node node, int numComps) {
885        HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox");
886        byte unknownColor =
887            (byte)(getElement("JPEG2000ColorSpecificationBox") == null ? 1: 0);
888        if (header != null) {
889            if (numComps ==0);
890                numComps = header.getNumComponents();
891
892            header = new HeaderBox(header.getHeight(), header.getWidth(),
893                                   numComps,
894                                   header.getBitDepth(),
895                                   header.getCompressionType(),
896                                   unknownColor,
897                                   header.getIntellectualProperty());
898        } else {
899            header = new HeaderBox(0, 0, numComps, 0, 0, unknownColor, 0);
900        }
901        replace("JPEG2000HeaderBox", header);
902    }
903
904    private void createChannelDefinitionFromStandardNode(Node node) {
905        if (node.getNodeName() != "Transparency")
906            throw new IllegalArgumentException(I18N.getString("J2KMetadata8"));
907
908        HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox");
909        int numComps = 3;
910
911        if (header != null) {
912            numComps = header.getNumComponents();
913        }
914
915        NodeList children = node.getChildNodes();
916        boolean hasAlpha = false;
917        boolean isPremultiplied = false;
918
919        for (int i = 0; i < children.getLength(); i++) {
920            Node child = children.item(i);
921            String name = child.getNodeName();
922
923            if (name.equals("Alpha")) {
924                String value = (String)Box.getAttribute(child, "value");
925                if (value.equals("premultiplied"))
926                    isPremultiplied = true;
927                if (value.equals("nonpremultiplied"))
928                    hasAlpha = true;
929            }
930        }
931
932        if (!hasAlpha)
933            return;
934
935        int num = (short)(numComps * (isPremultiplied ? 3 : 2));
936        short[] channels = new short[num];
937        short[] types = new short[num];
938        short[] associations = new short[num];
939        ChannelDefinitionBox.fillBasedOnBands(numComps, isPremultiplied,
940                                              channels, types, associations);
941        replace("JPEG2000ChannelDefinitionBox",
942                new ChannelDefinitionBox(channels, types, associations));
943    }
944
945    private void replace(String name, Box box) {
946        for (int i = boxes.size() - 1; i >= 0; i--) {
947            Box box1 = (Box)boxes.get(i);
948            if (name.equals(Box.getName(box1.getType()))) {
949                boxes.set(i, box);
950                return;
951            }
952        }
953
954        boxes.add(box);
955    }
956
957    private boolean insertNodeIntoTree(IIOMetadataNode root,
958                                       IIOMetadataNode node) {
959        String name = node.getNodeName();
960        String parent = format.getParent(name);
961        if (parent == null)
962            return false;
963
964        IIOMetadataNode parentNode = getNodeFromTree(root, parent, name);
965        if (parentNode == null)
966            parentNode = createNodeIntoTree(root, parent);
967        parentNode.appendChild(node);
968        return true;
969    }
970
971    private IIOMetadataNode getNodeFromTree(IIOMetadataNode root,
972                                            String name,
973                                            String childName) {
974        if (name.equals(root.getNodeName()))
975            return root;
976
977        NodeList list = root.getChildNodes();
978        for (int i = 0; i < list.getLength(); i++) {
979            IIOMetadataNode node = (IIOMetadataNode)list.item(i);
980            if (node.getNodeName().equals(name)) {
981                if (name.equals("JPEG2000UUIDInfoBox") &&
982                    checkUUIDInfoBox(node, childName))
983                    continue;
984                else
985                    return node;
986            }
987            node = getNodeFromTree(node, name, childName);
988            if (node != null)
989                return node;
990        }
991
992        return null;
993    }
994
995    private IIOMetadataNode createNodeIntoTree(IIOMetadataNode root,
996                                               String name) {
997        IIOMetadataNode node = getNodeFromTree(root, name, null);
998        if (node != null)
999            return node;
1000
1001        node = new IIOMetadataNode(name);
1002
1003        String parent = format.getParent(name);
1004        IIOMetadataNode parentNode = createNodeIntoTree(root, parent);
1005        parentNode.appendChild(node);
1006
1007        return node;
1008    }
1009
1010    private boolean isOriginalSigned(SampleModel sampleModel) {
1011        int type = sampleModel.getDataType();
1012        if (type == DataBuffer.TYPE_BYTE || type == DataBuffer.TYPE_USHORT)
1013            return false;
1014        return true;
1015    }
1016
1017    /** Check whether the child with a name <code>childName</code> exists.
1018     *  This method is designed because UUID info box may have many instances.
1019     *  So if one of its sub-box is inserted into the tree, an empty slut for
1020     *  this sub-box has to be find or created to avoid one UUID info box
1021     *  has duplicated sub-boxes.  The users have to guarantee each UUID info
1022     *  box has all the sub-boxes.
1023     */
1024    private boolean checkUUIDInfoBox(Node node, String childName) {
1025
1026        NodeList list = node.getChildNodes();
1027        for (int i = 0; i < list.getLength(); i++) {
1028            IIOMetadataNode child = (IIOMetadataNode)list.item(i);
1029            String name = child.getNodeName();
1030
1031            if (name.equals(childName))
1032                return true;
1033        }
1034
1035        return false;
1036    }
1037}