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