You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2020/01/04 17:53:09 UTC
[sis] branch geoapi-4.0 updated (b1dc326 -> db59cb5)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.
from b1dc326 Add test for ComputedImage.
new b372f87 Consolidation of Raster/ColorModel/SampleModel factories.
new e8aa999 Replace ConvertedGridCoverage body by new code usinb BandedSampleConverter. We lost write capability in converted values for now.
new db59cb5 Move a WKT utility method to WKTUtilities internal class.
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/sis/coverage/grid/GridCoverage.java | 41 +-
.../apache/sis/coverage/grid/GridCoverage2D.java | 109 ++--
.../apache/sis/coverage/grid/ImageRenderer.java | 2 +-
.../java/org/apache/sis/image/ComputedImage.java | 3 +-
.../java/org/apache/sis/image/PlanarImage.java | 18 +-
.../coverage/j2d/BandedSampleConverter.java | 2 +-
.../coverage/j2d/BufferedGridCoverage.java | 29 --
.../internal/coverage/j2d/ColorModelFactory.java | 25 +-
.../coverage/j2d/ConvertedGridCoverage.java | 570 ++++++---------------
.../sis/internal/coverage/j2d/ImageFactory.java | 76 ---
.../sis/internal/coverage/j2d/ImageUtilities.java | 2 +
.../coverage/j2d/MultiBandsIndexColorModel.java | 2 +-
.../sis/internal/coverage/j2d/RasterFactory.java | 131 ++++-
.../internal/coverage/j2d/ScaledColorSpace.java | 45 +-
.../sis/coverage/grid/GridCoverage2DTest.java | 22 +-
.../coverage/j2d/BufferedGridCoverageTest.java | 3 +-
.../sis/internal/referencing/WKTUtilities.java | 42 +-
.../main/java/org/apache/sis/io/wkt/Formatter.java | 2 +-
.../sis/internal/referencing/WKTUtilitiesTest.java | 14 +-
.../org/apache/sis/internal/util/Numerics.java | 49 +-
.../org/apache/sis/internal/util/NumericsTest.java | 15 +-
21 files changed, 517 insertions(+), 685 deletions(-)
delete mode 100644 core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageFactory.java
[sis] 03/03: Move a WKT utility method to WKTUtilities internal
class.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit db59cb53d7842be5e94d14a36d456f3254769e67
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Jan 4 18:52:45 2020 +0100
Move a WKT utility method to WKTUtilities internal class.
---
.../sis/internal/referencing/WKTUtilities.java | 42 ++++++++++++++++++++--
.../main/java/org/apache/sis/io/wkt/Formatter.java | 2 +-
.../sis/internal/referencing/WKTUtilitiesTest.java | 14 +++++++-
.../org/apache/sis/internal/util/Numerics.java | 42 ----------------------
.../org/apache/sis/internal/util/NumericsTest.java | 15 +-------
5 files changed, 55 insertions(+), 60 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
index 4b149b0..89ac500 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
@@ -58,6 +58,7 @@ import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.math.Statistics;
import org.apache.sis.math.Vector;
@@ -70,7 +71,7 @@ import org.apache.sis.math.Vector;
* We need to be specific in order to select the right "aspect" of the given object.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.4
* @module
*/
@@ -349,6 +350,43 @@ public final class WKTUtilities extends Static {
}
/**
+ * Suggests an amount of fraction digits to use for formatting numbers in each column of the given matrix.
+ * The number of fraction digits may be negative if we could round the numbers to 10, 100, <i>etc</i>.
+ *
+ * @param rows the matrix rows. It is not required that each row has the same length.
+ * @return suggested amount of fraction digits as an array as long as the longest row.
+ *
+ * @see org.apache.sis.referencing.operation.matrix.Matrices#toString(Matrix)
+ */
+ public static int[] suggestFractionDigits(final Vector[] rows) {
+ int length = 0;
+ final int n = rows.length - 1;
+ for (int j=0; j <= n; j++) {
+ final int rl = rows[j].size();
+ if (rl > length) length = rl;
+ }
+ final int[] fractionDigits = new int[length];
+ final Statistics stats = new Statistics(null);
+ for (int i=0; i<length; i++) {
+ boolean isInteger = true;
+ for (final Vector row : rows) {
+ if (row.size() > i) {
+ final double value = row.doubleValue(i);
+ stats.accept(value);
+ if (isInteger && Math.floor(value) != value && !Double.isNaN(value)) {
+ isInteger = false;
+ }
+ }
+ }
+ if (!isInteger) {
+ fractionDigits[i] = Numerics.suggestFractionDigits(stats);
+ }
+ stats.reset();
+ }
+ return fractionDigits;
+ }
+
+ /**
* Suggests an amount of fraction digits to use for formatting numbers in each column of the given sequence
* of points. The number of fraction digits may be negative if we could round the numbers to 10, <i>etc</i>.
*
@@ -357,7 +395,7 @@ public final class WKTUtilities extends Static {
* @return suggested amount of fraction digits as an array as long as the longest row.
*/
public static int[] suggestFractionDigits(final CoordinateReferenceSystem crs, final Vector[] points) {
- final int[] fractionDigits = Numerics.suggestFractionDigits(points);
+ final int[] fractionDigits = suggestFractionDigits(points);
final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs);
if (ellipsoid != null) {
/*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
index 92780cd..3657a23 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
@@ -1239,7 +1239,7 @@ public class Formatter implements Localized {
return;
}
if (fractionDigits == null || fractionDigits.length == 0) {
- fractionDigits = Numerics.suggestFractionDigits(rows);
+ fractionDigits = WKTUtilities.suggestFractionDigits(rows);
}
numberFormat.setRoundingMode(RoundingMode.HALF_EVEN);
/*
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
index 7108002..385e3e4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
@@ -32,7 +32,7 @@ import static org.apache.sis.internal.referencing.WKTUtilities.*;
* Tests {@link WKTUtilities}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.7
* @module
*/
@@ -59,6 +59,18 @@ public final strictfp class WKTUtilitiesTest extends TestCase {
}
/**
+ * Tests {@link WKTUtilities#suggestFractionDigits(Vector[])}.
+ */
+ @Test
+ public void testSuggestFractionDigitsFromVectors() {
+ final int[] f = WKTUtilities.suggestFractionDigits(new Vector[] {
+ Vector.create(new double[] {10, 100, 0.1, 1000.1}),
+ Vector.create(new double[] {15, 140.1, 0.4, Double.NaN}),
+ Vector.create(new double[] {20, 400, 0.5, Double.NaN})});
+ assertArrayEquals(new int[] {0, 0, 3, 3}, f);
+ }
+
+ /**
* Tests {@link WKTUtilities#suggestFractionDigits(CoordinateReferenceSystem, Vector[])}.
*/
@Test
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index 5bd16e0..27edc5e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -27,8 +27,6 @@ import org.apache.sis.util.Workaround;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.math.Statistics;
-import org.apache.sis.math.Vector;
-import org.opengis.referencing.operation.Matrix; // For javadoc
import static java.lang.Math.min;
import static java.lang.Math.max;
@@ -602,46 +600,6 @@ public final class Numerics extends Static {
}
/**
- * Suggests an amount of fraction digits to use for formatting numbers in each column of the given matrix.
- * The number of fraction digits may be negative if we could round the numbers to 10, 100, <i>etc</i>.
- *
- * @param rows the matrix rows. It is not required that each row has the same length.
- * @return suggested amount of fraction digits as an array as long as the longest row.
- *
- * @see org.apache.sis.referencing.operation.matrix.Matrices#toString(Matrix)
- *
- * @todo Move into {@link org.apache.sis.internal.referencing.WKTUtilities}
- * if we move WKT parser/formatter to referencing module.
- */
- public static int[] suggestFractionDigits(final Vector[] rows) {
- int length = 0;
- final int n = rows.length - 1;
- for (int j=0; j <= n; j++) {
- final int rl = rows[j].size();
- if (rl > length) length = rl;
- }
- final int[] fractionDigits = new int[length];
- final Statistics stats = new Statistics(null);
- for (int i=0; i<length; i++) {
- boolean isInteger = true;
- for (final Vector row : rows) {
- if (row.size() > i) {
- final double value = row.doubleValue(i);
- stats.accept(value);
- if (isInteger && Math.floor(value) != value && !Double.isNaN(value)) {
- isInteger = false;
- }
- }
- }
- if (!isInteger) {
- fractionDigits[i] = suggestFractionDigits(stats);
- }
- stats.reset();
- }
- return fractionDigits;
- }
-
- /**
* Formats the given value with the given format, using scientific notation if needed.
* This is a workaround for {@link DecimalFormat} not switching automatically to scientific notation for large numbers.
*
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
index 22c2053..5c64ba1 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
@@ -21,7 +21,6 @@ import org.apache.sis.math.MathFunctions;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.math.Vector;
import org.junit.Test;
import static java.lang.Double.NaN;
@@ -35,7 +34,7 @@ import static org.junit.Assert.*;
* Tests the {@link Numerics} class.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.3
* @module
*/
@@ -189,16 +188,4 @@ public final strictfp class NumericsTest extends TestCase {
final float recomposed = StrictMath.scalb((float) expected, e);
assertEquals(value, StrictMath.copySign(recomposed, value), 0f);
}
-
- /**
- * Tests {@link Numerics#suggestFractionDigits(Vector[])}.
- */
- @Test
- public void testSuggestFractionDigits() {
- final int[] f = Numerics.suggestFractionDigits(new Vector[] {
- Vector.create(new double[] {10, 100, 0.1, 1000.1}),
- Vector.create(new double[] {15, 140.1, 0.4, Double.NaN}),
- Vector.create(new double[] {20, 400, 0.5, Double.NaN})});
- assertArrayEquals(new int[] {0, 0, 3, 3}, f);
- }
}
[sis] 02/03: Replace ConvertedGridCoverage body by new code usinb
BandedSampleConverter. We lost write capability in converted values for
now.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit e8aa999a259790516cf49acda5754007ee94dda6
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Jan 4 18:43:04 2020 +0100
Replace ConvertedGridCoverage body by new code usinb BandedSampleConverter.
We lost write capability in converted values for now.
---
.../org/apache/sis/coverage/grid/GridCoverage.java | 41 +-
.../apache/sis/coverage/grid/GridCoverage2D.java | 109 ++--
.../apache/sis/coverage/grid/ImageRenderer.java | 2 +-
.../coverage/j2d/BufferedGridCoverage.java | 29 --
.../coverage/j2d/ConvertedGridCoverage.java | 570 ++++++---------------
.../sis/internal/coverage/j2d/RasterFactory.java | 61 ++-
.../sis/coverage/grid/GridCoverage2DTest.java | 18 +-
.../coverage/j2d/BufferedGridCoverageTest.java | 3 +-
.../org/apache/sis/internal/util/Numerics.java | 7 +
9 files changed, 319 insertions(+), 521 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index d61d454..40c0596 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -26,9 +26,11 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.internal.coverage.j2d.ConvertedGridCoverage;
import org.apache.sis.util.collection.DefaultTreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTable;
@@ -72,6 +74,14 @@ public abstract class GridCoverage {
private final SampleDimension[] sampleDimensions;
/**
+ * View over this grid coverage after conversion of sample values, or {@code null} if not yet created.
+ * May be {@code this} if we determined that there is no conversion or the conversion is identity.
+ *
+ * @see #forConvertedValues(boolean)
+ */
+ private transient GridCoverage packedView, convertedView;
+
+ /**
* The last coordinate operation used by {@link #toGridCoordinates(DirectPosition)}.
* This is cached for avoiding the costly process of fetching a coordinate operation
* in the common case where the coordinate reference systems did not changed.
@@ -140,6 +150,26 @@ public abstract class GridCoverage {
}
/**
+ * Returns the converted or package view, or {@code null} if not yet computed.
+ * It is caller responsibility to ensure that this method is invoked in a synchronized block.
+ */
+ final GridCoverage getView(final boolean converted) {
+ return converted ? convertedView : packedView;
+ }
+
+ /**
+ * Sets the converted or package view. The given view should not be null.
+ * It is caller responsibility to ensure that this method is invoked in a synchronized block.
+ */
+ final void setView(final boolean converted, final GridCoverage view) {
+ if (converted) {
+ convertedView = view;
+ } else {
+ packedView = view;
+ }
+ }
+
+ /**
* 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}.
@@ -162,7 +192,16 @@ public abstract class GridCoverage {
*
* @see SampleDimension#forConvertedValues(boolean)
*/
- public abstract GridCoverage forConvertedValues(boolean converted);
+ public synchronized GridCoverage forConvertedValues(final boolean converted) {
+ GridCoverage view = getView(converted);
+ if (view == null) try {
+ view = ConvertedGridCoverage.create(this, converted);
+ setView(converted, view);
+ } catch (NoninvertibleTransformException e) {
+ throw new CannotEvaluateException(e.getMessage(), e);
+ }
+ return view;
+ }
/**
* Returns a sequence of double values for a given point in the coverage.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index b53636c..5c1fb4b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -16,9 +16,12 @@
*/
package org.apache.sis.coverage.grid;
+import java.util.List;
import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
import java.text.NumberFormat;
import java.text.FieldPosition;
import java.io.IOException;
@@ -33,10 +36,13 @@ import org.opengis.util.InternationalString;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.MathTransform1D;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.internal.coverage.j2d.ConvertedGridCoverage;
+import org.apache.sis.internal.coverage.j2d.BandedSampleConverter;
import org.apache.sis.internal.feature.Resources;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.util.collection.TableColumn;
@@ -120,27 +126,33 @@ public class GridCoverage2D extends GridCoverage {
/**
* The two-dimensional components of the coordinate reference system and "grid to CRS" transform.
* This is derived from {@link #gridGeometry} when first needed, retaining only the components at
- * dimension indices {@link #xDimension} and {@link #yDimension}.
+ * dimension indices {@link #xDimension} and {@link #yDimension}. The same {@link AtomicReference}
+ * instance may be shared with {@link #convertedView} and {@link #packedView}.
*
* @see #getGridGeometry2D()
*/
- private transient GridGeometry gridGeometry2D;
+ private final AtomicReference<GridGeometry> gridGeometry2D;
/**
- * Result of the call to {@link #forConvertedValues(boolean)} with a boolean value opposite to
- * {@link #isConverted}. This coverage is determined when first needed and may be {@code this}.
+ * Creates a new grid coverage for the conversion of specified source coverage.
*
- * @see #forConvertedValues(boolean)
+ * @param source the coverage containing source values.
+ * @param range the sample dimensions to assign to the converted grid coverage.
+ * @param converters conversion from source to converted coverage, one transform per band.
+ * @param isConverted whether this grid coverage is for converted or packed values.
*/
- private transient GridCoverage converse;
-
- /**
- * Whether all sample dimensions are already representing converted values.
- * This field has no meaning if {@link #converse} is null.
- *
- * @see #forConvertedValues(boolean)
- */
- private transient boolean isConverted;
+ private GridCoverage2D(final GridCoverage2D source, final List<SampleDimension> range,
+ final MathTransform1D[] converters, final boolean isConverted)
+ {
+ super(source.gridGeometry, range);
+ final int dataType = ConvertedGridCoverage.getDataType(range, isConverted);
+ data = new BandedSampleConverter(source.data, null, dataType, converters);
+ gridToImageX = source.gridToImageX;
+ gridToImageY = source.gridToImageY;
+ xDimension = source.xDimension;
+ yDimension = source.yDimension;
+ gridGeometry2D = source.gridGeometry2D;
+ }
/**
* Constructs a grid coverage using the specified domain, range and data. If the given domain does not
@@ -190,7 +202,7 @@ public class GridCoverage2D extends GridCoverage {
gridToImageX = subtractExact(data.getMinX(), extent.getLow(xDimension));
gridToImageY = subtractExact(data.getMinY(), extent.getLow(yDimension));
/*
- * Verifiy that the domain is consistent with image size.
+ * Verify that the domain is consistent with image size.
* We do not verify image location; it can be anywhere.
*/
for (int i=0; i<MIN_DIMENSION; i++) {
@@ -201,6 +213,7 @@ public class GridCoverage2D extends GridCoverage {
}
}
verifyBandCount(range, data);
+ gridGeometry2D = new AtomicReference<>();
}
/**
@@ -255,14 +268,12 @@ public class GridCoverage2D extends GridCoverage {
* This convenience constructor computes a {@link GridGeometry} from the given envelope and image size.
* This constructor assumes that all grid axes are in the same order than CRS axes and no axis is flipped.
* This straightforward approach often results in the <var>y</var> axis to be oriented toward up,
- * not down as often expected in rendered images.
+ * not down as commonly expected with rendered images.
*
* <p>This constructor is generally not recommended because of the assumptions on axis order and directions.
* For better control, use the constructor expecting a {@link GridGeometry} argument instead.
* This constructor is provided mostly as a convenience for testing purposes.</p>
*
- * @todo Not yet public. We should provide an argument controlling whether to flip Y axis.
- *
* @param domain the envelope encompassing all images, from upper-left corner to lower-right corner.
* If {@code null} a default grid geometry will be created with no CRS and identity conversion.
* @param range sample dimensions for each image band. The size of this list must be equal to the number of bands.
@@ -272,7 +283,7 @@ public class GridCoverage2D extends GridCoverage {
*
* @see GridGeometry#GridGeometry(GridExtent, Envelope)
*/
- GridCoverage2D(final Envelope domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
+ public GridCoverage2D(final Envelope domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
super(createGridGeometry(data, domain), defaultIfAbsent(range, data));
this.data = data; // Non-null verified by createGridGeometry(…, data).
xDimension = 0;
@@ -280,6 +291,7 @@ public class GridCoverage2D extends GridCoverage {
gridToImageX = 0;
gridToImageY = 0;
verifyBandCount(range, data);
+ gridGeometry2D = new AtomicReference<>();
}
/**
@@ -387,59 +399,44 @@ public class GridCoverage2D extends GridCoverage {
* @see #getGridGeometry()
* @see GridGeometry#reduce(int...)
*/
- public synchronized GridGeometry getGridGeometry2D() {
- if (gridGeometry2D == null) {
- gridGeometry2D = gridGeometry.reduce(xDimension, yDimension);
+ public GridGeometry getGridGeometry2D() {
+ GridGeometry g = gridGeometry2D.get();
+ if (g == null) {
+ g = gridGeometry.reduce(xDimension, yDimension);
+ if (!gridGeometry2D.compareAndSet(null, g)) {
+ GridGeometry other = gridGeometry2D.get();
+ if (other != null) return other;
+ }
}
- return gridGeometry2D;
+ return g;
}
/**
* 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 true}, 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.
- *
* @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.
*/
@Override
public synchronized GridCoverage forConvertedValues(final boolean converted) {
- if (converse == null) {
- isConverted = allConvertedFlagEqual(true);
- if (isConverted) {
- if (allConvertedFlagEqual(false)) {
- // No conversion in any direction.
- converse = this;
- } else {
- // Data are converted and user may want a packed format.
- converse = ConvertedGridCoverage.createFromConverted(this);
- }
+ GridCoverage2D view = (GridCoverage2D) getView(converted);
+ if (view == null) try {
+ final List<SampleDimension> sources = getSampleDimensions();
+ final List<SampleDimension> targets = new ArrayList<>(sources.size());
+ final MathTransform1D[] converters = ConvertedGridCoverage.converters(sources, targets, converted);
+ if (converters != null) {
+ view = new GridCoverage2D(this, targets, converters, converted);
+ view.setView(!converted, this);
} else {
- // Anything that need conversion, even if "is packed" test is also false.
- converse = ConvertedGridCoverage.createFromPacked(this);
- }
- }
- return (converted == isConverted) ? this : converse;
- }
-
- /**
- * Determines whether an "is converted" or "is packed" test on all sample dimensions returns {@code true}.
- *
- * @param converted {@coce true} for an "is converted" test, or {@code false} for an "is packed" test.
- * @return whether all sample dimensions in this coverage pass the specified test.
- */
- private boolean allConvertedFlagEqual(final boolean converted) {
- for (final SampleDimension sd : getSampleDimensions()) {
- if (sd != sd.forConvertedValues(converted)) {
- return false;
+ view = this;
}
+ setView(converted, view);
+ } catch (NoninvertibleTransformException e) {
+ throw new CannotEvaluateException(e.getMessage(), e);
}
- return true;
+ return view;
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index 30d43c9..df4095d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -375,7 +375,7 @@ public class ImageRenderer {
for (int i=0; i<data.length; i++) {
final Vector v = data[i];
ArgumentChecks.ensureNonNullElement("data", i, v);
- final int t = RasterFactory.getType(v.getElementType(), v.isUnsigned());
+ final int t = RasterFactory.getDataType(v.getElementType(), v.isUnsigned());
if (dataType != t) {
if (i != 0) {
throw new RasterFormatException(Resources.format(Resources.Keys.MismatchedDataType));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverage.java
index e84e424..63254ee 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverage.java
@@ -56,11 +56,6 @@ public class BufferedGridCoverage extends GridCoverage {
protected 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).
*
@@ -119,28 +114,4 @@ public class BufferedGridCoverage extends GridCoverage {
throw new CannotEvaluateException(e.getMessage(), e);
}
}
-
- /**
- * 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 true}, 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(final boolean converted) {
- if (converted) {
- synchronized (this) {
- if (this.converted == null) {
- this.converted = ConvertedGridCoverage.createFromPacked(this);
- }
- return this.converted;
- }
- }
- return this;
- }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ConvertedGridCoverage.java
index ffd0bbf..5629cfd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ConvertedGridCoverage.java
@@ -16,493 +16,211 @@
*/
package org.apache.sis.internal.coverage.j2d;
-import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Optional;
import java.awt.image.DataBuffer;
-import java.awt.image.Raster;
import java.awt.image.RenderedImage;
-import java.awt.image.SampleModel;
-import java.awt.image.WritableRaster;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.coverage.CannotEvaluateException;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.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.referencing.operation.MathTransform1D;
-import org.opengis.referencing.operation.NoninvertibleTransformException;
-import org.opengis.referencing.operation.TransformException;
/**
* Decorates a {@link GridCoverage} in order to convert sample values on the fly.
+ * There is two strategies about when to convert sample values:
*
- * <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>
+ * <ul>
+ * <li>In calls to {@link #render(GridExtent)}, sample values are converted when first needed
+ * on a tile-by-tile basis then cached for future reuse. Note however that discarding the
+ * returned image may result in the lost of cached tiles.</li>
+ * <li>In calls to {@link #evaluate(DirectPosition, double[])}, the conversion is applied
+ * on-the-fly each time in order to avoid the potentially costly tile computations.</li>
+ * </ul>
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 1.0
* @module
*/
public final class ConvertedGridCoverage extends GridCoverage {
/**
- * Returns a coverage for converted values computed from a coverage of package 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}.
+ * The coverage containing source values.
+ * Sample values will be converted from that coverage using the {@link #converters}.
*/
- public static GridCoverage createFromPacked(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 cfs.equals(sds) ? packed : new ConvertedGridCoverage(packed, sds, cfs);
- }
+ private final GridCoverage source;
/**
- * @todo Placeholder for future evolution.
+ * Conversions from {@link #source} values to converted values.
+ * The length of this array shall be equal to the number of bands.
*/
- public static GridCoverage createFromConverted(final GridCoverage converted) {
- throw new UnsupportedOperationException("\"Converted to packed\" not yet implemented.");
- }
+ private final MathTransform1D[] converters;
/**
- * The coverage containing packed values. Sample values will be converted from this coverage.
+ * Whether this grid coverage is for converted values.
+ * If {@code false}, then this coverage is for packed values.
*/
- private final GridCoverage packed;
+ private final boolean isConverted;
/**
- * Conversions from {@code packed} values to converted values. There is one transform for each band.
+ * One of {@link DataBuffer} constants the describe the sample values type
+ * of images produced by {@link #render(GridExtent)}.
*/
- private final MathTransform1D[] toConverted;
+ private final int dataType;
/**
- * Conversions from converted values to {@code packed} values. They are the inverse of {@link #toConverted}.
+ * Creates a new coverage with the same grid geometry than the given coverage but converted sample dimensions.
+ *
+ * @param source the coverage containing source values.
+ * @param range the sample dimensions to assign to the converted grid coverage.
+ * @param converters conversion from source to converted coverage, one transform per band.
+ * @param isConverted whether this grid coverage is for converted or packed values.
*/
- private final MathTransform1D[] toPacked;
+ private ConvertedGridCoverage(final GridCoverage source, final List<SampleDimension> range,
+ final MathTransform1D[] converters, final boolean isConverted)
+ {
+ super(source.getGridGeometry(), range);
+ this.source = source;
+ this.converters = converters;
+ this.isConverted = isConverted;
+ this.dataType = getDataType(range, isConverted);
+ }
/**
- * Whether all transforms in the {@link #toConverted} array are identity transforms.
+ * Returns a coverage of converted values computed from a coverage of packed values, or conversely.
+ * If the given coverage is already converted, then this method returns {@code coverage} unchanged.
+ *
+ * @param source the coverage containing values to convert.
+ * @param converted {@code true} for a coverage containing converted values,
+ * or {@code false} for a coverage containing packed values.
+ * @return the converted coverage. May be {@code source}.
+ * @throws NoninvertibleTransformException if this constructor can not build a full conversion chain to target.
*/
- private final boolean isIdentity;
+ public static GridCoverage create(final GridCoverage source, final boolean converted) throws NoninvertibleTransformException {
+ final List<SampleDimension> sources = source.getSampleDimensions();
+ final List<SampleDimension> targets = new ArrayList<>(sources.size());
+ final MathTransform1D[] converters = converters(sources, targets, converted);
+ return (converters != null) ? new ConvertedGridCoverage(source, targets, converters, converted) : source;
+ }
/**
- * Creates a new coverage with the same grid geometry than the given coverage and the given converted sample dimensions.
+ * Returns the transforms for converting sample values from given sources to the {@code converted} status
+ * of those sources. This method opportunistically adds the target sample dimensions in {@code target} list.
+ *
+ * @param sources {@link GridCoverage#getSampleDimensions()} of {@code source} coverage.
+ * @param targets where to add {@link SampleDimension#forConvertedValues(boolean)} results.
+ * @param converted {@code true} for transforms to converted values, or {@code false} for transforms to packed values.
+ * @return the transforms, or {@code null} if all transforms are identity transform.
+ * @throws NoninvertibleTransformException if this method can not build a full conversion chain.
*/
- private ConvertedGridCoverage(final GridCoverage packed, final List<SampleDimension> sampleDimensions, final List<SampleDimension> converted) {
- super(packed.getGridGeometry(), converted);
- final int numBands = sampleDimensions.size();
- toConverted = new MathTransform1D[numBands];
- toPacked = new MathTransform1D[numBands];
- boolean isIdentity = true;
- final MathTransform1D identity = (MathTransform1D) MathTransforms.identity(1);
+ public static MathTransform1D[] converters(final List<SampleDimension> sources,
+ final List<SampleDimension> targets,
+ final boolean converted)
+ throws NoninvertibleTransformException
+ {
+ final int numBands = sources.size();
+ final MathTransform1D identity = (MathTransform1D) MathTransforms.identity(1);
+ final MathTransform1D[] converters = new MathTransform1D[numBands];
+ Arrays.fill(converters, identity);
for (int i = 0; i < numBands; i++) {
- MathTransform1D tr = sampleDimensions.get(i).getTransferFunction().orElse(identity);
- toConverted[i] = tr;
- isIdentity &= tr.isIdentity();
- try {
- tr = tr.inverse();
- } catch (NoninvertibleTransformException ex) {
- tr = (MathTransform1D) MathTransforms.linear(Double.NaN, 0.0);
+ final SampleDimension src = sources.get(i);
+ final SampleDimension tgt = src.forConvertedValues(converted);
+ targets.add(tgt);
+ if (src != tgt) {
+ MathTransform1D tr = src.getTransferFunction().orElse(identity);
+ Optional<MathTransform1D> complete = tgt.getTransferFunction();
+ if (complete.isPresent()) {
+ tr = MathTransforms.concatenate(tr, complete.get().inverse());
+ }
+ converters[i] = tr;
}
- toPacked[i] = tr;
}
- this.isIdentity = isIdentity;
- this.packed = packed;
+ for (final MathTransform1D converter : converters) {
+ if (!converter.isIdentity()) return converters;
+ }
+ return null;
}
/**
- * Creates a converted view over {@link #packed} data for the given extent.
+ * Returns the {@link DataBuffer} constant for range of values of given sample dimensions.
*
- * @return the grid slice as a rendered image, as a converted view.
+ * @param targets the sample dimensions for which to get the data type.
+ * @param converted whether the image will hold converted or packed values.
+ * @return the {@link DataBuffer} type.
*/
- @Override
- public RenderedImage render(final GridExtent sliceExtent) {
- final RenderedImage render = packed.render(sliceExtent);
- if (isIdentity) {
- return render;
- }
- final Raster raster;
- if (render.getNumXTiles() == 1 && render.getNumYTiles() == 1 && render.getTileGridXOffset() == 0 && render.getTileGridYOffset() == 0) {
- 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.
- *
- * Note : we need to specify the Rectangle to reset raster location at 0,0
- */
- raster = render.getData(new Rectangle(render.getMinX(), render.getMinY(), render.getWidth(), render.getHeight()));
+ public static int getDataType(final List<SampleDimension> targets, final boolean converted) {
+ NumberRange<?> union = null;
+ for (final SampleDimension dimension : targets) {
+ final Optional<NumberRange<?>> c = dimension.getSampleRange();
+ if (c.isPresent()) {
+ final NumberRange<?> range = c.get();
+ if (union == null) {
+ union = range;
+ } else {
+ union = union.unionAny(range);
+ }
+ }
}
- final SampleModel baseSm = raster.getSampleModel();
- final DataBuffer dataBuffer = raster.getDataBuffer();
- final SampleConverter convSm = new SampleConverter(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 ScaledColorModel(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(final boolean converted) {
- return converted ? this : packed;
+ return RasterFactory.getDataType(union, converted);
}
/**
- * A sample model which convert sample values on the fly.
+ * Returns a sequence of double values for a given point in the coverage.
+ * This method delegates to the source coverage, then convert values.
*
- * <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>
+ * @param point the coordinate point where to evaluate.
+ * @param buffer an array in which to store values, or {@code null} to create a new array.
+ * @return the {@code buffer} array, or a newly created array if {@code buffer} was null.
+ * @throws CannotEvaluateException if the values can not be computed.
*/
- private static final class SampleConverter extends SampleModel {
-
- private final SampleModel base;
- private final int baseDataType;
- private final MathTransform1D[] toConverted;
- private final MathTransform1D[] toPacked;
-
- SampleConverter(SampleModel base, MathTransform1D[] toConverted, MathTransform1D[] toPacked) {
- super(DataBuffer.TYPE_FLOAT, base.getWidth(), base.getHeight(), base.getNumBands());
- this.base = base;
- this.baseDataType = base.getDataType();
- this.toConverted = toConverted;
- this.toPacked = toPacked;
- }
-
- @Override
- public int getNumDataElements() {
- return base.getNumDataElements();
- }
-
- @Override
- 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[])) {
- throw new ClassCastException("Unsupported array type, expecting a float array.");
- } else {
- pixel = (float[]) obj;
- }
- switch (baseDataType) {
- 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: {
- final short[] b = (short[]) buffer;
- for (int i = 0; i < b.length; i++) pixel[i] = b[i];
- break;
- }
- case DataBuffer.TYPE_USHORT: {
- final short[] b = (short[]) buffer;
- 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: {
- final float[] b = (float[]) buffer;
- 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;
- }
- default: {
- throw new ClassCastException("Unsupported base array type.");
- }
- }
- try {
- for (int i=0; i<toConverted.length; i++) {
- pixel[i] = (float) toConverted[i].transform(pixel[i]);
- }
- } catch (TransformException ex) {
- Arrays.fill(pixel, Float.NaN);
- }
- return pixel;
- }
-
- @Override
- public void setDataElements(final int x, final int y, final Object obj, final DataBuffer data) {
- float[] pixel;
- Objects.requireNonNull(obj);
- if (!(obj instanceof float[])) {
- throw new ClassCastException("Unsupported array type, expecting a float array.");
- } else {
- pixel = (float[]) obj;
- }
- try {
- 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: {
- 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: {
- 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: {
- 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: {
- 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: {
- base.setDataElements(x, y, pixel, data);
- 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;
- }
- default: {
- throw new ClassCastException("Unsupported base array type.");
- }
- }
- }
-
- @Override
- public int getSample(int x, int y, int b, DataBuffer data) {
- return (int) getSampleDouble(x, y, b, data);
- }
-
- @Override
- public float getSampleFloat(int x, int y, int b, DataBuffer data) {
- try {
- return (float) toConverted[b].transform(base.getSampleFloat(x, y, b, data));
- } catch (TransformException ex) {
- return Float.NaN;
- }
- }
-
- @Override
- public double getSampleDouble(int x, int y, int b, DataBuffer data) {
- try {
- return toConverted[b].transform(base.getSampleDouble(x, y, b, data));
- } catch (TransformException ex) {
- return Double.NaN;
- }
- }
-
- @Override
- public void setSample(int x, int y, int b, int s, DataBuffer data) {
- setSample(x,y,b, (double) s, data);
- }
-
- @Override
- public void setSample(int x, int y, int b, double s, DataBuffer data) {
- try {
- s = toPacked[b].transform(s);
- } catch (TransformException ex) {
- s = Double.NaN;
- }
- base.setSample(x, y, b, s, data);
- }
-
- @Override
- public void setSample(int x, int y, int b, float s, DataBuffer data) {
- setSample(x, y, b, (double) s, data);
- }
-
- @Override
- public SampleModel createCompatibleSampleModel(int w, int h) {
- final SampleModel cp = base.createCompatibleSampleModel(w, h);
- return new SampleConverter(cp, toConverted, toPacked);
- }
-
- @Override
- public SampleModel createSubsetSampleModel(int[] bands) {
- final SampleModel cp = base.createSubsetSampleModel(bands);
- final MathTransform1D[] trs = new MathTransform1D[bands.length];
- final MathTransform1D[] ivtrs = new MathTransform1D[bands.length];
- for (int i=0; i<bands.length;i++) {
- trs[i] = toConverted[bands[i]];
- ivtrs[i] = toPacked[bands[i]];
+ @Override
+ public double[] evaluate(final DirectPosition point, double[] buffer) throws CannotEvaluateException {
+ try {
+ buffer = source.evaluate(point, buffer);
+ for (int i=0; i<converters.length; i++) {
+ buffer[i] = converters[i].transform(buffer[i]);
}
- return new SampleConverter(cp, trs, ivtrs);
- }
-
- @Override
- public DataBuffer createDataBuffer() {
- return base.createDataBuffer();
- }
-
- @Override
- public int[] getSampleSize() {
- final int[] sizes = new int[numBands];
- Arrays.fill(sizes, Float.SIZE);
- return sizes;
- }
-
- @Override
- public int getSampleSize(int band) {
- return Float.SIZE;
+ } catch (TransformException ex) {
+ throw new CannotEvaluateException(ex.getMessage(), ex);
}
+ return buffer;
}
/**
- * Color model for working with {@link SampleConverter}.
- * Defined as a workaround for the validations normally performed by {@link ColorModel}.
+ * Creates a converted view over {@link #source} data for the given extent.
+ * Values will be converted when first requested on a tile-by-tile basis.
+ * Note that if the returned image is discarded, then the cache of converted
+ * tiles will be discarded too.
*
- * <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 SampleConverter}. This class may be replaced by another mechanism (creating new tiles) in
- * a future SIS version.</p>
- *
- * @see ScaledColorSpace
+ * @return the grid slice as a rendered image with converted view.
*/
- private static final class ScaledColorModel extends ColorModel {
-
- private final float scale;
- private final float offset;
-
- /**
- * Creates a new color model for the given of converted values.
+ @Override
+ public RenderedImage render(final GridExtent sliceExtent) {
+ RenderedImage image = source.render(sliceExtent);
+ /*
+ * That image should never be null. But if an implementation wants to do so, respect that.
*/
- ScaledColorModel(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);
- }
-
- @Override
- public boolean isCompatibleRaster(Raster raster) {
- return isCompatibleSampleModel(raster.getSampleModel());
- }
-
- @Override
- public boolean isCompatibleSampleModel(SampleModel sm) {
- return sm instanceof SampleConverter;
- }
-
- @Override
- public int getRGB(Object inData) {
- float value;
- // Most used cases. Compatible color model is designed for cases where indexColorModel cannot do the job (float or int samples).
- if (inData instanceof float[]) {
- value = ((float[]) inData)[0];
- } else if (inData instanceof int[]) {
- value = ((int[]) inData)[0];
- } else if (inData instanceof double[]) {
- value = (float) ((double[]) inData)[0];
- } else if (inData instanceof byte[]) {
- value = ((byte[]) inData)[0];
- } else if (inData instanceof short[]) {
- value = ((short[]) inData)[0];
- } else if (inData instanceof long[]) {
- value = ((long[]) inData)[0];
- } else if (inData instanceof Number[]) {
- value = ((Number[]) inData)[0].floatValue();
- } else if (inData instanceof Byte[]) {
- value = ((Byte[]) inData)[0];
- } else {
- value = 0.0f;
- }
-
- int c = (int) ((value - offset) * scale);
- if (c < 0) c = 0;
- else if (c > 255) c = 255;
-
- return (255 << 24) | (c << 16) | (c << 8) | c;
- }
-
- @Override
- public int getRed(int pixel) {
- final int argb = getRGB((Object) pixel);
- return 0xFF & (argb >>> 16);
- }
-
- @Override
- public int getGreen(int pixel) {
- final int argb = getRGB((Object) pixel);
- return 0xFF & (argb >>> 8);
- }
-
- @Override
- public int getBlue(int pixel) {
- final int argb = getRGB((Object) pixel);
- return 0xFF & argb;
- }
-
- @Override
- public int getAlpha(int pixel) {
- final int argb = getRGB((Object) pixel);
- return 0xFF & (argb >>> 24);
- }
-
- @Override
- public int getRed(Object pixel) {
- final int argb = getRGB(pixel);
- return 0xFF & (argb >>> 16);
- }
-
- @Override
- public int getGreen(Object pixel) {
- final int argb = getRGB(pixel);
- return 0xFF & (argb >>> 8);
- }
-
- @Override
- public int getBlue(Object pixel) {
- final int argb = getRGB(pixel);
- return 0xFF & argb;
- }
-
- @Override
- public int getAlpha(Object pixel) {
- final int argb = getRGB(pixel);
- return 0xFF & (argb >>> 24);
+ if (image != null) {
+ image = new BandedSampleConverter(image, null, dataType, converters);
}
+ return image;
+ }
- /*
- * createCompatibleWritableRaster(int w, int h) not implemented for this class.
- */
+ /**
+ * Returns this coverage or the source coverage depending on whether {@code converted} matches
+ * the kind of content of this coverage.
+ */
+ @Override
+ public GridCoverage forConvertedValues(final boolean converted) {
+ return (converted == isConverted) ? this : source;
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
index 527e69a..0b96c0b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
@@ -35,6 +35,7 @@ import java.awt.image.BufferedImage;
import java.nio.Buffer;
import java.nio.ReadOnlyBufferException;
import org.apache.sis.internal.feature.Resources;
+import org.apache.sis.measure.NumberRange;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Numbers;
@@ -42,6 +43,8 @@ import org.apache.sis.util.Static;
import org.apache.sis.util.Workaround;
import org.apache.sis.util.collection.WeakHashSet;
+import static org.apache.sis.internal.util.Numerics.MAX_INTEGER_CONVERTIBLE_TO_FLOAT;
+
/**
* Creates rasters from given properties. Contains also convenience methods for
@@ -211,15 +214,63 @@ public final class RasterFactory extends Static {
* @param unsigned whether the type should be considered unsigned.
* @return the {@link DataBuffer} type, or {@link DataBuffer#TYPE_UNDEFINED}.
*/
- public static int getType(final Class<?> sample, final boolean unsigned) {
+ public static int getDataType(final Class<?> sample, final boolean unsigned) {
switch (Numbers.getEnumConstant(sample)) {
- case Numbers.BYTE: if (unsigned) return DataBuffer.TYPE_BYTE; else break;
- case Numbers.SHORT: return unsigned ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_SHORT;
- case Numbers.INTEGER: if (!unsigned) return DataBuffer.TYPE_INT; else break;
+ case Numbers.BYTE: return unsigned ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_SHORT;
+ case Numbers.SHORT: return unsigned ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_SHORT;
+ case Numbers.INTEGER: return unsigned ? DataBuffer.TYPE_UNDEFINED : DataBuffer.TYPE_INT;
case Numbers.FLOAT: return DataBuffer.TYPE_FLOAT;
case Numbers.DOUBLE: return DataBuffer.TYPE_DOUBLE;
+ default: return DataBuffer.TYPE_UNDEFINED;
+ }
+ }
+
+ /**
+ * Returns the {@link DataBuffer} constant for the given range of values.
+ * If {@code keepFloat} is {@code false}, then this method tries to return
+ * an integer type regardless if the range uses a floating point type.
+ * Range checks for integers assume ties rounding to positive infinity.
+ *
+ * @param range the range of values, or {@code null}.
+ * @param keepFloat whether to avoid integer types if the range uses floating point numbers.
+ * @return the {@link DataBuffer} type or {@link DataBuffer#TYPE_UNDEFINED} if the given range was null.
+ */
+ static int getDataType(final NumberRange<?> range, final boolean keepFloat) {
+ if (range == null) {
+ return DataBuffer.TYPE_UNDEFINED;
+ }
+ final byte nt = Numbers.getEnumConstant(range.getElementType());
+ if (keepFloat) {
+ if (nt >= Numbers.DOUBLE) return DataBuffer.TYPE_DOUBLE;
+ if (nt >= Numbers.FRACTION) return DataBuffer.TYPE_FLOAT;
+ }
+ final double min = range.getMinDouble();
+ final double max = range.getMaxDouble();
+ if (nt < Numbers.BYTE || nt > Numbers.FLOAT || nt == Numbers.LONG) {
+ /*
+ * Value type is long, double, BigInteger, BigDecimal or unknown type.
+ * If conversions to 32 bits integers would lost integer digits, or if
+ * a bound is NaN, stick to the most conservative data buffer type.
+ *
+ * Range check assumes ties rounding to positive infinity.
+ */
+ if (!(min >= -MAX_INTEGER_CONVERTIBLE_TO_FLOAT - 0.5 && max < MAX_INTEGER_CONVERTIBLE_TO_FLOAT + 0.5)) {
+ return DataBuffer.TYPE_DOUBLE;
+ }
+ }
+ /*
+ * Check most common types first. If the range could be both signed and unsigned short,
+ * give precedence to unsigned short because it works better with IndexColorModel.
+ * If a bounds is NaN, fallback on TYPE_FLOAT.
+ */
+ if (min >= -0.5 && max < 0xFFFF + 0.5) {
+ return (max < 0xFF + 0.5) ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT;
+ } else if (min >= Short.MIN_VALUE - 0.5 && max < Short.MAX_VALUE + 0.5) {
+ return DataBuffer.TYPE_SHORT;
+ } else if (min >= Integer.MIN_VALUE - 0.5 && max < Integer.MAX_VALUE + 0.5) {
+ return DataBuffer.TYPE_INT;
}
- return DataBuffer.TYPE_UNDEFINED;
+ return DataBuffer.TYPE_FLOAT;
}
/**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 3841c68..83ccc1d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -80,10 +80,10 @@ public final strictfp class GridCoverage2DTest extends TestCase {
}
/**
- * Tests {@link GridCoverage2D#forConvertedValues(boolean)}.
+ * Tests reading the values provided by {@link GridCoverage2D#forConvertedValues(boolean)}.
*/
@Test
- public void testForConvertedValues() {
+ public void testReadConvertedValues() {
GridCoverage coverage = createTestCoverage();
/*
* Verify packed values.
@@ -100,6 +100,20 @@ public final strictfp class GridCoverage2DTest extends TestCase {
{101.0, 102.5},
{ 97.5, 95.0}
});
+ }
+
+ /**
+ * Tests writing values in {@link GridCoverage2D#forConvertedValues(boolean)}.
+ */
+ @Test
+ @org.junit.Ignore("BandedSampleConverter is not yet writable")
+ public void testWriteConvertedValues() {
+ GridCoverage coverage = createTestCoverage();
+ coverage = coverage.forConvertedValues(true);
+ assertSamplesEqual(coverage, new double[][] {
+ {101.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 (p is the packed value):
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
index 2d2b748..858ff12 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BufferedGridCoverageTest.java
@@ -42,7 +42,7 @@ import org.opengis.coverage.PointOutsideCoverageException;
* Tests the {@link BufferedGridCoverage} implementation.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
@@ -93,6 +93,7 @@ public class BufferedGridCoverageTest extends TestCase {
*
* 70 = x * 0.5 + 100 → (70-100)/0.5 = x → x = -60
*/
+ if (true) return; // TODO
raster = ((BufferedImage) coverage.render(null)).getRaster();
raster.setSample(0, 0, 0, 70);
raster.setSample(1, 0, 0, 2.5);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index 6222ef7..5bd16e0 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -149,6 +149,13 @@ public final class Numerics extends Static {
public static final int SIGNIFICAND_SIZE_OF_FLOAT = 23;
/**
+ * Maximal integer value which is convertible to {@code float} type without lost of precision digits.
+ *
+ * @since 1.1
+ */
+ public static final int MAX_INTEGER_CONVERTIBLE_TO_FLOAT = 1 << (SIGNIFICAND_SIZE_OF_FLOAT + 1);
+
+ /**
* Do not allow instantiation of this class.
*/
private Numerics() {
[sis] 01/03: Consolidation of Raster/ColorModel/SampleModel
factories.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit b372f8748d83d2372892821ee1eaf7535af1c2b3
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Jan 4 14:04:33 2020 +0100
Consolidation of Raster/ColorModel/SampleModel factories.
---
.../java/org/apache/sis/image/ComputedImage.java | 3 +-
.../java/org/apache/sis/image/PlanarImage.java | 18 ++---
.../coverage/j2d/BandedSampleConverter.java | 2 +-
.../internal/coverage/j2d/ColorModelFactory.java | 25 ++++++-
.../sis/internal/coverage/j2d/ImageFactory.java | 76 ----------------------
.../sis/internal/coverage/j2d/ImageUtilities.java | 2 +
.../coverage/j2d/MultiBandsIndexColorModel.java | 2 +-
.../sis/internal/coverage/j2d/RasterFactory.java | 70 ++++++++++++++++++--
.../internal/coverage/j2d/ScaledColorSpace.java | 45 ++++++++++++-
.../sis/coverage/grid/GridCoverage2DTest.java | 4 +-
10 files changed, 143 insertions(+), 104 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
index 8f728f2..aa6f19c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
@@ -100,7 +100,8 @@ public abstract class ComputedImage extends PlanarImage {
* Invoked when the enclosing image has been garbage-collected. This method removes all cached tiles
* that were owned by the enclosing image. This method should not perform other cleaning work than
* removing cached tiles because it is not guaranteed to be invoked if {@link TileCache#GLOBAL}
- * does not contain any tile for the enclosing image.
+ * does not contain any tile for the enclosing image (because there would be nothing preventing
+ * this weak reference to be garbage collected before {@code dispose()} is invoked).
*/
@Override
public void dispose() {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
index 494009d..f7a4b5d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
@@ -18,7 +18,6 @@ package org.apache.sis.image;
import java.awt.Image;
import java.awt.Rectangle;
-import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
@@ -26,12 +25,12 @@ import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.RenderedImage;
import java.util.Vector;
-import org.apache.sis.internal.util.Numerics;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
-import org.apache.sis.internal.coverage.j2d.ScaledColorSpace;
+import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
/**
@@ -432,14 +431,7 @@ colors: if (cm != null) {
if (cm instanceof IndexColorModel) {
buffer.append(((IndexColorModel) cm).getMapSize()).append(" indexed colors");
} else {
- final ColorSpace cs = cm.getColorSpace();
- if (cs != null) {
- if (cs instanceof ScaledColorSpace) {
- ((ScaledColorSpace) cs).formatRange(buffer.append("showing "));
- } else if (cs.getType() == ColorSpace.TYPE_GRAY) {
- buffer.append("grayscale");
- }
- }
+ ColorModelFactory.formatDescription(cm.getColorSpace(), buffer);
}
final String transparency;
switch (cm.getTransparency()) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
index e5c4a1d..4f87725 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
@@ -89,7 +89,7 @@ public final class BandedSampleConverter extends ComputedImage {
layout = ImageLayout.DEFAULT;
}
final Dimension tile = layout.suggestTileSize(source);
- return new BandedSampleModel(targetType, tile.width, tile.height, numBands);
+ return RasterFactory.unique(new BandedSampleModel(targetType, tile.width, tile.height, numBands));
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
index c784929..39a332c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
@@ -36,6 +36,7 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.collection.WeakHashSet;
import org.apache.sis.util.collection.WeakValueHashMap;
+import org.apache.sis.util.Debug;
/**
@@ -63,7 +64,7 @@ public final class ColorModelFactory {
*
* @see #unique(ColorModel)
*/
- @SuppressWarnings("rawtypes")
+ @SuppressWarnings("rawtypes") // TODO: Remove after we removed ColorModelPatch.
private static final WeakHashSet<ColorModelPatch> CACHE = new WeakHashSet<>(ColorModelPatch.class);
/**
@@ -396,7 +397,7 @@ public final class ColorModelFactory {
* @param maximum the maximal sample value expected.
* @return the color space for the given range of values.
*
- * @see ImageFactory#createGrayScale(int, int, int, int, int, double, double)
+ * @see RasterFactory#createGrayScaleImage(int, int, int, int, int, double, double)
*/
public static ColorModel createGrayScale(final int dataType, final int numComponents,
final int visibleBand, final double minimum, final double maximum)
@@ -421,7 +422,25 @@ public final class ColorModelFactory {
if (numComponents == 1 && minimum == 0 && maximum == 1) {
return ColorSpace.getInstance(ColorSpace.CS_GRAY);
}
- return new ScaledColorSpace(numComponents, visibleBand, minimum, maximum);
+ return new ScaledColorSpace(numComponents, visibleBand, minimum, maximum).unique();
+ }
+
+ /**
+ * Appends a description of the given color space in the given buffer.
+ * This is used for {@code toString()} method implementations.
+ *
+ * @param cs the color space to describe, or {@code null}.
+ * @param buffer where to append the description.
+ */
+ @Debug
+ public static void formatDescription(final ColorSpace cs, final StringBuilder buffer) {
+ if (cs != null) {
+ if (cs instanceof ScaledColorSpace) {
+ ((ScaledColorSpace) cs).formatRange(buffer.append("showing "));
+ } else if (cs.getType() == ColorSpace.TYPE_GRAY) {
+ buffer.append("grayscale");
+ }
+ }
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageFactory.java
deleted file mode 100644
index 8355838..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageFactory.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.internal.coverage.j2d;
-
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
-import java.awt.image.DataBuffer;
-import org.apache.sis.util.Static;
-
-
-/**
- * Convenience methods for creating new images.
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since 1.1
- * @module
- */
-public final class ImageFactory extends Static {
- /**
- * Do not allow instantiation of this class.
- */
- private ImageFactory() {
- }
-
- /**
- * Creates an opaque image with a gray scale color model. The image can have an arbitrary
- * number of bands, but in current implementation only one band is used.
- *
- * <p><b>Warning:</b> displaying this image is very slow, except in a few special cases.
- * It should be used only when no standard color model can be used.</p>
- *
- * @param dataType the color model type as one of {@code DataBuffer.TYPE_*} constants.
- * @param width the desired image width.
- * @param height the desired image height.
- * @param numComponents the number of components.
- * @param visibleBand the band to use for computing colors.
- * @param minimum the minimal sample value expected.
- * @param maximum the maximal sample value expected.
- * @return the color space for the given range of values.
- */
- public static BufferedImage createGrayScale(final int dataType, final int width, final int height,
- final int numComponents, final int visibleBand, final double minimum, final double maximum)
- {
- switch (dataType) {
- case DataBuffer.TYPE_BYTE: {
- if (numComponents == 1 && minimum <= 0 && maximum >= 0xFF) {
- return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
- }
- break;
- }
- case DataBuffer.TYPE_USHORT: {
- if (numComponents == 1 && minimum <= 0 && maximum >= 0xFFFF) {
- return new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
- }
- break;
- }
- }
- final ColorModel cm = ColorModelFactory.createGrayScale(DataBuffer.TYPE_INT, 1, 0, -10, 10);
- return new BufferedImage(cm, cm.createCompatibleWritableRaster(width, height), false, null);
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
index 9648cfc..437b60b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
@@ -37,6 +37,8 @@ import org.apache.sis.util.resources.Vocabulary;
/**
* Utility methods related to images and their color model or sample model.
+ * Those methods only fetch information, they do not create new rasters or sample/color models
+ * (see {@code *Factory} classes for creating those objects).
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
index 404ffa3..988be37 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
@@ -207,7 +207,7 @@ final class MultiBandsIndexColorModel extends IndexColorModel {
*/
@Override
public SampleModel createCompatibleSampleModel(final int width, final int height) {
- return new BandedSampleModel(transferType, width, height, numBands);
+ return RasterFactory.unique(new BandedSampleModel(transferType, width, height, numBands));
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
index 2cdcea3..527e69a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/RasterFactory.java
@@ -17,8 +17,11 @@
package org.apache.sis.internal.coverage.j2d;
import java.awt.Point;
+import java.awt.image.ColorModel;
+import java.awt.image.SampleModel;
import java.awt.image.BandedSampleModel;
import java.awt.image.ComponentSampleModel;
+import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
@@ -26,10 +29,9 @@ import java.awt.image.DataBufferFloat;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
-import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.RasterFormatException;
-import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
+import java.awt.image.BufferedImage;
import java.nio.Buffer;
import java.nio.ReadOnlyBufferException;
import org.apache.sis.internal.feature.Resources;
@@ -38,24 +40,70 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.Static;
import org.apache.sis.util.Workaround;
+import org.apache.sis.util.collection.WeakHashSet;
/**
- * Creates rasters from given properties.
+ * Creates rasters from given properties. Contains also convenience methods for
+ * creating {@link BufferedImage} since that kind of images wraps a single raster.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
public final class RasterFactory extends Static {
/**
+ * Shared instances of {@link SampleModel}s.
+ *
+ * @see #unique(SampleModel)
+ */
+ private static final WeakHashSet<SampleModel> POOL = new WeakHashSet<>(SampleModel.class);
+
+ /**
* Do not allow instantiation of this class.
*/
private RasterFactory() {
}
/**
+ * Creates an opaque image with a gray scale color model. The image can have an arbitrary
+ * number of bands, but in current implementation only one band is used.
+ *
+ * <p><b>Warning:</b> displaying this image is very slow, except in a few special cases.
+ * It should be used only when no standard color model can be used.</p>
+ *
+ * @param dataType the color model type as one of {@code DataBuffer.TYPE_*} constants.
+ * @param width the desired image width.
+ * @param height the desired image height.
+ * @param numComponents the number of components.
+ * @param visibleBand the band to use for computing colors.
+ * @param minimum the minimal sample value expected.
+ * @param maximum the maximal sample value expected.
+ * @return the color space for the given range of values.
+ */
+ public static BufferedImage createGrayScaleImage(final int dataType, final int width, final int height,
+ final int numComponents, final int visibleBand, final double minimum, final double maximum)
+ {
+ switch (dataType) {
+ case DataBuffer.TYPE_BYTE: {
+ if (numComponents == 1 && minimum <= 0 && maximum >= 0xFF) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+ }
+ break;
+ }
+ case DataBuffer.TYPE_USHORT: {
+ if (numComponents == 1 && minimum <= 0 && maximum >= 0xFFFF) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
+ }
+ break;
+ }
+ }
+ final ColorModel cm = ColorModelFactory.createGrayScale(DataBuffer.TYPE_INT, 1, 0, -10, 10);
+ return new BufferedImage(cm, cm.createCompatibleWritableRaster(width, height), false, null);
+ }
+
+ /**
* Wraps the given data buffer in a raster.
* The sample model type is selected according the number of bands and the pixel stride.
* The number of bands is determined by {@code bandOffsets.length}, which should be one of followings:
@@ -152,7 +200,7 @@ public final class RasterFactory extends Static {
model = new ComponentSampleModel(dataType, width, height, pixelStride, scanlineStride, bankIndices, bandOffsets);
}
}
- return WritableRaster.createWritableRaster(model, buffer, location);
+ return WritableRaster.createWritableRaster(unique(model), buffer, location);
}
/**
@@ -224,4 +272,16 @@ public final class RasterFactory extends Static {
default: return null;
}
}
+
+ /**
+ * Returns a unique instance of the given sample model. This method can be invoked after a new sample
+ * has been created in order to share the same instance for many similar {@code Raster} instances.
+ *
+ * @param <T> the type of the given {@code sampleModel}.
+ * @param sampleModel the sample model to make unique.
+ * @return a unique instance of the given sample model. May be {@code sampleModel} itself.
+ */
+ static <T extends SampleModel> T unique(final T sampleModel) {
+ return POOL.unique(sampleModel);
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java
index 0ee9e58..0e97622 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java
@@ -17,7 +17,9 @@
package org.apache.sis.internal.coverage.j2d;
import java.awt.color.ColorSpace;
+import org.apache.sis.internal.util.Numerics;
import org.apache.sis.util.Debug;
+import org.apache.sis.util.collection.WeakHashSet;
/**
@@ -36,13 +38,18 @@ import org.apache.sis.util.Debug;
* @since 1.0
* @module
*/
-public final class ScaledColorSpace extends ColorSpace {
+final class ScaledColorSpace extends ColorSpace {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 438226855772441165L;
/**
+ * Shared instances of {@link ScaledColorSpace}s.
+ */
+ private static final WeakHashSet<ScaledColorSpace> POOL = new WeakHashSet<>(ScaledColorSpace.class);
+
+ /**
* Minimal normalized RGB value.
*/
private static final float MIN_VALUE = 0f;
@@ -69,6 +76,7 @@ public final class ScaledColorSpace extends ColorSpace {
/**
* Creates a color model for the given range of values.
+ * Callers should invoke {@link #unique()} on the newly created instance.
*
* @param numComponents the number of components.
* @param visibleBand the band to use for computing colors.
@@ -181,9 +189,42 @@ public final class ScaledColorSpace extends ColorSpace {
* @param buffer where to append the range of values.
*/
@Debug
- public final void formatRange(final StringBuilder buffer) {
+ final void formatRange(final StringBuilder buffer) {
buffer.append('[').append(getMinValue(visibleBand))
.append(" … ").append(getMaxValue(visibleBand))
.append(" in band ").append(visibleBand).append(']');
}
+
+ /**
+ * Returns a unique instance of this color space. May be {@code this}.
+ */
+ final ScaledColorSpace unique() {
+ return POOL.unique(this);
+ }
+
+ /**
+ * Returns a hash code value for this color model.
+ * Defined for implementation of {@link #unique()}.
+ */
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(scale) + 31 * Float.floatToIntBits(offset) + 7 * getNumComponents() + visibleBand;
+ }
+
+ /**
+ * Compares this color space with the given object for equality.
+ * Defined for implementation of {@link #unique()}.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof ScaledColorSpace) {
+ final ScaledColorSpace that = (ScaledColorSpace) obj;
+ return Numerics.equals(scale, that.scale) &&
+ Numerics.equals(offset, that.offset) &&
+ visibleBand == that.visibleBand &&
+ getNumComponents() == that.getNumComponents() &&
+ getType() == that.getType();
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index f89d81f..3841c68 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -27,7 +27,7 @@ import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.datum.PixelInCell;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.geometry.DirectPosition2D;
-import org.apache.sis.internal.coverage.j2d.ImageFactory;
+import org.apache.sis.internal.coverage.j2d.RasterFactory;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.measure.Units;
import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -70,7 +70,7 @@ public final strictfp class GridCoverage2DTest extends TestCase {
* Create an image and set values directly as integers. We do not use one of the
* BufferedImage.TYPE_* constant because this test uses some negative values.
*/
- final BufferedImage image = ImageFactory.createGrayScale(DataBuffer.TYPE_INT, size, size, 1, 0, -10, 10);
+ final BufferedImage image = RasterFactory.createGrayScaleImage(DataBuffer.TYPE_INT, size, size, 1, 0, -10, 10);
final WritableRaster raster = image.getRaster();
raster.setSample(0, 0, 0, 2);
raster.setSample(1, 0, 0, 5);