001/* 002 * $RCSfile: J2KMetadata.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.4 $ 042 * $Date: 2006/09/22 23:07:25 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.jpeg2000.impl; 046 047import java.awt.color.ColorSpace; 048import java.awt.color.ICC_ColorSpace; 049import java.awt.image.ColorModel; 050import java.awt.image.DataBuffer; 051import java.awt.image.IndexColorModel; 052import java.awt.image.SampleModel; 053import java.io.IOException; 054import java.util.ArrayList; 055import java.util.Iterator; 056 057import javax.imageio.ImageTypeSpecifier; 058import javax.imageio.ImageWriteParam; 059import javax.imageio.ImageWriter; 060import javax.imageio.metadata.IIOInvalidTreeException; 061import javax.imageio.metadata.IIOMetadata; 062import javax.imageio.metadata.IIOMetadataFormatImpl; 063import javax.imageio.metadata.IIOMetadataNode; 064import javax.imageio.stream.ImageInputStream; 065 066import jj2000.j2k.fileformat.FileFormatBoxes; 067import jj2000.j2k.fileformat.reader.FileFormatReader; 068import jj2000.j2k.io.RandomAccessIO; 069 070import org.w3c.dom.NamedNodeMap; 071import org.w3c.dom.Node; 072import org.w3c.dom.NodeList; 073 074/** 075 * Metadata for the J2K plug-in. 076 */ 077public class J2KMetadata extends IIOMetadata implements Cloneable { 078 static final String nativeMetadataFormatName = 079 "com_sun_media_imageio_plugins_jpeg2000_image_1.0"; 080 081 /** cache the metadata format */ 082 private J2KMetadataFormat format; 083 084 /** The boxes of JP2 file used as meta data, i. e., all the boxes 085 * except the data stream box 086 */ 087 private ArrayList boxes = new ArrayList(); 088 089 /** 090 * Constructor containing code shared by other constructors. 091 */ 092 public J2KMetadata() { 093 super(true, // Supports standard format 094 nativeMetadataFormatName, // and a native format 095 "com.github.jaiimageio.jpeg2000.impl.J2KMetadataFormat", 096 null, null); // No other formats 097 098 format = (J2KMetadataFormat)getMetadataFormat(nativeMetadataFormatName); 099 } 100 101 /* 102 * Constructs a <code>J2KMetadata</code> object by reading the 103 * contents of an <code>ImageInputStream</code>. Has package-only 104 * access. 105 * 106 * @param iis An <code>ImageInputStream</code> from which to read 107 * the metadata. 108 * @param reader The <code>J2KImageReader</code> calling this 109 * constructor, to which warnings should be sent. 110 */ 111 public J2KMetadata(ImageInputStream iis, 112 J2KImageReader reader) throws IOException { 113 this(); 114 RandomAccessIO in = new IISRandomAccessIO(iis); 115 116 iis.mark(); 117 // **** File Format **** 118 // If the codestream is wrapped in the jp2 fileformat, Read the 119 // file format wrapper 120 FileFormatReader ff = new FileFormatReader(in, this); 121 ff.readFileFormat(); 122 iis.reset(); 123 } 124 125 /** 126 * Constructs a default stream <code>J2KMetadata</code> object appropriate 127 * for the given write parameters. 128 */ 129 public J2KMetadata(ImageWriteParam param, ImageWriter writer) { 130 this(null, param, writer); 131 } 132 133 /** 134 * Constructs a default image <code>J2KMetadata</code> object appropriate 135 * for the given image type and write parameters. 136 */ 137 public J2KMetadata(ImageTypeSpecifier imageType, 138 ImageWriteParam param, 139 ImageWriter writer) { 140 this(imageType != null ? imageType.getColorModel() : null, 141 imageType != null ? imageType.getSampleModel() : null, 142 0, 0, 143 param, writer); 144 } 145 146 /** 147 * Constructs a default image <code>J2KMetadata</code> object appropriate 148 * for the given image type and write parameters. 149 */ 150 public J2KMetadata(ColorModel colorModel, 151 SampleModel sampleModel, 152 int width, 153 int height, 154 ImageWriteParam param, 155 ImageWriter writer) { 156 this(); 157 addNode(new SignatureBox()); 158 addNode(new FileTypeBox(0x6A703220, 0, new int[]{0x6A703220})); 159 160 ImageTypeSpecifier destType = null; 161 162 if (param != null) { 163 destType = param.getDestinationType(); 164 if (colorModel == null && sampleModel == null) { 165 colorModel = destType == null ? null : destType.getColorModel(); 166 sampleModel = 167 destType == null ? null : destType.getSampleModel(); 168 } 169 } 170 171 int[] bitDepths = null; 172 if(colorModel != null) { 173 bitDepths = colorModel.getComponentSize(); 174 } else if(sampleModel != null) { 175 bitDepths = sampleModel.getSampleSize(); 176 } 177 178 int bitsPerComponent = 0xff; 179 if(bitDepths != null) { 180 bitsPerComponent = bitDepths[0]; 181 int numComponents = bitDepths.length; 182 for(int i = 1; i < numComponents; i++) { 183 /* XXX: This statement should be removed when BPC behavior 184 is corrected as derscribed below. */ 185 if(bitDepths[i] > bitsPerComponent) { 186 bitsPerComponent = bitDepths[i]; 187 } 188 /* XXX: When the number of bits per component is not the 189 same for all components the BPC parameter of the Image 190 Header box should be set to 0xff and the actual number of 191 bits per component written in the Bits Per Component box. 192 if(bitDepths[i] != bitsPerComponent) { 193 bitsPerComponent = 0xff; 194 break; 195 } 196 */ 197 } 198 } 199 200 if (colorModel != null) { 201 ColorSpace cs = colorModel.getColorSpace(); 202 boolean iccColor = (cs instanceof ICC_ColorSpace); 203 int type = cs.getType(); 204 205 if (type == ColorSpace.TYPE_RGB) { 206 addNode(new ColorSpecificationBox((byte)1, 207 (byte)0, (byte)0, 208 ColorSpecificationBox.ECS_sRGB, 209 null)); 210 } else if (type == ColorSpace.TYPE_GRAY) 211 addNode(new ColorSpecificationBox((byte)1, 212 (byte)0, (byte)0, 213 ColorSpecificationBox.ECS_GRAY, 214 null)); 215 else if (cs instanceof ICC_ColorSpace) 216 addNode(new ColorSpecificationBox((byte)2, 217 (byte)0, (byte)0, 218 0, 219 ((ICC_ColorSpace)cs).getProfile())); 220 221 if (colorModel.hasAlpha()) { 222 addNode(new ChannelDefinitionBox(colorModel)); 223 } 224 225 if (colorModel instanceof IndexColorModel) { 226 addNode(new PaletteBox((IndexColorModel)colorModel)); 227 int numComp = colorModel.getComponentSize().length; 228 short[] channels = new short[numComp]; 229 byte[] types = new byte[numComp]; 230 byte[] maps = new byte[numComp]; 231 for (int i = 0; i < numComp; i++) { 232 channels[i] = 0; 233 types[i] = 1; 234 maps[i] = (byte)i; 235 } 236 addNode(new ComponentMappingBox(channels, types, maps)); 237 } 238 } 239 240 if (sampleModel != null) { 241 if (width <= 0) 242 width = sampleModel.getWidth(); 243 if (height <= 0) 244 height = sampleModel.getHeight(); 245 int bpc = bitsPerComponent == 0xff ? 246 0xff : ((bitsPerComponent - 1) | 247 (isOriginalSigned(sampleModel) ? 0x80 : 0)); 248 addNode(new HeaderBox(height, 249 width, 250 sampleModel.getNumBands(), 251 bpc, 252 7, 253 colorModel == null ? 1 : 0, 254 getElement("JPEG2000IntellectualPropertyRightsBox")==null ? 0 : 1)); 255 } 256 } 257 258 public Object clone() { 259 J2KMetadata theClone = null; 260 261 try { 262 theClone = (J2KMetadata) super.clone(); 263 } catch (CloneNotSupportedException e) {} // won't happen 264 265 if (boxes != null) { 266 int numBoxes = boxes.size(); 267 for(int i = 0; i < numBoxes; i++) { 268 theClone.addNode((Box)boxes.get(i)); 269 } 270 } 271 return theClone; 272 } 273 274 public Node getAsTree(String formatName) { 275 if (formatName == null) { 276 throw new IllegalArgumentException(I18N.getString("J2KMetadata0")); 277 } 278 279 if (formatName.equals(nativeMetadataFormatName)) { 280 return getNativeTree(); 281 } 282 283 if (formatName.equals 284 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 285 return getStandardTree(); 286 } 287 288 throw new IllegalArgumentException(I18N.getString("J2KMetadata1") 289 + " " + formatName); 290 } 291 292 IIOMetadataNode getNativeTree() { 293 IIOMetadataNode root = 294 new IIOMetadataNode(nativeMetadataFormatName); 295 296 Box signatureBox = null, fileTypeBox = null, headerBox = null; 297 int signatureIndex = -1, fileTypeIndex = -1, headerIndex = -1; 298 299 int numBoxes = boxes.size(); 300 301 int found = 0; 302 for(int i = 0; i < numBoxes && found < 3; i++) { 303 Box box = (Box)boxes.get(i); 304 if(Box.getName(box.getType()).equals("JPEG2000SignatureBox")) { 305 signatureBox = box; 306 signatureIndex = i; 307 found++; 308 } else if(Box.getName(box.getType()).equals("JPEG2000FileTypeBox")) { 309 fileTypeBox = box; 310 fileTypeIndex = i; 311 found++; 312 } else if(Box.getName(box.getType()).equals("JPEG2000HeaderBox")) { 313 headerBox = box; 314 headerIndex = i; 315 found++; 316 } 317 } 318 319 if(signatureBox != null) { 320 insertNodeIntoTree(root, signatureBox.getNativeNode()); 321 } 322 323 if(fileTypeBox != null) { 324 insertNodeIntoTree(root, fileTypeBox.getNativeNode()); 325 } 326 327 if(headerBox != null) { 328 insertNodeIntoTree(root, headerBox.getNativeNode()); 329 } 330 331 for(int i = 0; i < numBoxes; i++) { 332 if(i == signatureIndex || 333 i == fileTypeIndex || 334 i == headerIndex) continue; 335 Box box = (Box)boxes.get(i); 336 IIOMetadataNode node = box.getNativeNode(); 337 insertNodeIntoTree(root, node); 338 } 339 return root; 340 } 341 342 // Standard tree node methods 343 protected IIOMetadataNode getStandardChromaNode() { 344 HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox"); 345 PaletteBox palette = (PaletteBox)getElement("JPEG2000PaletteBox"); 346 ColorSpecificationBox color = 347 (ColorSpecificationBox)getElement("JPEG2000ColorSpecificationBox"); 348 349 IIOMetadataNode node = new IIOMetadataNode("Chroma"); 350 IIOMetadataNode subNode = null; 351 if (header != null) { 352 if (header.getUnknownColorspace() == 0) { 353 if (color != null && color.getMethod() == 1) { 354 subNode = new IIOMetadataNode("ColorSpaceType"); 355 int ecs = color.getEnumeratedColorSpace(); 356 if (ecs == FileFormatBoxes.CSB_ENUM_SRGB) 357 subNode.setAttribute("name", "RGB"); 358 if (ecs == FileFormatBoxes.CSB_ENUM_GREY) 359 subNode.setAttribute("name", "GRAY"); 360 node.appendChild(subNode); 361 } 362 } 363 364 subNode = new IIOMetadataNode("NumChannels"); 365 subNode.setAttribute("value", "" + header.getNumComponents()); 366 node.appendChild(subNode); 367 368 if (palette != null) { 369 subNode.setAttribute("value", "" + palette.getNumComp()); 370 subNode = new IIOMetadataNode("Palette"); 371 byte[][] lut = palette.getLUT(); 372 373 int size = lut[0].length; 374 int numComp = lut.length; 375 376 for (int i = 0; i < size; i++) { 377 IIOMetadataNode subNode1 = 378 new IIOMetadataNode("PaletteEntry"); 379 subNode1.setAttribute("index", ""+i); 380 subNode1.setAttribute("red", "" + (lut[0][i]&0xff)); 381 subNode1.setAttribute("green", "" + (lut[1][i]&0xff)); 382 subNode1.setAttribute("blue", "" + (lut[2][i]&0xff)); 383 if (numComp == 4) 384 subNode1.setAttribute("alpha", "" + (lut[3][i]&0xff)); 385 subNode.appendChild(subNode1); 386 } 387 node.appendChild(subNode); 388 } 389 } 390 return node; 391 } 392 393 protected IIOMetadataNode getStandardCompressionNode() { 394 IIOMetadataNode node = new IIOMetadataNode("Compression"); 395 396 // CompressionTypeName 397 IIOMetadataNode subNode = new IIOMetadataNode("CompressionTypeName"); 398 subNode.setAttribute("value", "JPEG2000"); 399 node.appendChild(subNode); 400 return node; 401 } 402 403 protected IIOMetadataNode getStandardDataNode() { 404 IIOMetadataNode node = new IIOMetadataNode("Data"); 405 PaletteBox palette = (PaletteBox)getElement("JPEG2000PaletteBox"); 406 boolean sampleFormat = false; 407 408 if (palette != null) { 409 IIOMetadataNode subNode = new IIOMetadataNode("SampleFormat"); 410 subNode.setAttribute("value", "Index"); 411 node.appendChild(subNode); 412 sampleFormat = true; 413 } 414 415 BitsPerComponentBox bitDepth = 416 (BitsPerComponentBox)getElement("JPEG2000BitsPerComponentBox"); 417 String value = ""; 418 boolean signed = false; 419 boolean gotSampleInfo = false; 420 421 // JPEG 2000 "B" parameter represents "bitDepth - 1" in the 422 // right 7 least significant bits with the most significant 423 // bit indicating signed if set and unsigned if not. 424 if (bitDepth != null) { 425 byte[] bits = bitDepth.getBitDepth(); 426 if ((bits[0] & 0x80) == 0x80) 427 signed = true; 428 429 int numComp = bits.length; 430 for (int i = 0; i < numComp; i++) { 431 value += (bits[i] & 0x7f) + 1; 432 if(i != numComp - 1) value += " "; 433 } 434 435 gotSampleInfo = true; 436 } else { 437 HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox"); 438 if(header != null) { 439 int bits = header.getBitDepth(); 440 if ((bits & 0x80) == 0x80) 441 signed = true; 442 bits = (bits & 0x7f) + 1; 443 int numComp = header.getNumComponents(); 444 for (int i = 0; i < numComp; i++) { 445 value += bits; 446 if(i != numComp - 1) value += " "; 447 } 448 449 gotSampleInfo = true; 450 } 451 } 452 453 IIOMetadataNode subNode = null; 454 455 if(gotSampleInfo) { 456 subNode = new IIOMetadataNode("BitsPerSample"); 457 subNode.setAttribute("value", value); 458 node.appendChild(subNode); 459 } 460 461 subNode = new IIOMetadataNode("PlanarConfiguration"); 462 subNode.setAttribute("value", "TileInterleaved"); 463 node.appendChild(subNode); 464 465 if (!sampleFormat && gotSampleInfo) { 466 subNode = new IIOMetadataNode("SampleFormat"); 467 subNode.setAttribute("value", 468 signed ? "SignedIntegral": "UnsignedIntegral"); 469 node.appendChild(subNode); 470 } 471 472 return node; 473 } 474 475 protected IIOMetadataNode getStandardDimensionNode() { 476 ResolutionBox box = 477 (ResolutionBox)getElement("JPEG2000CaptureResolutionBox"); 478 if (box != null) { 479 IIOMetadataNode node = new IIOMetadataNode("Dimension"); 480 float hRes = box.getHorizontalResolution(); 481 float vRes = box.getVerticalResolution(); 482 float ratio = vRes / hRes; 483 IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio"); 484 subNode.setAttribute("value", "" + ratio); 485 node.appendChild(subNode); 486 487 subNode = new IIOMetadataNode("HorizontalPixelSize"); 488 subNode.setAttribute("value", "" + (1000 / hRes)); 489 node.appendChild(subNode); 490 491 subNode = new IIOMetadataNode("VerticalPixelSize"); 492 subNode.setAttribute("value", "" + (1000 / vRes)); 493 node.appendChild(subNode); 494 495 return node; 496 } 497 498 return null; 499 } 500 501 protected IIOMetadataNode getStandardTransparencyNode() { 502 ChannelDefinitionBox channel = 503 (ChannelDefinitionBox)getElement("JPEG2000ChannelDefinitionBox"); 504 if (channel != null) { 505 IIOMetadataNode node = new IIOMetadataNode("Transparency"); 506 507 boolean hasAlpha = false; 508 boolean isPremultiplied = false; 509 short[] type = channel.getTypes(); 510 511 for (int i = 0; i < type.length; i++) { 512 if (type[i] == 1) 513 hasAlpha = true; 514 if (type[i] == 2) 515 isPremultiplied = true; 516 } 517 518 String value = "none"; 519 if (isPremultiplied) 520 value = "premultiplied"; 521 else if (hasAlpha) 522 value = "nonpremultiplied"; 523 524 IIOMetadataNode subNode = new IIOMetadataNode("Alpha"); 525 subNode.setAttribute("value", value); 526 node.appendChild(subNode); 527 528 return node; 529 } 530 531 IIOMetadataNode node = new IIOMetadataNode("Transparency"); 532 IIOMetadataNode subNode = new IIOMetadataNode("Alpha"); 533 subNode.setAttribute("value", "none"); 534 node.appendChild(subNode); 535 536 return null; 537 } 538 539 protected IIOMetadataNode getStandardTextNode() { 540 if (boxes == null) 541 return null; 542 IIOMetadataNode text = null; 543 544 Iterator iterator = boxes.iterator(); 545 546 while(iterator.hasNext()) { 547 Box box = (Box)iterator.next(); 548 if (box instanceof XMLBox) { 549 if (text == null) 550 text = new IIOMetadataNode("Text"); 551 IIOMetadataNode subNode = new IIOMetadataNode("TextEntry"); 552 String content = new String(box.getContent()); 553 subNode.setAttribute("value", content); 554 text.appendChild(subNode); 555 } 556 } 557 return text; 558 } 559 560 public boolean isReadOnly() { 561 return false; 562 } 563 564 public void mergeTree(String formatName, Node root) 565 throws IIOInvalidTreeException { 566 if (formatName == null) { 567 throw new IllegalArgumentException(I18N.getString("J2KMetadata0")); 568 } 569 570 if (root == null) { 571 throw new IllegalArgumentException(I18N.getString("J2KMetadata2")); 572 } 573 574 if (formatName.equals(nativeMetadataFormatName) && 575 root.getNodeName().equals(nativeMetadataFormatName)) { 576 mergeNativeTree(root); 577 } else if (formatName.equals 578 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 579 mergeStandardTree(root); 580 } else { 581 throw new IllegalArgumentException(I18N.getString("J2KMetadata1") 582 + " " + formatName); 583 } 584 } 585 586 public void setFromTree(String formatName, Node root) 587 throws IIOInvalidTreeException { 588 if (formatName == null) { 589 throw new IllegalArgumentException(I18N.getString("J2KMetadata0")); 590 } 591 592 if (root == null) { 593 throw new IllegalArgumentException(I18N.getString("J2KMetadata2")); 594 } 595 596 if (formatName.equals(nativeMetadataFormatName) && 597 root.getNodeName().equals(nativeMetadataFormatName)) { 598 boxes = new ArrayList(); 599 mergeNativeTree(root); 600 } else if (formatName.equals 601 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 602 boxes = new ArrayList(); 603 mergeStandardTree(root); 604 } else { 605 throw new IllegalArgumentException(I18N.getString("J2KMetadata1") 606 + " " + formatName); 607 } 608 } 609 610 public void reset() { 611 boxes.clear(); 612 } 613 614 public void addNode(Box node) { 615 if (boxes == null) 616 boxes = new ArrayList(); 617 replace(Box.getName(node.getType()), node); 618 } 619 620 public Box getElement(String name) { 621 for (int i = boxes.size() - 1; i >= 0; i--) { 622 Box box = (Box)boxes.get(i); 623 if (name.equals(Box.getName(box.getType()))) 624 return box; 625 } 626 return null; 627 } 628 629 private void mergeNativeTree(Node root) throws IIOInvalidTreeException { 630 NodeList list = root.getChildNodes(); 631 for (int i = list.getLength() - 1; i >= 0; i--) { 632 Node node = list.item(i); 633 String name = node.getNodeName(); 634 if (format.getParent(name) != null) { 635 if (format.isLeaf(name)) { 636 String s = (String)Box.getAttribute(node, "Type"); 637 Box box = Box.createBox(Box.getTypeInt(s), node); 638 if (format.singleInstance(name)&&getElement(name) != null) { 639 replace(name, box); 640 } else 641 boxes.add(box); 642 } else { 643 mergeNativeTree(node); 644 } 645 } 646 } 647 } 648 649 private void mergeStandardTree(Node root) throws IIOInvalidTreeException { 650 NodeList children = root.getChildNodes(); 651 int numComps = 0; 652 653 for (int i = 0; i < children.getLength(); i++) { 654 Node node = children.item(i); 655 String name = node.getNodeName(); 656 if (name.equals("Chroma")) { 657 NodeList children1 = node.getChildNodes(); 658 for (int j = 0; j < children1.getLength(); j++) { 659 Node child = children1.item(j); 660 String name1 = child.getNodeName(); 661 662 if (name1.equals("NumChannels")) { 663 String s = (String)Box.getAttribute(child, "value"); 664 numComps = new Integer(s).intValue(); 665 } 666 667 if (name1.equals("ColorSpaceType")) 668 createColorSpecificationBoxFromStandardNode(child); 669 670 if (name1.equals("Palette")) { 671 createPaletteBoxFromStandardNode(child); 672 } 673 } 674 } else if (name.equals("Compression")) { 675 // Intentionally do nothing: just prevent entry into 676 // the default "else" block and an ensuing 677 // IIOInvalidTreeException; fixes 5110389. 678 } else if (name.equals("Data")) { 679 createBitsPerComponentBoxFromStandardNode(node); 680 createHeaderBoxFromStandardNode(node, numComps); 681 } else if (name.equals("Dimension")) { 682 createResolutionBoxFromStandardNode(node); 683 } else if (name.equals("Document")) { 684 createXMLBoxFromStandardNode(node); 685 } else if (name.equals("Text")) { 686 createXMLBoxFromStandardNode(node); 687 } else if (name.equals("Transparency")) { 688 createChannelDefinitionFromStandardNode(node); 689 } else { 690 throw new IIOInvalidTreeException(I18N.getString("J2KMetadata3") 691 + " " + name, node); 692 } 693 } 694 } 695 696 private void createColorSpecificationBoxFromStandardNode(Node node) { 697 if (node.getNodeName() != "ColorSpaceType") 698 throw new IllegalArgumentException(I18N.getString("J2KMetadata4")); 699 String name = (String)Box.getAttribute(node, "name"); 700 int ecs = name.equals("RGB") ? ColorSpecificationBox.ECS_sRGB : 701 (name.equals("Gray") ? ColorSpecificationBox.ECS_GRAY : 0); 702 703 if (ecs == ColorSpecificationBox.ECS_sRGB || 704 ecs ==ColorSpecificationBox.ECS_GRAY) { 705 replace ("JPEG2000ColorSpecificationBox", 706 new ColorSpecificationBox((byte)1, (byte)0, (byte)0, 707 ecs, null)); 708 } 709 } 710 711 private void createPaletteBoxFromStandardNode(Node node) { 712 if (node.getNodeName() != "Palette") 713 throw new IllegalArgumentException(I18N.getString("J2KMetadata5")); 714 NodeList children = node.getChildNodes(); 715 int maxIndex = -1; 716 boolean hasAlpha = false; 717 for (int i = 0; i < children.getLength(); i++) { 718 Node child = children.item(i); 719 String name = child.getNodeName(); 720 721 if (name.equals("PaletteEntry")) { 722 String s = (String)Box.getAttribute(child, "index"); 723 int index = new Integer(s).intValue(); 724 if(index > maxIndex) { 725 maxIndex = index; 726 } 727 if(Box.getAttribute(child, "alpha") != null) { 728 hasAlpha = true; 729 } 730 } 731 } 732 733 // Determine palette size. 734 int numBits = 32; 735 int mask = 0x80000000; 736 while(mask != 0 && (maxIndex & mask) == 0) { 737 numBits--; 738 mask >>>= 1; 739 } 740 int size = 1 << numBits; 741 742 byte[] red = new byte[size]; 743 byte[] green = new byte[size]; 744 byte[] blue = new byte[size]; 745 byte[] alpha = hasAlpha ? new byte[size]: null; 746 747 for (int i = 0; i < children.getLength(); i++) { 748 Node child = children.item(i); 749 String name = child.getNodeName(); 750 751 if (name.equals("PaletteEntry")) { 752 String s = (String)Box.getAttribute(child, "index"); 753 int index = new Integer(s).intValue(); 754 s = (String)Box.getAttribute(child, "red"); 755 red[index] = (byte)(new Integer(s).intValue()); 756 s = (String)Box.getAttribute(child, "green"); 757 green[index] = (byte)(new Integer(s).intValue()); 758 s = (String)Box.getAttribute(child, "blue"); 759 blue[index] = (byte)(new Integer(s).intValue()); 760 761 byte t = (byte)255; 762 s = (String)Box.getAttribute(child, "alpha"); 763 if(s != null) { 764 t = (byte)(new Integer(s).intValue()); 765 } 766 767 if(alpha != null) { 768 alpha[index] = t; 769 } 770 } 771 } 772 773 IndexColorModel icm; 774 if (alpha == null) 775 icm = new IndexColorModel(numBits, size, red, green, blue); 776 else 777 icm = new IndexColorModel(numBits, size, red, green, blue, alpha); 778 779 replace("JPEG2000PaletteBox", new PaletteBox(icm)); 780 } 781 782 private void createBitsPerComponentBoxFromStandardNode(Node node) { 783 if (node.getNodeName() != "Data") 784 throw new IllegalArgumentException(I18N.getString("J2KMetadata6")); 785 786 NodeList children = node.getChildNodes(); 787 788 byte[] bits = null; 789 boolean isSigned = false; 790 for (int i = 0; i < children.getLength(); i++) { 791 Node child = children.item(i); 792 String name = child.getNodeName(); 793 794 if (name.equals("BitsPerSample")) { 795 String s = (String)Box.getAttribute(child, "value"); 796 bits = (byte[])Box.parseByteArray(s).clone(); 797 } else if(name.equals("SampleFormat")) { 798 String s = (String)Box.getAttribute(child, "value"); 799 isSigned = s.equals("SignedIntegral"); 800 } 801 } 802 803 if(bits != null) { 804 // JPEG 2000 "B" parameter represents "bitDepth - 1" in the 805 // right 7 least significant bits with the most significant 806 // bit indicating signed if set and unsigned if not. 807 for (int i = 0; i < bits.length; i++) { 808 bits[i] = (byte)((bits[i]&0xff) - 1); 809 if(isSigned) { 810 bits[i] |= 0x80; 811 } 812 } 813 814 replace("JPEG2000BitsPerComponent", 815 new BitsPerComponentBox(bits)); 816 } 817 } 818 819 private void createResolutionBoxFromStandardNode(Node node) { 820 if (node.getNodeName() != "Dimension") 821 throw new IllegalArgumentException(I18N.getString("J2KMetadata7")); 822 NodeList children = node.getChildNodes(); 823 float hRes = 0.0f; 824 float vRes = 0.0f; 825 826 boolean gotH = false, gotV = false; 827 828 for (int i = 0; i < children.getLength(); i++) { 829 Node child = children.item(i); 830 String name = child.getNodeName(); 831 832 if (name.equals("HorizontalPixelSize")) { 833 String s = (String)Box.getAttribute(child, "value"); 834 hRes = new Float(s).floatValue(); 835 hRes = 1000 / hRes; 836 gotH = true; 837 } 838 839 if (name.equals("VerticalPixelSize")) { 840 String s = (String)Box.getAttribute(child, "value"); 841 vRes = new Float(s).floatValue(); 842 vRes = 1000 / vRes; 843 gotV = true; 844 } 845 } 846 847 if(gotH && !gotV) { 848 vRes = hRes; 849 } else if(gotV && !gotH) { 850 hRes = vRes; 851 } 852 853 if(gotH || gotV) { 854 replace("JPEG2000CaptureResolutionBox", 855 new ResolutionBox(0x72657363, hRes, vRes)); 856 } 857 } 858 859 private void createXMLBoxFromStandardNode(Node node) { 860 NodeList children = node.getChildNodes(); 861 String value = "<" + node.getNodeName() + ">"; 862 863 for (int i = 0; i < children.getLength(); i++) { 864 Node child = children.item(i); 865 String name = child.getNodeName(); 866 value += "<" + name + " "; 867 868 NamedNodeMap map = child.getAttributes(); 869 870 for (int j = 0; j < map.getLength(); j++) { 871 Node att = map.item(j); 872 value += att.getNodeName() + "=\"" + 873 att.getNodeValue() + "\" "; 874 } 875 876 value += " />"; 877 } 878 879 value += "</" + node.getNodeName() + ">"; 880 881 boxes.add(new XMLBox(value.getBytes())); 882 } 883 884 private void createHeaderBoxFromStandardNode(Node node, int numComps) { 885 HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox"); 886 byte unknownColor = 887 (byte)(getElement("JPEG2000ColorSpecificationBox") == null ? 1: 0); 888 if (header != null) { 889 if (numComps ==0); 890 numComps = header.getNumComponents(); 891 892 header = new HeaderBox(header.getHeight(), header.getWidth(), 893 numComps, 894 header.getBitDepth(), 895 header.getCompressionType(), 896 unknownColor, 897 header.getIntellectualProperty()); 898 } else { 899 header = new HeaderBox(0, 0, numComps, 0, 0, unknownColor, 0); 900 } 901 replace("JPEG2000HeaderBox", header); 902 } 903 904 private void createChannelDefinitionFromStandardNode(Node node) { 905 if (node.getNodeName() != "Transparency") 906 throw new IllegalArgumentException(I18N.getString("J2KMetadata8")); 907 908 HeaderBox header = (HeaderBox)getElement("JPEG2000HeaderBox"); 909 int numComps = 3; 910 911 if (header != null) { 912 numComps = header.getNumComponents(); 913 } 914 915 NodeList children = node.getChildNodes(); 916 boolean hasAlpha = false; 917 boolean isPremultiplied = false; 918 919 for (int i = 0; i < children.getLength(); i++) { 920 Node child = children.item(i); 921 String name = child.getNodeName(); 922 923 if (name.equals("Alpha")) { 924 String value = (String)Box.getAttribute(child, "value"); 925 if (value.equals("premultiplied")) 926 isPremultiplied = true; 927 if (value.equals("nonpremultiplied")) 928 hasAlpha = true; 929 } 930 } 931 932 if (!hasAlpha) 933 return; 934 935 int num = (short)(numComps * (isPremultiplied ? 3 : 2)); 936 short[] channels = new short[num]; 937 short[] types = new short[num]; 938 short[] associations = new short[num]; 939 ChannelDefinitionBox.fillBasedOnBands(numComps, isPremultiplied, 940 channels, types, associations); 941 replace("JPEG2000ChannelDefinitionBox", 942 new ChannelDefinitionBox(channels, types, associations)); 943 } 944 945 private void replace(String name, Box box) { 946 for (int i = boxes.size() - 1; i >= 0; i--) { 947 Box box1 = (Box)boxes.get(i); 948 if (name.equals(Box.getName(box1.getType()))) { 949 boxes.set(i, box); 950 return; 951 } 952 } 953 954 boxes.add(box); 955 } 956 957 private boolean insertNodeIntoTree(IIOMetadataNode root, 958 IIOMetadataNode node) { 959 String name = node.getNodeName(); 960 String parent = format.getParent(name); 961 if (parent == null) 962 return false; 963 964 IIOMetadataNode parentNode = getNodeFromTree(root, parent, name); 965 if (parentNode == null) 966 parentNode = createNodeIntoTree(root, parent); 967 parentNode.appendChild(node); 968 return true; 969 } 970 971 private IIOMetadataNode getNodeFromTree(IIOMetadataNode root, 972 String name, 973 String childName) { 974 if (name.equals(root.getNodeName())) 975 return root; 976 977 NodeList list = root.getChildNodes(); 978 for (int i = 0; i < list.getLength(); i++) { 979 IIOMetadataNode node = (IIOMetadataNode)list.item(i); 980 if (node.getNodeName().equals(name)) { 981 if (name.equals("JPEG2000UUIDInfoBox") && 982 checkUUIDInfoBox(node, childName)) 983 continue; 984 else 985 return node; 986 } 987 node = getNodeFromTree(node, name, childName); 988 if (node != null) 989 return node; 990 } 991 992 return null; 993 } 994 995 private IIOMetadataNode createNodeIntoTree(IIOMetadataNode root, 996 String name) { 997 IIOMetadataNode node = getNodeFromTree(root, name, null); 998 if (node != null) 999 return node; 1000 1001 node = new IIOMetadataNode(name); 1002 1003 String parent = format.getParent(name); 1004 IIOMetadataNode parentNode = createNodeIntoTree(root, parent); 1005 parentNode.appendChild(node); 1006 1007 return node; 1008 } 1009 1010 private boolean isOriginalSigned(SampleModel sampleModel) { 1011 int type = sampleModel.getDataType(); 1012 if (type == DataBuffer.TYPE_BYTE || type == DataBuffer.TYPE_USHORT) 1013 return false; 1014 return true; 1015 } 1016 1017 /** Check whether the child with a name <code>childName</code> exists. 1018 * This method is designed because UUID info box may have many instances. 1019 * So if one of its sub-box is inserted into the tree, an empty slut for 1020 * this sub-box has to be find or created to avoid one UUID info box 1021 * has duplicated sub-boxes. The users have to guarantee each UUID info 1022 * box has all the sub-boxes. 1023 */ 1024 private boolean checkUUIDInfoBox(Node node, String childName) { 1025 1026 NodeList list = node.getChildNodes(); 1027 for (int i = 0; i < list.getLength(); i++) { 1028 IIOMetadataNode child = (IIOMetadataNode)list.item(i); 1029 String name = child.getNodeName(); 1030 1031 if (name.equals(childName)) 1032 return true; 1033 } 1034 1035 return false; 1036 } 1037}