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}