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 <martin.desruisse...@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);

Reply via email to