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