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  package org.apache.commons.imaging.formats.tiff.datareaders;
18  
19  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG;
20  
21  import java.awt.Rectangle;
22  import java.io.ByteArrayInputStream;
23  import java.io.IOException;
24  import java.nio.ByteOrder;
25  
26  import org.apache.commons.imaging.ImagingException;
27  import org.apache.commons.imaging.common.Allocator;
28  import org.apache.commons.imaging.common.ImageBuilder;
29  import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
30  import org.apache.commons.imaging.formats.tiff.TiffDirectory;
31  import org.apache.commons.imaging.formats.tiff.TiffRasterData;
32  import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
33  import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
34  import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
35  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
36  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
37  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
38  
39  /**
40   * Provides a data reader for TIFF file images organized by tiles.
41   * <p>
42   * See {@link ImageDataReader} for notes discussing design and development with particular emphasis on run-time performance.
43   */
44  public final class DataReaderStrips extends ImageDataReader {
45  
46      private final int bitsPerPixel;
47      private final int compression;
48      private final int rowsPerStrip;
49      private final TiffPlanarConfiguration planarConfiguration;
50      private final ByteOrder byteOrder;
51      private int x;
52      private int y;
53      private final AbstractTiffImageData.Strips imageData;
54  
55      public DataReaderStrips(final TiffDirectory directory, final PhotometricInterpreter photometricInterpreter, final int bitsPerPixel,
56              final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, final int height,
57              final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, final int rowsPerStrip,
58              final AbstractTiffImageData.Strips imageData) {
59          super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration);
60  
61          this.bitsPerPixel = bitsPerPixel;
62          this.compression = compression;
63          this.rowsPerStrip = rowsPerStrip;
64          this.planarConfiguration = planarConfiguration;
65          this.imageData = imageData;
66          this.byteOrder = byteOrder;
67      }
68  
69      private void interpretStrip(final ImageBuilder imageBuilder, final byte[] bytes, final int pixelsPerStrip, final int yLimit)
70              throws ImagingException, IOException {
71          if (y >= yLimit) {
72              return;
73          }
74  
75          // changes added March 2020
76          if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
77              int k = 0;
78              int nRows = pixelsPerStrip / width;
79              if (y + nRows > yLimit) {
80                  nRows = yLimit - y;
81              }
82              final int i0 = y;
83              final int i1 = y + nRows;
84              x = 0;
85              y += nRows;
86              final int[] samples = new int[1];
87              final int[] b = unpackFloatingPointSamples(width, i1 - i0, width, bytes, bitsPerPixel, byteOrder);
88  
89              for (int i = i0; i < i1; i++) {
90                  for (int j = 0; j < width; j++) {
91                      samples[0] = b[k];
92                      k += samplesPerPixel;
93                      photometricInterpreter.interpretPixel(imageBuilder, samples, j, i);
94                  }
95              }
96  
97              return;
98          }
99  
100         // changes added May 2012
101         // In the original implementation, a general-case bit reader called
102         // getSamplesAsBytes is used to retrieve the samples (raw data values)
103         // for each pixel in the strip. These samples are then passed into a
104         // photogrammetric interpreter that converts them to ARGB pixel values
105         // and stores them in the image. Because the bit-reader must handle
106         // a large number of formats, it involves several conditional
107         // branches that must be executed each time a pixel is read.
108         // Depending on the size of an image, the same evaluations must be
109         // executed redundantly thousands and perhaps millions of times
110         // in order to process the complete collection of pixels.
111         // This code attempts to remove that redundancy by
112         // evaluating the format up-front and bypassing the general-format
113         // code for two commonly used data formats: the 8 bits-per-pixel
114         // and 24 bits-per-pixel cases. For these formats, the
115         // special case code achieves substantial reductions in image-loading
116         // time. In other cases, it simply falls through to the original code
117         // and continues to read the data correctly as it did in previous
118         // versions of this class.
119         // In addition to bypassing the getBytesForSample() method,
120         // the 24-bit case also implements a special block for RGB
121         // formatted images. To get a sense of the contributions of each
122         // optimization (removing getSamplesAsBytes and removing the
123         // photometric interpreter), consider the following results from tests
124         // conducted with large TIFF images using the 24-bit RGB format
125         // bypass getSamplesAsBytes: 67.5 % reduction
126         // bypass both optimizations: 77.2 % reduction
127         //
128         //
129         // Future Changes
130         // Both of the 8-bit and 24-bit blocks make the assumption that a strip
131         // always begins on x = 0 and that each strip exactly fills out the rows
132         // it contains (no half rows). The original code did not make this
133         // assumption, but the approach is consistent with the TIFF 6.0 spec
134         // (1992),
135         // and should probably be considered as an enhancement to the
136         // original general-case code block that remains from the original
137         // implementation. Taking this approach saves one conditional
138         // operation per pixel or about 5 percent of the total run time
139         // in the 8 bits/pixel case.
140         // verify that all samples are one byte in size
141         final boolean allSamplesAreOneByte = isHomogenous(8);
142 
143         if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) {
144             int k = 0;
145             int nRows = pixelsPerStrip / width;
146             if (y + nRows > yLimit) {
147                 nRows = yLimit - y;
148             }
149             final int i0 = y;
150             final int i1 = y + nRows;
151             x = 0;
152             y += nRows;
153             final int[] samples = new int[1];
154             for (int i = i0; i < i1; i++) {
155                 for (int j = 0; j < width; j++) {
156                     samples[0] = bytes[k++] & 0xff;
157                     photometricInterpreter.interpretPixel(imageBuilder, samples, j, i);
158                 }
159             }
160             return;
161         }
162         if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte && photometricInterpreter instanceof PhotometricInterpreterRgb) {
163             int k = 0;
164             int nRows = pixelsPerStrip / width;
165             if (y + nRows > yLimit) {
166                 nRows = yLimit - y;
167             }
168             final int i0 = y;
169             final int i1 = y + nRows;
170             x = 0;
171             y += nRows;
172             if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
173                 applyPredictorToBlock(width, nRows, samplesPerPixel, bytes);
174             }
175 
176             if (bitsPerPixel == 24) {
177                 // 24 bit case, we don't mask the red byte because any
178                 // sign-extended bits get covered by opacity mask
179                 for (int i = i0; i < i1; i++) {
180                     for (int j = 0; j < width; j++, k += 3) {
181                         final int rgb = 0xff000000 | bytes[k] << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff;
182                         imageBuilder.setRgb(j, i, rgb);
183                     }
184                 }
185             } else {
186                 // 32 bit case, we don't mask the high byte because any
187                 // sign-extended bits get shifted up and out of result
188                 for (int i = i0; i < i1; i++) {
189                     for (int j = 0; j < width; j++, k += 4) {
190                         final int rgb = (bytes[k] & 0xff) << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff | bytes[k + 3] << 24;
191                         imageBuilder.setRgb(j, i, rgb);
192                     }
193                 }
194             }
195 
196             return;
197         }
198 
199         // original code before May 2012 modification
200         // this logic will handle all cases not conforming to the
201         // special case handled above
202         try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
203 
204             int[] samples = Allocator.intArray(bitsPerSampleLength);
205             resetPredictor();
206             for (int i = 0; i < pixelsPerStrip; i++) {
207                 getSamplesAsBytes(bis, samples);
208 
209                 if (x < width) {
210                     samples = applyPredictor(samples);
211 
212                     photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
213                 }
214 
215                 x++;
216                 if (x >= width) {
217                     x = 0;
218                     resetPredictor();
219                     y++;
220                     bis.flushCache();
221                     if (y >= yLimit) {
222                         break;
223                     }
224                 }
225             }
226         }
227     }
228 
229     @Override
230     public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied)
231             throws IOException, ImagingException {
232 
233         final Rectangle subImage;
234         if (subImageSpecification == null) {
235             // configure subImage to read entire image
236             subImage = new Rectangle(0, 0, width, height);
237         } else {
238             subImage = subImageSpecification;
239         }
240 
241         // the legacy code is optimized to the reading of whole
242         // strips (except for the last strip in the image, which can
243         // be a partial). So create a working image with compatible
244         // dimensions and read that. Later on, the working image
245         // will be sub-imaged to the proper size.
246         // strip0 and strip1 give the indices of the strips containing
247         // the first and last rows of pixels in the subimage
248         final int strip0 = subImage.y / rowsPerStrip;
249         final int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip;
250         final int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip;
251 
252         // the legacy code uses a member element "y" to keep track
253         // of the row index of the output image that is being processed
254         // by interpretStrip. y is set to zero before the first
255         // call to interpretStrip. y0 will be the index of the first row
256         // in the full image (the source image) that will be processed.
257         final int y0 = strip0 * rowsPerStrip;
258         final int yLimit = subImage.y - y0 + subImage.height;
259 
260         // When processing a subimage, the workingBuilder height is set
261         // to be an integral multiple of the rowsPerStrip and
262         // the full width of the strips. So the working image may be larger than
263         // the specified size of the subimage. If necessary, the subimage
264         // is extracted from the workingBuilder at the end of this method.
265         // This approach avoids the need for the interpretStrips method
266         // to implement bounds checking for a subimage.
267         final ImageBuilder workingBuilder = new ImageBuilder(width, workingHeight, hasAlpha, isAlphaPreMultiplied);
268 
269         // the following statement accounts for cases where planar configuration
270         // is not specified and the default (CHUNKY) is assumed.
271         final boolean interleaved = planarConfiguration != TiffPlanarConfiguration.PLANAR;
272         if (interleaved) {
273             // Pixel definitions are organized in an interleaved format
274             // For example, red-green-blue values for each pixel
275             // would appear contiguous in input sequence.
276             for (int strip = strip0; strip <= strip1; strip++) {
277                 final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
278                 final long rowsRemaining = height - strip * rowsPerStripLong;
279                 final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
280                 final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
281                 final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
282                 final long pixelsPerStrip = rowsInThisStrip * width;
283 
284                 final byte[] compressed = imageData.getImageData(strip).getData();
285 
286                 if (compression == TIFF_COMPRESSION_JPEG) {
287                     final int yBlock = strip * rowsPerStrip;
288                     final int yWork = yBlock - y0;
289                     DataInterpreterJpeg.intepretBlock(directory, workingBuilder, 0, yWork, width, (int) rowsInThisStrip, compressed);
290                     continue;
291                 }
292 
293                 final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip);
294 
295                 interpretStrip(workingBuilder, decompressed, (int) pixelsPerStrip, yLimit);
296             }
297         } else {
298             // pixel definitions are organized in a 3 separate sections of input
299             // sequence. For example, red-green-blue values would be given as
300             // red values for all pixels, followed by green values for all pixels,
301             // etc.
302             if (compression == TIFF_COMPRESSION_JPEG) {
303                 throw new ImagingException("TIFF file in non-supported configuration: JPEG compression used in planar configuration.");
304             }
305             final int nStripsInPlane = imageData.getImageDataLength() / 3;
306             for (int strip = strip0; strip <= strip1; strip++) {
307                 final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
308                 final long rowsRemaining = height - strip * rowsPerStripLong;
309                 final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
310                 final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
311                 final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
312                 final long pixelsPerStrip = rowsInThisStrip * width;
313 
314                 final byte[] b = Allocator.byteArray((int) bytesPerStrip);
315                 for (int iPlane = 0; iPlane < 3; iPlane++) {
316                     final int planeStrip = iPlane * nStripsInPlane + strip;
317                     final byte[] compressed = imageData.getImageData(planeStrip).getData();
318                     final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip);
319                     int index = iPlane;
320                     for (final byte element : decompressed) {
321                         b[index] = element;
322                         index += 3;
323                     }
324                 }
325                 interpretStrip(workingBuilder, b, (int) pixelsPerStrip, height);
326             }
327         }
328 
329         if (subImage.x == 0 && subImage.y == y0 && subImage.width == width && subImage.height == workingHeight) {
330             // the subimage exactly matches the ImageBuilder bounds
331             // so we can return that.
332             return workingBuilder;
333         }
334         return workingBuilder.getSubset(subImage.x, subImage.y - y0, subImage.width, subImage.height);
335     }
336 
337     @Override
338     public TiffRasterData readRasterData(final Rectangle subImage) throws ImagingException, IOException {
339         switch (sampleFormat) {
340         case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
341             return readRasterDataFloat(subImage);
342         case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
343             return readRasterDataInt(subImage);
344         default:
345             throw new ImagingException("Unsupported sample format, value=" + sampleFormat);
346         }
347     }
348 
349     private TiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImagingException, IOException {
350         int xRaster;
351         int yRaster;
352         int rasterWidth;
353         int rasterHeight;
354         if (subImage != null) {
355             xRaster = subImage.x;
356             yRaster = subImage.y;
357             rasterWidth = subImage.width;
358             rasterHeight = subImage.height;
359         } else {
360             xRaster = 0;
361             yRaster = 0;
362             rasterWidth = width;
363             rasterHeight = height;
364         }
365 
366         final float[] rasterDataFloat = Allocator.floatArray(rasterWidth * rasterHeight * samplesPerPixel);
367 
368         // the legacy code is optimized to the reading of whole
369         // strips (except for the last strip in the image, which can
370         // be a partial). So create a working image with compatible
371         // dimensions and read that. Later on, the working image
372         // will be sub-imaged to the proper size.
373         // strip0 and strip1 give the indices of the strips containing
374         // the first and last rows of pixels in the subimage
375         final int strip0 = yRaster / rowsPerStrip;
376         final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
377 
378         for (int strip = strip0; strip <= strip1; strip++) {
379             final int yStrip = strip * rowsPerStrip;
380             final int rowsRemaining = height - yStrip;
381             final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
382             final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
383             final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
384 
385             final byte[] compressed = imageData.getImageData(strip).getData();
386             final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip);
387 
388             final int[] blockData = unpackFloatingPointSamples(width, rowsInThisStrip, width, decompressed, bitsPerPixel, byteOrder);
389             transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
390         }
391         return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
392     }
393 
394     private TiffRasterData readRasterDataInt(final Rectangle subImage) throws ImagingException, IOException {
395         int xRaster;
396         int yRaster;
397         int rasterWidth;
398         int rasterHeight;
399         if (subImage != null) {
400             xRaster = subImage.x;
401             yRaster = subImage.y;
402             rasterWidth = subImage.width;
403             rasterHeight = subImage.height;
404         } else {
405             xRaster = 0;
406             yRaster = 0;
407             rasterWidth = width;
408             rasterHeight = height;
409         }
410 
411         final int[] rasterDataInt = Allocator.intArray(rasterWidth * rasterHeight);
412 
413         // the legacy code is optimized to the reading of whole
414         // strips (except for the last strip in the image, which can
415         // be a partial). So create a working image with compatible
416         // dimensions and read that. Later on, the working image
417         // will be sub-imaged to the proper size.
418         // strip0 and strip1 give the indices of the strips containing
419         // the first and last rows of pixels in the subimage
420         final int strip0 = yRaster / rowsPerStrip;
421         final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
422 
423         for (int strip = strip0; strip <= strip1; strip++) {
424             final int yStrip = strip * rowsPerStrip;
425             final int rowsRemaining = height - yStrip;
426             final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
427             final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
428             final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
429 
430             final byte[] compressed = imageData.getImageData(strip).getData();
431             final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip);
432             final int[] blockData = unpackIntSamples(width, rowsInThisStrip, width, decompressed, predictor, bitsPerPixel, byteOrder);
433             transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt);
434         }
435         return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt);
436     }
437 }