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