001/* 002 * $RCSfile: TIFFIFD.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.7 $ 042 * $Date: 2006/09/27 23:56:30 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.tiff; 046 047import java.io.EOFException; 048import java.io.IOException; 049import java.util.ArrayList; 050import java.util.Arrays; 051import java.util.Iterator; 052import java.util.List; 053import java.util.Map; 054import java.util.Set; 055import java.util.TreeMap; 056import java.util.Vector; 057 058import javax.imageio.stream.ImageInputStream; 059import javax.imageio.stream.ImageOutputStream; 060 061import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet; 062import com.github.jaiimageio.plugins.tiff.TIFFDirectory; 063import com.github.jaiimageio.plugins.tiff.TIFFField; 064import com.github.jaiimageio.plugins.tiff.TIFFTag; 065import com.github.jaiimageio.plugins.tiff.TIFFTagSet; 066 067public class TIFFIFD extends TIFFDirectory { 068 069 private long stripOrTileByteCountsPosition = -1; 070 private long stripOrTileOffsetsPosition = -1; 071 private long lastPosition = -1; 072 073 public static TIFFTag getTag(int tagNumber, List tagSets) { 074 Iterator iter = tagSets.iterator(); 075 while (iter.hasNext()) { 076 TIFFTagSet tagSet = (TIFFTagSet)iter.next(); 077 TIFFTag tag = tagSet.getTag(tagNumber); 078 if (tag != null) { 079 return tag; 080 } 081 } 082 083 return null; 084 } 085 086 public static TIFFTag getTag(String tagName, List tagSets) { 087 Iterator iter = tagSets.iterator(); 088 while (iter.hasNext()) { 089 TIFFTagSet tagSet = (TIFFTagSet)iter.next(); 090 TIFFTag tag = tagSet.getTag(tagName); 091 if (tag != null) { 092 return tag; 093 } 094 } 095 096 return null; 097 } 098 099 private static void writeTIFFFieldToStream(TIFFField field, 100 ImageOutputStream stream) 101 throws IOException { 102 int count = field.getCount(); 103 Object data = field.getData(); 104 105 switch (field.getType()) { 106 case TIFFTag.TIFF_ASCII: 107 for (int i = 0; i < count; i++) { 108 String s = ((String[])data)[i]; 109 int length = s.length(); 110 for (int j = 0; j < length; j++) { 111 stream.writeByte(s.charAt(j) & 0xff); 112 } 113 stream.writeByte(0); 114 } 115 break; 116 case TIFFTag.TIFF_UNDEFINED: 117 case TIFFTag.TIFF_BYTE: 118 case TIFFTag.TIFF_SBYTE: 119 stream.write((byte[])data); 120 break; 121 case TIFFTag.TIFF_SHORT: 122 stream.writeChars((char[])data, 0, ((char[])data).length); 123 break; 124 case TIFFTag.TIFF_SSHORT: 125 stream.writeShorts((short[])data, 0, ((short[])data).length); 126 break; 127 case TIFFTag.TIFF_SLONG: 128 stream.writeInts((int[])data, 0, ((int[])data).length); 129 break; 130 case TIFFTag.TIFF_LONG: 131 for (int i = 0; i < count; i++) { 132 stream.writeInt((int)(((long[])data)[i])); 133 } 134 break; 135 case TIFFTag.TIFF_IFD_POINTER: 136 stream.writeInt(0); // will need to be backpatched 137 break; 138 case TIFFTag.TIFF_FLOAT: 139 stream.writeFloats((float[])data, 0, ((float[])data).length); 140 break; 141 case TIFFTag.TIFF_DOUBLE: 142 stream.writeDoubles((double[])data, 0, ((double[])data).length); 143 break; 144 case TIFFTag.TIFF_SRATIONAL: 145 for (int i = 0; i < count; i++) { 146 stream.writeInt(((int[][])data)[i][0]); 147 stream.writeInt(((int[][])data)[i][1]); 148 } 149 break; 150 case TIFFTag.TIFF_RATIONAL: 151 for (int i = 0; i < count; i++) { 152 long num = ((long[][])data)[i][0]; 153 long den = ((long[][])data)[i][1]; 154 stream.writeInt((int)num); 155 stream.writeInt((int)den); 156 } 157 break; 158 default: 159 // error 160 } 161 } 162 163 public TIFFIFD(List tagSets, TIFFTag parentTag) { 164 super((TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]), 165 parentTag); 166 } 167 168 public TIFFIFD(List tagSets) { 169 this(tagSets, null); 170 } 171 172 public List getTagSetList() { 173 return Arrays.asList(getTagSets()); 174 } 175 176 /** 177 * Returns an <code>Iterator</code> over the TIFF fields. The 178 * traversal is in the order of increasing tag number. 179 */ 180 // Note: the sort is guaranteed for low fields by the use of an 181 // array wherein the index corresponds to the tag number and for 182 // the high fields by the use of a TreeMap with tag number keys. 183 public Iterator iterator() { 184 return Arrays.asList(getTIFFFields()).iterator(); 185 } 186 187 // Stream position initially at beginning, left at end 188 // if ignoreUnknownFields is true, do not load fields for which 189 // a tag cannot be found in an allowed TagSet. 190 public void initialize(ImageInputStream stream, 191 boolean ignoreUnknownFields) throws IOException { 192 removeTIFFFields(); 193 194 List tagSetList = getTagSetList(); 195 196 int numEntries = stream.readUnsignedShort(); 197 for (int i = 0; i < numEntries; i++) { 198 // Read tag number, value type, and value count. 199 int tag = stream.readUnsignedShort(); 200 int type = stream.readUnsignedShort(); 201 int count = (int)stream.readUnsignedInt(); 202 203 // Get the associated TIFFTag. 204 TIFFTag tiffTag = getTag(tag, tagSetList); 205 206 // Ignore unknown fields. 207 if(ignoreUnknownFields && tiffTag == null) { 208 // Skip the value/offset so as to leave the stream 209 // position at the start of the next IFD entry. 210 stream.skipBytes(4); 211 212 // XXX Warning message ... 213 214 // Continue with the next IFD entry. 215 continue; 216 } 217 218 long nextTagOffset = stream.getStreamPosition() + 4; 219 220 int sizeOfType = TIFFTag.getSizeOfType(type); 221 if (count*sizeOfType > 4) { 222 long value = stream.readUnsignedInt(); 223 stream.seek(value); 224 } 225 226 if (tag == BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS || 227 tag == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS || 228 tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) { 229 this.stripOrTileByteCountsPosition = 230 stream.getStreamPosition(); 231 } else if (tag == BaselineTIFFTagSet.TAG_STRIP_OFFSETS || 232 tag == BaselineTIFFTagSet.TAG_TILE_OFFSETS || 233 tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) { 234 this.stripOrTileOffsetsPosition = 235 stream.getStreamPosition(); 236 } 237 238 Object obj = null; 239 240 try { 241 switch (type) { 242 case TIFFTag.TIFF_BYTE: 243 case TIFFTag.TIFF_SBYTE: 244 case TIFFTag.TIFF_UNDEFINED: 245 case TIFFTag.TIFF_ASCII: 246 byte[] bvalues = new byte[count]; 247 stream.readFully(bvalues, 0, count); 248 249 if (type == TIFFTag.TIFF_ASCII) { 250 // Can be multiple strings 251 Vector v = new Vector(); 252 boolean inString = false; 253 int prevIndex = 0; 254 for (int index = 0; index <= count; index++) { 255 if (index < count && bvalues[index] != 0) { 256 if (!inString) { 257 // start of string 258 prevIndex = index; 259 inString = true; 260 } 261 } else { // null or special case at end of string 262 if (inString) { 263 // end of string 264 String s = new String(bvalues, prevIndex, 265 index - prevIndex); 266 v.add(s); 267 inString = false; 268 } 269 } 270 } 271 272 count = v.size(); 273 String[] strings; 274 if(count != 0) { 275 strings = new String[count]; 276 for (int c = 0 ; c < count; c++) { 277 strings[c] = (String)v.elementAt(c); 278 } 279 } else { 280 // This case has been observed when the value of 281 // 'count' recorded in the field is non-zero but 282 // the value portion contains all nulls. 283 count = 1; 284 strings = new String[] {""}; 285 } 286 287 obj = strings; 288 } else { 289 obj = bvalues; 290 } 291 break; 292 293 case TIFFTag.TIFF_SHORT: 294 char[] cvalues = new char[count]; 295 for (int j = 0; j < count; j++) { 296 cvalues[j] = (char)(stream.readUnsignedShort()); 297 } 298 obj = cvalues; 299 break; 300 301 case TIFFTag.TIFF_LONG: 302 case TIFFTag.TIFF_IFD_POINTER: 303 long[] lvalues = new long[count]; 304 for (int j = 0; j < count; j++) { 305 lvalues[j] = stream.readUnsignedInt(); 306 } 307 obj = lvalues; 308 break; 309 310 case TIFFTag.TIFF_RATIONAL: 311 long[][] llvalues = new long[count][2]; 312 for (int j = 0; j < count; j++) { 313 llvalues[j][0] = stream.readUnsignedInt(); 314 llvalues[j][1] = stream.readUnsignedInt(); 315 } 316 obj = llvalues; 317 break; 318 319 case TIFFTag.TIFF_SSHORT: 320 short[] svalues = new short[count]; 321 for (int j = 0; j < count; j++) { 322 svalues[j] = stream.readShort(); 323 } 324 obj = svalues; 325 break; 326 327 case TIFFTag.TIFF_SLONG: 328 int[] ivalues = new int[count]; 329 for (int j = 0; j < count; j++) { 330 ivalues[j] = stream.readInt(); 331 } 332 obj = ivalues; 333 break; 334 335 case TIFFTag.TIFF_SRATIONAL: 336 int[][] iivalues = new int[count][2]; 337 for (int j = 0; j < count; j++) { 338 iivalues[j][0] = stream.readInt(); 339 iivalues[j][1] = stream.readInt(); 340 } 341 obj = iivalues; 342 break; 343 344 case TIFFTag.TIFF_FLOAT: 345 float[] fvalues = new float[count]; 346 for (int j = 0; j < count; j++) { 347 fvalues[j] = stream.readFloat(); 348 } 349 obj = fvalues; 350 break; 351 352 case TIFFTag.TIFF_DOUBLE: 353 double[] dvalues = new double[count]; 354 for (int j = 0; j < count; j++) { 355 dvalues[j] = stream.readDouble(); 356 } 357 obj = dvalues; 358 break; 359 360 default: 361 // XXX Warning 362 break; 363 } 364 } catch(EOFException eofe) { 365 // The TIFF 6.0 fields have tag numbers less than or equal 366 // to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright). 367 // If there is an error reading a baseline tag, then re-throw 368 // the exception and fail; otherwise continue with the next 369 // field. 370 if(BaselineTIFFTagSet.getInstance().getTag(tag) == null) { 371 throw eofe; 372 } 373 } 374 375 if (tiffTag == null) { 376 // XXX Warning: unknown tag 377 } else if (!tiffTag.isDataTypeOK(type)) { 378 // XXX Warning: bad data type 379 } else if (tiffTag.isIFDPointer() && obj != null) { 380 stream.mark(); 381 stream.seek(((long[])obj)[0]); 382 383 List tagSets = new ArrayList(1); 384 tagSets.add(tiffTag.getTagSet()); 385 TIFFIFD subIFD = new TIFFIFD(tagSets); 386 387 // XXX Use same ignore policy for sub-IFD fields? 388 subIFD.initialize(stream, ignoreUnknownFields); 389 obj = subIFD; 390 stream.reset(); 391 } 392 393 if (tiffTag == null) { 394 tiffTag = new TIFFTag(null, tag, 1 << type, null); 395 } 396 397 // Add the field if its contents have been initialized which 398 // will not be the case if an EOF was ignored above. 399 if(obj != null) { 400 TIFFField f = new TIFFField(tiffTag, type, count, obj); 401 addTIFFField(f); 402 } 403 404 stream.seek(nextTagOffset); 405 } 406 407 this.lastPosition = stream.getStreamPosition(); 408 } 409 410 public void writeToStream(ImageOutputStream stream) 411 throws IOException { 412 413 int numFields = getNumTIFFFields(); 414 stream.writeShort(numFields); 415 416 long nextSpace = stream.getStreamPosition() + 12*numFields + 4; 417 418 Iterator iter = iterator(); 419 while (iter.hasNext()) { 420 TIFFField f = (TIFFField)iter.next(); 421 422 TIFFTag tag = f.getTag(); 423 424 int type = f.getType(); 425 int count = f.getCount(); 426 427 // Hack to deal with unknown tags 428 if (type == 0) { 429 type = TIFFTag.TIFF_UNDEFINED; 430 } 431 int size = count*TIFFTag.getSizeOfType(type); 432 433 if (type == TIFFTag.TIFF_ASCII) { 434 int chars = 0; 435 for (int i = 0; i < count; i++) { 436 chars += f.getAsString(i).length() + 1; 437 } 438 count = chars; 439 size = count; 440 } 441 442 int tagNumber = f.getTagNumber(); 443 stream.writeShort(tagNumber); 444 stream.writeShort(type); 445 stream.writeInt(count); 446 447 // Write a dummy value to fill space 448 stream.writeInt(0); 449 stream.mark(); // Mark beginning of next field 450 stream.skipBytes(-4); 451 452 long pos; 453 454 if (size > 4 || tag.isIFDPointer()) { 455 // Ensure IFD or value is written on a word boundary 456 nextSpace = (nextSpace + 3) & ~0x3; 457 458 stream.writeInt((int)nextSpace); 459 stream.seek(nextSpace); 460 pos = nextSpace; 461 462 if (tag.isIFDPointer()) { 463 TIFFIFD subIFD = (TIFFIFD)f.getData(); 464 subIFD.writeToStream(stream); 465 nextSpace = subIFD.lastPosition; 466 } else { 467 writeTIFFFieldToStream(f, stream); 468 nextSpace = stream.getStreamPosition(); 469 } 470 } else { 471 pos = stream.getStreamPosition(); 472 writeTIFFFieldToStream(f, stream); 473 } 474 475 // If we are writing the data for the 476 // StripByteCounts, TileByteCounts, StripOffsets, 477 // TileOffsets, JPEGInterchangeFormat, or 478 // JPEGInterchangeFormatLength fields, record the current stream 479 // position for backpatching 480 if (tagNumber == 481 BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS || 482 tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS || 483 tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) { 484 this.stripOrTileByteCountsPosition = pos; 485 } else if (tagNumber == 486 BaselineTIFFTagSet.TAG_STRIP_OFFSETS || 487 tagNumber == 488 BaselineTIFFTagSet.TAG_TILE_OFFSETS || 489 tagNumber == 490 BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) { 491 this.stripOrTileOffsetsPosition = pos; 492 } 493 494 stream.reset(); // Go to marked position of next field 495 } 496 497 this.lastPosition = nextSpace; 498 } 499 500 public long getStripOrTileByteCountsPosition() { 501 return stripOrTileByteCountsPosition; 502 } 503 504 public long getStripOrTileOffsetsPosition() { 505 return stripOrTileOffsetsPosition; 506 } 507 508 public long getLastPosition() { 509 return lastPosition; 510 } 511 512 void setPositions(long stripOrTileOffsetsPosition, 513 long stripOrTileByteCountsPosition, 514 long lastPosition) { 515 this.stripOrTileOffsetsPosition = stripOrTileOffsetsPosition; 516 this.stripOrTileByteCountsPosition = stripOrTileByteCountsPosition; 517 this.lastPosition = lastPosition; 518 } 519 520 /** 521 * Returns a <code>TIFFIFD</code> wherein all fields from the 522 * <code>BaselineTIFFTagSet</code> are copied by value and all other 523 * fields copied by reference. 524 */ 525 public TIFFIFD getShallowClone() { 526 // Get the baseline TagSet. 527 TIFFTagSet baselineTagSet = BaselineTIFFTagSet.getInstance(); 528 529 // If the baseline TagSet is not included just return. 530 List tagSetList = getTagSetList(); 531 if(!tagSetList.contains(baselineTagSet)) { 532 return this; 533 } 534 535 // Create a new object. 536 TIFFIFD shallowClone = new TIFFIFD(tagSetList, getParentTag()); 537 538 // Get the tag numbers in the baseline set. 539 Set baselineTagNumbers = baselineTagSet.getTagNumbers(); 540 541 // Iterate over the fields in this IFD. 542 Iterator fields = iterator(); 543 while(fields.hasNext()) { 544 // Get the next field. 545 TIFFField field = (TIFFField)fields.next(); 546 547 // Get its tag number. 548 Integer tagNumber = new Integer(field.getTagNumber()); 549 550 // Branch based on membership in baseline set. 551 TIFFField fieldClone; 552 if(baselineTagNumbers.contains(tagNumber)) { 553 // Copy by value. 554 Object fieldData = field.getData(); 555 556 int fieldType = field.getType(); 557 558 try { 559 switch (fieldType) { 560 case TIFFTag.TIFF_BYTE: 561 case TIFFTag.TIFF_SBYTE: 562 case TIFFTag.TIFF_UNDEFINED: 563 fieldData = ((byte[])fieldData).clone(); 564 break; 565 case TIFFTag.TIFF_ASCII: 566 fieldData = ((String[])fieldData).clone(); 567 break; 568 case TIFFTag.TIFF_SHORT: 569 fieldData = ((char[])fieldData).clone(); 570 break; 571 case TIFFTag.TIFF_LONG: 572 case TIFFTag.TIFF_IFD_POINTER: 573 fieldData = ((long[])fieldData).clone(); 574 break; 575 case TIFFTag.TIFF_RATIONAL: 576 fieldData = ((long[][])fieldData).clone(); 577 break; 578 case TIFFTag.TIFF_SSHORT: 579 fieldData = ((short[])fieldData).clone(); 580 break; 581 case TIFFTag.TIFF_SLONG: 582 fieldData = ((int[])fieldData).clone(); 583 break; 584 case TIFFTag.TIFF_SRATIONAL: 585 fieldData = ((int[][])fieldData).clone(); 586 break; 587 case TIFFTag.TIFF_FLOAT: 588 fieldData = ((float[])fieldData).clone(); 589 break; 590 case TIFFTag.TIFF_DOUBLE: 591 fieldData = ((double[])fieldData).clone(); 592 break; 593 default: 594 // Shouldn't happen but do nothing ... 595 } 596 } catch(Exception e) { 597 // Ignore it and copy by reference ... 598 } 599 600 fieldClone = new TIFFField(field.getTag(), fieldType, 601 field.getCount(), fieldData); 602 } else { 603 // Copy by reference. 604 fieldClone = field; 605 } 606 607 // Add the field to the clone. 608 shallowClone.addTIFFField(fieldClone); 609 } 610 611 // Set positions. 612 shallowClone.setPositions(stripOrTileOffsetsPosition, 613 stripOrTileByteCountsPosition, 614 lastPosition); 615 616 return shallowClone; 617 } 618}