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}