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