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}