62
62
public class TIFFImageEncoder extends ImageEncoderImpl {
65
private static final int TIFF_UNSUPPORTED = -1;
66
private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0;
67
private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1;
68
private static final int TIFF_GRAY = 2;
69
private static final int TIFF_PALETTE = 3;
70
private static final int TIFF_RGB = 4;
71
private static final int TIFF_CMYK = 5;
72
private static final int TIFF_YCBCR = 6;
73
private static final int TIFF_CIELAB = 7;
74
private static final int TIFF_GENERIC = 8;
77
private static final int COMP_NONE = 1;
78
private static final int COMP_JPEG_TTN2 = 7;
79
private static final int COMP_PACKBITS = 32773;
80
private static final int COMP_DEFLATE = 32946;
83
65
private static final int TIFF_JPEG_TABLES = 347;
84
66
private static final int TIFF_YCBCR_SUBSAMPLING = 530;
85
67
private static final int TIFF_YCBCR_POSITIONING = 531;
86
68
private static final int TIFF_REF_BLACK_WHITE = 532;
89
private static final int EXTRA_SAMPLE_UNSPECIFIED = 0;
90
private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1;
91
private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
94
private static final int DEFAULT_ROWS_PER_STRIP = 8;
96
72
public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) {
97
73
super(output, param);
215
191
// Get SampleModel.
216
192
SampleModel sampleModel = im.getSampleModel();
218
// Retrieve and verify sample size.
193
ColorModel colorModel = im.getColorModel();
219
194
int[] sampleSize = sampleModel.getSampleSize();
220
for (int i = 1; i < sampleSize.length; i++) {
221
if (sampleSize[i] != sampleSize[0]) {
222
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder0"));
226
// Check low bit limits.
195
int dataTypeSize = sampleSize[0];
227
196
int numBands = sampleModel.getNumBands();
228
if ((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) {
229
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder1"));
232
// Retrieve and verify data type.
233
197
int dataType = sampleModel.getDataType();
235
case DataBuffer.TYPE_BYTE:
236
if (sampleSize[0] != 1 && sampleSize[0] == 4 && // todo does this make sense??
237
sampleSize[0] != 8) { // we get error only for 4
238
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder2"));
241
case DataBuffer.TYPE_SHORT:
242
case DataBuffer.TYPE_USHORT:
243
if (sampleSize[0] != 16) {
244
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder3"));
247
case DataBuffer.TYPE_INT:
248
case DataBuffer.TYPE_FLOAT:
249
if (sampleSize[0] != 32) {
250
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder4"));
254
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder5"));
257
boolean dataTypeIsShort =
258
dataType == DataBuffer.TYPE_SHORT ||
259
dataType == DataBuffer.TYPE_USHORT;
261
ColorModel colorModel = im.getColorModel();
262
if (colorModel != null &&
263
colorModel instanceof IndexColorModel &&
264
dataType != DataBuffer.TYPE_BYTE) {
265
// Don't support (unsigned) short palette-color images.
266
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder6"));
268
IndexColorModel icm = null;
269
int sizeOfColormap = 0;
270
char[] colormap = null;
198
validateImage(dataTypeSize, sampleSize, numBands, dataType, colorModel);
200
boolean dataTypeIsShort = dataType == DataBuffer.TYPE_SHORT
201
|| dataType == DataBuffer.TYPE_USHORT;
272
203
// Set image type.
273
int imageType = TIFF_UNSUPPORTED;
274
int numExtraSamples = 0;
275
int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED;
276
if (colorModel instanceof IndexColorModel) { // Bilevel or palette
277
icm = (IndexColorModel)colorModel;
278
int mapSize = icm.getMapSize();
280
if (sampleSize[0] == 1 && numBands == 1) { // Bilevel image
283
throw new IllegalArgumentException(PropertyUtil.getString("TIFFImageEncoder7"));
286
byte[] r = new byte[mapSize];
288
byte[] g = new byte[mapSize];
290
byte[] b = new byte[mapSize];
293
if ((r[0] & 0xff) == 0 &&
294
(r[1] & 0xff) == 255 &&
295
(g[0] & 0xff) == 0 &&
296
(g[1] & 0xff) == 255 &&
297
(b[0] & 0xff) == 0 &&
298
(b[1] & 0xff) == 255) {
300
imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
302
} else if ((r[0] & 0xff) == 255 &&
303
(r[1] & 0xff) == 0 &&
304
(g[0] & 0xff) == 255 &&
305
(g[1] & 0xff) == 0 &&
306
(b[0] & 0xff) == 255 &&
307
(b[1] & 0xff) == 0) {
309
imageType = TIFF_BILEVEL_WHITE_IS_ZERO;
312
imageType = TIFF_PALETTE;
315
} else if (numBands == 1) { // Non-bilevel image.
316
// Palette color image.
317
imageType = TIFF_PALETTE;
319
} else if (colorModel == null) {
321
if (sampleSize[0] == 1 && numBands == 1) { // bilevel
322
imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
323
} else { // generic image
324
imageType = TIFF_GENERIC;
326
numExtraSamples = numBands - 1;
330
} else { // colorModel is non-null but not an IndexColorModel
331
ColorSpace colorSpace = colorModel.getColorSpace();
333
switch(colorSpace.getType()) {
334
case ColorSpace.TYPE_CMYK:
335
imageType = TIFF_CMYK;
337
case ColorSpace.TYPE_GRAY:
338
imageType = TIFF_GRAY;
340
case ColorSpace.TYPE_Lab:
341
imageType = TIFF_CIELAB;
343
case ColorSpace.TYPE_RGB:
344
if (compression == COMP_JPEG_TTN2
345
&& encodeParam.getJPEGCompressRGBToYCbCr()) {
346
imageType = TIFF_YCBCR;
348
imageType = TIFF_RGB;
351
case ColorSpace.TYPE_YCbCr:
352
imageType = TIFF_YCBCR;
355
imageType = TIFF_GENERIC; // generic
359
if (imageType == TIFF_GENERIC) {
360
numExtraSamples = numBands - 1;
361
} else if (numBands > 1) {
362
numExtraSamples = numBands - colorSpace.getNumComponents();
365
if (numExtraSamples == 1 && colorModel.hasAlpha()) {
366
extraSampleType = colorModel.isAlphaPremultiplied() ?
367
EXTRA_SAMPLE_ASSOCIATED_ALPHA :
368
EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
372
if (imageType == TIFF_UNSUPPORTED) {
373
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
376
int photometricInterpretation = -1;
379
case TIFF_BILEVEL_WHITE_IS_ZERO:
380
photometricInterpretation = 0;
383
case TIFF_BILEVEL_BLACK_IS_ZERO:
384
photometricInterpretation = 1;
389
// Since the CS_GRAY colorspace is always of type black_is_zero
390
photometricInterpretation = 1;
394
photometricInterpretation = 3;
396
icm = (IndexColorModel)colorModel;
397
sizeOfColormap = icm.getMapSize();
399
byte[] r = new byte[sizeOfColormap];
401
byte[] g = new byte[sizeOfColormap];
403
byte[] b = new byte[sizeOfColormap];
406
int redIndex = 0, greenIndex = sizeOfColormap;
407
int blueIndex = 2 * sizeOfColormap;
408
colormap = new char[sizeOfColormap * 3];
409
for (int i = 0; i < sizeOfColormap; i++) {
410
int tmp = 0xff & r[i]; // beware of sign extended bytes
411
colormap[redIndex++] = (char)((tmp << 8) | tmp);
413
colormap[greenIndex++] = (char)((tmp << 8) | tmp);
415
colormap[blueIndex++] = (char)((tmp << 8) | tmp);
423
photometricInterpretation = 2;
427
photometricInterpretation = 5;
431
photometricInterpretation = 6;
435
photometricInterpretation = 8;
439
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
442
// Initialize tile dimensions.
446
tileWidth = encodeParam.getTileWidth() > 0 ?
447
encodeParam.getTileWidth() : im.getTileWidth();
448
tileHeight = encodeParam.getTileHeight() > 0 ?
449
encodeParam.getTileHeight() : im.getTileHeight();
453
tileHeight = encodeParam.getTileHeight() > 0 ?
454
encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP;
459
// NB: Parentheses are used in this statement for correct rounding.
461
((width + tileWidth - 1) / tileWidth) *
462
((height + tileHeight - 1) / tileHeight);
464
numTiles = (int)Math.ceil((double)height / (double)tileHeight);
204
ImageInfo imageInfo = ImageInfo.newInstance(im, dataTypeSize, numBands, colorModel,
207
if (imageInfo.getType() == ImageType.UNSUPPORTED) {
208
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
211
final int numTiles = imageInfo.getNumTiles();
212
final long bytesPerTile = imageInfo.getBytesPerTile();
213
final long bytesPerRow = imageInfo.getBytesPerRow();
214
final int tileHeight = imageInfo.getTileHeight();
215
final int tileWidth = imageInfo.getTileWidth();
467
217
long[] tileByteCounts = new long[numTiles];
470
(long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands);
472
long bytesPerTile = bytesPerRow * tileHeight;
474
218
for (int i = 0; i < numTiles; i++) {
475
219
tileByteCounts[i] = bytesPerTile;
643
382
// Add extra fields specified via the encoding parameters.
644
383
TIFFField[] extraFields = encodeParam.getExtraFields();
645
if (extraFields != null) {
646
List extantTags = new ArrayList(fields.size());
647
Iterator fieldIter = fields.iterator();
648
while (fieldIter.hasNext()) {
649
TIFFField fld = (TIFFField)fieldIter.next();
650
extantTags.add(new Integer(fld.getTag()));
384
List extantTags = new ArrayList(fields.size());
385
Iterator fieldIter = fields.iterator();
386
while (fieldIter.hasNext()) {
387
TIFFField fld = (TIFFField)fieldIter.next();
388
extantTags.add(fld.getTag());
653
int numExtraFields = extraFields.length;
654
for (int i = 0; i < numExtraFields; i++) {
655
TIFFField fld = extraFields[i];
656
Integer tagValue = new Integer(fld.getTag());
657
if (!extantTags.contains(tagValue)) {
659
extantTags.add(tagValue);
391
int numExtraFields = extraFields.length;
392
for (int i = 0; i < numExtraFields; i++) {
393
TIFFField fld = extraFields[i];
394
Integer tagValue = fld.getTag();
395
if (!extantTags.contains(tagValue)) {
397
extantTags.add(tagValue);
1079
816
bpixels[lf++] = (byte)((value & 0xff000000) >>> 24);
1080
817
bpixels[lf++] = (byte)((value & 0x00ff0000) >>> 16);
1081
818
bpixels[lf++] = (byte)((value & 0x0000ff00) >>> 8);
1082
bpixels[lf++] = (byte)( value & 0x000000ff);
819
bpixels[lf++] = (byte)(value & 0x000000ff);
1085
if (compression == COMP_NONE) {
1086
output.write(bpixels, 0, size*4);
1087
} else if (compression == COMP_PACKBITS) {
822
if (compression == CompressionValue.NONE) {
823
output.write(bpixels, 0, size * 4);
824
} else if (compression == CompressionValue.PACKBITS) {
1088
825
int numCompressedBytes =
1089
826
compressPackBits(bpixels, rows,
1092
829
tileByteCounts[tileNum++] = numCompressedBytes;
1093
830
output.write(compressBuf, 0, numCompressedBytes);
1094
} else if (compression == COMP_DEFLATE) {
831
} else if (compression == CompressionValue.DEFLATE) {
1095
832
int numCompressedBytes =
1096
833
deflate(deflater, bpixels, compressBuf);
1097
834
tileByteCounts[tileNum++] = numCompressedBytes;
1098
835
output.write(compressBuf, 0, numCompressedBytes);
1106
if (compression == COMP_NONE) {
844
if (compression == CompressionValue.NONE) {
1107
845
// Write an extra byte for IFD word alignment if needed.
1109
847
output.write((byte)0);
1151
889
// Open a FileInputStream from which to copy the data.
1152
890
FileInputStream fileStream = new FileInputStream(tempFile);
1154
// Close the original SeekableOutputStream.
1157
// Reset variable to the original OutputStream.
1161
writeDirectory(ifdOffset, fields, nextIFDOffset);
1163
// Write the image data.
1164
byte[] copyBuffer = new byte[8192];
1165
int bytesCopied = 0;
1166
while (bytesCopied < totalBytes) {
1167
int bytesRead = fileStream.read(copyBuffer);
1168
if (bytesRead == -1) {
892
// Close the original SeekableOutputStream.
895
// Reset variable to the original OutputStream.
899
writeDirectory(ifdOffset, fields, nextIFDOffset);
901
// Write the image data.
902
byte[] copyBuffer = new byte[8192];
904
while (bytesCopied < totalBytes) {
905
int bytesRead = fileStream.read(copyBuffer);
906
if (bytesRead == -1) {
909
output.write(copyBuffer, 0, bytesRead);
910
bytesCopied += bytesRead;
1171
output.write(copyBuffer, 0, bytesRead);
1172
bytesCopied += bytesRead;
913
// Delete the temporary file.
1175
// Delete the temporary file.
916
boolean isDeleted = tempFile.delete();
1179
919
// Write an extra byte for IFD word alignment if needed.
1209
949
return nextIFDOffset;
952
private void validateImage(int dataTypeSize, int[] sampleSize, int numBands, int dataType,
953
ColorModel colorModel) {
954
// Retrieve and verify sample size.
955
for (int i = 1; i < sampleSize.length; i++) {
956
if (sampleSize[i] != dataTypeSize) {
957
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder0"));
961
// Check low bit limits.
962
if ((dataTypeSize == 1 || dataTypeSize == 4) && numBands != 1) {
963
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder1"));
966
// Retrieve and verify data type.
968
case DataBuffer.TYPE_BYTE:
969
if (dataTypeSize != 1 && dataTypeSize == 4 && // todo does this make sense??
970
dataTypeSize != 8) { // we get error only for 4
971
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder2"));
974
case DataBuffer.TYPE_SHORT:
975
case DataBuffer.TYPE_USHORT:
976
if (dataTypeSize != 16) {
977
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder3"));
980
case DataBuffer.TYPE_INT:
981
case DataBuffer.TYPE_FLOAT:
982
if (dataTypeSize != 32) {
983
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder4"));
987
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder5"));
990
if (colorModel instanceof IndexColorModel && dataType != DataBuffer.TYPE_BYTE) {
991
// Don't support (unsigned) short palette-color images.
992
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder6"));
1213
997
* Calculates the size of the IFD.
1496
1280
output.write((int) (l & 0x000000ff));
1500
* Returns the current offset in the supplied OutputStream.
1501
* This method should only be used if compressing data.
1503
private long getOffset(OutputStream out) throws IOException {
1504
if (out instanceof ByteArrayOutputStream) {
1505
return ((ByteArrayOutputStream)out).size();
1506
} else if (out instanceof SeekableOutputStream) {
1507
return ((SeekableOutputStream)out).getFilePointer();
1509
// Shouldn't happen.
1510
throw new IllegalStateException(PropertyUtil.getString("TIFFImageEncoder13"));
1284
// * Returns the current offset in the supplied OutputStream.
1285
// * This method should only be used if compressing data.
1287
// private long getOffset(OutputStream out) throws IOException {
1288
// if (out instanceof ByteArrayOutputStream) {
1289
// return ((ByteArrayOutputStream)out).size();
1290
// } else if (out instanceof SeekableOutputStream) {
1291
// return ((SeekableOutputStream)out).getFilePointer();
1293
// // Shouldn't happen.
1294
// throw new IllegalStateException(PropertyUtil.getString("TIFFImageEncoder13"));
1515
1299
* Performs PackBits compression on a tile of data.
1517
1301
private static int compressPackBits(byte[] data, int numRows,
1518
int bytesPerRow, byte[] compData) {
1302
long bytesPerRow, byte[] compData) {
1519
1303
int inOffset = 0;
1520
1304
int outOffset = 0;
1522
1306
for (int i = 0; i < numRows; i++) {
1523
outOffset = packBits(data, inOffset, bytesPerRow,
1307
outOffset = packBits(data, inOffset, (int) bytesPerRow,
1524
1308
compData, outOffset);
1525
1309
inOffset += bytesPerRow;