001/*
002 * $RCSfile: PNMMetadata.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.1 $
042 * $Date: 2005/02/11 05:01:41 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.pnm;
046
047import java.awt.image.SampleModel;
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.Iterator;
051import java.util.List;
052import java.util.StringTokenizer;
053
054import javax.imageio.ImageTypeSpecifier;
055import javax.imageio.ImageWriteParam;
056import javax.imageio.metadata.IIOInvalidTreeException;
057import javax.imageio.metadata.IIOMetadata;
058import javax.imageio.metadata.IIOMetadataFormatImpl;
059import javax.imageio.metadata.IIOMetadataNode;
060
061import org.w3c.dom.NamedNodeMap;
062import org.w3c.dom.Node;
063import org.w3c.dom.NodeList;
064
065import com.github.jaiimageio.impl.common.ImageUtil;
066import com.github.jaiimageio.plugins.pnm.PNMImageWriteParam;
067/**
068 * Metadata for the PNM plug-in.
069 */
070public class PNMMetadata extends IIOMetadata implements Cloneable {
071    static final String nativeMetadataFormatName =
072        "com_sun_media_imageio_plugins_pnm_image_1.0";
073
074    /** The max value for the encoded/decoded image. */
075    private int maxSample;
076
077    /** The image width. */
078    private int width;
079
080    /** The image height. */
081    private int height;
082
083    /** The image variants. */
084    private int variant;
085
086    /** The comments. */
087    private ArrayList comments;
088
089    /** Maximum number of bits per sample (not in metadata). */
090    private int maxSampleSize;
091
092    /**
093     * Constructor containing code shared by other constructors.
094     */
095    PNMMetadata() {
096        super(true,  // Supports standard format
097              nativeMetadataFormatName,  // and a native format
098              "com.github.jaiimageio.impl.plugins.pnm.PNMMetadataFormat",
099              null, null);  // No other formats
100    }
101
102    public PNMMetadata(IIOMetadata metadata) throws IIOInvalidTreeException {
103
104        this();
105
106        if(metadata != null) {
107            List formats = Arrays.asList(metadata.getMetadataFormatNames());
108
109            if(formats.contains(nativeMetadataFormatName)) {
110                // Initialize from native image metadata format.
111                setFromTree(nativeMetadataFormatName,
112                            metadata.getAsTree(nativeMetadataFormatName));
113            } else if(metadata.isStandardMetadataFormatSupported()) {
114                // Initialize from standard metadata form of the input tree.
115                String format =
116                    IIOMetadataFormatImpl.standardMetadataFormatName;
117                setFromTree(format, metadata.getAsTree(format));
118            }
119        }
120    }
121
122    /**
123     * Constructs a default image <code>PNMMetadata</code> object appropriate
124     * for the given image type and write parameters.
125     */
126    PNMMetadata(ImageTypeSpecifier imageType,
127                ImageWriteParam param) {
128        this();
129        initialize(imageType, param);
130    }
131
132    void initialize(ImageTypeSpecifier imageType,
133                    ImageWriteParam param) {
134        ImageTypeSpecifier destType = null;
135
136        if (param != null) {
137            destType = param.getDestinationType();
138            if (destType == null) {
139                destType = imageType;
140            }
141        } else {
142            destType = imageType;
143        }
144
145        if (destType != null) {
146            SampleModel sm = destType.getSampleModel();
147            int[] sampleSize = sm.getSampleSize();
148
149            this.width = sm.getWidth();
150            this.height = sm.getHeight();
151
152            for (int i = 0; i < sampleSize.length; i++) {
153                if (sampleSize[i] > maxSampleSize) {
154                    maxSampleSize = sampleSize[i];
155                }
156            }
157            this.maxSample = (1 << maxSampleSize) - 1;
158
159            boolean isRaw = true; // default value
160            if(param instanceof PNMImageWriteParam) {
161                isRaw = ((PNMImageWriteParam)param).getRaw();
162            }
163
164            if (maxSampleSize == 1)
165                variant = '1';
166            else if (sm.getNumBands() == 1) {
167                variant = '2';
168            } else if (sm.getNumBands() == 3) {
169                variant = '3';
170            }
171
172            // Force to Raw if the sample size is small enough.
173            if (variant <= '3' && isRaw && maxSampleSize <= 8) {
174                variant += 0x3;
175            }
176        }
177    }
178
179    protected Object clone() {
180        PNMMetadata theClone = null;
181
182        try {
183            theClone = (PNMMetadata) super.clone();
184        } catch (CloneNotSupportedException e) {} // won't happen
185
186        if (comments != null) {
187            int numComments = comments.size();
188            for(int i = 0; i < numComments; i++) {
189                theClone.addComment((String)comments.get(i));
190            }
191        }
192        return theClone;
193    }
194
195    public Node getAsTree(String formatName) {
196        if (formatName == null) {
197            throw new IllegalArgumentException(I18N.getString("PNMMetadata0"));
198        }
199
200        if (formatName.equals(nativeMetadataFormatName)) {
201            return getNativeTree();
202        }
203
204        if (formatName.equals
205            (IIOMetadataFormatImpl.standardMetadataFormatName)) {
206            return getStandardTree();
207        }
208
209        throw  new IllegalArgumentException(I18N.getString("PNMMetadata1") + " " +
210                                                formatName);
211    }
212
213    IIOMetadataNode getNativeTree() {
214        IIOMetadataNode root =
215            new IIOMetadataNode(nativeMetadataFormatName);
216
217        IIOMetadataNode child = new IIOMetadataNode("FormatName");
218        child.setUserObject(getFormatName());
219        child.setNodeValue(getFormatName());
220        root.appendChild(child);
221
222        child = new IIOMetadataNode("Variant");
223        child.setUserObject(getVariant());
224        child.setNodeValue(getVariant());
225        root.appendChild(child);
226
227        child = new IIOMetadataNode("Width");
228        Object tmp = new Integer(width);
229        child.setUserObject(tmp);
230        child.setNodeValue(ImageUtil.convertObjectToString(tmp));
231        root.appendChild(child);
232
233        child = new IIOMetadataNode("Height");
234        tmp = new Integer(height);
235        child.setUserObject(tmp);
236        child.setNodeValue(ImageUtil.convertObjectToString(tmp));
237        root.appendChild(child);
238
239        child = new IIOMetadataNode("MaximumSample");
240        tmp = new Byte((byte)maxSample);
241        child.setUserObject(tmp);
242        child.setNodeValue(ImageUtil.convertObjectToString(new Integer(maxSample)));
243        root.appendChild(child);
244
245        if(comments != null) {
246            for (int i = 0; i < comments.size(); i++) {
247                child = new IIOMetadataNode("Comment");
248                tmp = comments.get(i);
249                child.setUserObject(tmp);
250                child.setNodeValue(ImageUtil.convertObjectToString(tmp));
251                root.appendChild(child);
252            }
253        }
254
255        return root;
256    }
257
258    // Standard tree node methods
259    protected IIOMetadataNode getStandardChromaNode() {
260        IIOMetadataNode node = new IIOMetadataNode("Chroma");
261
262        int temp = (variant - '1') % 3 + 1;
263
264        IIOMetadataNode subNode = new IIOMetadataNode("ColorSpaceType");
265        if (temp == 3) {
266            subNode.setAttribute("name", "RGB");
267        } else {
268            subNode.setAttribute("name", "GRAY");
269        }
270        node.appendChild(subNode);
271
272        subNode = new IIOMetadataNode("NumChannels");
273        subNode.setAttribute("value", "" + (temp == 3 ? 3 : 1));
274        node.appendChild(subNode);
275
276        if(temp != 3) {
277            subNode = new IIOMetadataNode("BlackIsZero");
278            subNode.setAttribute("value", "TRUE");
279            node.appendChild(subNode);
280        }
281
282        return node;
283    }
284
285    protected IIOMetadataNode getStandardDataNode() {
286        IIOMetadataNode node = new IIOMetadataNode("Data");
287
288        IIOMetadataNode subNode = new IIOMetadataNode("SampleFormat");
289        subNode.setAttribute("value", "UnsignedIntegral");
290        node.appendChild(subNode);
291
292        int temp = (variant - '1') % 3 + 1;
293        subNode = new IIOMetadataNode("BitsPerSample");
294        if(temp == 1) {
295            subNode.setAttribute("value", "1");
296        } else if(temp == 2) {
297            subNode.setAttribute("value", "8");
298        } else {
299            subNode.setAttribute("value", "8 8 8");
300        }
301        node.appendChild(subNode);
302
303        subNode = new IIOMetadataNode("SignificantBitsPerSample");
304        if(temp == 1 || temp == 2) {
305            subNode.setAttribute("value", "" + maxSampleSize);
306        } else {
307            subNode.setAttribute("value",
308                                 maxSampleSize + " " +
309                                 maxSampleSize + " " +
310                                 maxSampleSize);
311        }
312        node.appendChild(subNode);
313
314        return node;
315    }
316
317    protected IIOMetadataNode getStandardDimensionNode() {
318        IIOMetadataNode node = new IIOMetadataNode("Dimension");
319
320        IIOMetadataNode subNode = new IIOMetadataNode("ImageOrientation");
321        subNode.setAttribute("value", "Normal");
322        node.appendChild(subNode);
323
324        return node;
325    }
326
327    protected IIOMetadataNode getStandardTextNode() {
328        if(comments != null) {
329            IIOMetadataNode node = new IIOMetadataNode("Text");
330            Iterator iter = comments.iterator();
331            while(iter.hasNext()) {
332                String comment = (String)iter.next();
333                IIOMetadataNode subNode = new IIOMetadataNode("TextEntry");
334                subNode.setAttribute("keyword", "comment");
335                subNode.setAttribute("value", comment);
336                node.appendChild(subNode);
337            }
338            return node;
339        }
340        return null;
341    }
342
343    public boolean isReadOnly() {
344        return false;
345    }
346
347    public void mergeTree(String formatName, Node root)
348        throws IIOInvalidTreeException {
349        if (formatName == null) {
350            throw new IllegalArgumentException(I18N.getString("PNMMetadata0"));
351        }
352
353        if (root == null) {
354            throw new IllegalArgumentException(I18N.getString("PNMMetadata2"));
355        }
356
357        if (formatName.equals(nativeMetadataFormatName) &&
358            root.getNodeName().equals(nativeMetadataFormatName)) {
359            mergeNativeTree(root);
360        } else if (formatName.equals
361                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
362            mergeStandardTree(root);
363        } else {
364            throw  new IllegalArgumentException(I18N.getString("PNMMetadata1") + " " +
365                                                formatName);
366        }
367    }
368
369    public void setFromTree(String formatName, Node root)
370        throws IIOInvalidTreeException {
371        if (formatName == null) {
372            throw new IllegalArgumentException(I18N.getString("PNMMetadata0"));
373        }
374
375        if (root == null) {
376            throw new IllegalArgumentException(I18N.getString("PNMMetadata2"));
377        }
378
379        if (formatName.equals(nativeMetadataFormatName) &&
380            root.getNodeName().equals(nativeMetadataFormatName)) {
381            mergeNativeTree(root);
382        } else if (formatName.equals
383                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
384            mergeStandardTree(root);
385        } else {
386            throw  new IllegalArgumentException(I18N.getString("PNMMetadata2") + " " +
387                                                formatName);
388        }
389    }
390
391    public void reset() {
392        maxSample = width = height = variant = maxSampleSize = 0;
393        comments = null;
394    }
395
396    public String getFormatName() {
397        int v = (variant - '1') % 3 + 1;
398        if (v == 1)
399            return "PBM";
400        if (v == 2)
401            return "PGM";
402        if (v == 3)
403            return "PPM";
404        return null;
405    }
406
407    public String getVariant() {
408        if (variant > '3')
409            return "RAWBITS";
410        return "ASCII";
411    }
412
413    boolean isRaw() {
414        return getVariant().equals("RAWBITS");
415    }
416
417    /** Sets the variant: '1' - '6'. */
418    public void setVariant(int v) {
419        this.variant = v;
420    }
421
422    public void setWidth(int w) {
423        this.width = w;
424    }
425
426    public void setHeight(int h) {
427        this.height = h;
428    }
429
430    int getMaxBitDepth() {
431        return maxSampleSize;
432    }
433
434    int getMaxValue() {
435        return maxSample;
436    }
437
438    /** Set the maximum sample size and maximum sample value.
439     *  @param maxValue The maximum sample value.  This method computes the
440     *                  maximum sample size.
441     */
442    public void setMaxBitDepth(int maxValue) {
443        this.maxSample = maxValue;
444
445        this.maxSampleSize = 0;
446        while(maxValue > 0) {
447            maxValue >>>= 1;
448            maxSampleSize++;
449        }
450    }
451
452    public synchronized void addComment(String comment) {
453        if (comments == null) {
454            comments = new ArrayList();
455        }
456        comment = comment.replaceAll("[\n\r\f]", " ");
457        comments.add(comment);
458    }
459
460    Iterator getComments() {
461        return comments == null ? null : comments.iterator();
462    }
463
464    private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
465        NodeList list = root.getChildNodes();
466        String format = null;
467        String var = null;
468
469        for (int i = list.getLength() - 1; i >= 0; i--) {
470            IIOMetadataNode node = (IIOMetadataNode)list.item(i);
471            String name = node.getNodeName();
472
473            if (name.equals("Comment")) {
474                addComment((String)node.getUserObject());
475            } else if (name.equals("Width")) {
476                this.width = ((Integer)node.getUserObject()).intValue();
477            } else if (name.equals("Height")) {
478                this.width = ((Integer)node.getUserObject()).intValue();
479            } else if (name.equals("MaximumSample")) {
480                int maxValue = ((Integer)node.getUserObject()).intValue();
481                setMaxBitDepth(maxValue);
482            } else if (name.equals("FormatName")) {
483                format = (String)node.getUserObject();
484            } else if (name.equals("Variant")) {
485                var = (String)node.getUserObject();
486            }
487        }
488
489        if (format.equals("PBM"))
490            variant = '1';
491        else if (format.equals("PGM"))
492            variant = '2';
493        else if (format.equals("PPM"))
494            variant = '3';
495
496        if (var.equals("RAWBITS"))
497            variant += 3;
498    }
499
500    private void mergeStandardTree(Node root) throws IIOInvalidTreeException {
501        NodeList children = root.getChildNodes();
502
503        String colorSpace = null;
504        int numComps = 0;
505        int[] bitsPerSample = null;
506
507        for (int i = 0; i < children.getLength(); i++) {
508            Node node = children.item(i);
509            String name = node.getNodeName();
510            if (name.equals("Chroma")) {
511                NodeList children1 = node.getChildNodes();
512                for (int j = 0; j < children1.getLength(); j++) {
513                    Node child = children1.item(j);
514                    String name1 = child.getNodeName();
515
516                    if (name1.equals("NumChannels")) {
517                        String s = (String)getAttribute(child, "value");
518                        numComps = new Integer(s).intValue();
519                    } else if (name1.equals("ColorSpaceType")) {
520                        colorSpace = (String)getAttribute(child, "name");
521                    }
522                }
523            } else if (name.equals("Compression")) {
524                // Do nothing.
525            } else if (name.equals("Data")) {
526                NodeList children1 = node.getChildNodes();
527                int maxBitDepth = -1;
528                for (int j = 0; j < children1.getLength(); j++) {
529                    Node child = children1.item(j);
530                    String name1 = child.getNodeName();
531
532                    if (name1.equals("BitsPerSample")) {
533                        List bps = new ArrayList(3);
534                        String s = (String)getAttribute(child, "value");
535                        StringTokenizer t = new StringTokenizer(s);
536                        while(t.hasMoreTokens()) {
537                            bps.add(Integer.valueOf(t.nextToken()));
538                        }
539                        bitsPerSample = new int[bps.size()];
540                        for(int k = 0; k < bitsPerSample.length; k++) {
541                            bitsPerSample[k] =
542                                ((Integer)bps.get(k)).intValue();
543                        }
544                    } else if (name1.equals("SignificantBitsPerSample")) {
545                        String s = (String)getAttribute(child, "value");
546                        StringTokenizer t = new StringTokenizer(s);
547                        while(t.hasMoreTokens()) {
548                            int sbps =
549                                Integer.valueOf(t.nextToken()).intValue();
550                            maxBitDepth = Math.max(sbps, maxBitDepth);
551                        }
552                    }
553                }
554
555                // Set maximum bit depth and value.
556                if(maxBitDepth > 0) {
557                    setMaxBitDepth((1 << maxBitDepth) - 1);
558                } else if(bitsPerSample != null) {
559                    for(int k = 0; k < bitsPerSample.length; k++) {
560                        if(bitsPerSample[k] > maxBitDepth) {
561                            maxBitDepth = bitsPerSample[k];
562                        }
563                    }
564                    setMaxBitDepth((1 << maxBitDepth) - 1);
565                }
566            } else if (name.equals("Dimension")) {
567                // Do nothing.
568            } else if (name.equals("Document")) {
569                // Do nothing.
570            } else if (name.equals("Text")) {
571                NodeList children1 = node.getChildNodes();
572                for (int j = 0; j < children1.getLength(); j++) {
573                    Node child = children1.item(j);
574                    String name1 = child.getNodeName();
575
576                    if (name1.equals("TextEntry")) {
577                        addComment((String)getAttribute(child, "value"));
578                    }
579                }
580            } else if (name.equals("Transparency")) {
581                // Do nothing.
582            } else {
583                throw new IIOInvalidTreeException(I18N.getString("PNMMetadata3") + " " +
584                                                name, node);
585            }
586        }
587
588        // Go from higher to lower: PPM > PGM > PBM.
589        if((colorSpace != null && colorSpace.equals("RGB")) ||
590           numComps > 1 ||
591           bitsPerSample.length > 1) {
592            variant = '3';
593        } else if(maxSampleSize > 1) {
594            variant = '2';
595        } else {
596            variant = '1';
597        }
598    }
599
600    public Object getAttribute(Node node, String name) {
601        NamedNodeMap map = node.getAttributes();
602        node = map.getNamedItem(name);
603        return (node != null) ? node.getNodeValue() : null;
604    }
605}