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: Add documentation and checks against integer overflow (ArithmeticException). Make ConvertedGridCoverage package-private and declare ConvertedColorModel as compatible with ConvertedSampleModel only. Avoid the use of MathTransforms.compound(…) since in this particular case an ordinary loop will be more efficient.
Date Mon, 08 Apr 2019 19:00:52 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 32ac912  Add documentation and checks against integer overflow (ArithmeticException). Make ConvertedGridCoverage package-private and declare ConvertedColorModel as compatible with ConvertedSampleModel only. Avoid the use of MathTransforms.compound(…) since in this particular case an ordinary loop will be more efficient.
32ac912 is described below

commit 32ac912ef71b2efcb59b29c85c6341f80906a20f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Apr 8 20:56:57 2019 +0200

    Add documentation and checks against integer overflow (ArithmeticException).
    Make ConvertedGridCoverage package-private and declare ConvertedColorModel as compatible with ConvertedSampleModel only.
    Avoid the use of MathTransforms.compound(…) since in this particular case an ordinary loop will be more efficient.
---
 .../org/apache/sis/coverage/SampleDimension.java   |   8 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java |  28 +-
 .../internal/coverage/BufferedGridCoverage.java    | 113 ++++++--
 .../internal/coverage/ConvertedGridCoverage.java   | 322 +++++++++++++--------
 .../apache/sis/internal/coverage/package-info.java |  31 ++
 .../coverage/BufferedGridCoverageTest.java         | 118 +++++---
 .../org/apache/sis/internal/netcdf/Raster.java     |  30 +-
 7 files changed, 420 insertions(+), 230 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 3f80221..87a81aa 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -403,10 +403,12 @@ public class SampleDimension implements Serializable {
      * or {@code false} respectively.  If there is no {@linkplain #getTransferFunction() transfer function}, then this method
      * returns {@code this}.
      *
-     * @param  converted  {@code true} for a sample dimension representing converted values,
-     *                    or {@code false} for a sample dimension representing sample values.
-     * @return a sample dimension representing converted or sample values, depending on {@code converted} argument value.
+     * @param  converted  {@code true} for a sample dimension describing converted values,
+     *                    or {@code false} for a sample dimension describing packed values.
+     * @return a sample dimension describing converted or packed values, depending on {@code converted} argument value.
      *         May be {@code this} but never {@code null}.
+     *
+     * @see org.apache.sis.coverage.grid.GridCoverage#forConvertedValues(boolean)
      */
     public SampleDimension forConvertedValues(final boolean converted) {
         // Transfer function shall never be null if 'converse' is non-null.
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 a7ef211..c8ecffa 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
@@ -44,6 +44,7 @@ import org.opengis.coverage.CannotEvaluateException;
  * is that of the grid value whose location is nearest the point.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @author  Johann Sorel (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
@@ -125,16 +126,27 @@ public abstract class GridCoverage {
     }
 
     /**
-     * Returns a grid coverage that describes real values or sample values, depending if {@code converted} is {@code true}
-     * or {@code false} respectively.  If there are no converted values defined by sample dimensions, then this method
-     * returns {@code this}.
-     * As a result the {@linkplain RenderedImage} produced by {@linkplain GridCoverage#render(org.apache.sis.coverage.grid.GridExtent) }
-     * will be changed to contain the real or sample values.
+     * Returns a grid coverage that contains real values or sample values, depending if {@code converted} is {@code true}
+     * or {@code false} respectively. If there is no {@linkplain SampleDimension#getTransferFunction() transfer function}
+     * defined by the {@linkplain #getSampleDimensions() sample dimensions}, then this method returns {@code this}.
+     * In all cases, the returned grid coverage <var>r</var> has the following properties:
      *
-     * @param  converted  {@code true} for a coverage representing converted values,
-     *                    or {@code false} for a coverage representing sample values.
-     * @return a coverage representing converted or sample values, depending on {@code converted} argument value.
+     * <ul>
+     *   <li>The list returned by {@code r.getSampleDimensions()} is equal to the list returned by
+     *       <code>this.{@linkplain #getSampleDimensions()}</code> with each element <var>e</var> replaced by
+     *       <code>e.{@linkplain SampleDimension#forConvertedValues(boolean) forConvertedValues}(converted)</code>.</li>
+     *   <li>The {@link RenderedImage} produced by {@code r.render(extent)} is equivalent to the image returned by
+     *       <code>this.{@linkplain #render(GridExtent) render}(extent)</code> with all sample values converted
+     *       using the transfer function if {@code converted} is {@code true}, or the inverse of transfer function
+     *       if {@code converted} is {@code false}.</li>
+     * </ul>
+     *
+     * @param  converted  {@code true} for a coverage containing converted values,
+     *                    or {@code false} for a coverage containing packed values.
+     * @return a coverage containing converted or packed values, depending on {@code converted} argument value.
      *         May be {@code this} but never {@code null}.
+     *
+     * @see SampleDimension#forConvertedValues(boolean)
      */
     public abstract GridCoverage forConvertedValues(boolean converted);
 
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
index fb559ef..ba3438b 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
@@ -25,53 +25,93 @@ import java.awt.image.DataBufferShort;
 import java.awt.image.DataBufferUShort;
 import java.awt.image.RasterFormatException;
 import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
 import java.util.Collection;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.ImageRenderer;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-specific imports
 import org.opengis.coverage.CannotEvaluateException;
 
+
 /**
- * A GridCoverage which datas are stored in an in-memory buffer.
+ * A {@link GridCoverage} with data stored in an in-memory Java2D buffer.
+ * Those data can be shown as {@link RenderedImage}.
  *
- * @author Johann Sorel (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
  */
-public final class BufferedGridCoverage extends GridCoverage {
+public class BufferedGridCoverage extends GridCoverage {
+    /**
+     * The sample values, potentially multi-banded. The bands may be stored either in a single bank (pixel interleaved image)
+     * or in different banks (banded image). This class detects automatically which of those two sample models is used when
+     * {@link #render(GridExtent)} is invoked.
+     */
+    protected final DataBuffer data;
 
-    private final DataBuffer data;
+    /**
+     * Result of the call to {@link #forConvertedValues(boolean)}, created when first needed.
+     */
     private GridCoverage converted;
 
     /**
+     * Constructs a grid coverage using the specified grid geometry, sample dimensions and data buffer.
+     * This method stores the given buffer by reference (no copy).
+     *
+     * @param grid   the grid extent, CRS and conversion from cell indices to CRS.
+     * @param bands  sample dimensions for each image band.
+     * @param data   the sample values, potentially multi-banded.
+     */
+    public BufferedGridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands, final DataBuffer data) {
+        super(grid, bands);
+        this.data = data;
+        ArgumentChecks.ensureNonNull("data", data);
+    }
+
+    /**
      * Constructs a grid coverage using the specified grid geometry, sample dimensions and data type.
+     * This constructor create a single-bank {@link DataBuffer} (pixel interleaved sample model) with
+     * all sample values initialized to zero.
      *
-     * @param grid the grid extent, CRS and conversion from cell indices to CRS.
-     * @param bands sample dimensions for each image band.
-     * @param dataType One of DataBuffer.TYPE_* , the native data type used to store the coverage values.
+     * @param  grid      the grid extent, CRS and conversion from cell indices to CRS.
+     * @param  bands     sample dimensions for each image band.
+     * @param  dataType  one of {@code DataBuffer.TYPE_*} constants, the native data type used to store the coverage values.
+     * @throws ArithmeticException if the grid size is too large.
      */
-    public BufferedGridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands, int dataType) {
+    public BufferedGridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands, final int dataType) {
         super(grid, bands);
         long nbSamples = bands.size();
-        GridExtent extent = grid.getExtent();
-        for (int i = 0; i<grid.getDimension(); i++) {
-            nbSamples *= extent.getSize(i);
+        final GridExtent extent = grid.getExtent();
+        for (int i = grid.getDimension(); --i >= 0;) {
+            nbSamples = Math.multiplyExact(nbSamples, extent.getSize(i));
         }
-        final int nbSamplesi = Math.toIntExact(nbSamples);
-
+        final int n = Math.toIntExact(nbSamples);
         switch (dataType) {
-            case DataBuffer.TYPE_BYTE   : this.data = new DataBufferByte(nbSamplesi); break;
-            case DataBuffer.TYPE_SHORT  : this.data = new DataBufferShort(nbSamplesi); break;
-            case DataBuffer.TYPE_USHORT : this.data = new DataBufferUShort(nbSamplesi); break;
-            case DataBuffer.TYPE_INT    : this.data = new DataBufferInt(nbSamplesi); break;
-            case DataBuffer.TYPE_FLOAT  : this.data = new DataBufferFloat(nbSamplesi); break;
-            case DataBuffer.TYPE_DOUBLE : this.data = new DataBufferDouble(nbSamplesi); break;
-            default: throw new IllegalArgumentException("Unsupported data type "+ dataType);
+            case DataBuffer.TYPE_BYTE:   data = new DataBufferByte  (n); break;
+            case DataBuffer.TYPE_SHORT:  data = new DataBufferShort (n); break;
+            case DataBuffer.TYPE_USHORT: data = new DataBufferUShort(n); break;
+            case DataBuffer.TYPE_INT:    data = new DataBufferInt   (n); break;
+            case DataBuffer.TYPE_FLOAT:  data = new DataBufferFloat (n); break;
+            case DataBuffer.TYPE_DOUBLE: data = new DataBufferDouble(n); break;
+            default: throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, dataType));
         }
     }
 
+    /**
+     * Returns a two-dimensional slice of grid data as a rendered image.
+     * This method returns a view; sample values are not copied.
+     *
+     * @return the grid slice as a rendered image.
+     */
     @Override
-    public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException {
+    public RenderedImage render(final GridExtent sliceExtent) {
         try {
             final ImageRenderer renderer = new ImageRenderer(this, sliceExtent);
             renderer.setData(data);
@@ -81,12 +121,23 @@ public final class BufferedGridCoverage extends GridCoverage {
         }
     }
 
+    /**
+     * Returns a grid coverage that contains real values or sample values, depending if {@code converted} is {@code true}
+     * or {@code false} respectively.
+     *
+     * If the given value is {@code false}, then the default implementation returns a grid coverage which produces
+     * {@link RenderedImage} views. Those views convert each sample value on the fly. This is known to be very slow
+     * if an entire raster needs to be processed, but this is temporary until another implementation is provided in
+     * a future SIS release.
+     *
+     * @return a coverage containing converted or packed values, depending on {@code converted} argument value.
+     */
     @Override
-    public GridCoverage forConvertedValues(boolean converted) {
+    public GridCoverage forConvertedValues(final boolean converted) {
         if (converted) {
             synchronized (this) {
                 if (this.converted == null) {
-                    this.converted = ConvertedGridCoverage.convert(this);
+                    this.converted = convert(this);
                 }
                 return this.converted;
             }
@@ -94,4 +145,20 @@ public final class BufferedGridCoverage extends GridCoverage {
         return this;
     }
 
+    /**
+     * Returns a coverage for converted values. If the given coverage is already converted,
+     * then this method returns the given {@code coverage} unchanged.
+     *
+     * <p><b>WARNING: this is a temporary implementation.</b>
+     * This method uses a special {@link SampleModel} in departure with the contract documented in JDK javadoc.
+     * That sample model does not only define the sample layout (pixel stride, scanline stride, <i>etc.</i>), but
+     * also converts the sample values. This may be an issue for optimized pipelines accessing {@link DataBuffer}
+     * directly. This method may be replaced by another mechanism (creating new tiles) in a future SIS version.</p>
+     *
+     * @param  packed  the coverage containing packed values to convert.
+     * @return the converted coverage. May be {@code coverage}.
+     */
+    public static GridCoverage convert(final GridCoverage packed) {
+        return ConvertedGridCoverage.convert(packed);
+    }
 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
index 82e8b95..72e0ec0 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
@@ -16,11 +16,9 @@
  */
 package org.apache.sis.internal.coverage;
 
-import java.awt.Point;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.DataBuffer;
-import java.awt.image.DataBufferInt;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
@@ -28,101 +26,156 @@ import java.awt.image.WritableRaster;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
 
+
 /**
- * Warning : experimental class.
+ * Decorates a {@link GridCoverage} in order to convert sample values on the fly.
  *
- * Decorates a GridCoverage to convert values on the fly.
- * This class produces a special {@linkplain SampleModel} which may cause
- * issues in processing operations if the {@linkplain SampleModel} is not properly used as a fallback.
+ * <p><b>WARNING: this is a temporary class.</b>
+ * This class produces a special {@link SampleModel} in departure with the contract documented in JDK javadoc.
+ * That sample model does not only define the sample layout (pixel stride, scanline stride, <i>etc.</i>), but
+ * also converts the sample values. This may be an issue for optimized pipelines accessing {@link DataBuffer}
+ * directly. This class may be replaced by another mechanism (creating new tiles) in a future SIS version.</p>
  *
- * @author Johann Sorel (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
  */
-public final class ConvertedGridCoverage extends GridCoverage {
-
-    public static GridCoverage convert(GridCoverage coverage) {
-        final List<SampleDimension> sds = coverage.getSampleDimensions();
+final class ConvertedGridCoverage extends GridCoverage {
+    /**
+     * Returns a coverage for converted values. If the given coverage is already converted,
+     * then this method returns the given {@code coverage} unchanged.
+     *
+     * @param  packed  the coverage containing packed values to convert.
+     * @return the converted coverage. May be {@code coverage}.
+     */
+    public static GridCoverage convert(final GridCoverage packed) {
+        final List<SampleDimension> sds = packed.getSampleDimensions();
         final List<SampleDimension> cfs = new ArrayList<>(sds.size());
         for (SampleDimension sd : sds) {
             cfs.add(sd.forConvertedValues(true));
         }
-        return new ConvertedGridCoverage(coverage, cfs);
-    }
-
-    private final GridCoverage coverage;
-
-    private ConvertedGridCoverage(GridCoverage base, List<SampleDimension> sampleDims) {
-        super(base.getGridGeometry(), sampleDims);
-        this.coverage = base;
+        return cfs.equals(sds) ? packed : new ConvertedGridCoverage(packed, sds, cfs);
     }
 
-    @Override
-    public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException {
-        final BufferedImage render = (BufferedImage) coverage.render(sliceExtent);
-        final List<SampleDimension> sampleDimensions = getSampleDimensions();
+    /**
+     * The coverage containing packed values. Sample values will be converted from this coverage.
+     */
+    private final GridCoverage packed;
+
+    /**
+     * Conversions from {@code packed} values to converted values. There is one transform for each band.
+     */
+    private final MathTransform1D[] toConverted;
+
+    /**
+     * Conversions from converted values to {@code packed} values. They are the inverse of {@link #toConverted}.
+     */
+    private final MathTransform1D[] toPacked;
+
+    /**
+     * Whether all transforms in the {@link #toConverted} array are identity transforms.
+     */
+    private final boolean isIdentity;
+
+    /**
+     * Creates a new coverage with the same grid geometry than the given coverage and the given converted sample dimensions.
+     */
+    private ConvertedGridCoverage(final GridCoverage packed, final List<SampleDimension> sampleDimensions, final List<SampleDimension> converted) {
+        super(packed.getGridGeometry(), converted);
         final int numBands = sampleDimensions.size();
-        final MathTransform1D[] transforms = new MathTransform1D[numBands];
-        final MathTransform1D[] ivtransforms = new MathTransform1D[numBands];
+        toConverted = new MathTransform1D[numBands];
+        toPacked    = new MathTransform1D[numBands];
         boolean isIdentity = true;
+        final MathTransform1D identity = (MathTransform1D) MathTransforms.identity(1);
         for (int i = 0; i < numBands; i++) {
-            MathTransform1D transform = sampleDimensions.get(i).forConvertedValues(false).getTransferFunction().orElse(null);
-            if (transform == null) transform = (MathTransform1D) MathTransforms.linear(1.0, 0.0);
-            transforms[i] = transform;
+            MathTransform1D tr = sampleDimensions.get(i).getTransferFunction().orElse(identity);
+            toConverted[i] = tr;
+            isIdentity &= tr.isIdentity();
             try {
-                ivtransforms[i] = transform.inverse();
+                tr = tr.inverse();
             } catch (NoninvertibleTransformException ex) {
-                ivtransforms[i] = (MathTransform1D) MathTransforms.linear(Double.NaN, 0.0);
+                tr = (MathTransform1D) MathTransforms.linear(Double.NaN, 0.0);
             }
-            isIdentity &= transform.isIdentity();
+            toPacked[i] = tr;
         }
+        this.isIdentity = isIdentity;
+        this.packed     = packed;
+    }
+
+    /**
+     * Creates a converted view over {@link #packed} data for the given extent.
+     *
+     * @return the grid slice as a rendered image, as a converted view.
+     */
+    @Override
+    public RenderedImage render(final GridExtent sliceExtent) {
+        final RenderedImage render = packed.render(sliceExtent);
         if (isIdentity) {
             return render;
         }
-
-        final WritableRaster raster = render.getRaster();
+        final Raster raster;
+        if (render.getNumXTiles() == 1 && render.getNumYTiles() == 1) {
+            raster = render.getTile(render.getMinTileX(), render.getMinTileY());
+        } else {
+            /*
+             * This fallback is very inefficient since it copies all data in one big raster.
+             * We will replace this class by tiles management in a future Apache SIS version.
+             */
+            raster = render.getData();
+        }
         final SampleModel baseSm = raster.getSampleModel();
         final DataBuffer dataBuffer = raster.getDataBuffer();
-        final ConvertedSampleModel convSm = new ConvertedSampleModel(baseSm, transforms, ivtransforms);
-
-        //default color models have a lot of constraints
-        final WritableRaster convRaster = WritableRaster.createWritableRaster(convSm, dataBuffer, new Point(0, 0));
-        final ColorModel cm = new ConvertedColorModel(32, sampleDimensions.get(0).getSampleRange().get());
-
+        final ConvertedSampleModel convSm = new ConvertedSampleModel(baseSm, toConverted, toPacked);
+        final WritableRaster convRaster = WritableRaster.createWritableRaster(convSm, dataBuffer, null);
+        /*
+         * The default color models have a lot of constraints. Use a custom model with relaxed rules instead.
+         * We arbitrarily use the range of values of the first band only; a future Apache SIS version will
+         * need to perform another calculation.
+         */
+        final ColorModel cm = new ConvertedColorModel(getSampleDimensions().get(0).getSampleRange().get());
         return new BufferedImage(cm, convRaster, false, null);
     }
 
+    /**
+     * Returns the packed coverage if {@code converted} is {@code false}, or {@code this} otherwise.
+     */
     @Override
-    public GridCoverage forConvertedValues(boolean converted) {
-        return converted ? this : coverage;
+    public GridCoverage forConvertedValues(final boolean converted) {
+        return converted ? this : packed;
     }
 
+    /**
+     * A sample model which convert sample values on the fly.
+     *
+     * <p><b>WARNING: this is a temporary class.</b>
+     * This sample model does not only define the sample layout (pixel stride, scanline stride, <i>etc.</i>), but
+     * also converts the sample values. This may be an issue for optimized pipelines accessing {@link DataBuffer}
+     * directly. This class may be replaced by another mechanism (creating new tiles) in a future SIS version.</p>
+     */
     private static final class ConvertedSampleModel extends SampleModel {
 
         private final SampleModel base;
         private final int baseDataType;
-        private final MathTransform1D[] bandTransforms;
-        private final MathTransform1D[] bandIvtransforms;
-        private final MathTransform pixelTransform;
-        private final MathTransform pixelIvTransform;
+        private final MathTransform1D[] toConverted;
+        private final MathTransform1D[] toPacked;
 
-        public ConvertedSampleModel(SampleModel base, MathTransform1D[] transforms, MathTransform1D[] ivtransforms) {
+        ConvertedSampleModel(SampleModel base, MathTransform1D[] toConverted, MathTransform1D[] toPacked) {
             super(DataBuffer.TYPE_FLOAT, base.getWidth(), base.getHeight(), base.getNumBands());
-            this.base = base;
+            this.base         = base;
             this.baseDataType = base.getDataType();
-            this.bandTransforms = transforms;
-            this.bandIvtransforms = ivtransforms;
-            this.pixelTransform = MathTransforms.compound(bandTransforms);
-            this.pixelIvTransform = MathTransforms.compound(bandIvtransforms);
+            this.toConverted  = toConverted;
+            this.toPacked     = toPacked;
         }
 
         @Override
@@ -131,9 +184,9 @@ public final class ConvertedGridCoverage extends GridCoverage {
         }
 
         @Override
-        public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
-            Object buffer = base.getDataElements(x, y, null, data);
-            float[] pixel;
+        public Object getDataElements(final int x, final int y, final Object obj, final DataBuffer data) {
+            final Object buffer = base.getDataElements(x, y, null, data);
+            final float[] pixel;
             if (obj == null) {
                 pixel = new float[numBands];
             } else if (!(obj instanceof float[])) {
@@ -141,39 +194,45 @@ public final class ConvertedGridCoverage extends GridCoverage {
             } else {
                 pixel = (float[]) obj;
             }
-
             switch (baseDataType) {
-                case DataBuffer.TYPE_BYTE : {
+                case DataBuffer.TYPE_BYTE: {
                     final byte[] b = (byte[]) buffer;
                     for (int i = 0; i < b.length; i++) pixel[i] = b[i];
-                    } break;
-                case DataBuffer.TYPE_SHORT : {
+                    break;
+                }
+                case DataBuffer.TYPE_SHORT: {
                     final short[] b = (short[]) buffer;
                     for (int i = 0; i < b.length; i++) pixel[i] = b[i];
-                    } break;
-                case DataBuffer.TYPE_USHORT : {
+                    break;
+                }
+                case DataBuffer.TYPE_USHORT: {
                     final short[] b = (short[]) buffer;
-                    for (int i = 0; i < b.length; i++) pixel[i] = b[i] & 0xFFFF;
-                    } break;
-                case DataBuffer.TYPE_INT : {
+                    for (int i = 0; i < b.length; i++) pixel[i] = Short.toUnsignedInt(b[i]);
+                    break;
+                }
+                case DataBuffer.TYPE_INT: {
                     final int[] b = (int[]) buffer;
                     for (int i = 0; i < b.length; i++) pixel[i] = b[i];
-                    } break;
-                case DataBuffer.TYPE_FLOAT : {
+                    break;
+                }
+                case DataBuffer.TYPE_FLOAT: {
                     final float[] b = (float[]) buffer;
-                    for (int i = 0; i < b.length; i++) pixel[i] = b[i];
-                    } break;
-                case DataBuffer.TYPE_DOUBLE : {
+                    System.arraycopy(b, 0, pixel, 0, b.length);
+                    break;
+                }
+                case DataBuffer.TYPE_DOUBLE: {
                     final double[] b = (double[]) buffer;
                     for (int i = 0; i < b.length; i++) pixel[i] = (float) b[i];
-                    } break;
+                    break;
+                }
                 default: {
                     throw new ClassCastException("Unsupported base array type.");
-                    }
+                }
             }
-
             try {
-                pixelTransform.transform(pixel, 0, pixel, 0, 1);
+                for (int i=0; i<toConverted.length; i++) {
+                    pixel[i] = (float) toConverted[i].transform(pixel[i]);
+                }
             } catch (TransformException ex) {
                 Arrays.fill(pixel, Float.NaN);
             }
@@ -181,54 +240,59 @@ public final class ConvertedGridCoverage extends GridCoverage {
         }
 
         @Override
-        public void setDataElements(int x, int y, Object obj, DataBuffer data) {
+        public void setDataElements(final int x, final int y, final Object obj, final DataBuffer data) {
             float[] pixel;
-            if (obj == null) {
-                throw new ClassCastException("Null array values");
-            } else if (!(obj instanceof float[])) {
+            Objects.requireNonNull(obj);
+            if (!(obj instanceof float[])) {
                 throw new ClassCastException("Unsupported array type, expecting a float array.");
             } else {
                 pixel = (float[]) obj;
             }
-
             try {
-                pixelIvTransform.transform(pixel, 0, pixel, 0, 1);
+                for (int i=0; i<toConverted.length; i++) {
+                    pixel[i] = (float) toPacked[i].transform(pixel[i]);
+                }
             } catch (TransformException ex) {
                 Arrays.fill(pixel, Float.NaN);
             }
-
             switch (baseDataType) {
-                case DataBuffer.TYPE_BYTE : {
+                case DataBuffer.TYPE_BYTE: {
                     final byte[] b = new byte[pixel.length];
                     for (int i = 0; i < b.length; i++) b[i] = (byte) pixel[i];
                     base.setDataElements(x, y, b, data);
-                    } break;
-                case DataBuffer.TYPE_SHORT : {
+                    break;
+                }
+                case DataBuffer.TYPE_SHORT: {
                     final short[] b = new short[pixel.length];
                     for (int i = 0; i < b.length; i++) b[i] = (short) pixel[i];
                     base.setDataElements(x, y, b, data);
-                    } break;
-                case DataBuffer.TYPE_USHORT : {
+                    break;
+                }
+                case DataBuffer.TYPE_USHORT: {
                     final short[] b = new short[pixel.length];
                     for (int i = 0; i < b.length; i++) b[i] = (short) pixel[i];
                     base.setDataElements(x, y, b, data);
-                    } break;
-                case DataBuffer.TYPE_INT : {
+                    break;
+                }
+                case DataBuffer.TYPE_INT: {
                     final int[] b = new int[pixel.length];
                     for (int i = 0; i < b.length; i++) b[i] = (int) pixel[i];
                     base.setDataElements(x, y, b, data);
-                    } break;
-                case DataBuffer.TYPE_FLOAT : {
+                    break;
+                }
+                case DataBuffer.TYPE_FLOAT: {
                     base.setDataElements(x, y, pixel, data);
-                    } break;
-                case DataBuffer.TYPE_DOUBLE : {
+                    break;
+                }
+                case DataBuffer.TYPE_DOUBLE: {
                     final double[] b = new double[pixel.length];
                     for (int i = 0 ;i < b.length; i++) b[i] = pixel[i];
                     base.setDataElements(x, y, b, data);
-                    } break;
+                    break;
+                }
                 default: {
                     throw new ClassCastException("Unsupported base array type.");
-                    }
+                }
             }
         }
 
@@ -240,7 +304,7 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public float getSampleFloat(int x, int y, int b, DataBuffer data) {
             try {
-                return (float) bandTransforms[b].transform(base.getSampleFloat(x, y, b, data));
+                return (float) toConverted[b].transform(base.getSampleFloat(x, y, b, data));
             } catch (TransformException ex) {
                 return Float.NaN;
             }
@@ -249,7 +313,7 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public double getSampleDouble(int x, int y, int b, DataBuffer data) {
             try {
-                return bandTransforms[b].transform(base.getSampleDouble(x, y, b, data));
+                return toConverted[b].transform(base.getSampleDouble(x, y, b, data));
             } catch (TransformException ex) {
                 return Double.NaN;
             }
@@ -263,7 +327,7 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public void setSample(int x, int y, int b, double s, DataBuffer data) {
             try {
-                s = bandIvtransforms[b].transform(s);
+                s = toPacked[b].transform(s);
             } catch (TransformException ex) {
                 s = Double.NaN;
             }
@@ -278,7 +342,7 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public SampleModel createCompatibleSampleModel(int w, int h) {
             final SampleModel cp = base.createCompatibleSampleModel(w, h);
-            return new ConvertedSampleModel(cp, bandTransforms, bandIvtransforms);
+            return new ConvertedSampleModel(cp, toConverted, toPacked);
         }
 
         @Override
@@ -287,8 +351,8 @@ public final class ConvertedGridCoverage extends GridCoverage {
             final MathTransform1D[] trs = new MathTransform1D[bands.length];
             final MathTransform1D[] ivtrs = new MathTransform1D[bands.length];
             for (int i=0; i<bands.length;i++) {
-                trs[i] = bandTransforms[bands[i]];
-                ivtrs[i] = bandIvtransforms[bands[i]];
+                trs[i] = toConverted[bands[i]];
+                ivtrs[i] = toPacked[bands[i]];
             }
             return new ConvertedSampleModel(cp, trs, ivtrs);
         }
@@ -301,28 +365,35 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public int[] getSampleSize() {
             final int[] sizes = new int[numBands];
-            Arrays.fill(sizes, 32);
+            Arrays.fill(sizes, Float.SIZE);
             return sizes;
         }
 
         @Override
         public int getSampleSize(int band) {
-            return 32;
+            return Float.SIZE;
         }
-
     }
 
+    /**
+     * Color model for working with {@link ConvertedSampleModel}.
+     * Defined as a workaround for the validations normally performed by {@link ColorModel}.
+     *
+     * <p><b>WARNING: this is a temporary class.</b>
+     * This color model disable validations normally performed by {@link ColorModel}, in order to enable the use
+     * of {@link ConvertedSampleModel}. This class may be replaced by another mechanism (creating new tiles) in
+     * a future SIS version.</p>
+     */
     private static final class ConvertedColorModel extends ColorModel {
 
         private final float scale;
         private final float offset;
 
         /**
-         * @param nbbits
-         * @param fct : Interpolate or Categorize function
+         * Creates a new color model for the given of converted values.
          */
-        public ConvertedColorModel(final int nbbits, final NumberRange range){
-            super(nbbits);
+        ConvertedColorModel(final NumberRange<?> range){
+            super(Float.SIZE);
             final double scale  = (255.0) / (range.getMaxDouble() - range.getMinDouble());
             this.scale  = (float) scale;
             this.offset = (float) (range.getMinDouble() / scale);
@@ -330,12 +401,12 @@ public final class ConvertedGridCoverage extends GridCoverage {
 
         @Override
         public boolean isCompatibleRaster(Raster raster) {
-            return true;
+            return isCompatibleSampleModel(raster.getSampleModel());
         }
 
         @Override
         public boolean isCompatibleSampleModel(SampleModel sm) {
-            return true;
+            return sm instanceof ConvertedSampleModel;
         }
 
         @Override
@@ -372,56 +443,53 @@ public final class ConvertedGridCoverage extends GridCoverage {
         @Override
         public int getRed(int pixel) {
             final int argb = getRGB((Object) pixel);
-            return 0xFF & (argb >> 16);
+            return 0xFF & (argb >>> 16);
         }
 
         @Override
         public int getGreen(int pixel) {
             final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 8);
+            return 0xFF & (argb >>> 8);
         }
 
         @Override
         public int getBlue(int pixel) {
             final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 0);
+            return 0xFF & argb;
         }
 
         @Override
         public int getAlpha(int pixel) {
             final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 24);
+            return 0xFF & (argb >>> 24);
         }
 
         @Override
         public int getRed(Object pixel) {
-            final int argb = getRGB((Object) pixel);
-            return 0xFF & (argb >> 16);
+            final int argb = getRGB(pixel);
+            return 0xFF & (argb >>> 16);
         }
 
         @Override
         public int getGreen(Object pixel) {
-            final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 8);
+            final int argb = getRGB(pixel);
+            return 0xFF & (argb >>> 8);
         }
 
         @Override
         public int getBlue(Object pixel) {
-            final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 0);
+            final int argb = getRGB(pixel);
+            return 0xFF & argb;
         }
 
         @Override
         public int getAlpha(Object pixel) {
-            final int argb = getRGB((Object) pixel);
-            return 0xFF & ( argb >> 24);
-        }
-
-        @Override
-        public WritableRaster createCompatibleWritableRaster(int w, int h) {
-            return Raster.createPackedRaster(new DataBufferInt(w*h),w,h,16,null);
+            final int argb = getRGB(pixel);
+            return 0xFF & (argb >>> 24);
         }
 
+        /*
+         * createCompatibleWritableRaster(int w, int h) not implemented for this class.
+         */
     }
-
 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/package-info.java b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/package-info.java
new file mode 100644
index 0000000..b390cb7
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/coverage/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/**
+ * A set of helper classes for the SIS implementation.
+ *
+ * <p><strong>Do not use!</strong></p>
+ *
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+package org.apache.sis.internal.coverage;
diff --git a/core/sis-raster/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java b/core/sis-raster/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
index 608ca36..786b803 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
@@ -19,77 +19,99 @@ package org.apache.sis.internal.coverage;
 import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
-import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
 import java.util.Arrays;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
-import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.test.TestCase;
-import org.junit.Assert;
-import org.junit.Test;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform1D;
+import org.junit.Assert;
+import org.junit.Test;
+
 
 /**
  * Tests the {@link BufferedGridCoverage} implementation.
- * 
- * @author Johann Sorel (Geomatys)
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
  */
 public class BufferedGridCoverageTest extends TestCase {
-
+    /**
+     * Tests with a two-dimensional coverage.
+     */
     @Test
     public void testCoverage2D() {
-
-        //create coverage
-        final GridExtent extent = new GridExtent(null, new long[]{0,0}, new long[]{1,1}, true);
-        final MathTransform gridToCrs = new AffineTransform2D(1, 0, 0, 1, 0, 0);
-        final CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
-        final GridGeometry gridgeom = new GridGeometry(extent, PixelInCell.CELL_CENTER, gridToCrs, crs);
+        /*
+         * Create coverage of 2×2 pixels with an identity "grid to CRS" transform.
+         * The range of sample values will be [-10 … +10]°C.
+         */
+        final GridGeometry grid = new GridGeometry(new GridExtent(2, 2),
+                PixelInCell.CELL_CENTER, MathTransforms.identity(2), HardCodedCRS.WGS84);
 
         final MathTransform1D toUnits = (MathTransform1D) MathTransforms.linear(0.5, 100);
-        final SampleDimension sd = new SampleDimension.Builder().setName("t").addQuantitative("data", NumberRange.create(-10, true, 10, true), toUnits, Units.CELSIUS).build();
-
-        final BufferedGridCoverage coverage = new BufferedGridCoverage(gridgeom, Arrays.asList(sd), DataBuffer.TYPE_SHORT);
-
-        BufferedImage img = (BufferedImage) coverage.render(null);
-        img.getRaster().setSample(0, 0, 0, 0);
-        img.getRaster().setSample(1, 0, 0, 5);
-        img.getRaster().setSample(0, 1, 0, -5);
-        img.getRaster().setSample(1, 1, 0, -10);
-
-        //test not converted values
-        RenderedImage notConverted = (BufferedImage) coverage.render(null);
-        testSamples(notConverted, new double[][]{{0,5},{-5,-10}});
-
-        //test converted values
-        org.apache.sis.coverage.grid.GridCoverage convertedCoverage = coverage.forConvertedValues(true);
-        BufferedImage converted = (BufferedImage) convertedCoverage.render(null);
-        testSamples(converted, new double[][]{{100,102.5},{97.5,95}});
-
-        //test writing in geophysic
-        converted.getRaster().setSample(0, 0, 0, 70); // 70 = x * 0.5 + 100 // (70-100)/0.5 = x // x = -60
-        converted.getRaster().setSample(1, 0, 0, 2.5);
-        converted.getRaster().setSample(0, 1, 0, -8);
-        converted.getRaster().setSample(1, 1, 0, -90);
-        testSamples(notConverted, new double[][]{{-60,-195},{-216,-380}});
-
+        final SampleDimension sd = new SampleDimension.Builder().setName("t")
+                .addQuantitative("data", NumberRange.create(-10, true, 10, true), toUnits, Units.CELSIUS)
+                .build();
+        /*
+         * Create the grid coverage, gets its image and set values directly as short integers.
+         */
+        GridCoverage   coverage = new BufferedGridCoverage(grid, Arrays.asList(sd), DataBuffer.TYPE_SHORT);
+        WritableRaster raster = ((BufferedImage) coverage.render(null)).getRaster();
+        raster.setSample(0, 0, 0,   0);
+        raster.setSample(1, 0, 0,   5);
+        raster.setSample(0, 1, 0,  -5);
+        raster.setSample(1, 1, 0, -10);
+        /*
+         * Verify packed values.
+         */
+        assertSamplesEqual(coverage, new double[][] {
+            { 0,   5},
+            {-5, -10}
+        });
+        /*
+         * Verify converted values.
+         */
+        coverage = coverage.forConvertedValues(true);
+        assertSamplesEqual(coverage, new double[][] {
+            {100.0, 102.5},
+            { 97.5,  95.0}
+        });
+        /*
+         * Test writing converted values and verify the result in the packed coverage.
+         * For example for the sample value at (0,0), we have (x is the packed value):
+         *
+         *   70 = x * 0.5 + 100   →   (70-100)/0.5 = x   →   x = -60
+         */
+        raster = ((BufferedImage) coverage.render(null)).getRaster();
+        raster.setSample(0, 0, 0,  70);
+        raster.setSample(1, 0, 0,   2.5);
+        raster.setSample(0, 1, 0,  -8);
+        raster.setSample(1, 1, 0, -90);
+        assertSamplesEqual(coverage.forConvertedValues(false), new double[][] {
+            { -60, -195},
+            {-216, -380}
+        });
     }
 
-    private void testSamples(RenderedImage image, double[][] values) {
-        final Raster raster = image.getData();
-        for (int y=0;y<values.length;y++) {
-            for (int x=0;x<values[0].length;x++) {
+    /**
+     * assert that the sample values in the given coverage are equal to the expected values.
+     */
+    private static void assertSamplesEqual(final GridCoverage coverage, final double[][] expected) {
+        final Raster raster = coverage.render(null).getData();
+        for (int y=0; y<expected.length; y++) {
+            for (int x=0; x<expected[y].length; x++) {
                 double value = raster.getSampleDouble(x, y, 0);
-                Assert.assertEquals(values[y][x], value, 0.0);
+                Assert.assertEquals(expected[y][x], value, STRICT);
             }
         }
     }
-
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
index a9f6f2f..ffefe7b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
@@ -22,11 +22,10 @@ import java.awt.image.RenderedImage;
 import java.awt.image.RasterFormatException;
 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.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.ImageRenderer;
-import org.apache.sis.internal.coverage.ConvertedGridCoverage;
+import org.apache.sis.internal.coverage.BufferedGridCoverage;
 
 
 /**
@@ -34,22 +33,18 @@ import org.apache.sis.internal.coverage.ConvertedGridCoverage;
  * The rendered image is usually mono-banded, but may be multi-banded in some special cases
  * handled by {@link RasterResource#read(GridGeometry, int...)}.
  *
+ * <p>The inherited {@link #data} buffer contains the sample values, potentially multi-banded. If there is more than
+ * one band to put in the rendered image, then each band is a {@linkplain DataBuffer#getNumBanks() separated bank}
+ * in the buffer, even if two banks are actually wrapping the same arrays with different offsets.
+ * The later case is better represented by {@link java.awt.image.PixelInterleavedSampleModel},
+ * but it is {@link ImageRenderer} responsibility to perform this substitution as an optimization.</p>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-final class Raster extends GridCoverage {
-    /**
-     * The sample values, potentially multi-banded. If there is more than one band to put in the rendered image,
-     * then each band is a {@linkplain DataBuffer#getNumBanks() separated bank} in the buffer, even if two banks
-     * are actually wrapping the same arrays with different offsets.
-     *
-     * <div class="note">The later case is better represented by {@link java.awt.image.PixelInterleavedSampleModel},
-     * but it is {@link ImageRenderer} responsibility to perform this substitution as an optimization.</div>
-     */
-    private final DataBuffer data;
-
+final class Raster extends BufferedGridCoverage {
     /**
      * Increment to apply on index for moving to the next pixel in the same band.
      */
@@ -72,8 +67,7 @@ final class Raster extends GridCoverage {
     Raster(final GridGeometry domain, final List<SampleDimension> range, final DataBuffer data,
             final int pixelStride, final int[] bandOffsets, final String label)
     {
-        super(domain, range);
-        this.data        = data;
+        super(domain, range, data);
         this.label       = label;
         this.pixelStride = pixelStride;
         this.bandOffsets = bandOffsets;
@@ -96,10 +90,4 @@ final class Raster extends GridCoverage {
             throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
         }
     }
-
-    @Override
-    public GridCoverage forConvertedValues(boolean converted) {
-        return converted ? ConvertedGridCoverage.convert(this) : this;
-    }
-
 }


Mime
View raw message