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