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