1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.tiff;
18
19 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
20 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
21 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
22 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE;
23 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_PKZIP;
24 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG;
25 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG_OBSOLETE;
26 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
27 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
28 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_1;
29 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_2;
30
31 import java.awt.Dimension;
32 import java.awt.Rectangle;
33 import java.awt.image.BufferedImage;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.io.PrintWriter;
37 import java.nio.ByteOrder;
38 import java.nio.charset.StandardCharsets;
39 import java.util.ArrayList;
40 import java.util.List;
41
42 import org.apache.commons.imaging.AbstractImageParser;
43 import org.apache.commons.imaging.FormatCompliance;
44 import org.apache.commons.imaging.ImageFormat;
45 import org.apache.commons.imaging.ImageFormats;
46 import org.apache.commons.imaging.ImageInfo;
47 import org.apache.commons.imaging.ImagingException;
48 import org.apache.commons.imaging.bytesource.ByteSource;
49 import org.apache.commons.imaging.common.Allocator;
50 import org.apache.commons.imaging.common.ImageBuilder;
51 import org.apache.commons.imaging.common.ImageMetadata;
52 import org.apache.commons.imaging.common.XmpEmbeddable;
53 import org.apache.commons.imaging.common.XmpImagingParameters;
54 import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement;
55 import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
56 import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
57 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
58 import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader;
59 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
60 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel;
61 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab;
62 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk;
63 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv;
64 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette;
65 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
66 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr;
67 import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
68
69
70
71
72
73 public class TiffImageParser extends AbstractImageParser<TiffImagingParameters> implements XmpEmbeddable<TiffImagingParameters> {
74
75 private static final String DEFAULT_EXTENSION = ImageFormats.TIFF.getDefaultExtension();
76 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.TIFF.getExtensions();
77
78 private Rectangle checkForSubImage(final TiffImagingParameters params) {
79
80
81
82
83
84 if (params != null && params.isSubImageSet()) {
85 final int ix0 = params.getSubImageX();
86 final int iy0 = params.getSubImageY();
87 final int iwidth = params.getSubImageWidth();
88 final int iheight = params.getSubImageHeight();
89 return new Rectangle(ix0, iy0, iwidth, iheight);
90 }
91 return null;
92 }
93
94 public List<byte[]> collectRawImageData(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
95 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
96 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(byteSource, true, formatCompliance);
97
98 final List<byte[]> result = new ArrayList<>();
99 for (int i = 0; i < contents.directories.size(); i++) {
100 final TiffDirectory directory = contents.directories.get(i);
101 final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements();
102 for (final ImageDataElement element : dataElements) {
103 final byte[] bytes = byteSource.getByteArray(element.offset, element.length);
104 result.add(bytes);
105 }
106 }
107 return result;
108 }
109
110 @Override
111 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
112 try {
113 pw.println("tiff.dumpImageFile");
114
115 {
116 final ImageInfo imageData = getImageInfo(byteSource);
117 if (imageData == null) {
118 return false;
119 }
120
121 imageData.toString(pw, "");
122 }
123
124 pw.println("");
125
126
127 {
128 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
129 final TiffImagingParameters params = new TiffImagingParameters();
130 final TiffContents contents = new TiffReader(true).readContents(byteSource, params, formatCompliance);
131
132 final List<TiffDirectory> directories = contents.directories;
133 if (directories == null) {
134 return false;
135 }
136
137 for (int d = 0; d < directories.size(); d++) {
138 final TiffDirectory directory = directories.get(d);
139
140
141
142 for (final TiffField field : directory) {
143 field.dump(pw, Integer.toString(d));
144 }
145 }
146
147 pw.println("");
148 }
149
150
151
152
153
154
155
156 return true;
157 } finally {
158 pw.println("");
159 }
160 }
161
162 @Override
163 protected String[] getAcceptedExtensions() {
164 return ACCEPTED_EXTENSIONS;
165 }
166
167 @Override
168 protected ImageFormat[] getAcceptedTypes() {
169 return new ImageFormat[] { ImageFormats.TIFF,
170 };
171 }
172
173 @Override
174 public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
175 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
176 final TiffReader tiffReader = new TiffReader(true);
177 final TiffContents contents = tiffReader.readDirectories(byteSource, true, formatCompliance);
178 final List<BufferedImage> results = new ArrayList<>();
179 for (int i = 0; i < contents.directories.size(); i++) {
180 final TiffDirectory directory = contents.directories.get(i);
181 final BufferedImage result = directory.getTiffImage(tiffReader.getByteOrder(), null);
182 if (result != null) {
183 results.add(result);
184 }
185 }
186 return results;
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225 @Override
226 public BufferedImage getBufferedImage(final ByteSource byteSource, TiffImagingParameters params) throws ImagingException, IOException {
227 if (params == null) {
228 params = new TiffImagingParameters();
229 }
230 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
231 final TiffReader reader = new TiffReader(params.isStrict());
232 final TiffContents contents = reader.readFirstDirectory(byteSource, true, formatCompliance);
233 final ByteOrder byteOrder = reader.getByteOrder();
234 final TiffDirectory directory = contents.directories.get(0);
235 final BufferedImage result = directory.getTiffImage(byteOrder, params);
236 if (null == result) {
237 throw new ImagingException("TIFF does not contain an image.");
238 }
239 return result;
240 }
241
242 protected BufferedImage getBufferedImage(final TiffDirectory directory, final ByteOrder byteOrder, final TiffImagingParameters params)
243 throws ImagingException, IOException {
244 final short compressionFieldValue;
245 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
246 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
247 } else {
248 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
249 }
250 final int compression = 0xffff & compressionFieldValue;
251 final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
252 final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
253
254 final Rectangle subImage = checkForSubImage(params);
255 if (subImage != null) {
256
257
258 if (subImage.width <= 0) {
259 throw new ImagingException("Negative or zero subimage width.");
260 }
261 if (subImage.height <= 0) {
262 throw new ImagingException("Negative or zero subimage height.");
263 }
264 if (subImage.x < 0 || subImage.x >= width) {
265 throw new ImagingException("Subimage x is outside raster.");
266 }
267 if (subImage.x + subImage.width > width) {
268 throw new ImagingException("Subimage (x+width) is outside raster.");
269 }
270 if (subImage.y < 0 || subImage.y >= height) {
271 throw new ImagingException("Subimage y is outside raster.");
272 }
273 if (subImage.y + subImage.height > height) {
274 throw new ImagingException("Subimage (y+height) is outside raster.");
275 }
276 }
277
278 int samplesPerPixel = 1;
279 final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
280 if (samplesPerPixelField != null) {
281 samplesPerPixel = samplesPerPixelField.getIntValue();
282 }
283 int[] bitsPerSample = { 1 };
284 int bitsPerPixel = samplesPerPixel;
285 final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
286 if (bitsPerSampleField != null) {
287 bitsPerSample = bitsPerSampleField.getIntArrayValue();
288 bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
289 }
290
291
292
293
294 int predictor = -1;
295 {
296
297
298
299
300
301 final TiffField predictorField = directory.findField(TiffTagConstants.TIFF_TAG_PREDICTOR);
302 if (null != predictorField) {
303 predictor = predictorField.getIntValueOrArraySum();
304 }
305 }
306
307 if (samplesPerPixel != bitsPerSample.length) {
308 throw new ImagingException("Tiff: samplesPerPixel (" + samplesPerPixel + ")!=fBitsPerSample.length (" + bitsPerSample.length + ")");
309 }
310
311 final int photometricInterpretation = 0xffff & directory.getFieldValue(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
312
313 boolean hasAlpha = false;
314 boolean isAlphaPremultiplied = false;
315 if (photometricInterpretation == TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB && samplesPerPixel == 4) {
316 final TiffField extraSamplesField = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
317 if (extraSamplesField == null) {
318
319
320
321 hasAlpha = true;
322 isAlphaPremultiplied = false;
323 } else {
324 final int extraSamplesValue = extraSamplesField.getIntValue();
325 switch (extraSamplesValue) {
326 case TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA:
327 hasAlpha = true;
328 isAlphaPremultiplied = false;
329 break;
330 case TiffTagConstants.EXTRA_SAMPLE_ASSOCIATED_ALPHA:
331 hasAlpha = true;
332 isAlphaPremultiplied = true;
333 break;
334 case 0:
335 default:
336 hasAlpha = false;
337 isAlphaPremultiplied = false;
338 break;
339 }
340 }
341 }
342
343 PhotometricInterpreter photometricInterpreter = params == null ? null : params.getCustomPhotometricInterpreter();
344 if (photometricInterpreter == null) {
345 photometricInterpreter = getPhotometricInterpreter(directory, photometricInterpretation, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
346 width, height);
347 }
348
349
350 final TiffField pcField = directory.findField(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
351 final TiffPlanarConfiguration planarConfiguration = pcField == null ? TiffPlanarConfiguration.CHUNKY
352 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
353
354 if (planarConfiguration == TiffPlanarConfiguration.PLANAR) {
355
356
357
358 if (photometricInterpretation != TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB || bitsPerPixel != 24) {
359 throw new ImagingException("For planar configuration 2, only 24 bit RGB is currently supported");
360 }
361 if (null == directory.findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS)) {
362 throw new ImagingException("For planar configuration 2, only strips-organization is supported");
363 }
364 }
365
366 final AbstractTiffImageData imageData = directory.getTiffImageData();
367
368 final ImageDataReader dataReader = imageData.getDataReader(directory, photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
369 width, height, compression, planarConfiguration, byteOrder);
370
371 final ImageBuilder iBuilder = dataReader.readImageData(subImage, hasAlpha, isAlphaPremultiplied);
372 return iBuilder.getBufferedImage();
373 }
374
375 @Override
376 public String getDefaultExtension() {
377 return DEFAULT_EXTENSION;
378 }
379
380 @Override
381 public TiffImagingParameters getDefaultParameters() {
382 return new TiffImagingParameters();
383 }
384
385 @Override
386 public FormatCompliance getFormatCompliance(final ByteSource byteSource) throws ImagingException, IOException {
387 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
388 final TiffImagingParameters params = new TiffImagingParameters();
389 new TiffReader(params.isStrict()).readContents(byteSource, params, formatCompliance);
390 return formatCompliance;
391 }
392
393 @Override
394 public byte[] getIccProfileBytes(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
395 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
396 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory(byteSource, false, formatCompliance);
397 final TiffDirectory directory = contents.directories.get(0);
398
399 return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE, false);
400 }
401
402 @Override
403 public ImageInfo getImageInfo(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
404 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
405 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(byteSource, false, formatCompliance);
406 final TiffDirectory directory = contents.directories.get(0);
407
408 final TiffField widthField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
409 final TiffField heightField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
410
411 if (widthField == null || heightField == null) {
412 throw new ImagingException("TIFF image missing size info.");
413 }
414
415 final int height = heightField.getIntValue();
416 final int width = widthField.getIntValue();
417
418 final TiffField resolutionUnitField = directory.findField(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
419 int resolutionUnit = 2;
420 if (resolutionUnitField != null && resolutionUnitField.getValue() != null) {
421 resolutionUnit = resolutionUnitField.getIntValue();
422 }
423
424 double unitsPerInch = -1;
425 switch (resolutionUnit) {
426 case 1:
427 break;
428 case 2:
429 unitsPerInch = 1.0;
430 break;
431 case 3:
432 unitsPerInch = 2.54;
433 break;
434 default:
435 break;
436
437 }
438
439 int physicalWidthDpi = -1;
440 float physicalWidthInch = -1;
441 int physicalHeightDpi = -1;
442 float physicalHeightInch = -1;
443
444 if (unitsPerInch > 0) {
445 final TiffField xResolutionField = directory.findField(TiffTagConstants.TIFF_TAG_XRESOLUTION);
446 final TiffField yResolutionField = directory.findField(TiffTagConstants.TIFF_TAG_YRESOLUTION);
447
448 if (xResolutionField != null && xResolutionField.getValue() != null) {
449 final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue();
450 physicalWidthDpi = (int) Math.round(xResolutionPixelsPerUnit * unitsPerInch);
451 physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch));
452 }
453 if (yResolutionField != null && yResolutionField.getValue() != null) {
454 final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue();
455 physicalHeightDpi = (int) Math.round(yResolutionPixelsPerUnit * unitsPerInch);
456 physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch));
457 }
458 }
459
460 final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
461
462 int bitsPerSample = 1;
463 if (bitsPerSampleField != null && bitsPerSampleField.getValue() != null) {
464 bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();
465 }
466
467 final int bitsPerPixel = bitsPerSample;
468
469
470 final List<String> comments = Allocator.arrayList(directory.size());
471 for (final TiffField field : directory) {
472 final String comment = field.toString();
473 comments.add(comment);
474 }
475
476 final ImageFormat format = ImageFormats.TIFF;
477 final String formatName = "TIFF Tag-based Image File Format";
478 final String mimeType = "image/tiff";
479 final int numberOfImages = contents.directories.size();
480
481 final boolean progressive = false;
482
483
484 final String formatDetails = "TIFF v." + contents.header.tiffVersion;
485
486 boolean transparent = false;
487 boolean usesPalette = false;
488 final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP);
489 if (colorMapField != null) {
490 usesPalette = true;
491 }
492
493 final int photoInterp = 0xffff & directory.getFieldValue(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
494 final TiffField extraSamplesField = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
495 final int extraSamples;
496 if (extraSamplesField == null) {
497 extraSamples = 0;
498 } else {
499 extraSamples = extraSamplesField.getIntValue();
500 }
501 final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
502 final int samplesPerPixel;
503 if (samplesPerPixelField == null) {
504 samplesPerPixel = 1;
505 } else {
506 samplesPerPixel = samplesPerPixelField.getIntValue();
507 }
508
509 final ImageInfo.ColorType colorType;
510 switch (photoInterp) {
511 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_BLACK_IS_ZERO:
512 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_WHITE_IS_ZERO:
513
514
515 colorType = ImageInfo.ColorType.BW;
516 break;
517 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB:
518 colorType = ImageInfo.ColorType.RGB;
519
520
521
522 transparent = samplesPerPixel == 4 && extraSamples != 0;
523 break;
524 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB_PALETTE:
525 colorType = ImageInfo.ColorType.RGB;
526 usesPalette = true;
527 break;
528 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_CMYK:
529 colorType = ImageInfo.ColorType.CMYK;
530 break;
531 case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_YCB_CR:
532 colorType = ImageInfo.ColorType.YCbCr;
533 break;
534 default:
535 colorType = ImageInfo.ColorType.UNKNOWN;
536 }
537
538 final short compressionFieldValue;
539 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
540 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
541 } else {
542 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
543 }
544 final int compression = 0xffff & compressionFieldValue;
545 ImageInfo.CompressionAlgorithm compressionAlgorithm;
546
547 switch (compression) {
548 case TIFF_COMPRESSION_UNCOMPRESSED_1:
549 compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
550 break;
551 case TIFF_COMPRESSION_CCITT_1D:
552 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D;
553 break;
554 case TIFF_COMPRESSION_CCITT_GROUP_3:
555 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3;
556 break;
557 case TIFF_COMPRESSION_CCITT_GROUP_4:
558 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4;
559 break;
560 case TIFF_COMPRESSION_LZW:
561 compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW;
562 break;
563 case TIFF_COMPRESSION_JPEG_OBSOLETE:
564 compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG_TIFF_OBSOLETE;
565 break;
566 case TIFF_COMPRESSION_JPEG:
567 compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
568 break;
569 case TIFF_COMPRESSION_UNCOMPRESSED_2:
570 compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
571 break;
572 case TIFF_COMPRESSION_PACKBITS:
573 compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS;
574 break;
575 case TIFF_COMPRESSION_DEFLATE_PKZIP:
576 case TIFF_COMPRESSION_DEFLATE_ADOBE:
577 compressionAlgorithm = ImageInfo.CompressionAlgorithm.DEFLATE;
578 break;
579 default:
580 compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
581 break;
582 }
583
584 return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, height, mimeType, numberOfImages, physicalHeightDpi, physicalHeightInch,
585 physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
586 }
587
588 @Override
589 public Dimension getImageSize(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
590 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
591 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory(byteSource, false, formatCompliance);
592 final TiffDirectory directory = contents.directories.get(0);
593
594 final TiffField widthField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
595 final TiffField heightField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
596
597 if (widthField == null || heightField == null) {
598 throw new ImagingException("TIFF image missing size info.");
599 }
600
601 final int height = heightField.getIntValue();
602 final int width = widthField.getIntValue();
603
604 return new Dimension(width, height);
605 }
606
607 @Override
608 public ImageMetadata getMetadata(final ByteSource byteSource, TiffImagingParameters params) throws ImagingException, IOException {
609 if (params == null) {
610 params = this.getDefaultParameters();
611 }
612 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
613 final TiffReader tiffReader = new TiffReader(params.isStrict());
614 final TiffContents contents = tiffReader.readContents(byteSource, params, formatCompliance);
615
616 final List<TiffDirectory> directories = contents.directories;
617
618 final TiffImageMetadata result = new TiffImageMetadata(contents);
619
620 for (final TiffDirectory dir : directories) {
621 final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(tiffReader.getByteOrder(), dir);
622
623 final List<TiffField> entries = dir.getDirectoryEntries();
624
625 for (final TiffField entry : entries) {
626 metadataDirectory.add(entry);
627 }
628
629 result.add(metadataDirectory);
630 }
631
632 return result;
633 }
634
635 @Override
636 public String getName() {
637 return "Tiff-Custom";
638 }
639
640 private PhotometricInterpreter getPhotometricInterpreter(final TiffDirectory directory, final int photometricInterpretation, final int bitsPerPixel,
641 final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int width, final int height) throws ImagingException {
642 switch (photometricInterpretation) {
643 case 0:
644 case 1:
645 final boolean invert = photometricInterpretation == 0;
646
647 return new PhotometricInterpreterBiLevel(samplesPerPixel, bitsPerSample, predictor, width, height, invert);
648 case 3: {
649
650 final int[] colorMap = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP, true).getIntArrayValue();
651
652 final int expectedColormapSize = 3 * (1 << bitsPerPixel);
653
654 if (colorMap.length != expectedColormapSize) {
655 throw new ImagingException("Tiff: fColorMap.length (" + colorMap.length + ") != expectedColormapSize (" + expectedColormapSize + ")");
656 }
657
658 return new PhotometricInterpreterPalette(samplesPerPixel, bitsPerSample, predictor, width, height, colorMap);
659 }
660 case 2:
661 return new PhotometricInterpreterRgb(samplesPerPixel, bitsPerSample, predictor, width, height);
662 case 5:
663 return new PhotometricInterpreterCmyk(samplesPerPixel, bitsPerSample, predictor, width, height);
664 case 6: {
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680 return new PhotometricInterpreterYCbCr(samplesPerPixel, bitsPerSample, predictor, width, height);
681 }
682
683 case 8:
684 return new PhotometricInterpreterCieLab(samplesPerPixel, bitsPerSample, predictor, width, height);
685
686 case 32844:
687 case 32845: {
688
689 return new PhotometricInterpreterLogLuv(samplesPerPixel, bitsPerSample, predictor, width, height);
690 }
691
692 default:
693 throw new ImagingException("TIFF: Unknown fPhotometricInterpretation: " + photometricInterpretation);
694 }
695 }
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724 TiffRasterData getRasterData(final TiffDirectory directory, final ByteOrder byteOrder, TiffImagingParameters params) throws ImagingException, IOException {
725 if (params == null) {
726 params = this.getDefaultParameters();
727 }
728
729 final short[] sSampleFmt = directory.getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, true);
730 if (sSampleFmt == null || sSampleFmt.length < 1) {
731 throw new ImagingException("Directory does not specify numeric raster data");
732 }
733
734 int samplesPerPixel = 1;
735 final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
736 if (samplesPerPixelField != null) {
737 samplesPerPixel = samplesPerPixelField.getIntValue();
738 }
739
740 int[] bitsPerSample = { 1 };
741 int bitsPerPixel = samplesPerPixel;
742 final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
743 if (bitsPerSampleField != null) {
744 bitsPerSample = bitsPerSampleField.getIntArrayValue();
745 bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
746 }
747
748 final short compressionFieldValue;
749 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
750 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
751 } else {
752 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
753 }
754 final int compression = 0xffff & compressionFieldValue;
755
756 final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
757 final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
758
759 Rectangle subImage = checkForSubImage(params);
760 if (subImage != null) {
761
762
763 if (subImage.width <= 0) {
764 throw new ImagingException("Negative or zero subimage width.");
765 }
766 if (subImage.height <= 0) {
767 throw new ImagingException("Negative or zero subimage height.");
768 }
769 if (subImage.x < 0 || subImage.x >= width) {
770 throw new ImagingException("Subimage x is outside raster.");
771 }
772 if (subImage.x + subImage.width > width) {
773 throw new ImagingException("Subimage (x+width) is outside raster.");
774 }
775 if (subImage.y < 0 || subImage.y >= height) {
776 throw new ImagingException("Subimage y is outside raster.");
777 }
778 if (subImage.y + subImage.height > height) {
779 throw new ImagingException("Subimage (y+height) is outside raster.");
780 }
781
782
783
784 if (subImage.x == 0 && subImage.y == 0 && subImage.width == width && subImage.height == height) {
785 subImage = null;
786 }
787 }
788
789
790
791 int predictor = -1;
792 {
793
794
795
796
797
798 final TiffField predictorField = directory.findField(TiffTagConstants.TIFF_TAG_PREDICTOR);
799 if (null != predictorField) {
800 predictor = predictorField.getIntValueOrArraySum();
801 }
802 }
803
804
805 final TiffField pcField = directory.findField(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
806 final TiffPlanarConfiguration planarConfiguration = pcField == null ? TiffPlanarConfiguration.CHUNKY
807 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
808
809 if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
810 if (bitsPerSample[0] != 32 && bitsPerSample[0] != 64) {
811 throw new ImagingException("TIFF floating-point data uses unsupported bits-per-sample: " + bitsPerSample[0]);
812 }
813
814 if (predictor != -1 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
815 && predictor != TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) {
816 throw new ImagingException("TIFF floating-point data uses unsupported horizontal-differencing predictor");
817 }
818 } else if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) {
819
820 if (samplesPerPixel != 1) {
821 throw new ImagingException("TIFF integer data uses unsupported samples per pixel: " + samplesPerPixel);
822 }
823
824 if (bitsPerPixel != 16 && bitsPerPixel != 32) {
825 throw new ImagingException("TIFF integer data uses unsupported bits-per-pixel: " + bitsPerPixel);
826 }
827
828 if (predictor != -1 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
829 && predictor != TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
830 throw new ImagingException("TIFF integer data uses unsupported horizontal-differencing predictor");
831 }
832 } else {
833 throw new ImagingException("TIFF does not provide a supported raster-data format");
834 }
835
836
837
838 final PhotometricInterpreter photometricInterpreter = new PhotometricInterpreterBiLevel(samplesPerPixel, bitsPerSample, predictor, width, height,
839 false);
840
841 final AbstractTiffImageData imageData = directory.getTiffImageData();
842
843 final ImageDataReader dataReader = imageData.getDataReader(directory, photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
844 width, height, compression, planarConfiguration, byteOrder);
845
846 return dataReader.readRasterData(subImage);
847 }
848
849 @Override
850 public String getXmpXml(final ByteSource byteSource, XmpImagingParameters<TiffImagingParameters> params) throws ImagingException, IOException {
851 if (params == null) {
852 params = new XmpImagingParameters<>();
853 }
854 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
855 final TiffContents contents = new TiffReader(params.isStrict()).readDirectories(byteSource, false, formatCompliance);
856 final TiffDirectory directory = contents.directories.get(0);
857
858 final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP, false);
859 if (bytes == null) {
860 return null;
861 }
862
863
864 return new String(bytes, StandardCharsets.UTF_8);
865 }
866
867 @Override
868 public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params) throws ImagingException, IOException {
869 if (params == null) {
870 params = new TiffImagingParameters();
871 }
872 new TiffImageWriterLossy().writeImage(src, os, params);
873 }
874
875 }