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 2d122d3cdf9deedb5d1a7029159ef6ca50534f26
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Aug 13 18:37:01 2021 +0200

    Add a `GridDerivation.maximumSubsampling(…)` method and use it in 
`TiledGridResource` for disabling subsamplings on axes where it is not 
supported.
---
 .../apache/sis/coverage/grid/GridDerivation.java   | 223 +++++++++++++--------
 .../sis/coverage/grid/GridDerivationTest.java      |  32 ++-
 .../sis/internal/storage/TiledGridResource.java    |  16 +-
 .../apache/sis/test/storage/SubsampledImage.java   |  63 +++---
 4 files changed, 224 insertions(+), 110 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index 0c9c26a..6ff2ce0 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -126,6 +126,13 @@ public class GridDerivation {
      */
     private int[] chunkSize;
 
+    /**
+     * The maximum subsampling values (inclusive), or {@code null} if none.
+     *
+     * @see #maximumSubsampling(int...)
+     */
+    private int[] maximumSubsampling;
+
     // ──────── FIELDS COMPUTED BY METHODS IN THIS CLASS 
──────────────────────────────────────────────────────────────
 
     /**
@@ -308,20 +315,8 @@ public class GridDerivation {
      * @see GridExtent#expand(long...)
      */
     public GridDerivation margin(final int... cellCounts) {
-        ArgumentChecks.ensureNonNull("cellCounts", cellCounts);
         ensureSubgridNotSet();
-        int[] margin = null;
-        for (int i=cellCounts.length; --i >= 0;) {
-            final int n = cellCounts[i];
-            if (n != 0) {
-                ArgumentChecks.ensurePositive("cellCounts", n);
-                if (margin == null) {
-                    margin = new int[i+1];
-                }
-                margin[i] = n;
-            }
-        }
-        this.margin = margin;           // Set only on success. We want null 
if all margin values are 0.
+        margin = validateCellCounts("cellCounts", cellCounts, 0);
         return this;
     }
 
@@ -351,22 +346,63 @@ public class GridDerivation {
      * @since 1.1
      */
     public GridDerivation chunkSize(final int... cellCounts) {
-        ArgumentChecks.ensureNonNull("cellCounts", cellCounts);
         ensureSubgridNotSet();
-        int[] chunkSize = null;
-        for (int i=cellCounts.length; --i >= 0;) {
-            final int n = cellCounts[i];
-            if (n != 1) {
-                ArgumentChecks.ensureStrictlyPositive("cellCounts", n);
-                if (chunkSize == null) {
-                    chunkSize = new int[i+1];
-                    Arrays.fill(chunkSize, 1);
+        chunkSize = validateCellCounts("cellCounts", cellCounts, 1);
+        return this;
+    }
+
+    /**
+     * Specifies the maximum subsampling values (inclusive) for each dimension.
+     * If a subsampling value is greater than a specified value in the 
corresponding dimension,
+     * the subsampling will be clamped to the specified maximal value.
+     * Setting a maximum value of 1 in a dimension is equivalent to disabling 
subsampling in that dimension.
+     *
+     * <p>If this method is never invoked, then there is no maximum value.
+     * If this method is invoked too late, an {@link IllegalStateException} is 
thrown.
+     * If the {@code cellCounts} array length is shorter than the grid 
dimension,
+     * then all missing dimensions have no maximum value.</p>
+     *
+     * @param  subsampling  maximal subsampling values (inclusive).
+     * @return {@code this} for method call chaining.
+     * @throws IllegalArgumentException if a value is zero or negative.
+     * @throws IllegalStateException if {@link #subgrid(Envelope, double...)} 
or {@link #slice(DirectPosition)}
+     *         has already been invoked.
+     *
+     * @since 1.1
+     */
+    public GridDerivation maximumSubsampling(final int... subsampling) {
+        ensureSubgridNotSet();
+        maximumSubsampling = validateCellCounts("subsampling", subsampling, 
Integer.MAX_VALUE);
+        return this;
+    }
+
+    /**
+     * Returns a copy of the {@code values} array with trailing {@code 
defaultValue} trimmed.
+     * Returns {@code null} if all values are trimmed. This method verifies 
that values are valid.
+     *
+     * @param  property  argument name to use in error message in case of 
errors.
+     * @param  values    user-supplied values.
+     * @return values to save in {@link GridDerivation}.
+     */
+    private static int[] validateCellCounts(final String property, final int[] 
values, final int defaultValue) {
+        ArgumentChecks.ensureNonNull(property, values);
+        int[] copy = null;
+        for (int i=values.length; --i >= 0;) {
+            final int n = values[i];
+            if (n != defaultValue) {
+                if (defaultValue == 0) {
+                    ArgumentChecks.ensurePositive(property, n);
+                } else {
+                    ArgumentChecks.ensureStrictlyPositive(property, n);
+                }
+                if (copy == null) {
+                    copy = new int[i+1];
+                    Arrays.fill(copy, defaultValue);
                 }
-                chunkSize[i] = n;
+                copy[i] = n;
             }
         }
-        this.chunkSize = chunkSize;         // Set only on success. We want 
null if all size values are 1.
-        return this;
+        return copy;
     }
 
     /**
@@ -531,44 +567,45 @@ public class GridDerivation {
         if (areaOfInterest.isEnvelopeOnly()) {
             return subgrid(areaOfInterest.envelope, (double[]) null);
         }
+        final double[] scales;
         if (areaOfInterest.isExtentOnly()) {
-            int[] subsampling = null;
-            if (areaOfInterest.resolution != null) {
-                /*
-                 * In principle `resolution` is always null here because it is 
computed from `gridToCRS`,
-                 * which is null (otherwise `isExtentOnly()` would have been 
false). However an exception
-                 * to this rule happens if `areaOfInterest` has been computed 
by another `GridDerivation`,
-                 * in which case the resolution requested by user is saved 
even when `gridToCRS` is null.
-                 * In that case the resolution is relative to the base grid of 
the other `GridDerivation`.
-                 * Note however that the `resolution` field is only an 
approximation (the exact transform
-                 * would have been stored in `gridToCRS` if it was non-null) 
and the subsampling offsets
-                 * are lost (they would also have been stored in `gridToCRS`).
-                 */
-                subsampling = new int[areaOfInterest.resolution.length];
-                for (int i=0; i<subsampling.length; i++) {
-                    subsampling[i] = 
roundSubsampling(areaOfInterest.resolution[i], i);
-                }
+            if (baseExtent != null) {
+                baseExtent = baseExtent.intersect(areaOfInterest.extent);
+                subGridSetter = "subgrid";
             }
-            return subgrid(areaOfInterest.extent, subsampling);
-        }
-        subGridSetter = "subgrid";
-        if (base.equals(areaOfInterest)) {
-            return this;
-        }
-        final MathTransform mapCenters;
-        final GridExtent domain = areaOfInterest.getExtent();       // May 
throw IncompleteGridGeometryException.
-        try {
-            final CoordinateOperationFinder finder = new 
CoordinateOperationFinder(areaOfInterest, base);
-            final MathTransform mapCorners = finder.gridToGrid();
-            finder.setAnchor(PixelInCell.CELL_CENTER);
-            finder.nowraparound();
-            mapCenters = finder.gridToGrid();                               // 
We will use only the scale factors.
-            setBaseExtentClipped(domain.toCRS(mapCorners, mapCenters, null));
-        } catch (FactoryException | TransformException e) {
-            throw new IllegalGridGeometryException(e, "areaOfInterest");
-        }
-        if (baseExtent != base.extent && 
baseExtent.equals(areaOfInterest.extent)) {
-            baseExtent = areaOfInterest.extent;                                
         // Share common instance.
+            scales = areaOfInterest.resolution;
+            /*
+             * In principle `resolution` is always null here because it is 
computed from `gridToCRS`,
+             * which is null (otherwise `isExtentOnly()` would have been 
false). However an exception
+             * to this rule happens if `areaOfInterest` has been computed by 
another `GridDerivation`,
+             * in which case the resolution requested by user is saved even 
when `gridToCRS` is null.
+             * In that case the resolution is relative to the base grid of the 
other `GridDerivation`.
+             * Note however that the `resolution` field is only an 
approximation (the exact transform
+             * would have been stored in `gridToCRS` if it was non-null) and 
the subsampling offsets
+             * are lost (they would also have been stored in `gridToCRS`).
+             */
+        } else {
+            if (base.equals(areaOfInterest)) {
+                return this;
+            }
+            final MathTransform mapCenters;
+            final GridExtent domain = areaOfInterest.getExtent();       // May 
throw IncompleteGridGeometryException.
+            try {
+                final CoordinateOperationFinder finder = new 
CoordinateOperationFinder(areaOfInterest, base);
+                final MathTransform mapCorners = finder.gridToGrid();
+                finder.setAnchor(PixelInCell.CELL_CENTER);
+                finder.nowraparound();
+                mapCenters = finder.gridToGrid();                              
 // We will use only the scale factors.
+                setBaseExtentClipped(domain.toCRS(mapCorners, mapCenters, 
null));
+                subGridSetter = "subgrid";
+            } catch (FactoryException | TransformException e) {
+                throw new IllegalGridGeometryException(e, "areaOfInterest");
+            }
+            if (baseExtent != base.extent && 
baseExtent.equals(areaOfInterest.extent)) {
+                baseExtent = areaOfInterest.extent;                            
             // Share common instance.
+            }
+            // The `domain` extent must be the source of the `mapCenters` 
transform.
+            scales = GridGeometry.resolution(mapCenters, domain);
         }
         /*
          * The subsampling will determine the scale factors in the transform 
from the given desired grid geometry
@@ -577,8 +614,6 @@ public class GridDerivation {
          * the way transforms are concatenated) as the ratio between the 
resolutions of the `areaOfInterest` and
          * `base` grid geometries, computed in the center of the area of 
interest.
          */
-        // The `domain` extent must be the source of the `mapCenters` 
transform.
-        final double[] scales = GridGeometry.resolution(mapCenters, domain);
         if (scales == null) {
             return this;
         }
@@ -732,8 +767,16 @@ public class GridDerivation {
                     if (s > 1) {                                // Also for 
skipping NaN values.
                         scaled = true;
                         final int i = (modifiedDimensions != null) ? 
modifiedDimensions[k] : k;
-                        final int accuracy = Math.max(0, 
Math.getExponent(indices.getSpan(i))) + 1;     // Power of 2.
-                        s = Math.scalb(Math.rint(Math.scalb(s, accuracy)), 
-accuracy);
+                        if (chunkSize != null) {
+                            s = roundSubsampling(s, i);         // Include 
clamp to `maximumSubsampling`.
+                        } else {
+                            final int accuracy = Math.max(0, 
Math.getExponent(indices.getSpan(i))) + 1;   // Power of 2.
+                            s = Math.scalb(Math.rint(Math.scalb(s, accuracy)), 
-accuracy);
+                            if (maximumSubsampling != null && i < 
maximumSubsampling.length) {
+                                final double max = maximumSubsampling[i];
+                                if (s > max) s = max;
+                            }
+                        }
                         indices.setRange(i, indices.getLower(i) / s,
                                             indices.getUpper(i) / s);
                     }
@@ -873,7 +916,7 @@ public class GridDerivation {
      *
      * @since 1.1
      */
-    public GridDerivation subgrid(final GridExtent areaOfInterest, final 
int... subsampling) {
+    public GridDerivation subgrid(final GridExtent areaOfInterest, int... 
subsampling) {
         ensureSubgridNotSet();
         final int n = base.getDimension();
         if (areaOfInterest != null) {
@@ -883,11 +926,20 @@ public class GridDerivation {
                         Errors.Keys.MismatchedDimension_3, "extent", n, 
actual));
             }
         }
-        subGridSetter = "subgrid";
         if (areaOfInterest != null && baseExtent != null) {
             baseExtent = baseExtent.intersect(areaOfInterest);
+            subGridSetter = "subgrid";
+        }
+        if (subsampling == null) {
+            return this;
         }
-        return (subsampling != null) ? subsample(subsampling) : this;
+        if (chunkSize != null || maximumSubsampling != null) {
+            subsampling = subsampling.clone();
+            for (int i=0; i<subsampling.length; i++) {
+                subsampling[i] = roundSubsampling(subsampling[i], i);
+            }
+        }
+        return subsample(subsampling);
     }
 
     /**
@@ -903,7 +955,11 @@ public class GridDerivation {
      * </ul>
      *
      * The {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS} 
transform is scaled accordingly
-     * in order to map approximately to the same {@linkplain 
GridGeometry#getEnvelope() envelope}.
+     *
+     * <h4>Preconditions</h4>
+     * This method assumes that subsampling are divisors of {@linkplain 
#chunkSize(int...) chunk sizes}
+     * and are not greater than the {@linkplain #maximumSubsampling(int...) 
maximum subsampling}.
+     * It is caller responsibility to ensure that those preconditions are met.
      *
      * @param  subsampling  the subsampling to apply on each grid dimension. 
All values shall be greater than zero.
      *         If the array length is shorter than the number of dimensions, 
missing values are assumed to be 1.
@@ -919,7 +975,6 @@ public class GridDerivation {
      */
     @Deprecated
     // TODO: make private (do not delete) after next SIS release.
-    // This method assumes that subsampling are divisors of chunk sizes.
     public GridDerivation subsample(final int... subsampling) {
         ArgumentChecks.ensureNonNull("subsampling", subsampling);
         if (toBase != null) {
@@ -1276,6 +1331,7 @@ public class GridDerivation {
 
     /**
      * Rounds a subsampling value according the current {@link RoundingMode}.
+     * If {@link #maximumSubsampling} values have been specified, then 
subsampling is clamped if needed.
      * If a {@link #chunkSize} has been specified, then the subsampling will 
be a divisor of that size.
      * This is necessary for avoiding a drift of subsampled pixel coordinates 
computed from tile coordinates.
      *
@@ -1302,13 +1358,18 @@ public class GridDerivation {
      * @param  dimension  the dimension of the scale factor to round.
      */
     private int roundSubsampling(final double scale, final int dimension) {
-        final int subsampling;
+        int subsampling;
         switch (rounding) {
             default:        throw new AssertionError(rounding);
             case NEAREST:   subsampling = (int) Math.min(Math.round(scale), 
Integer.MAX_VALUE); break;
             case CONTAINED: subsampling = (int) Math.ceil(scale - 
tolerance(dimension)); break;
             case ENCLOSING: subsampling = (int) (scale + 
tolerance(dimension)); break;
         }
+        int max = Integer.MAX_VALUE;
+        if (maximumSubsampling != null && dimension < 
maximumSubsampling.length) {
+            max = maximumSubsampling[dimension];
+            if (subsampling > max) subsampling = max;
+        }
         if (subsampling <= 1) {
             return 1;
         }
@@ -1326,22 +1387,16 @@ public class GridDerivation {
                  * It is better to know now if there is any problem here.
                  */
                 int s = divisors[i-1];
-                if (i < divisors.length) {
-                    switch (rounding) {
-                        case CONTAINED: {
-                            s = divisors[i];
-                            break;
-                        }
-                        case NEAREST: {
-                            final int above = divisors[i];
-                            if (above - r < r - s) {
-                                s = above;
-                            }
-                            break;
+                final int offset = subsampling - r;
+                if (rounding != GridRoundingMode.ENCLOSING && i < 
divisors.length) {
+                    final int above = divisors[i];
+                    if (max == Integer.MAX_VALUE || above <= max - offset) {
+                        if (rounding == GridRoundingMode.CONTAINED || above - 
r < r - s) {
+                            s = above;
                         }
                     }
                 }
-                return s + (subsampling - r);
+                return s + offset;
             }
         }
         return subsampling;
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
index 28887b0..17932ba 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -23,6 +23,7 @@ import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.geometry.Envelope2D;
@@ -337,7 +338,14 @@ public final strictfp class GridDerivationTest extends 
TestCase {
         final GridDerivation derivation = grid.derive().margin(4, 
3).chunkSize(5, 10);
         grid = derivation.subgrid(envelope, 2, 1).build();
         assertExtentEquals(new long[] {55, 0}, new long[] {204, 39}, 
derivation.getIntersection());
-        assertExtentEquals(new long[] {14, 0}, new long[] { 50,  9}, 
grid.getExtent());
+        assertExtentEquals(new long[] {11, 0}, new long[] { 40,  7}, 
grid.getExtent());
+        assertArrayEquals(new double[] {2.5, 1.25}, grid.getResolution(false), 
STRICT);
+        /*
+         * Without chunk size, the resolution would have been {2,1} which 
correspond to a subsampling of {4,4}.
+         * But because of the chunk size, the subsampling have been rounded to 
{5,5} which correspond to above
+         * resolution. The grid extent, which would have been x:[14 … 50] and 
y:[0 … 9], is also affected by
+         * the subsampling adjustment.
+         */
     }
 
     /**
@@ -374,6 +382,28 @@ public final strictfp class GridDerivationTest extends 
TestCase {
     }
 
     /**
+     * Tests {@link GridDerivation#subgrid(GridGeometry)} with a maximum 
subsampling value.
+     */
+    @Test
+    public void testSubgridWithMaximumSubsampling() {
+        GridGeometry   query  = grid(  80,   -3,  110,    55, 100, -300);     
// Same as above test.
+        GridGeometry   base   = grid(2000, -1000, 9000, 8000,   2,   -1);
+        GridDerivation change = base.derive().chunkSize(390, 
70).maximumSubsampling(25, 100).subgrid(query);
+        GridGeometry   result = change.build();
+        Matrix         toCRS  = 
MathTransforms.getMatrix(result.getGridToCRS(PixelInCell.CELL_CORNER));
+        /*
+         * Subsampling values checked below shall be equal or smaller
+         * than the values given to `maximumSubsampling(…)`.
+         */
+        assertArrayEquals(new int[] {15,  84}, change.getSubsampling());
+        assertArrayEquals(new int[] { 0, -70}, change.getSubsamplingOffsets());
+        assertMatrixEquals("gridToCRS", new Matrix3(
+                 30,   0, 200,
+                  0, -84, 570,
+                  0,   0,   1), toCRS, STRICT);
+    }
+
+    /**
      * Tests {@link GridDerivation#subgrid(GridExtent)} with a null "grid to 
CRS" transform.
      */
     @Test
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
index e5d4c10..9fdf1a2 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
@@ -162,6 +162,19 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
     }
 
     /**
+     * Returns the maximal subsampling supported in the given dimension.
+     * The default implementation puts no limit if {@code 
getAtomSize(dimension)} is 1,
+     * and disables subsampling otherwise.
+     *
+     * @param  dimension  the dimension to test.
+     * @return the maximal subsampling supported in the given dimension.
+     * @throws DataStoreException if an error occurred while fetching the 
sample model.
+     */
+    protected int getMaximumSubsampling(final int dimension) throws 
DataStoreException {
+        return getAtomSize(dimension) == 1 ? Integer.MAX_VALUE : 1;
+    }
+
+    /**
      * Returns {@code true} if the reader can load only the requested bands 
and skip the other bands,
      * or {@code false} if the reader must load all bands. This value controls 
the amount of data to
      * be loaded by {@link #read(GridGeometry, int...)}:
@@ -356,7 +369,8 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
                 if (tileWidth  >= sourceExtent.getSize(0)) {tileWidth  = 
getAtomSize(0); sharedCache = false;}
                 if (tileHeight >= sourceExtent.getSize(1)) {tileHeight = 
getAtomSize(1); sharedCache = false;}
                 final GridDerivation target = 
gridGeometry.derive().chunkSize(tileWidth, tileHeight)
-                                              
.rounding(GridRoundingMode.ENCLOSING).subgrid(domain);
+                            .maximumSubsampling(getMaximumSubsampling(0), 
getMaximumSubsampling(1))
+                            
.rounding(GridRoundingMode.ENCLOSING).subgrid(domain);
                 domain             = target.build();
                 readExtent         = target.getIntersection();
                 subsampling        = target.getSubsampling();
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
index 088cb0b..fa4af0b 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
 import java.util.Vector;
 import java.awt.image.Raster;
 import java.awt.image.ColorModel;
+import java.awt.image.MultiPixelPackedSampleModel;
 import java.awt.image.PixelInterleavedSampleModel;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
@@ -36,7 +37,7 @@ import static org.junit.Assert.*;
  *
  * <h2>Limitations</h2>
  * <ul>
- *   <li>Sample model must be an instance of {@link 
PixelInterleavedSampleModel}.</li>
+ *   <li>Sample model must be an instance of {@link 
PixelInterleavedSampleModel} or {@link MultiPixelPackedSampleModel}.</li>
  *   <li>Subsampling must be a divisor of tile size, except in dimensions 
having only one tile.</li>
  *   <li>Conversion from source coordinates to target coordinates is a 
division only, without subsampling offsets.</li>
  * </ul>
@@ -84,33 +85,47 @@ final class SubsampledImage extends PlanarImage {
         this.source = source;
         this.subX   = subX;
         this.subY   = subY;
-        final PixelInterleavedSampleModel sm = (PixelInterleavedSampleModel) 
source.getSampleModel();
-        final int   pixelStride    = sm.getPixelStride();
-        final int   scanlineStride = sm.getScanlineStride();
-        final int   offset         = pixelStride*offX + scanlineStride*offY;
-        final int[] offsets        = sm.getBandOffsets();
-        /*
-         * Conversion from subsampled coordinate x′ to full resolution x is:
-         *
-         *    x = (x′ × subsampling) + offset
-         *
-         * We simulate the offset addition by adding the value in the offset 
bands.
-         * PixelInterleavedSampleModel uses that value for computing array 
index as below:
-         *
-         *    y*scanlineStride + x*pixelStride + bandOffsets[b]
-         */
-        for (int i=0; i<offsets.length; i++) {
-            offsets[i] += offset;
+        final SampleModel sourceModel = source.getSampleModel();
+        if (sourceModel instanceof PixelInterleavedSampleModel) {
+            final PixelInterleavedSampleModel sm = 
(PixelInterleavedSampleModel) sourceModel;
+            final int   pixelStride    = sm.getPixelStride();
+            final int   scanlineStride = sm.getScanlineStride();
+            final int   offset         = pixelStride*offX + 
scanlineStride*offY;
+            final int[] offsets        = sm.getBandOffsets();
+            /*
+             * Conversion from subsampled coordinate x′ to full resolution x 
is:
+             *
+             *    x = (x′ × subsampling) + offset
+             *
+             * We simulate the offset addition by adding the value in the 
offset bands.
+             * PixelInterleavedSampleModel uses that value for computing array 
index as below:
+             *
+             *    y*scanlineStride + x*pixelStride + bandOffsets[b]
+             */
+            for (int i=0; i<offsets.length; i++) {
+                offsets[i] += offset;
+            }
+            model = new PixelInterleavedSampleModel(sm.getDataType(),
+                    divExclusive(sm.getWidth(),  subX),
+                    divExclusive(sm.getHeight(), subY),
+                    pixelStride*subX, scanlineStride*subY, offsets);
+        } else if (sourceModel instanceof MultiPixelPackedSampleModel) {
+            final MultiPixelPackedSampleModel sm = 
(MultiPixelPackedSampleModel) sourceModel;
+            assertEquals("Subsampling on the X axis is not supported.", 1, 
subX);
+            model = new MultiPixelPackedSampleModel(sm.getDataType(),
+                    divExclusive(sm.getWidth(),  subX),
+                    divExclusive(sm.getHeight(), subY),
+                    sm.getPixelBitStride(),
+                    sm.getScanlineStride() * subY,
+                    sm.getDataBitOffset());
+        } else {
+            throw new AssertionError("Unsupported sample model: " + 
sourceModel);
         }
-        model = new PixelInterleavedSampleModel(sm.getDataType(),
-                divExclusive(sm.getWidth(),  subX),
-                divExclusive(sm.getHeight(), subY),
-                pixelStride*subX, scanlineStride*subY, offsets);
         /*
          * Conditions documented in class javadoc.
          */
-        if (getNumXTiles() > 1) assertEquals(0, sm.getWidth()  % subX);
-        if (getNumYTiles() > 1) assertEquals(0, sm.getHeight() % subY);
+        if (getNumXTiles() > 1) assertEquals(0, sourceModel.getWidth()  % 
subX);
+        if (getNumYTiles() > 1) assertEquals(0, sourceModel.getHeight() % 
subY);
     }
 
     /**

Reply via email to