KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > ext > awt > image > codec > tiff > TIFFImageEncoder


1 /*
2
3    Copyright 2001-2003 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    You may obtain a copy of the License at
8
9        http://www.apache.org/licenses/LICENSE-2.0
10
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16
17  */

18 package org.apache.batik.ext.awt.image.codec.tiff;
19
20 import java.awt.Rectangle JavaDoc;
21 import java.awt.color.ColorSpace JavaDoc;
22 import java.awt.image.BufferedImage JavaDoc;
23 import java.awt.image.ColorModel JavaDoc;
24 import java.awt.image.ComponentSampleModel JavaDoc;
25 import java.awt.image.DataBuffer JavaDoc;
26 import java.awt.image.DataBufferByte JavaDoc;
27 import java.awt.image.IndexColorModel JavaDoc;
28 import java.awt.image.MultiPixelPackedSampleModel JavaDoc;
29 import java.awt.image.Raster JavaDoc;
30 import java.awt.image.RenderedImage JavaDoc;
31 import java.awt.image.SampleModel JavaDoc;
32 import java.awt.image.WritableRaster JavaDoc;
33 import java.io.ByteArrayOutputStream JavaDoc;
34 import java.io.File JavaDoc;
35 import java.io.FileInputStream JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.io.OutputStream JavaDoc;
38 import java.io.RandomAccessFile JavaDoc;
39 import java.util.ArrayList JavaDoc;
40 import java.util.Iterator JavaDoc;
41 import java.util.SortedSet JavaDoc;
42 import java.util.TreeSet JavaDoc;
43 import java.util.zip.Deflater JavaDoc;
44
45 import org.apache.batik.ext.awt.image.codec.ImageEncodeParam;
46 import org.apache.batik.ext.awt.image.codec.ImageEncoderImpl;
47 import org.apache.batik.ext.awt.image.codec.SeekableOutputStream;
48
49 import com.sun.image.codec.jpeg.JPEGEncodeParam;
50 import com.sun.image.codec.jpeg.JPEGQTable;
51
52 /**
53  * A baseline TIFF writer. The writer outputs TIFF images in either Bilevel,
54  * Greyscale, Palette color or Full Color modes.
55  *
56  */

57 public class TIFFImageEncoder extends ImageEncoderImpl {
58
59     // Image Types
60
private static final int TIFF_UNSUPPORTED = -1;
61     private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0;
62     private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1;
63     private static final int TIFF_GRAY = 2;
64     private static final int TIFF_PALETTE = 3;
65     private static final int TIFF_RGB = 4;
66     private static final int TIFF_CMYK = 5;
67     private static final int TIFF_YCBCR = 6;
68     private static final int TIFF_CIELAB = 7;
69     private static final int TIFF_GENERIC = 8;
70
71     // Compression types
72
private static final int COMP_NONE = 1;
73     private static final int COMP_JPEG_TTN2 = 7;
74     private static final int COMP_PACKBITS = 32773;
75     private static final int COMP_DEFLATE = 32946;
76
77     // Incidental tags
78
private static final int TIFF_JPEG_TABLES = 347;
79     private static final int TIFF_YCBCR_SUBSAMPLING = 530;
80     private static final int TIFF_YCBCR_POSITIONING = 531;
81     private static final int TIFF_REF_BLACK_WHITE = 532;
82
83     // ExtraSamples types
84
private static final int EXTRA_SAMPLE_UNSPECIFIED = 0;
85     private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1;
86     private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
87
88     // Default values
89
private static final int DEFAULT_ROWS_PER_STRIP = 8;
90
91     public TIFFImageEncoder(OutputStream JavaDoc output, ImageEncodeParam param) {
92     super(output, param);
93     if (this.param == null) {
94         this.param = new TIFFEncodeParam();
95     }
96     }
97
98     /**
99      * Encodes a RenderedImage and writes the output to the
100      * OutputStream associated with this ImageEncoder.
101      */

102     public void encode(RenderedImage JavaDoc im) throws IOException JavaDoc {
103         // Write the file header (8 bytes).
104
writeFileHeader();
105
106         // Get the encoding parameters.
107
TIFFEncodeParam encodeParam = (TIFFEncodeParam)param;
108
109     Iterator JavaDoc iter = encodeParam.getExtraImages();
110     if(iter != null) {
111             int ifdOffset = 8;
112         RenderedImage JavaDoc nextImage = im;
113             TIFFEncodeParam nextParam = encodeParam;
114             boolean hasNext;
115             do {
116                 hasNext = iter.hasNext();
117                 ifdOffset = encode(nextImage, nextParam, ifdOffset, !hasNext);
118             if(hasNext) {
119                     Object JavaDoc obj = iter.next();
120                     if(obj instanceof RenderedImage JavaDoc) {
121                         nextImage = (RenderedImage JavaDoc)obj;
122                         nextParam = encodeParam;
123                     } else if(obj instanceof Object JavaDoc[]) {
124                         Object JavaDoc[] o = (Object JavaDoc[])obj;
125                         nextImage = (RenderedImage JavaDoc)o[0];
126                         nextParam = (TIFFEncodeParam)o[1];
127                     }
128             }
129             } while(hasNext);
130         } else {
131         encode(im, encodeParam, 8, true);
132         }
133     }
134
135     private int encode(RenderedImage JavaDoc im, TIFFEncodeParam encodeParam,
136                        int ifdOffset, boolean isLast) throws IOException JavaDoc {
137     // Currently all images are stored uncompressed.
138
int compression = encodeParam.getCompression();
139
140     // Get tiled output preference.
141
boolean isTiled = encodeParam.getWriteTiled();
142
143         // Set bounds.
144
int minX = im.getMinX();
145         int minY = im.getMinY();
146         int width = im.getWidth();
147         int height = im.getHeight();
148
149         // Get SampleModel.
150
SampleModel JavaDoc sampleModel = im.getSampleModel();
151
152         // Retrieve and verify sample size.
153
int sampleSize[] = sampleModel.getSampleSize();
154         for(int i = 1; i < sampleSize.length; i++) {
155             if(sampleSize[i] != sampleSize[0]) {
156                 throw new Error JavaDoc("TIFFImageEncoder0");
157             }
158         }
159
160         // Check low bit limits.
161
int numBands = sampleModel.getNumBands();
162         if((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) {
163             throw new Error JavaDoc("TIFFImageEncoder1");
164         }
165
166         // Retrieve and verify data type.
167
int dataType = sampleModel.getDataType();
168         switch(dataType) {
169         case DataBuffer.TYPE_BYTE:
170             if(sampleSize[0] != 1 && sampleSize[0] == 4 &&
171                sampleSize[0] != 8) {
172                 throw new Error JavaDoc("TIFFImageEncoder2");
173             }
174             break;
175         case DataBuffer.TYPE_SHORT:
176         case DataBuffer.TYPE_USHORT:
177             if(sampleSize[0] != 16) {
178                 throw new Error JavaDoc("TIFFImageEncoder3");
179             }
180             break;
181         case DataBuffer.TYPE_INT:
182         case DataBuffer.TYPE_FLOAT:
183             if(sampleSize[0] != 32) {
184                 throw new Error JavaDoc("TIFFImageEncoder4");
185             }
186             break;
187         default:
188         throw new Error JavaDoc("TIFFImageEncoder5");
189     }
190
191         boolean dataTypeIsShort =
192             dataType == DataBuffer.TYPE_SHORT ||
193             dataType == DataBuffer.TYPE_USHORT;
194
195     ColorModel JavaDoc colorModel = im.getColorModel();
196         if (colorModel != null &&
197             colorModel instanceof IndexColorModel JavaDoc &&
198             dataType != DataBuffer.TYPE_BYTE) {
199             // Don't support (unsigned) short palette-color images.
200
throw new Error JavaDoc("TIFFImageEncoder6");
201         }
202     IndexColorModel JavaDoc icm = null;
203     int sizeOfColormap = 0;
204         char colormap[] = null;
205
206         // Set image type.
207
int imageType = TIFF_UNSUPPORTED;
208         int numExtraSamples = 0;
209         int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED;
210         if(colorModel instanceof IndexColorModel JavaDoc) { // Bilevel or palette
211
icm = (IndexColorModel JavaDoc)colorModel;
212             int mapSize = icm.getMapSize();
213
214             if(sampleSize[0] == 1 && numBands == 1) { // Bilevel image
215

216         if (mapSize != 2) {
217             throw new IllegalArgumentException JavaDoc(
218                     "TIFFImageEncoder7");
219         }
220
221         byte r[] = new byte[mapSize];
222         icm.getReds(r);
223         byte g[] = new byte[mapSize];
224         icm.getGreens(g);
225         byte b[] = new byte[mapSize];
226         icm.getBlues(b);
227
228         if ((r[0] & 0xff) == 0 &&
229             (r[1] & 0xff) == 255 &&
230             (g[0] & 0xff) == 0 &&
231             (g[1] & 0xff) == 255 &&
232             (b[0] & 0xff) == 0 &&
233             (b[1] & 0xff) == 255) {
234
235             imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
236
237         } else if ((r[0] & 0xff) == 255 &&
238                (r[1] & 0xff) == 0 &&
239                (g[0] & 0xff) == 255 &&
240                (g[1] & 0xff) == 0 &&
241                (b[0] & 0xff) == 255 &&
242                (b[1] & 0xff) == 0) {
243             
244             imageType = TIFF_BILEVEL_WHITE_IS_ZERO;
245
246         } else {
247             imageType = TIFF_PALETTE;
248         }
249
250         } else if(numBands == 1) { // Non-bilevel image.
251
// Palette color image.
252
imageType = TIFF_PALETTE;
253         }
254     } else if(colorModel == null) {
255
256             if(sampleSize[0] == 1 && numBands == 1) { // bilevel
257
imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
258             } else { // generic image
259
imageType = TIFF_GENERIC;
260                 if(numBands > 1) {
261                     numExtraSamples = numBands - 1;
262                 }
263             }
264
265         } else { // colorModel is non-null but not an IndexColorModel
266
ColorSpace JavaDoc colorSpace = colorModel.getColorSpace();
267
268             switch(colorSpace.getType()) {
269             case ColorSpace.TYPE_CMYK:
270                 imageType = TIFF_CMYK;
271                 break;
272             case ColorSpace.TYPE_GRAY:
273                 imageType = TIFF_GRAY;
274                 break;
275             case ColorSpace.TYPE_Lab:
276                 imageType = TIFF_CIELAB;
277                 break;
278             case ColorSpace.TYPE_RGB:
279                 if(compression == COMP_JPEG_TTN2 &&
280                    encodeParam.getJPEGCompressRGBToYCbCr()) {
281                     imageType = TIFF_YCBCR;
282                 } else {
283                     imageType = TIFF_RGB;
284                 }
285                 break;
286             case ColorSpace.TYPE_YCbCr:
287                 imageType = TIFF_YCBCR;
288                 break;
289             default:
290                 imageType = TIFF_GENERIC; // generic
291
break;
292             }
293
294             if(imageType == TIFF_GENERIC) {
295                 numExtraSamples = numBands - 1;
296             } else if(numBands > 1) {
297                 numExtraSamples = numBands - colorSpace.getNumComponents();
298             }
299
300             if(numExtraSamples == 1 && colorModel.hasAlpha()) {
301                 extraSampleType = colorModel.isAlphaPremultiplied() ?
302                     EXTRA_SAMPLE_ASSOCIATED_ALPHA :
303                     EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
304             }
305         }
306
307         if(imageType == TIFF_UNSUPPORTED) {
308             throw new Error JavaDoc("TIFFImageEncoder8");
309         }
310
311         // Check JPEG compatibility.
312
if(compression == COMP_JPEG_TTN2) {
313             if(imageType == TIFF_PALETTE) {
314                 throw new Error JavaDoc("TIFFImageEncoder11");
315             } else if(!(sampleSize[0] == 8 &&
316                         (imageType == TIFF_GRAY ||
317                          imageType == TIFF_RGB ||
318                          imageType == TIFF_YCBCR))) {
319                 throw new Error JavaDoc("TIFFImageEncoder9");
320             }
321         }
322     
323     int photometricInterpretation = -1;
324     switch (imageType) {
325
326     case TIFF_BILEVEL_WHITE_IS_ZERO:
327         photometricInterpretation = 0;
328         break;
329
330     case TIFF_BILEVEL_BLACK_IS_ZERO:
331         photometricInterpretation = 1;
332         break;
333
334     case TIFF_GRAY:
335         case TIFF_GENERIC:
336         // Since the CS_GRAY colorspace is always of type black_is_zero
337
photometricInterpretation = 1;
338         break;
339
340     case TIFF_PALETTE:
341         photometricInterpretation = 3;
342
343         icm = (IndexColorModel JavaDoc)colorModel;
344         sizeOfColormap = icm.getMapSize();
345
346         byte r[] = new byte[sizeOfColormap];
347         icm.getReds(r);
348         byte g[] = new byte[sizeOfColormap];
349         icm.getGreens(g);
350         byte b[] = new byte[sizeOfColormap];
351         icm.getBlues(b);
352
353         int redIndex = 0, greenIndex = sizeOfColormap;
354         int blueIndex = 2 * sizeOfColormap;
355             colormap = new char[sizeOfColormap * 3];
356         for (int i=0; i<sizeOfColormap; i++) {
357                 colormap[redIndex++] = (char)(((r[i] << 8) | r[i]) & 0xffff);
358                 colormap[greenIndex++] = (char)(((g[i] << 8) | g[i]) & 0xffff);
359                 colormap[blueIndex++] = (char)(((b[i] << 8) | b[i]) & 0xffff);
360         }
361
362         sizeOfColormap *= 3;
363
364         break;
365
366     case TIFF_RGB:
367         photometricInterpretation = 2;
368         break;
369
370         case TIFF_CMYK:
371         photometricInterpretation = 5;
372             break;
373
374         case TIFF_YCBCR:
375         photometricInterpretation = 6;
376             break;
377
378         case TIFF_CIELAB:
379         photometricInterpretation = 8;
380             break;
381
382         default:
383             throw new Error JavaDoc("TIFFImageEncoder8");
384     }
385
386         // Initialize tile dimensions.
387
int tileWidth;
388         int tileHeight;
389         if(isTiled) {
390             tileWidth = encodeParam.getTileWidth() > 0 ?
391                 encodeParam.getTileWidth() : im.getTileWidth();
392             tileHeight = encodeParam.getTileHeight() > 0 ?
393                 encodeParam.getTileHeight() : im.getTileHeight();
394         } else {
395             tileWidth = width;
396             
397             tileHeight = encodeParam.getTileHeight() > 0 ?
398                 encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP;
399         }
400
401         // Re-tile for JPEG conformance if needed.
402
JPEGEncodeParam jep = null;
403         if(compression == COMP_JPEG_TTN2) {
404             // Get JPEGEncodeParam from encodeParam.
405
jep = encodeParam.getJPEGEncodeParam();
406
407             // Determine maximum subsampling.
408
int maxSubH = jep.getHorizontalSubsampling(0);
409             int maxSubV = jep.getVerticalSubsampling(0);
410             for(int i = 1; i < numBands; i++) {
411                 int subH = jep.getHorizontalSubsampling(i);
412                 if(subH > maxSubH) {
413                     maxSubH = subH;
414                 }
415                 int subV = jep.getVerticalSubsampling(i);
416                 if(subV > maxSubV) {
417                     maxSubV = subV;
418                 }
419             }
420
421             int factorV = 8*maxSubV;
422             tileHeight =
423                 (int)((float)tileHeight/(float)factorV + 0.5F)*factorV;
424             if(tileHeight < factorV) {
425                 tileHeight = factorV;
426             }
427
428             if(isTiled) {
429                 int factorH = 8*maxSubH;
430                 tileWidth =
431                     (int)((float)tileWidth/(float)factorH + 0.5F)*factorH;
432                 if(tileWidth < factorH) {
433                     tileWidth = factorH;
434                 }
435             }
436         }
437
438         int numTiles;
439         if(isTiled) {
440             // NB: Parentheses are used in this statement for correct rounding.
441
numTiles =
442                 ((width + tileWidth - 1)/tileWidth) *
443                 ((height + tileHeight - 1)/tileHeight);
444         } else {
445             numTiles = (int)Math.ceil((double)height/(double)tileHeight);
446         }
447
448     long tileByteCounts[] = new long[numTiles];
449
450     long bytesPerRow =
451             (long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands);
452
453     long bytesPerTile = bytesPerRow * tileHeight;
454
455     for (int i=0; i<numTiles; i++) {
456         tileByteCounts[i] = bytesPerTile;
457     }
458
459         if(!isTiled) {
460             // Last strip may have lesser rows
461
long lastStripRows = height - (tileHeight * (numTiles-1));
462             tileByteCounts[numTiles-1] = lastStripRows * bytesPerRow;
463         }
464
465     long totalBytesOfData = bytesPerTile * (numTiles - 1) +
466         tileByteCounts[numTiles-1];
467
468         // The data will be written after the IFD: create the array here
469
// but fill it in later.
470
long tileOffsets[] = new long[numTiles];
471
472     // Basic fields - have to be in increasing numerical order.
473
// ImageWidth 256
474
// ImageLength 257
475
// BitsPerSample 258
476
// Compression 259
477
// PhotoMetricInterpretation 262
478
// StripOffsets 273
479
// RowsPerStrip 278
480
// StripByteCounts 279
481
// XResolution 282
482
// YResolution 283
483
// ResolutionUnit 296
484

485     // Create Directory
486
SortedSet JavaDoc fields = new TreeSet JavaDoc();
487
488     // Image Width
489
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH,
490                                  TIFFField.TIFF_LONG, 1,
491                                  new long[] {width}));
492
493     // Image Length
494
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_LENGTH,
495                                  TIFFField.TIFF_LONG, 1,
496                                  new long[] {height}));
497
498         char [] shortSampleSize = new char[numBands];
499         for (int i=0; i<numBands; i++)
500             shortSampleSize[i] = (char)sampleSize[i];
501     fields.add(new TIFFField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE,
502                                  TIFFField.TIFF_SHORT, numBands,
503                                  shortSampleSize));
504
505     fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION,
506                                  TIFFField.TIFF_SHORT, 1,
507                                  new char[] {(char)compression}));
508
509     fields.add(
510         new TIFFField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION,
511                           TIFFField.TIFF_SHORT, 1,
512                                  new char[] {(char)photometricInterpretation}));
513
514         if(!isTiled) {
515             fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_OFFSETS,
516                                      TIFFField.TIFF_LONG, numTiles,
517                                      tileOffsets));
518         }
519     
520     fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL,
521                                  TIFFField.TIFF_SHORT, 1,
522                                  new char[] {(char)numBands}));
523
524         if(!isTiled) {
525             fields.add(new TIFFField(TIFFImageDecoder.TIFF_ROWS_PER_STRIP,
526                                      TIFFField.TIFF_LONG, 1,
527                                      new long[] {tileHeight}));
528
529             fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS,
530                                      TIFFField.TIFF_LONG, numTiles,
531                                      tileByteCounts));
532         }
533
534     if (colormap != null) {
535         fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP,
536                                      TIFFField.TIFF_SHORT, sizeOfColormap,
537                                      colormap));
538     }
539
540         if(isTiled) {
541             fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_WIDTH,
542                                      TIFFField.TIFF_LONG, 1,
543                                      new long[] {tileWidth}));
544
545             fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_LENGTH,
546                                      TIFFField.TIFF_LONG, 1,
547                                      new long[] {tileHeight}));
548
549             fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_OFFSETS,
550                                      TIFFField.TIFF_LONG, numTiles,
551                                      tileOffsets));
552
553             fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS,
554                                      TIFFField.TIFF_LONG, numTiles,
555                                      tileByteCounts));
556         }
557
558         if(numExtraSamples > 0) {
559             char[] extraSamples = new char[numExtraSamples];
560             for(int i = 0; i < numExtraSamples; i++) {
561                 extraSamples[i] = (char)extraSampleType;
562             }
563             fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES,
564                                      TIFFField.TIFF_SHORT, numExtraSamples,
565                                      extraSamples));
566         }
567
568         // Data Sample Format Extension fields.
569
if(dataType != DataBuffer.TYPE_BYTE) {
570             // SampleFormat
571
char[] sampleFormat = new char[numBands];
572             if(dataType == DataBuffer.TYPE_FLOAT) {
573                 sampleFormat[0] = 3;
574             } else if(dataType == DataBuffer.TYPE_USHORT) {
575                 sampleFormat[0] = 1;
576             } else {
577                 sampleFormat[0] = 2;
578             }
579             for(int b = 1; b < numBands; b++) {
580                 sampleFormat[b] = sampleFormat[0];
581             }
582         fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT,
583                                      TIFFField.TIFF_SHORT, numBands,
584                                      sampleFormat));
585
586             // NOTE: We don't bother setting the SMinSampleValue and
587
// SMaxSampleValue fields as these both default to the
588
// extrema of the respective data types. Probably we should
589
// check for the presence of the "extrema" property and
590
// use it if available.
591
}
592
593         // Initialize some JPEG variables.
594
com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = null;
595         com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = null;
596         int jpegColorID = 0;
597
598         if(compression == COMP_JPEG_TTN2) {
599
600             // Initialize JPEG color ID.
601
jpegColorID =
602                 com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_UNKNOWN;
603             switch(imageType) {
604             case TIFF_GRAY:
605             case TIFF_PALETTE:
606                 jpegColorID =
607                     com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_GRAY;
608                 break;
609             case TIFF_RGB:
610                 jpegColorID =
611                     com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_RGB;
612                 break;
613             case TIFF_YCBCR:
614                 jpegColorID =
615                     com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCr;
616                 break;
617             }
618
619             // Get the JDK encoding parameters.
620
Raster JavaDoc tile00 = im.getTile(0, 0);
621             jpegEncodeParam =
622                 com.sun.image.codec.jpeg.JPEGCodec.getDefaultJPEGEncodeParam(
623                     tile00, jpegColorID);
624
625             modifyEncodeParam(jep, jpegEncodeParam, numBands);
626
627             // Write an abbreviated tables-only stream to JPEGTables field.
628
jpegEncodeParam.setImageInfoValid(false);
629             jpegEncodeParam.setTableInfoValid(true);
630             ByteArrayOutputStream JavaDoc tableStream =
631               new ByteArrayOutputStream JavaDoc();
632             jpegEncoder =
633               com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder(
634                 tableStream,
635                 jpegEncodeParam);
636             jpegEncoder.encode(tile00);
637             byte[] tableData = tableStream.toByteArray();
638             fields.add(new TIFFField(TIFF_JPEG_TABLES,
639                                      TIFFField.TIFF_UNDEFINED,
640                                      tableData.length,
641                                      tableData));
642
643             // Reset encoder so it's recreated below.
644
jpegEncoder = null;
645         }
646
647         if(imageType == TIFF_YCBCR) {
648             // YCbCrSubSampling: 2 is the default so we must write 1 as
649
// we do not (yet) do any subsampling.
650
char subsampleH = 1;
651             char subsampleV = 1;
652
653             // If JPEG, update values.
654
if(compression == COMP_JPEG_TTN2) {
655                 // Determine maximum subsampling.
656
subsampleH = (char)jep.getHorizontalSubsampling(0);
657                 subsampleV = (char)jep.getVerticalSubsampling(0);
658                 for(int i = 1; i < numBands; i++) {
659                     char subH = (char)jep.getHorizontalSubsampling(i);
660                     if(subH > subsampleH) {
661                         subsampleH = subH;
662                     }
663                     char subV = (char)jep.getVerticalSubsampling(i);
664                     if(subV > subsampleV) {
665                         subsampleV = subV;
666                     }
667                 }
668             }
669
670             fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING,
671                                      TIFFField.TIFF_SHORT, 2,
672                                      new char[] {subsampleH, subsampleV}));
673
674
675             // YCbCr positioning.
676
fields.add(new TIFFField(TIFF_YCBCR_POSITIONING,
677                                      TIFFField.TIFF_SHORT, 1,
678                                      new int[] {compression == COMP_JPEG_TTN2 ?
679                                                 1 : 2}));
680
681             // Reference black/white.
682
long[][] refbw;
683             if(compression == COMP_JPEG_TTN2) {
684                 refbw =
685                     new long[][] { // no headroon/footroom
686
{0, 1}, {255, 1}, {128, 1}, {255, 1}, {128, 1}, {255, 1}
687                     };
688             } else {
689                 refbw =
690                     new long[][] { // CCIR 601.1 headroom/footroom (presumptive)
691
{15, 1}, {235, 1}, {128, 1}, {240, 1}, {128, 1}, {240, 1}
692                     };
693             }
694             fields.add(new TIFFField(TIFF_REF_BLACK_WHITE,
695                                      TIFFField.TIFF_RATIONAL, 6,
696                                      refbw));
697         }
698
699         // ---- No more automatically generated fields should be added
700
// after this point. ----
701

702         // Add extra fields specified via the encoding parameters.
703
TIFFField[] extraFields = encodeParam.getExtraFields();
704         if(extraFields != null) {
705             ArrayList JavaDoc extantTags = new ArrayList JavaDoc(fields.size());
706             Iterator JavaDoc fieldIter = fields.iterator();
707             while(fieldIter.hasNext()) {
708                 TIFFField fld = (TIFFField)fieldIter.next();
709                 extantTags.add(new Integer JavaDoc(fld.getTag()));
710             }
711
712             int numExtraFields = extraFields.length;
713             for(int i = 0; i < numExtraFields; i++) {
714                 TIFFField fld = extraFields[i];
715                 Integer JavaDoc tagValue = new Integer JavaDoc(fld.getTag());
716                 if(!extantTags.contains(tagValue)) {
717                     fields.add(fld);
718                     extantTags.add(tagValue);
719                 }
720             }
721         }
722
723         // ---- No more fields of any type should be added after this. ----
724

725         // Determine the size of the IFD which is written after the header
726
// of the stream or after the data of the previous image in a
727
// multi-page stream.
728
int dirSize = getDirectorySize(fields);
729
730         // The first data segment is written after the field overflow
731
// following the IFD so initialize the first offset accordingly.
732
tileOffsets[0] = ifdOffset + dirSize;
733
734         // Branch here depending on whether data are being comrpressed.
735
// If not, then the IFD is written immediately.
736
// If so then there are three possibilities:
737
// A) the OutputStream is a SeekableOutputStream (outCache null);
738
// B) the OutputStream is not a SeekableOutputStream and a file cache
739
// is used (outCache non-null, tempFile non-null);
740
// C) the OutputStream is not a SeekableOutputStream and a memory cache
741
// is used (outCache non-null, tempFile null).
742

743         OutputStream JavaDoc outCache = null;
744         byte[] compressBuf = null;
745         File JavaDoc tempFile = null;
746
747         int nextIFDOffset = 0;
748         boolean skipByte = false;
749
750         Deflater JavaDoc deflater = null;
751         boolean jpegRGBToYCbCr = false;
752
753         if(compression == COMP_NONE) {
754             // Determine the number of bytes of padding necessary between
755
// the end of the IFD and the first data segment such that the
756
// alignment of the data conforms to the specification (required
757
// for uncompressed data only).
758
int numBytesPadding = 0;
759             if(sampleSize[0] == 16 && tileOffsets[0] % 2 != 0) {
760                 numBytesPadding = 1;
761                 tileOffsets[0]++;
762             } else if(sampleSize[0] == 32 && tileOffsets[0] % 4 != 0) {
763                 numBytesPadding = (int)(4 - tileOffsets[0] % 4);
764                 tileOffsets[0] += numBytesPadding;
765             }
766
767             // Update the data offsets (which TIFFField stores by reference).
768
for (int i = 1; i < numTiles; i++) {
769                 tileOffsets[i] = tileOffsets[i-1] + tileByteCounts[i-1];
770             }
771
772             if(!isLast) {
773                 // Determine the offset of the next IFD.
774
nextIFDOffset = (int)(tileOffsets[0] + totalBytesOfData);
775
776                 // IFD offsets must be on a word boundary.
777
if ((nextIFDOffset&0x01) != 0) {
778                     nextIFDOffset++;
779                     skipByte = true;
780                 }
781             }
782
783             // Write the IFD and field overflow before the image data.
784
writeDirectory(ifdOffset, fields, nextIFDOffset);
785
786             // Write any padding bytes needed between the end of the IFD
787
// and the start of the actual image data.
788
if(numBytesPadding != 0) {
789                 for(int padding = 0; padding < numBytesPadding; padding++) {
790                     output.write((byte)0);
791                 }
792             }
793         } else {
794             // If compressing, the cannot be written yet as the size of the
795
// data segments is unknown.
796

797             if((output instanceof SeekableOutputStream)) {
798                 // Simply seek to the first data segment position.
799
((SeekableOutputStream)output).seek(tileOffsets[0]);
800             } else {
801                 // Cache the original OutputStream.
802
outCache = output;
803
804                 try {
805                     // Attempt to create a temporary file.
806
tempFile = File.createTempFile("jai-SOS-", ".tmp");
807                     tempFile.deleteOnExit();
808                     RandomAccessFile JavaDoc raFile =
809                         new RandomAccessFile JavaDoc(tempFile, "rw");
810                     output = new SeekableOutputStream(raFile);
811                     
812                     // this method is exited!
813
} catch(Exception JavaDoc e) {
814                     // Allocate memory for the entire image data (!).
815
output = new ByteArrayOutputStream JavaDoc((int)totalBytesOfData);
816                 }
817             }
818
819             int bufSize = 0;
820             switch(compression) {
821             case COMP_PACKBITS:
822                 bufSize = (int)(bytesPerTile +
823                                 ((bytesPerRow+127)/128)*tileHeight);
824                 break;
825             case COMP_JPEG_TTN2:
826                 bufSize = 0;
827
828                 // Set color conversion flag.
829
if(imageType == TIFF_YCBCR &&
830                    colorModel != null &&
831                    colorModel.getColorSpace().getType() ==
832                    ColorSpace.TYPE_RGB) {
833                     jpegRGBToYCbCr = true;
834                 }
835             case COMP_DEFLATE:
836                 bufSize = (int)bytesPerTile;
837                 deflater = new Deflater JavaDoc(encodeParam.getDeflateLevel());
838                 break;
839             default:
840                 bufSize = 0;
841             }
842             if(bufSize != 0) {
843                 compressBuf = new byte[bufSize];
844             }
845         }
846
847         // ---- Writing of actual image data ----
848

849     // Buffer for up to tileHeight rows of pixels
850
int[] pixels = null;
851         float[] fpixels = null;
852
853         // Whether to test for contiguous data.
854
boolean checkContiguous =
855             ((sampleSize[0] == 1 &&
856               sampleModel instanceof MultiPixelPackedSampleModel JavaDoc &&
857               dataType == DataBuffer.TYPE_BYTE) ||
858              (sampleSize[0] == 8 &&
859               sampleModel instanceof ComponentSampleModel JavaDoc));
860
861         // Also create a buffer to hold tileHeight lines of the
862
// data to be written to the file, so we can use array writes.
863
byte[] bpixels = null;
864         if(compression != COMP_JPEG_TTN2) {
865             if(dataType == DataBuffer.TYPE_BYTE) {
866                 bpixels = new byte[tileHeight * tileWidth * numBands];
867             } else if(dataTypeIsShort) {
868                 bpixels = new byte[2 * tileHeight * tileWidth * numBands];
869             } else if(dataType == DataBuffer.TYPE_INT ||
870                       dataType == DataBuffer.TYPE_FLOAT) {
871                 bpixels = new byte[4 * tileHeight * tileWidth * numBands];
872             }
873         }
874
875     // Process tileHeight rows at a time
876
int lastRow = minY + height;
877         int lastCol = minX + width;
878         int tileNum = 0;
879         for (int row = minY; row < lastRow; row += tileHeight) {
880             int rows = isTiled ?
881                 tileHeight : Math.min(tileHeight, lastRow - row);
882             int size = rows * tileWidth * numBands;
883
884             for(int col = minX; col < lastCol; col += tileWidth) {
885                 // Grab the pixels
886
Raster JavaDoc src =
887                     im.getData(new Rectangle JavaDoc(col, row, tileWidth, rows));
888
889                 boolean useDataBuffer = false;
890                 if(compression != COMP_JPEG_TTN2) { // JPEG access Raster
891
if(checkContiguous) {
892                         if(sampleSize[0] == 8) { // 8-bit
893
ComponentSampleModel JavaDoc csm =
894                                 (ComponentSampleModel JavaDoc)src.getSampleModel();
895                             int[] bankIndices = csm.getBankIndices();
896                             int[] bandOffsets = csm.getBandOffsets();
897                             int pixelStride = csm.getPixelStride();
898                             int lineStride = csm.getScanlineStride();
899
900                             if(pixelStride != numBands ||
901                                lineStride != bytesPerRow) {
902                                 useDataBuffer = false;
903                             } else {
904                                 useDataBuffer = true;
905                                 for(int i = 0;
906                                     useDataBuffer && i < numBands;
907                                     i++) {
908                                     if(bankIndices[i] != 0 ||
909                                        bandOffsets[i] != i) {
910                                         useDataBuffer = false;
911                                     }
912                                 }
913                             }
914                         } else { // 1-bit
915
MultiPixelPackedSampleModel JavaDoc mpp =
916                                 (MultiPixelPackedSampleModel JavaDoc)src.getSampleModel();
917                             if(mpp.getNumBands() == 1 &&
918                                mpp.getDataBitOffset() == 0 &&
919                                mpp.getPixelBitStride() == 1) {
920                                 useDataBuffer = true;
921                             }
922                         }
923                     }
924
925                     if(!useDataBuffer) {
926                         if(dataType == DataBuffer.TYPE_FLOAT) {
927                             fpixels = src.getPixels(col, row, tileWidth, rows,
928                                                     fpixels);
929                         } else {
930                             pixels = src.getPixels(col, row, tileWidth, rows,
931                                                    pixels);
932                         }
933                     }
934                 }
935
936                 int index;
937
938                 int pixel = 0;
939                 int k = 0;
940                 switch(sampleSize[0]) {
941
942                 case 1:
943
944                     if(useDataBuffer) {
945                         byte[] btmp =
946                             ((DataBufferByte JavaDoc)src.getDataBuffer()).getData();
947                         MultiPixelPackedSampleModel JavaDoc mpp =
948                             (MultiPixelPackedSampleModel JavaDoc)src.getSampleModel();
949                         int lineStride = mpp.getScanlineStride();
950                         int inOffset =
951                             mpp.getOffset(col -
952                                           src.getSampleModelTranslateX(),
953                                           row -
954                                           src.getSampleModelTranslateY());
955                         if(lineStride == (int)bytesPerRow) {
956                             System.arraycopy(btmp, inOffset,
957                                              bpixels, 0,
958                                              (int)bytesPerRow*rows);
959                         } else {
960                             int outOffset = 0;
961                             for(int j = 0; j < rows; j++) {
962                                 System.arraycopy(btmp, inOffset,
963                                                  bpixels, outOffset,
964                                                  (int)bytesPerRow);
965                                 inOffset += lineStride;
966                                 outOffset += (int)bytesPerRow;
967                             }
968                         }
969                     } else {
970                         index = 0;
971
972                         // For each of the rows in a strip
973
for (int i=0; i<rows; i++) {
974
975                             // Write number of pixels exactly divisible by 8
976
for (int j=0; j<tileWidth/8; j++) {
977             
978                                 pixel =
979                                     (pixels[index++] << 7) |
980                                     (pixels[index++] << 6) |
981                                     (pixels[index++] << 5) |
982                                     (pixels[index++] << 4) |
983                                     (pixels[index++] << 3) |
984                                     (pixels[index++] << 2) |
985                                     (pixels[index++] << 1) |
986                                     pixels[index++];
987                                 bpixels[k++] = (byte)pixel;
988                             }
989
990                             // Write the pixels remaining after division by 8
991
if (tileWidth%8 > 0) {
992                                 pixel = 0;
993                                 for (int j=0; j<tileWidth%8; j++) {
994                                     pixel |= (pixels[index++] << (7 - j));
995                                 }
996                                 bpixels[k++] = (byte)pixel;
997                             }
998                         }
999                     }
1000
1001                    if(compression == COMP_NONE) {
1002                        output.write(bpixels, 0, rows * ((tileWidth+7)/8));
1003                    } else if(compression == COMP_PACKBITS) {
1004                        int numCompressedBytes =
1005                            compressPackBits(bpixels, rows,
1006                                             (int)bytesPerRow,
1007                                             compressBuf);
1008                        tileByteCounts[tileNum++] = numCompressedBytes;
1009                        output.write(compressBuf, 0, numCompressedBytes);
1010                    } else if(compression == COMP_DEFLATE) {
1011                        int numCompressedBytes =
1012                            deflate(deflater, bpixels, compressBuf);
1013                        tileByteCounts[tileNum++] = numCompressedBytes;
1014                        output.write(compressBuf, 0, numCompressedBytes);
1015                    }
1016
1017                    break;
1018
1019                case 4:
1020        
1021                    index = 0;
1022
1023                    // For each of the rows in a strip
1024
for (int i=0; i<rows; i++) {
1025            
1026                        // Write the number of pixels that will fit into an
1027
// even number of nibbles.
1028
for (int j=0; j<tileWidth/2; j++) {
1029                            pixel = (pixels[index++] << 4) | pixels[index++];
1030                            bpixels[k++] = (byte)pixel;
1031                        }
1032
1033                        // Last pixel for odd-length lines
1034
if ((tileWidth % 2) == 1) {
1035                            pixel = pixels[index++] << 4;
1036                            bpixels[k++] = (byte)pixel;
1037                        }
1038                    }
1039
1040                    if(compression == COMP_NONE) {
1041                        output.write(bpixels, 0, rows * ((tileWidth+1)/2));
1042                    } else if(compression == COMP_PACKBITS) {
1043                        int numCompressedBytes =
1044                            compressPackBits(bpixels, rows,
1045                                             (int)bytesPerRow,
1046                                             compressBuf);
1047                        tileByteCounts[tileNum++] = numCompressedBytes;
1048                        output.write(compressBuf, 0, numCompressedBytes);
1049                    } else if(compression == COMP_DEFLATE) {
1050                        int numCompressedBytes =
1051                            deflate(deflater, bpixels, compressBuf);
1052                        tileByteCounts[tileNum++] = numCompressedBytes;
1053                        output.write(compressBuf, 0, numCompressedBytes);
1054                    }
1055                    break;
1056 
1057                case 8:
1058
1059                    if(compression != COMP_JPEG_TTN2) {
1060                        if(useDataBuffer) {
1061                            byte[] btmp =
1062                                ((DataBufferByte JavaDoc)src.getDataBuffer()).getData();
1063                            ComponentSampleModel JavaDoc csm =
1064                                (ComponentSampleModel JavaDoc)src.getSampleModel();
1065                            int inOffset =
1066                                csm.getOffset(col -
1067                                              src.getSampleModelTranslateX(),
1068                                              row -
1069                                              src.getSampleModelTranslateY());
1070                            int lineStride = csm.getScanlineStride();
1071                            if(lineStride == (int)bytesPerRow) {
1072                                System.arraycopy(btmp,
1073                                                 inOffset,
1074                                                 bpixels, 0,
1075                                                 (int)bytesPerRow*rows);
1076                            } else {
1077                                int outOffset = 0;
1078                                for(int j = 0; j < rows; j++) {
1079                                    System.arraycopy(btmp, inOffset,
1080                                                     bpixels, outOffset,
1081                                                     (int)bytesPerRow);
1082                                    inOffset += lineStride;
1083                                    outOffset += (int)bytesPerRow;
1084                                }
1085                            }
1086                        } else {
1087                            for (int i = 0; i < size; i++) {
1088                                bpixels[i] = (byte)pixels[i];
1089                            }
1090                        }
1091                    }
1092
1093                    if(compression == COMP_NONE) {
1094                        output.write(bpixels, 0, size);
1095                    } else if(compression == COMP_PACKBITS) {
1096                        int numCompressedBytes =
1097                            compressPackBits(bpixels, rows,
1098                                             (int)bytesPerRow,
1099                                             compressBuf);
1100                        tileByteCounts[tileNum++] = numCompressedBytes;
1101                        output.write(compressBuf, 0, numCompressedBytes);
1102                    } else if(compression == COMP_JPEG_TTN2) {
1103                        long startPos = getOffset(output);
1104
1105                        // Recreate encoder and parameters if the encoder
1106
// is null (first data segment) or if its size
1107
// doesn't match the current data segment.
1108
if(jpegEncoder == null ||
1109                           jpegEncodeParam.getWidth() != src.getWidth() ||
1110                           jpegEncodeParam.getHeight() != src.getHeight()) {
1111
1112                          jpegEncodeParam =
1113                            com.sun.image.codec.jpeg.JPEGCodec.
1114                                getDefaultJPEGEncodeParam(src, jpegColorID);
1115
1116                            modifyEncodeParam(jep, jpegEncodeParam,
1117                                              numBands);
1118
1119                            jpegEncoder =
1120                              com.sun.image.codec.jpeg.JPEGCodec.
1121                              createJPEGEncoder(output, jpegEncodeParam);
1122                        }
1123
1124                        if(jpegRGBToYCbCr) {
1125                            WritableRaster JavaDoc wRas = null;
1126                            if(src instanceof WritableRaster JavaDoc) {
1127                                wRas = (WritableRaster JavaDoc)src;
1128                            } else {
1129                                wRas = src.createCompatibleWritableRaster();
1130                                wRas.setRect(src);
1131                            }
1132
1133                            if (wRas.getMinX() != 0 || wRas.getMinY() != 0) {
1134                                wRas =
1135                                    wRas.createWritableTranslatedChild(0, 0);
1136                            }
1137                            BufferedImage JavaDoc bi =
1138                                new BufferedImage JavaDoc(colorModel, wRas,
1139                                                  false, null);
1140                            jpegEncoder.encode(bi);
1141                        } else {
1142                            jpegEncoder.encode(src.createTranslatedChild(0,
1143                                                                         0));
1144                        }
1145
1146                        long endPos = getOffset(output);
1147                        tileByteCounts[tileNum++] = (int)(endPos - startPos);
1148                    } else if(compression == COMP_DEFLATE) {
1149                        int numCompressedBytes =
1150                            deflate(deflater, bpixels, compressBuf);
1151                        tileByteCounts[tileNum++] = numCompressedBytes;
1152                        output.write(compressBuf, 0, numCompressedBytes);
1153                    }
1154                    break;
1155        
1156                case 16:
1157
1158                    int ls = 0;
1159                    for (int i = 0; i < size; i++) {
1160                        short value = (short)pixels[i];
1161                        bpixels[ls++] = (byte)((value & 0xff00) >> 8);
1162                        bpixels[ls++] = (byte)(value & 0x00ff);
1163                    }
1164
1165                    if(compression == COMP_NONE) {
1166                        output.write(bpixels, 0, size*2);
1167                    } else if(compression == COMP_PACKBITS) {
1168                        int numCompressedBytes =
1169                            compressPackBits(bpixels, rows,
1170                                             (int)bytesPerRow,
1171                                             compressBuf);
1172                        tileByteCounts[tileNum++] = numCompressedBytes;
1173                        output.write(compressBuf, 0, numCompressedBytes);
1174                    } else if(compression == COMP_DEFLATE) {
1175                        int numCompressedBytes =
1176                            deflate(deflater, bpixels, compressBuf);
1177                        tileByteCounts[tileNum++] = numCompressedBytes;
1178                        output.write(compressBuf, 0, numCompressedBytes);
1179                    }
1180                    break;
1181
1182                case 32:
1183                    if(dataType == DataBuffer.TYPE_INT) {
1184                        int li = 0;
1185                        for (int i = 0; i < size; i++) {
1186                            int value = pixels[i];
1187                            bpixels[li++] = (byte)((value & 0xff000000) >> 24);
1188                            bpixels[li++] = (byte)((value & 0x00ff0000) >> 16);
1189                            bpixels[li++] = (byte)((value & 0x0000ff00) >> 8);
1190                            bpixels[li++] = (byte)(value & 0x000000ff);
1191                        }
1192                    } else { // DataBuffer.TYPE_FLOAT
1193
int lf = 0;
1194                        for (int i = 0; i < size; i++) {
1195                            int value = Float.floatToIntBits(fpixels[i]);
1196                            bpixels[lf++] = (byte)((value & 0xff000000) >> 24);
1197                            bpixels[lf++] = (byte)((value & 0x00ff0000) >> 16);
1198                            bpixels[lf++] = (byte)((value & 0x0000ff00) >> 8);
1199                            bpixels[lf++] = (byte)(value & 0x000000ff);
1200                        }
1201                    }
1202                    if(compression == COMP_NONE) {
1203                        output.write(bpixels, 0, size*4);
1204                    } else if(compression == COMP_PACKBITS) {
1205                        int numCompressedBytes =
1206                            compressPackBits(bpixels, rows,
1207                                             (int)bytesPerRow,
1208                                             compressBuf);
1209                        tileByteCounts[tileNum++] = numCompressedBytes;
1210                        output.write(compressBuf, 0, numCompressedBytes);
1211                    } else if(compression == COMP_DEFLATE) {
1212                        int numCompressedBytes =
1213                            deflate(deflater, bpixels, compressBuf);
1214                        tileByteCounts[tileNum++] = numCompressedBytes;
1215                        output.write(compressBuf, 0, numCompressedBytes);
1216                    }
1217                    break;
1218
1219                }
1220            }
1221        }
1222
1223        if(compression == COMP_NONE) {
1224            // Write an extra byte for IFD word alignment if needed.
1225
if(skipByte) {
1226                output.write((byte)0);
1227            }
1228        } else {
1229            // Recompute the tile offsets the size of the compressed tiles.
1230
int totalBytes = 0;
1231            for (int i=1; i<numTiles; i++) {
1232                int numBytes = (int)tileByteCounts[i-1];
1233                totalBytes += numBytes;
1234                tileOffsets[i] = tileOffsets[i-1] + numBytes;
1235            }
1236            totalBytes += (int)tileByteCounts[numTiles-1];
1237
1238            nextIFDOffset = isLast ?
1239                0 : ifdOffset + dirSize + totalBytes;
1240            if ((nextIFDOffset&0x01) != 0) {
1241                nextIFDOffset++;
1242                skipByte = true;
1243            }
1244
1245            if(outCache == null) {
1246                // Original OutputStream must be a SeekableOutputStream.
1247

1248                // Write an extra byte for IFD word alignment if needed.
1249
if(skipByte) {
1250                    output.write((byte)0);
1251                }
1252
1253                SeekableOutputStream sos = (SeekableOutputStream)output;
1254
1255                // Save current position.
1256
long savePos = sos.getFilePointer();
1257
1258                // Seek backward to the IFD offset and write IFD.
1259
sos.seek(ifdOffset);
1260                writeDirectory(ifdOffset, fields, nextIFDOffset);
1261
1262                // Seek forward to position after data.
1263
sos.seek(savePos);
1264            } else if(tempFile != null) {
1265
1266                // Using a file cache for the image data.
1267

1268                // Open a FileInputStream from which to copy the data.
1269
FileInputStream JavaDoc fileStream = new FileInputStream JavaDoc(tempFile);
1270
1271                // Close the original SeekableOutputStream.
1272
output.close();
1273
1274                // Reset variable to the original OutputStream.
1275
output = outCache;
1276
1277                // Write the IFD.
1278
writeDirectory(ifdOffset, fields, nextIFDOffset);
1279
1280                // Write the image data.
1281
byte[] copyBuffer = new byte[8192];
1282                int bytesCopied = 0;
1283                while(bytesCopied < totalBytes) {
1284                    int bytesRead = fileStream.read(copyBuffer);
1285                    if(bytesRead == -1) {
1286                        break;
1287                    }
1288                    output.write(copyBuffer, 0, bytesRead);
1289                    bytesCopied += bytesRead;
1290                }
1291
1292                // Delete the temporary file.
1293
fileStream.close();
1294                tempFile.delete();
1295
1296                // Write an extra byte for IFD word alignment if needed.
1297
if(skipByte) {
1298                    output.write((byte)0);
1299                }
1300            } else if(output instanceof ByteArrayOutputStream JavaDoc) {
1301
1302                // Using a memory cache for the image data.
1303

1304                ByteArrayOutputStream JavaDoc memoryStream =
1305                    (ByteArrayOutputStream JavaDoc)output;
1306
1307                // Reset variable to the original OutputStream.
1308
output = outCache;
1309
1310                // Write the IFD.
1311
writeDirectory(ifdOffset, fields, nextIFDOffset);
1312
1313                // Write the image data.
1314
memoryStream.writeTo(output);
1315
1316                // Write an extra byte for IFD word alignment if needed.
1317
if(skipByte) {
1318                    output.write((byte)0);
1319                }
1320            } else {
1321                // This should never happen.
1322
throw new IllegalStateException JavaDoc();
1323            }
1324        }
1325
1326
1327        return nextIFDOffset;
1328    }
1329
1330    /**
1331     * Calculates the size of the IFD.
1332     */

1333    private int getDirectorySize(SortedSet JavaDoc fields) {
1334        // Get the number of entries.
1335
int numEntries = fields.size();
1336
1337        // Initialize the size excluding that of any values > 4 bytes.
1338
int dirSize = 2 + numEntries*12 + 4;
1339
1340        // Loop over fields adding the size of all values > 4 bytes.
1341
Iterator JavaDoc iter = fields.iterator();
1342        while(iter.hasNext()) {
1343        // Get the field.
1344
TIFFField field = (TIFFField)iter.next();
1345
1346            // Determine the size of the field value.
1347
int valueSize = field.getCount()*sizeOfType[field.getType()];
1348
1349            // Add any excess size.
1350
if(valueSize > 4) {
1351                dirSize += valueSize;
1352            }
1353        }
1354
1355        return dirSize;
1356    }
1357
1358    private void writeFileHeader() throws IOException JavaDoc {
1359    // 8 byte image file header
1360

1361    // Byte order used within the file - Big Endian
1362
output.write('M');
1363    output.write('M');
1364    
1365    // Magic value
1366
output.write(0);
1367    output.write(42);
1368    
1369    // Offset in bytes of the first IFD.
1370
writeLong(8);
1371    }
1372
1373    private void writeDirectory(int thisIFDOffset, SortedSet JavaDoc fields,
1374                                int nextIFDOffset)
1375    throws IOException JavaDoc {
1376
1377    // 2 byte count of number of directory entries (fields)
1378
int numEntries = fields.size();
1379
1380    long offsetBeyondIFD = thisIFDOffset + 12 * numEntries + 4 + 2;
1381    ArrayList JavaDoc tooBig = new ArrayList JavaDoc();
1382
1383    // Write number of fields in the IFD
1384
writeUnsignedShort(numEntries);
1385
1386        Iterator JavaDoc iter = fields.iterator();
1387    while(iter.hasNext()) {
1388        
1389        // 12 byte field entry TIFFField
1390
TIFFField field = (TIFFField)iter.next();
1391
1392        // byte 0-1 Tag that identifies a field
1393
int tag = field.getTag();
1394        writeUnsignedShort(tag);
1395
1396        // byte 2-3 The field type
1397
int type = field.getType();
1398        writeUnsignedShort(type);
1399        
1400        // bytes 4-7 the number of values of the indicated type except
1401
// ASCII-valued fields which require the total number of bytes.
1402
int count = field.getCount();
1403            int valueSize = getValueSize(field);
1404        writeLong(type == TIFFField.TIFF_ASCII ? valueSize : count);
1405
1406        // bytes 8 - 11 the value or value offset
1407
if (valueSize > 4) {
1408
1409        // We need an offset as data won't fit into 4 bytes
1410
writeLong(offsetBeyondIFD);
1411        offsetBeyondIFD += valueSize;
1412        tooBig.add(field);
1413
1414        } else {
1415
1416        writeValuesAsFourBytes(field);
1417        }
1418
1419    }
1420
1421    // Address of next IFD
1422
writeLong(nextIFDOffset);
1423
1424    // Write the tag values that did not fit into 4 bytes
1425
for (int i = 0; i < tooBig.size(); i++) {
1426        writeValues((TIFFField)tooBig.get(i));
1427    }
1428    }
1429
1430    /**
1431     * Determine the number of bytes in the value portion of the field.
1432     */

1433    private static final int getValueSize(TIFFField field) {
1434        int type = field.getType();
1435        int count = field.getCount();
1436        int valueSize = 0;
1437        if(type == TIFFField.TIFF_ASCII) {
1438            for(int i = 0; i < count; i++) {
1439                byte[] stringBytes = field.getAsString(i).getBytes();
1440                valueSize += stringBytes.length;
1441                if(stringBytes[stringBytes.length-1] != (byte)0) {
1442                    valueSize++;
1443                }
1444            }
1445        } else {
1446            valueSize = count * sizeOfType[type];
1447        }
1448        return valueSize;
1449    }
1450    
1451    private static final int[] sizeOfType = {
1452        0, // 0 = n/a
1453
1, // 1 = byte
1454
1, // 2 = ascii
1455
2, // 3 = short
1456
4, // 4 = long
1457
8, // 5 = rational
1458
1, // 6 = sbyte
1459
1, // 7 = undefined
1460
2, // 8 = sshort
1461
4, // 9 = slong
1462
8, // 10 = srational
1463
4, // 11 = float
1464
8 // 12 = double
1465
};
1466
1467    private void writeValuesAsFourBytes(TIFFField field) throws IOException JavaDoc {
1468        
1469    int dataType = field.getType();
1470    int count = field.getCount();
1471
1472        switch (dataType) {
1473        
1474        // unsigned 8 bits
1475
case TIFFField.TIFF_BYTE:
1476        byte bytes[] = field.getAsBytes();
1477            if (count > 4) count =4;
1478            for (int i=0; i<count; i++)
1479        output.write(bytes[i]);
1480
1481            for (int i = 0; i < (4 - count); i++)
1482        output.write(0);
1483        break;
1484        
1485        // unsigned 16 bits
1486
case TIFFField.TIFF_SHORT:
1487            char chars[] = field.getAsChars();
1488        if (count > 2) count=2;
1489        for (int i=0; i<count; i++)
1490            writeUnsignedShort(chars[i]);
1491        for (int i = 0; i < (2 - count); i++)
1492            writeUnsignedShort(0);
1493
1494        break;
1495        
1496        // unsigned 32 bits
1497
case TIFFField.TIFF_LONG:
1498        long longs[] = field.getAsLongs();
1499
1500        for (int i=0; i<count; i++) {
1501        writeLong(longs[i]);
1502        }
1503        break;
1504    }
1505    
1506    }
1507
1508    private void writeValues(TIFFField field) throws IOException JavaDoc {
1509
1510    int dataType = field.getType();
1511    int count = field.getCount();
1512
1513    switch (dataType) {
1514        
1515        // unsigned 8 bits
1516
case TIFFField.TIFF_BYTE:
1517    case TIFFField.TIFF_SBYTE:
1518    case TIFFField.TIFF_UNDEFINED:
1519        byte bytes[] = field.getAsBytes();
1520        for (int i=0; i<count; i++) {
1521        output.write(bytes[i]);
1522        }
1523        break;
1524        
1525        // unsigned 16 bits
1526
case TIFFField.TIFF_SHORT:
1527        char chars[] = field.getAsChars();
1528        for (int i=0; i<count; i++) {
1529            writeUnsignedShort(chars[i]);
1530        }
1531        break;
1532    case TIFFField.TIFF_SSHORT:
1533        short shorts[] = field.getAsShorts();
1534        for (int i=0; i<count; i++) {
1535        writeUnsignedShort(shorts[i]);
1536        }
1537        break;
1538        
1539        // unsigned 32 bits
1540
case TIFFField.TIFF_LONG:
1541    case TIFFField.TIFF_SLONG:
1542        long longs[] = field.getAsLongs();
1543        for (int i=0; i<count; i++) {
1544        writeLong(longs[i]);
1545        }
1546        break;
1547            
1548        case TIFFField.TIFF_FLOAT:
1549            float[] floats = field.getAsFloats();
1550        for (int i=0; i<count; i++) {
1551                int intBits = Float.floatToIntBits(floats[i]);
1552        writeLong(intBits);
1553        }
1554            break;
1555
1556        case TIFFField.TIFF_DOUBLE:
1557            double[] doubles = field.getAsDoubles();
1558        for (int i=0; i<count; i++) {
1559                long longBits = Double.doubleToLongBits(doubles[i]);
1560                writeLong(longBits >>> 32);
1561        writeLong(longBits & 0xffffffff);
1562        }
1563            break;
1564
1565    case TIFFField.TIFF_RATIONAL:
1566    case TIFFField.TIFF_SRATIONAL:
1567        long rationals[][] = field.getAsRationals();
1568        for (int i=0; i<count; i++) {
1569        writeLong(rationals[i][0]);
1570        writeLong(rationals[i][1]);
1571        }
1572        break;
1573
1574        case TIFFField.TIFF_ASCII:
1575        for (int i=0; i<count; i++) {
1576                byte[] stringBytes = field.getAsString(i).getBytes();
1577                output.write(stringBytes);
1578                if(stringBytes[stringBytes.length-1] != (byte)0) {
1579                    output.write((byte)0);
1580                }
1581            }
1582            break;
1583
1584        default:
1585            throw new Error JavaDoc("TIFFImageEncoder10");
1586
1587    }
1588
1589    }
1590
1591    // Here s is never expected to have value greater than what can be
1592
// stored in 2 bytes.
1593
private void writeUnsignedShort(int s) throws IOException JavaDoc {
1594    output.write((s & 0xff00) >>> 8);
1595    output.write(s & 0x00ff);
1596    }
1597
1598    private void writeLong(long l) throws IOException JavaDoc {
1599        output.write( (int)((l & 0xff000000) >>> 24));
1600        output.write( (int)((l & 0x00ff0000) >>> 16));
1601        output.write( (int)((l & 0x0000ff00) >>> 8));
1602        output.write( ((int)l & 0x000000ff));
1603    }
1604
1605    /**
1606     * Returns the current offset in the supplied OutputStream.
1607     * This method should only be used if compressing data.
1608     */

1609    private long getOffset(OutputStream JavaDoc out) throws IOException JavaDoc {
1610        if(out instanceof ByteArrayOutputStream JavaDoc) {
1611            return ((ByteArrayOutputStream JavaDoc)out).size();
1612        } else if(out instanceof SeekableOutputStream) {
1613            return ((SeekableOutputStream)out).getFilePointer();
1614        } else {
1615            // Shouldn't happen.
1616
throw new IllegalStateException JavaDoc();
1617        }
1618    }
1619
1620    /**
1621     * Performs PackBits compression on a tile of data.
1622     */

1623    private static int compressPackBits(byte[] data, int numRows,
1624                                        int bytesPerRow, byte[] compData) {
1625        int inOffset = 0;
1626        int outOffset = 0;
1627
1628        for(int i = 0; i < numRows; i++) {
1629            outOffset = packBits(data, inOffset, bytesPerRow,
1630                                 compData, outOffset);
1631            inOffset += bytesPerRow;
1632        }
1633
1634        return outOffset;
1635    }
1636
1637    /**
1638     * Performs PackBits compression for a single buffer of data.
1639     * This should be called for each row of each tile. The returned
1640     * value is the offset into the output buffer after compression.
1641     */

1642    private static int packBits(byte[] input, int inOffset, int inCount,
1643                                byte[] output, int outOffset) {
1644        int inMax = inOffset + inCount - 1;
1645        int inMaxMinus1 = inMax - 1;
1646
1647        while(inOffset <= inMax) {
1648            int run = 1;
1649            byte replicate = input[inOffset];
1650            while(run < 127 && inOffset < inMax &&
1651                  input[inOffset] == input[inOffset+1]) {
1652                run++;
1653                inOffset++;
1654            }
1655            if(run > 1) {
1656                inOffset++;
1657                output[outOffset++] = (byte)(-(run - 1));
1658                output[outOffset++] = replicate;
1659            }
1660
1661            run = 0;
1662            int saveOffset = outOffset;
1663            while(run < 128 &&
1664                  ((inOffset < inMax &&
1665                    input[inOffset] != input[inOffset+1]) ||
1666                   (inOffset < inMaxMinus1 &&
1667                    input[inOffset] != input[inOffset+2]))) {
1668                run++;
1669                output[++outOffset] = input[inOffset++];
1670            }
1671            if(run > 0) {
1672                output[saveOffset] = (byte)(run - 1);
1673                outOffset++;
1674            }
1675
1676            if(inOffset == inMax) {
1677                if(run > 0 && run < 128) {
1678                    output[saveOffset]++;
1679                    output[outOffset++] = input[inOffset++];
1680                } else {
1681                    output[outOffset++] = (byte)0;
1682                    output[outOffset++] = input[inOffset++];
1683                }
1684            }
1685        }
1686
1687        return outOffset;
1688    }
1689
1690    private static int deflate(Deflater JavaDoc deflater,
1691                               byte[] inflated, byte[] deflated) {
1692        deflater.setInput(inflated);
1693        deflater.finish();
1694        int numCompressedBytes = deflater.deflate(deflated);
1695        deflater.reset();
1696        return numCompressedBytes;
1697    }
1698
1699    private static void modifyEncodeParam(JPEGEncodeParam src,
1700                                          JPEGEncodeParam dst,
1701                                          int nbands) {
1702      dst.setDensityUnit (src.getDensityUnit());
1703      dst.setXDensity (src.getXDensity());
1704      dst.setYDensity (src.getYDensity());
1705      dst.setRestartInterval(src.getRestartInterval());
1706      for (int i=0; i<4; i++) {
1707        JPEGQTable tbl = src.getQTable(i);
1708        if (tbl != null)
1709          dst.setQTable(i, tbl);
1710      }
1711    }
1712}
1713
1714
Popular Tags