sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Fix an incorrect computation of image (minX, minY) coordinates when the `sliceExtent` has some pixels outside the image bounds. Allow `ImageRenderer` to use an implementation other than `BufferedImage` when the (minX, minY) are different than (0, 0).
Date Fri, 05 Jun 2020 17:00:53 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

commit 6bbaee11a33ed0db8fd24d5622b078bd337d03b1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Jun 5 17:25:21 2020 +0200

    Fix an incorrect computation of image (minX, minY) coordinates when the `sliceExtent`
has some pixels outside the image bounds.
    Allow `ImageRenderer` to use an implementation other than `BufferedImage` when the (minX,
minY) are different than (0,0).
---
 .../sis/coverage/grid/GridCoverageBuilder.java     | 33 +++++++++-
 .../apache/sis/coverage/grid/ImageRenderer.java    | 71 +++++++++++----------
 .../apache/sis/coverage/grid/SliceGeometry.java    | 35 ++++++++++-
 .../internal/coverage/j2d/DeferredProperty.java    | 73 ++++++++++++++++++++++
 .../sis/internal/coverage/j2d/TiledImage.java      | 42 ++++++++++++-
 .../sis/coverage/grid/GridCoverage2DTest.java      | 47 +++++++++-----
 .../coverage/grid/ResampledGridCoverageTest.java   |  2 +-
 .../org/apache/sis/image/InterpolationTest.java    |  2 +-
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  8 +++
 9 files changed, 259 insertions(+), 54 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index ad15613..0a2ae54 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Hashtable;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
@@ -160,6 +161,13 @@ public class GridCoverageBuilder {
     private long flippedAxes;
 
     /**
+     * The properties to give to the image, or {@code null} if none.
+     *
+     * @see #addImageProperty(String, Object)
+     */
+    private Hashtable<String,Object> properties;
+
+    /**
      * Creates an initially empty builder.
      */
     public GridCoverageBuilder() {
@@ -392,6 +400,27 @@ public class GridCoverageBuilder {
     }
 
     /**
+     * Adds a value associated to an image property. This method can be invoked only once
for each {@code key}.
+     * Those properties will be given to the {@link RenderedImage} created by the {@link
#build()} method.
+     *
+     * @param  key    key of the property to set.
+     * @param  value  value to associate to the given key.
+     * @throws IllegalArgumentException if a value is already associated to the given key.
+     *
+     * @since 1.1
+     */
+    public void addImageProperty(final String key, final Object value) {
+        ArgumentChecks.ensureNonNull("key",   key);
+        ArgumentChecks.ensureNonNull("value", value);
+        if (properties == null) {
+            properties = new Hashtable<>();
+        }
+        if (properties.putIfAbsent(key, value) != null) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1,
key));
+        }
+    }
+
+    /**
      * Creates the grid coverage from the domain, ranges and values given to setter methods.
      * The returned coverage is often a {@link GridCoverage2D} instance, but not necessarily.
      *
@@ -442,9 +471,9 @@ public class GridCoverageBuilder {
                  * and fallback on TiledImage only if the BufferedImage can not be created.
                  */
                 if (raster instanceof WritableRaster && raster.getMinX() == 0 &&
raster.getMinY() == 0) {
-                    image = new BufferedImage(colors, (WritableRaster) raster, false, null);
+                    image = new BufferedImage(colors, (WritableRaster) raster, false, properties);
                 } else {
-                    image = new TiledImage(colors, raster.getWidth(), raster.getHeight(),
0, 0, raster);
+                    image = new TiledImage(properties, colors, raster.getWidth(), raster.getHeight(),
0, 0, raster);
                 }
             }
             /*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index 7b9f8d3..3d32da1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -16,8 +16,8 @@
  */
 package org.apache.sis.coverage.grid;
 
-import java.util.Arrays;
 import java.util.Hashtable;
+import java.util.Arrays;
 import java.nio.Buffer;
 import java.awt.Point;
 import java.awt.Rectangle;
@@ -34,8 +34,10 @@ import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 import org.apache.sis.coverage.MismatchedCoverageRangeException;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.coverage.j2d.DeferredProperty;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 import org.apache.sis.internal.coverage.j2d.RasterFactory;
+import org.apache.sis.internal.coverage.j2d.TiledImage;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.util.NullArgumentException;
@@ -96,6 +98,7 @@ import static org.apache.sis.image.PlanarImage.GRID_GEOMETRY_KEY;
 public class ImageRenderer {
     /**
      * The grid geometry of the {@link GridCoverage} specified at construction time.
+     * Never {@code null}.
      */
     private final GridGeometry geometry;
 
@@ -285,8 +288,8 @@ public class ImageRenderer {
         }
         width   = Math.incrementExact(Math.toIntExact(xmax - xmin));
         height  = Math.incrementExact(Math.toIntExact(ymax - ymin));
-        imageX  = Math.toIntExact(Math.subtractExact(xreq, xmin));
-        imageY  = Math.toIntExact(Math.subtractExact(yreq, ymin));
+        imageX  = Math.toIntExact(Math.subtractExact(xmin, xreq));
+        imageY  = Math.toIntExact(Math.subtractExact(ymin, yreq));
         offsetX = Math.subtractExact(xmin, xcov);
         offsetY = Math.subtractExact(ymin, ycov);
         /*
@@ -378,7 +381,7 @@ public class ImageRenderer {
                 ig = new SliceGeometry(geometry, sliceExtent, gridDimensions, null)
                         .reduce(new GridExtent(imageX, imageY, width, height), dimCRS);
             } catch (FactoryException e) {
-                throw canNotCompute(e);
+                throw SliceGeometry.canNotCompute(e);
             }
             if (dimCRS == GridCoverage2D.BIDIMENSIONAL) {
                 imageGeometry = ig;
@@ -415,7 +418,8 @@ public class ImageRenderer {
      * @since 1.1
      */
     public void addProperty(final String key, final Object value) {
-        ArgumentChecks.ensureNonNull("key", key);
+        ArgumentChecks.ensureNonNull("key",   key);
+        ArgumentChecks.ensureNonNull("value", value);
         if (!GRID_GEOMETRY_KEY.equals(key)) {
             if (properties == null) {
                 properties = new Hashtable<>();
@@ -614,17 +618,26 @@ public class ImageRenderer {
      * @throws ArithmeticException if a property of the image to construct exceeds the capacity
of 32 bits integers.
      */
     public RenderedImage image() {
-        WritableRaster raster = raster();
-        ColorModel colors = ColorModelFactory.createColorModel(bands, visibleBand, buffer.getDataType(),
ColorModelFactory.GRAYSCALE);
-        final Untiled image = new Untiled(colors, raster, properties);
-        if (imageGeometry != null) {
-            image.geometry = imageGeometry;
-        } else if (isSameGeometry(GridCoverage2D.BIDIMENSIONAL)) {
-            image.geometry = imageGeometry = geometry;
-        } else {
-            image.supplier = new SliceGeometry(geometry, sliceExtent, gridDimensions, null);
+        final WritableRaster raster = raster();
+        final ColorModel colors = ColorModelFactory.createColorModel(bands, visibleBand,
+                                    buffer.getDataType(), ColorModelFactory.GRAYSCALE);
+
+        SliceGeometry supplier = null;
+        if (imageGeometry == null) {
+            if (isSameGeometry(GridCoverage2D.BIDIMENSIONAL)) {
+                imageGeometry = geometry;
+            } else {
+                supplier = new SliceGeometry(geometry, sliceExtent, gridDimensions, null);
+            }
+        }
+        if ((imageX | imageY) == 0) {
+            return new Untiled(colors, raster, properties, imageGeometry, supplier);
         }
-        return image;
+        if (properties == null) {
+            properties = new Hashtable<>();
+        }
+        properties.putIfAbsent(GRID_GEOMETRY_KEY, (supplier != null) ? new DeferredProperty(supplier)
: imageGeometry);
+        return new TiledImage(properties, colors, width, height, 0, 0, raster);
     }
 
     /**
@@ -650,8 +663,12 @@ public class ImageRenderer {
         /**
          * Creates a new buffered image wrapping the given raster.
          */
-        Untiled(final ColorModel colors, final WritableRaster raster, final Hashtable<?,?>
properties) {
+        Untiled(final ColorModel colors, final WritableRaster raster, final Hashtable<?,?>
properties,
+                final GridGeometry geometry, final SliceGeometry supplier)
+        {
             super(colors, raster, false, properties);
+            this.geometry = geometry;
+            this.supplier = supplier;
         }
 
         /**
@@ -675,25 +692,15 @@ public class ImageRenderer {
                 return super.getProperty(key);
             }
             synchronized (this) {
-                if (geometry == null) try {
-                    final GridExtent extent = new GridExtent(getMinX(), getMinY(), getWidth(),
getHeight());
-                    geometry = supplier.reduce(extent, GridCoverage2D.BIDIMENSIONAL);
-                } catch (FactoryException e) {
-                    throw canNotCompute(e);
+                if (geometry == null) {
+                    final SliceGeometry s = supplier;
+                    if (s != null) {
+                        supplier = null;                // Let GC do its work.
+                        geometry = s.apply(this);
+                    }
                 }
-                supplier = null;                // Let GC do its work.
             }
             return geometry;
         }
     }
-
-    /**
-     * Invoked if an error occurred while computing the {@link #getImageGeometry(int)} value.
-     * This exception should never occur actually, unless a custom factory implementation
is
-     * used (instead of the Apache SIS default) and there is a problem with that factory.
-     */
-    private static ImagingOpException canNotCompute(final FactoryException e) {
-        throw (ImagingOpException) new ImagingOpException(
-                Errors.format(Errors.Keys.CanNotCompute_1, "ImageGeometry")).initCause(e);
-    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
index f850d27..fdb017d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.coverage.grid;
 
+import java.util.function.Function;
 import java.awt.image.RenderedImage;
+import java.awt.image.ImagingOpException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
@@ -33,18 +35,22 @@ import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
  * Builds a grid geometry for a slice in a {@link GridCoverage}. This is the implementation
of
  * {@link GridGeometry#reduce(int...)} and {@link ImageRenderer#getImageGeometry(int)} methods.
  *
+ * <p>This class implements {@link Function} for allowing {@code apply(…)} to be
invoked from outside this package.
+ * That function is invoked (indirectly) by {@link org.apache.sis.internal.coverage.j2d.TiledImage#getProperty(String)}.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  * @since   1.1
  * @module
  */
-final class SliceGeometry {
+final class SliceGeometry implements Function<RenderedImage, GridGeometry> {
     /**
      * The coverage grid geometry from which to take a slice.
      */
@@ -83,6 +89,23 @@ final class SliceGeometry {
     }
 
     /**
+     * Computes the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}
+     * property value for the given image.
+     *
+     * @param  image  the image for which to compute the image geometry.
+     * @throws ImagingOpException if the property can not be computed.
+     */
+    @Override
+    public GridGeometry apply(final RenderedImage image) {
+        try {
+            final GridExtent extent = new GridExtent(image.getMinX(), image.getMinY(), image.getWidth(),
image.getHeight());
+            return reduce(extent, GridCoverage2D.BIDIMENSIONAL);
+        } catch (FactoryException e) {
+            throw canNotCompute(e);
+        }
+    }
+
+    /**
      * Creates a new grid geometry over the specified dimensions of the geometry specified
at construction time.
      * The number of grid dimensions will be the length of the {@link #gridDimensions} array,
and the number of
      * CRS dimensions will be reduced by the same amount.
@@ -341,4 +364,14 @@ final class SliceGeometry {
             return MathTransforms.concatenate(tr1, tr2);
         }
     }
+
+    /**
+     * Invoked if an error occurred while computing the {@link ImageRenderer#getImageGeometry(int)}
value.
+     * This exception should never occur actually, unless a custom factory implementation
is used
+     * (instead of the Apache SIS default) and there is a problem with that factory.
+     */
+    static ImagingOpException canNotCompute(final FactoryException e) {
+        throw (ImagingOpException) new ImagingOpException(
+                Errors.format(Errors.Keys.CanNotCompute_1, "ImageGeometry")).initCause(e);
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/DeferredProperty.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/DeferredProperty.java
new file mode 100644
index 0000000..2473e4e
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/DeferredProperty.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.coverage.j2d;
+
+import java.util.function.Function;
+import java.awt.image.RenderedImage;
+
+
+/**
+ * An image property for which the computation is differed.
+ * This special kind of properties is recognized by the following methods:
+ *
+ * <ul>
+ *   <li>{@link TiledImage#getProperty(String)}</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final class DeferredProperty {
+    /**
+     * The value, or {@code null} if not yet computed.
+     */
+    private Object value;
+
+    /**
+     * The function computing the {@linkplain #value}. This function is reset to {@code null}
+     * after the value has been computed for allowing the garbage collector to do its work.
+     */
+    private Function<RenderedImage, ?> provider;
+
+    /**
+     * Creates a new deferred property.
+     *
+     * @param  provider  function computing the {@linkplain #value}.
+     */
+    public DeferredProperty(final Function<RenderedImage, ?> provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * Returns the property value, which is computed when this method is first invoked.
+     *
+     * @param  image  the image for which to compute the property value.
+     * @return the property value, or {@code null} if it can not be computed.
+     */
+    final synchronized Object compute(final RenderedImage image) {
+        if (value == null) {
+            final Function<RenderedImage, ?> p = provider;
+            if (p != null) {
+                provider = null;            // Clear first in case an exception is thrown
below.
+                value = p.apply(image);
+            }
+        }
+        return value;
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
index 2060815..1d40cc0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
@@ -16,10 +16,14 @@
  */
 package org.apache.sis.internal.coverage.j2d;
 
+import java.util.Map;
+import java.util.Collections;
+import java.awt.Image;
 import java.awt.image.Raster;
 import java.awt.image.ColorModel;
 import java.awt.image.SampleModel;
 import org.apache.sis.image.PlanarImage;
+import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.util.ArgumentChecks;
 
 
@@ -59,11 +63,17 @@ public final class TiledImage extends PlanarImage {
     private final Raster[] tiles;
 
     /**
+     * Image properties, or an empty map if none.
+     */
+    private final Map<String,Object> properties;
+
+    /**
      * Creates a new tiled image. The first tile in the given array must be the
      * one located at the minimal tile indices. All tiles must have the same size
      * and the same sample model and must be sorted in row-major fashion
      * (this is not verified in current version, but may be in the future).
      *
+     * @param properties  image properties, or {@code null} if none.
      * @param colorModel  the color model, or {@code null} if none.
      * @param width       number of pixels along X axis in the whole rendered image.
      * @param height      number of pixels along Y axis in the whole rendered image.
@@ -71,8 +81,9 @@ public final class TiledImage extends PlanarImage {
      * @param minTileY    minimum tile index in the Y direction.
      * @param tiles       the tiles. Must contains at least one element.
      */
-    public TiledImage(final ColorModel colorModel, final int width, final int height,
-                      final int minTileX, final int minTileY, final Raster... tiles)
+    public TiledImage(final Map<String,Object> properties, final ColorModel colorModel,
+                      final int width, final int height, final int minTileX, final int minTileY,
+                      final Raster... tiles)
     {
         ArgumentChecks.ensureStrictlyPositive("width",  width);
         ArgumentChecks.ensureStrictlyPositive("height", height);
@@ -83,6 +94,7 @@ public final class TiledImage extends PlanarImage {
         this.minTileX   = minTileX;
         this.minTileY   = minTileY;
         this.tiles      = tiles;
+        this.properties = (properties != null) ? JDK9.copyOf(properties) : Collections.emptyMap();
     }
 
     /**
@@ -101,6 +113,32 @@ public final class TiledImage extends PlanarImage {
         return tiles[0].getSampleModel();
     }
 
+    /**
+     * Gets a property from this image.
+     *
+     * @param  key  the name of the property to get.
+     * @return the property value, or {@link Image#UndefinedProperty} if none.
+     */
+    @Override
+    public Object getProperty(final String key) {
+        Object value = properties.getOrDefault(key, Image.UndefinedProperty);
+        if (value instanceof DeferredProperty) {
+            value = ((DeferredProperty) value).compute(this);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the names of all recognized properties,
+     * or {@code null} if this image has no properties.
+     *
+     * @return names of all recognized properties, or {@code null} if none.
+     */
+    @Override
+    public String[] getPropertyNames() {
+        final int n = properties.size();
+        return (n == 0) ? null : properties.keySet().toArray(new String[n]);
+    }
 
     /**
      * Returns the minimum <var>x</var> coordinate (inclusive) of this image.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 84ecda3..91170a0 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -22,6 +22,7 @@ import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import org.opengis.geometry.DirectPosition;
@@ -115,7 +116,9 @@ public strictfp class GridCoverage2DTest extends TestCase {
      */
     private static void assertSamplesEqual(final GridCoverage coverage, final double[][]
expected) {
         final Raster raster = coverage.render(null).getData();
+        assertEquals("height", expected.length, raster.getHeight());
         for (int y=0; y<expected.length; y++) {
+            assertEquals("width", expected[y].length, raster.getWidth());
             for (int x=0; x<expected[y].length; x++) {
                 double value = raster.getSampleDouble(x, y, 0);
                 assertEquals(expected[y][x], value, STRICT);
@@ -213,12 +216,13 @@ public strictfp class GridCoverage2DTest extends TestCase {
     }
 
     /**
-     * Ensures that calling {@link GridCoverage#render(GridExtent)} with a sub-extent (crop
operation)
+     * Verifies that calling {@link GridCoverage#render(GridExtent)} with a sub-extent (crop
operation)
      * returns precisely the requested area, not a smaller or bigger one.
      */
     @Test
-    public void render_of_subextent() {
+    public void testRenderOfSubextent() {
         final GridCoverage coverage = createTestCoverage();
+        RenderedImage result;
         /*
          * Row extraction:
          *   - Expected size (2,1) is verified by `assertPixelsEqual(…)`.
@@ -226,12 +230,10 @@ public strictfp class GridCoverage2DTest extends TestCase {
          *   - Pixel source(0, 1) → output(0, 0)
          *   - Pixel source(1, 1) → output(1, 0)
          */
-        final GridExtent singleRow = new GridExtent(2, 1).translate(0, 1);
-        assertPixelsEqual(coverage.render(null), new Rectangle(0, 1, 2, 1),     // Expected
values.
-                          coverage.render(singleRow), null);                    // Actual
values to test.
-        assertSamplesEqual(coverage, new double[][] {
-            {2, 5}                                      // Redundant with previous assert,
but let be explicit.
-        });
+        final GridExtent singleRow = new GridExtent(GRID_SIZE, 1).translate(0, 1);
+        result = coverage.render(singleRow);
+        assertInstanceOf("render", BufferedImage.class, result);
+        assertPixelsEqual(coverage.render(null), new Rectangle(0, 1, GRID_SIZE, 1), result,
null);
         /*
          * Column extraction:
          *   - Expected size (1,2) is verified by `assertPixelsEqual(…)`.
@@ -239,13 +241,28 @@ public strictfp class GridCoverage2DTest extends TestCase {
          *   - Pixel source(1, 0) → output(0, 0)
          *   - Pixel source(1, 1) → output(0, 1)
          */
-        final GridExtent singleCol = new GridExtent(1, 2).translate(1, 0);
-        assertPixelsEqual(coverage.render(null), new Rectangle(1, 0, 1, 2),     // Expected
values.
-                          coverage.render(singleCol), null);                    // Actual
values to test.
-        assertSamplesEqual(coverage, new double[][] {
-            { 2},                                       // Redundant with previous assert,
but let be explicit.
-            {-5}
-        });
+        final GridExtent singleCol = new GridExtent(1, GRID_SIZE).translate(1, 0);
+        result = coverage.render(singleCol);
+        assertInstanceOf("render", BufferedImage.class, result);
+        assertPixelsEqual(coverage.render(null), new Rectangle(1, 0, 1, GRID_SIZE), result,
null);
+    }
+
+    /**
+     * Verifies that calling {@link GridCoverage#render(GridExtent)} with a larger extent
+     * returns an image with the appropriate offset.
+     */
+    @Test
+    public void testRenderOfLargerExtent() {
+        final GridCoverage coverage = createTestCoverage();
+        final GridExtent sliceExtent = new GridExtent(null,
+                new long[] {-5, -2},
+                new long[] {GRID_SIZE + 3, GRID_SIZE + 5}, true);
+        final RenderedImage result = coverage.render(sliceExtent);
+        assertEquals("minX",   5,         result.getMinX());
+        assertEquals("minY",   2,         result.getMinY());
+        assertEquals("width",  GRID_SIZE, result.getWidth());
+        assertEquals("height", GRID_SIZE, result.getHeight());
+        assertPixelsEqual(coverage.render(null), null, result, null);
     }
 
     /**
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 b184602..841e08d 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
@@ -163,7 +163,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
         final int minX = random.nextInt(5) - 2;
         final int minY = random.nextInt(5) - 2;
         GridGeometry gg = createGridGeometryND(withTime ? HardCodedCRS.WGS84_4D : HardCodedCRS.WGS84_3D,
0, 1, 2, 3, false);
-        final TiledImage shiftedImage = new TiledImage(
+        final TiledImage shiftedImage = new TiledImage(null,
                 image.getColorModel(),
                 image.getWidth(), image.getHeight(),        // Image size
                 random.nextInt(32) - 10,                    // minTileX
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/InterpolationTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/InterpolationTest.java
index dbf6f6f..3f0e6ad 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/InterpolationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/InterpolationTest.java
@@ -88,7 +88,7 @@ public final strictfp class InterpolationTest extends TestCase {
                 raster.setSample(x, y, 0, value++);
             }
         }
-        final TiledImage source = new TiledImage(null, XUP-XMIN, YUP-YMIN, 0, 0, raster);
+        final TiledImage source = new TiledImage(null, null, XUP-XMIN, YUP-YMIN, 0, 0, raster);
         assertNull(source.verify());
         iterator = new PixelIterator.Builder().setWindowSize(new Dimension(support, support)).create(source);
         window   = iterator.createWindow(TransferType.DOUBLE);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
index cc2d3ed..8f4b7f1 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
@@ -30,6 +30,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 
@@ -93,6 +94,13 @@ public final class JDK9 {
     }
 
     /**
+     * Placeholder for {@code Map.of(...)} (actually a JDK10 method).
+     */
+    public static <K,V> Map<K,V> copyOf(final Map<K,V> map) {
+        return map.size() < 2 ? CollectionsExt.compact(map) : new HashMap<>(map);
+    }
+
+    /**
      * Place holder for {@code Buffer.slice()}.
      *
      * @param  b the buffer to slice.


Mime
View raw message