sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: In ResampledGridCoverage.render(…) converts the `sliceExtent` from target grid coordinates to source grid coordinates. Use ImageProcessor for creating ResampledImage.
Date Mon, 30 Mar 2020 21:56:02 GMT
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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 61196ea  In ResampledGridCoverage.render(…) converts the `sliceExtent` from target
grid coordinates to source grid coordinates. Use ImageProcessor for creating ResampledImage.
61196ea is described below

commit 61196ea667b7dc19565b006ec980f63d025b4c83
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Mar 30 23:23:25 2020 +0200

    In ResampledGridCoverage.render(…) converts the `sliceExtent` from target grid coordinates
to source grid coordinates.
    Use ImageProcessor for creating ResampledImage.
---
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  11 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   | 103 ++++++++++--------
 .../java/org/apache/sis/image/ImageProcessor.java  | 121 ++++++++++++++++++---
 .../java/org/apache/sis/image/ResampledImage.java  |  22 ++--
 .../org/apache/sis/internal/feature/Resources.java |   6 -
 .../sis/internal/feature/Resources.properties      |   1 -
 .../sis/internal/feature/Resources_fr.properties   |   1 -
 .../coverage/grid/ResampledGridCoverageTest.java   |  46 ++++++--
 .../coverage/j2d/BufferedGridCoverageTest.java     |   1 +
 9 files changed, 220 insertions(+), 92 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index a89bdb7..13733ad 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -551,11 +551,14 @@ public class GridCoverage2D extends GridCoverage {
      */
     @Override
     @SuppressWarnings("AssertWithSideEffects")
-    public RenderedImage render(final GridExtent sliceExtent) throws CannotEvaluateException
{
+    public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException {
+        final GridExtent extent = gridGeometry.extent;
         if (sliceExtent == null) {
-            return data;
+            if (extent == null || (data.getMinX() == 0 && data.getMinY() == 0)) {
+                return data;
+            }
+            sliceExtent = extent;
         }
-        final GridExtent extent = gridGeometry.extent;
         if (extent != null) {
             for (int i = min(sliceExtent.getDimension(), extent.getDimension()); --i >=
0;) {
                 if (i != xDimension && i != yDimension) {
@@ -600,7 +603,7 @@ public class GridCoverage2D extends GridCoverage {
              * may force data loading earlier than desired.
              */
             final ReshapedImage r = new ReshapedImage(data, xmin, ymin, xmax, ymax);
-            String error; assert (error = r.verify()) != null : error;
+            String error; assert (error = r.verify()) == null : error;
             return r.isIdentity() ? data : r;
         } catch (ArithmeticException e) {
             throw new CannotEvaluateException(e.getMessage(), e);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 2906aa6..6a06d63 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -30,7 +30,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.image.Interpolation;
-import org.apache.sis.image.ResampledImage;
+import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.util.DoubleDouble;
@@ -72,7 +72,7 @@ final class ResampledGridCoverage extends GridCoverage {
      * Note that an offset may exist between cell coordinates and pixel coordinates, so some
translations may need
      * to be concatenated with this transform on an image-by-image basis.
      */
-    private final MathTransform toSource;
+    private final MathTransform toSourceCorner, toSourceCenter;
 
     /**
      * The interpolation method to use for resampling images.
@@ -89,18 +89,22 @@ final class ResampledGridCoverage extends GridCoverage {
     /**
      * Creates a new grid coverage which will be the resampling of the given source.
      *
-     * @param  source         the coverage to resample.
-     * @param  domain         the grid extent, CRS and conversion from cell indices to CRS.
-     * @param  toSource       transform from cell coordinates in this coverage to source
coverage.
-     * @param  interpolation  the interpolation method to use for resampling images.
+     * @param  source          the coverage to resample.
+     * @param  domain          the grid extent, CRS and conversion from cell indices to CRS.
+     * @param  toSourceCorner  transform from cell corner coordinates in this coverage to
source coverage.
+     * @param  toSourceCenter  transform from cell center coordinates in this coverage to
source coverage.
+     * @param  interpolation   the interpolation method to use for resampling images.
      */
     private ResampledGridCoverage(final GridCoverage source, final GridGeometry domain,
-                               final MathTransform toSource, final Interpolation interpolation)
+                                  final MathTransform toSourceCorner,
+                                  final MathTransform toSourceCenter,
+                                  final Interpolation interpolation)
     {
         super(source, domain);
-        this.source        = source;
-        this.toSource      = toSource;
-        this.interpolation = interpolation;
+        this.source         = source;
+        this.toSourceCorner = toSourceCorner;
+        this.toSourceCenter = toSourceCenter;
+        this.interpolation  = interpolation;
         final GridExtent extent = domain.getExtent();
         long size1 = 0; int idx1 = 0;
         long size2 = 0; int idx2 = 1;
@@ -176,9 +180,12 @@ final class ResampledGridCoverage extends GridCoverage {
          * The following line may throw IncompleteGridGeometryException, which is desired
because if that
          * transform is missing, we can not continue (we have no way to guess it).
          */
-        MathTransform sourceGridToCRS = sourceGG.getGridToCRS(PixelInCell.CELL_CENTER);
+        MathTransform sourceCornerToCRS = sourceGG.getGridToCRS(PixelInCell.CELL_CORNER);
+        MathTransform sourceCenterToCRS = sourceGG.getGridToCRS(PixelInCell.CELL_CENTER);
         if (changeOfCRS != null) {
-            sourceGridToCRS = MathTransforms.concatenate(sourceGridToCRS, changeOfCRS.getMathTransform());
+            final MathTransform tr = changeOfCRS.getMathTransform();
+            sourceCornerToCRS = MathTransforms.concatenate(sourceCornerToCRS, tr);
+            sourceCenterToCRS = MathTransforms.concatenate(sourceCenterToCRS, tr);
         }
         /*
          * Compute the transform from target grid to target CRS. This transform may be unspecified,
@@ -186,11 +193,12 @@ final class ResampledGridCoverage extends GridCoverage {
          * point of interest.
          */
         GridExtent targetExtent = target.isDefined(GridGeometry.EXTENT) ? target.getExtent()
: null;
-        final MathTransform targetGridToCRS;
+        final MathTransform targetCenterToCRS;
         if (target.isDefined(GridGeometry.GRID_TO_CRS)) {
-            targetGridToCRS = target.getGridToCRS(PixelInCell.CELL_CENTER);
+            targetCenterToCRS = target.getGridToCRS(PixelInCell.CELL_CENTER);
             if (targetExtent == null) {
-                targetExtent = targetExtent(sourceGG, changeOfCRS, targetGridToCRS.inverse());
+                targetExtent = targetExtent(sourceGG.getExtent(), sourceCornerToCRS,
+                        target.getGridToCRS(PixelInCell.CELL_CORNER).inverse(), false);
             }
         } else {
             /*
@@ -198,8 +206,9 @@ final class ResampledGridCoverage extends GridCoverage {
              * toward right, and the second column gives the displacement when moving one
cell toward up (positive y).
              * More columns may exist in 3D, 4D, etc. cases.
              */
-            final MatrixSIS vectors = MatrixSIS.castOrCopy(sourceGridToCRS.derivative(
-                    new DirectPositionView.Double(sourceGG.getExtent().getPointOfInterest())));
+            final GridExtent sourceExtent = sourceGG.getExtent();
+            final MatrixSIS vectors = MatrixSIS.castOrCopy(sourceCenterToCRS.derivative(
+                    new DirectPositionView.Double(sourceExtent.getPointOfInterest())));
             /*
              * We will retain only the magnitudes of those vectors, in order to have directions
parallel with target
              * grid axes. There is one magnitude value for each target CRS dimension. If
there is more target grid
@@ -260,7 +269,8 @@ final class ResampledGridCoverage extends GridCoverage {
              * Apply the complete transform chain on source extent; this will give us a tentative
target extent.
              * This tentative extent will be compared with desired target.
              */
-            final GridExtent tentative = targetExtent(sourceGG, changeOfCRS, MathTransforms.linear(crsToGrid));
+            final GridExtent tentative = targetExtent(sourceExtent, sourceCornerToCRS,
+                                                      MathTransforms.linear(crsToGrid), true);
             if (targetExtent == null) {
                 // Create an extent of same size but with lower coordinates set to 0.
                 final long[] coordinates = new long[gridDim * 2];
@@ -288,7 +298,7 @@ final class ResampledGridCoverage extends GridCoverage {
                 offset.add(tmp);
                 crsToGrid.convertAfter(j, scale, offset);
             }
-            targetGridToCRS = MathTransforms.linear(crsToGrid.inverse());
+            targetCenterToCRS = MathTransforms.linear(crsToGrid.inverse());
         }
         /*
          * At this point all target grid geometry components are non-null.
@@ -296,7 +306,7 @@ final class ResampledGridCoverage extends GridCoverage {
          */
         ComparisonMode mode = ComparisonMode.IGNORE_METADATA;
         if (!target.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS | GridGeometry.CRS))
{
-            target = new GridGeometry(targetExtent, PixelInCell.CELL_CENTER, targetGridToCRS,
targetCRS);
+            target = new GridGeometry(targetExtent, PixelInCell.CELL_CENTER, targetCenterToCRS,
targetCRS);
             mode = ComparisonMode.APPROXIMATE;
         }
         if (sourceGG.equals(target, mode)) {
@@ -305,31 +315,32 @@ final class ResampledGridCoverage extends GridCoverage {
         /*
          * Complete the "target to source" transform.
          */
-        final MathTransform toSource = MathTransforms.concatenate(targetGridToCRS, sourceGridToCRS.inverse());
-        return new ResampledGridCoverage(source, target, toSource, interpolation).specialize();
+        final MathTransform targetCornerToCRS = target.getGridToCRS(PixelInCell.CELL_CORNER);
+        return new ResampledGridCoverage(source, target,
+                MathTransforms.concatenate(targetCornerToCRS, sourceCornerToCRS.inverse()),
+                MathTransforms.concatenate(targetCenterToCRS, sourceCenterToCRS.inverse()),
+                interpolation).specialize();
     }
 
     /**
-     * Computes a target grid extent by transforming the source grid extent. This method
expects a transform
-     * mapping cell centers, but will adjust for getting a result as if the transform was
mapping cell corners.
+     * Computes a target grid extent by transforming the source grid extent.
      *
-     * @param  source       the source grid geometry.
-     * @param  changeOfCRS  the coordinate operation from source CRS to target CRS, or {@code
null}.
-     * @param  crsToGrid    the transform from target CRS to target grid, mapping cell <em>centers</em>.
+     * @param  source       the source grid extent to transform.
+     * @param  cornerToCRS  transform from source grid corners to target CRS.
+     * @param  crsToGrid    transform from target CRS to target grid corners or centers.
+     * @param  center       whether {@code crsToGrid} maps cell centers ({@code true}) or
cell corners ({@code false}).
      * @return target grid extent.
      */
-    private static GridExtent targetExtent(final GridGeometry source, final CoordinateOperation
changeOfCRS,
-            final MathTransform crsToGrid) throws TransformException
+    private static GridExtent targetExtent(final GridExtent source, final MathTransform cornerToCRS,
+            final MathTransform crsToGrid, final boolean center) throws TransformException
     {
-        MathTransform extentMapper = source.getGridToCRS(PixelInCell.CELL_CORNER);
-        if (changeOfCRS != null) {
-            extentMapper = MathTransforms.concatenate(extentMapper, changeOfCRS.getMathTransform());
+        final MathTransform tr = MathTransforms.concatenate(cornerToCRS, crsToGrid);
+        final GeneralEnvelope bounds = source.toCRS(tr, tr, null);
+        if (center) {
+            final double[] vector = new double[bounds.getDimension()];
+            Arrays.fill(vector, 0.5);
+            bounds.translate(vector);       // Convert cell centers to cell corners.
         }
-        extentMapper = MathTransforms.concatenate(extentMapper, crsToGrid);
-        final GeneralEnvelope bounds = source.getExtent().toCRS(extentMapper, extentMapper,
null);
-        final double[] vector = new double[bounds.getDimension()];
-        Arrays.fill(vector, 0.5);
-        bounds.translate(vector);       // Convert cell centers to cell corners.
         return new GridExtent(bounds, GridRoundingMode.NEAREST, null, null, null);
     }
 
@@ -337,11 +348,14 @@ final class ResampledGridCoverage extends GridCoverage {
      * Returns a two-dimensional slice of resampled grid data as a rendered image.
      */
     @Override
-    public RenderedImage render(final GridExtent sliceExtent) {
-        if (sliceExtent != null) {
-            throw new CannotEvaluateException("Slice extent not yet supported.");
+    public RenderedImage render(GridExtent sliceExtent) {
+        if (sliceExtent != null) try {
+            final GeneralEnvelope bounds = sliceExtent.toCRS(toSourceCorner, toSourceCenter,
null);
+            sliceExtent = new GridExtent(bounds, GridRoundingMode.ENCLOSING, null, null,
null);
+        } catch (TransformException e) {
+            throw new CannotEvaluateException(e.getLocalizedMessage(), e);
         }
-        final RenderedImage image        = source.render(null);           // TODO: compute
slice.
+        final RenderedImage image        = source.render(sliceExtent);
         final GridExtent    sourceExtent = source.getGridGeometry().getExtent();
         final GridExtent    targetExtent = gridGeometry.getExtent();
         final Rectangle     bounds       = new Rectangle(Math.toIntExact(targetExtent.getSize(xDimension)),
@@ -359,7 +373,7 @@ final class ResampledGridCoverage extends GridCoverage {
                 Math.subtractExact(image.getMinX(), sourceExtent.getLow(xDimension)),
                 Math.subtractExact(image.getMinY(), sourceExtent.getLow(yDimension)));
 
-        final MathTransform toImage = MathTransforms.concatenate(pixelsToTransform, toSource,
transformToPixels);
+        final MathTransform toImage = MathTransforms.concatenate(pixelsToTransform, toSourceCenter,
transformToPixels);
         /*
          * Get fill values from background values declared for each band, if any.
          * If no background value is declared, default is 0 for integer data or
@@ -374,6 +388,9 @@ final class ResampledGridCoverage extends GridCoverage {
                 fillValues[i] = bg.get();
             }
         }
-        return new ResampledImage(bounds, toImage, image, interpolation, fillValues);
+        final ImageProcessor processor = new ImageProcessor();
+        processor.setInterpolation(interpolation);
+        processor.setFillValues(fillValues);
+        return processor.resample(bounds, toImage, image);
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index d6f0b87..67f44c1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -18,11 +18,13 @@ package org.apache.sis.image;
 
 import java.util.logging.Filter;
 import java.util.logging.LogRecord;
+import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.image.ImagingOpException;
 import java.awt.image.RasterFormatException;
+import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
@@ -78,6 +80,31 @@ import org.apache.sis.internal.coverage.j2d.TiledImage;
  */
 public class ImageProcessor {
     /**
+     * Interpolation to use during resample operations.
+     *
+     * @see #getInterpolation()
+     * @see #setInterpolation(Interpolation)
+     */
+    private Interpolation interpolation;
+
+    /**
+     * The values to use for pixels that can not be computed.
+     * This array may be {@code null} or may contain {@code null} elements.
+     *
+     * @see #getFillValues()
+     * @see #setFillValues(Number...)
+     */
+    private Number[] fillValues;
+
+    /**
+     * Whether the operations can be executed in parallel.
+     *
+     * @see #getExecutionMode()
+     * @see #setExecutionMode(Mode)
+     */
+    private Mode executionMode;
+
+    /**
      * Execution modes specifying whether operations can be executed in parallel.
      * If {@link #SEQUENTIAL}, operations are executed sequentially in the caller thread.
      * If {@link #PARALLEL}, some operations may be parallelized using an arbitrary number
of threads.
@@ -109,6 +136,16 @@ public class ImageProcessor {
     }
 
     /**
+     * Whether errors occurring during computation should be propagated or wrapped in a {@link
LogRecord}.
+     * If errors are wrapped in a {@link LogRecord}, this field specifies what to do with
the record.
+     * Only one log record is created for all tiles that failed for the same operation on
the same image.
+     *
+     * @see #getErrorAction()
+     * @see #setErrorAction(Filter)
+     */
+    private Filter errorAction;
+
+    /**
      * Specifies how exceptions occurring during calculation should be handled.
      * This enumeration provides common actions, but the set of values that can
      * be specified to {@link #setErrorAction(Filter)} is not limited to this enumeration.
@@ -148,30 +185,59 @@ public class ImageProcessor {
     }
 
     /**
-     * Whether the operations can be executed in parallel.
+     * Creates a new set of image operations with default configuration.
+     * The execution mode is initialized to {@link Mode#DEFAULT} and the error action to
{@link ErrorAction#THROW}.
+     */
+    public ImageProcessor() {
+        executionMode = Mode.DEFAULT;
+        errorAction   = ErrorAction.THROW;
+        interpolation = Interpolation.BILINEAR;
+    }
+
+    /**
+     * Returns the interpolation method to use during resample operations.
      *
-     * @see #getExecutionMode()
-     * @see #setExecutionMode(Mode)
+     * @return interpolation method to use during resample operations.
+     *
+     * @see #resample(Rectangle, MathTransform, RenderedImage)
      */
-    private Mode executionMode;
+    public Interpolation getInterpolation() {
+        return interpolation;
+    }
 
     /**
-     * Whether errors occurring during computation should be propagated or wrapped in a {@link
LogRecord}.
-     * If errors are wrapped in a {@link LogRecord}, this field specifies what to do with
the record.
-     * Only one log record is created for all tiles that failed for the same operation on
the same image.
+     * Sets the interpolation method to use during resample operations.
      *
-     * @see #getErrorAction()
-     * @see #setErrorAction(Filter)
+     * @param  method  interpolation method to use during resample operations.
+     *
+     * @see #resample(Rectangle, MathTransform, RenderedImage)
      */
-    private Filter errorAction;
+    public void setInterpolation(final Interpolation method) {
+        ArgumentChecks.ensureNonNull("method", method);
+        interpolation = method;
+    }
 
     /**
-     * Creates a new set of image operations with default configuration.
-     * The execution mode is initialized to {@link Mode#DEFAULT} and the error action to
{@link ErrorAction#THROW}.
+     * Returns the values to use for pixels that can not be computed.
+     * This method returns a copy of the array set by the last call to {@link #setFillValues(Number...)}.
+     *
+     * @return fill values to use for pixels that can not be computed, or {@code null} for
the defaults.
      */
-    public ImageProcessor() {
-        executionMode = Mode.DEFAULT;
-        errorAction   = ErrorAction.THROW;
+    public Number[] getFillValues() {
+        return (fillValues != null) ? fillValues.clone() : null;
+    }
+
+    /**
+     * Sets the values to use for pixels that can not be computed. The given array may be
{@code null} or may contain
+     * {@code null} elements for default values. Those defaults are zero for images storing
sample values as integers,
+     * or {@link Float#NaN} or {@link Double#NaN} for images storing sample values as floating
point numbers. If the
+     * given array contains less elements than the number of bands in an image, missing elements
will be assumed null.
+     * If the given array contains more elements than the number of bands, extraneous elements
will be ignored.
+     *
+     * @param  values  fill values to use for pixels that can not be computed, or {@code
null} for the defaults.
+     */
+    public void setFillValues(final Number... values) {
+        fillValues = (values != null) ? values.clone() : null;
     }
 
     /**
@@ -347,6 +413,31 @@ public class ImageProcessor {
     }
 
     /**
+     * Creates a new image which will resample the given image. The resampling operation
is defined
+     * by a non-linear transform from the <em>new</em> image to the specified
<em>source</em> image.
+     * That transform should map {@linkplain org.opengis.referencing.datum.PixelInCell#CELL_CENTER
pixel centers}.
+     * If that transform produces coordinates that are outside source envelope bounds, then
the corresponding pixels
+     * in the new image are set to {@linkplain #getFillValues() fill values}. Otherwise sample
values are interpolated
+     * using the method given by {@link #getInterpolation()}.
+     *
+     * @param  bounds    domain of pixel coordinates of resampled image.
+     * @param  toSource  conversion of pixel coordinates of this image to pixel coordinates
of {@code source} image.
+     * @param  source    the image to be resampled.
+     * @return resampled image (may be {@code source}).
+     */
+    public RenderedImage resample(final Rectangle bounds, final MathTransform toSource, final
RenderedImage source) {
+        ArgumentChecks.ensureNonNull("bounds",   bounds);
+        ArgumentChecks.ensureNonNull("toSource", toSource);
+        ArgumentChecks.ensureNonNull("source",   source);
+        if (toSource.isIdentity() && bounds.x == source.getMinX() && bounds.y
== source.getMinY()
+                && bounds.width == source.getWidth() && bounds.height ==
source.getHeight())
+        {
+            return source;
+        }
+        return new ResampledImage(bounds, toSource, source, interpolation, fillValues);
+    }
+
+    /**
      * Computes all tiles immediately, then return an image will all tiles ready.
      * Computations will use many threads if {@linkplain #getExecutionMode() execution mode}
is parallel.
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index 745de69..e5adcf4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -31,7 +31,6 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.internal.coverage.j2d.ImageLayout;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
-import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
@@ -113,15 +112,16 @@ public class ResampledImage extends ComputedImage {
      * @param  source         the image to be resampled.
      * @param  interpolation  the object to use for performing interpolations.
      * @param  fillValues     the values to use for pixels in this image that can not be
mapped to pixels in source image.
-     *                        The array length must be equal to the number of bands.
-     *                        May be {@code null} or contain {@code null} elements.
+     *                        May be {@code null} or contain {@code null} elements. If shorter
than the number of bands,
+     *                        missing values are assumed {@code null}. If longer than the
number of bands, extraneous
+     *                        values are ignored.
+     *
+     * @see ImageProcessor#resample(Rectangle, MathTransform, RenderedImage)
      */
-    public ResampledImage(final Rectangle bounds, final MathTransform toSource, final RenderedImage
source,
-                          final Interpolation interpolation, final Number[] fillValues)
+    protected ResampledImage(final Rectangle bounds, final MathTransform toSource, final
RenderedImage source,
+                             final Interpolation interpolation, final Number[] fillValues)
     {
         super(ImageLayout.DEFAULT.createCompatibleSampleModel(source), source);
-        ArgumentChecks.ensureNonNull("bounds", bounds);
-        ArgumentChecks.ensureNonNull("toSource", toSource);
         ArgumentChecks.ensureNonNull("interpolation", interpolation);
         ArgumentChecks.ensureStrictlyPositive("width",  width  = bounds.width);
         ArgumentChecks.ensureStrictlyPositive("height", height = bounds.height);
@@ -152,10 +152,6 @@ public class ResampledImage extends ComputedImage {
         offset[1] = interpolationSupportOffset(s.height);
         this.toSource = MathTransforms.concatenate(toSource, MathTransforms.translation(offset));
         final int numBands = ImageUtilities.getNumBands(source);
-        if (fillValues != null && fillValues.length != numBands) {
-            throw new IllegalArgumentException(Resources.format(
-                    Resources.Keys.MismatchedArrayLengthForBands_3, "fillValues", numBands,
fillValues.length));
-        }
         /*
          * Copy the `fillValues` either as an `int[]` or `double[]` array, depending on
          * whether the data type is an integer type or not. Null elements default to zero.
@@ -164,7 +160,7 @@ public class ResampledImage extends ComputedImage {
         if (ImageUtilities.isIntegerType(dataType)) {
             final int[] fill = new int[numBands];
             if (fillValues != null) {
-                for (int i=0; i<numBands; i++) {
+                for (int i=Math.min(fillValues.length, numBands); --i >= 0;) {
                     final Number f = fillValues[i];
                     if (f != null) fill[i] = f.intValue();
                 }
@@ -174,7 +170,7 @@ public class ResampledImage extends ComputedImage {
             final double[] fill = new double[numBands];
             Arrays.fill(fill, Double.NaN);
             if (fillValues != null) {
-                for (int i=0; i<numBands; i++) {
+                for (int i=Math.min(fillValues.length, numBands); --i >= 0;) {
                     final Number f = fillValues[i];
                     if (f != null) fill[i] = f.doubleValue();
                 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index 257ab9b..5a790bf 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -248,12 +248,6 @@ public final class Resources extends IndexedResourceBundle {
         public static final short IterationNotStarted = 37;
 
         /**
-         * The “{0}” array length is {2,number} while {1,number} was expected for matching
the number
-         * of bands.
-         */
-        public static final short MismatchedArrayLengthForBands_3 = 73;
-
-        /**
          * Image number of bands {0,number} does not match the number of sample dimensions
          * ({1,number}).
          */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
index 836b71b..3cb001c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
@@ -56,7 +56,6 @@ InsufficientBufferCapacity_3      = Data buffer capacity is insufficient
for a g
 InvalidExpression_2               = Invalid or unsupported \u201c{1}\u201d expression at
index {0}.
 IterationIsFinished               = Iteration is finished.
 IterationNotStarted               = Iteration did not started.
-MismatchedArrayLengthForBands_3   = The \u201c{0}\u201d array length is {2,number} while
{1,number} was expected for matching the number of bands.
 MismatchedBandCount_2             = Image number of bands {0,number} does not match the number
of sample dimensions ({1,number}).
 MismatchedBandSize                = The bands have different number of sample values.
 MismatchedDataType                = The bands store sample values using different data types.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
index 06c12c8..324af81 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
@@ -61,7 +61,6 @@ InsufficientBufferCapacity_3      = La capacit\u00e9 du buffer est insuffisante
 InvalidExpression_2               = Expression \u00ab\u202f{1}\u202f\u00bb invalide ou non-support\u00e9e
\u00e0 l\u2019index {0}.
 IterationIsFinished               = L\u2019it\u00e9ration est termin\u00e9e.
 IterationNotStarted               = L\u2019it\u00e9ration n\u2019a pas commenc\u00e9e.
-MismatchedArrayLengthForBands_3   = La longueur du tableau \u00ab\u202f{0}\u202f\u00bb est
{2,number} alors qu\u2019on attendait {1,number} pour correspondre au nombre de bandes.
 MismatchedBandCount_2             = Le nombre de bandes de l\u2019image ({0,number}) ne correspond
pas au nombre de dimensions d\u2019\u00e9chantillonnage ({1,number}).
 MismatchedBandSize                = Les bandes ont un nombre diff\u00e9rent de valeurs.
 MismatchedDataType                = Les bandes stockent leurs valeurs en utilisant des types
de donn\u00e9es diff\u00e9rents.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index efa98db..a89226d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -18,15 +18,17 @@ package org.apache.sis.coverage.grid;
 
 import java.util.Random;
 import java.awt.image.DataBuffer;
-import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.geometry.Envelope2D;
+import org.apache.sis.image.Interpolation;
 import org.apache.sis.image.TiledImageMock;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.opengis.referencing.datum.PixelInCell.CELL_CENTER;
+import static org.opengis.test.Assert.*;
 
 
 /**
@@ -42,7 +44,7 @@ public final strictfp class ResampledGridCoverageTest {
      * Creates a small grid coverage with arbitrary data. The rendered image will
      * have only one tile since testing tiling is not the purpose of this class.
      */
-    private static GridCoverage createGridCoverage() {
+    private static GridCoverage2D createGridCoverage() {
         final Random random  = TestUtilities.createRandomNumberGenerator();
         final int width  = random.nextInt(8) + 3;
         final int height = random.nextInt(8) + 3;
@@ -68,15 +70,41 @@ public final strictfp class ResampledGridCoverageTest {
      * Tests application of an identity transform.
      * We expect the source coverage to be returned unchanged.
      *
+     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testIdentity() throws TransformException {
-        final GridCoverage source = createGridCoverage();
+    public void testIdentity() throws FactoryException, TransformException {
+        final GridCoverage2D source = createGridCoverage();
         GridGeometry gg = source.getGridGeometry();
-        gg = new GridGeometry(null, PixelInCell.CELL_CENTER, gg.getGridToCRS(PixelInCell.CELL_CENTER),
-                              gg.getCoordinateReferenceSystem());
-        final GridCoverage target = new GridCoverageProcessor().resample(source, gg);
-        assertSame(source, target);
+        gg = new GridGeometry(null, CELL_CENTER, gg.getGridToCRS(CELL_CENTER), gg.getCoordinateReferenceSystem());
+        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        assertSame("Identity transform should result in same coverage.", source, target);
+    }
+
+    /**
+     * Tests application of a translation.
+     *
+     * @throws FactoryException if transformation between CRS can not be computed.
+     * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
+     */
+    @Test
+    public void testTranslation() throws FactoryException, TransformException {
+        final GridCoverage2D source = createGridCoverage();
+        GridGeometry gg = source.getGridGeometry();
+        gg = new GridGeometry(null, CELL_CENTER, null, gg.getCoordinateReferenceSystem());
+        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        /*
+         * We let ResampledGridCoverageTest chooses the `GridExtent` itself. Since the default
behavior
+         * is to create extents starting at zero, it should be different than source extent
unless the
+         * random number generator selected a (0,0) location by coincidence.
+         */
+        assertTrue("GridExtent.startsAtZero", target.getGridGeometry().getExtent().startsAtZero());
+        if (source.getGridGeometry().getExtent().startsAtZero()) {
+            assertSame("Identity transform should result in same coverage.", source, target);
+        } else {
+            assertNotSame(source, target);
+            assertInstanceOf("Expected specialized type for 2D.", GridCoverage2D.class, target);
+        }
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
index 6145f84..fe957aa 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
@@ -51,6 +51,7 @@ public final strictfp class BufferedGridCoverageTest extends GridCoverage2DTest
      * @param  sd    the sample dimensions of the coverage to create.
      * @return the coverage instance to test, with above-cited values.
      */
+    @Override
     protected GridCoverage createTestCoverage(final GridGeometry grid, final List<SampleDimension>
sd) {
         /*
          * Create the grid coverage, gets its image and set values directly as short integers.


Mime
View raw message