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: ImageProcessor API adjustment, in particular with introduction of a type-safe DataType enumeration value instead than the DataBuffer.TYPE_* integer constants.
Date Thu, 30 Jul 2020 16:49:07 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 5ad04a9  ImageProcessor API adjustment, in particular with introduction of a type-safe DataType enumeration value instead than the DataBuffer.TYPE_* integer constants.
5ad04a9 is described below

commit 5ad04a9acad1e57cfc96dd35176551a660af73fb
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Jul 30 14:28:44 2020 +0200

    ImageProcessor API adjustment, in particular with introduction of a type-safe DataType enumeration value instead than the DataBuffer.TYPE_* integer constants.
---
 .../org/apache/sis/gui/coverage/RenderingData.java |  18 +-
 .../sis/coverage/grid/BufferedGridCoverage.java    |   8 +-
 .../sis/coverage/grid/ConvertedGridCoverage.java   |  31 +--
 .../org/apache/sis/coverage/grid/GridCoverage.java |  17 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  10 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    |  47 ++--
 .../sis/coverage/grid/ResampledGridCoverage.java   |   6 +-
 .../apache/sis/image/BandedSampleConverter.java    |   3 +-
 .../main/java/org/apache/sis/image/DataType.java   | 250 +++++++++++++++++++++
 .../java/org/apache/sis/image/ImageProcessor.java  | 162 ++++++++-----
 .../java/org/apache/sis/image/ResampledImage.java  |  38 ++--
 .../java/org/apache/sis/image/TransferType.java    |   4 +-
 .../sis/internal/coverage/j2d/Colorizer.java       |   8 +-
 .../sis/internal/coverage/j2d/RasterFactory.java   | 106 ++-------
 .../java/org/apache/sis/image/DataTypeTest.java    |  99 ++++++++
 .../org/apache/sis/image/ResampledImageTest.java   |   6 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   1 +
 .../org/apache/sis/internal/netcdf/Convention.java |   3 +-
 .../org/apache/sis/internal/netcdf/DataType.java   |  38 ++--
 19 files changed, 595 insertions(+), 260 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index 9a4151e..44e0cb4 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -19,15 +19,12 @@ package org.apache.sis.gui.coverage;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
-import java.util.function.Function;
-import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
-import org.apache.sis.coverage.Category;
 import org.apache.sis.coverage.SampleDimension;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.PixelInCell;
@@ -41,7 +38,6 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.Shapes2D;
 import org.apache.sis.image.ImageProcessor;
-import org.apache.sis.internal.coverage.j2d.Colorizer;
 import org.apache.sis.internal.coverage.j2d.ColorModelType;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.coverage.j2d.PreferredSize;
@@ -171,14 +167,6 @@ final class RenderingData implements Cloneable {
     private Statistics[] statistics;
 
     /**
-     * The colors to apply for given categories, or {@code null} if none.
-     * A null value means that the original colors specified in the {@link #data} image are used unmodified.
-     *
-     * @see Colorizer#GRAYSCALE
-     */
-    private Function<Category,Color[]> colors;
-
-    /**
      * The processor that we use for resampling image and stretching their color ramps.
      */
     final ImageProcessor processor;
@@ -227,7 +215,6 @@ final class RenderingData implements Cloneable {
 
     /**
      * Sets the data to given image, which can be {@code null}.
-     * This method does not reset the {@link #colors} to null.
      */
     @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
     final void setImage(final RenderedImage data, final GridGeometry domain, final List<SampleDimension> ranges) {
@@ -328,9 +315,8 @@ final class RenderingData implements Cloneable {
          */
         if (CREATE_INDEX_COLOR_MODEL) {
             final ColorModelType ct = ColorModelType.find(resampledImage.getColorModel());
-            if (ct.isSlow || (colors != null && ct.useColorRamp)) {
-                resampledImage = processor.visualize(resampledImage,
-                        dataRanges, (colors != null) ? colors : Colorizer.GRAYSCALE);
+            if (ct.isSlow || (processor.getCategoryColors() != null && ct.useColorRamp)) {
+                resampledImage = processor.visualize(resampledImage, dataRanges);
             }
         }
         return resampledImage;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index ac1d976..0df7a8c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -32,6 +32,7 @@ import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.image.DataType;
 
 // Branch-specific imports
 import org.apache.sis.internal.jdk9.JDK9;
@@ -177,12 +178,11 @@ public class BufferedGridCoverage extends GridCoverage {
     }
 
     /**
-     * Returns the {@link java.awt.image.DataBuffer} constant
-     * identifying the primitive type used for storing sample values.
+     * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final int getDataType() {
-        return data.getDataType();
+    final DataType getDataType() {
+        return DataType.forDataBufferType(data.getDataType());
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index 336b8ab..bb8de9e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -20,7 +20,6 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Optional;
-import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.coverage.CannotEvaluateException;
@@ -29,8 +28,8 @@ import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.internal.coverage.j2d.RasterFactory;
 import org.apache.sis.measure.NumberRange;
+import org.apache.sis.image.DataType;
 
 
 /**
@@ -71,10 +70,12 @@ final class ConvertedGridCoverage extends GridCoverage {
     private final boolean isConverted;
 
     /**
-     * One of {@link DataBuffer} constants the describe the sample values type
-     * of images produced by {@link #render(GridExtent)}.
+     * One of enumeration value that describe the sample values type of images
+     * produced by {@link #render(GridExtent)}. Shall not be {@code null}.
+     *
+     * @see #getDataType()
      */
-    private final int dataType;
+    private final DataType dataType;
 
     /**
      * Creates a new coverage with the same grid geometry than the given coverage but converted sample dimensions.
@@ -150,14 +151,14 @@ final class ConvertedGridCoverage extends GridCoverage {
     }
 
     /**
-     * Returns the {@link DataBuffer} constant for range of values of given sample dimensions.
+     * Returns the data type for range of values of given sample dimensions.
      *
      * @param  targets    the sample dimensions for which to get the data type.
      * @param  converted  whether the image will hold converted or packed values.
      * @param  source     if the type can not be determined, coverage from which to inherit the type as a fallback.
-     * @return the {@link DataBuffer} type.
+     * @return the data type (never null).
      */
-    static int getDataType(final List<SampleDimension> targets, final boolean converted, final GridCoverage source) {
+    static DataType getDataType(final List<SampleDimension> targets, final boolean converted, final GridCoverage source) {
         NumberRange<?> union = null;
         boolean allowsNaN = false;
         for (final SampleDimension dimension : targets) {
@@ -172,21 +173,21 @@ final class ConvertedGridCoverage extends GridCoverage {
             }
             if (!allowsNaN) allowsNaN = dimension.allowsNaN();
         }
-        int type = RasterFactory.getDataType(union, converted);
-        if (allowsNaN && type >= DataBuffer.TYPE_BYTE && type < DataBuffer.TYPE_FLOAT) {
-            type = (type < DataBuffer.TYPE_INT) ? DataBuffer.TYPE_FLOAT : DataBuffer.TYPE_DOUBLE;
+        if (union == null) {
+            return source.getDataType();
         }
-        if (type == DataBuffer.TYPE_UNDEFINED) {
-            type = source.getDataType();
+        DataType type = DataType.forRange(union, !converted);
+        if (allowsNaN) {
+            type = type.toFloat();
         }
         return type;
     }
 
     /**
-     * Returns the {@link DataBuffer} constant identifying the primitive type used for storing sample values.
+     * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final int getDataType() {
+    final DataType getDataType() {
         return dataType;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 459bd82..2336a16 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -20,7 +20,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.awt.image.ColorModel;
-import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
@@ -31,6 +30,7 @@ import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.coverage.j2d.Colorizer;
@@ -60,7 +60,7 @@ import org.opengis.coverage.CannotEvaluateException;
  */
 public abstract class GridCoverage {
     /**
-     * The processor to use for {@link #convert(RenderedImage, int, MathTransform1D[])} operations.
+     * The processor to use for {@link #convert(RenderedImage, DataType, MathTransform1D[])} operations.
      * Wrapped in a class for lazy instantiation.
      */
     private static final class Lazy {
@@ -183,11 +183,10 @@ public abstract class GridCoverage {
     }
 
     /**
-     * Returns the {@link DataBuffer} constant identifying the primitive type used for storing sample values.
-     * If the type is unknown, returns {@link DataBuffer#TYPE_UNDEFINED}.
+     * Returns the data type identifying the primitive type used for storing sample values.
      */
-    int getDataType() {
-        return DataBuffer.TYPE_UNDEFINED;
+    DataType getDataType() {
+        return DataType.DOUBLE;     // Must conservative value, should be overridden by subclasses.
     }
 
     /**
@@ -248,16 +247,16 @@ public abstract class GridCoverage {
      * Creates a new image of the given data type which will compute values using the given converters.
      *
      * @param  source      the image for which to convert sample values.
-     * @param  dataType    the type of this image resulting from conversion of given image.
+     * @param  dataType    the type of the image resulting from conversion of given image.
      * @param  converters  the transfer functions to apply on each band of the source image.
      * @return the image which compute converted values from the given source.
      */
-    final RenderedImage convert(final RenderedImage source, final int dataType, final MathTransform1D[] converters) {
+    final RenderedImage convert(final RenderedImage source, final DataType dataType, final MathTransform1D[] converters) {
         final int visibleBand = Math.max(0, ImageUtilities.getVisibleBand(source));
         final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
         final ColorModel colors;
         if (colorizer.initialize(sampleDimensions[visibleBand]) || colorizer.initialize(source.getColorModel())) {
-            colors = colorizer.createColorModel(dataType, sampleDimensions.length, visibleBand);
+            colors = colorizer.createColorModel(dataType.ordinal(), sampleDimensions.length, visibleBand);
         } else {
             colors = Colorizer.NULL_COLOR_MODEL;
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index e27fd61..1ef38c4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -40,6 +40,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform1D;
+import org.apache.sis.image.DataType;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.feature.Resources;
@@ -147,7 +148,7 @@ public class GridCoverage2D extends GridCoverage {
                            final MathTransform1D[] converters, final boolean isConverted)
     {
         super(source.gridGeometry, range);
-        final int dataType = ConvertedGridCoverage.getDataType(range, isConverted, source);
+        final DataType dataType = ConvertedGridCoverage.getDataType(range, isConverted, source);
         data           = convert(source.data, dataType, converters);
         gridToImageX   = source.gridToImageX;
         gridToImageY   = source.gridToImageY;
@@ -440,12 +441,11 @@ public class GridCoverage2D extends GridCoverage {
     }
 
     /**
-     * Returns the {@link java.awt.image.DataBuffer} constant
-     * identifying the primitive type used for storing sample values.
+     * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final int getDataType() {
-        return ImageUtilities.getDataType(data);
+    final DataType getDataType() {
+        return DataType.of(data);
     }
 
     /**
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 869c3f2..749dcce 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
@@ -32,6 +32,7 @@ import java.awt.image.RasterFormatException;
 import java.awt.image.Raster;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.image.DataType;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 import org.apache.sis.coverage.MismatchedCoverageRangeException;
 import org.apache.sis.coverage.SampleDimension;
@@ -480,32 +481,41 @@ public class ImageRenderer {
      * 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>
+     * {@link DataType#BYTE} and {@link DataType#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  dataType  type of data.
      * @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.
+     *
+     * @since 1.1
      */
-    public void setData(final int dataType, final Buffer... data) {
+    public void setData(final DataType dataType, final Buffer... data) {
+        ArgumentChecks.ensureNonNull("dataType", dataType);
         ArgumentChecks.ensureNonNull("data", data);
         ensureExpectedBandCount(data.length, true);
-        final DataBuffer banks = RasterFactory.wrap(dataType, data);
-        if (banks == null) {
-            throw new IllegalArgumentException(Resources.format(Resources.Keys.UnknownDataType_1, dataType));
-        }
-        setData(banks);
+        setData(RasterFactory.wrap(dataType, data));
+    }
+
+    /**
+     * @deprecated Replaced by {@link #setData(DataType, Buffer...)}.
+     *
+     * @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 RasterFormatException if {@code dataType} is not a supported value.
+     */
+    @Deprecated
+    public void setData(final int dataType, final Buffer... data) {
+        setData(DataType.forDataBufferType(dataType), data);
     }
 
     /**
@@ -514,7 +524,7 @@ public class ImageRenderer {
      * 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...)},
+     * <p><b>Implementation note:</b> the NIO buffers are set by a call to {@link #setData(DataType, Buffer...)},
      * which can be overridden by subclasses if desired.</p>
      *
      * @param  data  the vectors wrapping arrays of primitive type.
@@ -528,16 +538,15 @@ public class ImageRenderer {
         ArgumentChecks.ensureNonNull("data", data);
         ensureExpectedBandCount(data.length, true);
         final Buffer[] buffers = new Buffer[data.length];
-        int dataType = DataBuffer.TYPE_UNDEFINED;
+        DataType dataType = null;
         for (int i=0; i<data.length; i++) {
             final Vector v = data[i];
             ArgumentChecks.ensureNonNullElement("data", i, v);
-            final int t = RasterFactory.getDataType(v.getElementType(), v.isUnsigned());
-            if (dataType != t) {
-                if (i != 0) {
-                    throw new RasterFormatException(Resources.format(Resources.Keys.MismatchedDataType));
-                }
+            final DataType t = DataType.forPrimitiveType(v.getElementType(), v.isUnsigned());
+            if (dataType == null) {
                 dataType = t;
+            } else if (dataType != t) {
+                throw new RasterFormatException(Resources.format(Resources.Keys.MismatchedDataType));
             }
             buffers[i] = v.buffer().orElseThrow(UnsupportedOperationException::new);
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 4bc03d9..a06eb46 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -28,6 +28,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -532,11 +533,10 @@ final class ResampledGridCoverage extends GridCoverage {
     }
 
     /**
-     * Returns the {@link java.awt.image.DataBuffer} constant
-     * identifying the primitive type used for storing sample values.
+     * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final int getDataType() {
+    final DataType getDataType() {
         return source.getDataType();
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
index 7962232..d83b327 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
@@ -179,10 +179,11 @@ class BandedSampleConverter extends ComputedImage {
      * @param  sourceRanges  the expected range of values for each band in source image, or {@code null} if unknown.
      * @param  converters    the transfer functions to apply on each band of the source image.
      * @param  targetType    the type of this image resulting from conversion of given image.
+     *                       Shall be one of {@link DataBuffer} constants.
      * @param  colorModel    the color model for the expected range of values, or {@code null}.
      * @return the image which compute converted values from the given source.
      *
-     * @see ImageProcessor#convert(RenderedImage, NumberRange[], MathTransform1D[], int, ColorModel)
+     * @see ImageProcessor#convert(RenderedImage, NumberRange[], MathTransform1D[], DataType, ColorModel)
      */
     static BandedSampleConverter create(final RenderedImage source, final ImageLayout layout,
             final NumberRange<?>[] sourceRanges, final MathTransform1D[] converters,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java b/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java
new file mode 100644
index 0000000..1ef11ae
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java
@@ -0,0 +1,250 @@
+/*
+ * 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.image;
+
+import java.awt.image.Raster;
+import java.awt.image.DataBuffer;
+import java.awt.image.RenderedImage;
+import java.awt.image.RasterFormatException;
+import org.apache.sis.util.Numbers;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.feature.Resources;
+
+import static org.apache.sis.internal.util.Numerics.MAX_INTEGER_CONVERTIBLE_TO_FLOAT;
+
+
+/**
+ * Identification of the primitive type used for storing sample values in an image.
+ * This is a type-safe version of the {@code TYPE_*} constants defined in {@link DataBuffer}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public enum DataType {
+    /*
+     * Enumeration values must be declared in order of increasing `DataBuffer.TYPE_*` constant values,
+     * without skiping values. This requirement allow us to get `FOO.ordinal() == DataBuffer.TYPE_FOO`.
+     * This matching is verified by DataTypeTest.verifyOrdinalValues().
+     */
+
+    /**
+     * Unsigned 8-bits data.
+     */
+    BYTE,
+
+    /**
+     * Unsigned 16-bits data.
+     */
+    USHORT,
+
+    /**
+     * Signed 16-bits data.
+     */
+    SHORT,
+
+    /**
+     * Signed 32-bits data.
+     */
+    INT,
+
+    /**
+     * Single precision (32-bits) floating point data.
+     */
+    FLOAT,
+
+    /**
+     * Double precision (64-bits) floating point data.
+     */
+    DOUBLE;
+
+    /**
+     * All enumeration values, cached for avoiding to recreate this array
+     * on every {@link #forDataBufferType(int)} call.
+     */
+    private static final DataType[] VALUES = values();
+
+    /**
+     * Creates a new enumeration.
+     */
+    private DataType() {
+    }
+
+    /**
+     * Returns the data type of the given image.
+     *
+     * @param  image  the image for which to get the type.
+     * @return type of the given image (never {@code null}).
+     * @throws RasterFormatException if the image does not use a recognized data type.
+     */
+    public static DataType of(final RenderedImage image) {
+        return forDataBufferType(ImageUtilities.getDataType(image));
+    }
+
+    /**
+     * Returns the data type of the given raster.
+     *
+     * @param  raster  the raster for which to get the type.
+     * @return type of the given raster (never {@code null}).
+     * @throws RasterFormatException if the raster does not use a recognized data type.
+     */
+    public static DataType of(final Raster raster) {
+        return forDataBufferType(ImageUtilities.getDataType(raster));
+    }
+
+    /**
+     * Returns the smallest data type capable to store the given range of values.
+     * If the given range uses a floating point type, there there is a choice:
+     *
+     * <ul>
+     *   <li>If {@code asInteger} is {@code false}, then this method returns
+     *       {@link #FLOAT} or {@link #DOUBLE} depending on the range type.</li>
+     *   <li>Otherwise this method treats the floating point values as if they
+     *       were integers, with minimum value rounded toward negative infinity
+     *       and maximum value rounded toward positive infinity.</li>
+     * </ul>
+     *
+     * @param  range      the range of values.
+     * @param  asInteger  whether to handle floating point values as integers.
+     * @return smallest data type for the given range of values.
+     */
+    public static DataType forRange(final NumberRange<?> range, final boolean asInteger) {
+        ArgumentChecks.ensureNonNull("range", range);
+        final byte nt = Numbers.getEnumConstant(range.getElementType());
+        if (!asInteger) {
+            if (nt >= Numbers.DOUBLE)   return DOUBLE;
+            if (nt >= Numbers.FRACTION) return FLOAT;
+        }
+        final double min = range.getMinDouble();
+        final double max = range.getMaxDouble();
+        if (nt < Numbers.BYTE || nt > Numbers.FLOAT || nt == Numbers.LONG) {
+            /*
+             * Value type is long, double, BigInteger, BigDecimal or unknown type.
+             * If conversions to 32 bits integers would lost integer digits, or if
+             * a bound is NaN, stick to the most conservative data buffer type.
+             */
+            if (!(min >= -MAX_INTEGER_CONVERTIBLE_TO_FLOAT - 0.5 &&
+                  max <   MAX_INTEGER_CONVERTIBLE_TO_FLOAT + 0.5))
+            {
+                return DOUBLE;
+            }
+        }
+        /*
+         * Check most common types first. If the range could be both signed and unsigned short,
+         * give precedence to unsigned short because it works better with IndexColorModel.
+         * If a bounds is NaN, fallback on TYPE_FLOAT.
+         */
+        final DataType type;
+        if (min >= -0.5 && max < 0xFFFF + 0.5) {
+            type = (max < 0xFF + 0.5) ? BYTE : USHORT;
+        } else if (min >= Short.MIN_VALUE - 0.5 && max < Short.MAX_VALUE + 0.5) {
+            type = SHORT;
+        } else if (min >= Integer.MIN_VALUE - 0.5 && max < Integer.MAX_VALUE + 0.5) {
+            type = INT;
+        } else {
+            type = FLOAT;
+        }
+        return type;
+    }
+
+    /**
+     * Returns the data type for the given primitive type. The given {@code type} should be a primitive
+     * type such as {@link Short#TYPE}, but wrappers class such as {@code Short.class} are also accepted.
+     *
+     * @param  type      the primitive type or its wrapper class.
+     * @param  unsigned  whether the type should be considered unsigned.
+     * @return the data type (never {@code null}) for the given primitive type.
+     * @throws RasterFormatException if the given type is not a recognized.
+     */
+    public static DataType forPrimitiveType(final Class<?> type, final boolean unsigned) {
+        switch (Numbers.getEnumConstant(type)) {
+            case Numbers.BYTE:    return unsigned ? BYTE   : SHORT;
+            case Numbers.SHORT:   return unsigned ? USHORT : SHORT;
+            case Numbers.INTEGER: if (unsigned) break; else return INT;
+            case Numbers.FLOAT:   return FLOAT;
+            case Numbers.DOUBLE:  return DOUBLE;
+        }
+        throw new RasterFormatException(Resources.format(Resources.Keys.UnknownDataType_1, type));
+    }
+
+    /**
+     * Returns the enumeration value for the given {@link DataBuffer} constant.
+     *
+     * @param  type  one of {@code DataBuffer.TYPE_*} constants.
+     * @return the data type (never {@code null}) for the given data buffer type.
+     * @throws RasterFormatException if the given type is not a recognized {@code DataBuffer.TYPE_*} constant.
+     */
+    public static DataType forDataBufferType(final int type) {
+        if (type >= 0 && type < VALUES.length) {
+            return VALUES[type];
+        } else {
+            throw new RasterFormatException(Resources.format(Resources.Keys.UnknownDataType_1, type));
+        }
+    }
+
+    /**
+     * Returns the size in bits of this data type.
+     *
+     * @return size in bits of this data type.
+     */
+    public int size() {
+        return DataBuffer.getDataTypeSize(ordinal());
+    }
+
+    /**
+     * Returns whether this type is an unsigned integer type.
+     * Unsigned types are {@link #BYTE} and {@link #USHORT}.
+     *
+     * @return {@code true} if this type is an unsigned integer type.
+     */
+    public boolean isUnsigned() {
+        return ordinal() <= DataBuffer.TYPE_USHORT;
+    }
+
+    /**
+     * Returns whether this type is an integer type, signed or not.
+     * Integer types are {@link #BYTE}, {@link #USHORT}, {@link #SHORT} and {@link #INT}.
+     *
+     * @return {@code true} if this type is an integer type.
+     */
+    public boolean isInteger() {
+        return ordinal() <= DataBuffer.TYPE_INT;
+    }
+
+    /**
+     * Returns the smallest floating point type capable to store all values of this type
+     * without precision lost. This method returns:
+     *
+     * <ul>
+     *   <li>{@link #DOUBLE} if this data type is {@link #DOUBLE} or {@link #INT}.</li>
+     *   <li>{@link #FLOAT} for all other types.</li>
+     * </ul>
+     *
+     * The promotion of integer values to floating point values is sometime necessary
+     * when the image may contain {@link Float#NaN} values.
+     *
+     * @return the smallest of {@link #FLOAT} or {@link #DOUBLE} types
+     *         which can store all values of this type without any lost.
+     */
+    public DataType toFloat() {
+        final int type = ordinal();
+        return (type < DataBuffer.TYPE_INT || type == DataBuffer.TYPE_FLOAT) ? FLOAT : DOUBLE;
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index f1043e4..e279937 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -26,7 +26,6 @@ import java.util.logging.LogRecord;
 import java.awt.Color;
 import java.awt.Shape;
 import java.awt.Rectangle;
-import java.awt.image.DataBuffer;
 import java.awt.image.ColorModel;
 import java.awt.image.SampleModel;
 import java.awt.image.BufferedImage;
@@ -41,6 +40,7 @@ import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.coverage.j2d.Colorizer;
 import org.apache.sis.internal.coverage.j2d.ImageLayout;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.math.Statistics;
@@ -145,6 +145,26 @@ public class ImageProcessor implements Cloneable {
     private Number[] fillValues;
 
     /**
+     * Colors to use for arbitrary categories of sample values. This function can return {@code null}
+     * or empty arrays for some categories, which are interpreted as fully transparent pixels.
+     *
+     * @see #getCategoryColors()
+     * @see #setCategoryColors(Function)
+     */
+    private Function<Category,Color[]> colors;
+
+    /**
+     * Hints about the desired positional accuracy (in "real world" units or in pixel units),
+     * or {@code null} if unspecified. In order to avoid the need to clone this array in the
+     * {@link #clone()} method, the content of this array should not be modified.
+     * For setting new values, a new array should be created.
+     *
+     * @see #getPositionalAccuracyHints()
+     * @see #setPositionalAccuracyHints(Quantity...)
+     */
+    private Quantity<?>[] positionalAccuracyHints;
+
+    /**
      * Whether the operations can be executed in parallel.
      *
      * @see #getExecutionMode()
@@ -184,17 +204,6 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
-     * Hints about the desired positional accuracy (in "real world" units or in pixel units),
-     * or {@code null} if unspecified. In order to avoid the need to clone this array in the
-     * {@link #clone()} method, the content of this array should not be modified.
-     * For setting new values, a new array should be created.
-     *
-     * @see #getPositionalAccuracyHints()
-     * @see #setPositionalAccuracyHints(Quantity...)
-     */
-    private Quantity<?>[] positionalAccuracyHints;
-
-    /**
      * Whether errors occurring during computation should be propagated or wrapped in a {@link LogRecord}.
      * If errors are wrapped in a {@link LogRecord}, this field specifies what to do with the record.
      * Only one log record is created for all tiles that failed for the same operation on the same image.
@@ -300,45 +309,31 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
-     * Returns whether operations can be executed in parallel.
-     * If {@link Mode#SEQUENTIAL}, operations are executed sequentially in the caller thread.
-     * If {@link Mode#PARALLEL}, some operations may be parallelized using an arbitrary number of threads.
+     * Returns the colors to use for given categories of sample values, or {@code null} is unspecified.
+     * This method returns the function set by the last call to {@link #setCategoryColors(Function)}.
      *
-     * @return whether the operations can be executed in parallel.
+     * @return colors to use for arbitrary categories of sample values, or {@code null} for default.
      */
-    public synchronized Mode getExecutionMode() {
-        return executionMode;
+    public synchronized Function<Category,Color[]> getCategoryColors() {
+        return colors;
     }
 
     /**
-     * Sets whether operations can be executed in parallel.
-     * This value can be set to {@link Mode#PARALLEL} if the {@link RenderedImage} instances are thread-safe
-     * and provide a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int, int)}.
-     * If {@link Mode#SEQUENTIAL}, only the caller thread is used. Sequential operations may be useful
-     * for processing {@link RenderedImage} implementations that may not be thread-safe.
+     * Sets the colors to use for given categories in image, or {@code null} is unspecified.
+     * This function provides a way to colorize images without knowing in advance the numerical values of pixels.
+     * For example instead of specifying <cite>"pixel value 0 in blue, 1 in green, 2 in yellow"</cite>,
+     * this function allows to specify <cite>"Lakes in blue, Forests in green, Sand in yellow"</cite>.
+     * It is still possible however to use numerical values if the function desires to do so,
+     * since this information is available with {@link Category#getSampleRange()}.
      *
-     * <p>It is safe to set this flag to {@link Mode#PARALLEL} with {@link java.awt.image.BufferedImage}
-     * (it will actually have no effect in this particular case) or with Apache SIS implementations of
-     * {@link RenderedImage}.</p>
+     * <p>This function is used by methods expecting {@link SampleDimension} arguments such as
+     * {@link #visualize(RenderedImage, List)}. The given function can return {@code null} or
+     * empty arrays for some categories, which are interpreted as fully transparent pixels.</p>
      *
-     * @param  mode  whether the operations can be executed in parallel.
+     * @param  colors  colors to use for arbitrary categories of sample values, or {@code null} for default.
      */
-    public synchronized void setExecutionMode(final Mode mode) {
-        ArgumentChecks.ensureNonNull("mode", mode);
-        executionMode = mode;
-    }
-
-    /**
-     * Whether the operations can be executed in parallel for the specified image.
-     * This method shall be invoked in a method synchronized on {@code this}.
-     */
-    private boolean parallel(final RenderedImage source) {
-        assert Thread.holdsLock(this);
-        switch (executionMode) {
-            case PARALLEL:   return true;
-            case SEQUENTIAL: return false;
-            default:         return source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX);
-        }
+    public synchronized void setCategoryColors(final Function<Category,Color[]> colors) {
+        this.colors = colors;
     }
 
     /**
@@ -390,6 +385,48 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
+     * Returns whether operations can be executed in parallel.
+     * If {@link Mode#SEQUENTIAL}, operations are executed sequentially in the caller thread.
+     * If {@link Mode#PARALLEL}, some operations may be parallelized using an arbitrary number of threads.
+     *
+     * @return whether the operations can be executed in parallel.
+     */
+    public synchronized Mode getExecutionMode() {
+        return executionMode;
+    }
+
+    /**
+     * Sets whether operations can be executed in parallel.
+     * This value can be set to {@link Mode#PARALLEL} if the {@link RenderedImage} instances are thread-safe
+     * and provide a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int, int)}.
+     * If {@link Mode#SEQUENTIAL}, only the caller thread is used. Sequential operations may be useful
+     * for processing {@link RenderedImage} implementations that may not be thread-safe.
+     *
+     * <p>It is safe to set this flag to {@link Mode#PARALLEL} with {@link java.awt.image.BufferedImage}
+     * (it will actually have no effect in this particular case) or with Apache SIS implementations of
+     * {@link RenderedImage}.</p>
+     *
+     * @param  mode  whether the operations can be executed in parallel.
+     */
+    public synchronized void setExecutionMode(final Mode mode) {
+        ArgumentChecks.ensureNonNull("mode", mode);
+        executionMode = mode;
+    }
+
+    /**
+     * Whether the operations can be executed in parallel for the specified image.
+     * This method shall be invoked in a method synchronized on {@code this}.
+     */
+    private boolean parallel(final RenderedImage source) {
+        assert Thread.holdsLock(this);
+        switch (executionMode) {
+            case PARALLEL:   return true;
+            case SEQUENTIAL: return false;
+            default:         return source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX);
+        }
+    }
+
+    /**
      * Returns whether exceptions occurring during computation are propagated or logged.
      * If {@link ErrorAction#THROW} (the default), exceptions are wrapped in {@link ImagingOpException} and thrown.
      * If any other value, exceptions are wrapped in a {@link LogRecord}, filtered then eventually logged.
@@ -626,32 +663,35 @@ public class ImageProcessor implements Cloneable {
      *
      * <p>The {@code sourceRanges} array is only a hint for this method. The array may be {@code null}
      * or contain {@code null} elements, and may be of any length. Missing elements are considered null
-     * and extraneous elements are ignored.</p>
+     * and extraneous elements are ignored. Those ranges do not need to encompass all possible values;
+     * it is sufficient to provide only typical or "most interesting" ranges.</p>
      *
      * @param  source        the image for which to convert sample values.
      * @param  sourceRanges  approximate ranges of values for each band in source image, or {@code null} if unknown.
      * @param  converters    the transfer functions to apply on each band of the source image.
-     * @param  targetType    the type of image resulting from conversions. Shall be one of {@link DataBuffer} constants.
+     * @param  targetType    the type of image resulting from conversions.
      * @param  colorModel    color model of resulting image, or {@code null}.
      * @return the image which compute converted values from the given source.
      */
     public RenderedImage convert(final RenderedImage source, final NumberRange<?>[] sourceRanges,
-                MathTransform1D[] converters, final int targetType, final ColorModel colorModel)
+                MathTransform1D[] converters, final DataType targetType, final ColorModel colorModel)
     {
         ArgumentChecks.ensureNonNull("source", source);
         ArgumentChecks.ensureNonNull("converters", converters);
+        ArgumentChecks.ensureNonNull("targetType", targetType);
         ArgumentChecks.ensureSizeBetween("converters", 1, ImageUtilities.getNumBands(source), converters.length);
         converters = converters.clone();
         for (int i=0; i<converters.length; i++) {
             ArgumentChecks.ensureNonNullElement("converters", i, converters[i]);
         }
         // No need to clone `sourceRanges` because it is not stored by `BandedSampleConverter`.
-        return unique(BandedSampleConverter.create(source, ImageLayout.DEFAULT, sourceRanges, converters, targetType, colorModel));
+        return unique(BandedSampleConverter.create(source, ImageLayout.DEFAULT,
+                sourceRanges, converters, targetType.ordinal(), colorModel));
     }
 
     /**
      * Creates a new image which will resample the given image. The resampling operation is defined
-     * by a non-linear transform from the <em>new</em> image to the specified <em>source</em> image.
+     * by a potentially non-linear transform from the <em>new</em> image to the specified <em>source</em> image.
      * That transform should map {@linkplain org.opengis.referencing.datum.PixelInCell#CELL_CENTER pixel centers}.
      * If that transform produces coordinates that are outside source envelope bounds, then the corresponding pixels
      * in the new image are set to {@linkplain #getFillValues() fill values}. Otherwise sample values are interpolated
@@ -659,7 +699,7 @@ public class ImageProcessor implements Cloneable {
      *
      * <p>If the given source is an instance of {@link ResampledImage},
      * then this method will use {@linkplain PlanarImage#getSources() the source} of the given source.
-     * The intent is to avoid resampling a resampled image; instead this method tries to work on the original data.</p>
+     * The intent is to avoid resampling a resampled image; instead this method works on the original data.</p>
      *
      * @param  source    the image to be resampled.
      * @param  bounds    domain of pixel coordinates of resampled image to create.
@@ -708,8 +748,9 @@ public class ImageProcessor implements Cloneable {
                 fillValues              = this.fillValues;
                 positionalAccuracyHints = this.positionalAccuracyHints;
             }
-            resampled = unique(new ResampledImage(source, bounds, toSource,
-                    interpolation, fillValues, positionalAccuracyHints));
+            resampled = unique(new ResampledImage(source,
+                    ImageLayout.DEFAULT.createCompatibleSampleModel(source, bounds),
+                    bounds, toSource, interpolation, fillValues, positionalAccuracyHints));
             break;
         }
         return RecoloredImage.create(resampled, cm);
@@ -798,15 +839,17 @@ public class ImageProcessor implements Cloneable {
      * @param  source  the image to recolor for visualization purposes.
      * @param  ranges  description of {@code source} bands, or {@code null} if none. This is typically
      *                 obtained by {@link org.apache.sis.coverage.grid.GridCoverage#getSampleDimensions()}.
-     * @param  colors  the colors to use for given categories. This function can return {@code null} or
-     *                 empty arrays for some categories, which are interpreted as fully transparent pixels.
      * @return recolored image for visualization purposes only.
      */
-    public RenderedImage visualize(final RenderedImage source,
-            final List<SampleDimension> ranges, final Function<Category,Color[]> colors)
-    {
+    public RenderedImage visualize(final RenderedImage source, final List<SampleDimension> ranges) {
         ArgumentChecks.ensureNonNull("source", source);
-        ArgumentChecks.ensureNonNull("colors", colors);
+        Function<Category,Color[]> colors;
+        synchronized (this) {
+            colors = this.colors;
+        }
+        if (colors == null) {
+            colors = Colorizer.GRAYSCALE;
+        }
         try {
             return RecoloredImage.toIndexedColors(this, source, ranges, colors, null);
         } catch (IllegalStateException | NoninvertibleTransformException e) {
@@ -829,18 +872,21 @@ public class ImageProcessor implements Cloneable {
             final Filter        errorAction;
             final Interpolation interpolation;
             final Number[]      fillValues;
+            final Function<Category,Color[]> colors;
             final Quantity<?>[] positionalAccuracyHints;
             synchronized (this) {
                 executionMode           = this.executionMode;
                 errorAction             = this.errorAction;
                 interpolation           = this.interpolation;
                 fillValues              = this.fillValues;
+                colors                  = this.colors;
                 positionalAccuracyHints = this.positionalAccuracyHints;
             }
             synchronized (other) {
                 return errorAction.equals(other.errorAction)     &&
                      executionMode.equals(other.executionMode)   &&
                      interpolation.equals(other.interpolation)   &&
+                     Objects.equals(colors, other.colors)        &&
                      Arrays.equals(fillValues, other.fillValues) &&
                      Arrays.equals(positionalAccuracyHints, other.positionalAccuracyHints);
             }
@@ -856,7 +902,7 @@ public class ImageProcessor implements Cloneable {
     @Override
     public synchronized int hashCode() {
         return Objects.hash(getClass(), errorAction, executionMode, interpolation)
-                + 37 * Arrays.hashCode(fillValues)
+                + 37 * Arrays.hashCode(fillValues) + 31 * Objects.hashCode(colors)
                 + 39 * Arrays.hashCode(positionalAccuracyHints);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index 425ab06..87de4f5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -28,6 +28,7 @@ import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.ImagingOpException;
+import java.awt.image.SampleModel;
 import javax.measure.Quantity;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
@@ -35,7 +36,6 @@ import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.apache.sis.internal.coverage.j2d.ImageLayout;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
@@ -162,21 +162,30 @@ public class ResampledImage extends ComputedImage {
 
     /**
      * Creates a new image which will resample the given image. The resampling operation is defined
-     * by a non-linear transform from <em>this</em> image to the specified <em>source</em> image.
+     * by a potentially non-linear transform from <em>this</em> image to the specified <em>source</em> image.
      * That transform should map {@linkplain org.opengis.referencing.datum.PixelInCell#CELL_CENTER pixel centers}.
      *
+     * <p>The {@code sampleModel} determines the tile size and the target data type. This is often the same sample
+     * model than the one used by the {@code source} image, but may also be different for forcing a different tile
+     * size or a different data type (e.g. {@code byte} versus {@code float}) for storing resampled values.
+     * If the specified sample model is not the same than the one used by the source image,
+     * then subclass should override {@link #getColorModel()} for returning a color model which is
+     * {@linkplain ColorModel#isCompatibleSampleModel(SampleModel) compatible with the sample model}.</p>
+     *
      * <p>If a pixel in this image can not be mapped to a pixel in the source image, then the sample values are set
      * to {@code fillValues}. If the given array is {@code null}, or if any element in the given array is {@code null},
-     * then the default fill value is NaN for floating point data types or zero for integer data types.</p>
+     * then the default fill value is NaN for floating point data types or zero for integer data types.
+     * If the array is shorter than the number of bands, then above-cited default values are used for missing values.
+     * If longer than the number of bands, extraneous values are ignored.</p>
      *
      * @param  source         the image to be resampled.
+     * @param  sampleModel    the sample model shared by all tiles in this resampled image.
      * @param  bounds         domain of pixel coordinates of this resampled image.
      * @param  toSource       conversion of pixel coordinates of this image to pixel coordinates of {@code source} image.
      * @param  interpolation  the object to use for performing interpolations.
      * @param  fillValues     the values to use for pixels in this image that can not be mapped to pixels in source image.
-     *                        May be {@code null} or contain {@code null} elements. If shorter than the number of bands,
-     *                        missing values are assumed {@code null}. If longer than the number of bands, extraneous
-     *                        values are ignored.
+     *                        May be {@code null} or contain {@code null} elements, and may have any length
+     *                        (see above for more details).
      * @param  accuracy       values of {@value #POSITIONAL_ACCURACY_KEY} property, or {@code null} if none.
      *                        This constructor may retain only a subset of specified values or replace some of them.
      *                        If an accuracy is specified in {@linkplain Units#PIXEL pixel units}, then a value such as
@@ -185,10 +194,11 @@ public class ResampledImage extends ComputedImage {
      *
      * @see ImageProcessor#resample(RenderedImage, Rectangle, MathTransform)
      */
-    protected ResampledImage(final RenderedImage source, final Rectangle bounds, final MathTransform toSource,
-            final Interpolation interpolation, final Number[] fillValues, final Quantity<?>[] accuracy)
+    protected ResampledImage(final RenderedImage source, final SampleModel sampleModel, final Rectangle bounds,
+            final MathTransform toSource, final Interpolation interpolation, final Number[] fillValues,
+            final Quantity<?>[] accuracy)
     {
-        super(ImageLayout.DEFAULT.createCompatibleSampleModel(source, bounds), source);
+        super(sampleModel, source);
         if (source.getWidth() <= 0 || source.getHeight() <= 0) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.EmptyImage));
         }
@@ -250,12 +260,11 @@ public class ResampledImage extends ComputedImage {
         this.toSourceSupport = toSourceSupport;
         this.linearAccuracy  = linearAccuracy;
         /*
-         * Copy the `fillValues` either as an `int[]` or `double[]` array, depending on
-         * whether the data type is an integer type or not. Null elements default to zero.
+         * Copy the `fillValues` either as an `int[]` or `double[]` array, depending on whether
+         * the target data type is an integer type or not. Null elements default to zero.
          */
         final int numBands = ImageUtilities.getNumBands(source);
-        final int dataType = ImageUtilities.getDataType(source);
-        if (ImageUtilities.isIntegerType(dataType)) {
+        if (ImageUtilities.isIntegerType(sampleModel.getDataType())) {
             final int[] fill = new int[numBands];
             if (fillValues != null) {
                 for (int i=Math.min(fillValues.length, numBands); --i >= 0;) {
@@ -402,7 +411,8 @@ public class ResampledImage extends ComputedImage {
     }
 
     /**
-     * Returns the same color model than the source image.
+     * Returns the color model of this resampled image.
+     * Default implementation assumes that this image has the same color model than the source image.
      *
      * @return the color model, or {@code null} if unspecified.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/TransferType.java b/core/sis-feature/src/main/java/org/apache/sis/image/TransferType.java
index 07c1bf4..16e1702 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/TransferType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/TransferType.java
@@ -34,8 +34,8 @@ import org.apache.sis.util.resources.Errors;
  * the same than the type used by the raster for storing data. In particular, {@code byte} and {@code short}
  * (both signed and unsigned) are converted to {@code int} during the transfer.
  *
- * {@link Raster} and {@link PixelIterator} transfer data in {@code int[]}, {@code float[]} and {@code double[]} arrays.
- * Additionally, {@code PixelIterator} uses also {@link IntBuffer}, {@link FloatBuffer} and {@link DoubleBuffer}.
+ * <p>{@link Raster} and {@link PixelIterator} transfer data in {@code int[]}, {@code float[]} and {@code double[]} arrays.
+ * Additionally, {@code PixelIterator} uses also {@link IntBuffer}, {@link FloatBuffer} and {@link DoubleBuffer}.</p>
  *
  * <div class="note"><b>Future evolution:</b>
  * this class may be refactored as an enumeration in a future Java version if
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
index b8f8d4d..52c3ef2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
@@ -31,6 +31,7 @@ import java.awt.image.SampleModel;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.image.DataType;
 import org.apache.sis.coverage.Category;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.feature.Resources;
@@ -83,10 +84,9 @@ public final class Colorizer {
     private static final int MAX_VALUE = 0xFF;
 
     /**
-     * The {@link DataBuffer} type resulting from sample values conversion applied by
-     * {@link #compactColorModel(int, int)}.
+     * The type resulting from sample values conversion applied by {@link #compactColorModel(int, int)}.
      */
-    public static final int TYPE_COMPACT = DataBuffer.TYPE_BYTE;
+    public static final DataType TYPE_COMPACT = DataType.BYTE;
 
     /**
      * Applies a gray scale to quantitative category and transparent colors to qualitative categories.
@@ -496,7 +496,7 @@ reuse:  if (source != null) {
     public ColorModel compactColorModel(final int numBands, final int visibleBand) {
         checkInitializationStatus(true);
         compact();
-        return createColorModel(TYPE_COMPACT, numBands, visibleBand);
+        return createColorModel(TYPE_COMPACT.ordinal(), numBands, visibleBand);
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
index a2ced94..0823dd5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
@@ -34,17 +34,14 @@ import java.awt.image.WritableRaster;
 import java.awt.image.BufferedImage;
 import java.nio.Buffer;
 import java.nio.ReadOnlyBufferException;
+import org.apache.sis.image.DataType;
 import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.ArgumentChecks;
 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.collection.WeakHashSet;
 
-import static org.apache.sis.internal.util.Numerics.MAX_INTEGER_CONVERTIBLE_TO_FLOAT;
-
 
 /**
  * Creates rasters from given properties. Contains also convenience methods for
@@ -203,98 +200,31 @@ 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 getDataType(final Class<?> sample, final boolean unsigned) {
-        switch (Numbers.getEnumConstant(sample)) {
-            case Numbers.BYTE:    return unsigned ? DataBuffer.TYPE_BYTE      : DataBuffer.TYPE_SHORT;
-            case Numbers.SHORT:   return unsigned ? DataBuffer.TYPE_USHORT    : DataBuffer.TYPE_SHORT;
-            case Numbers.INTEGER: return unsigned ? DataBuffer.TYPE_UNDEFINED : DataBuffer.TYPE_INT;
-            case Numbers.FLOAT:   return DataBuffer.TYPE_FLOAT;
-            case Numbers.DOUBLE:  return DataBuffer.TYPE_DOUBLE;
-            default:              return DataBuffer.TYPE_UNDEFINED;
-        }
-    }
-
-    /**
-     * Returns the {@link DataBuffer} constant for the given range of values.
-     * If {@code keepFloat} is {@code false}, then this method tries to return
-     * an integer type regardless if the range uses a floating point type.
-     * Range checks for integers assume ties rounding to positive infinity.
-     *
-     * @param  range      the range of values, or {@code null}.
-     * @param  keepFloat  whether to avoid integer types if the range uses floating point numbers.
-     * @return the {@link DataBuffer} type or {@link DataBuffer#TYPE_UNDEFINED} if the given range was null.
-     */
-    public static int getDataType(final NumberRange<?> range, final boolean keepFloat) {
-        if (range == null) {
-            return DataBuffer.TYPE_UNDEFINED;
-        }
-        final byte nt = Numbers.getEnumConstant(range.getElementType());
-        if (keepFloat) {
-            if (nt >= Numbers.DOUBLE)   return DataBuffer.TYPE_DOUBLE;
-            if (nt >= Numbers.FRACTION) return DataBuffer.TYPE_FLOAT;
-        }
-        final double min = range.getMinDouble();
-        final double max = range.getMaxDouble();
-        if (nt < Numbers.BYTE || nt > Numbers.FLOAT || nt == Numbers.LONG) {
-            /*
-             * Value type is long, double, BigInteger, BigDecimal or unknown type.
-             * If conversions to 32 bits integers would lost integer digits, or if
-             * a bound is NaN, stick to the most conservative data buffer type.
-             *
-             * Range check assumes ties rounding to positive infinity.
-             */
-            if (!(min >= -MAX_INTEGER_CONVERTIBLE_TO_FLOAT - 0.5 && max < MAX_INTEGER_CONVERTIBLE_TO_FLOAT + 0.5)) {
-                return DataBuffer.TYPE_DOUBLE;
-            }
-        }
-        /*
-         * Check most common types first. If the range could be both signed and unsigned short,
-         * give precedence to unsigned short because it works better with IndexColorModel.
-         * If a bounds is NaN, fallback on TYPE_FLOAT.
-         */
-        if (min >= -0.5 && max < 0xFFFF + 0.5) {
-            return (max < 0xFF + 0.5) ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT;
-        } else if (min >= Short.MIN_VALUE - 0.5 && max < Short.MAX_VALUE + 0.5) {
-            return DataBuffer.TYPE_SHORT;
-        } else if (min >= Integer.MIN_VALUE - 0.5 && max < Integer.MAX_VALUE + 0.5) {
-            return DataBuffer.TYPE_INT;
-        }
-        return DataBuffer.TYPE_FLOAT;
-    }
-
-    /**
      * 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}
      * and ends at {@linkplain Buffer#limit() limit}.
      *
-     * @param  dataType  type of buffer to create as one of {@link DataBuffer} constants.
+     * @param  dataType  type of buffer to create.
      * @param  data      the data, one for each band.
-     * @return buffer of the given type, or {@code null} if {@code dataType} is unrecognized.
+     * @return buffer of the given type (ever null).
      * @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 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) {
+    public static DataBuffer wrap(final DataType dataType, final Buffer... data) {
         final int numBands = data.length;
         final Object[] arrays;
         switch (dataType) {
-            case DataBuffer.TYPE_USHORT: // fall through
-            case DataBuffer.TYPE_SHORT:  arrays = new short [numBands][]; break;
-            case DataBuffer.TYPE_INT:    arrays = new int   [numBands][]; break;
-            case DataBuffer.TYPE_BYTE:   arrays = new byte  [numBands][]; break;
-            case DataBuffer.TYPE_FLOAT:  arrays = new float [numBands][]; break;
-            case DataBuffer.TYPE_DOUBLE: arrays = new double[numBands][]; break;
-            default: return null;
+            case USHORT: // fall through
+            case SHORT:  arrays = new short [numBands][]; break;
+            case INT:    arrays = new int   [numBands][]; break;
+            case BYTE:   arrays = new byte  [numBands][]; break;
+            case FLOAT:  arrays = new float [numBands][]; break;
+            case DOUBLE: arrays = new double[numBands][]; break;
+            default: throw new AssertionError(dataType);
         }
         final int[] offsets = new int[numBands];
         int length = 0;
@@ -310,13 +240,13 @@ public final class RasterFactory extends Static {
             }
         }
         switch (dataType) {
-            case DataBuffer.TYPE_BYTE:   return new DataBufferByte  (  (byte[][]) arrays, length, offsets);
-            case DataBuffer.TYPE_SHORT:  return new DataBufferShort ( (short[][]) arrays, length, offsets);
-            case DataBuffer.TYPE_USHORT: return new DataBufferUShort( (short[][]) arrays, length, offsets);
-            case DataBuffer.TYPE_INT:    return new DataBufferInt   (   (int[][]) arrays, length, offsets);
-            case DataBuffer.TYPE_FLOAT:  return new DataBufferFloat ( (float[][]) arrays, length, offsets);
-            case DataBuffer.TYPE_DOUBLE: return new DataBufferDouble((double[][]) arrays, length, offsets);
-            default: return null;
+            case BYTE:   return new DataBufferByte  (  (byte[][]) arrays, length, offsets);
+            case SHORT:  return new DataBufferShort ( (short[][]) arrays, length, offsets);
+            case USHORT: return new DataBufferUShort( (short[][]) arrays, length, offsets);
+            case INT:    return new DataBufferInt   (   (int[][]) arrays, length, offsets);
+            case FLOAT:  return new DataBufferFloat ( (float[][]) arrays, length, offsets);
+            case DOUBLE: return new DataBufferDouble((double[][]) arrays, length, offsets);
+            default: throw new AssertionError(dataType);
         }
     }
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/DataTypeTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/DataTypeTest.java
new file mode 100644
index 0000000..6e911ce
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/DataTypeTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.image;
+
+import java.awt.image.DataBuffer;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Verifies {@link DataType}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class DataTypeTest extends TestCase {
+    /**
+     * Verifies that {@link DataType} ordinal values match {@link DataBuffer} constant values.
+     */
+    @Test
+    public void verifyOrdinalValues() {
+        assertEquals(DataBuffer.TYPE_BYTE  , DataType.BYTE  .ordinal());
+        assertEquals(DataBuffer.TYPE_USHORT, DataType.USHORT.ordinal());
+        assertEquals(DataBuffer.TYPE_SHORT , DataType.SHORT .ordinal());
+        assertEquals(DataBuffer.TYPE_INT   , DataType.INT   .ordinal());
+        assertEquals(DataBuffer.TYPE_FLOAT , DataType.FLOAT .ordinal());
+        assertEquals(DataBuffer.TYPE_DOUBLE, DataType.DOUBLE.ordinal());
+    }
+
+    /**
+     * Tests {@link DataType#size()}.
+     */
+    @Test
+    public void testSize() {
+        assertEquals(Byte   .SIZE, DataType.BYTE  .size());
+        assertEquals(Short  .SIZE, DataType.USHORT.size());
+        assertEquals(Short  .SIZE, DataType.SHORT .size());
+        assertEquals(Integer.SIZE, DataType.INT   .size());
+        assertEquals(Float  .SIZE, DataType.FLOAT .size());
+        assertEquals(Double .SIZE, DataType.DOUBLE.size());
+    }
+
+    /**
+     * Tests {@link DataType#isUnsigned()}.
+     */
+    @Test
+    public void testIsUnsigned() {
+        assertTrue (DataType.BYTE  .isUnsigned());
+        assertTrue (DataType.USHORT.isUnsigned());
+        assertFalse(DataType.SHORT .isUnsigned());
+        assertFalse(DataType.INT   .isUnsigned());
+        assertFalse(DataType.FLOAT .isUnsigned());
+        assertFalse(DataType.DOUBLE.isUnsigned());
+    }
+
+    /**
+     * Tests {@link DataType#isInteger()}.
+     */
+    @Test
+    public void testIsInteger() {
+        assertTrue (DataType.BYTE  .isInteger());
+        assertTrue (DataType.USHORT.isInteger());
+        assertTrue (DataType.SHORT .isInteger());
+        assertTrue (DataType.INT   .isInteger());
+        assertFalse(DataType.FLOAT .isInteger());
+        assertFalse(DataType.DOUBLE.isInteger());
+    }
+
+    /**
+     * Tests {@link DataType#toFloat()}.
+     */
+    @Test
+    public void testToFloat() {
+        assertEquals(DataType.FLOAT,  DataType.BYTE  .toFloat());
+        assertEquals(DataType.FLOAT,  DataType.USHORT.toFloat());
+        assertEquals(DataType.FLOAT,  DataType.SHORT .toFloat());
+        assertEquals(DataType.DOUBLE, DataType.INT   .toFloat());
+        assertEquals(DataType.FLOAT,  DataType.FLOAT .toFloat());
+        assertEquals(DataType.DOUBLE, DataType.DOUBLE.toFloat());
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
index ac104ba..7722097 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
@@ -29,6 +29,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
+import org.apache.sis.internal.coverage.j2d.ImageLayout;
 import org.apache.sis.internal.coverage.j2d.RasterFactory;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
 import org.apache.sis.test.TestCase;
@@ -270,7 +271,10 @@ public final strictfp class ResampledImageTest extends TestCase {
         } catch (NoninvertibleTransformException e) {
             throw new AssertionError(e);
         }
-        target = new ResampledImage(source, new Rectangle(9, 9), toSource, interpolation, null, null);
+        final Rectangle bounds = new Rectangle(9, 9);
+        target = new ResampledImage(source,
+                ImageLayout.DEFAULT.createCompatibleSampleModel(source, bounds),
+                bounds, toSource, interpolation, null, null);
 
         assertEquals("numXTiles", 1, target.getNumXTiles());
         assertEquals("numYTiles", 1, target.getNumYTiles());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index b623240..0ebc6d6 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -79,6 +79,7 @@ import org.junit.runners.Suite;
     org.apache.sis.internal.coverage.j2d.ImageLayoutTest.class,
     org.apache.sis.internal.coverage.j2d.ScaledColorSpaceTest.class,
     org.apache.sis.internal.coverage.j2d.ColorizerTest.class,
+    org.apache.sis.image.DataTypeTest.class,
     org.apache.sis.image.PlanarImageTest.class,
     org.apache.sis.image.ComputedImageTest.class,
     org.apache.sis.image.DefaultIteratorTest.class,
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index d7c3973..ebd410e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -23,7 +23,6 @@ import java.util.Iterator;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Locale;
-import java.awt.image.DataBuffer;
 import javax.measure.Unit;
 import javax.measure.format.ParserException;
 import org.opengis.referencing.crs.ProjectedCRS;
@@ -237,7 +236,7 @@ public class Convention {
         }
         if (numVectors >= Grid.MIN_DIMENSION) {
             final DataType dataType = variable.getDataType();
-            if (dataType.rasterDataType != DataBuffer.TYPE_UNDEFINED) {
+            if (dataType.rasterDataType != null) {
                 return VariableRole.COVERAGE;
             }
         }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
index 45a4184..0bc81b4 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
@@ -45,77 +45,77 @@ public enum DataType {
     /**
      * The enumeration for unknown data type. This is not a valid netCDF type.
      */
-    UNKNOWN(Numbers.OTHER, false, false, (byte) 0, DataBuffer.TYPE_UNDEFINED),
+    UNKNOWN(Numbers.OTHER, false, false, (byte) 0, null),
 
     /**
      * 8 bits signed integer (netCDF type 1).
      * Can be made unsigned by assigning the “_Unsigned” attribute to a netCDF variable.
      */
-    BYTE(Numbers.BYTE, true, false, (byte) 7, DataBuffer.TYPE_BYTE),
+    BYTE(Numbers.BYTE, true, false, (byte) 7, org.apache.sis.image.DataType.BYTE),
 
     /**
      * Character type as unsigned 8 bits (netCDF type 2).
      * Encoding can be specified by assigning the “_Encoding” attribute to a netCDF variable.
      */
-    CHAR(Numbers.BYTE, false, true, (byte) 2, DataBuffer.TYPE_UNDEFINED),        // NOT Numbers.CHARACTER
+    CHAR(Numbers.BYTE, false, true, (byte) 2, null),                // NOT Numbers.CHARACTER
 
     /**
      * 16 bits signed integer (netCDF type 3).
      */
-    SHORT(Numbers.SHORT, true, false, (byte) 8, DataBuffer.TYPE_SHORT),
+    SHORT(Numbers.SHORT, true, false, (byte) 8, org.apache.sis.image.DataType.SHORT),
 
     /**
      * 32 bits signed integer (netCDF type 4).
      * This is also called "long", but that name is deprecated.
      */
-    INT(Numbers.INTEGER, true, false, (byte) 9, DataBuffer.TYPE_INT),
+    INT(Numbers.INTEGER, true, false, (byte) 9, org.apache.sis.image.DataType.INT),
 
     /**
      * 32 bits floating point number (netCDF type 5)
      * This is also called "real".
      */
-    FLOAT(Numbers.FLOAT, false, false, (byte) 5, DataBuffer.TYPE_FLOAT),
+    FLOAT(Numbers.FLOAT, false, false, (byte) 5, org.apache.sis.image.DataType.FLOAT),
 
     /**
      * 64 bits floating point number (netCDF type 6).
      */
-    DOUBLE(Numbers.DOUBLE, false, false, (byte) 6, DataBuffer.TYPE_DOUBLE),
+    DOUBLE(Numbers.DOUBLE, false, false, (byte) 6, org.apache.sis.image.DataType.DOUBLE),
 
     /**
      * 8 bits unsigned integer (netCDF type 7).
      * Not available in netCDF classic format.
      */
-    UBYTE(Numbers.BYTE, true, true, (byte) 1, DataBuffer.TYPE_BYTE),
+    UBYTE(Numbers.BYTE, true, true, (byte) 1, org.apache.sis.image.DataType.BYTE),
 
     /**
      * 16 bits unsigned integer (netCDF type 8).
      * Not available in netCDF classic format.
      */
-    USHORT(Numbers.SHORT, true, true, (byte) 3, DataBuffer.TYPE_USHORT),
+    USHORT(Numbers.SHORT, true, true, (byte) 3, org.apache.sis.image.DataType.USHORT),
 
     /**
      * 32 bits unsigned integer (netCDF type 9).
      * Not available in netCDF classic format.
      */
-    UINT(Numbers.INTEGER, true, true, (byte) 4, DataBuffer.TYPE_INT),
+    UINT(Numbers.INTEGER, true, true, (byte) 4, org.apache.sis.image.DataType.INT),
 
     /**
      * 64 bits signed integer (netCDF type 10).
      * Not available in netCDF classic format.
      */
-    INT64(Numbers.LONG, true, false, (byte) 11, DataBuffer.TYPE_UNDEFINED),
+    INT64(Numbers.LONG, true, false, (byte) 11, null),
 
     /**
      * 64 bits unsigned integer (netCDF type 11).
      * Not available in netCDF classic format.
      */
-    UINT64(Numbers.LONG, true, true, (byte) 10, DataBuffer.TYPE_UNDEFINED),
+    UINT64(Numbers.LONG, true, true, (byte) 10, null),
 
     /**
      * Character string (netCDF type 12).
      * Not available in netCDF classic format.
      */
-    STRING(Numbers.OTHER, false, false, (byte) 12, DataBuffer.TYPE_UNDEFINED);
+    STRING(Numbers.OTHER, false, false, (byte) 12, null);
 
     /**
      * Mapping from the netCDF data type to the enumeration used by our {@link Numbers} class.
@@ -141,18 +141,18 @@ public enum DataType {
     private final byte opposite;
 
     /**
-     * The {@link DataBuffer} constant which most closely represents the "raw" internal data of the variable.
-     * This is the value to be returned by {@link java.awt.image.SampleModel#getDataType()} for Java2D rasters
-     * created from a variable data. If the variable data type can not be mapped to a Java2D data type, then
-     * the raster data type is {@link DataBuffer#TYPE_UNDEFINED}.
+     * Wrapper of {@link DataBuffer} constant which most closely represents the "raw" internal data of the variable.
+     * This wraps the value to be returned by {@link java.awt.image.SampleModel#getDataType()} for Java2D rasters
+     * created from a variable data. If the variable data type can not be mapped to a Java2D data type, then the
+     * raster data type is {@code null}.
      */
-    public final int rasterDataType;
+    public final org.apache.sis.image.DataType rasterDataType;
 
     /**
      * Creates a new enumeration.
      */
     private DataType(final byte number, final boolean isInteger, final boolean isUnsigned,
-            final byte opposite, final int rasterDataType)
+            final byte opposite, final org.apache.sis.image.DataType rasterDataType)
     {
         this.number         = number;
         this.isInteger      = isInteger;


Mime
View raw message