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.TiffElement.DataElement; 029import org.apache.commons.imaging.formats.tiff.TiffImageData; 030import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; 031import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb; 032 033public final class DataReaderTiled extends ImageDataReader { 034 035 private final int tileWidth; 036 private final int tileLength; 037 038 private final int bitsPerPixel; 039 040 private final int compression; 041 private final ByteOrder byteOrder; 042 043 private final TiffImageData.Tiles imageData; 044 045 public DataReaderTiled(final TiffDirectory directory, 046 final PhotometricInterpreter photometricInterpreter, final int tileWidth, 047 final int tileLength, final int bitsPerPixel, final int[] bitsPerSample, 048 final int predictor, final int samplesPerPixel, final int width, final int height, 049 final int compression, final ByteOrder byteOrder, final TiffImageData.Tiles imageData) { 050 super(directory, photometricInterpreter, bitsPerSample, predictor, 051 samplesPerPixel, width, height); 052 053 this.tileWidth = tileWidth; 054 this.tileLength = tileLength; 055 056 this.bitsPerPixel = bitsPerPixel; 057 this.compression = compression; 058 059 this.imageData = imageData; 060 this.byteOrder = byteOrder; 061 } 062 063 private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes, 064 final int startX, final int startY, final int xLimit, final int yLimit) throws ImageReadException, IOException { 065 // changes introduced May 2012 066 // The following block of code implements changes that 067 // reduce image loading time by using special-case processing 068 // instead of the general-purpose logic from the original 069 // implementation. For a detailed discussion, see the comments for 070 // a similar treatment in the DataReaderStrip class 071 // 072 073 // verify that all samples are one byte in size 074 final boolean allSamplesAreOneByte = isHomogenous(8); 075 076 if (predictor != 2 && bitsPerPixel == 24 && allSamplesAreOneByte) { 077 int k = 0; 078 final int i0 = startY; 079 int i1 = startY + tileLength; 080 if (i1 > yLimit) { 081 // the tile is padded past bottom of image 082 i1 = yLimit; 083 } 084 final int j0 = startX; 085 int j1 = startX + tileWidth; 086 if (j1 > xLimit) { 087 // the tile is padded to beyond the tile width 088 j1 = xLimit; 089 } 090 if (photometricInterpreter instanceof PhotometricInterpreterRgb) { 091 for (int i = i0; i < i1; i++) { 092 k = (i - i0) * tileWidth * 3; 093 for (int j = j0; j < j1; j++, k += 3) { 094 final int rgb = 0xff000000 095 | (((bytes[k] << 8) | (bytes[k + 1] & 0xff)) << 8) 096 | (bytes[k + 2] & 0xff); 097 imageBuilder.setRGB(j, i, rgb); 098 } 099 } 100 } else { 101 final int[] samples = new int[3]; 102 for (int i = i0; i < i1; i++) { 103 k = (i - i0) * tileWidth * 3; 104 for (int j = j0; j < j1; j++) { 105 samples[0] = bytes[k++] & 0xff; 106 samples[1] = bytes[k++] & 0xff; 107 samples[2] = bytes[k++] & 0xff; 108 photometricInterpreter.interpretPixel(imageBuilder, 109 samples, j, i); 110 } 111 } 112 } 113 return; 114 } 115 116 // End of May 2012 changes 117 118 try (final BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) { 119 120 final int pixelsPerTile = tileWidth * tileLength; 121 122 int tileX = 0; 123 int tileY = 0; 124 125 int[] samples = new int[bitsPerSampleLength]; 126 resetPredictor(); 127 for (int i = 0; i < pixelsPerTile; i++) { 128 129 final int x = tileX + startX; 130 final int y = tileY + startY; 131 132 getSamplesAsBytes(bis, samples); 133 134 if ((x < xLimit) && (y < yLimit)) { 135 samples = applyPredictor(samples); 136 photometricInterpreter.interpretPixel(imageBuilder, samples, x, y); 137 } 138 139 tileX++; 140 141 if (tileX >= tileWidth) { 142 tileX = 0; 143 resetPredictor(); 144 tileY++; 145 bis.flushCache(); 146 if (tileY >= tileLength) { 147 break; 148 } 149 } 150 151 } 152 } 153 } 154 155 @Override 156 public void readImageData(final ImageBuilder imageBuilder) 157 throws ImageReadException, IOException { 158 final int bitsPerRow = tileWidth * bitsPerPixel; 159 final int bytesPerRow = (bitsPerRow + 7) / 8; 160 final int bytesPerTile = bytesPerRow * tileLength; 161 int x = 0; 162 int y = 0; 163 164 for (final DataElement tile2 : imageData.tiles) { 165 final byte[] compressed = tile2.getData(); 166 167 final byte[] decompressed = decompress(compressed, compression, 168 bytesPerTile, tileWidth, tileLength); 169 170 interpretTile(imageBuilder, decompressed, x, y, width, height); 171 172 x += tileWidth; 173 if (x >= width) { 174 x = 0; 175 y += tileLength; 176 if (y >= height) { 177 break; 178 } 179 } 180 181 } 182 } 183 184 @Override 185 public BufferedImage readImageData(final Rectangle subImage) 186 throws ImageReadException, IOException { 187 final int bitsPerRow = tileWidth * bitsPerPixel; 188 final int bytesPerRow = (bitsPerRow + 7) / 8; 189 final int bytesPerTile = bytesPerRow * tileLength; 190 int x = 0; 191 int y = 0; 192 193 // tileWidth is the width of the tile 194 // tileLength is the height of the tile 195 final int col0 = subImage.x / tileWidth; 196 final int col1 = (subImage.x + subImage.width - 1) / tileWidth; 197 final int row0 = subImage.y / tileLength; 198 final int row1 = (subImage.y + subImage.height - 1) / tileLength; 199 200 final int nCol = col1 - col0 + 1; 201 final int nRow = row1 - row0 + 1; 202 final int workingWidth = nCol * tileWidth; 203 final int workingHeight = nRow * tileLength; 204 205 final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth; 206 207 final int x0 = col0 * tileWidth; 208 final int y0 = row0 * tileLength; 209 210 final ImageBuilder workingBuilder = 211 new ImageBuilder(workingWidth, workingHeight, false); 212 213 for (int iRow = row0; iRow <= row1; iRow++) { 214 for (int iCol = col0; iCol <= col1; iCol++) { 215 final int tile = iRow * nColumnsOfTiles + iCol; 216 final byte[] compressed = imageData.tiles[tile].getData(); 217 final byte[] decompressed = decompress(compressed, compression, 218 bytesPerTile, tileWidth, tileLength); 219 x = iCol * tileWidth - x0; 220 y = iRow * tileLength - y0; 221 interpretTile(workingBuilder, decompressed, x, y, workingWidth, workingHeight); 222 } 223 } 224 225 if (subImage.x == x0 226 && subImage.y == y0 227 && subImage.width == workingWidth 228 && subImage.height == workingHeight) { 229 return workingBuilder.getBufferedImage(); 230 } 231 return workingBuilder.getSubimage( 232 subImage.x - x0, 233 subImage.y - y0, 234 subImage.width, 235 subImage.height); 236 } 237 238}