1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. 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.commons.imaging.formats.tiff.datareaders;
19
20 import static org.apache.commons.imaging.formats.tiff.constants.AdobePhotoshopTagConstants.EXIF_TAG_JPEGTABLES;
21 import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB;
22 import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION;
23
24 import java.awt.image.BufferedImage;
25 import java.io.IOException;
26
27 import org.apache.commons.imaging.ImagingException;
28 import org.apache.commons.imaging.bytesource.ByteSource;
29 import org.apache.commons.imaging.common.ImageBuilder;
30 import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
31 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
32 import org.apache.commons.imaging.formats.tiff.TiffField;
33
34 /**
35 * Provides logic for obtaining image data from a JPEG-encoded TIFF strip or tile (the "block") from the source file.
36 */
37 final class DataInterpreterJpeg {
38
39 /**
40 * Interpret the content of a TIFF strip or tile obtained from the source file. The workingBuilder is an image builder that is configured to receive the
41 * extracted data.
42 * <p>
43 * The dimensions of the workingBuilder may or may not be identical to those of the source image. If the calling module is extracting a partial image, the
44 * workingBuilder will be sized so that its upper-left corner coordinate (row and column) and width and height are all even multiples of the dimensions of
45 * the tile or strip definition from the source. The reason for that approach is to simplify some of the logic in the legacy code by eliminating
46 * bounds-checking in the transfer loops.
47 * </p>
48 *
49 * @param directory the source directory containing JPEG tables
50 * @param workingBuilder the output image-builder to receive pixels.
51 * @param xBlock column offset of the block within the workingBuilder
52 * @param yBlock row offset of the block within the workingBuilder
53 * @param blockWidth the width of the block (may be smaller than the full width of the JPEG image obtained from the source data)
54 * @param blockHeight the height of the block (may be smaller than the full height of the JPEG image obtained from the source data)
55 * @param compressed the raw bytes from the TIFF source file
56 * @throws ImagingException in the event of an unsupported feature or JPEG-specific encoding error
57 * @throws IOException in the event of an unrecoverable I/O error
58 */
59 static void intepretBlock(final TiffDirectory directory, final ImageBuilder workingBuilder, final int xBlock, final int yBlock, final int blockWidth,
60 final int blockHeight, final byte[] compressed) throws ImagingException, IOException {
61 // the data for the block. The TIFF format can store some
62 // of the internal JPEG tables in a separate TIFF field (tag) called
63 // "JPEG Tables" which are used for all of the tiles in the file.
64 // So we need to concatenate the field bytes into the
65 // compressed sequence. Both the field bytes and compressed
66 // arrays start and end with the sequences SOI (0xffd8)
67 // and EOI (0xffd9). To concatenate the two, we need to get rid
68 // of the EOI from field bytes and SOI from compressed.
69 if (compressed.length <= 4) {
70 return;
71 }
72
73 byte[] concat;
74
75 final byte[] field = directory.getFieldValue(EXIF_TAG_JPEGTABLES, false);
76
77 if (field == null || field.length == 0) {
78 // The TIFF information was ommitted.
79 // We will just pass the compressed sequence to the
80 // decoder and let it throw the relevant exception.
81 // There is a possibility that some non-standard
82 // TIFF encoders might not use the JPEG Tables tag
83 // but embed to coding values in the compressed sequence
84 concat = compressed;
85 } else {
86 concat = new byte[field.length + compressed.length - 4];
87 System.arraycopy(field, 0, concat, 0, field.length - 2);
88 System.arraycopy(compressed, 2, concat, field.length - 2, compressed.length - 2);
89 }
90
91 final ByteSource bsArray = ByteSource.array(concat, "JPEGtile");
92
93 // Set up the decoder. In some cases, TIFF files may
94 // use the RGB encoding (which is slightly unusual for JPEGs).
95 // So check the photometric interpretation. If it is
96 // RGB, we make a special setting in the decoder. Otherwise,
97 // we let the decoder act according to the standard
98 // JPEG rules.
99 final JpegDecoder decoder = new JpegDecoder();
100
101 final TiffField piField = directory.findField(TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
102
103 if (piField != null) {
104 final int pi = piField.getIntValue();
105 if (pi == PHOTOMETRIC_INTERPRETATION_VALUE_RGB) {
106 decoder.setTiffRgb();
107 }
108 }
109
110 final BufferedImage bImage = decoder.decode(bsArray);
111 // a null result is not expected. An exception would
112 // be thrown if there was a formatting error
113 final int iWidth = bImage.getWidth();
114 final int iHeight = bImage.getHeight();
115 final int[] argb = new int[iWidth * iHeight];
116 bImage.getRGB(0, 0, iWidth, iHeight, argb, 0, iWidth);
117
118 // Limit iHeight and iWidth in case the JPEG block
119 // extends past the output image size
120 final int i1 = iHeight > blockHeight ? blockHeight : iHeight;
121 final int j1 = iWidth > blockWidth ? blockWidth : iWidth;
122
123 for (int i = 0; i < i1; i++) {
124 for (int j = 0; j < j1; j++) {
125 workingBuilder.setRgb(j + xBlock, i + yBlock, argb[i * iWidth + j]);
126 }
127 }
128 }
129 }