Author: damjan Date: Wed Dec 12 05:41:35 2012 New Revision: 1420531 URL: http://svn.apache.org/viewvc?rev=1420531&view=rev Log: Add ability to load partial TIFF images
Jira issue key: IMAGING-94 Submitted by: Gary Lucas <gwlucas at sonalysts dot com> Modified: commons/proper/imaging/trunk/src/changes/changes.xml commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageData.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageParser.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/constants/TiffConstants.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderStrips.java commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java Modified: commons/proper/imaging/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/changes/changes.xml?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/changes/changes.xml (original) +++ commons/proper/imaging/trunk/src/changes/changes.xml Wed Dec 12 05:41:35 2012 @@ -248,6 +248,9 @@ The <action> type attribute can be add,u <action issue="IMAGING-99" dev="damjan" type="fix" due-to="st.h"> java.io.IOException: Could not read block </action> + <action issue="IMAGING-94" dev="damjan" type="add" due-to="gwlucas"> + Add ability to load partial TIFF images + </action> </release> </body> Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java Wed Dec 12 05:41:35 2012 @@ -14,61 +14,195 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + + +/** + * Development notes: + * This class was introduced to the Apache Commons Imaging library in + * order to improve performance in building images. The setRGB method + * provided by this class represents a substantial improvement in speed + * compared to that of the BufferedImage class that was originally used + * in Apache Sanselan. + * This increase is attained because ImageBuilder is a highly specialized + * class that does not need to perform the general-purpose logic required + * for BufferedImage. If you need to modify this class to add new + * image formats or functionality, keep in mind that some of its methods + * are invoked literally millions of times when building an image. + * Since even the introduction of something as small as a single conditional + * inside of setRGB could result in a noticeable increase in the + * time to read a file, changes should be made with care. + * During development, I experimented with inlining the setRGB logic + * in some of the code that uses it. This approach did not significantly + * improve performance, leading me to speculate that the Java JIT compiler + * might have inlined the method at run time. Further investigation + * is required. + * + */ package org.apache.commons.imaging.common; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBufferInt; import java.awt.image.DirectColorModel; +import java.awt.image.RasterFormatException; import java.awt.image.WritableRaster; import java.util.Properties; +/** + * A utility class primary intended for storing data obtained by reading + * image files. + */ public class ImageBuilder { private final int[] data; private final int width; private final int height; private final boolean hasAlpha; + /** + * Construct an ImageBuilder instance + * @param width the width of the image to be built + * @param height the height of the image to be built + * @param hasAlpha indicates whether the image has an alpha channel + * (the selection of alpha channel does not change the memory + * requirements for the ImageBuilder or resulting BufferedImage. + */ public ImageBuilder(final int width, final int height, final boolean hasAlpha) { + if(width<=0){ + throw new RasterFormatException("zero or negative width value"); + } + if(height<=0){ + throw new RasterFormatException("zero or negative height value"); + } + data = new int[width * height]; this.width = width; this.height = height; this.hasAlpha = hasAlpha; } + /** + * Get the width of the ImageBuilder pixel field + * @return a positive integer + */ public int getWidth() { return width; } + /** + * Get the height of the ImageBuilder pixel field + * @return a positive integer + */ public int getHeight() { return height; } + /** + * Get the RGB or ARGB value for the pixel at the position (x,y) + * within the image builder pixel field. For performance reasons + * no bounds checking is applied. + * @param x the X coordinate of the pixel to be read + * @param y the Y coordinate of the pixel to be read + * @return + */ public int getRGB(final int x, final int y) { final int rowOffset = y * width; return data[rowOffset + x]; } + /** + * Set the RGB or ARGB value for the pixel at position (x,y) + * within the image builder pixel field. For performance reasons, + * no bounds checking is applied. + * @param x the X coordinate of the pixel to be set + * @param y the Y coordinate of the pixel to be set + * @param argb the RGB or ARGB value to be stored. + */ public void setRGB(final int x, final int y, final int argb) { final int rowOffset = y * width; data[rowOffset + x] = argb; } + /** + * Create a BufferedImage using the data stored in the ImageBuilder. + * @return a valid BufferedImage. + */ public BufferedImage getBufferedImage() { + return makeBufferedImage(data, width, height, hasAlpha); + } + + /** + * Gets a subimage from the ImageBuilder using the specified parameters. + * If the parameters specify a rectangular region that is not entirely + * contained within the bounds defined by the ImageBuilder, this method will + * throw a RasterFormatException. This runtime-exception behavior + * is consistent with the behavior of the getSubimage method + * provided by BufferdImage. + * @param x the X coordinate of the upper-left corner of the + * specified rectangular region + * @param y the Y coordinate of the upper-left corner of the + * specified rectangular region + * @param w the width of the specified rectangular region + * @param h the height of the specified rectangular region + * @return a BufferedImage that constructed from the deta within the + * specified rectangular region + * @throws RasterFormatException f the specified area is not contained + * within this ImageBuilder + */ + public BufferedImage getSubimage(int x, int y, int w, int h) + { + if (w <= 0) { + throw new RasterFormatException("negative or zero subimage width"); + } + if (h <= 0) { + throw new RasterFormatException("negative or zero subimage height"); + } + if (x < 0 || x >= width) { + throw new RasterFormatException("subimage x is outside raster"); + } + if (x + w > width) { + throw new RasterFormatException( + "subimage (x+width) is outside raster"); + } + if (y < 0 || y >= height) { + throw new RasterFormatException("subimage y is outside raster"); + } + if (y + h > height) { + throw new RasterFormatException( + "subimage (y+height) is outside raster"); + } + + + // Transcribe the data to an output image array + int[] argb = new int[w * h]; + int k = 0; + for (int iRow = 0; iRow < h; iRow++) { + int dIndex = (iRow + y) * width + x; + System.arraycopy(this.data, dIndex, argb, k, w); + k += w; + + } + + return makeBufferedImage(argb, w, h, hasAlpha); + + } + + private BufferedImage makeBufferedImage( + int[] argb, int w, int h, boolean useAlpha) + { ColorModel colorModel; WritableRaster raster; - final DataBufferInt buffer = new DataBufferInt(data, width * height); - if (hasAlpha) { + final DataBufferInt buffer = new DataBufferInt(argb, w * h); + if (useAlpha) { colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); - raster = WritableRaster.createPackedRaster(buffer, width, height, - width, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff, + raster = WritableRaster.createPackedRaster(buffer, w, h, + w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }, null); } else { colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff); - raster = WritableRaster.createPackedRaster(buffer, width, height, - width, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff }, + raster = WritableRaster.createPackedRaster(buffer, w, h, + w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff }, null); } return new BufferedImage(colorModel, raster, Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageData.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageData.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageData.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageData.java Wed Dec 12 05:41:35 2012 @@ -62,6 +62,26 @@ public abstract class TiffImageData { predictor, samplesPerPixel, width, height, compression, byteOrder, this); } + + /** + * Get the width of individual tiles. Note that if the overall + * image width is not a multiple of the tile width, then + * the last column of tiles may extend beyond the image width. + * @return an integer value greater than zero + */ + public int getTileWidth(){ + return tileWidth; + } + + /** + * Get the height of individual tiles. Note that if the overall + * image height is not a multiple of the tile height, then + * the last row of tiles may extend beyond the image height. + * @return an integer value greater than zero + */ + public int getTileHeight(){ + return tileLength; + } // public TiffElement[] getElements() // { Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageParser.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageParser.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageParser.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/TiffImageParser.java Wed Dec 12 05:41:35 2012 @@ -17,6 +17,7 @@ package org.apache.commons.imaging.formats.tiff; import java.awt.Dimension; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -435,6 +436,40 @@ public class TiffImageParser extends Ima return result; } + /** + * Gets a buffered image specified by the byte source. + * The TiffImageParser class features support for a number of options that + * are unique to the TIFF format. These options can be specified by + * supplying the appropriate parameters using the keys from the + * TiffConstants class and the params argument for this method. + * <h4>Loading Partial Images</h4> + * The TIFF parser includes support for loading partial images without + * committing significantly more memory resources than are necessary + * to store the image. This feature is useful for conserving memory + * in applications that require a relatively small sub image from a + * very large TIFF file. The specifications for partial images are + * as follows: + * <code><pre> + * HashMap<String, Object> params = new HashMap<String, Object>(); + * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, new Integer(x)); + * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, new Integer(y)); + * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, new Integer(width)); + * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, new Integer(height)); + * </pre></code> + * Note that the arguments x, y, width, and height must specify a + * valid rectangular region that is fully contained within the + * source TIFF image. + * @param byteSource A valid instance of ByteSource + * @param params Optional instructions for special-handling or + * interpretation of the input data (null objects are permitted and + * must be supported by implementations). + * @return A valid instance of BufferedImage. + * @throws ImageReadException In the event that the the specified + * content does not conform to the format of the specific parser + * implementation. + * @throws IOException In the event of unsuccessful read or + * access operation. + */ @Override public BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String,Object> params) throws ImageReadException, IOException { @@ -470,8 +505,67 @@ public class TiffImageParser extends Ima return results; } + private Integer getIntegerParameter( + String key, Map<String, Object>params) + throws ImageReadException + { + + if(!params.containsKey(key)) + return null; + + Object obj = params.get(key); + + if(obj instanceof Integer){ + return (Integer)obj; + } + throw new ImageReadException( + "Non-Integer parameter "+key); + } + + private Rectangle checkForSubImage( + Map<String, Object> params) + throws ImageReadException + { + Integer ix0, iy0, iwidth, iheight; + ix0 = getIntegerParameter( + TiffConstants.PARAM_KEY_SUBIMAGE_X, params); + iy0 = getIntegerParameter( + TiffConstants.PARAM_KEY_SUBIMAGE_Y, params); + iwidth = getIntegerParameter( + TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, params); + iheight = getIntegerParameter( + TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, params); + if (ix0 == null && iy0 == null && iwidth == null && iheight == null) + return null; + + StringBuilder sb = new StringBuilder(); + if (ix0 == null) + sb.append(" x0,"); + if (iy0 == null) + sb.append(" y0,"); + if (iwidth == null) + sb.append(" width,"); + if (iheight == null) + sb.append(" height,"); + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + throw new ImageReadException( + "Incomplete subimage parameters, missing" + + sb.toString()); + } + + int x0 = ix0.intValue(); + int y0 = iy0.intValue(); + int width = iwidth.intValue(); + int height = iheight.intValue(); + + return new Rectangle(x0, y0, width, height); + } + protected BufferedImage getBufferedImage(final TiffDirectory directory, - final ByteOrder byteOrder, final Map<String,Object> params) throws ImageReadException, IOException { + final ByteOrder byteOrder, final Map<String,Object> params) + throws ImageReadException, IOException + { final List<TiffField> entries = directory.entries; if (entries == null) { @@ -485,7 +579,43 @@ public class TiffImageParser extends Ima final int width = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true).getIntValue(); final int height = directory.findField( - TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true).getIntValue(); + TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true).getIntValue(); + Rectangle subImage = checkForSubImage(params); + if(subImage!=null){ + // Check for valid subimage specification. The following checks + // are consistent with BufferedImage.getSubimage() + if (subImage.width <= 0) { + throw new ImageReadException("negative or zero subimage width"); + } + if (subImage.height <= 0) { + throw new ImageReadException("negative or zero subimage height"); + } + if(subImage.x<0 || subImage.x>=width){ + throw new ImageReadException("subimage x is outside raster"); + } + if(subImage.x+subImage.width>width){ + throw new ImageReadException( + "subimage (x+width) is outside raster"); + } + if(subImage.y<0 || subImage.y>=height){ + throw new ImageReadException("subimage y is outside raster"); + } + if(subImage.y+subImage.height>height){ + throw new ImageReadException( + "subimage (y+height) is outside raster"); + } + + // if the subimage is just the same thing as the whole + // image, suppress the subimage processing + if(subImage.x==0 + && subImage.y==0 + && subImage.width==width + && subImage.height==height){ + subImage = null; + } + } + + int samplesPerPixel = 1; final TiffField samplesPerPixelField = directory .findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL); @@ -524,8 +654,7 @@ public class TiffImageParser extends Ima + bitsPerSample.length + ")"); } - final boolean hasAlpha = false; - final ImageBuilder imageBuilder = new ImageBuilder(width, height, hasAlpha); + final PhotometricInterpreter photometricInterpreter = getPhotometricInterpreter( directory, photometricInterpretation, bitsPerPixel, @@ -537,11 +666,18 @@ public class TiffImageParser extends Ima photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel, width, height, compression, byteOrder); - dataReader.readImageData(imageBuilder); + BufferedImage result = null; + if (subImage != null) { + result = dataReader.readImageData(subImage); + } else { + boolean hasAlpha = false; + ImageBuilder imageBuilder = new ImageBuilder(width, height, hasAlpha); + dataReader.readImageData(imageBuilder); + result = imageBuilder.getBufferedImage(); + } photometricInterpreter.dumpstats(); - - return imageBuilder.getBufferedImage(); + return result; } private PhotometricInterpreter getPhotometricInterpreter( Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/constants/TiffConstants.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/constants/TiffConstants.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/constants/TiffConstants.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/constants/TiffConstants.java Wed Dec 12 05:41:35 2012 @@ -66,4 +66,10 @@ public interface TiffConstants public static final int TIFF_FLAG_T4_OPTIONS_UNCOMPRESSED_MODE = 2; public static final int TIFF_FLAG_T4_OPTIONS_FILL = 4; public static final int TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE = 2; + + + public static final String PARAM_KEY_SUBIMAGE_X = "SUBIMAGE_X"; + public static final String PARAM_KEY_SUBIMAGE_Y = "SUBIMAGE_Y"; + public static final String PARAM_KEY_SUBIMAGE_WIDTH = "SUBIMAGE_WIDTH"; + public static final String PARAM_KEY_SUBIMAGE_HEIGHT = "SUBIMAGE_HEIGHT"; } Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java Wed Dec 12 05:41:35 2012 @@ -16,6 +16,8 @@ */ package org.apache.commons.imaging.formats.tiff.datareaders; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -61,6 +63,12 @@ public abstract class DataReader impleme public abstract void readImageData(ImageBuilder imageBuilder) throws ImageReadException, IOException; + + public abstract BufferedImage readImageData(Rectangle subImage) + throws ImageReadException, IOException; + + + /** * Reads samples and returns them in an int array. * @@ -200,5 +208,4 @@ public abstract class DataReader impleme "Tiff: unknown/unsupported compression: " + compression); } } - } Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderStrips.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderStrips.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderStrips.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderStrips.java Wed Dec 12 05:41:35 2012 @@ -16,6 +16,8 @@ */ package org.apache.commons.imaging.formats.tiff.datareaders; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -52,9 +54,12 @@ public final class DataReaderStrips exte this.byteOrder = byteOrder; } - private void interpretStrip(final ImageBuilder imageBuilder, final byte bytes[], - final int pixels_per_strip) throws ImageReadException, IOException { - if (y >= height) { + private void interpretStrip( + final ImageBuilder imageBuilder, + final byte bytes[], + final int pixels_per_strip, + final int yLimit) throws ImageReadException, IOException { + if (y >= yLimit) { return; } @@ -111,8 +116,8 @@ public final class DataReaderStrips exte if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) { int k = 0; int nRows = pixels_per_strip / width; - if (y + nRows > height) { - nRows = height - y; + if (y + nRows > yLimit) { + nRows = yLimit - y; } final int i0 = y; final int i1 = y + nRows; @@ -130,8 +135,8 @@ public final class DataReaderStrips exte } else if (predictor != 2 && bitsPerPixel == 24 && allSamplesAreOneByte) { int k = 0; int nRows = pixels_per_strip / width; - if (y + nRows > height) { - nRows = height - y; + if (y + nRows > yLimit) { + nRows = yLimit - y; } final int i0 = y; final int i1 = y + nRows; @@ -178,8 +183,8 @@ public final class DataReaderStrips exte if (x < width) { samples = applyPredictor(samples); - photometricInterpreter.interpretPixel(imageBuilder, samples, x, - y); + photometricInterpreter.interpretPixel( + imageBuilder, samples, x, y); } x++; @@ -188,7 +193,7 @@ public final class DataReaderStrips exte resetPredictor(); y++; bis.flushCache(); - if (y >= height) { + if (y >= yLimit) { break; } } @@ -213,9 +218,82 @@ public final class DataReaderStrips exte final byte decompressed[] = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip); - interpretStrip(imageBuilder, decompressed, (int) pixelsPerStrip); + interpretStrip( + imageBuilder, + decompressed, + (int) pixelsPerStrip, + height); } } + + + @Override + public BufferedImage readImageData(Rectangle subImage) + throws ImageReadException, IOException + { + // the legacy code is optimized to the reading of whole + // strips (except for the last strip in the image, which can + // be a partial). So create a working image with compatible + // dimensions and read that. Later on, the working image + // will be sub-imaged to the proper size. + + // strip0 and strip1 give the indices of the strips containing + // the first and last rows of pixels in the subimage + int strip0 = subImage.y / rowsPerStrip; + int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip; + int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip; + + + // the legacy code uses a member element "y" to keep track + // of the row index of the output image that is being processed + // by interpretStrip. y is set to zero before the first + // call to interpretStrip. y0 will be the index of the first row + // in the full image (the source image) that will be processed. + + int y0 = strip0 * rowsPerStrip; + int yLimit = subImage.y-y0+subImage.height; + + + // TO DO: we can probably save some processing by using yLimit instead + // or working + ImageBuilder workingBuilder = + new ImageBuilder(width, workingHeight, false); + + for (int strip = strip0; strip <= strip1; strip++) { + long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip; + long rowsRemaining = height - (strip * rowsPerStripLong); + long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong); + long bytesPerRow = (bitsPerPixel * width + 7) / 8; + long bytesPerStrip = rowsInThisStrip * bytesPerRow; + long pixelsPerStrip = rowsInThisStrip * width; + + byte compressed[] = imageData.strips[strip].getData(); + + byte decompressed[] = decompress(compressed, compression, + (int) bytesPerStrip, width, (int) rowsInThisStrip); + + interpretStrip( + workingBuilder, + decompressed, + (int) pixelsPerStrip, + yLimit); + } + + + if (subImage.x == 0 + && subImage.y == y0 + && subImage.width == width + && subImage.height == workingHeight) { + // the subimage exactly matches the ImageBuilder bounds + return workingBuilder.getBufferedImage(); + } else { + return workingBuilder.getSubimage( + subImage.x, + subImage.y - y0, + subImage.width, + subImage.height); + } + } } Modified: commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java URL: http://svn.apache.org/viewvc/commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java?rev=1420531&r1=1420530&r2=1420531&view=diff ============================================================================== --- commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java (original) +++ commons/proper/imaging/trunk/src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java Wed Dec 12 05:41:35 2012 @@ -16,6 +16,8 @@ */ package org.apache.commons.imaging.formats.tiff.datareaders; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -60,7 +62,7 @@ public final class DataReaderTiled exten } private void interpretTile(final ImageBuilder imageBuilder, final byte bytes[], - final int startX, final int startY) throws ImageReadException, IOException { + final int startX, final int startY, final int xLimit, final int yLimit) throws ImageReadException, IOException { // changes introduced May 2012 // The following block of code implements changes that // reduce image loading time by using special-case processing @@ -82,15 +84,15 @@ public final class DataReaderTiled exten int k = 0; final int i0 = startY; int i1 = startY + tileLength; - if (i1 > height) { + if (i1 > yLimit) { // the tile is padded past bottom of image - i1 = height; + i1 = yLimit; } final int j0 = startX; int j1 = startX + tileWidth; - if (j1 > width) { + if (j1 > xLimit) { // the tile is padded to beyond the tile width - j1 = width; + j1 = xLimit; } if (photometricInterpreter instanceof PhotometricInterpreterRgb) { for (int i = i0; i < i1; i++) { @@ -136,7 +138,7 @@ public final class DataReaderTiled exten getSamplesAsBytes(bis, samples); - if ((x < width) && (y < height)) { + if ((x < xLimit) && (y < yLimit)) { samples = applyPredictor(samples); photometricInterpreter.interpretPixel(imageBuilder, samples, x, y); @@ -171,7 +173,7 @@ public final class DataReaderTiled exten final byte decompressed[] = decompress(compressed, compression, bytesPerTile, tileWidth, tileLength); - interpretTile(imageBuilder, decompressed, x, y); + interpretTile(imageBuilder, decompressed, x, y, width, height); x += tileWidth; if (x >= width) { @@ -184,4 +186,60 @@ public final class DataReaderTiled exten } } + + @Override + public BufferedImage readImageData(final Rectangle subImage) + throws ImageReadException, IOException + { + int bitsPerRow = tileWidth * bitsPerPixel; + int bytesPerRow = (bitsPerRow + 7) / 8; + int bytesPerTile = bytesPerRow * tileLength; + int x = 0, y = 0; + + // tileWidth is the width of the tile + // tileLength is the height of the tile + int col0 = subImage.x / tileWidth; + int col1 = (subImage.x + subImage.width - 1) / tileWidth; + int row0 = subImage.y / tileLength; + int row1 = (subImage.y + subImage.height - 1) / tileLength; + + int nCol = col1 - col0 + 1; + int nRow = row1 - row0 + 1; + int workingWidth = nCol * tileWidth; + int workingHeight = nRow * tileLength; + + int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth; + + int x0 = col0*tileWidth; + int y0 = row0*tileLength; + + ImageBuilder workingBuilder = + new ImageBuilder(workingWidth, workingHeight, false); + + for (int iRow = row0; iRow <= row1; iRow++) { + for (int iCol = col0; iCol <= col1; iCol++) { + int tile = iRow * nColumnsOfTiles+iCol; + byte compressed[] = imageData.tiles[tile].getData(); + byte decompressed[] = decompress(compressed, compression, + bytesPerTile, tileWidth, tileLength); + x = iCol * tileWidth - x0; + y = iRow * tileLength - y0; + interpretTile(workingBuilder, decompressed, x, y, workingWidth, workingHeight); + } + } + + if (subImage.x == x0 + && subImage.y == y0 + && subImage.width == workingWidth + && subImage.height == workingHeight) { + return workingBuilder.getBufferedImage(); + }else{ + return workingBuilder.getSubimage( + subImage.x - x0, + subImage.y - y0, + subImage.width, + subImage.height); + } + } + }