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}