View Javadoc
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 }