Author: damjan
Date: Fri Jun  1 15:59:00 2012
New Revision: 1345260

URL: http://svn.apache.org/viewvc?rev=1345260&view=rev
Log:
Add a streamlined TIFF reader that reduces load time by a factor of 5.

Jira issue key: IMAGING-69
Submitted by: Gary Lucas <gwlucas at sonalysts dot com>


Modified:
    
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/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=1345260&r1=1345259&r2=1345260&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
 Fri Jun  1 15:59:00 2012
@@ -25,6 +25,7 @@ import org.apache.commons.imaging.common
 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
 import org.apache.commons.imaging.formats.tiff.TiffImageData;
 import 
org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
+import 
org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
 
 public final class DataReaderStrips extends DataReader {
 
@@ -52,12 +53,122 @@ public final class DataReaderStrips exte
 
     private void interpretStrip(ImageBuilder imageBuilder, byte bytes[],
             int pixels_per_strip) throws ImageReadException, IOException {
-        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
-        BitInputStream bis = new BitInputStream(bais, byteOrder);
-
         if (y >= height) {
             return;
         }
+
+        // changes added May 2012
+        // In the original implementation, a general-case bit reader called
+        // getSamplesAsBytes is used to retrieve the samples (raw data values)
+        // for each pixel in the strip. These samples are then passed into a
+        // photogrammetric interpreter that converts them to ARGB pixel values
+        // and stores them in the image. Because the bit-reader must handle
+        // a large number of formats, it involves several conditional
+        // branches that must be executed each time a pixel is read.
+        // Depending on the size of an image, the same evaluations must be
+        // executed redundantly thousands and perhaps millions of times
+        // in order to process the complete collection of pixels.
+        // This code attempts to remove that redundancy by
+        // evaluating the format up-front and bypassing the general-format
+        // code for two commonly used data formats: the 8 bits-per-pixel
+        // and 24 bits-per-pixel cases. For these formats, the
+        // special case code achieves substantial reductions in image-loading
+        // time. In other cases, it simply falls through to the original code
+        // and continues to read the data correctly as it did in previous
+        // versions of this class.
+        // In addition to bypassing the getBytesForSample() method,
+        // the 24-bit case also implements a special block for RGB
+        // formatted images. To get a sense of the contributions of each
+        // optimization (removing getSamplesAsBytes and removing the
+        // photometric interpreter), consider the following results from tests
+        // conducted with large TIFF images using the 24-bit RGB format
+        // bypass getSamplesAsBytes: 67.5 % reduction
+        // bypass both optimizations: 77.2 % reduction
+        //
+        //
+        // Future Changes
+        // Both of the 8-bit and 24-bit blocks make the assumption that a strip
+        // always begins on x = 0 and that each strip exactly fills out the 
rows
+        // it contains (no half rows). The original code did not make this
+        // assumption, but the approach is consistent with the TIFF 6.0 spec
+        // (1992),
+        // and should probably be considered as an enhancement to the
+        // original general-case code block that remains from the original
+        // implementation. Taking this approach saves one conditional
+        // operation per pixel or about 5 percent of the total run time
+        // in the 8 bits/pixel case.
+
+        // verify that all samples are one byte in size
+        boolean allSamplesAreOneByte = true;
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            if (bitsPerSample[i] != 8) {
+                allSamplesAreOneByte = false;
+                break;
+            }
+        }
+
+        if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) {
+            int k = 0;
+            int nRows = pixels_per_strip / width;
+            if (y + nRows > height) {
+                nRows = height - y;
+            }
+            int i0 = y;
+            int i1 = y + nRows;
+            x = 0;
+            y += nRows;
+            int[] samples = new int[1];
+            for (int i = i0; i < i1; i++) {
+                for (int j = 0; j < width; j++) {
+                    samples[0] = bytes[k++] & 0xff;
+                    photometricInterpreter.interpretPixel(imageBuilder,
+                            samples, j, i);
+                }
+            }
+            return;
+        } else if (predictor != 2 && bitsPerPixel == 24 && 
allSamplesAreOneByte) {
+            int k = 0;
+            int nRows = pixels_per_strip / width;
+            if (y + nRows > height) {
+                nRows = height - y;
+            }
+            int i0 = y;
+            int i1 = y + nRows;
+            x = 0;
+            y += nRows;
+            if (photometricInterpreter instanceof PhotometricInterpreterRgb) {
+                for (int i = i0; i < i1; i++) {
+                    for (int j = 0; j < width; j++, k += 3) {
+                        int rgb = 0xff000000
+                                | (((bytes[k] << 8) | (bytes[k + 1] & 0xff)) 
<< 8)
+                                | (bytes[k + 2] & 0xff);
+                        imageBuilder.setRGB(j, i, rgb);
+                    }
+                }
+            } else {
+                int samples[] = new int[3];
+                for (int i = i0; i < i1; i++) {
+                    for (int j = 0; j < width; j++) {
+                        samples[0] = bytes[k++] & 0xff;
+                        samples[1] = bytes[k++] & 0xff;
+                        samples[2] = bytes[k++] & 0xff;
+                        photometricInterpreter.interpretPixel(imageBuilder,
+                                samples, j, i);
+                    }
+                }
+            }
+
+            return;
+        }
+
+        // ------------------------------------------------------------
+        // original code before May 2012 modification
+        // this logic will handle all cases not conforming to the
+        // special case handled above
+
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        BitInputStream bis = new BitInputStream(bais, byteOrder);
+
         int[] samples = new int[bitsPerSample.length];
         resetPredictor();
         for (int i = 0; i < pixels_per_strip; i++) {

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=1345260&r1=1345259&r2=1345260&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
 Fri Jun  1 15:59:00 2012
@@ -25,6 +25,7 @@ import org.apache.commons.imaging.common
 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
 import org.apache.commons.imaging.formats.tiff.TiffImageData;
 import 
org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
+import 
org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
 
 public final class DataReaderTiled extends DataReader {
 
@@ -58,6 +59,65 @@ public final class DataReaderTiled exten
 
     private void interpretTile(ImageBuilder imageBuilder, byte bytes[],
             int startX, int startY) throws ImageReadException, IOException {
+        // changes introduced May 2012
+        // The following block of code implements changes that
+        // reduce image loading time by using special-case processing
+        // instead of the general-purpose logic from the original
+        // implementation. For a detailed discussion, see the comments for
+        // a similar treatment in the DataReaderStrip class
+        //
+
+        // verify that all samples are one byte in size
+        boolean allSamplesAreOneByte = true;
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            if (bitsPerSample[i] != 8) {
+                allSamplesAreOneByte = false;
+                break;
+            }
+        }
+
+        if (predictor != 2 && bitsPerPixel == 24 && allSamplesAreOneByte) {
+            int k = 0;
+            int i0 = startY;
+            int i1 = startY + tileLength;
+            if (i1 > height) {
+                // the tile is padded past bottom of image
+                i1 = height - startY;
+            }
+            int j0 = startX;
+            int j1 = startX + tileWidth;
+            if (j1 > width) {
+                // the tile is padded to beyond the tile width
+                j1 = width - startX;
+            }
+            if (photometricInterpreter instanceof PhotometricInterpreterRgb) {
+                for (int i = i0; i < i1; i++) {
+                    k = (i - i0) * tileWidth * 3;
+                    for (int j = j0; j < j1; j++, k += 3) {
+                        int rgb = 0xff000000
+                                | (((bytes[k] << 8) | (bytes[k + 1] & 0xff)) 
<< 8)
+                                | (bytes[k + 2] & 0xff);
+                        imageBuilder.setRGB(j, i, rgb);
+                    }
+                }
+            } else {
+                int samples[] = new int[3];
+                for (int i = i0; i < i1; i++) {
+                    k = (i - i0) * tileWidth * 3;
+                    for (int j = j0; j < j1; j++) {
+                        samples[0] = bytes[k++] & 0xff;
+                        samples[1] = bytes[k++] & 0xff;
+                        samples[2] = bytes[k++] & 0xff;
+                        photometricInterpreter.interpretPixel(imageBuilder,
+                                samples, j, i);
+                    }
+                }
+            }
+            return;
+        }
+
+        // End of May 2012 changes
+
         ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
         BitInputStream bis = new BitInputStream(bais, byteOrder);
 


Reply via email to