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 java.awt.Rectangle;
020import java.io.ByteArrayInputStream;
021import java.io.IOException;
022import java.nio.ByteOrder;
023
024import org.apache.commons.imaging.ImageReadException;
025import org.apache.commons.imaging.common.ImageBuilder;
026import org.apache.commons.imaging.formats.tiff.TiffRasterData;
027import org.apache.commons.imaging.formats.tiff.TiffDirectory;
028import org.apache.commons.imaging.formats.tiff.TiffImageData;
029import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
030import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
031import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
032import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
033import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
034import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
035
036/**
037 * Provides a data reader for TIFF file images organized by tiles.
038 * <p>
039 * See {@link ImageDataReader} for notes discussing design and development with
040 * particular emphasis on run-time performance.
041 */
042public final class DataReaderStrips extends ImageDataReader {
043
044    private final int bitsPerPixel;
045    private final int compression;
046    private final int rowsPerStrip;
047    private final TiffPlanarConfiguration planarConfiguration;
048    private final ByteOrder byteOrder;
049    private int x;
050    private int y;
051    private final TiffImageData.Strips imageData;
052
053    public DataReaderStrips(final TiffDirectory directory,
054        final PhotometricInterpreter photometricInterpreter, final int bitsPerPixel,
055        final int[] bitsPerSample, final int predictor,
056        final int samplesPerPixel, final int sampleFormat, final int width,
057        final int height, final int compression,
058        final TiffPlanarConfiguration planarConfiguration,
059        final ByteOrder byteOrder,
060        final int rowsPerStrip, final TiffImageData.Strips imageData) {
061        super(directory, photometricInterpreter, bitsPerSample, predictor,
062            samplesPerPixel, sampleFormat, width, height, planarConfiguration);
063
064        this.bitsPerPixel = bitsPerPixel;
065        this.compression = compression;
066        this.rowsPerStrip = rowsPerStrip;
067        this.planarConfiguration = planarConfiguration;
068        this.imageData = imageData;
069        this.byteOrder = byteOrder;
070    }
071
072    private void interpretStrip(
073            final ImageBuilder imageBuilder,
074            final byte[] bytes,
075            final int pixelsPerStrip,
076            final int yLimit) throws ImageReadException, IOException {
077        if (y >= yLimit) {
078            return;
079        }
080
081        // changes added March 2020
082        if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
083            int k = 0;
084            int nRows = pixelsPerStrip / width;
085            if (y + nRows > yLimit) {
086                nRows = yLimit - y;
087            }
088            final int i0 = y;
089            final int i1 = y + nRows;
090            x = 0;
091            y += nRows;
092            final int[] samples = new int[1];
093            final int[] b = unpackFloatingPointSamples(
094                width, i1 - i0, width, bytes, bitsPerPixel, byteOrder);
095
096            for (int i = i0; i < i1; i++) {
097                for (int j = 0; j < width; j++) {
098                    samples[0] = b[k];
099                    k += samplesPerPixel;
100                    photometricInterpreter.interpretPixel(imageBuilder,
101                        samples, j, i);
102                }
103            }
104
105            return;
106        }
107
108        // changes added May 2012
109        // In the original implementation, a general-case bit reader called
110        // getSamplesAsBytes is used to retrieve the samples (raw data values)
111        // for each pixel in the strip. These samples are then passed into a
112        // photogrammetric interpreter that converts them to ARGB pixel values
113        // and stores them in the image. Because the bit-reader must handle
114        // a large number of formats, it involves several conditional
115        // branches that must be executed each time a pixel is read.
116        // Depending on the size of an image, the same evaluations must be
117        // executed redundantly thousands and perhaps millions of times
118        // in order to process the complete collection of pixels.
119        // This code attempts to remove that redundancy by
120        // evaluating the format up-front and bypassing the general-format
121        // code for two commonly used data formats: the 8 bits-per-pixel
122        // and 24 bits-per-pixel cases. For these formats, the
123        // special case code achieves substantial reductions in image-loading
124        // time. In other cases, it simply falls through to the original code
125        // and continues to read the data correctly as it did in previous
126        // versions of this class.
127        // In addition to bypassing the getBytesForSample() method,
128        // the 24-bit case also implements a special block for RGB
129        // formatted images. To get a sense of the contributions of each
130        // optimization (removing getSamplesAsBytes and removing the
131        // photometric interpreter), consider the following results from tests
132        // conducted with large TIFF images using the 24-bit RGB format
133        // bypass getSamplesAsBytes: 67.5 % reduction
134        // bypass both optimizations: 77.2 % reduction
135        //
136        //
137        // Future Changes
138        // Both of the 8-bit and 24-bit blocks make the assumption that a strip
139        // always begins on x = 0 and that each strip exactly fills out the rows
140        // it contains (no half rows). The original code did not make this
141        // assumption, but the approach is consistent with the TIFF 6.0 spec
142        // (1992),
143        // and should probably be considered as an enhancement to the
144        // original general-case code block that remains from the original
145        // implementation. Taking this approach saves one conditional
146        // operation per pixel or about 5 percent of the total run time
147        // in the 8 bits/pixel case.
148        // verify that all samples are one byte in size
149        final boolean allSamplesAreOneByte = isHomogenous(8);
150
151        if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) {
152            int k = 0;
153            int nRows = pixelsPerStrip / width;
154            if (y + nRows > yLimit) {
155                nRows = yLimit - y;
156            }
157            final int i0 = y;
158            final int i1 = y + nRows;
159            x = 0;
160            y += nRows;
161            final int[] samples = new int[1];
162            for (int i = i0; i < i1; i++) {
163                for (int j = 0; j < width; j++) {
164                    samples[0] = bytes[k++] & 0xff;
165                    photometricInterpreter.interpretPixel(imageBuilder,
166                        samples, j, i);
167                }
168            }
169            return;
170        }
171        if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte
172            && photometricInterpreter instanceof PhotometricInterpreterRgb) {
173            int k = 0;
174            int nRows = pixelsPerStrip / width;
175            if (y + nRows > yLimit) {
176                nRows = yLimit - y;
177            }
178            final int i0 = y;
179            final int i1 = y + nRows;
180            x = 0;
181            y += nRows;
182            if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
183                applyPredictorToBlock(width, nRows, samplesPerPixel, bytes);
184            }
185
186            if (bitsPerPixel == 24) {
187                // 24 bit case, we don't mask the red byte because any
188                // sign-extended bits get covered by opacity mask
189                for (int i = i0; i < i1; i++) {
190                    for (int j = 0; j < width; j++, k += 3) {
191                        final int rgb = 0xff000000
192                            | (bytes[k] << 16)
193                            | ((bytes[k + 1] & 0xff) << 8)
194                            | (bytes[k + 2] & 0xff);
195                        imageBuilder.setRGB(j, i, rgb);
196                    }
197                }
198            } else {
199                // 32 bit case, we don't mask the high byte because any
200                // sign-extended bits get shifted up and out of result
201                for (int i = i0; i < i1; i++) {
202                    for (int j = 0; j < width; j++, k += 4) {
203                        final int rgb
204                            = ((bytes[k] & 0xff) << 16)
205                            | ((bytes[k + 1] & 0xff) << 8)
206                            | (bytes[k + 2] & 0xff)
207                            | (bytes[k + 3] << 24);
208                        imageBuilder.setRGB(j, i, rgb);
209                    }
210                }
211            }
212
213            return;
214        }
215
216        // ------------------------------------------------------------
217        // original code before May 2012 modification
218        // this logic will handle all cases not conforming to the
219        // special case handled above
220        try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
221
222            int[] samples = new int[bitsPerSampleLength];
223            resetPredictor();
224            for (int i = 0; i < pixelsPerStrip; i++) {
225                getSamplesAsBytes(bis, samples);
226
227                if (x < width) {
228                    samples = applyPredictor(samples);
229
230                    photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
231                }
232
233                x++;
234                if (x >= width) {
235                    x = 0;
236                    resetPredictor();
237                    y++;
238                    bis.flushCache();
239                    if (y >= yLimit) {
240                        break;
241                    }
242                }
243            }
244        }
245    }
246
247    @Override
248    public ImageBuilder readImageData(final Rectangle subImageSpecification,
249        final boolean hasAlpha,
250        final boolean isAlphaPreMultiplied)
251        throws ImageReadException, IOException {
252
253        final Rectangle subImage;
254        if (subImageSpecification == null) {
255            // configure subImage to read entire image
256            subImage = new Rectangle(0, 0, width, height);
257        } else {
258            subImage = subImageSpecification;
259        }
260
261        // the legacy code is optimized to the reading of whole
262        // strips (except for the last strip in the image, which can
263        // be a partial).  So create a working image with compatible
264        // dimensions and read that.  Later on, the working image
265        // will be sub-imaged to the proper size.
266        // strip0 and strip1 give the indices of the strips containing
267        // the first and last rows of pixels in the subimage
268        final int strip0 = subImage.y / rowsPerStrip;
269        final int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip;
270        final int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip;
271
272        // the legacy code uses a member element "y" to keep track
273        // of the row index of the output image that is being processed
274        // by interpretStrip. y is set to zero before the first
275        // call to interpretStrip.  y0 will be the index of the first row
276        // in the full image (the source image) that will be processed.
277        final int y0 = strip0 * rowsPerStrip;
278        final int yLimit = subImage.y - y0 + subImage.height;
279
280        final ImageBuilder workingBuilder
281            = new ImageBuilder(width, workingHeight,
282                hasAlpha, isAlphaPreMultiplied);
283        if (planarConfiguration != TiffPlanarConfiguration.PLANAR) {
284            for (int strip = strip0; strip <= strip1; strip++) {
285                final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
286                final long rowsRemaining = height - (strip * rowsPerStripLong);
287                final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
288                final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
289                final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
290                final long pixelsPerStrip = rowsInThisStrip * width;
291
292                final byte[] compressed = imageData.getImageData(strip).getData();
293
294                final byte[] decompressed = decompress(compressed, compression,
295                        (int) bytesPerStrip, width, (int) rowsInThisStrip);
296
297                interpretStrip(
298                    workingBuilder,
299                    decompressed,
300                    (int) pixelsPerStrip,
301                    yLimit);
302            }
303        } else {
304            final int nStripsInPlane = imageData.getImageDataLength() / 3;
305            for (int strip = strip0; strip <= strip1; strip++) {
306                final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
307                final long rowsRemaining = height - (strip * rowsPerStripLong);
308                final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
309                final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
310                final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
311                final long pixelsPerStrip = rowsInThisStrip * width;
312
313                final byte[] b = new byte[(int) bytesPerStrip];
314                for (int iPlane = 0; iPlane < 3; iPlane++) {
315                    final int planeStrip = iPlane * nStripsInPlane + strip;
316                    final byte[] compressed = imageData.getImageData(planeStrip).getData();
317                    final byte[] decompressed = decompress(compressed, compression,
318                        (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
330                && subImage.y == y0
331                && subImage.width == width
332                && subImage.height == workingHeight) {
333            // the subimage exactly matches the ImageBuilder bounds
334            // so we can return that.
335            return workingBuilder;
336        }
337        return workingBuilder.getSubset(
338                subImage.x,
339                subImage.y - y0,
340                subImage.width,
341                subImage.height);
342    }
343
344    @Override
345    public TiffRasterData readRasterData(final Rectangle subImage)
346            throws ImageReadException, IOException {
347        switch (sampleFormat) {
348            case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
349                return readRasterDataFloat(subImage);
350            case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
351                return readRasterDataInt(subImage);
352            default:
353                throw new ImageReadException("Unsupported sample format, value="
354                        + sampleFormat);
355        }
356    }
357
358    private TiffRasterData readRasterDataFloat(final Rectangle subImage)
359            throws ImageReadException, IOException {
360        int xRaster;
361        int yRaster;
362        int rasterWidth;
363        int rasterHeight;
364        if (subImage != null) {
365            xRaster = subImage.x;
366            yRaster = subImage.y;
367            rasterWidth = subImage.width;
368            rasterHeight = subImage.height;
369        } else {
370            xRaster = 0;
371            yRaster = 0;
372            rasterWidth = width;
373            rasterHeight = height;
374        }
375
376        float[] rasterDataFloat = new float[rasterWidth * rasterHeight * samplesPerPixel];
377
378        // the legacy code is optimized to the reading of whole
379        // strips (except for the last strip in the image, which can
380        // be a partial).  So create a working image with compatible
381        // dimensions and read that.  Later on, the working image
382        // will be sub-imaged to the proper size.
383        // strip0 and strip1 give the indices of the strips containing
384        // the first and last rows of pixels in the subimage
385        final int strip0 = yRaster / rowsPerStrip;
386        final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
387
388        for (int strip = strip0; strip <= strip1; strip++) {
389            final int yStrip = strip * rowsPerStrip;
390            final int rowsRemaining = height - yStrip;
391            final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
392            final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
393            final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
394
395            final byte[] compressed = imageData.getImageData(strip).getData();
396            final byte[] decompressed = decompress(compressed, compression,
397                    bytesPerStrip, width, rowsInThisStrip);
398
399            final int[] blockData = unpackFloatingPointSamples(
400                    width,
401                    rowsInThisStrip,
402                    width,
403                    decompressed,
404                    bitsPerPixel, byteOrder);
405            transferBlockToRaster(0, yStrip, width, (int) rowsInThisStrip, blockData,
406                    xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
407        }
408        return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
409    }
410
411    private TiffRasterData readRasterDataInt(final Rectangle subImage)
412            throws ImageReadException, IOException {
413        int xRaster;
414        int yRaster;
415        int rasterWidth;
416        int rasterHeight;
417        if (subImage != null) {
418            xRaster = subImage.x;
419            yRaster = subImage.y;
420            rasterWidth = subImage.width;
421            rasterHeight = subImage.height;
422        } else {
423            xRaster = 0;
424            yRaster = 0;
425            rasterWidth = width;
426            rasterHeight = height;
427        }
428
429        int[] rasterDataInt = new int[rasterWidth * rasterHeight];
430
431        // the legacy code is optimized to the reading of whole
432        // strips (except for the last strip in the image, which can
433        // be a partial).  So create a working image with compatible
434        // dimensions and read that.  Later on, the working image
435        // will be sub-imaged to the proper size.
436        // strip0 and strip1 give the indices of the strips containing
437        // the first and last rows of pixels in the subimage
438        final int strip0 = yRaster / rowsPerStrip;
439        final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
440
441        for (int strip = strip0; strip <= strip1; strip++) {
442            final int yStrip = strip * rowsPerStrip;
443            final int rowsRemaining = height - yStrip;
444            final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
445            final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
446            final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
447
448            final byte[] compressed = imageData.getImageData(strip).getData();
449            final byte[] decompressed = decompress(compressed, compression,
450                    bytesPerStrip, width, rowsInThisStrip);
451            final int[] blockData = unpackIntSamples(
452                    width,
453                    rowsInThisStrip,
454                    width,
455                    decompressed,
456                    predictor, bitsPerPixel, byteOrder);
457            transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData,
458                    xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt);
459        }
460        return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt);
461    }
462}