You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by da...@apache.org on 2012/12/12 06:41:38 UTC

svn commit: r1420531 - in /commons/proper/imaging/trunk/src: changes/ main/java/org/apache/commons/imaging/common/ main/java/org/apache/commons/imaging/formats/tiff/ main/java/org/apache/commons/imaging/formats/tiff/constants/ main/java/org/apache/comm...

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);
+        }
+    }
+
 }