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