001/* 002 * $RCSfile: CLibJPEGMetadata.java,v $ 003 * 004 * 005 * Copyright (c) 2006 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.7 $ 042 * $Date: 2007/08/28 18:45:53 $ 043 * $State: Exp $ 044 */ 045 046package com.github.jaiimageio.impl.plugins.jpeg; 047 048import java.awt.Dimension; 049import java.awt.Transparency; 050import java.awt.color.ColorSpace; 051import java.awt.color.ICC_Profile; 052import java.awt.image.BufferedImage; 053import java.awt.image.ColorModel; 054import java.awt.image.ComponentColorModel; 055import java.awt.image.DataBuffer; 056import java.awt.image.DataBufferByte; 057import java.awt.image.IndexColorModel; 058import java.awt.image.Raster; 059import java.awt.image.RenderedImage; 060import java.awt.image.SampleModel; 061import java.awt.image.WritableRaster; 062import java.io.ByteArrayInputStream; 063import java.io.EOFException; 064import java.io.IOException; 065import java.io.UnsupportedEncodingException; 066import java.nio.ByteOrder; 067import java.util.ArrayList; 068import java.util.Arrays; 069import java.util.Iterator; 070import java.util.List; 071import java.util.Map; 072import java.util.SortedMap; 073import java.util.TreeMap; 074 075import javax.imageio.IIOException; 076import javax.imageio.IIOImage; 077import javax.imageio.ImageIO; 078import javax.imageio.ImageReader; 079import javax.imageio.ImageTypeSpecifier; 080import javax.imageio.metadata.IIOMetadata; 081import javax.imageio.metadata.IIOMetadataFormatImpl; 082import javax.imageio.metadata.IIOMetadataNode; 083import javax.imageio.metadata.IIOInvalidTreeException; 084import javax.imageio.plugins.jpeg.JPEGHuffmanTable; 085import javax.imageio.plugins.jpeg.JPEGQTable; 086import javax.imageio.stream.ImageInputStream; 087import javax.imageio.stream.MemoryCacheImageInputStream; 088 089import org.w3c.dom.Node; 090import org.w3c.dom.NodeList; 091 092import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet; 093import com.github.jaiimageio.plugins.tiff.EXIFGPSTagSet; 094import com.github.jaiimageio.plugins.tiff.EXIFInteroperabilityTagSet; 095import com.github.jaiimageio.plugins.tiff.EXIFParentTIFFTagSet; 096import com.github.jaiimageio.plugins.tiff.EXIFTIFFTagSet; 097import com.github.jaiimageio.plugins.tiff.TIFFDirectory; 098import com.github.jaiimageio.plugins.tiff.TIFFField; 099import com.github.jaiimageio.plugins.tiff.TIFFTag; 100import com.github.jaiimageio.plugins.tiff.TIFFTagSet; 101 102public class CLibJPEGMetadata extends IIOMetadata { 103 // --- Constants --- 104 105 static final String NATIVE_FORMAT = "javax_imageio_jpeg_image_1.0"; 106 // XXX Reference to a non-API J2SE class: 107 static final String NATIVE_FORMAT_CLASS = 108 "com.sun.imageio.plugins.jpeg.JPEGImageMetadataFormat"; 109 110 static final String TIFF_FORMAT = 111 "com_sun_media_imageio_plugins_tiff_image_1.0"; 112 static final String TIFF_FORMAT_CLASS = 113 "com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadataFormat"; 114 115 // Marker codes from J2SE in numerically increasing order. 116 117 /** For temporary use in arithmetic coding */ 118 static final int TEM = 0x01; 119 120 // Codes 0x02 - 0xBF are reserved 121 122 // SOF markers for Nondifferential Huffman coding 123 /** Baseline DCT */ 124 static final int SOF0 = 0xC0; 125 /** Extended Sequential DCT */ 126 static final int SOF1 = 0xC1; 127 /** Progressive DCT */ 128 static final int SOF2 = 0xC2; 129 /** Lossless Sequential */ 130 static final int SOF3 = 0xC3; 131 132 /** Define Huffman Tables */ 133 static final int DHT = 0xC4; 134 135 // SOF markers for Differential Huffman coding 136 /** Differential Sequential DCT */ 137 static final int SOF5 = 0xC5; 138 /** Differential Progressive DCT */ 139 static final int SOF6 = 0xC6; 140 /** Differential Lossless */ 141 static final int SOF7 = 0xC7; 142 143 /** Reserved for JPEG extensions */ 144 static final int JPG = 0xC8; 145 146 // SOF markers for Nondifferential arithmetic coding 147 /** Extended Sequential DCT, Arithmetic coding */ 148 static final int SOF9 = 0xC9; 149 /** Progressive DCT, Arithmetic coding */ 150 static final int SOF10 = 0xCA; 151 /** Lossless Sequential, Arithmetic coding */ 152 static final int SOF11 = 0xCB; 153 154 /** Define Arithmetic conditioning tables */ 155 static final int DAC = 0xCC; 156 157 // SOF markers for Differential arithmetic coding 158 /** Differential Sequential DCT, Arithmetic coding */ 159 static final int SOF13 = 0xCD; 160 /** Differential Progressive DCT, Arithmetic coding */ 161 static final int SOF14 = 0xCE; 162 /** Differential Lossless, Arithmetic coding */ 163 static final int SOF15 = 0xCF; 164 165 // Restart Markers 166 static final int RST0 = 0xD0; 167 static final int RST1 = 0xD1; 168 static final int RST2 = 0xD2; 169 static final int RST3 = 0xD3; 170 static final int RST4 = 0xD4; 171 static final int RST5 = 0xD5; 172 static final int RST6 = 0xD6; 173 static final int RST7 = 0xD7; 174 /** Number of restart markers */ 175 static final int RESTART_RANGE = 8; 176 177 /** Start of Image */ 178 static final int SOI = 0xD8; 179 /** End of Image */ 180 static final int EOI = 0xD9; 181 /** Start of Scan */ 182 static final int SOS = 0xDA; 183 184 /** Define Quantisation Tables */ 185 static final int DQT = 0xDB; 186 187 /** Define Number of lines */ 188 static final int DNL = 0xDC; 189 190 /** Define Restart Interval */ 191 static final int DRI = 0xDD; 192 193 /** Define Heirarchical progression */ 194 static final int DHP = 0xDE; 195 196 /** Expand reference image(s) */ 197 static final int EXP = 0xDF; 198 199 // Application markers 200 /** APP0 used by JFIF */ 201 static final int APP0 = 0xE0; 202 static final int APP1 = 0xE1; 203 static final int APP2 = 0xE2; 204 static final int APP3 = 0xE3; 205 static final int APP4 = 0xE4; 206 static final int APP5 = 0xE5; 207 static final int APP6 = 0xE6; 208 static final int APP7 = 0xE7; 209 static final int APP8 = 0xE8; 210 static final int APP9 = 0xE9; 211 static final int APP10 = 0xEA; 212 static final int APP11 = 0xEB; 213 static final int APP12 = 0xEC; 214 static final int APP13 = 0xED; 215 /** APP14 used by Adobe */ 216 static final int APP14 = 0xEE; 217 static final int APP15 = 0xEF; 218 219 // codes 0xF0 to 0xFD are reserved 220 221 /** Comment marker */ 222 static final int COM = 0xFE; 223 224 // Marker codes for JPEG-LS 225 226 /** JPEG-LS SOF marker */ 227 // This was SOF48 in an earlier revision of the JPEG-LS specification. 228 // "55" is the numerical value of SOF55 - SOF0 (= 247 - 192). 229 static final int SOF55 = 0xF7; 230 231 /** JPEG-LS parameters */ 232 static final int LSE = 0xF2; 233 234 // Min and max APPn codes. 235 static final int APPN_MIN = APP0; 236 static final int APPN_MAX = APP15; 237 238 // Min and max contiguous SOFn codes. 239 static final int SOFN_MIN = SOF0; 240 static final int SOFN_MAX = SOF15; 241 242 // Min and Max RSTn codes. 243 static final int RST_MIN = RST0; 244 static final int RST_MAX = RST7; 245 246 // Specific segment types defined as (code << 8) | X. 247 static final int APP0_JFIF = (APP0 << 8) | 0; 248 static final int APP0_JFXX = (APP0 << 8) | 1; 249 static final int APP1_EXIF = (APP1 << 8) | 0; 250 static final int APP2_ICC = (APP2 << 8) | 0; 251 static final int APP14_ADOBE = (APP14 << 8) | 0; 252 static final int UNKNOWN_MARKER = 0xffff; 253 static final int SOF_MARKER = (SOF0 << 8) | 0; 254 255 // Resolution unit types. 256 static final int JFIF_RESUNITS_ASPECT = 0; 257 static final int JFIF_RESUNITS_DPI = 1; 258 static final int JFIF_RESUNITS_DPC = 2; 259 260 // Thumbnail types 261 static final int THUMBNAIL_JPEG = 0x10; 262 static final int THUMBNAIL_PALETTE = 0x11; 263 static final int THUMBNAIL_RGB = 0x12; 264 265 // Adobe transform type. 266 static final int ADOBE_TRANSFORM_UNKNOWN = 0; 267 static final int ADOBE_TRANSFORM_YCC = 1; 268 static final int ADOBE_TRANSFORM_YCCK = 2; 269 270 // Zig-zag to natural re-ordering array. 271 static final int [] zigzag = { 272 0, 1, 5, 6, 14, 15, 27, 28, 273 2, 4, 7, 13, 16, 26, 29, 42, 274 3, 8, 12, 17, 25, 30, 41, 43, 275 9, 11, 18, 24, 31, 40, 44, 53, 276 10, 19, 23, 32, 39, 45, 52, 54, 277 20, 22, 33, 38, 46, 51, 55, 60, 278 21, 34, 37, 47, 50, 56, 59, 61, 279 35, 36, 48, 49, 57, 58, 62, 63 280 }; 281 282 // --- Static methods --- 283 284 private static IIOImage getThumbnail(ImageInputStream stream, int len, 285 int thumbnailType, int w, int h) 286 throws IOException { 287 288 IIOImage result; 289 290 long startPos = stream.getStreamPosition(); 291 292 if(thumbnailType == THUMBNAIL_JPEG) { 293 Iterator readers = ImageIO.getImageReaders(stream); 294 if(readers == null || !readers.hasNext()) return null; 295 ImageReader reader = (ImageReader)readers.next(); 296 reader.setInput(stream); 297 BufferedImage image = reader.read(0, null); 298 IIOMetadata metadata = null; 299 try { 300 metadata = reader.getImageMetadata(0); 301 } catch(Exception e) { 302 // Ignore it 303 } 304 result = new IIOImage(image, null, metadata); 305 } else { 306 int numBands; 307 ColorModel cm; 308 if(thumbnailType == THUMBNAIL_PALETTE) { 309 if(len < 768 + w*h) { 310 return null; 311 } 312 313 numBands = 1; 314 315 byte[] palette = new byte[768]; 316 stream.readFully(palette); 317 byte[] r = new byte[256]; 318 byte[] g = new byte[256]; 319 byte[] b = new byte[256]; 320 for(int i = 0, off = 0; i < 256; i++) { 321 r[i] = palette[off++]; 322 g[i] = palette[off++]; 323 b[i] = palette[off++]; 324 } 325 326 cm = new IndexColorModel(8, 256, r, g, b); 327 } else { 328 if(len < 3*w*h) { 329 return null; 330 } 331 332 numBands = 3; 333 334 ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 335 cm = new ComponentColorModel(cs, false, false, 336 Transparency.OPAQUE, 337 DataBuffer.TYPE_BYTE); 338 } 339 340 byte[] data = new byte[w*h*numBands]; 341 stream.readFully(data); 342 DataBufferByte db = new DataBufferByte(data, data.length); 343 WritableRaster wr = 344 Raster.createInterleavedRaster(db, w, h, w*numBands, numBands, 345 new int[] {0, 1, 2}, null); 346 BufferedImage image = new BufferedImage(cm, wr, false, null); 347 result = new IIOImage(image, null, null); 348 } 349 350 stream.seek(startPos + len); 351 352 return result; 353 } 354 355 // --- Instance variables --- 356 357 /** Whether the object may be edited. */ 358 private boolean isReadOnly = true; 359 360 // APP0 JFIF marker segment parameters. 361 boolean app0JFIFPresent; 362 int majorVersion = 1; 363 int minorVersion = 2; 364 int resUnits; // (0 = aspect ratio; 1 = dots/inch; 2 = dots/cm) 365 int Xdensity = 1; 366 int Ydensity = 1; 367 int thumbWidth = 0; 368 int thumbHeight = 0; 369 BufferedImage jfifThumbnail; 370 371 // APP0 JFIF thumbnail(s). 372 boolean app0JFXXPresent; 373 List extensionCodes; // Integers 0x10, 0x11, 0x12 374 List jfxxThumbnails; // IIOImages 375 376 // APP2 ICC_PROFILE marker segment parameters. 377 boolean app2ICCPresent; 378 ICC_Profile profile = null; 379 380 // DQT marker segment parameters. 381 boolean dqtPresent; 382 List qtables; // Each element is a List of QTables 383 384 // DHT marker segment parameters. 385 boolean dhtPresent; 386 List htables; // Each element is a List of HuffmanTables 387 388 // DRI marker segment parameters. 389 boolean driPresent; 390 int driInterval; 391 392 // COM marker segment parameters. 393 boolean comPresent; 394 List comments; // byte[]s 395 396 // Unknown marker segment parameters. 397 boolean unknownPresent; 398 List markerTags; // Integers 399 List unknownData; // byte[] (NB: 'length' parameter is array length) 400 401 // APP14 Adobe marker segment parameters. 402 boolean app14AdobePresent; 403 int version = 100; 404 int flags0 = 0; 405 int flags1 = 0; 406 int transform; // 0 = Unknown, 1 = YCbCr, 2 = YCCK 407 408 // SOF marker segment parameters. 409 boolean sofPresent; 410 int sofProcess; 411 int samplePrecision = 8; 412 int numLines; 413 int samplesPerLine; 414 int numFrameComponents; 415 int[] componentId; 416 int[] hSamplingFactor; 417 int[] vSamplingFactor; 418 int[] qtableSelector; 419 420 // SOS marker segment parameters. 421 boolean sosPresent; 422 int numScanComponents; 423 int[] componentSelector; 424 int[] dcHuffTable; 425 int[] acHuffTable; 426 int startSpectralSelection; 427 int endSpectralSelection; 428 int approxHigh; 429 int approxLow; 430 431 // Embedded TIFF stream from EXIF segment. 432 byte[] exifData = null; 433 434 /** Marker codes in the order encountered. */ 435 private List markers = null; // List of Integer 436 437 // Standard metadata variables. 438 private boolean hasAlpha = false; 439 440 // Agregated list of thumbnails: JFIF > JFXX > EXIF. 441 private boolean thumbnailsInitialized = false; 442 private List thumbnails = new ArrayList(); 443 444 CLibJPEGMetadata() { 445 super(true, NATIVE_FORMAT, NATIVE_FORMAT_CLASS, 446 new String[] {TIFF_FORMAT}, new String[] {TIFF_FORMAT_CLASS}); 447 448 this.isReadOnly = isReadOnly; 449 } 450 451 CLibJPEGMetadata(ImageInputStream stream) 452 throws IIOException { 453 this(); 454 455 try { 456 initializeFromStream(stream); 457 } catch(IOException e) { 458 throw new IIOException("Cannot initialize JPEG metadata!", e); 459 } 460 } 461 462 private class QTable { 463 private static final int QTABLE_SIZE = 64; 464 465 int elementPrecision; 466 int tableID; 467 JPEGQTable table; 468 469 int length; 470 471 QTable(ImageInputStream stream) throws IOException { 472 elementPrecision = (int)stream.readBits(4); 473 tableID = (int)stream.readBits(4); 474 byte[] tmp = new byte[QTABLE_SIZE]; 475 stream.readFully(tmp); 476 int[] data = new int[QTABLE_SIZE]; 477 for (int i = 0; i < QTABLE_SIZE; i++) { 478 data[i] = tmp[zigzag[i]] & 0xff; 479 } 480 table = new JPEGQTable(data); 481 length = data.length + 1; 482 } 483 } 484 485 private class HuffmanTable { 486 private static final int NUM_LENGTHS = 16; 487 488 int tableClass; 489 int tableID; 490 JPEGHuffmanTable table; 491 492 int length; 493 494 HuffmanTable(ImageInputStream stream) throws IOException { 495 tableClass = (int)stream.readBits(4); 496 tableID = (int)stream.readBits(4); 497 short[] lengths = new short[NUM_LENGTHS]; 498 for (int i = 0; i < NUM_LENGTHS; i++) { 499 lengths[i] = (short)stream.read(); 500 } 501 int numValues = 0; 502 for (int i = 0; i < NUM_LENGTHS; i++) { 503 numValues += lengths[i]; 504 } 505 short[] values = new short[numValues]; 506 for (int i = 0; i < numValues; i++) { 507 values[i] = (short)stream.read(); 508 } 509 table = new JPEGHuffmanTable(lengths, values); 510 511 length = 1 + NUM_LENGTHS + values.length; 512 } 513 } 514 515 private synchronized void initializeFromStream(ImageInputStream iis) 516 throws IOException { 517 iis.mark(); 518 iis.setByteOrder(ByteOrder.BIG_ENDIAN); 519 520 markers = new ArrayList(); 521 522 boolean isICCProfileValid = true; 523 int numICCProfileChunks = 0; 524 long[] iccProfileChunkOffsets = null; 525 int[] iccProfileChunkLengths = null; 526 527 while(true) { 528 try { 529 // 0xff denotes a potential marker. 530 if(iis.read() == 0xff) { 531 // Get next byte. 532 int code = iis.read(); 533 534 // Is a marker if and only if code not in {0x00, 0xff}. 535 // Continue to next marker if this is not a marker or if 536 // it is an empty marker. 537 if(code == 0x00 || code == 0xff || 538 code == SOI || code == TEM || 539 (code >= RST_MIN && code <= RST_MAX)) { 540 continue; 541 } 542 543 // If at the end, quit. 544 if(code == EOI) { 545 break; 546 } 547 548 // Get the content length. 549 int dataLength = iis.readUnsignedShort() - 2; 550 551 if(APPN_MIN <= code && code <= APPN_MAX) { 552 long pos = iis.getStreamPosition(); 553 boolean appnAdded = false; 554 555 switch(code) { 556 case APP0: 557 if(dataLength >= 5) { 558 byte[] b = new byte[5]; 559 iis.readFully(b); 560 String id = new String(b); 561 if(id.startsWith("JFIF") && 562 !app0JFIFPresent) { 563 app0JFIFPresent = true; 564 markers.add(new Integer(APP0_JFIF)); 565 majorVersion = iis.read(); 566 minorVersion = iis.read(); 567 resUnits = iis.read(); 568 Xdensity = iis.readUnsignedShort(); 569 Ydensity = iis.readUnsignedShort(); 570 thumbWidth = iis.read(); 571 thumbHeight = iis.read(); 572 if(thumbWidth > 0 && thumbHeight > 0) { 573 IIOImage imiio = 574 getThumbnail(iis, dataLength - 14, 575 THUMBNAIL_RGB, 576 thumbWidth, 577 thumbHeight); 578 if(imiio != null) { 579 jfifThumbnail = (BufferedImage) 580 imiio.getRenderedImage(); 581 } 582 } 583 appnAdded = true; 584 } else if(id.startsWith("JFXX")) { 585 if(!app0JFXXPresent) { 586 extensionCodes = new ArrayList(1); 587 jfxxThumbnails = new ArrayList(1); 588 app0JFXXPresent = true; 589 } 590 markers.add(new Integer(APP0_JFXX)); 591 int extCode = iis.read(); 592 extensionCodes.add(new Integer(extCode)); 593 int w = 0, h = 0, offset = 6; 594 if(extCode != THUMBNAIL_JPEG) { 595 w = iis.read(); 596 h = iis.read(); 597 offset += 2; 598 } 599 IIOImage imiio = 600 getThumbnail(iis, dataLength - offset, 601 extCode, w, h); 602 if(imiio != null) { 603 jfxxThumbnails.add(imiio); 604 } 605 appnAdded = true; 606 } 607 } 608 break; 609 case APP1: 610 if(dataLength >= 6) { 611 byte[] b = new byte[6]; 612 iis.readFully(b); 613 if(b[0] == (byte)'E' && 614 b[1] == (byte)'x' && 615 b[2] == (byte)'i' && 616 b[3] == (byte)'f' && 617 b[4] == (byte)0 && 618 b[5] == (byte)0) { 619 exifData = new byte[dataLength - 6]; 620 iis.readFully(exifData); 621 } 622 } 623 case APP2: 624 if(dataLength >= 12) { 625 byte[] b = new byte[12]; 626 iis.readFully(b); 627 String id = new String(b); 628 if(id.startsWith("ICC_PROFILE")) { 629 if(!isICCProfileValid) { 630 iis.skipBytes(dataLength - 12); 631 continue; 632 } 633 634 int chunkNum = iis.read(); 635 int numChunks = iis.read(); 636 if(numChunks == 0 || 637 chunkNum == 0 || 638 chunkNum > numChunks || 639 (app2ICCPresent && 640 (numChunks != numICCProfileChunks || 641 iccProfileChunkOffsets[chunkNum] 642 != 0L))) { 643 isICCProfileValid = false; 644 iis.skipBytes(dataLength - 14); 645 continue; 646 } 647 648 if(!app2ICCPresent) { 649 app2ICCPresent = true; 650 // Only flag one marker even though 651 // multiple may be present. 652 markers.add(new Integer(APP2_ICC)); 653 654 numICCProfileChunks = numChunks; 655 656 if(numChunks == 1) { 657 b = new byte[dataLength - 14]; 658 iis.readFully(b); 659 profile = 660 ICC_Profile.getInstance(b); 661 } else { 662 iccProfileChunkOffsets = 663 new long[numChunks + 1]; 664 iccProfileChunkLengths = 665 new int[numChunks + 1]; 666 iccProfileChunkOffsets[chunkNum] = 667 iis.getStreamPosition(); 668 iccProfileChunkLengths[chunkNum] = 669 dataLength - 14; 670 iis.skipBytes(dataLength - 14); 671 } 672 } else { 673 iccProfileChunkOffsets[chunkNum] = 674 iis.getStreamPosition(); 675 iccProfileChunkLengths[chunkNum] = 676 dataLength - 14; 677 iis.skipBytes(dataLength - 14); 678 } 679 680 appnAdded = true; 681 } 682 } 683 break; 684 case APP14: 685 if(dataLength >= 5) { 686 byte[] b = new byte[5]; 687 iis.readFully(b); 688 String id = new String(b); 689 if(id.startsWith("Adobe") && 690 !app14AdobePresent) { // Adobe segment 691 app14AdobePresent = true; 692 markers.add(new Integer(APP14_ADOBE)); 693 version = iis.readUnsignedShort(); 694 flags0 = iis.readUnsignedShort(); 695 flags1 = iis.readUnsignedShort(); 696 transform = iis.read(); 697 iis.skipBytes(dataLength - 12); 698 appnAdded = true; 699 } 700 } 701 break; 702 default: 703 appnAdded = false; 704 break; 705 } 706 707 if(!appnAdded) { 708 iis.seek(pos); 709 addUnknownMarkerSegment(iis, code, dataLength); 710 } 711 } else if(code == DQT) { 712 if(!dqtPresent) { 713 dqtPresent = true; 714 qtables = new ArrayList(1); 715 } 716 markers.add(new Integer(DQT)); 717 List l = new ArrayList(1); 718 do { 719 QTable t = new QTable(iis); 720 l.add(t); 721 dataLength -= t.length; 722 } while(dataLength > 0); 723 qtables.add(l); 724 } else if(code == DHT) { 725 if(!dhtPresent) { 726 dhtPresent = true; 727 htables = new ArrayList(1); 728 } 729 markers.add(new Integer(DHT)); 730 List l = new ArrayList(1); 731 do { 732 HuffmanTable t = new HuffmanTable(iis); 733 l.add(t); 734 dataLength -= t.length; 735 } while(dataLength > 0); 736 htables.add(l); 737 } else if(code == DRI) { 738 if(!driPresent) { 739 driPresent = true; 740 } 741 markers.add(new Integer(DRI)); 742 driInterval = iis.readUnsignedShort(); 743 } else if(code == COM) { 744 if(!comPresent) { 745 comPresent = true; 746 comments = new ArrayList(1); 747 } 748 markers.add(new Integer(COM)); 749 byte[] b = new byte[dataLength]; 750 iis.readFully(b); 751 comments.add(b); 752 } else if((code >= SOFN_MIN && code <= SOFN_MAX) || 753 code == SOF55) { // SOFn 754 if(!sofPresent) { 755 sofPresent = true; 756 sofProcess = code - SOFN_MIN; 757 samplePrecision = iis.read(); 758 numLines = iis.readUnsignedShort(); 759 samplesPerLine = iis.readUnsignedShort(); 760 numFrameComponents = iis.read(); 761 componentId = new int[numFrameComponents]; 762 hSamplingFactor = new int[numFrameComponents]; 763 vSamplingFactor = new int[numFrameComponents]; 764 qtableSelector = new int[numFrameComponents]; 765 for(int i = 0; i < numFrameComponents; i++) { 766 componentId[i] = iis.read(); 767 hSamplingFactor[i] = (int)iis.readBits(4); 768 vSamplingFactor[i] = (int)iis.readBits(4); 769 qtableSelector[i] = iis.read(); 770 } 771 markers.add(new Integer(SOF_MARKER)); 772 } 773 } else if(code == SOS) { 774 if(!sosPresent) { 775 sosPresent = true; 776 numScanComponents = iis.read(); 777 componentSelector = new int[numScanComponents]; 778 dcHuffTable = new int[numScanComponents]; 779 acHuffTable = new int[numScanComponents]; 780 for(int i = 0; i < numScanComponents; i++) { 781 componentSelector[i] = iis.read(); 782 dcHuffTable[i] = (int)iis.readBits(4); 783 acHuffTable[i] = (int)iis.readBits(4); 784 } 785 startSpectralSelection = iis.read(); 786 endSpectralSelection = iis.read(); 787 approxHigh = (int)iis.readBits(4); 788 approxLow = (int)iis.readBits(4); 789 markers.add(new Integer(SOS)); 790 } 791 break; 792 } else { // Any other marker 793 addUnknownMarkerSegment(iis, code, dataLength); 794 } 795 } 796 } catch(EOFException eofe) { 797 // XXX Should this be caught? 798 break; 799 } 800 } 801 802 if(app2ICCPresent && isICCProfileValid && profile == null) { 803 int profileDataLength = 0; 804 for(int i = 1; i <= numICCProfileChunks; i++) { 805 if(iccProfileChunkOffsets[i] == 0L) { 806 isICCProfileValid = false; 807 break; 808 } 809 profileDataLength += iccProfileChunkLengths[i]; 810 } 811 812 if(isICCProfileValid) { 813 byte[] b = new byte[profileDataLength]; 814 int off = 0; 815 for(int i = 1; i <= numICCProfileChunks; i++) { 816 iis.seek(iccProfileChunkOffsets[i]); 817 iis.read(b, off, iccProfileChunkLengths[i]); 818 off += iccProfileChunkLengths[i]; 819 } 820 821 profile = ICC_Profile.getInstance(b); 822 } 823 } 824 825 iis.reset(); 826 } 827 828 private void addUnknownMarkerSegment(ImageInputStream stream, 829 int code, int len) 830 throws IOException { 831 if(!unknownPresent) { 832 unknownPresent = true; 833 markerTags = new ArrayList(1); 834 unknownData = new ArrayList(1); 835 } 836 markerTags.add(new Integer(code)); 837 byte[] b = new byte[len]; 838 stream.readFully(b); 839 unknownData.add(b); 840 markers.add(new Integer(UNKNOWN_MARKER)); 841 } 842 843 public boolean isReadOnly() { 844 return isReadOnly; 845 } 846 847 public Node getAsTree(String formatName) { 848 if (formatName.equals(nativeMetadataFormatName)) { 849 return getNativeTree(); 850 } else if (formatName.equals 851 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 852 return getStandardTree(); 853 } else if(formatName.equals(TIFF_FORMAT)) { 854 return getTIFFTree(); 855 } else { 856 throw new IllegalArgumentException("Not a recognized format!"); 857 } 858 } 859 860 public void mergeTree(String formatName, Node root) 861 throws IIOInvalidTreeException { 862 if(isReadOnly) { 863 throw new IllegalStateException("isReadOnly() == true!"); 864 } 865 } 866 867 public void reset() { 868 if(isReadOnly) { 869 throw new IllegalStateException("isReadOnly() == true!"); 870 } 871 } 872 873 // Native tree method. 874 875 private Node getNativeTree() { 876 int jfxxIndex = 0; 877 int dqtIndex = 0; 878 int dhtIndex = 0; 879 int comIndex = 0; 880 int unknownIndex = 0; 881 882 IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); 883 884 IIOMetadataNode JPEGvariety = new IIOMetadataNode("JPEGvariety"); 885 root.appendChild(JPEGvariety); 886 887 IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); 888 root.appendChild(markerSequence); 889 890 IIOMetadataNode app0JFIF = null; 891 if(app0JFIFPresent || app0JFXXPresent || app2ICCPresent) { 892 app0JFIF = new IIOMetadataNode("app0JFIF"); 893 app0JFIF.setAttribute("majorVersion", 894 Integer.toString(majorVersion)); 895 app0JFIF.setAttribute("minorVersion", 896 Integer.toString(minorVersion)); 897 app0JFIF.setAttribute("resUnits", 898 Integer.toString(resUnits)); 899 app0JFIF.setAttribute("Xdensity", 900 Integer.toString(Xdensity)); 901 app0JFIF.setAttribute("Ydensity", 902 Integer.toString(Ydensity)); 903 app0JFIF.setAttribute("thumbWidth", 904 Integer.toString(thumbWidth)); 905 app0JFIF.setAttribute("thumbHeight", 906 Integer.toString(thumbHeight)); 907 JPEGvariety.appendChild(app0JFIF); 908 } 909 910 IIOMetadataNode JFXX = null; 911 if(app0JFXXPresent) { 912 JFXX = new IIOMetadataNode("JFXX"); 913 app0JFIF.appendChild(JFXX); 914 } 915 916 Iterator markerIter = markers.iterator(); 917 while(markerIter.hasNext()) { 918 int marker = ((Integer)markerIter.next()).intValue(); 919 switch(marker) { 920 case APP0_JFIF: 921 // Do nothing: already handled above. 922 break; 923 case APP0_JFXX: 924 IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX"); 925 Integer extensionCode = (Integer)extensionCodes.get(jfxxIndex); 926 app0JFXX.setAttribute("extensionCode", 927 extensionCode.toString()); 928 IIOMetadataNode JFIFthumb = null; 929 switch(extensionCode.intValue()) { 930 case THUMBNAIL_JPEG: 931 JFIFthumb = new IIOMetadataNode("JFIFthumbJPEG"); 932 break; 933 case THUMBNAIL_PALETTE: 934 JFIFthumb = new IIOMetadataNode("JFIFthumbPalette"); 935 break; 936 case THUMBNAIL_RGB: 937 JFIFthumb = new IIOMetadataNode("JFIFthumbRGB"); 938 break; 939 default: 940 // No JFIFthumb node will be appended. 941 } 942 if(JFIFthumb != null) { 943 IIOImage img = (IIOImage)jfxxThumbnails.get(jfxxIndex++); 944 if(extensionCode.intValue() == THUMBNAIL_JPEG) { 945 IIOMetadata thumbMetadata = img.getMetadata(); 946 if(thumbMetadata != null) { 947 Node thumbTree = 948 thumbMetadata.getAsTree(nativeMetadataFormatName); 949 if(thumbTree instanceof IIOMetadataNode) { 950 IIOMetadataNode elt = 951 (IIOMetadataNode)thumbTree; 952 NodeList elts = 953 elt.getElementsByTagName("markerSequence"); 954 if(elts.getLength() > 0) { 955 JFIFthumb.appendChild(elts.item(0)); 956 } 957 } 958 } 959 } else { 960 BufferedImage thumb = 961 (BufferedImage)img.getRenderedImage(); 962 JFIFthumb.setAttribute("thumbWidth", 963 Integer.toString(thumb.getWidth())); 964 JFIFthumb.setAttribute("thumbHeight", 965 Integer.toString(thumb.getHeight())); 966 } 967 // Add thumbnail as a user object even though not in 968 // metadata specification. 969 JFIFthumb.setUserObject(img); 970 app0JFXX.appendChild(JFIFthumb); 971 } 972 JFXX.appendChild(app0JFXX); 973 break; 974 case APP2_ICC: 975 IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); 976 app2ICC.setUserObject(profile); 977 app0JFIF.appendChild(app2ICC); 978 break; 979 case DQT: 980 IIOMetadataNode dqt = new IIOMetadataNode("dqt"); 981 List tables = (List)qtables.get(dqtIndex++); 982 int numTables = tables.size(); 983 for(int j = 0; j < numTables; j++) { 984 IIOMetadataNode dqtable = new IIOMetadataNode("dqtable"); 985 QTable t = (QTable)tables.get(j); 986 dqtable.setAttribute("elementPrecision", 987 Integer.toString(t.elementPrecision)); 988 dqtable.setAttribute("qtableId", 989 Integer.toString(t.tableID)); 990 dqtable.setUserObject(t.table); 991 dqt.appendChild(dqtable); 992 } 993 markerSequence.appendChild(dqt); 994 break; 995 case DHT: 996 IIOMetadataNode dht = new IIOMetadataNode("dht"); 997 tables = (List)htables.get(dhtIndex++); 998 numTables = tables.size(); 999 for(int j = 0; j < numTables; j++) { 1000 IIOMetadataNode dhtable = new IIOMetadataNode("dhtable"); 1001 HuffmanTable t = (HuffmanTable)tables.get(j); 1002 dhtable.setAttribute("class", 1003 Integer.toString(t.tableClass)); 1004 dhtable.setAttribute("htableId", 1005 Integer.toString(t.tableID)); 1006 dhtable.setUserObject(t.table); 1007 dht.appendChild(dhtable); 1008 } 1009 markerSequence.appendChild(dht); 1010 break; 1011 case DRI: 1012 IIOMetadataNode dri = new IIOMetadataNode("dri"); 1013 dri.setAttribute("interval", Integer.toString(driInterval)); 1014 markerSequence.appendChild(dri); 1015 break; 1016 case COM: 1017 IIOMetadataNode com = new IIOMetadataNode("com"); 1018 com.setUserObject(comments.get(comIndex++)); 1019 markerSequence.appendChild(com); 1020 break; 1021 case UNKNOWN_MARKER: 1022 IIOMetadataNode unknown = new IIOMetadataNode("unknown"); 1023 Integer markerTag = (Integer)markerTags.get(unknownIndex); 1024 unknown.setAttribute("MarkerTag", markerTag.toString()); 1025 unknown.setUserObject(unknownData.get(unknownIndex++)); 1026 markerSequence.appendChild(unknown); 1027 break; 1028 case APP14_ADOBE: 1029 IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe"); 1030 app14Adobe.setAttribute("version", Integer.toString(version)); 1031 app14Adobe.setAttribute("flags0", Integer.toString(flags0)); 1032 app14Adobe.setAttribute("flags1", Integer.toString(flags1)); 1033 app14Adobe.setAttribute("transform", 1034 Integer.toString(transform)); 1035 markerSequence.appendChild(app14Adobe); 1036 break; 1037 case SOF_MARKER: 1038 IIOMetadataNode sof = new IIOMetadataNode("sof"); 1039 sof.setAttribute("process", Integer.toString(sofProcess)); 1040 sof.setAttribute("samplePrecision", 1041 Integer.toString(samplePrecision)); 1042 sof.setAttribute("numLines", Integer.toString(numLines)); 1043 sof.setAttribute("samplesPerLine", 1044 Integer.toString(samplesPerLine)); 1045 sof.setAttribute("numFrameComponents", 1046 Integer.toString(numFrameComponents)); 1047 for(int i = 0; i < numFrameComponents; i++) { 1048 IIOMetadataNode componentSpec = 1049 new IIOMetadataNode("componentSpec"); 1050 componentSpec.setAttribute("componentId", 1051 Integer.toString(componentId[i])); 1052 componentSpec.setAttribute("HsamplingFactor", 1053 Integer.toString(hSamplingFactor[i])); 1054 componentSpec.setAttribute("VsamplingFactor", 1055 Integer.toString(vSamplingFactor[i])); 1056 componentSpec.setAttribute("QtableSelector", 1057 Integer.toString(qtableSelector[i])); 1058 sof.appendChild(componentSpec); 1059 } 1060 markerSequence.appendChild(sof); 1061 break; 1062 case SOS: 1063 IIOMetadataNode sos = new IIOMetadataNode("sos"); 1064 sos.setAttribute("numScanComponents", 1065 Integer.toString(numScanComponents)); 1066 sos.setAttribute("startSpectralSelection", 1067 Integer.toString(startSpectralSelection)); 1068 sos.setAttribute("endSpectralSelection", 1069 Integer.toString(endSpectralSelection)); 1070 sos.setAttribute("approxHigh", Integer.toString(approxHigh)); 1071 sos.setAttribute("approxLow", Integer.toString(approxLow)); 1072 for(int i = 0; i < numScanComponents; i++) { 1073 IIOMetadataNode scanComponentSpec = 1074 new IIOMetadataNode("scanComponentSpec"); 1075 scanComponentSpec.setAttribute("componentSelector", 1076 Integer.toString(componentSelector[i])); 1077 scanComponentSpec.setAttribute("dcHuffTable", 1078 Integer.toString(dcHuffTable[i])); 1079 scanComponentSpec.setAttribute("acHuffTable", 1080 Integer.toString(acHuffTable[i])); 1081 sos.appendChild(scanComponentSpec); 1082 } 1083 markerSequence.appendChild(sos); 1084 break; 1085 } 1086 } 1087 1088 return root; 1089 } 1090 1091 // Standard tree node methods 1092 1093 protected IIOMetadataNode getStandardChromaNode() { 1094 if(!sofPresent) { 1095 // No image, so no chroma 1096 return null; 1097 } 1098 1099 IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); 1100 IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); 1101 chroma.appendChild(csType); 1102 1103 IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels"); 1104 chroma.appendChild(numChanNode); 1105 numChanNode.setAttribute("value", 1106 Integer.toString(numFrameComponents)); 1107 1108 // Check JFIF presence. 1109 if(app0JFIFPresent) { 1110 if(numFrameComponents == 1) { 1111 csType.setAttribute("name", "GRAY"); 1112 } else { 1113 csType.setAttribute("name", "YCbCr"); 1114 } 1115 return chroma; 1116 } 1117 1118 // How about an Adobe marker segment? 1119 if(app14AdobePresent){ 1120 switch(transform) { 1121 case ADOBE_TRANSFORM_YCCK: // YCCK 1122 csType.setAttribute("name", "YCCK"); 1123 break; 1124 case ADOBE_TRANSFORM_YCC: // YCC 1125 csType.setAttribute("name", "YCbCr"); 1126 break; 1127 case ADOBE_TRANSFORM_UNKNOWN: // Unknown 1128 if(numFrameComponents == 3) { 1129 csType.setAttribute("name", "RGB"); 1130 } else if(numFrameComponents == 4) { 1131 csType.setAttribute("name", "CMYK"); 1132 } 1133 break; 1134 } 1135 return chroma; 1136 } 1137 1138 // Initially assume no opacity. 1139 hasAlpha = false; 1140 1141 // Neither marker. Check components 1142 if(numFrameComponents < 3) { 1143 csType.setAttribute("name", "GRAY"); 1144 if(numFrameComponents == 2) { 1145 hasAlpha = true; 1146 } 1147 return chroma; 1148 } 1149 1150 boolean idsAreJFIF = true; 1151 1152 for(int i = 0; i < componentId.length; i++) { 1153 int id = componentId[i]; 1154 if((id < 1) || (id >= componentId.length)) { 1155 idsAreJFIF = false; 1156 } 1157 } 1158 1159 if(idsAreJFIF) { 1160 csType.setAttribute("name", "YCbCr"); 1161 if(numFrameComponents == 4) { 1162 hasAlpha = true; 1163 } 1164 return chroma; 1165 } 1166 1167 // Check against the letters 1168 if(componentId[0] == 'R' && 1169 componentId[1] == 'G' && 1170 componentId[2] == 'B'){ 1171 csType.setAttribute("name", "RGB"); 1172 if(numFrameComponents == 4 && componentId[3] == 'A') { 1173 hasAlpha = true; 1174 } 1175 return chroma; 1176 } 1177 1178 if(componentId[0] == 'Y' && 1179 componentId[1] == 'C' && 1180 componentId[2] == 'c'){ 1181 csType.setAttribute("name", "PhotoYCC"); 1182 if(numFrameComponents == 4 && 1183 componentId[3] == 'A') { 1184 hasAlpha = true; 1185 } 1186 return chroma; 1187 } 1188 1189 // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB 1190 // 4-channel subsampled are YCbCrA, unsubsampled are CMYK 1191 1192 boolean subsampled = false; 1193 1194 int hfactor = hSamplingFactor[0]; 1195 int vfactor = vSamplingFactor[0]; 1196 1197 for(int i = 1; i < componentId.length; i++) { 1198 if(hSamplingFactor[i] != hfactor || 1199 vSamplingFactor[i] != vfactor){ 1200 subsampled = true; 1201 break; 1202 } 1203 } 1204 1205 if(subsampled) { 1206 csType.setAttribute("name", "YCbCr"); 1207 if(numFrameComponents == 4) { 1208 hasAlpha = true; 1209 } 1210 return chroma; 1211 } 1212 1213 // Not subsampled. numFrameComponents < 3 is taken care of above 1214 if(numFrameComponents == 3) { 1215 csType.setAttribute("name", "RGB"); 1216 } else { 1217 csType.setAttribute("name", "CMYK"); 1218 } 1219 1220 return chroma; 1221 } 1222 1223 protected IIOMetadataNode getStandardCompressionNode() { 1224 IIOMetadataNode compression = null; 1225 1226 if(sofPresent || sosPresent) { 1227 compression = new IIOMetadataNode("Compression"); 1228 1229 if(sofPresent) { 1230 // Process 55 is JPEG-LS, others are lossless JPEG. 1231 boolean isLossless = 1232 sofProcess == 3 || sofProcess == 7 || sofProcess == 11 || 1233 sofProcess == 15 || sofProcess == 55; 1234 1235 // CompressionTypeName 1236 IIOMetadataNode name = 1237 new IIOMetadataNode("CompressionTypeName"); 1238 String compressionType = isLossless ? 1239 (sofProcess == 55 ? "JPEG-LS" : "JPEG-LOSSLESS") : "JPEG"; 1240 name.setAttribute("value", compressionType); 1241 compression.appendChild(name); 1242 1243 // Lossless - false 1244 IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); 1245 lossless.setAttribute("value", isLossless ? "true" : "false"); 1246 compression.appendChild(lossless); 1247 } 1248 1249 if(sosPresent) { 1250 IIOMetadataNode prog = 1251 new IIOMetadataNode("NumProgressiveScans"); 1252 prog.setAttribute("value", "1"); 1253 compression.appendChild(prog); 1254 } 1255 } 1256 1257 return compression; 1258 } 1259 1260 protected IIOMetadataNode getStandardDimensionNode() { 1261 IIOMetadataNode dim = new IIOMetadataNode("Dimension"); 1262 IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation"); 1263 orient.setAttribute("value", "normal"); 1264 dim.appendChild(orient); 1265 1266 if(app0JFIFPresent) { 1267 float aspectRatio; 1268 if(resUnits == JFIF_RESUNITS_ASPECT) { 1269 // Aspect ratio. 1270 aspectRatio = (float)Xdensity/(float)Ydensity; 1271 } else { 1272 // Density. 1273 aspectRatio = (float)Ydensity/(float)Xdensity; 1274 } 1275 IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio"); 1276 aspect.setAttribute("value", Float.toString(aspectRatio)); 1277 dim.insertBefore(aspect, orient); 1278 1279 if(resUnits != JFIF_RESUNITS_ASPECT) { 1280 // 1 == dpi, 2 == dpc 1281 float scale = (resUnits == JFIF_RESUNITS_DPI) ? 25.4F : 10.0F; 1282 1283 IIOMetadataNode horiz = 1284 new IIOMetadataNode("HorizontalPixelSize"); 1285 horiz.setAttribute("value", 1286 Float.toString(scale/Xdensity)); 1287 dim.appendChild(horiz); 1288 1289 IIOMetadataNode vert = 1290 new IIOMetadataNode("VerticalPixelSize"); 1291 vert.setAttribute("value", 1292 Float.toString(scale/Ydensity)); 1293 dim.appendChild(vert); 1294 } 1295 } 1296 return dim; 1297 } 1298 1299 protected IIOMetadataNode getStandardTextNode() { 1300 IIOMetadataNode text = null; 1301 if(comPresent) { 1302 text = new IIOMetadataNode("Text"); 1303 Iterator iter = comments.iterator(); 1304 while (iter.hasNext()) { 1305 IIOMetadataNode entry = new IIOMetadataNode("TextEntry"); 1306 entry.setAttribute("keyword", "comment"); 1307 byte[] data = (byte[])iter.next(); 1308 try { 1309 entry.setAttribute("value", 1310 new String(data, "ISO-8859-1")); 1311 } catch(UnsupportedEncodingException e) { 1312 entry.setAttribute("value", new String(data)); 1313 } 1314 text.appendChild(entry); 1315 } 1316 } 1317 return text; 1318 } 1319 1320 // This method assumes that getStandardChromaNode() has already been 1321 // called to initialize hasAlpha. 1322 protected IIOMetadataNode getStandardTransparencyNode() { 1323 IIOMetadataNode trans = null; 1324 if (hasAlpha == true) { 1325 trans = new IIOMetadataNode("Transparency"); 1326 IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); 1327 alpha.setAttribute("value", "nonpremultiplied"); // Always assume 1328 trans.appendChild(alpha); 1329 } 1330 return trans; 1331 } 1332 1333 // TIFF tree method 1334 1335 private Node getTIFFTree() { 1336 String metadataName = TIFF_FORMAT; 1337 1338 BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); 1339 1340 TIFFDirectory dir = 1341 new TIFFDirectory(new TIFFTagSet[] { 1342 base, EXIFParentTIFFTagSet.getInstance() 1343 }, null); 1344 1345 if(sofPresent) { 1346 // sofProcess -> Compression ? 1347 int compression = BaselineTIFFTagSet.COMPRESSION_JPEG; 1348 TIFFField compressionField = 1349 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION), 1350 compression); 1351 dir.addTIFFField(compressionField); 1352 1353 // samplePrecision -> BitsPerSample 1354 char[] bitsPerSample = new char[numFrameComponents]; 1355 Arrays.fill(bitsPerSample, (char)(samplePrecision & 0xff)); 1356 TIFFField bitsPerSampleField = 1357 new TIFFField( 1358 base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE), 1359 TIFFTag.TIFF_SHORT, 1360 bitsPerSample.length, 1361 bitsPerSample); 1362 dir.addTIFFField(bitsPerSampleField); 1363 1364 // numLines -> ImageLength 1365 TIFFField imageLengthField = 1366 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH), 1367 numLines); 1368 dir.addTIFFField(imageLengthField); 1369 1370 // samplesPerLine -> ImageWidth 1371 TIFFField imageWidthField = 1372 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH), 1373 samplesPerLine); 1374 dir.addTIFFField(imageWidthField); 1375 1376 // numFrameComponents -> SamplesPerPixel 1377 TIFFField samplesPerPixelField = 1378 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL), 1379 numFrameComponents); 1380 dir.addTIFFField(samplesPerPixelField); 1381 1382 // componentId -> PhotometricInterpretation + ExtraSamples 1383 IIOMetadataNode chroma = getStandardChromaNode(); 1384 if(chroma != null) { 1385 IIOMetadataNode csType = 1386 (IIOMetadataNode)chroma.getElementsByTagName("ColorSpaceType").item(0); 1387 String name = csType.getAttribute("name"); 1388 int photometricInterpretation = -1; 1389 if(name.equals("GRAY")) { 1390 photometricInterpretation = 1391 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 1392 } else if(name.equals("YCbCr") || name.equals("PhotoYCC")) { 1393 // NOTE: PhotoYCC -> YCbCr 1394 photometricInterpretation = 1395 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; 1396 } else if(name.equals("RGB")) { 1397 photometricInterpretation = 1398 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; 1399 } else if(name.equals("CMYK") || name.equals("YCCK")) { 1400 // NOTE: YCCK -> CMYK 1401 photometricInterpretation = 1402 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK; 1403 } 1404 1405 if(photometricInterpretation != -1) { 1406 TIFFField photometricInterpretationField = 1407 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION), 1408 photometricInterpretation); 1409 dir.addTIFFField(photometricInterpretationField); 1410 } 1411 1412 if(hasAlpha) { 1413 char[] extraSamples = 1414 new char[] {BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA}; 1415 TIFFField extraSamplesField = 1416 new TIFFField( 1417 base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES), 1418 TIFFTag.TIFF_SHORT, 1419 extraSamples.length, 1420 extraSamples); 1421 dir.addTIFFField(extraSamplesField); 1422 } 1423 } // chroma != null 1424 } // sofPresent 1425 1426 // JFIF APP0 -> Resolution fields. 1427 if(app0JFIFPresent) { 1428 long[][] xResolution = new long[][] {{Xdensity, 1}}; 1429 TIFFField XResolutionField = 1430 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), 1431 TIFFTag.TIFF_RATIONAL, 1432 1, 1433 xResolution); 1434 dir.addTIFFField(XResolutionField); 1435 1436 long[][] yResolution = new long[][] {{Ydensity, 1}}; 1437 TIFFField YResolutionField = 1438 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), 1439 TIFFTag.TIFF_RATIONAL, 1440 1, 1441 yResolution); 1442 dir.addTIFFField(YResolutionField); 1443 1444 int resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE; 1445 switch(resUnits) { 1446 case JFIF_RESUNITS_ASPECT: 1447 resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_NONE; 1448 case JFIF_RESUNITS_DPI: 1449 resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_INCH; 1450 break; 1451 case JFIF_RESUNITS_DPC: 1452 resolutionUnit = BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER; 1453 break; 1454 } 1455 TIFFField ResolutionUnitField = 1456 new TIFFField(base.getTag 1457 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), 1458 resolutionUnit); 1459 dir.addTIFFField(ResolutionUnitField); 1460 } 1461 1462 // DQT + DHT -> JPEGTables. 1463 byte[] jpegTablesData = null; 1464 if(dqtPresent || dqtPresent) { 1465 // Determine length of JPEGTables data. 1466 int jpegTablesLength = 2; // SOI 1467 if(dqtPresent) { 1468 Iterator dqts = qtables.iterator(); 1469 while(dqts.hasNext()) { 1470 Iterator qtiter = ((List)dqts.next()).iterator(); 1471 while(qtiter.hasNext()) { 1472 QTable qt = (QTable)qtiter.next(); 1473 jpegTablesLength += 4 + qt.length; 1474 } 1475 } 1476 } 1477 if(dhtPresent) { 1478 Iterator dhts = htables.iterator(); 1479 while(dhts.hasNext()) { 1480 Iterator htiter = ((List)dhts.next()).iterator(); 1481 while(htiter.hasNext()) { 1482 HuffmanTable ht = (HuffmanTable)htiter.next(); 1483 jpegTablesLength += 4 + ht.length; 1484 } 1485 } 1486 } 1487 jpegTablesLength += 2; // EOI 1488 1489 // Allocate space. 1490 jpegTablesData = new byte[jpegTablesLength]; 1491 1492 // SOI 1493 jpegTablesData[0] = (byte)0xff; 1494 jpegTablesData[1] = (byte)SOI; 1495 int jpoff = 2; 1496 1497 if(dqtPresent) { 1498 Iterator dqts = qtables.iterator(); 1499 while(dqts.hasNext()) { 1500 Iterator qtiter = ((List)dqts.next()).iterator(); 1501 while(qtiter.hasNext()) { 1502 jpegTablesData[jpoff++] = (byte)0xff; 1503 jpegTablesData[jpoff++] = (byte)DQT; 1504 QTable qt = (QTable)qtiter.next(); 1505 int qtlength = qt.length + 2; 1506 jpegTablesData[jpoff++] = 1507 (byte)((qtlength & 0xff00) >> 8); 1508 jpegTablesData[jpoff++] = (byte)(qtlength & 0xff); 1509 jpegTablesData[jpoff++] = 1510 (byte)(((qt.elementPrecision & 0xf0) << 4) | 1511 (qt.tableID & 0x0f)); 1512 int[] table = qt.table.getTable(); 1513 int qlen = table.length; 1514 for(int i = 0; i < qlen; i++) { 1515 jpegTablesData[jpoff + zigzag[i]] = (byte)table[i]; 1516 } 1517 jpoff += qlen; 1518 } 1519 } 1520 } 1521 1522 if(dhtPresent) { 1523 Iterator dhts = htables.iterator(); 1524 while(dhts.hasNext()) { 1525 Iterator htiter = ((List)dhts.next()).iterator(); 1526 while(htiter.hasNext()) { 1527 jpegTablesData[jpoff++] = (byte)0xff; 1528 jpegTablesData[jpoff++] = (byte)DHT; 1529 HuffmanTable ht = (HuffmanTable)htiter.next(); 1530 int htlength = ht.length + 2; 1531 jpegTablesData[jpoff++] = 1532 (byte)((htlength & 0xff00) >> 8); 1533 jpegTablesData[jpoff++] = (byte)(htlength & 0xff); 1534 jpegTablesData[jpoff++] = 1535 (byte)(((ht.tableClass & 0x0f) << 4) | 1536 (ht.tableID & 0x0f)); 1537 short[] lengths = ht.table.getLengths(); 1538 int numLengths = lengths.length; 1539 for(int i = 0; i < numLengths; i++) { 1540 jpegTablesData[jpoff++] = (byte)lengths[i]; 1541 } 1542 short[] values = ht.table.getValues(); 1543 int numValues = values.length; 1544 for(int i = 0; i < numValues; i++) { 1545 jpegTablesData[jpoff++] = (byte)values[i]; 1546 } 1547 } 1548 } 1549 } 1550 1551 jpegTablesData[jpoff++] = (byte)0xff; 1552 jpegTablesData[jpoff] = (byte)EOI; 1553 } 1554 if(jpegTablesData != null) { 1555 TIFFField JPEGTablesField = 1556 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES), 1557 TIFFTag.TIFF_UNDEFINED, 1558 jpegTablesData.length, 1559 jpegTablesData); 1560 dir.addTIFFField(JPEGTablesField); 1561 } 1562 1563 IIOMetadata tiffMetadata = dir.getAsMetadata(); 1564 1565 if(exifData != null) { 1566 try { 1567 Iterator tiffReaders = 1568 ImageIO.getImageReadersByFormatName("TIFF"); 1569 if(tiffReaders != null && tiffReaders.hasNext()) { 1570 ImageReader tiffReader = (ImageReader)tiffReaders.next(); 1571 ByteArrayInputStream bais = 1572 new ByteArrayInputStream(exifData); 1573 ImageInputStream exifStream = 1574 new MemoryCacheImageInputStream(bais); 1575 tiffReader.setInput(exifStream); 1576 IIOMetadata exifMetadata = tiffReader.getImageMetadata(0); 1577 tiffMetadata.mergeTree(metadataName, 1578 exifMetadata.getAsTree(metadataName)); 1579 tiffReader.reset(); 1580 } 1581 } catch(IOException ioe) { 1582 // Ignore it. 1583 } 1584 } 1585 1586 return tiffMetadata.getAsTree(metadataName); 1587 } 1588 1589 // Thumbnail methods 1590 1591 private void initializeThumbnails() { 1592 synchronized(thumbnails) { 1593 if(!thumbnailsInitialized) { 1594 // JFIF/JFXX are not supposed to coexist in the same 1595 // JPEG stream but in reality sometimes they do. 1596 1597 // JFIF thumbnail 1598 if(app0JFIFPresent && jfifThumbnail != null) { 1599 thumbnails.add(jfifThumbnail); 1600 } 1601 1602 // JFXX thumbnail(s) 1603 if(app0JFXXPresent && jfxxThumbnails != null) { 1604 int numJFXX = jfxxThumbnails.size(); 1605 for(int i = 0; i < numJFXX; i++) { 1606 IIOImage img = (IIOImage)jfxxThumbnails.get(i); 1607 BufferedImage jfxxThumbnail = 1608 (BufferedImage)img.getRenderedImage(); 1609 thumbnails.add(jfxxThumbnail); 1610 } 1611 } 1612 1613 // EXIF thumbnail 1614 if(exifData != null) { 1615 try { 1616 Iterator tiffReaders = 1617 ImageIO.getImageReadersByFormatName("TIFF"); 1618 if(tiffReaders != null && tiffReaders.hasNext()) { 1619 ImageReader tiffReader = 1620 (ImageReader)tiffReaders.next(); 1621 ByteArrayInputStream bais = 1622 new ByteArrayInputStream(exifData); 1623 ImageInputStream exifStream = 1624 new MemoryCacheImageInputStream(bais); 1625 tiffReader.setInput(exifStream); 1626 if(tiffReader.getNumImages(true) > 1) { 1627 BufferedImage exifThumbnail = 1628 tiffReader.read(1, null); 1629 thumbnails.add(exifThumbnail); 1630 } 1631 tiffReader.reset(); 1632 } 1633 } catch(IOException ioe) { 1634 // Ignore it. 1635 } 1636 } 1637 1638 thumbnailsInitialized = true; 1639 } // if(!thumbnailsInitialized) 1640 } // sychronized 1641 } 1642 1643 int getNumThumbnails() throws IOException { 1644 initializeThumbnails(); 1645 return thumbnails.size(); 1646 } 1647 1648 BufferedImage getThumbnail(int thumbnailIndex) throws IOException { 1649 if(thumbnailIndex < 0) { 1650 throw new IndexOutOfBoundsException("thumbnailIndex < 0!"); 1651 } 1652 1653 initializeThumbnails(); 1654 1655 if(thumbnailIndex >= thumbnails.size()) { 1656 throw new IndexOutOfBoundsException 1657 ("thumbnailIndex > getNumThumbnails()"); 1658 } 1659 1660 return (BufferedImage)thumbnails.get(thumbnailIndex); 1661 } 1662}