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