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}