001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff.datareaders;
018
019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.COMPRESSION_JPEG;
020
021import java.awt.Rectangle;
022import java.io.ByteArrayInputStream;
023import java.io.IOException;
024import java.nio.ByteOrder;
025
026import org.apache.commons.imaging.ImagingException;
027import org.apache.commons.imaging.common.Allocator;
028import org.apache.commons.imaging.common.ImageBuilder;
029import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
030import org.apache.commons.imaging.formats.tiff.AbstractTiffRasterData;
031import org.apache.commons.imaging.formats.tiff.TiffDirectory;
032import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
033import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
034import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
036import org.apache.commons.imaging.formats.tiff.photometricinterpreters.AbstractPhotometricInterpreter;
037import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
038
039/**
040 * Provides a data reader for TIFF file images organized by tiles.
041 * <p>
042 * See {@link AbstractImageDataReader} for notes discussing design and development with particular emphasis on run-time performance.
043 */
044public final class DataReaderStrips extends AbstractImageDataReader {
045
046    private final int bitsPerPixel;
047    private final int compression;
048    private final int rowsPerStrip;
049    private final TiffPlanarConfiguration planarConfiguration;
050    private final ByteOrder byteOrder;
051    private int x;
052    private int y;
053    private final AbstractTiffImageData.Strips imageData;
054
055    public DataReaderStrips(final TiffDirectory directory, final AbstractPhotometricInterpreter photometricInterpreter, final int bitsPerPixel,
056            final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, final int height,
057            final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, final int rowsPerStrip,
058            final AbstractTiffImageData.Strips imageData) {
059        super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration);
060
061        this.bitsPerPixel = bitsPerPixel;
062        this.compression = compression;
063        this.rowsPerStrip = rowsPerStrip;
064        this.planarConfiguration = planarConfiguration;
065        this.imageData = imageData;
066        this.byteOrder = byteOrder;
067    }
068
069    private void interpretStrip(final ImageBuilder imageBuilder, final byte[] bytes, final int pixelsPerStrip, final int yLimit)
070            throws ImagingException, IOException {
071        if (y >= yLimit) {
072            return;
073        }
074
075        // changes added March 2020
076        if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
077            int k = 0;
078            int nRows = pixelsPerStrip / width;
079            if (y + nRows > yLimit) {
080                nRows = yLimit - y;
081            }
082            final int i0 = y;
083            final int i1 = y + nRows;
084            x = 0;
085            y += nRows;
086            final int[] samples = new int[1];
087            final int[] b = unpackFloatingPointSamples(width, i1 - i0, width, bytes, bitsPerPixel, byteOrder);
088
089            for (int i = i0; i < i1; i++) {
090                for (int j = 0; j < width; j++) {
091                    samples[0] = b[k];
092                    k += samplesPerPixel;
093                    photometricInterpreter.interpretPixel(imageBuilder, samples, j, i);
094                }
095            }
096
097            return;
098        }
099
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 == 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 == 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 AbstractTiffRasterData 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 AbstractTiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImagingException, IOException {
350        final int xRaster;
351        final int yRaster;
352        final int rasterWidth;
353        final 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 AbstractTiffRasterData readRasterDataInt(final Rectangle subImage) throws ImagingException, IOException {
395        final int xRaster;
396        final int yRaster;
397        final int rasterWidth;
398        final 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}