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 2022/06/01 15:07:26 UTC
[sis] branch geoapi-4.0 updated: Improvement in the cache of `RenderedImage` instances: - Revisit the `equals(Object)` and `hashCode()` methods. - Reuse existing `RenderedImage` instances for a given `SliceExtent`.
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 4d0e134cc8 Improvement in the cache of `RenderedImage` instances: - Revisit the `equals(Object)` and `hashCode()` methods. - Reuse existing `RenderedImage` instances for a given `SliceExtent`.
4d0e134cc8 is described below
commit 4d0e134cc83977c18a485bb92de1e74070bbab68
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Jun 1 16:25:54 2022 +0200
Improvement in the cache of `RenderedImage` instances:
- Revisit the `equals(Object)` and `hashCode()` methods.
- Reuse existing `RenderedImage` instances for a given `SliceExtent`.
The latter item is helpful even if the rendered images are cheap to create
(some of them are just wrappers around existing `DataBuffer`) because the
image may be the starting point of a chain of operations with statistics,
raster resampling, etc. Not reusing an existing image means discarding
all the derivated information.
---
.../sis/coverage/grid/BufferedGridCoverage.java | 79 ++++++++++++++++++++--
.../sis/coverage/grid/ConvertedGridCoverage.java | 2 +
.../org/apache/sis/coverage/grid/GridCoverage.java | 2 +-
.../apache/sis/coverage/grid/GridEvaluator.java | 2 +
.../org/apache/sis/coverage/grid/GridExtent.java | 2 +
.../apache/sis/coverage/grid/ReshapedImage.java | 31 ++++++++-
.../java/org/apache/sis/image/AnnotatedImage.java | 9 ++-
.../java/org/apache/sis/image/BandSelectImage.java | 23 ++++++-
.../apache/sis/image/BandedSampleConverter.java | 44 +++++++++++-
.../java/org/apache/sis/image/ComputedImage.java | 27 ++++++++
.../java/org/apache/sis/image/ImageAdapter.java | 11 +--
.../main/java/org/apache/sis/image/MaskImage.java | 28 +++++++-
.../java/org/apache/sis/image/MaskedImage.java | 9 +--
.../java/org/apache/sis/image/PlanarImage.java | 10 +++
.../sis/image/PositionalConsistencyImage.java | 23 ++++++-
.../java/org/apache/sis/image/PrefetchedImage.java | 30 +++++++-
.../java/org/apache/sis/image/RecoloredImage.java | 8 ++-
.../java/org/apache/sis/image/ResampledImage.java | 9 ++-
.../org/apache/sis/image/SourceAlignedImage.java | 28 +++++++-
.../org/apache/sis/image/StatisticsCalculator.java | 2 +-
.../java/org/apache/sis/image/Visualization.java | 26 ++++++-
.../org/apache/sis/internal/netcdf/Raster.java | 29 +++-----
.../sis/internal/storage/TiledGridCoverage.java | 4 +-
.../apache/sis/test/storage/SubsampledImage.java | 27 +++++++-
24 files changed, 407 insertions(+), 58 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index 0304be0419..8f20214e49 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -17,6 +17,7 @@
package org.apache.sis.coverage.grid;
import java.util.List;
+import java.util.function.Function;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
@@ -28,11 +29,13 @@ import java.awt.image.RasterFormatException;
import java.awt.image.RenderedImage;
import org.opengis.util.FactoryException;
import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.internal.feature.Resources;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.collection.Cache;
import org.apache.sis.image.DataType;
// Branch-specific imports
@@ -75,7 +78,7 @@ import org.opengis.coverage.PointOutsideCoverageException;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -90,6 +93,41 @@ public class BufferedGridCoverage extends GridCoverage {
*/
protected final DataBuffer data;
+ /**
+ * Cache of rendered images produced by calls to {@link #render(GridExtent)}.
+ * Those images are cached because, even if they are cheap to create,
+ * they may become the source of a chain of operations for statistics,
+ * {@linkplain org.apache.sis.image.ResampledImage image resampling}, <i>etc.</i>
+ * Caching the source image preserves not only the {@link RenderedImage} instance created by the
+ * {@link #render(GridExtent)} method, but also the chain of operations potentially derived from that image.
+ *
+ * <h4>Usage</h4>
+ * Implementation of {@link #render(GridExtent)} method can be like below:
+ *
+ * {@preformat java
+ * @Override
+ * public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException {
+ * if (sliceExtent == null) {
+ * sliceExtent = gridGeometry.getExtent();
+ * }
+ * // Do some other verification if needed…
+ * // … then get or compute the image.
+ * try {
+ * return cachedRenderings.computeIfAbsent(sliceExtent, (slice) -> {
+ * val renderer = new ImageRenderer(this, slice);
+ * renderer.setData(data);
+ * return renderer.createImage();
+ * });
+ * } catch (IllegalGridGeometryException | MismatchedDimensionException e) {
+ * throw e;
+ * } catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
+ * throw new CannotEvaluateException(e.getMessage(), e);
+ * }
+ * }
+ * }
+ */
+ private final Cache<GridExtent,RenderedImage> cachedRenderings;
+
/**
* Constructs a grid coverage using the specified grid geometry, sample dimensions and data buffer.
* This method stores the given buffer by reference (no copy). The bands in the given buffer can be
@@ -139,6 +177,7 @@ public class BufferedGridCoverage extends GridCoverage {
throw new IllegalGridGeometryException(Resources.format(
Resources.Keys.InsufficientBufferCapacity_3, b, numBands, expectedSize - bufferSize));
}
+ cachedRenderings = new Cache<>();
}
/**
@@ -163,6 +202,7 @@ public class BufferedGridCoverage extends GridCoverage {
case DataBuffer.TYPE_DOUBLE: data = new DataBufferDouble(n); break;
default: throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, dataType));
}
+ cachedRenderings = new Cache<>();
}
/**
@@ -204,19 +244,48 @@ public class BufferedGridCoverage extends GridCoverage {
* Returns a two-dimensional slice of grid data as a rendered image.
* This method returns a view; sample values are not copied.
*
+ * <p>The default implementation prepares an {@link ImageRenderer},
+ * then invokes {@link #configure(ImageRenderer)} for allowing subclasses
+ * to complete the renderer configuration before to create the image.</p>
+ *
* @return the grid slice as a rendered image.
*/
@Override
- public RenderedImage render(final GridExtent sliceExtent) {
- final ImageRenderer renderer = new ImageRenderer(this, sliceExtent);
+ public RenderedImage render(GridExtent sliceExtent) {
+ if (sliceExtent == null) {
+ sliceExtent = gridGeometry.extent;
+ }
try {
- renderer.setData(data);
- return renderer.createImage();
+ return cachedRenderings.computeIfAbsent(sliceExtent, (slice) -> {
+ ImageRenderer renderer = new ImageRenderer(this, slice);
+ renderer.setData(data);
+ return renderer.createImage();
+ });
+ } catch (IllegalGridGeometryException | MismatchedDimensionException e) {
+ throw e;
} catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
throw new CannotEvaluateException(e.getMessage(), e);
}
}
+ /**
+ * Invoked by the default implementation of {@link #render(GridExtent)}
+ * for completing the renderer configuration before to create an image.
+ * The default implementation does nothing.
+ *
+ * <p>Some example of methods that subclasses may want to use are:</p>
+ * <ul>
+ * <li>{@link ImageRenderer#setCategoryColors(Function)}</li>
+ * <li>{@link ImageRenderer#setVisibleBand(int)}</li>
+ * </ul>
+ *
+ * @param renderer the renderer to configure before to create an image.
+ *
+ * @since 1.3
+ */
+ protected void configure(final ImageRenderer renderer) {
+ }
+
/**
* Implementation of evaluator returned by {@link #evaluator()}.
*/
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index c2f6b8870a..8c30d9b63d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -332,6 +332,8 @@ final class ConvertedGridCoverage extends GridCoverage {
RenderedImage image = source.render(sliceExtent);
/*
* That image should never be null. But if an implementation wants to do so, respect that.
+ * We do not cache the image because caching is already handled by `ImageProcessor`,
+ * assuming that `source` returned an image from its own cache.
*/
if (image != null) {
image = convert(image, bandType, converters, processor);
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 c4f7115e92..bd0425eedb 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
@@ -75,7 +75,7 @@ public abstract class GridCoverage extends BandedCoverage {
*
* @see #getGridGeometry()
*/
- final GridGeometry gridGeometry;
+ protected final GridGeometry gridGeometry;
/**
* List of sample dimension (band) information for the grid coverage. Information include such things
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
index d79883df73..6a40742dbb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
@@ -208,6 +208,8 @@ public class GridEvaluator implements GridCoverage.Evaluator {
* @param slice the default slice where to perform evaluation, or an empty map if none.
* @throws IllegalArgumentException if the map contains an illegal dimension or grid coordinate value.
*
+ * @see GridExtent#getSliceCoordinates()
+ *
* @since 1.3
*/
public void setDefaultSlice(Map<Integer,Long> slice) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 2616661c94..838e443331 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -841,6 +841,8 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
*
* @return grid coordinates for all dimensions where the grid has a size of 1.
*
+ * @see GridEvaluator#setDefaultSlice(Map)
+ *
* @since 1.3
*/
public SortedMap<Integer,Long> getSliceCoordinates() {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ReshapedImage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ReshapedImage.java
index 9500a8edf2..0a7efb56ff 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ReshapedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ReshapedImage.java
@@ -17,6 +17,7 @@
package org.apache.sis.coverage.grid;
import java.util.Vector;
+import java.util.Objects;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
@@ -42,7 +43,7 @@ import static java.lang.Math.toIntExact;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -305,4 +306,32 @@ final class ReshapedImage extends PlanarImage {
if (getTileGridYOffset() != super.getTileGridYOffset()) return "tileGridYOffset";
return super.verify(); // "width" and "height" properties should be checked last.
}
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(source, minX, minY, width, height);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (object instanceof ReshapedImage) {
+ final ReshapedImage other = (ReshapedImage) object;
+ return source.equals(other.source) &&
+ minX == other.minX &&
+ minY == other.minY &&
+ width == other.width &&
+ height == other.height &&
+ offsetX == other.offsetX &&
+ offsetY == other.offsetY &&
+ minTileX == other.minTileX &&
+ minTileY == other.minTileY;
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
index 3c1d7e7143..c6e4c07e50 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
@@ -540,7 +540,7 @@ abstract class AnnotatedImage extends ImageAdapter {
* @return a hash code value based on a description of the operation performed by this image.
*/
@Override
- public int hashCode() {
+ public final int hashCode() {
return super.hashCode() + Objects.hashCode(areaOfInterest) + Boolean.hashCode(failOnException);
}
@@ -548,11 +548,16 @@ abstract class AnnotatedImage extends ImageAdapter {
* Compares the given object with this image for equality. This method should be quick and compare
* how images compute their values from their sources; it should not compare the actual pixel values.
*
+ * <h4>Requirements for subclasses</h4>
+ * Subclasses should override {@link #getExtraParameter()} instead of this method.
+ *
* @param object the object to compare with this image.
* @return {@code true} if the given object is an image performing the same calculation than this image.
+ *
+ * @see #getExtraParameter()
*/
@Override
- public boolean equals(final Object object) {
+ public final boolean equals(final Object object) {
return super.equals(object) && equalParameters((AnnotatedImage) object);
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
index 53247c09c1..c3dfe1db09 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
@@ -17,6 +17,7 @@
package org.apache.sis.image;
import java.util.Set;
+import java.util.Arrays;
import java.util.Hashtable;
import java.lang.reflect.Array;
import java.awt.Image;
@@ -37,7 +38,7 @@ import org.apache.sis.internal.jdk9.JDK9;
* it works by modifying the sample model and color model.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -174,4 +175,24 @@ final class BandSelectImage extends SourceAlignedImage {
*/
return parent.createChild(x, y, parent.getWidth(), parent.getHeight(), x, y, bands);
}
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + 97 * Arrays.hashCode(bands);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (super.equals(object)) {
+ final BandSelectImage other = (BandSelectImage) object;
+ return Arrays.equals(bands, other.bands);
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
index e6b15c5b43..2f3fda058e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
@@ -16,6 +16,8 @@
*/
package org.apache.sis.image;
+import java.util.Arrays;
+import java.util.Objects;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
@@ -64,7 +66,7 @@ import static java.util.logging.Logger.getLogger;
* In such case, writing converted values will cause the corresponding source values to be updated too.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -487,5 +489,45 @@ class BandedSampleConverter extends ComputedImage {
markDirtyTiles(executor.getTileIndices());
}
}
+
+ /**
+ * Restores the identity behavior for writable image,
+ * because it may have listeners attached to this specific instance.
+ */
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ /**
+ * Restores the identity behavior for writable image,
+ * because it may have listeners attached to this specific instance.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ return object == this;
+ }
+ }
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return hashCodeBase() + 37 * Arrays.hashCode(converters) + Objects.hashCode(colorModel);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (equalsBase(object)) {
+ final BandedSampleConverter other = (BandedSampleConverter) object;
+ return Arrays .equals(converters, other.converters) &&
+ Objects.equals(colorModel, other.colorModel) &&
+ Arrays .equals(sampleResolutions, other.sampleResolutions);
+ }
+ return false;
}
}
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 56fc18a4d5..879638cd36 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
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;
+import java.util.Objects;
import java.lang.ref.Reference;
import java.awt.Insets;
import java.awt.Point;
@@ -781,4 +782,30 @@ public abstract class ComputedImage extends PlanarImage implements Disposable {
public void dispose() {
reference.dispose();
}
+
+ /**
+ * Returns a hash code value based on the fields known to this base class.
+ * This is a helper method for {@link #hashCode()} implementation in subclasses.
+ * It should <strong>not</strong> be used by {@link WritableRenderedImage} implementations,
+ * because those images have listeners that are attached to a specific instance.
+ */
+ final int hashCodeBase() {
+ return Arrays.hashCode(sources) + 31*sampleModel.hashCode() + 37*Objects.hash(destination);
+ }
+
+ /**
+ * Compares the given object with this image for equality using the fields known to this base class.
+ * This is a helper method for {@link #equals(Object)} implementation in subclasses.
+ * It should <strong>not</strong> be used by {@link WritableRenderedImage} implementations,
+ * because those images have listeners that are attached to a specific instance.
+ */
+ final boolean equalsBase(final Object object) {
+ if (object != null && getClass().equals(object.getClass())) {
+ final ComputedImage other = (ComputedImage) object;
+ return Arrays .equals(sources, other.sources) &&
+ Objects.equals(destination, other.destination) &&
+ sampleModel.equals(other.sampleModel);
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
index 1482872c5d..e34393ebfc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
@@ -33,13 +33,16 @@ import org.apache.sis.util.Disposable;
* All {@link RenderedImage} methods related to coordinate systems (pixel coordinates or tile
* indices), and all methods fetching tiles, delegate to the wrapped image.
*
- * <div class="note"><b>Design note:</b>
+ * <h2>Design note</h2>
* most non-abstract methods are final because {@link PixelIterator} (among others) relies
- * on the fact that it can unwrap this image and still get the same pixel values.</div>
+ * on the fact that it can unwrap this image and still get the same pixel values.
*
- * <div class="note"><b>Relationship with other classes</b><br>
+ * <h2>Relationship with other classes</h2>
* This class is similar to {@link SourceAlignedImage} except that it does not extend {@link ComputedImage}
- * and forward {@link #getTile(int, int)}, {@link #getData()} and other data methods to the source image.</div>
+ * and forward {@link #getTile(int, int)}, {@link #getData()} and other data methods to the source image.
+ *
+ * <h2>Requirements for subclasses</h2>
+ * All subclasses shall override {@link #equals(Object)} and {@link #hashCode()}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.2
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
index c2284d4c60..de89515d62 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.image;
+import java.util.Objects;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import org.opengis.referencing.operation.MathTransform;
@@ -34,7 +35,7 @@ import static java.util.logging.Logger.getLogger;
* This is the implementation of {@value ResampledImage#MASK_KEY} property value.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
*
* @see ResampledImage#getProperty(String)
* @see ResampledImage#MASK_KEY
@@ -47,7 +48,7 @@ final class MaskImage extends SourceAlignedImage {
* Convert integer values to floating point values, or {@code null} if none.
* This is needed since we use {@link Float#isNaN(float)} for identifying values to mask.
*/
- private MathTransform converter;
+ private final MathTransform converter;
/**
* Creates a new instance for the given image.
@@ -55,12 +56,15 @@ final class MaskImage extends SourceAlignedImage {
MaskImage(final ResampledImage image) {
super(image, ColorModelFactory.createIndexColorModel(
1, ImageUtilities.getVisibleBand(image), new int[] {0, -1}, true, 0));
+
+ MathTransform converter = null;
if (image.interpolation instanceof Visualization.InterpConvert) try {
converter = ((Visualization.InterpConvert) image.interpolation).converter.inverse();
} catch (NoninvertibleTransformException e) {
// ResampledImage.getProperty("org.apache.sis.Mask") is the public caller of this constructor.
Logging.unexpectedException(getLogger(Modules.RASTER), ResampledImage.class, "getProperty", e);
}
+ this.converter = converter;
}
/**
@@ -120,4 +124,24 @@ final class MaskImage extends SourceAlignedImage {
}
return tile;
}
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + 97 * Objects.hashCode(converter);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (super.equals(object)) {
+ final MaskImage other = (MaskImage) object;
+ return Objects.equals(converter, other.converter);
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/MaskedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/MaskedImage.java
index ae15580b50..0b25ef0022 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/MaskedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MaskedImage.java
@@ -16,7 +16,6 @@
*/
package org.apache.sis.image;
-import java.util.Objects;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.awt.Rectangle;
@@ -470,11 +469,9 @@ complete: for (int border = 0; ; border++) {
*/
@Override
public boolean equals(final Object object) {
- if (object != null && object.getClass().equals(getClass())) {
+ if (super.equals(object)) {
final MaskedImage other = (MaskedImage) object;
- return clip.equals(other.clip) &&
- Objects.deepEquals(fillValues, other.fillValues) &&
- getSources().equals(other.getSources());
+ return clip.equals(other.clip) && fillValues.equals(other.fillValues);
}
return false;
}
@@ -486,6 +483,6 @@ complete: for (int border = 0; ; border++) {
*/
@Override
public int hashCode() {
- return clip.hashCode() + Objects.hashCode(fillValues) + getSources().hashCode();
+ return super.hashCode() + clip.hashCode() + fillValues.hashCode();
}
}
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 3ac76d2e00..31a5937a41 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
@@ -646,4 +646,14 @@ colors: if (cm != null) {
}
return buffer.toString();
}
+
+ /*
+ * Note on `equals(Object)` and `hashCode()` methods:
+ *
+ * Do not provide base implementation for those methods, because they can only be incomplete and it is too easy
+ * to forget to override those methods in subclasses. Furthermore we should override those methods only in final
+ * classes that are read-only images. Base classes of potentially writable images should continue to use identity
+ * comparisons, especially when some tiles have been acquired for writing and not yet released at the time the
+ * `equals(Object)` method is invoked.
+ */
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
index 370cb2fe14..69c0b3988b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
@@ -31,7 +31,7 @@ import org.apache.sis.internal.jdk9.JDK9;
* This is the implementation of {@link ResampledImage#POSITIONAL_CONSISTENCY_KEY} property value.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -141,4 +141,25 @@ final class PositionalConsistencyImage extends SourceAlignedImage {
}
return tile;
}
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + 67 * toSource.hashCode()
+ + 97 * toTarget.hashCode();
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (super.equals(object)) {
+ final PositionalConsistencyImage other = (PositionalConsistencyImage) object;
+ return toSource.equals(other.toSource) && toTarget.equals(other.toTarget);
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
index 989c58e112..5b52aa4e60 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
@@ -17,6 +17,7 @@
package org.apache.sis.image;
import java.util.Vector;
+import java.util.Objects;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
@@ -38,7 +39,7 @@ import org.apache.sis.util.Disposable;
* This image has the same coordinate systems than the source image.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
*
* @see ImageProcessor#prefetch(RenderedImage, Rectangle)
*
@@ -303,4 +304,31 @@ final class PrefetchedImage extends PlanarImage implements TileErrorHandler.Exec
return p.create(new Point(ImageUtilities.tileToPixelX(source, tileX),
ImageUtilities.tileToPixelY(source, tileY)));
}
+
+ /**
+ * Returns a hash code value for this image.
+ * Defined for consistency with {@link #equals(Object)}.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(source, minTileX, minTileY, numXTiles, numYTiles);
+ }
+
+ /**
+ * Compares the given object with this image for equality. This is defined as a matter of principle,
+ * but is a little bit useless for {@link PrefetchedImage} because tiles have already been computed
+ * in the constructor. So it is too late for caching for example.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (object instanceof PrefetchedImage) {
+ final PrefetchedImage other = (PrefetchedImage) object;
+ return source.equals(other.source) &&
+ minTileX == other.minTileX &&
+ minTileY == other.minTileY &&
+ numXTiles == other.numXTiles &&
+ numYTiles == other.numYTiles;
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
index a4f7dcfbd0..d03658e87a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
@@ -369,7 +369,13 @@ final class RecoloredImage extends ImageAdapter {
*/
@Override
public boolean equals(final Object object) {
- return super.equals(object) && colors.equals(((RecoloredImage) object).colors);
+ if (super.equals(object)) {
+ final RecoloredImage other = (RecoloredImage) object;
+ return Numerics.equals(minimum, other.minimum) &&
+ Numerics.equals(maximum, other.maximum) &&
+ colors.equals(other.colors);
+ }
+ return false;
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index 344a886265..2867b04cdb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -907,7 +907,7 @@ public class ResampledImage extends ComputedImage {
*/
@Override
public boolean equals(final Object object) {
- if (object != null && object.getClass().equals(getClass())) {
+ if (equalsBase(object)) {
final ResampledImage other = (ResampledImage) object;
return minX == other.minX &&
minY == other.minY &&
@@ -917,8 +917,7 @@ public class ResampledImage extends ComputedImage {
minTileY == other.minTileY &&
interpolation.equals(other.interpolation) &&
Objects.deepEquals(fillValues, other.fillValues) &&
- toSource.equals(other.toSource) &&
- getSources().equals(other.getSources());
+ toSource.equals(other.toSource);
}
return false;
}
@@ -930,7 +929,7 @@ public class ResampledImage extends ComputedImage {
*/
@Override
public int hashCode() {
- return minX + 31*(minY + 31*(width + 31*height)) + interpolation.hashCode()
- + toSource.hashCode() + getSources().hashCode();
+ return hashCodeBase() + minX + 31*(minY + 31*(width + 31*height))
+ + interpolation.hashCode() + toSource.hashCode();
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
index e0602ef256..907a701d1a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
@@ -17,6 +17,7 @@
package org.apache.sis.image;
import java.util.Set;
+import java.util.Objects;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
@@ -43,9 +44,10 @@ import org.apache.sis.internal.jdk9.JDK9;
* That method is invoked when a requested tile is not in the cache or needs to be updated.
* All methods related to pixel and tile coordinates ({@link #getMinX()}, {@link #getMinTileX()},
* <i>etc.</i>) are final and delegate to the source image.
+ * The {@link #equals(Object)} and {@link #hashCode()} methods should also be overridden.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -59,7 +61,7 @@ abstract class SourceAlignedImage extends ComputedImage {
POSITIONAL_ACCURACY_KEY, ResampledImage.POSITIONAL_CONSISTENCY_KEY);
/**
- * The color model for this image.
+ * The color model for this image. May be {@code null}.
*/
private final ColorModel colorModel;
@@ -205,4 +207,26 @@ abstract class SourceAlignedImage extends ComputedImage {
return super.prefetch(tiles);
}
}
+
+ /**
+ * Returns a hash code value for this image.
+ * Subclasses should override this method.
+ */
+ @Override
+ public int hashCode() {
+ return hashCodeBase() + 37 * Objects.hashCode(colorModel);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ * Subclasses should override this method.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (equalsBase(object)) {
+ final SourceAlignedImage other = (SourceAlignedImage) object;
+ return Objects.equals(colorModel, other.colorModel);
+ }
+ return false;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java b/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
index 213501652f..dcb691f971 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
@@ -109,7 +109,7 @@ final class StatisticsCalculator extends AnnotatedImage {
* @param accumulator where to accumulate the statistics results.
* @return the accumulator optionally filtered.
*/
- private final DoubleConsumer[] filtered(final Statistics[] accumulator) {
+ private DoubleConsumer[] filtered(final Statistics[] accumulator) {
if (sampleFilters == null) {
return accumulator;
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
index 3695693b9d..7a69dd26f5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
@@ -18,6 +18,8 @@ package org.apache.sis.image;
import java.util.Map;
import java.util.List;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.DoubleUnaryOperator;
@@ -58,7 +60,7 @@ import org.apache.sis.util.collection.BackingStoreException;
* {@link WritableRaster#setPixel(int, int, int[])} has more efficient implementations for integers.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -463,4 +465,26 @@ final class Visualization extends ResampledImage {
Transferer.create(getSource(), tile).compute(converters);
return tile;
}
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (super.equals(object)) {
+ final Visualization other = (Visualization) object;
+ return Arrays .equals(converters, other.converters) &&
+ Objects.equals(colorModel, other.colorModel);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + 67 * Arrays.hashCode(converters)
+ + 97 * Objects.hashCode(colorModel);
+ }
}
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 611452f19d..cc019c1cc4 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
@@ -21,12 +21,9 @@ import java.util.function.Function;
import java.awt.Color;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
-import java.awt.image.RasterFormatException;
-import org.opengis.coverage.CannotEvaluateException;
import org.apache.sis.coverage.Category;
import org.apache.sis.coverage.SampleDimension;
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.coverage.grid.BufferedGridCoverage;
@@ -43,7 +40,7 @@ import org.apache.sis.coverage.grid.BufferedGridCoverage;
* but it is {@link ImageRenderer} responsibility to perform this substitution as an optimization.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.0
* @module
*/
@@ -103,24 +100,16 @@ final class Raster extends BufferedGridCoverage {
}
/**
- * Returns a two-dimensional slice of grid data as a rendered image.
- * This returns a view as much as possible; sample values are not copied.
+ * Configures a two-dimensional slice of grid data as a rendered image.
*/
@Override
- public RenderedImage render(final GridExtent target) {
- final ImageRenderer renderer = new ImageRenderer(this, target);
- try {
- renderer.setData(data);
- if (bandOffsets != null) {
- renderer.setInterleavedPixelOffsets(pixelStride, bandOffsets);
- }
- if (colors != null) {
- renderer.setCategoryColors(colors);
- }
- renderer.setVisibleBand(visibleBand);
- return renderer.createImage();
- } catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
- throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
+ protected void configure(final ImageRenderer renderer) {
+ if (bandOffsets != null) {
+ renderer.setInterleavedPixelOffsets(pixelStride, bandOffsets);
}
+ if (colors != null) {
+ renderer.setCategoryColors(colors);
+ }
+ renderer.setVisibleBand(visibleBand);
}
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
index 646aebf84d..4b01e6a7e5 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
@@ -382,7 +382,7 @@ public abstract class TiledGridCoverage extends GridCoverage {
*/
@Override
public RenderedImage render(GridExtent sliceExtent) {
- final GridExtent available = getGridGeometry().getExtent();
+ final GridExtent available = gridGeometry.getExtent();
final int dimension = available.getDimension();
if (sliceExtent == null) {
sliceExtent = available;
@@ -433,7 +433,7 @@ public abstract class TiledGridCoverage extends GridCoverage {
* - Two-dimensional conversion from pixel coordinates to "real world" coordinates.
*/
final AOI iterator = new AOI(tileLower, tileUpper, offsetAOI, dimension);
- final Map<String,Object> properties = DeferredProperty.forGridGeometry(getGridGeometry(), selectedDimensions);
+ final Map<String,Object> properties = DeferredProperty.forGridGeometry(gridGeometry, selectedDimensions);
if (deferredTileReading) {
image = new TiledDeferredImage(imageSize, tileLower, properties, iterator);
} else {
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
index 20aa540796..cb826be6a5 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/SubsampledImage.java
@@ -19,6 +19,7 @@ package org.apache.sis.test.storage;
import java.awt.Point;
import java.util.Arrays;
import java.util.Vector;
+import java.util.Objects;
import java.awt.image.Raster;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
@@ -45,7 +46,7 @@ import static org.junit.Assert.*;
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -352,4 +353,28 @@ final class SubsampledImage extends PlanarImage {
}
return target;
}
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(source, subX, subY, offX, offY);
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (object instanceof SubsampledImage) {
+ final SubsampledImage other = (SubsampledImage) object;
+ return source.equals(other.source) &&
+ subX == other.subX &&
+ subY == other.subY &&
+ offX == other.offX &&
+ offY == other.offY;
+ }
+ return false;
+ }
}