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 <[email protected]>
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;
- }
-
}