1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.image.libs; 4 5 import java.awt.*; 6 import java.awt.color.ICC_Profile; 7 import java.awt.image.*; 8 import java.io.*; 9 import java.nio.ByteBuffer; 10 import java.nio.ByteOrder; 11 import java.util.Iterator; 12 import java.util.Map; 13 14 import org.w3c.dom.Document; 15 16 import com.lightcrafts.image.metadata.*; 17 import com.lightcrafts.image.metadata.values.ImageMetaValue; 18 import com.lightcrafts.image.types.TIFFImageType; 19 import com.lightcrafts.mediax.jai.PlanarImage; 20 import com.lightcrafts.utils.file.OrderableRandomAccessFile; 21 import com.lightcrafts.utils.ProgressIndicator; 22 import com.lightcrafts.utils.thread.ProgressThread; 23 import com.lightcrafts.image.export.ResolutionOption; 24 import com.lightcrafts.image.export.ResolutionUnitOption; 25 import com.lightcrafts.image.types.TIFFConstants; 26 import com.lightcrafts.utils.Version; 27 import com.lightcrafts.utils.xml.XMLUtil; 28 29 import static com.lightcrafts.image.metadata.EXIFConstants.*; 30 import static com.lightcrafts.image.metadata.EXIFTags.*; 31 import static com.lightcrafts.image.metadata.TIFFTags.*; 32 import static com.lightcrafts.image.types.TIFFConstants.*; 33 34 /** 35 * An <code>LCTIFFWriter</code> is a Java wrapper around the LibTIFF library. 36 * 37 * @author Paul J. Lucas [paul@lightcrafts.com] 38 * @see <a href="http://www.remotesensing.org/libtiff/">LibTIFF</a> 39 */ 40 public final class LCTIFFWriter extends LCTIFFCommon { 41 42 ////////// public ///////////////////////////////////////////////////////// 43 44 /** 45 * Construct an <code>LCTIFFWriter</code> and open a TIFF file. 46 * 47 * @param fileName The name of the TIFF file to open. 48 * @param width The width of the image in pixels. 49 * @param height The height of the image in pixels. 50 */ LCTIFFWriter( String fileName, int width, int height )51 public LCTIFFWriter( String fileName, int width, int height ) 52 throws LCImageLibException, UnsupportedEncodingException 53 { 54 this( fileName, null, width, height ); 55 } 56 57 /** 58 * Construct an <code>LCTIFFWriter</code> and open a TIFF file. 59 * 60 * @param fileName The name of the TIFF file to open. 61 * @param width The width of the image in pixels. 62 * @param height The height of the image in pixels. 63 * @param resolution The resolution (in pixels per unit). 64 * @param resolutionUnit The resolution unit; must be either 65 * {@link TIFFConstants#TIFF_RESOLUTION_UNIT_CM} or 66 * {@link TIFFConstants#TIFF_RESOLUTION_UNIT_INCH}. 67 */ LCTIFFWriter( String fileName, int width, int height, int resolution, int resolutionUnit )68 public LCTIFFWriter( String fileName, int width, int height, 69 int resolution, int resolutionUnit ) 70 throws LCImageLibException, UnsupportedEncodingException 71 { 72 this( fileName, null, width, height, resolution, resolutionUnit ); 73 } 74 75 /** 76 * Construct an <code>LCTIFFWriter</code> and open a TIFF file. 77 * 78 * @param fileName The name of the TIFF file to open. 79 * @param appendFileName The name of the TIFF file to append as the second 80 * page in a 2-page TIFF file. 81 * @param width The width of the image in pixels. 82 * @param height The height of the image in pixels. 83 */ LCTIFFWriter( String fileName, String appendFileName, int width, int height )84 public LCTIFFWriter( String fileName, String appendFileName, int width, 85 int height ) 86 throws LCImageLibException, UnsupportedEncodingException 87 { 88 this( 89 fileName, appendFileName, width, height, 90 ResolutionOption.DEFAULT_VALUE, ResolutionUnitOption.DEFAULT_VALUE 91 ); 92 } 93 94 /** 95 * Construct an <code>LCTIFFWriter</code> and open a TIFF file. 96 * 97 * @param fileName The name of the TIFF file to open. 98 * @param appendFileName The name of the TIFF file to append as the second 99 * page in a 2-page TIFF file. 100 * @param width The width of the image in pixels. 101 * @param height The height of the image in pixels. 102 * @param resolution The resolution (in pixels per unit). 103 * @param resolutionUnit The resolution unit; must be either 104 * {@link TIFFConstants#TIFF_RESOLUTION_UNIT_CM} or 105 * {@link TIFFConstants#TIFF_RESOLUTION_UNIT_INCH}. 106 */ LCTIFFWriter( String fileName, String appendFileName, int width, int height, int resolution, int resolutionUnit )107 public LCTIFFWriter( String fileName, String appendFileName, int width, 108 int height, int resolution, int resolutionUnit ) 109 throws LCImageLibException, UnsupportedEncodingException 110 { 111 m_fileName = fileName; 112 m_appendFileName = appendFileName; 113 m_exportWidth = width; 114 m_exportHeight = height; 115 m_resolution = resolution; 116 m_resolutionUnit = resolutionUnit; 117 openForWriting( fileName ); 118 // 119 // If openForWriting() fails, it will store 0 in the native pointer. 120 // 121 if ( m_nativePtr == 0 ) 122 throw new LCImageLibException( "Could not open " + fileName ); 123 } 124 125 /** 126 * Puts a TIFF image as tiles. 127 * 128 * @param image The image to put. 129 * @param thread The thread that's doing the putting. 130 */ putImageTiled( RenderedImage image, ProgressThread thread )131 public void putImageTiled( RenderedImage image, ProgressThread thread ) 132 throws IOException, LCImageLibException 133 { 134 try { 135 writeImageTiled( image, thread ); 136 if ( m_appendFileName != null ) { 137 append( m_appendFileName ); 138 } 139 dispose(); 140 if ( m_hasExifMetadata ) 141 fixEXIFMetadata( m_fileName ); 142 } 143 finally { 144 dispose(); 145 } 146 } 147 148 /** 149 * Puts a TIFF image as strips. 150 * 151 * @param image The image to put. 152 * @param thread The thread that's doing the putting. 153 */ putImageStriped( RenderedImage image, ProgressThread thread )154 public void putImageStriped( RenderedImage image, ProgressThread thread ) 155 throws IOException, LCImageLibException 156 { 157 try { 158 writeImageStriped( image, thread ); 159 if ( m_appendFileName != null ) { 160 append( m_appendFileName ); 161 } 162 dispose(); 163 if ( m_hasExifMetadata ) 164 fixEXIFMetadata( m_fileName ); 165 } 166 finally { 167 dispose(); 168 } 169 } 170 171 /** 172 * Puts the given {@link ImageMetadata} into the TIFF file. 173 * This <i>must</i> be called only once and prior 174 * to {@link #putImageStriped(RenderedImage,ProgressThread)}. 175 * 176 * @param metadata The {@link ImageMetadata} to put. 177 */ putMetadata( ImageMetadata metadata )178 public void putMetadata( ImageMetadata metadata ) 179 throws IOException, LCImageLibException 180 { 181 metadata = metadata.prepForExport( 182 TIFFImageType.INSTANCE, m_exportWidth, m_exportHeight, 183 m_resolution, m_resolutionUnit, false 184 ); 185 186 ////////// Put TIFF metadata ////////////////////////////////////////// 187 188 final ImageMetadataDirectory tiffDir = 189 metadata.getDirectoryFor( TIFFDirectory.class ); 190 if ( tiffDir != null ) { 191 for ( Iterator<Map.Entry<Integer,ImageMetaValue>> 192 i = tiffDir.iterator(); i.hasNext(); ) { 193 final Map.Entry<Integer,ImageMetaValue> me = i.next(); 194 final int tagID = me.getKey(); 195 final ImageMetaValue value = me.getValue(); 196 switch ( tagID ) { 197 case TIFF_ARTIST: 198 case TIFF_COPYRIGHT: 199 case TIFF_DATE_TIME: 200 case TIFF_DOCUMENT_NAME: 201 case TIFF_HOST_COMPUTER: 202 case TIFF_IMAGE_DESCRIPTION: 203 case TIFF_INK_NAMES: 204 case TIFF_MAKE: 205 case TIFF_MODEL: 206 case TIFF_PAGE_NAME: 207 case TIFF_SOFTWARE: 208 case TIFF_TARGET_PRINTER: 209 setStringField( tagID, value.getStringValue() ); 210 break; 211 case TIFF_MS_RATING: 212 case TIFF_RESOLUTION_UNIT: 213 setIntField( tagID, value.getIntValue() ); 214 break; 215 case TIFF_X_RESOLUTION: 216 case TIFF_Y_RESOLUTION: 217 setFloatField( tagID, value.getFloatValue() ); 218 break; 219 } 220 } 221 } 222 223 ////////// Put EXIF metadata ////////////////////////////////////////// 224 225 final ImageMetadataDirectory exifDir = 226 metadata.getDirectoryFor( EXIFDirectory.class ); 227 if ( exifDir != null ) { 228 final ByteBuffer exifBuf = EXIFEncoder.encode( metadata, false ); 229 //ByteBufferUtil.dumpToFile( exifBuf, "/tmp/tiff.exif"); 230 // 231 // Libtiff doesn't support writing EXIF metadata so we have to do 232 // an annoying work-around. We temporarily store the encoded EXIF 233 // metadata as belonging to the PHOTOSHOP tag. Later, after the 234 // TIFF file has been completely written, we go back and patch the 235 // file in-place by changing the tag ID to EXIF_IFD_POINTER and 236 // adjusting the EXIF metadata offsets. 237 // 238 // The reason the PHOTOSHOP tag is used is because: (1) we don't 239 // use it for anything else, (2) its field type is unsigned byte 240 // so we can set its value to the encoded binary EXIF metadata, and 241 // (3) its tag ID (0x8649) is fairly close to that of the real tag 242 // ID of EXIF_IFD_POINTER (0x8769). Point #3 is important because 243 // the tag IDs in a TIFF file must be in ascending sorted order so 244 // even after the tag ID is changed, the set of tags is still in 245 // ascending sorted order. 246 // 247 setByteField( TIFF_PHOTOSHOP_IMAGE_RESOURCES, exifBuf.array() ); 248 m_hasExifMetadata = true; 249 } 250 251 ////////// Put IPTC metadata ////////////////////////////////////////// 252 253 final ImageMetadataDirectory iptcDir = 254 metadata.getDirectoryFor( IPTCDirectory.class ); 255 if ( iptcDir != null ) { 256 // 257 // Write both the binary and XMP forms of IPTC metadata: the binary 258 // form to enable non-XMP-aware applications to read it and the 259 // XMP form to write all the metadata, i.e., the additional IPTC 260 // tags present in XMP. 261 // 262 final byte[] iptcBuf = ((IPTCDirectory)iptcDir).encode( false ); 263 if ( iptcBuf != null ) 264 setByteField( TIFF_RICH_TIFF_IPTC, iptcBuf ); 265 266 final Document xmpDoc = metadata.toXMP( false, true, IPTCDirectory.class ); 267 final byte[] xmpBuf = XMLUtil.encodeDocument( xmpDoc, false ); 268 setByteField( TIFF_XMP_PACKET, xmpBuf ); 269 } 270 } 271 272 /** 273 * Sets the value of the given TIFF byte field. 274 * 275 * @param tagID The tag ID of the metadata field to set. The ID should be 276 * that of a tag whose value is a byte array 277 * ({@link TIFFConstants#TIFF_FIELD_TYPE_UBYTE}. 278 * @param value The value for the given tag. 279 * @return Returns <code>true</code> only if the value was set. 280 * @throws IllegalArgumentException if <code>tagID</code> isn't that of an 281 * byte metadata field or is otherwise unsupported. 282 * @see #setFloatField(int,float) 283 * @see #setIntField(int,int) 284 * @see #setStringField(int,String) 285 */ setByteField( int tagID, byte[] value )286 public native boolean setByteField( int tagID, byte[] value ) 287 throws LCImageLibException; 288 289 /** 290 * Sets the value of the given TIFF integer metadata field. 291 * 292 * @param tagID The tag ID of the metadata field to set. The ID should be 293 * that of a tag whose value is an integer 294 * ({@link TIFFConstants#TIFF_FIELD_TYPE_USHORT} or 295 * {@link TIFFConstants#TIFF_FIELD_TYPE_ULONG} and not a string. 296 * @param value The value for the given tag. 297 * @return Returns <code>true</code> only if the value was set. 298 * @throws IllegalArgumentException if <code>tagID</code> isn't that of an 299 * integer metadata field or is otherwise unsupported. 300 * @see #setByteField(int,byte[]) 301 * @see #setIntField(int,int) 302 * @see #setStringField(int,String) 303 */ setFloatField( int tagID, float value )304 public native boolean setFloatField( int tagID, float value ) 305 throws LCImageLibException; 306 307 /** 308 * Sets the ICC profile of the TIFF image. This <i>must</i> be called only 309 * once and prior to {@link #putImageStriped(RenderedImage,ProgressThread)} 310 * or {@link #putImageTiled(RenderedImage,ProgressThread)}. 311 * 312 * @param iccProfile The {@link ICC_Profile} to set. 313 */ setICCProfile( ICC_Profile iccProfile )314 public void setICCProfile( ICC_Profile iccProfile ) 315 throws LCImageLibException 316 { 317 setByteField( TIFF_ICC_PROFILE, iccProfile.getData() ); 318 } 319 320 /** 321 * Sets the value of the given TIFF integer metadata field. 322 * 323 * @param tagID The tag ID of the metadata field to set. The ID should be 324 * that of a tag whose value is an integer 325 * ({@link TIFFConstants#TIFF_FIELD_TYPE_USHORT} or 326 * {@link TIFFConstants#TIFF_FIELD_TYPE_ULONG} and not a string. 327 * @param value The value for the given tag. 328 * @return Returns <code>true</code> only if the value was set. 329 * @throws IllegalArgumentException if <code>tagID</code> isn't that of an 330 * integer metadata field or is otherwise unsupported. 331 * @see #setByteField(int,byte[]) 332 * @see #setFloatField(int,float) 333 * @see #setStringField(int,String) 334 */ setIntField( int tagID, int value )335 public native boolean setIntField( int tagID, int value ) 336 throws LCImageLibException; 337 338 /** 339 * Sets the value of the given TIFF string metadata field. 340 * 341 * @param tagID The tag ID of the metadata field to set. The ID should be 342 * that of a tag whose value is a string 343 * ({@link TIFFConstants#TIFF_FIELD_TYPE_ASCII}. 344 * @param value The value for the given tag. 345 * @return Returns <code>true</code> only if the value was set. 346 * @throws IllegalArgumentException if <code>tagID</code> isn't that of an 347 * string metadata field or is otherwise unsupported. 348 * @see #setByteField(int,byte[]) 349 * @see #setFloatField(int,float) 350 * @see #setIntField(int,int) 351 */ setStringField( int tagID, String value )352 public boolean setStringField( int tagID, String value ) 353 throws LCImageLibException, UnsupportedEncodingException 354 { 355 byte[] valueUtf8 = ( value + '\000' ).getBytes( "UTF-8" ); 356 return setStringField( tagID, valueUtf8 ); 357 } 358 setStringField( int tagID, byte[] valueUtf8 )359 public native boolean setStringField( int tagID, byte[] valueUtf8 ) 360 throws LCImageLibException; 361 362 ////////// private //////////////////////////////////////////////////////// 363 364 /** 365 * Append the TIFF image in the given file creating a multi-page TIFF file. 366 * 367 * @param fileName The name of the TIFF file to append. 368 * @return Returns <code>true</code> only if the append succeeded. 369 */ append( String fileName )370 private boolean append( String fileName ) 371 throws IOException 372 { 373 byte[] fileNameUtf8 = ( fileName + '\000' ).getBytes( "UTF-8" ); 374 return append( fileNameUtf8 ); 375 } 376 append( byte[] fileNameUtf8 )377 private native boolean append( byte[] fileNameUtf8 ); 378 379 /** 380 * Computes which tile a given point is in. 381 * 382 * @param x The X coordinate. 383 * @param y The Y coordinate. 384 * @param z The Z coordinate. 385 * @param sample TODO 386 * @return Returns the tile index. 387 */ computeTile( int x, int y, int z, int sample )388 private native int computeTile( int x, int y, int z, int sample ); 389 390 /** 391 * Fix the EXIF metadata in a TIFF file. 392 * 393 * @param fileName The full path of the TIFF file. 394 */ fixEXIFMetadata( String fileName )395 private static void fixEXIFMetadata( String fileName ) throws IOException { 396 // 397 // This code is based on the code in TIFFMetadataReader but it's been 398 // simplified and does much less error-checking because we just wrote 399 // the TIFF file ourselves so we know it's valid. 400 // 401 final OrderableRandomAccessFile file = 402 new OrderableRandomAccessFile( fileName, "rw" ); 403 try { 404 if ( file.readShort() == TIFF_LITTLE_ENDIAN ) 405 file.order( ByteOrder.LITTLE_ENDIAN ); 406 file.seek( TIFF_HEADER_SIZE - TIFF_INT_SIZE ); 407 int ifdOffset = file.readInt(); 408 while ( ifdOffset > 0 ) { 409 file.seek( ifdOffset ); 410 final int entryCount = file.readUnsignedShort(); 411 for ( int entry = 0; entry < entryCount; ++entry ) { 412 final int entryOffset = 413 TIFFMetadataReader.calcIFDEntryOffset( ifdOffset, entry ); 414 file.seek( entryOffset ); 415 final int tagID = file.readUnsignedShort(); 416 if ( tagID == TIFF_PHOTOSHOP_IMAGE_RESOURCES ) { 417 file.seek( file.getFilePointer() - TIFF_SHORT_SIZE ); 418 file.writeShort( TIFF_EXIF_IFD_POINTER ); 419 file.writeShort( TIFF_FIELD_TYPE_ULONG ); 420 file.writeInt( 1 ); 421 final int subdirOffset = file.readInt(); 422 fixEXIFDirectory( file, subdirOffset, 0 ); 423 return; 424 } 425 } 426 ifdOffset = file.readInt(); 427 } 428 } 429 finally { 430 try { 431 file.close(); 432 } 433 catch (Exception e) { 434 // do nothing 435 } 436 } 437 } 438 439 /** 440 * Fix an EXIF directory in a TIFF file. Specifically, this means to 441 * adjust all value offsets so that they are relative to the beginning of 442 * the TIFF file (as is required by the TIFF specification) rather than 443 * relative to the start of the EXIF directory (as is the case when in a 444 * JPEG file). 445 * 446 * @param file The TIFF file containing an EXIF directory to fix. 447 * @param dirOffset The offset to the start of the EXIF directory. 448 * @param parentDirSize The size of the parent EXIF directory, if any. 449 */ fixEXIFDirectory( OrderableRandomAccessFile file, long dirOffset, int parentDirSize )450 private static void fixEXIFDirectory( OrderableRandomAccessFile file, 451 long dirOffset, int parentDirSize ) 452 throws IOException 453 { 454 file.seek( dirOffset ); 455 final int entryCount = file.readUnsignedShort(); 456 for ( int entry = 0; entry < entryCount; ++entry ) { 457 final int entryOffset = 458 TIFFMetadataReader.calcIFDEntryOffset( (int)dirOffset, entry ); 459 file.seek( entryOffset ); 460 final int tagID = file.readUnsignedShort(); 461 final int fieldType = file.readUnsignedShort(); 462 final int numValues = file.readInt(); 463 final int byteCount = numValues * EXIF_FIELD_SIZE[ fieldType ]; 464 if ( byteCount > TIFF_INLINE_VALUE_MAX_SIZE || 465 tagID == EXIF_IFD_POINTER ) { 466 int valueOffset = file.readInt(); 467 valueOffset += (int)dirOffset + parentDirSize; 468 file.seek( file.getFilePointer() - TIFF_INT_SIZE ); 469 file.writeInt( valueOffset ); 470 switch ( tagID ) { 471 case EXIF_IFD_POINTER: 472 final int exifIFDSize = 473 EXIF_SHORT_SIZE 474 + entryCount * EXIF_IFD_ENTRY_SIZE 475 + EXIF_INT_SIZE; 476 fixEXIFDirectory( file, valueOffset, -exifIFDSize ); 477 break; 478 } 479 } 480 } 481 } 482 483 /** 484 * Opens a TIFF file. 485 * 486 * @param fileName The name of the TIFF file to open. 487 */ openForWriting( String fileName )488 private void openForWriting( String fileName ) 489 throws LCImageLibException, UnsupportedEncodingException 490 { 491 byte[] fileNameUtf8 = ( fileName + '\000' ).getBytes( "UTF-8" ); 492 openForWriting( fileNameUtf8 ); 493 } 494 openForWriting( byte[] fileNameUtf8 )495 private native void openForWriting( byte[] fileNameUtf8 ) 496 throws LCImageLibException; 497 498 /** 499 * Writes a TIFF image as strips. 500 * 501 * @param image The image to put. 502 * @param thread The thread that's doing the writing. 503 */ writeImageStriped( RenderedImage image, ProgressThread thread )504 private void writeImageStriped( RenderedImage image, 505 ProgressThread thread ) 506 throws LCImageLibException 507 { 508 final int dataType = image.getSampleModel().getDataType(); 509 final int bands = image.getSampleModel().getNumBands(); 510 final int imageWidth = image.getWidth(); 511 final int imageHeight = image.getHeight(); 512 final int stripHeight = 32; 513 514 setIntField( 515 TIFF_BITS_PER_SAMPLE, dataType == DataBuffer.TYPE_BYTE ? 8 : 16 516 ); 517 setIntField( TIFF_IMAGE_WIDTH, imageWidth ); 518 setIntField( TIFF_IMAGE_LENGTH, imageHeight ); 519 setIntField( 520 TIFF_PHOTOMETRIC_INTERPRETATION, 521 bands == 4 ? TIFF_PHOTOMETRIC_SEPARATED : 522 bands == 3 ? TIFF_PHOTOMETRIC_RGB : 523 TIFF_PHOTOMETRIC_BLACK_IS_ZERO 524 ); 525 setIntField( 526 TIFF_PLANAR_CONFIGURATION, TIFF_PLANAR_CONFIGURATION_CHUNKY 527 ); 528 setIntField( TIFF_ROWS_PER_STRIP, stripHeight ); 529 setIntField( TIFF_SAMPLES_PER_PIXEL, bands ); 530 531 final ProgressIndicator indicator; 532 if ( thread != null ) { 533 indicator = thread.getProgressIndicator(); 534 if ( indicator != null ) 535 indicator.setMaximum( imageHeight ); 536 } else 537 indicator = null; 538 539 // Allocate the output buffer only once 540 final int type; 541 if (dataType == DataBuffer.TYPE_BYTE) { 542 type = DataBuffer.TYPE_BYTE; 543 } 544 else { 545 type = DataBuffer.TYPE_USHORT; 546 } 547 final WritableRaster outBuffer = 548 Raster.createInterleavedRaster( 549 type, imageWidth, stripHeight, bands * imageWidth, bands, 550 bands == 1 ? new int[]{ 0 } : 551 bands == 3 ? new int[]{ 0, 1, 2 } : 552 new int[]{ 0, 1, 2, 3 }, 553 new Point(0, 0) 554 ); 555 556 int stripIndex = 0; 557 for ( int y = 0; y < imageHeight; y += stripHeight ) { 558 if ( thread != null && thread.isCanceled() ) 559 return; 560 561 final int currentStripHeight = Math.min( stripHeight, imageHeight - y ); 562 563 // Create a child raster of the out buffer for the current strip 564 final WritableRaster raster = outBuffer.createWritableChild(0, 0, imageWidth, currentStripHeight, 0, y, null); 565 566 // Prefetch tiles, uses all CPUs 567 if (image instanceof PlanarImage) 568 ((PlanarImage) image).getTiles(((PlanarImage) image).getTileIndices(raster.getBounds())); 569 570 image.copyData(raster); 571 572 final ComponentSampleModel csm = (ComponentSampleModel)raster.getSampleModel(); 573 final int[] offsets = csm.getBandOffsets(); 574 int offset = offsets[0]; 575 for (int i = 1; i < offsets.length; i++) 576 offset = Math.min(offset, offsets[i]); 577 578 if (dataType == DataBuffer.TYPE_BYTE) { 579 final DataBufferByte db = (DataBufferByte)raster.getDataBuffer(); 580 581 final int written = writeStripByte( stripIndex, db.getData(), offset, bands * imageWidth * currentStripHeight ); 582 583 if ( written != bands * imageWidth * currentStripHeight ) 584 throw new LCImageLibException( 585 "something is wrong: " + written + " != " + 586 (bands * imageWidth * currentStripHeight) 587 ); 588 } else { 589 final DataBufferUShort db = (DataBufferUShort) raster.getDataBuffer(); 590 591 final int written = writeStripShort( stripIndex, db.getData(), offset, 2 * bands * imageWidth * currentStripHeight ); 592 593 if ( written != 2 * bands * imageWidth * currentStripHeight ) 594 throw new LCImageLibException( 595 "something is wrong: " + written + " != " + (2 * bands * imageWidth * currentStripHeight) 596 ); 597 } 598 stripIndex++; 599 if ( indicator != null ) 600 indicator.incrementBy( currentStripHeight ); 601 } 602 603 if ( indicator != null ) 604 indicator.setIndeterminate( true ); 605 } 606 607 /** 608 * Writes a TIFF image as tiles. 609 * 610 * @param image The image to put. 611 * @param thread 612 */ writeImageTiled( RenderedImage image, ProgressThread thread )613 private void writeImageTiled( RenderedImage image, 614 ProgressThread thread ) 615 throws LCImageLibException 616 { 617 final int dataType = image.getSampleModel().getDataType(); 618 619 setIntField(TIFF_IMAGE_WIDTH, image.getWidth()); 620 setIntField(TIFF_IMAGE_LENGTH, image.getHeight()); 621 setIntField(TIFF_BITS_PER_SAMPLE, dataType == DataBuffer.TYPE_BYTE ? 8 : 16); 622 setIntField(TIFF_SAMPLES_PER_PIXEL, image.getSampleModel().getNumBands()); 623 624 setIntField(TIFF_PLANAR_CONFIGURATION, TIFF_PLANAR_CONFIGURATION_CHUNKY); 625 setIntField(TIFF_PHOTOMETRIC_INTERPRETATION, TIFF_PHOTOMETRIC_RGB ); 626 627 setIntField(TIFF_TILE_WIDTH, image.getTileWidth()); 628 setIntField(TIFF_TILE_LENGTH, image.getTileHeight()); 629 630 final ProgressIndicator indicator; 631 if (thread != null) { 632 indicator = thread.getProgressIndicator(); 633 if ( indicator != null ) 634 indicator.setMaximum( image.getNumXTiles() * image.getNumYTiles() ); 635 } else 636 indicator = null; 637 638 for ( int tileX = 0; tileX < image.getNumXTiles(); tileX++ ) 639 for ( int tileY = 0; tileY < image.getNumYTiles(); tileY++ ) { 640 if ( thread != null && thread.isCanceled() ) 641 return; 642 final int tileIndex = computeTile(tileX * image.getTileWidth(), tileY * image.getTileHeight(), 0, 0); 643 final Raster tile = image.getTile(tileX, tileY); 644 645 if (dataType == DataBuffer.TYPE_BYTE) { 646 final byte[] buffer = ((DataBufferByte) tile.getDataBuffer()).getData(); 647 648 final int bytesWritten = writeTileByte( 649 tileIndex, buffer, 0, buffer.length 650 ); 651 if ( bytesWritten != buffer.length ) 652 throw new LCImageLibException( 653 "something is wrong: " + bytesWritten + " != " + buffer.length 654 ); 655 } else { 656 final short[] buffer = ((DataBufferUShort) tile.getDataBuffer()).getData(); 657 658 final int bytesWritten = writeTileShort( 659 tileIndex, buffer, 0, buffer.length * 2 660 ); 661 if ( bytesWritten != buffer.length * 2 ) 662 throw new LCImageLibException( 663 "something is wrong: " + bytesWritten + " != " + buffer.length * 2 664 ); 665 } 666 if ( indicator != null ) 667 indicator.incrementBy( 1 ); 668 } 669 if ( indicator != null ) 670 indicator.setIndeterminate( true ); 671 } 672 673 /** 674 * Encodes and writes a strip to the TIFF image. 675 * 676 * @param stripIndex The index of the strip to write. 677 * @param buf The buffer into which to write the image data. 678 * @param offset The offset into the buffer where the image data will begin 679 * being placed. 680 * @param stripSize The size of the strip. 681 * @return Returns the number of bytes written or -1 if there was an error. 682 */ writeStripByte( int stripIndex, byte[] buf, long offset, int stripSize )683 private native int writeStripByte( int stripIndex, byte[] buf, long offset, 684 int stripSize ) 685 throws LCImageLibException; 686 687 /** 688 * Encodes and writes a strip to the TIFF image. 689 * 690 * @param stripIndex The index of the strip to write. 691 * @param buf The buffer into which to write the image data. 692 * @param offset The offset into the buffer where the image data will begin 693 * being placed. 694 * @param stripSize The size of the strip. 695 * @return Returns the number of bytes written or -1 if there was an error. 696 */ writeStripShort( int stripIndex, short[] buf, long offset, int stripSize )697 private native int writeStripShort( int stripIndex, short[] buf, long offset, 698 int stripSize ) 699 throws LCImageLibException; 700 701 /** 702 * Encodes and writes a tile to the TIFF image. 703 * 704 * @param tileIndex The index of the tile to write. 705 * @param buf The buffer into which to write the image data. 706 * @param offset The offset into the buffer where the image data will begin 707 * being placed. 708 * @param tileSize The size of the tile. 709 * @return Returns the number of bytes written or -1 if there was an error. 710 */ writeTileByte( int tileIndex, byte[] buf, long offset, int tileSize )711 private native int writeTileByte( int tileIndex, byte[] buf, long offset, 712 int tileSize ) 713 throws LCImageLibException; 714 715 /** 716 * Encodes and writes a tile to the TIFF image. 717 * 718 * @param tileIndex The index of the tile to write. 719 * @param buf The buffer into which to write the image data. 720 * @param offset The offset into the buffer where the image data will begin 721 * being placed. 722 * @param tileSize The size of the tile. 723 * @return Returns the number of bytes written or -1 if there was an error. 724 */ writeTileShort( int tileIndex, short[] buf, long offset, int tileSize )725 private native int writeTileShort( int tileIndex, short[] buf, long offset, 726 int tileSize ) 727 throws LCImageLibException; 728 729 /** 730 * The name of the TIFF file to append, if any. 731 */ 732 private final String m_appendFileName; 733 734 /** 735 * The height of the image as exported. 736 */ 737 private final int m_exportHeight; 738 739 /** 740 * The width of the image as exported. 741 */ 742 private final int m_exportWidth; 743 744 /** 745 * The name of the TIFF file. 746 */ 747 private final String m_fileName; 748 749 /** 750 * Flag used to remember whether the image has EXIF metadata. 751 */ 752 private boolean m_hasExifMetadata; 753 754 /** 755 * The resolution (in pixels per unit) of the image as exported. 756 */ 757 private final int m_resolution; 758 759 /** 760 * The resolution unit of the image as exported. 761 */ 762 private final int m_resolutionUnit; 763 764 ////////// main() ///////////////////////////////////////////////////////// 765 main(String[] args)766 public static void main(String[] args) throws Exception { 767 try { 768 final LCTIFFReader tiff = new LCTIFFReader( args[0] ); 769 final PlanarImage image = tiff.getImage( null ); 770 771 final LCTIFFWriter writer = new LCTIFFWriter( 772 "/Users/pjl/Desktop/out.tiff", args[1], 773 image.getWidth(), image.getHeight() 774 ); 775 writer.setStringField( TIFF_SOFTWARE, Version.getApplicationName() ); 776 writer.putImageStriped( image, null ); 777 } 778 catch ( Exception e ) { 779 e.printStackTrace(); 780 } 781 } 782 } 783 /* vim:set et sw=4 ts=4: */ 784