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 static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.COMPRESSION_JPEG; 020 021import java.awt.Rectangle; 022import java.io.ByteArrayInputStream; 023import java.io.IOException; 024import java.nio.ByteOrder; 025 026import org.apache.commons.imaging.ImagingException; 027import org.apache.commons.imaging.common.Allocator; 028import org.apache.commons.imaging.common.ImageBuilder; 029import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData; 030import org.apache.commons.imaging.formats.tiff.AbstractTiffRasterData; 031import org.apache.commons.imaging.formats.tiff.TiffDirectory; 032import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat; 033import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt; 034import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration; 035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 036import org.apache.commons.imaging.formats.tiff.photometricinterpreters.AbstractPhotometricInterpreter; 037import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb; 038 039/** 040 * Provides a data reader for TIFF file images organized by tiles. 041 * <p> 042 * See {@link AbstractImageDataReader} for notes discussing design and development with particular emphasis on run-time performance. 043 */ 044public final class DataReaderStrips extends AbstractImageDataReader { 045 046 private final int bitsPerPixel; 047 private final int compression; 048 private final int rowsPerStrip; 049 private final TiffPlanarConfiguration planarConfiguration; 050 private final ByteOrder byteOrder; 051 private int x; 052 private int y; 053 private final AbstractTiffImageData.Strips imageData; 054 055 public DataReaderStrips(final TiffDirectory directory, final AbstractPhotometricInterpreter photometricInterpreter, final int bitsPerPixel, 056 final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, final int height, 057 final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, final int rowsPerStrip, 058 final AbstractTiffImageData.Strips imageData) { 059 super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration); 060 061 this.bitsPerPixel = bitsPerPixel; 062 this.compression = compression; 063 this.rowsPerStrip = rowsPerStrip; 064 this.planarConfiguration = planarConfiguration; 065 this.imageData = imageData; 066 this.byteOrder = byteOrder; 067 } 068 069 private void interpretStrip(final ImageBuilder imageBuilder, final byte[] bytes, final int pixelsPerStrip, final int yLimit) 070 throws ImagingException, IOException { 071 if (y >= yLimit) { 072 return; 073 } 074 075 // changes added March 2020 076 if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) { 077 int k = 0; 078 int nRows = pixelsPerStrip / width; 079 if (y + nRows > yLimit) { 080 nRows = yLimit - y; 081 } 082 final int i0 = y; 083 final int i1 = y + nRows; 084 x = 0; 085 y += nRows; 086 final int[] samples = new int[1]; 087 final int[] b = unpackFloatingPointSamples(width, i1 - i0, width, bytes, bitsPerPixel, byteOrder); 088 089 for (int i = i0; i < i1; i++) { 090 for (int j = 0; j < width; j++) { 091 samples[0] = b[k]; 092 k += samplesPerPixel; 093 photometricInterpreter.interpretPixel(imageBuilder, samples, j, i); 094 } 095 } 096 097 return; 098 } 099 100 // changes added May 2012 101 // In the original implementation, a general-case bit reader called 102 // getSamplesAsBytes is used to retrieve the samples (raw data values) 103 // for each pixel in the strip. These samples are then passed into a 104 // photogrammetric interpreter that converts them to ARGB pixel values 105 // and stores them in the image. Because the bit-reader must handle 106 // a large number of formats, it involves several conditional 107 // branches that must be executed each time a pixel is read. 108 // Depending on the size of an image, the same evaluations must be 109 // executed redundantly thousands and perhaps millions of times 110 // in order to process the complete collection of pixels. 111 // This code attempts to remove that redundancy by 112 // evaluating the format up-front and bypassing the general-format 113 // code for two commonly used data formats: the 8 bits-per-pixel 114 // and 24 bits-per-pixel cases. For these formats, the 115 // special case code achieves substantial reductions in image-loading 116 // time. In other cases, it simply falls through to the original code 117 // and continues to read the data correctly as it did in previous 118 // versions of this class. 119 // In addition to bypassing the getBytesForSample() method, 120 // the 24-bit case also implements a special block for RGB 121 // formatted images. To get a sense of the contributions of each 122 // optimization (removing getSamplesAsBytes and removing the 123 // photometric interpreter), consider the following results from tests 124 // conducted with large TIFF images using the 24-bit RGB format 125 // bypass getSamplesAsBytes: 67.5 % reduction 126 // bypass both optimizations: 77.2 % reduction 127 // 128 // 129 // Future Changes 130 // Both of the 8-bit and 24-bit blocks make the assumption that a strip 131 // always begins on x = 0 and that each strip exactly fills out the rows 132 // it contains (no half rows). The original code did not make this 133 // assumption, but the approach is consistent with the TIFF 6.0 spec 134 // (1992), 135 // and should probably be considered as an enhancement to the 136 // original general-case code block that remains from the original 137 // implementation. Taking this approach saves one conditional 138 // operation per pixel or about 5 percent of the total run time 139 // in the 8 bits/pixel case. 140 // verify that all samples are one byte in size 141 final boolean allSamplesAreOneByte = isHomogenous(8); 142 143 if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) { 144 int k = 0; 145 int nRows = pixelsPerStrip / width; 146 if (y + nRows > yLimit) { 147 nRows = yLimit - y; 148 } 149 final int i0 = y; 150 final int i1 = y + nRows; 151 x = 0; 152 y += nRows; 153 final int[] samples = new int[1]; 154 for (int i = i0; i < i1; i++) { 155 for (int j = 0; j < width; j++) { 156 samples[0] = bytes[k++] & 0xff; 157 photometricInterpreter.interpretPixel(imageBuilder, samples, j, i); 158 } 159 } 160 return; 161 } 162 if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte && photometricInterpreter instanceof PhotometricInterpreterRgb) { 163 int k = 0; 164 int nRows = pixelsPerStrip / width; 165 if (y + nRows > yLimit) { 166 nRows = yLimit - y; 167 } 168 final int i0 = y; 169 final int i1 = y + nRows; 170 x = 0; 171 y += nRows; 172 if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) { 173 applyPredictorToBlock(width, nRows, samplesPerPixel, bytes); 174 } 175 176 if (bitsPerPixel == 24) { 177 // 24 bit case, we don't mask the red byte because any 178 // sign-extended bits get covered by opacity mask 179 for (int i = i0; i < i1; i++) { 180 for (int j = 0; j < width; j++, k += 3) { 181 final int rgb = 0xff000000 | bytes[k] << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff; 182 imageBuilder.setRgb(j, i, rgb); 183 } 184 } 185 } else { 186 // 32 bit case, we don't mask the high byte because any 187 // sign-extended bits get shifted up and out of result 188 for (int i = i0; i < i1; i++) { 189 for (int j = 0; j < width; j++, k += 4) { 190 final int rgb = (bytes[k] & 0xff) << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff | bytes[k + 3] << 24; 191 imageBuilder.setRgb(j, i, rgb); 192 } 193 } 194 } 195 196 return; 197 } 198 199 // original code before May 2012 modification 200 // this logic will handle all cases not conforming to the 201 // special case handled above 202 try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) { 203 204 int[] samples = Allocator.intArray(bitsPerSampleLength); 205 resetPredictor(); 206 for (int i = 0; i < pixelsPerStrip; i++) { 207 getSamplesAsBytes(bis, samples); 208 209 if (x < width) { 210 samples = applyPredictor(samples); 211 212 photometricInterpreter.interpretPixel(imageBuilder, samples, x, y); 213 } 214 215 x++; 216 if (x >= width) { 217 x = 0; 218 resetPredictor(); 219 y++; 220 bis.flushCache(); 221 if (y >= yLimit) { 222 break; 223 } 224 } 225 } 226 } 227 } 228 229 @Override 230 public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied) 231 throws IOException, ImagingException { 232 233 final Rectangle subImage; 234 if (subImageSpecification == null) { 235 // configure subImage to read entire image 236 subImage = new Rectangle(0, 0, width, height); 237 } else { 238 subImage = subImageSpecification; 239 } 240 241 // the legacy code is optimized to the reading of whole 242 // strips (except for the last strip in the image, which can 243 // be a partial). So create a working image with compatible 244 // dimensions and read that. Later on, the working image 245 // will be sub-imaged to the proper size. 246 // strip0 and strip1 give the indices of the strips containing 247 // the first and last rows of pixels in the subimage 248 final int strip0 = subImage.y / rowsPerStrip; 249 final int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip; 250 final int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip; 251 252 // the legacy code uses a member element "y" to keep track 253 // of the row index of the output image that is being processed 254 // by interpretStrip. y is set to zero before the first 255 // call to interpretStrip. y0 will be the index of the first row 256 // in the full image (the source image) that will be processed. 257 final int y0 = strip0 * rowsPerStrip; 258 final int yLimit = subImage.y - y0 + subImage.height; 259 260 // When processing a subimage, the workingBuilder height is set 261 // to be an integral multiple of the rowsPerStrip and 262 // the full width of the strips. So the working image may be larger than 263 // the specified size of the subimage. If necessary, the subimage 264 // is extracted from the workingBuilder at the end of this method. 265 // This approach avoids the need for the interpretStrips method 266 // to implement bounds checking for a subimage. 267 final ImageBuilder workingBuilder = new ImageBuilder(width, workingHeight, hasAlpha, isAlphaPreMultiplied); 268 269 // the following statement accounts for cases where planar configuration 270 // is not specified and the default (CHUNKY) is assumed. 271 final boolean interleaved = planarConfiguration != TiffPlanarConfiguration.PLANAR; 272 if (interleaved) { 273 // Pixel definitions are organized in an interleaved format 274 // For example, red-green-blue values for each pixel 275 // would appear contiguous in input sequence. 276 for (int strip = strip0; strip <= strip1; strip++) { 277 final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip; 278 final long rowsRemaining = height - strip * rowsPerStripLong; 279 final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong); 280 final long bytesPerRow = (bitsPerPixel * width + 7) / 8; 281 final long bytesPerStrip = rowsInThisStrip * bytesPerRow; 282 final long pixelsPerStrip = rowsInThisStrip * width; 283 284 final byte[] compressed = imageData.getImageData(strip).getData(); 285 286 if (compression == COMPRESSION_JPEG) { 287 final int yBlock = strip * rowsPerStrip; 288 final int yWork = yBlock - y0; 289 DataInterpreterJpeg.intepretBlock(directory, workingBuilder, 0, yWork, width, (int) rowsInThisStrip, compressed); 290 continue; 291 } 292 293 final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip); 294 295 interpretStrip(workingBuilder, decompressed, (int) pixelsPerStrip, yLimit); 296 } 297 } else { 298 // pixel definitions are organized in a 3 separate sections of input 299 // sequence. For example, red-green-blue values would be given as 300 // red values for all pixels, followed by green values for all pixels, 301 // etc. 302 if (compression == COMPRESSION_JPEG) { 303 throw new ImagingException("TIFF file in non-supported configuration: JPEG compression used in planar configuration."); 304 } 305 final int nStripsInPlane = imageData.getImageDataLength() / 3; 306 for (int strip = strip0; strip <= strip1; strip++) { 307 final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip; 308 final long rowsRemaining = height - strip * rowsPerStripLong; 309 final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong); 310 final long bytesPerRow = (bitsPerPixel * width + 7) / 8; 311 final long bytesPerStrip = rowsInThisStrip * bytesPerRow; 312 final long pixelsPerStrip = rowsInThisStrip * width; 313 314 final byte[] b = Allocator.byteArray((int) bytesPerStrip); 315 for (int iPlane = 0; iPlane < 3; iPlane++) { 316 final int planeStrip = iPlane * nStripsInPlane + strip; 317 final byte[] compressed = imageData.getImageData(planeStrip).getData(); 318 final byte[] decompressed = decompress(compressed, compression, (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 && subImage.y == y0 && subImage.width == width && subImage.height == workingHeight) { 330 // the subimage exactly matches the ImageBuilder bounds 331 // so we can return that. 332 return workingBuilder; 333 } 334 return workingBuilder.getSubset(subImage.x, subImage.y - y0, subImage.width, subImage.height); 335 } 336 337 @Override 338 public AbstractTiffRasterData readRasterData(final Rectangle subImage) throws ImagingException, IOException { 339 switch (sampleFormat) { 340 case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT: 341 return readRasterDataFloat(subImage); 342 case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER: 343 return readRasterDataInt(subImage); 344 default: 345 throw new ImagingException("Unsupported sample format, value=" + sampleFormat); 346 } 347 } 348 349 private AbstractTiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImagingException, IOException { 350 final int xRaster; 351 final int yRaster; 352 final int rasterWidth; 353 final int rasterHeight; 354 if (subImage != null) { 355 xRaster = subImage.x; 356 yRaster = subImage.y; 357 rasterWidth = subImage.width; 358 rasterHeight = subImage.height; 359 } else { 360 xRaster = 0; 361 yRaster = 0; 362 rasterWidth = width; 363 rasterHeight = height; 364 } 365 366 final float[] rasterDataFloat = Allocator.floatArray(rasterWidth * rasterHeight * samplesPerPixel); 367 368 // the legacy code is optimized to the reading of whole 369 // strips (except for the last strip in the image, which can 370 // be a partial). So create a working image with compatible 371 // dimensions and read that. Later on, the working image 372 // will be sub-imaged to the proper size. 373 // strip0 and strip1 give the indices of the strips containing 374 // the first and last rows of pixels in the subimage 375 final int strip0 = yRaster / rowsPerStrip; 376 final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip; 377 378 for (int strip = strip0; strip <= strip1; strip++) { 379 final int yStrip = strip * rowsPerStrip; 380 final int rowsRemaining = height - yStrip; 381 final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip); 382 final int bytesPerRow = (bitsPerPixel * width + 7) / 8; 383 final int bytesPerStrip = rowsInThisStrip * bytesPerRow; 384 385 final byte[] compressed = imageData.getImageData(strip).getData(); 386 final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip); 387 388 final int[] blockData = unpackFloatingPointSamples(width, rowsInThisStrip, width, decompressed, bitsPerPixel, byteOrder); 389 transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); 390 } 391 return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); 392 } 393 394 private AbstractTiffRasterData readRasterDataInt(final Rectangle subImage) throws ImagingException, IOException { 395 final int xRaster; 396 final int yRaster; 397 final int rasterWidth; 398 final int rasterHeight; 399 if (subImage != null) { 400 xRaster = subImage.x; 401 yRaster = subImage.y; 402 rasterWidth = subImage.width; 403 rasterHeight = subImage.height; 404 } else { 405 xRaster = 0; 406 yRaster = 0; 407 rasterWidth = width; 408 rasterHeight = height; 409 } 410 411 final int[] rasterDataInt = Allocator.intArray(rasterWidth * rasterHeight); 412 413 // the legacy code is optimized to the reading of whole 414 // strips (except for the last strip in the image, which can 415 // be a partial). So create a working image with compatible 416 // dimensions and read that. Later on, the working image 417 // will be sub-imaged to the proper size. 418 // strip0 and strip1 give the indices of the strips containing 419 // the first and last rows of pixels in the subimage 420 final int strip0 = yRaster / rowsPerStrip; 421 final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip; 422 423 for (int strip = strip0; strip <= strip1; strip++) { 424 final int yStrip = strip * rowsPerStrip; 425 final int rowsRemaining = height - yStrip; 426 final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip); 427 final int bytesPerRow = (bitsPerPixel * width + 7) / 8; 428 final int bytesPerStrip = rowsInThisStrip * bytesPerRow; 429 430 final byte[] compressed = imageData.getImageData(strip).getData(); 431 final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip); 432 final int[] blockData = unpackIntSamples(width, rowsInThisStrip, width, decompressed, predictor, bitsPerPixel, byteOrder); 433 transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt); 434 } 435 return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt); 436 } 437}