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 91538a6510745ce6c6212da2e19b5b668ed2671b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Apr 15 12:13:26 2026 +0200

    Complete the help method for creating a coverage from functions.
    A variant working of floating-point values is added.
    The image size argument is replaced by image bounds.
---
 .../sis/coverage/grid/GridCoverageBuilder.java     | 250 ++++++++++++---------
 .../main/org/apache/sis/image/BandedIterator.java  |  27 ++-
 .../apache/sis/image/WritablePixelIterator.java    |  38 +++-
 .../sis/coverage/grid/GridCoverageBuilderTest.java |   2 +-
 .../sis/storage/geotiff/GeoTiffStoreTest.java      |  18 +-
 5 files changed, 217 insertions(+), 118 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index 1740406167..0c6d5a0de1 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Hashtable;
 import java.util.Objects;
 import java.util.function.IntBinaryOperator;
+import java.util.function.DoubleBinaryOperator;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
@@ -38,6 +39,7 @@ import java.awt.image.WritableRenderedImage;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.image.DataType;
+import org.apache.sis.image.ImageLayout;
 import org.apache.sis.image.PlanarImage;
 import org.apache.sis.image.WritablePixelIterator;
 import org.apache.sis.image.internal.shared.ColorScaleBuilder;
@@ -158,11 +160,19 @@ public class GridCoverageBuilder {
      */
     private DataBuffer buffer;
 
+    /**
+     * Providers of sample values as floating-point values, one provider per 
band.
+     * Exactly one of {@code image}, {@link #raster}, {@link #buffer} and 
{@code calc*} fields shall be non-null.
+     *
+     * @see #setValues(DataType, Rectangle, Dimension, DoubleBinaryOperator...)
+     */
+    private DoubleBinaryOperator[] calcAsDoubles;
+
     /**
      * Providers of sample values as integers, one provider per band.
      * Exactly one of {@code image}, {@link #raster}, {@link #buffer} and 
{@code calc*} fields shall be non-null.
      *
-     * @see #setValues(DataType, Dimension, IntBinaryOperator...)
+     * @see #setIntegerValues(DataType, Rectangle, Dimension, 
IntBinaryOperator...)
      */
     private IntBinaryOperator[] calcAsIntegers;
 
@@ -174,22 +184,24 @@ public class GridCoverageBuilder {
     private DataType dataType;
 
     /**
-     * The preferred tile size, or {@code null} if unspecified.
-     * This dimension is used only if it can be used without copying the data.
-     * For example if the user specified a fully constructed image, it will 
not be re-tiled.
-     *
-     * @see #setPreferredTileSize(Dimension)
+     * The preferred tile size, or {@code null} for automatic.
+     * If non-null, the {@linkplain GridGeometry#getExtent() domain extent} 
will be divided
+     * in tiles of the specified size if this division can be done without 
copying the data.
+     * Otherwise (for example, if the user specified a {@linkplain 
#setValues(RenderedImage)
+     * fully constructed image}, which is already tiled), this parameter is 
ignored.
      */
     private Dimension tileSize;
 
     /**
-     * The image size, or {@code null} if unspecified. This size needs to be 
specified only if sample
-     * values were specified as a buffer or as a function without information 
about the grid extent.
+     * The image size and the pixel coordinate of the upper-left corner, or 
{@code null} if unspecified.
+     * This size needs to be specified only if sample values were specified as 
a buffer or as a function
+     * without information about the grid extent.
      *
      * @see #setValues(DataBuffer, Dimension)
-     * @see #setValues(DataType, Dimension, IntBinaryOperator...)
+     * @see #setValues(DataType, Rectangle, Dimension, DoubleBinaryOperator...)
+     * @see #setIntegerValues(DataType, Rectangle, Dimension, 
IntBinaryOperator...)
      */
-    private Dimension imageSize;
+    private Rectangle imageBounds;
 
     /**
      * Set of grid axes to reverse, as a bit mask. For any dimension 
<var>i</var>, the bit
@@ -233,7 +245,7 @@ public class GridCoverageBuilder {
      * The given envelope should contain all pixel area. For example, the
      * {@linkplain Envelope#getLowerCorner() envelope lower corner} should 
locate the lower-left
      * (or upper-left, depending on <var>y</var> axis orientation) pixel 
corner, not pixel center.
-     * If the given envelope contains a CRS, then that CRS will be the 
coverage CRS.
+     * If the given envelope contains a <abbr>CRS</abbr>, then that CRS will 
be the coverage CRS.
      * A transform from grid indices to domain coordinates will be created 
automatically.
      * That transform will map grid dimensions to envelope dimensions in the 
same order
      * (i.e. the matrix representation of the affine transform will be 
diagonal,
@@ -341,35 +353,17 @@ public class GridCoverageBuilder {
     }
 
     /**
-     * Sets the preferred tile size, or {@code null} if no preference.
-     * This values is used only if it can be used without copying the data.
-     * For example if the user specified a fully constructed image, that image 
will not be re-tiled.
-     *
-     * @param  tileSize  the desired tile size, or {@code null} if no 
preference.
-     * @return {@code this} for method invocation chaining.
-     *
-     * @see #setValues(DataType, Dimension, IntBinaryOperator...)
-     *
-     * @since  1.7
-     */
-    public GridCoverageBuilder setPreferredTileSize(Dimension tileSize) {
-        if (tileSize != null) {
-            tileSize = new Dimension(tileSize);
-        }
-        this.tileSize = tileSize;
-        return this;
-    }
-
-    /**
-     * Clears all the ways to specify sample values, together with 
dependencies such as image size.
+     * Clears all the ways to specify sample values,
+     * together with dependencies such as image bounds and tile size.
      */
     private void clearValues() {
-        image     = null;
-        raster    = null;
-        buffer    = null;
-        dataType  = null;
-        tileSize  = null;
-        imageSize = null;
+        image          = null;
+        raster         = null;
+        buffer         = null;
+        dataType       = null;
+        tileSize       = null;
+        imageBounds    = null;
+        calcAsDoubles  = null;
         calcAsIntegers = null;
     }
 
@@ -423,62 +417,108 @@ public class GridCoverageBuilder {
      *
      * @param  data  the data buffer to be wrapped in a {@code GridCoverage}. 
Cannot be {@code null}.
      * @param  size  the image size in pixels, or {@code null} if unspecified. 
If null, then the image
-     *               size will be taken from the {@linkplain 
GridGeometry#getExtent() grid extent}.
+     *               bounds will be taken from the {@linkplain 
GridGeometry#getExtent() grid extent}.
      * @return {@code this} for method invocation chaining.
-     * @throws IllegalArgumentException if {@code width} or {@code height} is 
negative or equals to zero.
+     * @throws IllegalArgumentException if {@code size} is empty.
      */
-    public GridCoverageBuilder setValues(final DataBuffer data, Dimension 
size) {
+    public GridCoverageBuilder setValues(final DataBuffer data, final 
Dimension size) {
         ArgumentChecks.ensureNonNull("data", data);
         clearValues();
         if (size != null) {
-            size = new Dimension(size);
-            ArgumentChecks.ensureStrictlyPositive("width",  size.width);
-            ArgumentChecks.ensureStrictlyPositive("height", size.height);
-            final int length = Math.multiplyExact(size.width, size.height);
+            final var bounds = new Rectangle(size);
+            ArgumentChecks.ensureStrictlyPositive("width",  bounds.width);
+            ArgumentChecks.ensureStrictlyPositive("height", bounds.height);
+            final int length = Math.multiplyExact(bounds.width, bounds.height);
             final int capacity = data.getSize();
             if (length > capacity) {
                 throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 
length, capacity));
             }
+            imageBounds = bounds;
         }
-        imageSize = size;
-        buffer    = data;
+        buffer = data;
         return this;
     }
 
     /**
-     * Sets a two-dimensional slice to the values computed by the given 
functions.
-     * The number of bands is the number of functions and the color model 
defaults to gray scale.
-     * The tiling is unspecified and may change in future versions of Apache 
<abbr>SIS</abbr>.
+     * Sets a two-dimensional slice to the floating-point values computed by 
the given functions.
+     * The number of bands is the number of functions given to this method.
+     * Each function will receive (<var>x</var>, <var>y</var>) pixel 
coordinates
+     * and shall return the sample value to store in one band of the image at 
that pixel position.
+     * The functions will be invoked for all pixel coordinates inside the 
given {@code bounds} rectangle,
+     * but not necessarily in any particular order.
+     * The color model defaults to gray scale.
+     *
+     * @param  type    the type of values to store in the image (example: 
{@link DataType#FLOAT}).
+     * @param  bounds  the image size in pixels and the pixel coordinates of 
the upper-left corner,
+     *                 or {@code null} if unspecified. If null, then the image 
bounds will be taken
+     *                 from the {@linkplain GridGeometry#getExtent() grid 
extent}.
+     * @param  tiling  the preferred tile size, or {@code null} for automatic.
+     * @param  bands   functions providing sample values in each band, in 
order.
+     * @return {@code this} for method invocation chaining.
+     * @throws IllegalArgumentException if {@code bands} or {@code bounds} are 
empty.
+     *
+     * @see WritablePixelIterator#setRemainingPixels(DoubleBinaryOperator[])
      *
-     * <p>Each function will receive pixel coordinates (<var>x</var>, 
<var>y</var>)
+     * @since 1.7
+     */
+    public GridCoverageBuilder setValues(DataType type, Rectangle bounds, 
Dimension tiling, DoubleBinaryOperator... bands) {
+        calcAsDoubles = functions(type, bounds, tiling, bands);
+        return this;
+    }
+
+    /**
+     * Sets a two-dimensional slice to the integer values computed by the 
given functions.
+     * The number of bands is the number of functions given to this method.
+     * Each function will receive (<var>x</var>, <var>y</var>) pixel 
coordinates
      * and shall return the sample value to store in one band of the image at 
that pixel position.
-     * The <var>x</var> values will be between 0 inclusive and {@link 
Dimension#width} exclusive.
-     * The <var>y</var> values will be between 0 inclusive and {@link 
Dimension#height} exclusive.
-     * The functions may be invoked with pixel coordinates in any order.</p>
-     *
-     * @param  type   the type of values to store in the image.
-     * @param  size   the image size in pixels, or {@code null} if 
unspecified. If null, then the image
-     *                size will be taken from the {@linkplain 
GridGeometry#getExtent() grid extent}.
-     * @param  bands  functions providing sample values in each band, in order.
+     * The functions will be invoked for all pixel coordinates inside the 
given {@code bounds} rectangle,
+     * but not necessarily in any particular order.
+     * The color model defaults to gray scale.
+     *
+     * @param  type    the type of values to store in the image (example: 
{@link DataType#USHORT}).
+     * @param  bounds  the image size in pixels and the pixel coordinates of 
the upper-left corner,
+     *                 or {@code null} if unspecified. If null, then the image 
bounds will be taken
+     *                 from the {@linkplain GridGeometry#getExtent() grid 
extent}.
+     * @param  tiling  the preferred tile size, or {@code null} for automatic.
+     * @param  bands   functions providing sample values in each band, in 
order.
      * @return {@code this} for method invocation chaining.
-     * @throws IllegalArgumentException if {@code width} or {@code height} is 
negative or equals to zero.
+     * @throws IllegalArgumentException if {@code bands} or {@code bounds} are 
empty.
      *
-     * @see #setPreferredTileSize(Dimension)
-     * @see WritablePixelIterator#setRemainingPixels(IntBinaryOperator...)
+     * @see WritablePixelIterator#setRemainingPixels(IntBinaryOperator[])
      *
      * @since 1.7
      */
-    public GridCoverageBuilder setValues(final DataType type, Dimension size, 
IntBinaryOperator... bands) {
+    public GridCoverageBuilder setIntegerValues(DataType type, Rectangle 
bounds, Dimension tiling, IntBinaryOperator... bands) {
+        calcAsIntegers = functions(type, bounds, tiling, bands);
+        return this;
+    }
+
+    /**
+     * Implementation of the public methods that specify data by functions.
+     *
+     * @param  <E>     the type of the functions computing sample values.
+     * @param  type    the type of values to store in the image.
+     * @param  bounds  the image bounds, or {@code null} for using the extent.
+     * @param  tiling  the preferred tile size, or {@code null} for automatic.
+     * @param  bands   functions providing sample values in each band, in 
order.
+     * @return a clone of the {@code data} array.
+     * @throws IllegalArgumentException if {@code bands} or {@code bounds} are 
empty.
+     */
+    private <E> E[] functions(final DataType type, Rectangle bounds, final 
Dimension tiling, final E[] bands) {
         ArgumentChecks.ensureNonNull ("type",  type);
         ArgumentChecks.ensureNonEmpty("bands", bands);
         clearValues();
-        if (size != null) {
-            size = new Dimension(size);
+        if (bounds != null) {
+            bounds = new Rectangle(bounds);
+            ArgumentChecks.ensureStrictlyPositive("width",  bounds.width);
+            ArgumentChecks.ensureStrictlyPositive("height", bounds.height);
+            imageBounds = bounds;
         }
-        dataType  = type;
-        imageSize = size;
-        calcAsIntegers = bands.clone();
-        return this;
+        if (tiling != null) {
+            tileSize = new Dimension(tiling);
+        }
+        dataType = type;
+        return bands.clone();
     }
 
     /**
@@ -572,47 +612,50 @@ public class GridCoverageBuilder {
     }
 
     /**
-     * Creates an image with values computed by the {@link #calcAsIntegers} 
function.
+     * Creates an image with values computed by a function.
+     * One of {@link #calcAsDoubles} or {@link #calcAsIntegers} shall be 
non-null.
      *
-     * @param  grid   {@link #domain} if non-null, or a default non-null value 
otherwise.
-     * @param  bands  {@link #ranges} if non-null, or a default non-null value 
otherwise.
-     * @return an image computed from the {@link #calcAsIntegers} function.
+     * @param  grid      {@link #domain} if non-null, or a default non-null 
value otherwise.
+     * @param  bands     {@link #ranges} if non-null, or a default non-null 
value otherwise.
+     * @param  numBands  length of the {@link #calcAsDoubles} or {@link 
#calcAsIntegers} array.
+     * @return an image computed from the {@link #calcAsDoubles} or {@link 
#calcAsIntegers} function.
      */
-    private RenderedImage computeImage(final GridGeometry grid, final List<? 
extends SampleDimension> bands) {
-        final int width, height;
-        if (imageSize != null) {
-            width  = imageSize.width;
-            height = imageSize.height;
+    private RenderedImage computeImage(final GridGeometry grid, final List<? 
extends SampleDimension> bands, final int numBands) {
+        final int xmin, ymin, width, height, tileWidth, tileHeight;
+        if (imageBounds != null) {
+            xmin   = imageBounds.x;
+            ymin   = imageBounds.y;
+            width  = imageBounds.width;
+            height = imageBounds.height;
         } else {
             final GridExtent extent = grid.getExtent();
             final int[] imageAxes = 
extent.getSubspaceDimensions(GridCoverage.BIDIMENSIONAL);
+            xmin   = Math.toIntExact(extent.getLow (imageAxes[0]));
+            ymin   = Math.toIntExact(extent.getLow (imageAxes[1]));
             width  = Math.toIntExact(extent.getSize(imageAxes[0]));
             height = Math.toIntExact(extent.getSize(imageAxes[1]));
         }
-        final int tileWidth, tileHeight;
-        if (tileSize != null) {
-            tileWidth  = tileSize.width;
-            tileHeight = tileSize.height;
-        } else {
-            tileWidth  = width;
-            tileHeight = height;
+        Dimension tiling = tileSize;
+        if (tiling == null) {
+            tiling = 
ImageLayout.DEFAULT.allowPartialTiles(false).suggestTileSize(width, height);
         }
-        final int numXTiles = JDK18.ceilDiv(width,  tileWidth);
-        final int numYTiles = JDK18.ceilDiv(height, tileHeight);
-        final var sm = new BandedSampleModel(
-                (dataType != null) ? dataType.toDataBufferType() : 
DataBuffer.TYPE_INT,
-                tileWidth, tileHeight, calcAsIntegers.length);
-
-        final var location = new Point();
-        final var tiles = new WritableRaster[Math.multiplyExact(numXTiles, 
numYTiles)];
+        final int numXTiles = JDK18.ceilDiv(width,  tileWidth  = tiling.width);
+        final int numYTiles = JDK18.ceilDiv(height, tileHeight = 
tiling.height);
+        final var sm        = new 
BandedSampleModel(dataType.toDataBufferType(), tileWidth, tileHeight, numBands);
+        final var location  = new Point();
+        final var tiles     = new WritableRaster[Math.multiplyExact(numXTiles, 
numYTiles)];
         for (int i=0; i<tiles.length; i++) {
-            location.x = (i % numXTiles) * tileWidth;
-            location.y = (i / numXTiles) * tileHeight;
+            location.x = xmin + (i % numXTiles) * tileWidth;
+            location.y = ymin + (i / numXTiles) * tileHeight;
             tiles[i] = WritableRaster.createWritableRaster(sm, location);
         }
         final var data = (WritableRenderedImage) 
createImage(createColorModel(sm, bands), width, height, tiles);
         final WritablePixelIterator i = new 
WritablePixelIterator.Builder().createWritable(data);
-        i.setRemainingPixels(calcAsIntegers);
+        if (calcAsDoubles != null) {
+            i.setRemainingPixels(calcAsDoubles);
+        } else {
+            i.setRemainingPixels(calcAsIntegers);
+        }
         return data;
     }
 
@@ -656,21 +699,22 @@ public class GridCoverageBuilder {
                     /*
                      * Case of data specified as an array (wrapped in a 
buffer) or as functions.
                      */
-                    if (buffer == null && calcAsIntegers == null) {
+                    if (buffer == null && calcAsDoubles == null && 
calcAsIntegers == null) {
                         throw new 
IllegalStateException(missingProperty("values"));
                     }
-                    if (imageSize != null) {
-                        grid = GridCoverage2D.addExtentIfAbsent(grid, new 
Rectangle(imageSize));
-                        verifyGridExtent(grid.getExtent(), imageSize.width, 
imageSize.height);
+                    if (imageBounds != null) {
+                        grid = GridCoverage2D.addExtentIfAbsent(grid, 
imageBounds);
+                        verifyGridExtent(grid.getExtent(), imageBounds.width, 
imageBounds.height);
                     } else if (grid == null) {
-                        throw new 
IncompleteGridGeometryException(missingProperty("imageSize"));
+                        throw new 
IncompleteGridGeometryException(missingProperty("imageBounds"));
                     }
                     if (buffer != null) {
                         bands = GridCoverage2D.defaultIfAbsent(bands, null, 
buffer.getNumBanks());
                         return new 
BufferedGridCoverage(domainWithAxisFlips(grid), bands, buffer);
                     } else {
-                        bands = GridCoverage2D.defaultIfAbsent(bands, null, 
calcAsIntegers.length);
-                        data = computeImage(grid, bands);
+                        final int numBands = (calcAsDoubles != null) ? 
calcAsDoubles.length : calcAsIntegers.length;
+                        bands = GridCoverage2D.defaultIfAbsent(bands, null, 
numBands);
+                        data  = computeImage(grid, bands, numBands);
                     }
                 }
             }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
index 71af21c212..975d8132e3 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
@@ -29,6 +29,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import java.util.function.IntBinaryOperator;
+import java.util.function.DoubleBinaryOperator;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.coverage.grid.SequenceType;
@@ -300,10 +301,10 @@ final class BandedIterator extends WritablePixelIterator {
 
     /**
      * Sets all remaining pixels to sample values computed by the given 
functions.
-     * If the iterator is not in a valid position, then this method behavior 
is undetermined.
+     * The behavior of this method is undetermined if the iterator is not in a 
valid position.
      */
     @Override
-    public void setRemainingPixels(IntBinaryOperator... bands) {
+    public void setRemainingPixels(IntBinaryOperator[] bands) {
         bands = validate(bands);
         if (bands.length == 1) {    // Slight optimization for a common case.
             final IntBinaryOperator band = bands[0];
@@ -320,6 +321,28 @@ final class BandedIterator extends WritablePixelIterator {
         }
     }
 
+    /**
+     * Sets all remaining pixels to sample values computed by the given 
functions.
+     * The behavior of this method is undetermined if the iterator is not in a 
valid position.
+     */
+    @Override
+    public void setRemainingPixels(DoubleBinaryOperator[] bands) {
+        bands = validate(bands);
+        if (bands.length == 1) {    // Slight optimization for a common case.
+            final DoubleBinaryOperator band = bands[0];
+            while (next()) {
+                destBuffer.setElemDouble(x + xToBuffer, band.applyAsDouble(x, 
y));
+            }
+        } else {
+            while (next()) {
+                final int index = x + xToBuffer;
+                for (int i=0; i<bands.length; i++) {
+                    destBuffer.setElemDouble(i, index, 
bands[i].applyAsDouble(x, y));
+                }
+            }
+        }
+    }
+
     /**
      * Creates a window for floating point values using the given arrays.
      */
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
index eccbade41e..bb41b225fe 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
@@ -24,6 +24,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import java.util.function.IntBinaryOperator;
+import java.util.function.DoubleBinaryOperator;
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.util.ArgumentChecks;
 
@@ -256,7 +257,7 @@ public class WritablePixelIterator extends PixelIterator 
implements Closeable {
     /**
      * Returns a clone of the given array where all elements are guaranteed 
non-null.
      */
-    final IntBinaryOperator[] validate(IntBinaryOperator[] bands) {
+    final <F> F[] validate(F[] bands) {
         if (bands.length != numBands) {
             throw new 
IllegalArgumentException(Resources.format(Resources.Keys.UnexpectedNumberOfBands_2,
 numBands, bands.length));
         }
@@ -268,7 +269,7 @@ public class WritablePixelIterator extends PixelIterator 
implements Closeable {
     }
 
     /**
-     * Sets all remaining pixels to sample values computed by the given 
functions.
+     * Sets all remaining pixels to integer values computed by the given 
functions.
      * Each function will receive pixel coordinates (<var>x</var>, 
<var>y</var>)
      * and shall return the sample value to store in one band of the image at 
that pixel position.
      * The functions shall be given in the same order as the bands.
@@ -280,7 +281,7 @@ public class WritablePixelIterator extends PixelIterator 
implements Closeable {
      *
      * @since 1.7
      */
-    public void setRemainingPixels(IntBinaryOperator... bands) {
+    public void setRemainingPixels(IntBinaryOperator[] bands) {
         bands = validate(bands);
         if (bands.length == 1) {    // Slight optimization for a common case.
             final IntBinaryOperator band = bands[0];
@@ -298,6 +299,37 @@ public class WritablePixelIterator extends PixelIterator 
implements Closeable {
         }
     }
 
+    /**
+     * Sets all remaining pixels to floating-point values computed by the 
given functions.
+     * Each function will receive pixel coordinates (<var>x</var>, 
<var>y</var>)
+     * and shall return the sample value to store in one band of the image at 
that pixel position.
+     * The functions shall be given in the same order as the bands.
+     * Therefore, the function at index <var>i</var> computes the sample 
values of band <var>i</var>.
+     *
+     * @param  bands  functions providing sample values in each band, in order.
+     * @throws IllegalArgumentException if the number of functions is not equal
+     *         to the {@linkplain #getNumBands() number of bands}.
+     *
+     * @since 1.7
+     */
+    public void setRemainingPixels(DoubleBinaryOperator[] bands) {
+        bands = validate(bands);
+        if (bands.length == 1) {    // Slight optimization for a common case.
+            final DoubleBinaryOperator band = bands[0];
+            while (next()) {
+                destRaster.setSample(x, y, 0, band.applyAsDouble(x, y));
+            }
+        } else {
+            final double[] samples = new double[bands.length];
+            while (next()) {
+                for (int i=0; i<bands.length; i++) {
+                    samples[i] = bands[i].applyAsDouble(x, y);
+                }
+                destRaster.setPixel(x, y, samples);
+            }
+        }
+    }
+
     /**
      * Sets the data elements (not necessarily band values) of current pixel.
      * The {@code Object} argument is a relatively opaque format (it may be 
{@code int[]}, {@code byte[]}, <i>etc.</i>).
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
index 525a758e7f..edcb43c147 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
@@ -164,7 +164,7 @@ public final class GridCoverageBuilderTest extends TestCase 
{
         assertSame(builder, builder.setValues(buffer, null));
         var e = assertThrows(IncompleteGridGeometryException.class, () -> 
builder.build(),
                              "Extent is undefined, build() should fail.");
-        assertMessageContains(e, "imageSize");
+        assertMessageContains(e, "imageBounds");
         final GridCoverage coverage = testSetDomain(builder, 3, 2);
         assertSame(buffer, coverage.render(null).getTile(0,0).getDataBuffer());
     }
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java
index 614db3ee38..51aae148cc 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
 import java.nio.file.Path;
 import java.nio.file.Files;
 import java.awt.Dimension;
+import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import org.opengis.referencing.cs.AxisDirection;
@@ -139,7 +140,7 @@ public final class GeoTiffStoreTest extends TestCase {
      */
     @Test
     public void testWriteUntiled() throws TransformException, 
DataStoreException, IOException {
-        testWrite(UNTILED, new Dimension(32, 16), null, 2284);
+        testWrite(UNTILED, new Rectangle(32, 16), null, 2284);
     }
 
     /**
@@ -152,18 +153,18 @@ public final class GeoTiffStoreTest extends TestCase {
     @Test
     public void testWriteTiled() throws TransformException, 
DataStoreException, IOException {
         final var tileSize = new Dimension(16, 16);     // TIFF tile size must 
be multiple of 16.
-        testWrite(TILED, new Dimension(tileSize.width * 3, tileSize.height * 
2), tileSize, 3564);
+        testWrite(TILED, new Rectangle(tileSize.width * 3, tileSize.height * 
2), tileSize, 3564);
     }
 
     /**
      * Implementation of {@link #testWriteUntiled()} and {@link 
#testWriteTiled()}.
      *
-     * @param  filename   name of the file which contain the expected image.
-     * @param  imageSize  size of the image to create.
-     * @param  tileSize   size of the tiles, or {@code null} for the image 
size.
-     * @param  length     expected length in bytes.
+     * @param  filename  name of the file which contain the expected image.
+     * @param  bounds    bounds of the image to create.
+     * @param  tileSize  size of the tiles, or {@code null} for the image size.
+     * @param  length    expected length in bytes.
      */
-    private static void testWrite(final String filename, final Dimension 
imageSize, final Dimension tileSize, final int length)
+    private static void testWrite(final String filename, final Rectangle 
bounds, final Dimension tileSize, final int length)
             throws TransformException, DataStoreException, IOException
     {
         var geographicArea = new GeneralEnvelope(HardCodedCRS.WGS84);
@@ -171,8 +172,7 @@ public final class GeoTiffStoreTest extends TestCase {
         geographicArea.setRange(1,  30,  42);   // Range of latitude values.
         final GridCoverage coverage = new GridCoverageBuilder()
                 .setDomain(Envelopes.transform(geographicArea, 
HardCodedConversions.mercator()))
-                .setValues(DataType.BYTE, imageSize, (x, y) -> 100 * y + x)
-                .setPreferredTileSize(tileSize)
+                .setIntegerValues(DataType.BYTE, bounds, tileSize, (x, y) -> 
100 * y + x)
                 .flipGridAxis(1)
                 .build();
 

Reply via email to