You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/12/30 05:24:40 UTC

[sis] branch geoapi-4.0 updated (cbe0457 -> 3c3b0d8)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from cbe0457  Add a safety check for tile size before to reuse a tile from the cache.
     new 02e2210  Layout TIFF "image description" on a single line.
     new 3c3b0d8  Rewrite the horizontal predictor for fixing a bug in the handling of types other than byte. Contains an opportunistic renaming of `setInput(long, long)` method as `setInputRegion(…)`.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/sis/internal/util/Strings.java |  28 +-
 .../org/apache/sis/internal/util/package-info.java |   2 +-
 .../sis/internal/storage/inflater/CCITTRLE.java    |   8 +-
 .../storage/inflater/CompressionChannel.java       |   6 +-
 .../storage/inflater/HorizontalPredictor.java      | 443 +++++++++++----------
 .../sis/internal/storage/inflater/Inflater.java    |  12 +-
 .../apache/sis/internal/storage/inflater/LZW.java  |   8 +-
 .../sis/internal/storage/inflater/PackBits.java    |   8 +-
 .../internal/storage/inflater/PixelChannel.java    |   4 +-
 .../storage/inflater/PredictorChannel.java         |  11 +-
 .../apache/sis/internal/storage/inflater/ZIP.java  |   8 +-
 .../internal/storage/inflater/package-info.java    |   2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |   3 +-
 .../internal/storage/inflater/CCITTRLETest.java    |   2 +-
 14 files changed, 301 insertions(+), 244 deletions(-)

[sis] 01/02: Layout TIFF "image description" on a single line.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 02e2210684c3b41ea2b5da7730bc88929e21de84
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Dec 29 18:01:47 2021 +0100

    Layout TIFF "image description" on a single line.
---
 .../java/org/apache/sis/internal/util/Strings.java | 28 +++++++++++++++++++++-
 .../org/apache/sis/internal/util/package-info.java |  2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |  3 ++-
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
index 925a528..a07b148 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
@@ -21,6 +21,7 @@ import java.util.Formatter;
 import java.util.FormattableFlags;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Classes;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
 
@@ -30,7 +31,7 @@ import org.apache.sis.util.CharSequences;
  * Most of those methods are for {@link Object#toString()} implementations.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.3
  * @module
  */
@@ -272,6 +273,31 @@ public final class Strings extends Static {
     }
 
     /**
+     * Concatenates a potentially multi-lines text to a single line.
+     * White spaces at the beginning and end of each line are removed.
+     *
+     * @param  delimiter  the separator to insert between lines.
+     * @param  text       the multi-lines text to convert to single line, or {@code null}.
+     * @return the        the text on a single line text, or {@code null} if none.
+     */
+    public static String singleLine(final String delimiter, final CharSequence text) {
+        if (text != null) {
+            final CharSequence[] lines = CharSequences.splitOnEOL(text);
+            int count = 0;
+            for (int i=0; i<lines.length; i++) {
+                CharSequence line = CharSequences.trimWhitespaces(lines[i]);
+                if (line.length() != 0) lines[count++] = line;          // TODO: use !line.isEmpty() with JDK15.
+            }
+            switch (count) {
+                case 0:  break;
+                case 1:  return lines[0].toString();
+                default: return String.join(delimiter, ArraysExt.resize(lines, count));
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns a string representation of an instance of the given class having the given properties.
      * This is a convenience method for implementation of {@link Object#toString()} methods that are
      * used mostly for debugging purpose.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/package-info.java
index 54cd1b9..8d4ec29 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/package-info.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/package-info.java
@@ -30,7 +30,7 @@
  * so some serialized classes still exist in this package.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.3
  * @module
  */
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index d4330d9..c7484ad 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -46,6 +46,7 @@ import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 import org.apache.sis.internal.coverage.j2d.SampleModelFactory;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.internal.util.Strings;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
@@ -948,7 +949,7 @@ final class ImageFileDirectory extends DataCube {
              */
             case Tags.ImageDescription: {
                 for (final String value : type.readString(input(), count, encoding())) {
-                    metadata.addTitle(value);
+                    metadata.addTitle(Strings.singleLine(" ", value));
                 }
                 break;
             }

[sis] 02/02: Rewrite the horizontal predictor for fixing a bug in the handling of types other than byte. Contains an opportunistic renaming of `setInput(long, long)` method as `setInputRegion(…)`.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 3c3b0d8ab1974e92110af727777da76af4c731ae
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Dec 30 02:30:37 2021 +0100

    Rewrite the horizontal predictor for fixing a bug in the handling of types other than byte.
    Contains an opportunistic renaming of `setInput(long, long)` method as `setInputRegion(…)`.
---
 .../sis/internal/storage/inflater/CCITTRLE.java    |   8 +-
 .../storage/inflater/CompressionChannel.java       |   6 +-
 .../storage/inflater/HorizontalPredictor.java      | 443 +++++++++++----------
 .../sis/internal/storage/inflater/Inflater.java    |  12 +-
 .../apache/sis/internal/storage/inflater/LZW.java  |   8 +-
 .../sis/internal/storage/inflater/PackBits.java    |   8 +-
 .../internal/storage/inflater/PixelChannel.java    |   4 +-
 .../storage/inflater/PredictorChannel.java         |  11 +-
 .../apache/sis/internal/storage/inflater/ZIP.java  |   8 +-
 .../internal/storage/inflater/package-info.java    |   2 +-
 .../internal/storage/inflater/CCITTRLETest.java    |   2 +-
 11 files changed, 271 insertions(+), 241 deletions(-)

diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java
index 6d63b42..0d7bac2 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java
@@ -41,7 +41,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
  * which is not equivalent to "0011" neither. Consequently we can not parse directly the bits as integer values.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -116,7 +116,7 @@ final class CCITTRLE extends CompressionChannel {
 
     /**
      * Creates a new channel which will decompress data from the given input.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input        the source of data to decompress.
@@ -135,8 +135,8 @@ final class CCITTRLE extends CompressionChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        super.setInput(start, byteCount);
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
+        super.setInputRegion(start, byteCount);
         remainingBitsInRow = bitsPerRow;
         runIsWhite = false;
         runLength  = 0;
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
index b7ffb9e..ec97aa3 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
@@ -29,7 +29,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
  * <p>The {@link #close()} method shall be invoked when this channel is no longer used.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -51,7 +51,7 @@ abstract class CompressionChannel extends PixelChannel {
 
     /**
      * Creates a new channel which will decompress data from the given input.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input  the source of data to decompress.
@@ -68,7 +68,7 @@ abstract class CompressionChannel extends PixelChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
         endPosition = Math.addExact(start, byteCount);
         input.seek(start);
     }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
index b65d8f2..263a01c 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
@@ -28,7 +28,7 @@ import org.apache.sis.internal.jdk9.JDK9;
  * Values packed on 4, 2 or 1 bits are not yet supported.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -41,7 +41,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
     /**
      * Number of <em>bytes</em> between a sample value of a pixel and the same sample value of the next pixel.
      * Contrarily to similar fields in other classes, the value in this class is expressed in <em>bytes</em>
-     * rather than a count of sample values because this stride we be applied to {@link ByteBuffer} no matter
+     * rather than a count of sample values because this stride will be applied to {@link ByteBuffer} no matter
      * the data type.
      */
     protected final int pixelStride;
@@ -49,37 +49,53 @@ abstract class HorizontalPredictor extends PredictorChannel {
     /**
      * Number of <em>bytes</em> between a column in a row and the same column in the next row.
      * Contrarily to similar fields in other classes, the value in this class is expressed in <em>bytes</em>
-     * rather than a count of sample values because this stride we be applied to {@link ByteBuffer} no matter
+     * rather than a count of sample values because this stride will be applied to {@link ByteBuffer} no matter
      * the data type.
+     *
+     * <p>Invariants:</p>
+     * <ul>
+     *   <li>This is a multiple of {@link #pixelStride}.</li>
+     *   <li>Must be strictly greater than {@link #pixelStride}
+     *       (i.e. image width must be at least 2 pixels).</li>
+     * </ul>
      */
     private final int scanlineStride;
 
     /**
      * Column index (as a count of <em>bytes</em>, not a count of sample values or pixels).
-     * Used for detecting when the decoding process starts a new row.
+     * Used for detecting when the decoding process starts a new row. It shall always be a
+     * multiple of the data size (in bytes) and between 0 to {@link #scanlineStride}.
      */
     private int column;
 
     /**
-     * Creates a new predictor.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The mask to apply for truncating a position to a multiple of data type size.
+     * For example if the data type is unsigned short, then the mask shall truncate
+     * positions to a multiple of {@value Short#BYTES}.
+     */
+    private final int truncationMask;
+
+    /**
+     * Creates a new predictor which will read compressed data from the given channel.
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
-     * @param  input        the channel that decompress data.
-     * @param  pixelStride  number of sample values per pixel in the source image.
-     * @param  width        number of pixels in the source image.
-     * @param  sampleSize   number of bytes in a sample value.
+     * @param  input            the channel that decompress data.
+     * @param  samplesPerPixel  number of sample values per pixel in the source image.
+     * @param  width            number of pixels in the source image.
+     * @param  sampleSize       number of bytes in a sample value.
      */
-    HorizontalPredictor(final CompressionChannel input, final int pixelStride, final int width, final int sampleSize) {
+    HorizontalPredictor(final CompressionChannel input, final int samplesPerPixel, final int width, final int sampleSize) {
         super(input);
-        this.sampleSizeM1   = sampleSize - Byte.BYTES;
-        this.pixelStride    = pixelStride * sampleSize;
-        this.scanlineStride = Math.multiplyExact(width, this.pixelStride);
+        sampleSizeM1   = sampleSize - Byte.BYTES;
+        truncationMask = ~sampleSizeM1;
+        pixelStride    = samplesPerPixel * sampleSize;
+        scanlineStride = Math.multiplyExact(width, pixelStride);
     }
 
     /**
-     * Creates a new predictor. The {@link #setInput(long, long)} method must
-     * be invoked after construction before a reading process can start.
+     * Creates a new predictor. The {@link #setInputRegion(long, long)} method
+     * must be invoked after construction before a reading process can start.
      *
      * @param  input        the channel that decompress data.
      * @param  dataType     primitive type used for storing data elements in the bank.
@@ -110,8 +126,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        super.setInput(start, byteCount);
+    public final void setInputRegion(final long start, final long byteCount) throws IOException {
+        super.setInputRegion(start, byteCount);
         column = 0;
     }
 
@@ -121,88 +137,103 @@ abstract class HorizontalPredictor extends PredictorChannel {
      *
      * @param  buffer  the buffer on which to apply the predictor.
      * @param  start   position of first byte to process.
-     * @return position after the same sample value processed. Should be {@code limit},
+     * @return position after the last sample value processed. Should be {@code limit},
      *         unless the predictor needs more data for processing the last bytes.
      */
     @Override
-    protected int uncompress(final ByteBuffer buffer, final int start) {
-        final int limit = buffer.position() - sampleSizeM1;
+    protected final int uncompress(final ByteBuffer buffer, final int start) {
+        final int limit   = buffer.position();
+        final int limitMS = limit - sampleSizeM1;       // Limit with enough space for last sample value.
+        /*
+         * Pixels in the first column are left unchanged. The column index is not necessarily zero
+         * because during the previous invocation of this method, the buffer may have stopped in the
+         * middle of the first column (this method does not have control about where the buffer stops).
+         */
         int position = start;
-        while (position < limit) {
-            /*
-             * This loop body should be executed on a row-by-row basis. But the `startOfRow` and `endOfRow` indices
-             * may not be the real start/end of row if the previous call to this method finished before end of row,
-             * or if current call to this method also finishes before end of row (because of buffer limit).
-             */
-            final int startOfRow    = position;
-            final int endOfRow      = Math.min(position + (scanlineStride - column), limit);
-            final int endOfDeferred = Math.min(position + pixelStride, endOfRow);
-            if (column < pixelStride) {
+        if (column < pixelStride) {
+            position += Math.min(pixelStride - column, (limit - position) & truncationMask);
+        }
+        /*
+         * For the first pixel in the buffer, we can not combine with previous values from the buffer
+         * because the buffer does not contain those values anymore.  We have to use the values saved
+         * at the end of the last invocation of this method. Note that this will perform no operation
+         * if the block above skipped fully the pixel in the first column.
+         */
+        position = applyOnFirst(buffer, position, Math.min(start + pixelStride, limitMS), position - start);
+        if ((column += position - start) >= scanlineStride) {
+            column = 0;
+        }
+        /*
+         * This loop body should be executed on a row-by-row basis. But the `startOfRow` and `endOfRow` indices
+         * may not be the real start/end of row if the previous call to this method finished before end of row,
+         * or if current call to this method also finishes before end of row (because of buffer limit).
+         */
+        while (position < limitMS) {
+            assert (column & ~truncationMask) == 0 : column;
+            if (column == 0) {
                 // Pixels in the first column are left unchanged.
-                position += Math.min(pixelStride - column, endOfRow - position);
+                column = Math.min(pixelStride, (limit - position) & truncationMask);
+                position += column;
             }
-            position = applyOnRow(buffer, startOfRow, position, endOfDeferred, endOfRow);
-            column += position - startOfRow;
-            if (column >= scanlineStride) {
+            final int startOfRow = position;
+            position = applyOnRow(buffer, position, Math.min(position + (scanlineStride - column), limitMS));
+            if ((column += position - startOfRow) >= scanlineStride) {
                 column = 0;
             }
         }
         /*
-         * Save the last bytes for next invocation of this method.
+         * Save the last bytes for next invocation of this method. There is two cases:
+         *
+         *   - In the usual case where the above call to `applyOnFirst(…)` used all `savedValues` elements
+         *     (this is true when at least `pixelStride` bytes have been used), the `keep` value below will
+         *     be zero and the call to `saveLastPixel(…)` will store the last `pixelStride` bytes.
+         *
+         *   - If the above call to `applyOnFirst(…)` had to stop prematurely before the `limit` position,
+         *     the `while` loop is never executed and the `savedValues` array has some residual elements.
+         *     The number of residual bytes is given by `keep`.
          */
-        final int capacity = position - start;
-        if (capacity >= pixelStride) {
-            saveLastPixel(buffer, position - pixelStride);
-        } else {
-            saveLastPixel(buffer, pixelStride - capacity, start, capacity);
-        }
+        int from = position - pixelStride;
+        int keep = Math.max(start - from, 0);
+        assert (keep & ~truncationMask) == 0 : keep;
+        saveLastPixel(buffer, keep, from + keep);
         return position;
     }
 
     /**
-     * Applies the predictor on the specified region of the given buffer.
-     * The region to process is divided in two parts:
-     *
-     * <ul>
-     *   <li>From {@code position} to {@code deferred}: values that need to be added with values
-     *       from a previous invocation of {@link #uncompress(ByteBuffer, int)}.</li>
-     *   <li>From {@code deferred} to {@code endOfRow}: values to be added with values available
-     *       at a previous position in the current buffer.</li>
-     * </ul>
-     *
-     * All integer arguments given to this method are in bytes, with increasing values from left to right.
+     * Applies the predictor on the specified region of the given buffer, but using {@code savedValues}
+     * array as the source of previous values. This is used only for the first pixel in a new invocation
+     * of {@link #uncompress(ByteBuffer, int)}.
      *
-     * @param  buffer         the buffer on which to apply the predictor.
-     * @param  startOfRow     position of the start of the (possibly truncated) row.
-     * @param  position       position of the first value to modify.
-     * @param  endOfDeferred  position after the last value combined with values saved from previous batch.
-     * @param  endOfRow       position after the last value to process in this {@code apply(…)} call.
+     * @param  buffer    the buffer on which to apply the predictor.
+     * @param  position  position of the first value to modify in the given buffer.
+     * @param  end       position after the last value to process in this {@code apply(…)} call.
+     * @param  offset    offset (in bytes) of the first saved value to use.
      * @return value of {@code position} after the last sample values processed by this method.
-     *         Should be equal to {@code endOfRow}, unless this method needs more byte for processing
-     *         the last sample value.
      */
-    abstract int applyOnRow(ByteBuffer buffer, int startOfRow, int position, int endOfDeferred, int endOfRow);
+    abstract int applyOnFirst(ByteBuffer buffer, int position, int end, int offset);
 
     /**
-     * Saves the sample values of the last pixel, starting from given buffer position.
-     * Those values will be needed for processing the first pixel in the next invocation
-     * of {@link #uncompress(ByteBuffer, int)}.
+     * Applies the predictor on the specified region of the given buffer.
+     * All integer arguments given to this method are in bytes, with increasing values from left to right.
+     * This method shall increment the position by a multiple of data type size (e.g. 2 for short integers).
      *
-     * @param  buffer    buffer from which to save sample values.
-     * @param  position  position in the buffer of the first byte to save.
+     * @param  buffer    the buffer on which to apply the predictor.
+     * @param  position  position of the first value to modify in the given buffer.
+     * @param  end       position after the last value to process in this {@code apply(…)} call.
+     * @return value of {@code position} after the last sample values processed by this method.
      */
-    abstract void saveLastPixel(ByteBuffer buffer, int position);
+    abstract int applyOnRow(ByteBuffer buffer, int position, int end);
 
     /**
-     * Saves some sample values of the last pixel, starting from given position.
-     * This method is invoked when there is not enough space in the buffer for saving a complete pixel.
+     * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+     * The first sample value to read from the buffer is given by {@code position}.
+     * In rare occasions, some previously saved values may need to be reused.
      *
      * @param  buffer    buffer from which to save sample values.
      * @param  keep      number of bytes to keep in the currently saved values.
-     * @param  position  position in the buffer of the first byte to save.
-     * @param  length    number of bytes to save.
+     * @param  position  position in the buffer of the first byte to save, after the values to keep.
      */
-    abstract void saveLastPixel(ByteBuffer buffer, int keep, int position, int length);
+    abstract void saveLastPixel(ByteBuffer buffer, int keep, int position);
 
 
 
@@ -211,51 +242,53 @@ abstract class HorizontalPredictor extends PredictorChannel {
      */
     private static final class Bytes extends HorizontalPredictor {
         /**
-         * Data in the previous column. The length of this array is the pixel stride.
+         * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
+         * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+         * for use by the next invocation. The buffer capacity is exactly one pixel.
          */
-        private final byte[] previousColumns;
+        private final byte[] savedValues;
 
         /**
          * Creates a new predictor.
          */
-        Bytes(final CompressionChannel input, final int pixelStride, final int width) {
-            super(input, pixelStride, width, Byte.BYTES);
-            previousColumns = new byte[pixelStride];
+        Bytes(final CompressionChannel input, final int samplesPerPixel, final int width) {
+            super(input, samplesPerPixel, width, Byte.BYTES);
+            savedValues = new byte[samplesPerPixel];
         }
 
         /**
-         * Applies the predictor on a row of bytes.
+         * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+         * The first sample value to read from the buffer is given by {@code position}.
          */
         @Override
-        int applyOnRow(final ByteBuffer buffer, final int startOfRow, int position, final int endOfDeferred, final int endOfRow) {
-            while (position < endOfDeferred) {
-                buffer.put(position, (byte) (buffer.get(position) + previousColumns[position - startOfRow]));
-                position++;
-            }
-            while (position < endOfRow) {
-                buffer.put(position, (byte) (buffer.get(position) + buffer.get(position - pixelStride)));
-                position++;
-            }
-            return position;
+        void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+            System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
+            JDK9.get(buffer, position, savedValues, offset, savedValues.length - offset);
         }
 
         /**
-         * Saves the sample values of the last pixel, starting from given buffer position.
-         * Needed for processing the first pixel in next {@code uncompress(…)} invocation.
+         * Applies the predictor, using {@link #savedValues} as the source of previous values.
+         * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, final int position) {
-            JDK9.get(buffer, position, previousColumns);
+        int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+            while (position < end) {
+                buffer.put(position, (byte) (buffer.get(position) + savedValues[offset++]));
+                position++;
+            }
+            return position;
         }
 
         /**
-         * Saves some sample values of the last pixel, starting from given buffer position.
-         * Invoked when there is not enough space in the buffer for saving a complete pixel.
+         * Applies the predictor on a row of bytes.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, final int keep, final int position, final int length) {
-            System.arraycopy(previousColumns, keep, previousColumns, 0, length);
-            JDK9.get(buffer, position, previousColumns, keep, length);
+        int applyOnRow(final ByteBuffer buffer, int position, final int end) {
+            while (position < end) {
+                buffer.put(position, (byte) (buffer.get(position) + buffer.get(position - pixelStride)));
+                position++;
+            }
+            return position;
         }
     }
 
@@ -266,60 +299,58 @@ abstract class HorizontalPredictor extends PredictorChannel {
      */
     private static final class Shorts extends HorizontalPredictor {
         /**
-         * Data in the previous column. The length of this array is the pixel stride.
+         * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
+         * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+         * for use by the next invocation. The buffer capacity is exactly one pixel.
          */
-        private final short[] previousColumns;
+        private final short[] savedValues;
 
         /**
          * Creates a new predictor.
          */
-        Shorts(final CompressionChannel input, final int pixelStride, final int width) {
-            super(input, pixelStride, width, Short.BYTES);
-            previousColumns = new short[pixelStride];
+        Shorts(final CompressionChannel input, final int samplesPerPixel, final int width) {
+            super(input, samplesPerPixel, width, Short.BYTES);
+            savedValues = new short[samplesPerPixel];
         }
 
         /**
-         * Applies the predictor on a row of short integers.
+         * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+         * The first sample value to read from the buffer is given by {@code position}.
          */
         @Override
-        int applyOnRow(final ByteBuffer buffer, final int startOfRow, int position, final int endOfDeferred, final int endOfRow) {
-            while (position < endOfDeferred) {
-                buffer.putShort(position, (short) (buffer.getShort(position) + previousColumns[position - startOfRow]));
+        void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+            offset /= Short.BYTES;
+            System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
+            while (offset < savedValues.length) {
+                savedValues[offset++] = buffer.getShort(position);
                 position += Short.BYTES;
             }
-            while (position < endOfRow) {
-                buffer.putShort(position, (short) (buffer.getShort(position) + buffer.getShort(position - pixelStride)));
-                position += Short.BYTES;
-            }
-            return position;
         }
 
         /**
-         * Saves the sample values of the last pixel, starting from given buffer position.
-         * Needed for processing the first pixel in next {@code uncompress(…)} invocation.
+         * Applies the predictor, using {@link #savedValues} as the source of previous values.
+         * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int position) {
-            for (int i=0; i<previousColumns.length; i++) {
-                previousColumns[i] = buffer.getShort(position);
+        int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+            offset /= Short.BYTES;
+            while (position < end) {
+                buffer.putShort(position, (short) (buffer.getShort(position) + savedValues[offset++]));
                 position += Short.BYTES;
             }
+            return position;
         }
 
         /**
-         * Saves some sample values of the last pixel, starting from given buffer position.
-         * Invoked when there is not enough space in the buffer for saving a complete pixel.
+         * Applies the predictor on a row of short integers.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int keep, int position, int length) {
-            keep   /= Short.BYTES;
-            length /= Short.BYTES;
-            System.arraycopy(previousColumns, keep, previousColumns, 0, length);
-            length += keep;
-            while (keep < length) {
-                previousColumns[keep++] = buffer.get(position);
+        int applyOnRow(final ByteBuffer buffer, int position, final int end) {
+            while (position < end) {
+                buffer.putShort(position, (short) (buffer.getShort(position) + buffer.getShort(position - pixelStride)));
                 position += Short.BYTES;
             }
+            return position;
         }
     }
 
@@ -330,60 +361,58 @@ abstract class HorizontalPredictor extends PredictorChannel {
      */
     private static final class Integers extends HorizontalPredictor {
         /**
-         * Data in the previous column. The length of this array is the pixel stride.
+         * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
+         * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+         * for use by the next invocation. The buffer capacity is exactly one pixel.
          */
-        private final int[] previousColumns;
+        private final int[] savedValues;
 
         /**
          * Creates a new predictor.
          */
-        Integers(final CompressionChannel input, final int pixelStride, final int width) {
-            super(input, pixelStride, width, Integer.BYTES);
-            previousColumns = new int[pixelStride];
+        Integers(final CompressionChannel input, final int samplesPerPixel, final int width) {
+            super(input, samplesPerPixel, width, Integer.BYTES);
+            savedValues = new int[samplesPerPixel];
         }
 
         /**
-         * Applies the predictor on a row of integers.
+         * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+         * The first sample value to read from the buffer is given by {@code position}.
          */
         @Override
-        int applyOnRow(final ByteBuffer buffer, final int startOfRow, int position, final int endOfDeferred, final int endOfRow) {
-            while (position < endOfDeferred) {
-                buffer.putInt(position, buffer.getInt(position) + previousColumns[position - startOfRow]);
-                position += Integer.BYTES;
-            }
-            while (position < endOfRow) {
-                buffer.putInt(position, buffer.getInt(position) + buffer.getInt(position - pixelStride));
+        void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+            offset /= Integer.BYTES;
+            System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
+            while (offset < savedValues.length) {
+                savedValues[offset++] = buffer.getInt(position);
                 position += Integer.BYTES;
             }
-            return position;
         }
 
         /**
-         * Saves the sample values of the last pixel, starting from given buffer position.
-         * Needed for processing the first pixel in next {@code uncompress(…)} invocation.
+         * Applies the predictor, using {@link #savedValues} as the source of previous values.
+         * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int position) {
-            for (int i=0; i<previousColumns.length; i++) {
-                previousColumns[i] = buffer.getInt(position);
+        int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+            offset /= Integer.BYTES;
+            while (position < end) {
+                buffer.putInt(position, buffer.getInt(position) + savedValues[offset++]);
                 position += Integer.BYTES;
             }
+            return position;
         }
 
         /**
-         * Saves some sample values of the last pixel, starting from given buffer position.
-         * Invoked when there is not enough space in the buffer for saving a complete pixel.
+         * Applies the predictor on a row of integers.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int keep, int position, int length) {
-            keep   /= Integer.BYTES;
-            length /= Integer.BYTES;
-            System.arraycopy(previousColumns, keep, previousColumns, 0, length);
-            length += keep;
-            while (keep < length) {
-                previousColumns[keep++] = buffer.get(position);
+        int applyOnRow(final ByteBuffer buffer, int position, final int end) {
+            while (position < end) {
+                buffer.putInt(position, buffer.getInt(position) + buffer.getInt(position - pixelStride));
                 position += Integer.BYTES;
             }
+            return position;
         }
     }
 
@@ -394,60 +423,58 @@ abstract class HorizontalPredictor extends PredictorChannel {
      */
     private static final class Floats extends HorizontalPredictor {
         /**
-         * Data in the previous column. The length of this array is the pixel stride.
+         * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
+         * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+         * for use by the next invocation. The buffer capacity is exactly one pixel.
          */
-        private final float[] previousColumns;
+        private final float[] savedValues;
 
         /**
          * Creates a new predictor.
          */
-        Floats(final CompressionChannel input, final int pixelStride, final int width) {
-            super(input, pixelStride, width, Float.BYTES);
-            previousColumns = new float[pixelStride];
+        Floats(final CompressionChannel input, final int samplesPerPixel, final int width) {
+            super(input, samplesPerPixel, width, Float.BYTES);
+            savedValues = new float[samplesPerPixel];
         }
 
         /**
-         * Applies the predictor on a row of floating point values.
+         * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+         * The first sample value to read from the buffer is given by {@code position}.
          */
         @Override
-        int applyOnRow(final ByteBuffer buffer, final int startOfRow, int position, final int endOfDeferred, final int endOfRow) {
-            while (position < endOfDeferred) {
-                buffer.putFloat(position, buffer.getFloat(position) + previousColumns[position - startOfRow]);
-                position += Float.BYTES;
-            }
-            while (position < endOfRow) {
-                buffer.putFloat(position, buffer.getFloat(position) + buffer.getFloat(position - pixelStride));
+        void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+            offset /= Float.BYTES;
+            System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
+            while (offset < savedValues.length) {
+                savedValues[offset++] = buffer.getFloat(position);
                 position += Float.BYTES;
             }
-            return position;
         }
 
         /**
-         * Saves the sample values of the last pixel, starting from given buffer position.
-         * Needed for processing the first pixel in next {@code uncompress(…)} invocation.
+         * Applies the predictor, using {@link #savedValues} as the source of previous values.
+         * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int position) {
-            for (int i=0; i<previousColumns.length; i++) {
-                previousColumns[i] = buffer.getFloat(position);
+        int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+            offset /= Float.BYTES;
+            while (position < end) {
+                buffer.putFloat(position, buffer.getFloat(position) + savedValues[offset++]);
                 position += Float.BYTES;
             }
+            return position;
         }
 
         /**
-         * Saves some sample values of the last pixel, starting from given buffer position.
-         * Invoked when there is not enough space in the buffer for saving a complete pixel.
+         * Applies the predictor on a row of floating point values.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int keep, int position, int length) {
-            keep   /= Float.BYTES;
-            length /= Float.BYTES;
-            System.arraycopy(previousColumns, keep, previousColumns, 0, length);
-            length += keep;
-            while (keep < length) {
-                previousColumns[keep++] = buffer.get(position);
+        int applyOnRow(final ByteBuffer buffer, int position, final int end) {
+            while (position < end) {
+                buffer.putFloat(position, buffer.getFloat(position) + buffer.getFloat(position - pixelStride));
                 position += Float.BYTES;
             }
+            return position;
         }
     }
 
@@ -458,60 +485,58 @@ abstract class HorizontalPredictor extends PredictorChannel {
      */
     private static final class Doubles extends HorizontalPredictor {
         /**
-         * Data in the previous column. The length of this array is the pixel stride.
+         * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
+         * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+         * for use by the next invocation. The buffer capacity is exactly one pixel.
          */
-        private final double[] previousColumns;
+        private final double[] savedValues;
 
         /**
          * Creates a new predictor.
          */
-        Doubles(final CompressionChannel input, final int pixelStride, final int width) {
-            super(input, pixelStride, width, Double.BYTES);
-            previousColumns = new double[pixelStride];
+        Doubles(final CompressionChannel input, final int samplesPerPixel, final int width) {
+            super(input, samplesPerPixel, width, Double.BYTES);
+            savedValues = new double[samplesPerPixel];
         }
 
         /**
-         * Applies the predictor on a row of floating point values.
+         * Saves {@link #pixelStride} bytes making the sample values of the last pixel.
+         * The first sample value to read from the buffer is given by {@code position}.
          */
         @Override
-        int applyOnRow(final ByteBuffer buffer, final int startOfRow, int position, final int endOfDeferred, final int endOfRow) {
-            while (position < endOfDeferred) {
-                buffer.putDouble(position, buffer.getDouble(position) + previousColumns[position - startOfRow]);
-                position += Double.BYTES;
-            }
-            while (position < endOfRow) {
-                buffer.putDouble(position, buffer.getDouble(position) + buffer.getDouble(position - pixelStride));
+        void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+            offset /= Double.BYTES;
+            System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
+            while (offset < savedValues.length) {
+                savedValues[offset++] = buffer.getDouble(position);
                 position += Double.BYTES;
             }
-            return position;
         }
 
         /**
-         * Saves the sample values of the last pixel, starting from given buffer position.
-         * Needed for processing the first pixel in next {@code uncompress(…)} invocation.
+         * Applies the predictor, using {@link #savedValues} as the source of previous values.
+         * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int position) {
-            for (int i=0; i<previousColumns.length; i++) {
-                previousColumns[i] = buffer.getDouble(position);
+        int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+            offset /= Double.BYTES;
+            while (position < end) {
+                buffer.putDouble(position, buffer.getDouble(position) + savedValues[offset++]);
                 position += Double.BYTES;
             }
+            return position;
         }
 
         /**
-         * Saves some sample values of the last pixel, starting from given buffer position.
-         * Invoked when there is not enough space in the buffer for saving a complete pixel.
+         * Applies the predictor on a row of floating point values.
          */
         @Override
-        void saveLastPixel(final ByteBuffer buffer, int keep, int position, int length) {
-            keep   /= Double.BYTES;
-            length /= Double.BYTES;
-            System.arraycopy(previousColumns, keep, previousColumns, 0, length);
-            length += keep;
-            while (keep < length) {
-                previousColumns[keep++] = buffer.get(position);
+        int applyOnRow(final ByteBuffer buffer, int position, final int end) {
+            while (position < end) {
+                buffer.putDouble(position, buffer.getDouble(position) + buffer.getDouble(position - pixelStride));
                 position += Double.BYTES;
             }
+            return position;
         }
     }
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java
index da1561d..a4d4490 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java
@@ -46,7 +46,7 @@ import static org.apache.sis.internal.util.Numerics.ceilDiv;
  * and band subset.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -225,7 +225,11 @@ public abstract class Inflater implements Closeable {
                 break;
             }
             case HORIZONTAL: {
-                if (pixelsPerElement == 1 && dataType == DataType.BYTE) {
+                if (sourceWidth == 1) {
+                    channel = inflater;     // Horizontal predictor is no-op if image width is 1 pixel.
+                    break;
+                }
+                if (pixelsPerElement == 1) {
                     channel = HorizontalPredictor.create(inflater, dataType, sourcePixelStride, sourceWidth);
                     if (channel != null) break;
                 }
@@ -257,12 +261,12 @@ public abstract class Inflater implements Closeable {
     public void setInputOutput(final long start, final long byteCount, final Buffer bank) throws IOException {
         /*
          * If the input is a wrapper around a decompression algorithm (PackBits, CCITTRLE, etc),
-         * we need to inform the wrapper about the new operation. The call to `setInput(…)` below
+         * we need to inform the wrapper about the new operation. The call to `setInputRegion(…)`
          * will perform a seek operation. As a consequence the buffer content become invalid and
          * must be emptied.
          */
         if (input.channel instanceof PixelChannel) {
-            ((PixelChannel) input.channel).setInput(start, byteCount);
+            ((PixelChannel) input.channel).setInputRegion(start, byteCount);
             input.buffer.limit(0);
             input.setStreamPosition(start);         // Must be after above call to `limit(0)`.
         }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java
index 3591f27..07d4e84 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java
@@ -34,7 +34,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -108,7 +108,7 @@ final class LZW extends CompressionChannel {
 
     /**
      * Creates a new channel which will decompress data from the given input.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input  the source of data to decompress.
@@ -126,8 +126,8 @@ final class LZW extends CompressionChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        super.setInput(start, byteCount);
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
+        super.setInputRegion(start, byteCount);
         previousSequence   = ArraysExt.EMPTY_BYTE;
         codeSize           = MIN_CODE_SIZE;
         nextAvailableEntry = 0;
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java
index d2f6575..51b1e59 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java
@@ -26,7 +26,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
  * This compression is described in section 9 of TIFF 6 specification.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -50,7 +50,7 @@ final class PackBits extends CompressionChannel {
 
     /**
      * Creates a new channel which will decompress data from the given input.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input  the source of data to decompress.
@@ -67,8 +67,8 @@ final class PackBits extends CompressionChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        super.setInput(start, byteCount);
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
+        super.setInputRegion(start, byteCount);
         literalCount    = 0;
         duplicatedCount = 0;
     }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
index 89a09d7..a4f2ba9 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
@@ -33,7 +33,7 @@ import java.nio.channels.ReadableByteChannel;
  * The {@link #close()} method shall be invoked when this channel is no longer used.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -51,5 +51,5 @@ abstract class PixelChannel implements ReadableByteChannel {
      * @param  byteCount  number of byte to read from the input.
      * @throws IOException if the stream can not be seek to the given start position.
      */
-    public abstract void setInput(long start, long byteCount) throws IOException;
+    public abstract void setInputRegion(long start, long byteCount) throws IOException;
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
index 46cc1e7..00f8876 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
@@ -27,7 +27,7 @@ import org.apache.sis.internal.jdk9.JDK9;
  * Implementation of a {@link Predictor} to be executed after decompression.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -50,7 +50,7 @@ abstract class PredictorChannel extends PixelChannel {
 
     /**
      * Creates a predictor.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input  the channel that decompress data.
@@ -68,8 +68,9 @@ abstract class PredictorChannel extends PixelChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        input.setInput(start, byteCount);
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
+        input.setInputRegion(start, byteCount);
+        deferredCount = 0;
     }
 
     /**
@@ -78,7 +79,7 @@ abstract class PredictorChannel extends PixelChannel {
      *
      * @param  buffer  the buffer on which to apply the predictor.
      * @param  start   position of first sample value to process.
-     * @return position after the same sample value processed. Should be {@link ByteBuffer#position()},
+     * @return position after the last sample value processed. Should be {@link ByteBuffer#position()},
      *         unless the predictor needs more data for processing the last bytes.
      */
     protected abstract int uncompress(ByteBuffer buffer, int start);
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java
index f33b0cd..1810e9b 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java
@@ -29,7 +29,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
  *
  * @author  Rémi Marechal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -42,7 +42,7 @@ final class ZIP extends CompressionChannel {
 
     /**
      * Creates a new channel which will decompress data from the given input.
-     * The {@link #setInput(long, long)} method must be invoked after construction
+     * The {@link #setInputRegion(long, long)} method must be invoked after construction
      * before a reading process can start.
      *
      * @param  input      the source of data to decompress.
@@ -63,8 +63,8 @@ final class ZIP extends CompressionChannel {
      * @throws IOException if the stream can not be seek to the given start position.
      */
     @Override
-    public void setInput(final long start, final long byteCount) throws IOException {
-        super.setInput(start, byteCount);
+    public void setInputRegion(final long start, final long byteCount) throws IOException {
+        super.setInputRegion(start, byteCount);
         inflater.reset();
     }
 
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
index 162a188..c975f32 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
@@ -42,7 +42,7 @@
  * </dl>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
diff --git a/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java b/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java
index 5658f6c..94bb189 100644
--- a/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java
+++ b/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java
@@ -501,7 +501,7 @@ public final strictfp class CCITTRLETest extends TestCase {
         final CCITTRLE decoder = new CCITTRLE(
                 new ChannelDataInput("sequenceOfAllWords", null, sequenceOfAllWords, true),
                 sequenceOfAllWords.limit());
-        decoder.setInput(0, sequenceOfAllWords.limit());
+        decoder.setInputRegion(0, sequenceOfAllWords.limit());
         int runLength = 0;
         do {
             assertEquals(decoder.getRunLength(tree), runLength);