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