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: Refactor a RasterFactor static method into an ImageRenderer helper class.
Date Mon, 07 Jan 2019 10:45:51 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 3cb7f5d  Refactor a RasterFactor static method into an ImageRenderer helper class.
3cb7f5d is described below

commit 3cb7f5d36dd7084e4f8a006baf697893ee5de03c
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jan 7 11:23:34 2019 +0100

    Refactor a RasterFactor static method into an ImageRenderer helper class.
---
 .../coverage/MismatchedCoverageRangeException.java |  63 ++++
 .../org/apache/sis/coverage/grid/GridCoverage.java |  56 +++-
 .../org/apache/sis/coverage/grid/GridGeometry.java |   1 +
 .../grid/IllegalGridGeometryException.java         |   4 +
 .../apache/sis/coverage/grid/ImageRenderer.java    | 351 +++++++++++++++++++++
 .../grid/IncompleteGridGeometryException.java      |  12 +
 .../sis/internal/raster/ColorModelFactory.java     |   9 +-
 .../apache/sis/internal/raster/RasterFactory.java  |  93 ++----
 .../org/apache/sis/internal/raster/Resources.java  |  25 ++
 .../sis/internal/raster/Resources.properties       |   5 +
 .../sis/internal/raster/Resources_fr.properties    |   5 +
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.xml         |   1 +
 .../java/org/apache/sis/storage/netcdf/Image.java  |  24 +-
 14 files changed, 552 insertions(+), 99 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/MismatchedCoverageRangeException.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/MismatchedCoverageRangeException.java
new file mode 100644
index 0000000..18d182e
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/MismatchedCoverageRangeException.java
@@ -0,0 +1,63 @@
+/*
+ * 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.coverage;
+
+
+/**
+ * Thrown when the number of bands or sample dimensions specified to a method
+ * is not equal to the number expected by a coverage.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ *
+ * @see org.opengis.geometry.MismatchedDimensionException
+ *
+ * @since 1.0
+ * @module
+ */
+public class MismatchedCoverageRangeException extends IllegalArgumentException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -8484971745880403661L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public MismatchedCoverageRangeException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     */
+    public MismatchedCoverageRangeException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message and cause.
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     * @param cause    the cause, saved for later retrieval by the {@link #getCause()} method.
+     */
+    public MismatchedCoverageRangeException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index dea57aa..d921ef2 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -116,12 +116,27 @@ public abstract class GridCoverage {
     }
 
     /**
-     * Returns a two-dimensional slice of grid data as a rendered image. The given {@code slicePoint} argument specifies
+     * Returns a two-dimensional slice of grid data as a rendered image. The given {@code sliceExtent} argument specifies
      * the coordinates of the slice in all dimensions that are not in the two-dimensional image. For example if this grid
-     * coverage has (<var>x</var>, <var>y</var>, <var>z</var>, <var>t</var>) dimensions and we want to render an image
-     * of data in the (<var>x</var>, <var>y</var>) dimensions, then the given {@code slicePoint} shall contain the
-     * (<var>z</var>, <var>t</var>) coordinates of the desired slice. The two coordinates of the data to be shown
-     * (<var>x</var> and <var>y</var> in our example) shall be excluded from the slice point in one of the following ways:
+     * coverage has <i>(<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>)</i> dimensions and we want to render an image
+     * of data in the <i>(<var>x</var>,<var>y</var>)</i> dimensions, then the given {@code sliceExtent} shall contain the
+     * <i>(<var>z</var>,<var>t</var>)</i> coordinates of the desired slice. Those coordinates are specified in a grid extent
+     * where {@linkplain GridExtent#getLow(int) low coordinate} = {@linkplain GridExtent#getHigh(int) high coordinate} in the
+     * <var>z</var> and <var>t</var> dimensions. The two dimensions of the data to be shown (<var>x</var> and <var>y</var>
+     * in our example) shall be the only dimensions with a {@linkplain GridExtent#getSize(int) size} greater than 1 cell.
+     *
+     * <p>If the {@code sliceExtent} argument is {@code null}, then the default value is
+     * <code>{@linkplain #getGridGeometry()}.{@linkplain GridGeometry#getExtent() getExtent()}</code>.
+     * This means that {@code gridExtent} is optional for two-dimensional grid coverages or grid coverages where all dimensions
+     * except two have a size of 1 cell. If the grid extent contains more than 2 dimensions with a size greater than one cell,
+     * then a {@link SubspaceNotSpecifiedException} is thrown. If some {@code sliceExtent} coordinates are outside the extent
+     * of this grid coverage, then a {@link PointOutsideCoverageException} is thrown.</p>
+     *
+     * <div class="section">Computing a slice extent from a slice point in "real world" coordinates</div>
+     * The {@code sliceExtent} is specified to this method as grid indices. If the <var>z</var> and <var>t</var> values
+     * are not grid indices but are relative to some Coordinate Reference System (CRS) instead, then the slice extent can
+     * be computed as below. First, a <cite>slice point</cite> containing the <var>z</var> and <var>t</var> coordinates
+     * should be constructed as a {@link DirectPosition} in one of the following ways:
      *
      * <ul>
      *   <li>The {@code slicePoint} has a CRS with two dimensions less than this grid coverage CRS.</li>
@@ -129,21 +144,36 @@ public abstract class GridCoverage {
      *       exclude are set to {@link Double#NaN}.</li>
      * </ul>
      *
+     * Then:
+     *
+     * <blockquote><code>sliceExtent = {@linkplain #getGridGeometry()}.{@linkplain GridGeometry#subExtent(DirectPosition)
+     * subExtent}(slicePoint);</code></blockquote>
+     *
      * If the {@code slicePoint} CRS is different than this grid coverage CRS (except for the number of dimensions),
-     * a coordinate transformation will be applied. If the {@code slicePoint} CRS is {@code null}, it is assumed the
-     * same than this grid coverage CRS. If this grid coverage is two-dimensional or can render only one image for
-     * other reason, then the {@code slicePoint} can be null.
+     * a coordinate transformation will be applied as needed.
+     *
+     * <div class="section">Rendered image properties</div>
+     * The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height} will be
+     * the {@code sliceExtent} {@linkplain GridExtent#getSize(int) sizes} in the first and second dimension respectively
+     * of the two-dimensional {@code sliceExtent} {@linkplain GridExtent#getSubspaceDimensions(int) subspace}.
+     * The image location ({@linkplain RenderedImage#getMinX() x}, {@linkplain RenderedImage#getMinY() y}) can be any point;
+     * that location may not be the same as the {@code sliceExtent} {@linkplain GridExtent#getLow(int) low} coordinates
+     * since conversion from {@code long} to {@code int} primitive type may cause lost of precision, and some implementations
+     * like {@link java.awt.image.BufferedImage} restrict that location to (0,0).
      *
-     * <p>Implementations should return a view as much as possible, without copying sample values.</p>
+     * <p>Implementations should return a view as much as possible, without copying sample values.
+     * {@code GridCoverage} subclasses can use the {@link ImageRenderer} class as a helper tool for that purpose.
+     * This method does not mandate any behavior regarding tiling (size of tiles, their numbering system, <i>etc.</i>).
+     * Some implementations may defer data loading until {@linkplain RenderedImage#getTile(int, int) a tile is requested}.</p>
      *
-     * @param  slicePoint  coordinates of the slice in all dimensions other than the two dimensions to be shown on the image.
-     *         May be {@code null} if this coverage can render only one image, for example because its CRS is two-dimensional.
+     * @param  sliceExtent  a subspace of this grid coverage extent where all dimensions except two have a size of 1 cell.
+     *         May be {@code null} if this grid coverage has only two dimensions with a size greater than 1 cell.
      * @return the grid slice as a rendered image.
-     * @throws PointOutsideCoverageException if the given slice point is illegal.
+     * @throws PointOutsideCoverageException if the given slice extent contains illegal coordinates.
      * @throws SubspaceNotSpecifiedException if the given argument is not sufficient for reducing the grid to a two-dimensional slice.
      * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
      */
-    public abstract RenderedImage render(DirectPosition slicePoint) throws CannotEvaluateException;
+    public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
 
     /**
      * Returns a string representation of this grid coverage for debugging purpose.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index fa361cc..272d191 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -713,6 +713,7 @@ public class GridGeometry implements Serializable {
      * @throws IllegalGridGeometryException if an error occurred while converting the point coordinates to grid coordinates.
      *
      * @see #slice(DirectPosition)
+     * @see GridCoverage#render(GridExtent)
      */
     public GridExtent subExtent(final DirectPosition slicePoint) {
         ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
index fcfb8be..1f2d0ee 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
@@ -21,6 +21,10 @@ import org.apache.sis.internal.raster.Resources;
 
 /**
  * Thrown when the argument specified to a method or constructor would result in an invalid {@link GridGeometry}.
+ * This exception may have a {@link org.opengis.referencing.operation.TransformException} as its cause, in which
+ * case the grid geometry failed to use a given "grid to CRS" transform over the given grid extent. Such failure
+ * may happen with non-linear transforms, but are less likely in the common case where the grid geometry uses a
+ * linear (or affine) "grid to CRS" transform.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
new file mode 100644
index 0000000..8f95bc0
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -0,0 +1,351 @@
+/*
+ * 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.coverage.grid;
+
+import java.util.Arrays;
+import java.nio.Buffer;
+import java.awt.Point;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.awt.image.RasterFormatException;
+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.raster.ColorModelFactory;
+import org.apache.sis.internal.raster.RasterFactory;
+import org.apache.sis.internal.raster.Resources;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.util.NullArgumentException;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.math.Vector;
+
+
+/**
+ * A builder for the rendered image to be returned by {@link GridCoverage#render(GridExtent)}.
+ * This builder does not copy any sample values. Instead, it wraps existing data arrays into
+ * {@link java.awt.image.Raster} objects by computing required information such as
+ * {@linkplain java.awt.image.ComponentSampleModel#getPixelStride() pixel stride},
+ * {@linkplain java.awt.image.ComponentSampleModel#getScanlineStride() scanline stride} and
+ * {@linkplain java.awt.image.ComponentSampleModel#getBandOffsets() band offsets}.
+ * Different {@code setData(…)} methods are provided for allowing to specify the data arrays
+ * from different objects such as Java2D {@link DataBuffer} or NIO {@link Buffer}.
+ *
+ * <p>All {@code setData(…)} methods assume that the first valid element in each array is the value
+ * located at <code>{@linkplain GridCoverage#getGridGeometry()}.{@linkplain GridGeometry#getExtent()
+ * getExtent()}.{@linkplain GridExtent#getLow(int) getLow()}</code>. This {@code ImageRenderer} class
+ * computes automatically the offsets from that position to the position of the first value included
+ * in the {@code sliceExtent} given to the constructor.</p>
+ *
+ * <p>Current implementation constructs only images made of a single tile.
+ * Support for tiled images will be added in a future version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public class ImageRenderer {
+    /**
+     * Pixel coordinates of the image upper-left location.
+     * This is often left to zero since {@link BufferedImage} has this constraint.
+     *
+     * @see #setLocation(int, int)
+     */
+    private int x, y;
+
+    /**
+     * Width (number of pixels in a row) of the image to render.
+     * This is usually set to the grid extent along the first dimension
+     * having a {@linkplain GridExtent#getSize(int) size} greater than 1.
+     *
+     * @see RenderedImage#getWidth()
+     */
+    private final int width;
+
+    /**
+     * Height (number of pixels in a column) of the image to render.
+     * This is usually set to the grid extent along the second dimension
+     * having a {@linkplain GridExtent#getSize(int) size} greater than 1.
+     *
+     * @see RenderedImage#getHeight()
+     */
+    private final int height;
+
+    /**
+     * Number of data elements between two samples for the same band on the same line.
+     * This is set to the product of {@linkplain GridExtent#getSize(int) grid sizes} of enclosing
+     * {@code GridCoverage} in all dimensions before the dimension of image {@linkplain #width}.
+     *
+     * @see java.awt.image.ComponentSampleModel#pixelStride
+     */
+    private final int pixelStride;
+
+    /**
+     * Number of data elements between a given sample and the corresponding sample in the same column of the next line.
+     * This is set to the product of {@linkplain GridExtent#getSize(int) grid sizes} of enclosing {@code GridCoverage}
+     * in all dimensions before the dimension of image {@linkplain #height}.
+     *
+     * @see java.awt.image.ComponentSampleModel#scanlineStride
+     */
+    private final int scanlineStride;
+
+    /**
+     * The sample dimensions, to be used for defining the bands.
+     */
+    private final SampleDimension[] bands;
+
+    /**
+     * Bank indices for each band, or {@code null} for 0, 1, 2, 3….
+     * If non-null, this array length must be equal to {@link #bands} array length.
+     */
+    private int[] bankIndices;
+
+    /**
+     * Number of data elements from the first element of the bank to the first sample of the band, or {@code null} for all 0.
+     * If non-null, this array length must be equal to {@link #bands} array length.
+     */
+    private final int[] bandOffsets;
+
+    /**
+     * The band to be made visible (usually 0). All other bands, if any will be ignored.
+     */
+    private int visibleBand;
+
+    /**
+     * The data to render, or {@code null} if not yet specified.
+     * If non-null, {@link DataBuffer#getNumBanks()} must be equal to {@link #bands} array length.
+     */
+    private DataBuffer buffer;
+
+    /**
+     * Creates a new image renderer for the given slice extent. The image will have only one tile.
+     *
+     * @param  coverage     the grid coverage for which to build an image.
+     * @param  sliceExtent  the grid geometry from which to create an image, or {@code null} for the {@code coverage} extent.
+     * @throws SubspaceNotSpecifiedException if this method can not infer a two-dimensional slice from {@code sliceExtent}.
+     * @throws ArithmeticException if a stride calculation overflows the 32 bits integer capacity.
+     */
+    public ImageRenderer(final GridCoverage coverage, GridExtent sliceExtent) {
+        ArgumentChecks.ensureNonNull("coverage", coverage);
+        bands = CollectionsExt.toArray(coverage.getSampleDimensions(), SampleDimension.class);
+        final GridExtent source = coverage.getGridGeometry().getExtent();
+        if (sliceExtent != null) {
+            final int dimension = sliceExtent.getDimension();
+            if (source.getDimension() != dimension) {
+                throw new MismatchedDimensionException(Errors.format(
+                        Errors.Keys.MismatchedDimension_3, "target", source.getDimension(), dimension));
+            }
+        } else {
+            sliceExtent = source;
+        }
+        final int[] dimensions = sliceExtent.getSubspaceDimensions(2);
+        int  xd = dimensions[0];
+        int  yd = dimensions[1];
+        long xo = sliceExtent.getLow(xd);
+        long yo = sliceExtent.getLow(yd);
+        width  = Math.toIntExact(sliceExtent.getSize(xd));
+        height = Math.toIntExact(sliceExtent.getSize(yd));
+        /*
+         * After this point, xd and yd should be indices relative to source extent.
+         * For now we keep them unchanged on the assumption that the two grid extents have the same dimensions.
+         */
+        xo = Math.subtractExact(xo, source.getLow(xd));
+        yo = Math.subtractExact(yo, source.getLow(yd));
+        long pixelStride = 1;
+        for (int i=0; i<xd; i++) {
+            pixelStride = Math.multiplyExact(pixelStride, source.getSize(i));
+        }
+        long scanlineStride = pixelStride;
+        for (int i=xd; i<yd; i++) {
+            scanlineStride = Math.multiplyExact(scanlineStride, source.getSize(i));
+        }
+        this.pixelStride    = Math.toIntExact(pixelStride);
+        this.scanlineStride = Math.toIntExact(scanlineStride);
+        this.bandOffsets    = new int[getNumBands()];
+        Arrays.fill(bandOffsets, Math.toIntExact(xo + Math.multiplyExact(yo, scanlineStride)));
+    }
+
+    /**
+     * Returns the number of bands that the image will have. By default, this is the number of
+     * {@linkplain GridCoverage#getSampleDimensions() sample dimensions} in the grid coverage.
+     *
+     * @return the number of bands in the rendered image.
+     */
+    public final int getNumBands() {
+        return bands.length;
+    }
+
+    /**
+     * Ensures that the given number is equals to the expected number of bands.
+     */
+    private void ensureExpectedBandCount(final int n) {
+        final int e = getNumBands();
+        if (n != e) {
+            throw new MismatchedCoverageRangeException(Resources.format(Resources.Keys.UnexpectedNumberOfBands_2, e, n));
+        }
+    }
+
+    /**
+     * Sets the pixel coordinates of the upper-left corner of the rendered image to create.
+     * If this method is not invoked, then the default value is (0,0).
+     * That default value is often suitable since {@link BufferedImage} constraints that corner to (0,0).
+     *
+     * @param  x  the minimum <var>x</var> coordinate (inclusive) of the rendered image.
+     * @param  y  the minimum <var>y</var> coordinate (inclusive) of the rendered image.
+     *
+     * @see RenderedImage#getMinX()
+     * @see RenderedImage#getMinY()
+     */
+    public void setLocation(final int x, final int y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * Returns the location of the image upper-left corner.
+     * This is the last value set by a call to {@link #setLocation(int, int)}.
+     * The default value is (0,0).
+     *
+     * @return the image location (never null).
+     */
+    public final Point getLocation() {
+        return new Point(x,y);
+    }
+
+    /**
+     * Sets the data as a Java2D buffer. The {@linkplain DataBuffer#getNumBanks() number of banks}
+     * in the given buffer must be equal to the {@linkplain #getNumBands() expected number of bands}.
+     * In each bank, the value located at the {@linkplain DataBuffer#getOffsets() bank offset} is the value
+     * located at <code>{@linkplain GridCoverage#getGridGeometry()}.{@linkplain GridGeometry#getExtent()
+     * getExtent()}.{@linkplain GridExtent#getLow(int) getLow()}</code>, as specified in class javadoc.
+     *
+     * @param  data  the Java2D buffer containing data for all bands.
+     * @throws NullArgumentException if {@code data} is null.
+     * @throws MismatchedCoverageRangeException if the given data buffer does not have the expected amount of banks.
+     */
+    public void setData(final DataBuffer data) {
+        ArgumentChecks.ensureNonNull("data", data);
+        ensureExpectedBandCount(data.getNumBanks());
+        buffer = data;
+    }
+
+    /**
+     * Sets the data as NIO buffers. The number of buffers must be equal to the {@linkplain #getNumBands() expected
+     * number of bands}. All buffers must be {@linkplain Buffer#array() backed by arrays} of the type specified by
+     * the {@code dataType} argument and have the same amount of {@linkplain Buffer#remaining() remaining elements}.
+     * This method wraps the underlying arrays of a primitive type into a Java2D buffer; data are not copied.
+     * For each buffer, the grid coverage data (not only the slice data) starts at {@linkplain Buffer#position()
+     * buffer position} and ends at that position + {@linkplain Buffer#remaining() remaining}.
+     *
+     * <p>The data type must be specified in order to distinguish between the signed and unsigned types.
+     * {@link DataBuffer#TYPE_BYTE} and {@link DataBuffer#TYPE_USHORT} are unsigned, all other supported
+     * types are signed.</p>
+     *
+     * <p><b>Implementation note:</b> the Java2D buffer is set by a call to {@link #setData(DataBuffer)},
+     * which can be overridden by subclasses if desired.</p>
+     *
+     * @param  dataType  type of data as one of {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_SHORT TYPE_SHORT}
+     *         {@link DataBuffer#TYPE_USHORT TYPE_USHORT}, {@link DataBuffer#TYPE_INT TYPE_INT},
+     *         {@link DataBuffer#TYPE_FLOAT TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE TYPE_DOUBLE} constants.
+     * @param  data  the buffers wrapping arrays of primitive type.
+     * @throws NullArgumentException if {@code data} is null or one of {@code data} element is null.
+     * @throws IllegalArgumentException if {@code dataType} is not a supported value.
+     * @throws MismatchedCoverageRangeException if the number of specified buffers is not equal to the number of bands.
+     * @throws UnsupportedOperationException if a buffer is not backed by an accessible array or is read-only.
+     * @throws ArrayStoreException if a buffer type is incompatible with {@code dataType}.
+     * @throws RasterFormatException if buffers do not have the same amount of remaining values.
+     * @throws ArithmeticException if a buffer position overflows the 32 bits integer capacity.
+     */
+    public void setData(final int dataType, final Buffer... data) {
+        ArgumentChecks.ensureNonNull("data", data);
+        ensureExpectedBandCount(data.length);
+        final DataBuffer banks = RasterFactory.wrap(dataType, data);
+        if (banks == null) {
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.UnknownDataType_1, dataType));
+        }
+        setData(banks);
+    }
+
+    /**
+     * Sets the data as vectors. The number of vectors must be equal to the {@linkplain #getNumBands() expected number of bands}.
+     * All vectors must be backed by arrays (indirectly, through {@linkplain Vector#buffer() buffers} backed by arrays) and have
+     * the same {@linkplain Vector#size() size}.
+     * This method wraps the underlying arrays of a primitive type into a Java2D buffer; data are not copied.
+     *
+     * <p><b>Implementation note:</b> the NIO buffers are set by a call to {@link #setData(int, Buffer...)},
+     * which can be overridden by subclasses if desired.</p>
+     *
+     * @param  data  the vectors wrapping arrays of primitive type.
+     * @throws NullArgumentException if {@code data} is null or one of {@code data} element is null.
+     * @throws MismatchedCoverageRangeException if the number of specified vectors is not equal to the number of bands.
+     * @throws UnsupportedOperationException if a vector is not backed by an accessible array or is read-only.
+     * @throws RasterFormatException if vectors do not have the same size.
+     * @throws ArithmeticException if a buffer position overflows the 32 bits integer capacity.
+     */
+    public void setData(final Vector... data) {
+        ArgumentChecks.ensureNonNull("data", data);
+        ensureExpectedBandCount(data.length);
+        final Buffer[] buffers = new Buffer[data.length];
+        int dataType = DataBuffer.TYPE_UNDEFINED;
+        for (int i=0; i<data.length; i++) {
+            final Vector v = data[i];
+            ArgumentChecks.ensureNonNullElement("data", i, v);
+            final int t = RasterFactory.getType(v.getElementType(), v.isUnsigned());
+            if (dataType != t) {
+                if (i != 0) {
+                    throw new RasterFormatException(Resources.format(Resources.Keys.MismatchedDataType));
+                }
+                dataType = t;
+            }
+            buffers[i] = v.buffer().orElseThrow(UnsupportedOperationException::new);
+        }
+        setData(dataType, buffers);
+    }
+
+    /**
+     * Creates a raster with the data specified by the last call to a {@code setData(…)} method.
+     * The raster upper-left corner is located at the position given by {@link #getLocation()}.
+     *
+     * @return the raster.
+     * @throws IllegalStateException if no {@code setData(…)} method has been invoked before this method call.
+     */
+    public WritableRaster raster() {
+        if (buffer == null) {
+            throw new IllegalStateException(Resources.format(Resources.Keys.UnspecifiedRasterData));
+        }
+        final Point location = ((x | y) != 0) ? new Point(x,y) : null;
+        return RasterFactory.createRaster(buffer, width, height, pixelStride, scanlineStride, bankIndices, bandOffsets, location);
+    }
+
+    /**
+     * Creates an image with the data specified by the last call to a {@code setData(…)} method.
+     * The image upper-left corner is located at the position given by {@link #getLocation()}.
+     *
+     * @return the image.
+     * @throws IllegalStateException if no {@code setData(…)} method has been invoked before this method call.
+     */
+    public RenderedImage image() {
+        WritableRaster raster = raster();
+        ColorModel colors = ColorModelFactory.createColorModel(bands, visibleBand, buffer.getDataType(), ColorModelFactory.GRAYSCALE);
+        return new BufferedImage(colors, raster, false, null);
+    }
+}
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java
index 84f5bbe..dfc4572 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java
@@ -22,6 +22,18 @@ package org.apache.sis.coverage.grid;
  * For example this exception is thrown when {@link GridGeometry#getEnvelope()} is invoked while
  * the grid geometry has been built with a null envelope.
  *
+ * <p>The {@link GridGeometry#isDefined(int)} can be used for avoiding this exception.
+ * For example if a process is going to need both the grid extent and the "grid to CRS" transform,
+ * than it can verify if those two conditions are met in a single method call:</p>
+ *
+ * {@preformat java
+ *     if (gg.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS) {
+ *         GridExtent    extent    = gg.getGridExtent();
+ *         MathTransform gridToCRS = gg.getGridToCRS(PixelInCell.CELL_CENTER);
+ *         // Do the process.
+ *     }
+ * }
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
  * @since   1.0
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
index f21386a..281b8b5 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.raster;
 
 import java.util.Map;
-import java.util.List;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
@@ -50,7 +49,7 @@ import org.apache.sis.util.collection.WeakValueHashMap;
 public final class ColorModelFactory {
     /**
      * Applies a gray scale to quantitative category and transparent colors to qualitative categories.
-     * This is a possible argument for {@link #createColorModel(List, int, int, Function)}.
+     * This is a possible argument for {@link #createColorModel(SampleDimension[], int, int, Function)}.
      */
     public static final Function<Category,Color[]> GRAYSCALE =
             (category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null;
@@ -283,16 +282,16 @@ public final class ColorModelFactory {
      * @param  colors       the colors to use for each category. The function may return {@code null}, which means transparent.
      * @return a color model suitable for {@link java.awt.image.RenderedImage} objects with values in the given ranges.
      */
-    public static ColorModel createColorModel(final List<? extends SampleDimension> bands,
+    public static ColorModel createColorModel(final SampleDimension[] bands,
             final int visibleBand, final int type, Function<Category,Color[]> colors)
     {
         ArgumentChecks.ensureNonNull("bands",  bands);
         ArgumentChecks.ensureNonNull("colors", colors);
         final Map<NumberRange<?>, Color[]> ranges = new LinkedHashMap<>();
-        for (final Category category : bands.get(visibleBand).getCategories()) {
+        for (final Category category : bands[visibleBand].getCategories()) {
             ranges.put(category.getSampleRange(), colors.apply(category));
         }
-        return createColorModel(ranges, visibleBand, bands.size(), type);
+        return createColorModel(ranges, visibleBand, bands.length, type);
     }
 
     /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
index 9619ca6..edfdd23 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
@@ -30,15 +30,13 @@ import java.awt.image.SampleModel;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.ComponentSampleModel;
 import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.RasterFormatException;
 import java.awt.image.WritableRaster;
-import org.opengis.geometry.MismatchedDimensionException;
-import org.apache.sis.coverage.SubspaceNotSpecifiedException;
-import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Numbers;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -57,57 +55,6 @@ public final class RasterFactory extends Static {
     }
 
     /**
-     * Wraps a sub-region of the given data buffer in a raster.
-     * The raster width, raster height, pixel stride and scanline stride are inferred from the grid extents.
-     * The sample model type is selected according the number of bands and the pixel stride.
-     *
-     * @param  buffer          buffer that contains the sample values.
-     * @param  bankIndices     bank indices for each band, or {@code null} for 0, 1, 2, 3….
-     * @param  bandOffsets     number of data elements from the first element of the bank to the first sample of the band, or {@code null} for all 0.
-     * @param  source          extent of the data wrapped by the given buffer. May have any number of dimensions.
-     * @param  target          extent of the subspace to wrap in a raster.
-     * @param  startAtZero     whether to force the raster to start at (0,0) instead than the target extent low coordinates.
-     * @return a raster built from given properties.
-     * @throws ArithmeticException if a stride calculation overflows the 32 bits integer capacity.
-     * @throws SubspaceNotSpecifiedException if this method can not infer a two-dimensional slice from {@code target}.
-     */
-    public static WritableRaster createRaster(final DataBuffer buffer, final int[] bankIndices, final int[] bandOffsets,
-            final GridExtent source, final GridExtent target, final boolean startAtZero)
-    {
-        int dimension = target.getDimension();
-        if (source.getDimension() != dimension) {
-            throw new MismatchedDimensionException(Errors.format(
-                    Errors.Keys.MismatchedDimension_3, "target", source.getDimension(), dimension));
-        }
-        final int[] dimensions = target.getSubspaceDimensions(2);
-        int xd = dimensions[0];
-        int yd = dimensions[1];
-        final Point location;
-        if (startAtZero) {
-            location = null;
-        } else {
-            location = new Point(Math.toIntExact(target.getLow(xd)),
-                                 Math.toIntExact(target.getLow(yd)));
-        }
-        final int width  = Math.toIntExact(target.getSize(xd));
-        final int height = Math.toIntExact(target.getSize(yd));
-        /*
-         * After this point, xd and yd should be indices relative to source extent.
-         * For now we keep them unchanged on the assumption that the two grid extents have the same dimensions.
-         */
-        long pixelStride = 1;
-        for (int i=0; i<xd; i++) {
-            pixelStride = Math.multiplyExact(pixelStride, target.getSize(i));
-        }
-        long scanlineStride = pixelStride;
-        for (int i=xd; i<yd; i++) {
-            scanlineStride = Math.multiplyExact(scanlineStride, target.getSize(i));
-        }
-        return createRaster(buffer, width, height,
-                Math.toIntExact(pixelStride), Math.toIntExact(scanlineStride), bankIndices, bandOffsets, location);
-    }
-
-    /**
      * Wraps the given data buffer in a raster.
      * The sample model type is selected according the number of bands and the pixel stride.
      *
@@ -120,6 +67,8 @@ public final class RasterFactory extends Static {
      * @param  bandOffsets     number of data elements from the first element of the bank to the first sample of the band, or {@code null} for all 0.
      * @param  location        the upper-left corner of the raster, or {@code null} for (0,0).
      * @return a raster built from given properties.
+     * @throws NullPointerException if {@code buffer} is {@code null}.
+     * @throws RasterFormatException if the width or height is less than or equal to zero, or if there is an integer overflow.
      *
      * @see WritableRaster#createInterleavedRaster(DataBuffer, int, int, int, int, int[], Point)
      * @see WritableRaster#createBandedRaster(DataBuffer, int, int, int, int[], int[], Point)
@@ -129,10 +78,10 @@ public final class RasterFactory extends Static {
             final int width, final int height, final int pixelStride, final int scanlineStride,
             int[] bankIndices, int[] bandOffsets, final Point location)
     {
-        ArgumentChecks.ensureStrictlyPositive("width",          width);
-        ArgumentChecks.ensureStrictlyPositive("height",         height);
-        ArgumentChecks.ensureStrictlyPositive("pixelStride",    pixelStride);
-        ArgumentChecks.ensureStrictlyPositive("scanlineStride", scanlineStride);
+        /*
+         * We do not verify the argument validity. Since this class is internal, caller should have done verification
+         * itself. Furthermore those arguments are verified by WritableRaster constructors anyway.
+         */
         if (bandOffsets == null) {
             bandOffsets = new int[buffer.getNumBanks()];
         }
@@ -197,6 +146,25 @@ public final class RasterFactory extends Static {
     }
 
     /**
+     * Returns the {@link DataBuffer} constant for the given type. The given {@code sample} class
+     * should be a primitive type such as {@link Float#TYPE}. Wrappers class are also accepted.
+     *
+     * @param  sample    the primitive type or its wrapper class. May be {@code null}.
+     * @param  unsigned  whether the type should be considered unsigned.
+     * @return the {@link DataBuffer} type, or {@link DataBuffer#TYPE_UNDEFINED}.
+     */
+    public static int getType(final Class<?> sample, final boolean unsigned) {
+        switch (Numbers.getEnumConstant(sample)) {
+            case Numbers.BYTE:    if (unsigned) return DataBuffer.TYPE_BYTE; else break;
+            case Numbers.SHORT:   return unsigned ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_SHORT;
+            case Numbers.INTEGER: if (!unsigned) return DataBuffer.TYPE_INT; else break;
+            case Numbers.FLOAT:   return DataBuffer.TYPE_FLOAT;
+            case Numbers.DOUBLE:  return DataBuffer.TYPE_DOUBLE;
+        }
+        return DataBuffer.TYPE_UNDEFINED;
+    }
+
+    /**
      * Wraps the backing arrays of given NIO buffers into Java2D buffers.
      * This method wraps the underlying array of primitive types; data are not copied.
      * For each buffer, the data starts at {@linkplain Buffer#position() buffer position}
@@ -208,8 +176,8 @@ public final class RasterFactory extends Static {
      * @throws UnsupportedOperationException if a buffer is not backed by an accessible array.
      * @throws ReadOnlyBufferException if a buffer is backed by an array but is read-only.
      * @throws ArrayStoreException if the type of a backing array is not {@code dataType}.
-     * @throws ArithmeticException if the position of a buffer is too high.
-     * @throws IllegalArgumentException if buffers do not have the same amount of remaining values.
+     * @throws ArithmeticException if a buffer position overflows the 32 bits integer capacity.
+     * @throws RasterFormatException if buffers do not have the same amount of remaining values.
      */
     public static DataBuffer wrap(final int dataType, final Buffer... data) {
         final int numBands = data.length;
@@ -227,12 +195,13 @@ public final class RasterFactory extends Static {
         int length = 0;
         for (int i=0; i<numBands; i++) {
             final Buffer buffer = data[i];
+            ArgumentChecks.ensureNonNullElement("data", i, buffer);
             arrays [i] = buffer.array();
             offsets[i] = Math.addExact(buffer.arrayOffset(), buffer.position());
             final int r = buffer.remaining();
             if (i == 0) length = r;
             else if (length != r) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLengths));
+                throw new RasterFormatException(Resources.format(Resources.Keys.MismatchedBandSize));
             }
         }
         switch (dataType) {
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
index 2728aab..ffc6eb3 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
@@ -125,6 +125,16 @@ public final class Resources extends IndexedResourceBundle {
         public static final short IterationNotStarted = 4;
 
         /**
+         * The bands have different number of sample values.
+         */
+        public static final short MismatchedBandSize = 28;
+
+        /**
+         * The bands store sample values using different data types.
+         */
+        public static final short MismatchedDataType = 30;
+
+        /**
          * The two images have different size or pixel coordinates.
          */
         public static final short MismatchedImageLocation = 5;
@@ -176,6 +186,16 @@ public final class Resources extends IndexedResourceBundle {
         public static final short TooManyQualitatives = 17;
 
         /**
+         * Expected {0} bands but got {1}.
+         */
+        public static final short UnexpectedNumberOfBands_2 = 27;
+
+        /**
+         * Raster data type ‘{0}’ is unknown or unsupported.
+         */
+        public static final short UnknownDataType_1 = 29;
+
+        /**
          * Coordinate reference system is unspecified.
          */
         public static final short UnspecifiedCRS = 9;
@@ -186,6 +206,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short UnspecifiedGridExtent = 10;
 
         /**
+         * Raster data are unspecified.
+         */
+        public static final short UnspecifiedRasterData = 31;
+
+        /**
          * Coordinates transform is unspecified.
          */
         public static final short UnspecifiedTransform = 11;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
index 90419df..157b261 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
@@ -32,6 +32,8 @@ IllegalTransferFunction_1         = Illegal transfer function for \u201c{0}\u201
 IncompatibleTile_2                = The ({0}, {1}) tile has an unexpected size, number of bands or sample layout.
 IterationIsFinished               = Iteration is finished.
 IterationNotStarted               = Iteration did not started.
+MismatchedBandSize                = The bands have different number of sample values.
+MismatchedDataType                = The bands store sample values using different data types.
 MismatchedImageLocation           = The two images have different size or pixel coordinates.
 MismatchedSampleModel             = The two images use different sample models.
 MismatchedTileGrid                = The two images have different tile grid.
@@ -42,6 +44,9 @@ NotStrictlyOrderedDimensions      = The specified dimensions are not in strictly
 OutOfIteratorDomain_2             = The ({0,number}, {1,number}) pixel coordinate is outside iterator domain.
 PointOutsideCoverageDomain_1      = Point ({0}) is outside the coverage domain.
 TooManyQualitatives               = Too many qualitative categories.
+UnexpectedNumberOfBands_2         = Expected {0} bands but got {1}.
+UnknownDataType_1                 = Raster data type \u2018{0}\u2019 is unknown or unsupported.
 UnspecifiedCRS                    = Coordinate reference system is unspecified.
 UnspecifiedGridExtent             = Grid extent is unspecified.
+UnspecifiedRasterData             = Raster data are unspecified.
 UnspecifiedTransform              = Coordinates transform is unspecified.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
index f2a6c2d..ba0f80c 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
@@ -37,6 +37,8 @@ IllegalTransferFunction_1         = Fonction de transfert ill\u00e9gale pour la
 IncompatibleTile_2                = La tuile ({0}, {1}) a une taille, un nombre de bandes ou une disposition des valeurs inattendu.
 IterationIsFinished               = L\u2019it\u00e9ration est termin\u00e9e.
 IterationNotStarted               = L\u2019it\u00e9ration n\u2019a pas commenc\u00e9e.
+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.
 MismatchedImageLocation           = Les deux images ont une taille ou des coordonn\u00e9es pixels diff\u00e9rentes.
 MismatchedSampleModel             = Les deux images disposent les pixels diff\u00e9remment.
 MismatchedTileGrid                = Les deux images utilisent des grilles de tuiles diff\u00e9rentes.
@@ -47,6 +49,9 @@ NotStrictlyOrderedDimensions      = Les dimensions sp\u00e9cifi\u00e9es ne sont
 OutOfIteratorDomain_2             = La coordonn\u00e9e pixel ({0,number}, {1,number}) est en dehors du domaine de l\u2019it\u00e9rateur.
 PointOutsideCoverageDomain_1      = Le point ({0}) est en dehors du domaine de la couverture de donn\u00e9es.
 TooManyQualitatives               = Trop de cat\u00e9gories qualitatives.
+UnexpectedNumberOfBands_2         = On attendait {0} bandes mais {1} ont \u00e9t\u00e9 sp\u00e9cifi\u00e9es.
+UnknownDataType_1                 = Le type de donn\u00e9es raster \u2018{0}\u2019 est inconnu ou non-support\u00e9.
 UnspecifiedCRS                    = Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es n\u2019a pas \u00e9t\u00e9 sp\u00e9cifi\u00e9.
 UnspecifiedGridExtent             = L\u2019\u00e9tendue de la grille n\u2019a pas \u00e9t\u00e9 sp\u00e9cifi\u00e9e.
+UnspecifiedRasterData             = Les donn\u00e9es du raster n\u2019ont pas \u00e9t\u00e9 sp\u00e9cifi\u00e9es.
 UnspecifiedTransform              = La transformation de coordonn\u00e9es n\u2019a pas \u00e9t\u00e9 sp\u00e9cifi\u00e9e.
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties b/ide-project/NetBeans/nbproject/genfiles.properties
index cb85add..ec9b777 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=1a7e4f72
+nbproject/build-impl.xml.data.CRC32=80e7865b
 nbproject/build-impl.xml.script.CRC32=a7689f96
 nbproject/build-impl.xml.stylesheet.CRC32=3a2fa800@1.89.1.48
diff --git a/ide-project/NetBeans/nbproject/project.xml b/ide-project/NetBeans/nbproject/project.xml
index 2e7964c..3d5b601 100644
--- a/ide-project/NetBeans/nbproject/project.xml
+++ b/ide-project/NetBeans/nbproject/project.xml
@@ -116,6 +116,7 @@
             <word>polyline</word>
             <word>polylines</word>
             <word>recursivity</word>
+            <word>scanline</word>
             <word>spliterator</word>
             <word>subsampled</word>
             <word>subsampling</word>
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
index ba322ce..845eaf3 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
@@ -18,17 +18,13 @@ package org.apache.sis.storage.netcdf;
 
 import java.util.List;
 import java.awt.image.DataBuffer;
-import java.awt.image.ColorModel;
-import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
-import java.awt.image.WritableRaster;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.coverage.CannotEvaluateException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.internal.raster.RasterFactory;
-import org.apache.sis.internal.raster.ColorModelFactory;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.ImageRenderer;
 
 
 /**
@@ -41,11 +37,6 @@ import org.apache.sis.internal.raster.ColorModelFactory;
  */
 final class Image extends GridCoverage {
     /**
-     * Index of the band to show in rendered image.
-     */
-    private static final int VISIBLE_BAND = 0;
-
-    /**
      * The sample values.
      */
     private final DataBuffer data;
@@ -63,14 +54,11 @@ final class Image extends GridCoverage {
      * This returns a view as much as possible; sample values are not copied.
      */
     @Override
-    public RenderedImage render(final DirectPosition slicePoint) {
-        GridGeometry source = getGridGeometry();
-        GridGeometry target = source;
-        if (slicePoint != null) target = target.slice(slicePoint);
+    public RenderedImage render(final GridExtent target) {
         try {
-            WritableRaster raster = RasterFactory.createRaster(data, null, null, source.getExtent(), target.getExtent(), true);
-            ColorModel colors = ColorModelFactory.createColorModel(getSampleDimensions(), VISIBLE_BAND, data.getDataType(), ColorModelFactory.GRAYSCALE);
-            return new BufferedImage(colors, raster, false, null);
+            final ImageRenderer renderer = new ImageRenderer(this, target);
+            renderer.setData(data);
+            return renderer.image();
         } catch (ArithmeticException | IllegalArgumentException e) {
             throw new CannotEvaluateException(null, e);
         }


Mime
View raw message