This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit b2c6b5ee0268de08cc18100ddc446ba92bb63e24 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Jul 31 16:29:21 2026 +0200 Rename `ObservableImage` as `WritableUntiledImage` and merge with `ImageRenderer.Untiled`. The goal is to avoid incorrect inheritance of the `GridGeometry` property in sub-images. --- .../sis/coverage/grid/GridCoverageBuilder.java | 4 +- .../apache/sis/coverage/grid/ImageRenderer.java | 119 +++---------- .../main/org/apache/sis/image/BandSelectImage.java | 4 +- .../apache/sis/image/WritableComputedImage.java | 8 +- .../sis/image/internal/shared/ImageUtilities.java | 2 +- .../sis/image/internal/shared/RasterFactory.java | 4 +- .../image/internal/shared/WritableTiledImage.java | 14 +- ...ervableImage.java => WritableUntiledImage.java} | 193 +++++++++++++++------ .../sis/storage/sql/postgis/RasterReader.java | 4 +- .../org/apache/sis/storage/esri/RasterStore.java | 4 +- .../sis/storage/image/internal/BufferedImages.java | 6 +- 11 files changed, 185 insertions(+), 177 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java index 67f59f1d3c..484967f95e 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageBuilder.java @@ -43,7 +43,7 @@ import org.apache.sis.image.ImageLayout; import org.apache.sis.image.PlanarImage; import org.apache.sis.image.WritablePixelIterator; import org.apache.sis.image.internal.shared.ColorScaleBuilder; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; import org.apache.sis.image.internal.shared.TiledImage; import org.apache.sis.image.internal.shared.WritableTiledImage; import org.apache.sis.coverage.SampleDimension; @@ -601,7 +601,7 @@ public class GridCoverageBuilder { if (colors != null && tiles.length == 1) { final Raster tile = tiles[0]; if (tile instanceof WritableRaster && (tile.getMinX() | tile.getMinY()) == 0) { - return new ObservableImage(colors, (WritableRaster) tile, false, properties); + return new WritableUntiledImage(colors, (WritableRaster) tile, false, properties); } } if (tiles instanceof WritableRaster[]) { diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java index d78dd3b258..b5990beb96 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java @@ -27,10 +27,8 @@ import java.awt.Rectangle; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.SampleModel; -import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; -import java.awt.image.ImagingOpException; import java.awt.image.RasterFormatException; import java.awt.image.Raster; import static java.lang.Math.addExact; @@ -48,13 +46,12 @@ import org.apache.sis.coverage.Category; import org.apache.sis.image.internal.shared.ColorScaleBuilder; import org.apache.sis.image.internal.shared.DeferredProperty; import org.apache.sis.image.internal.shared.RasterFactory; -import org.apache.sis.image.internal.shared.ObservableImage; import org.apache.sis.image.internal.shared.TiledImage; import org.apache.sis.image.internal.shared.WritableTiledImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; import org.apache.sis.feature.internal.Resources; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ComparisonMode; -import org.apache.sis.util.ArraysExt; import org.apache.sis.util.resources.Errors; import org.apache.sis.math.Vector; import static org.apache.sis.image.PlanarImage.XY_DIMENSIONS_KEY; @@ -103,7 +100,7 @@ import org.opengis.coordinate.MismatchedDimensionException; * Support for tiled images will be added in a future version. * * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.7 * * @see GridCoverage#render(GridExtent) * @@ -281,6 +278,7 @@ public class ImageRenderer { /** * The properties to give to the image, or {@code null} if none. * + * @see #properties() * @see #addProperty(String, Object) */ @SuppressWarnings("UseOfObsoleteCollectionType") @@ -512,10 +510,7 @@ public class ImageRenderer { case GRID_GEOMETRY_KEY: case SAMPLE_DIMENSIONS_KEY: break; default: { - if (properties == null) { - properties = new Hashtable<>(); - } - if (properties.putIfAbsent(key, value) == null) { + if (properties().putIfAbsent(key, value) == null) { return; } } @@ -523,6 +518,17 @@ public class ImageRenderer { throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1, key)); } + /** + * Returns the properties, created when first needed. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + private Hashtable<String, Object> properties() { + if (properties == null) { + properties = new Hashtable<>(); + } + return properties; + } + /** * Returns {@code true} if a {@link #getImageGeometry(int)} request for the given number of CRS dimensions * can return {@link #geometry} directly. This common case avoids the need for more costly computation with @@ -780,104 +786,19 @@ public class ImageRenderer { supplier = new SliceGeometry(geometry, sliceExtent, gridDimensions, mtFactory); } } + @SuppressWarnings("LocalVariableHidesMemberVariable") + final Hashtable<String, Object> properties = properties(); + properties.putIfAbsent(SAMPLE_DIMENSIONS_KEY, bands); + properties.putIfAbsent(XY_DIMENSIONS_KEY, gridDimensions); final WritableRaster wr = (raster instanceof WritableRaster) ? (WritableRaster) raster : null; if (wr != null && cm != null && (imageX | imageY) == 0) { - return new Untiled(cm, wr, properties, gridDimensions, imageGeometry, supplier, bands); + return new WritableUntiledImage(cm, wr, false, properties).setGridGeometry(imageGeometry, supplier); } - if (properties == null) { - properties = new Hashtable<>(); - } - properties.putIfAbsent(XY_DIMENSIONS_KEY, gridDimensions); properties.putIfAbsent(GRID_GEOMETRY_KEY, (supplier != null) ? new DeferredProperty(supplier) : imageGeometry); - properties.putIfAbsent(SAMPLE_DIMENSIONS_KEY, bands); if (wr != null) { return new WritableTiledImage(properties, cm, width, height, 0, 0, wr); } else { return new TiledImage(properties, cm, width, height, 0, 0, raster); } } - - /** - * A {@link BufferedImage} which will compute the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY} - * property when first needed. We use this class even when the property value is known in advance because it - * has the desired side-effect of not letting {@link #getSubimage(int, int, int, int)} inherit that property. - * The use of a {@link BufferedImage} subclass is desired because Java2D rendering pipeline has optimizations - * in the form {@code if (image instanceof BufferedImage)}. - */ - private static final class Untiled extends ObservableImage { - /** - * The value associated to the {@value org.apache.sis.image.PlanarImage#XY_DIMENSIONS_KEY} key. - */ - private final int[] gridDimensions; - - /** - * The value associated to the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY} key, - * or {@code null} if not yet computed. - */ - private GridGeometry geometry; - - /** - * The object to use for computing {@link #geometry}, or {@code null} if not needed. - * This field is cleared after {@link #geometry} has been computed. - */ - private SliceGeometry supplier; - - /** - * The value associated to the {@value org.apache.sis.image.PlanarImage#SAMPLE_DIMENSIONS_KEY} key. - */ - private final SampleDimension[] bands; - - /** - * Creates a new buffered image wrapping the given raster. - */ - @SuppressWarnings("UseOfObsoleteCollectionType") - Untiled(final ColorModel colors, final WritableRaster raster, final Hashtable<?,?> properties, - final int[] gridDimensions, final GridGeometry geometry, final SliceGeometry supplier, final SampleDimension[] bands) - { - super(colors, raster, false, properties); - this.gridDimensions = gridDimensions; - this.geometry = geometry; - this.supplier = supplier; - this.bands = bands; - } - - /** - * Returns the names of properties that this image can provide. - */ - @Override - public String[] getPropertyNames() { - return ArraysExt.concatenate(super.getPropertyNames(), new String[] { - XY_DIMENSIONS_KEY, - GRID_GEOMETRY_KEY, - SAMPLE_DIMENSIONS_KEY}); - } - - /** - * Returns the property associated to the given key. - * If the key is {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}, - * then the {@link GridGeometry} will be computed when first needed. - * - * @throws ImagingOpException if the property value cannot be computed. - */ - @Override - public Object getProperty(final String key) { - switch (key) { - default: return super.getProperty(key); - case SAMPLE_DIMENSIONS_KEY: return bands.clone(); - case XY_DIMENSIONS_KEY: return gridDimensions.clone(); - case GRID_GEOMETRY_KEY: { - synchronized (this) { - if (geometry == null) { - final SliceGeometry s = supplier; - if (s != null) { - supplier = null; // Let GC do its work. - geometry = s.apply(this); - } - } - return geometry; - } - } - } - } - } } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandSelectImage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandSelectImage.java index 2280fdfc8a..ffc1441ef0 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandSelectImage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandSelectImage.java @@ -33,7 +33,7 @@ import org.apache.sis.util.ArgumentChecks; import org.apache.sis.image.internal.shared.ImageUtilities; import org.apache.sis.image.internal.shared.TileOpExecutor; import org.apache.sis.image.internal.shared.ColorModelFactory; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; /** @@ -154,7 +154,7 @@ class BandSelectImage extends SourceAlignedImage { properties.put(key, value); } } - image = new ObservableImage(cm, + image = new WritableUntiledImage(cm, bi.getRaster().createWritableChild(0, 0, bi.getWidth(), bi.getHeight(), 0, 0, bands), bi.isAlphaPremultiplied(), properties); } else if (source instanceof WritableRenderedImage) { diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritableComputedImage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritableComputedImage.java index b27a9e6237..a689c83d00 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritableComputedImage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritableComputedImage.java @@ -22,7 +22,7 @@ import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.WritableRenderedImage; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; /** @@ -104,7 +104,7 @@ abstract class WritableComputedImage extends ComputedImage { * @param observer the observer to notify. */ public synchronized void addTileObserver(final TileObserver observer) { - observers = ObservableImage.addTileObserver(observers, observer); + observers = WritableUntiledImage.addTileObserver(observers, observer); } /** @@ -115,7 +115,7 @@ abstract class WritableComputedImage extends ComputedImage { * @param observer the observer to stop notifying. */ public synchronized void removeTileObserver(final TileObserver observer) { - observers = ObservableImage.removeTileObserver(observers, observer); + observers = WritableUntiledImage.removeTileObserver(observers, observer); } /** @@ -129,7 +129,7 @@ abstract class WritableComputedImage extends ComputedImage { protected boolean markTileWritable(final int tileX, final int tileY, final boolean writing) { final boolean notify = super.markTileWritable(tileX, tileY, writing); if (notify && this instanceof WritableRenderedImage) { - ObservableImage.fireTileUpdate(observers, (WritableRenderedImage) this, tileX, tileY, writing); + WritableUntiledImage.fireTileUpdate(observers, (WritableRenderedImage) this, tileX, tileY, writing); } return notify; } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java index 36b7481e2b..e52c1a543b 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java @@ -46,7 +46,7 @@ import static org.apache.sis.util.internal.shared.Numerics.COMPARISON_THRESHOLD; /** - * Utility methods related to images and their color model or sample model. + * Utility methods related to images, their tiles, their color model and their 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). * diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java index 59592dd328..230e8ad398 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java @@ -90,14 +90,14 @@ public final class RasterFactory { case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_USHORT: { if (numComponents == 1 && ColorModelFactory.isStandardRange(dataType, minimum, maximum)) { - return new ObservableImage(width, height, (dataType == DataBuffer.TYPE_BYTE) + return new WritableUntiledImage(width, height, (dataType == DataBuffer.TYPE_BYTE) ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_USHORT_GRAY); } break; } } final ColorModel cm = ColorModelFactory.createGrayScale(dataType, numComponents, visibleBand, minimum, maximum); - return new ObservableImage(cm, cm.createCompatibleWritableRaster(width, height), false, null); + return new WritableUntiledImage(cm, cm.createCompatibleWritableRaster(width, height), false, null); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java index 50e2967429..d2f87d2c12 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java @@ -51,7 +51,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm * Values are counter of calls to {@link #getWritableTile(int, int)}. * All accesses to this map shall be synchronized on the map instance. */ - private final Map<Point,Integer> writables; + private final Map<Point, Integer> writables; /** * Creates a new tiled image. @@ -83,7 +83,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm */ @Override public synchronized void addTileObserver(final TileObserver observer) { - observers = ObservableImage.addTileObserver(observers, observer); + observers = WritableUntiledImage.addTileObserver(observers, observer); } /** @@ -95,7 +95,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm */ @Override public synchronized void removeTileObserver(final TileObserver observer) { - observers = ObservableImage.removeTileObserver(observers, observer); + observers = WritableUntiledImage.removeTileObserver(observers, observer); } /** @@ -115,7 +115,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm count = writables.merge(key, 1, (old, one) -> old + 1); } if (count <= 1) { - ObservableImage.fireTileUpdate(observers, this, tileX, tileY, true); + WritableUntiledImage.fireTileUpdate(observers, this, tileX, tileY, true); } return tile; } @@ -144,7 +144,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm throw new IllegalArgumentException(Resources.format(Resources.Keys.TileNotWritable_2, tileX, tileY)); } if (close) { - ObservableImage.fireTileUpdate(observers, this, tileX, tileY, false); + WritableUntiledImage.fireTileUpdate(observers, this, tileX, tileY, false); } } @@ -157,7 +157,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm */ @Override public boolean isTileWritable(final int tileX, final int tileY) { - final Point key = new Point(tileX, tileY); + final var key = new Point(tileX, tileY); synchronized (writables) { return writables.containsKey(key); } @@ -207,7 +207,7 @@ public class WritableTiledImage extends TiledImage implements WritableRenderedIm final Rectangle bounds = data.getBounds(); ImageUtilities.clipBounds(this, bounds); if (!bounds.isEmpty()) { - final TileOpExecutor op = new TileOpExecutor(this, bounds) { + final var op = new TileOpExecutor(this, bounds) { @Override protected void writeTo(final WritableRaster target) { target.setRect(data); } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ObservableImage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java similarity index 66% rename from endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ObservableImage.java rename to endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java index 13192e2b3f..77a738ec84 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ObservableImage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java @@ -18,30 +18,46 @@ package org.apache.sis.image.internal.shared; import java.util.Arrays; import java.util.Hashtable; +import java.util.function.Function; import java.awt.Point; import java.awt.image.TileObserver; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; +import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.WritableRenderedImage; -import org.apache.sis.feature.internal.Resources; +import java.awt.image.ImagingOpException; import org.apache.sis.util.ArraysExt; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.feature.internal.Resources; +import static org.apache.sis.image.PlanarImage.GRID_GEOMETRY_KEY; /** - * A buffered image which can notify tile observers when tile are acquired for write operations. - * Provides also helper methods for {@link WritableRenderedImage} implementations. + * The buffered image used by Apache <abbr>SIS</abbr> for untiled images. + * This class is preferred to {@link TiledImage} in the untiled case because + * Java2D has performance optimizations for instances of {@link BufferedImage}. + * This class is also preferred to instances of the exact {@link BufferedImage} + * class for the following reasons: + * + * <p>First, this class can notify tile observers when tiles are acquired for write operations. + * We cannot prevent {@link BufferedImage} to implement {@link WritableRenderedImage}, but we can + * increase the chances that Apache <abbr>SIS</abbr> is notified about pixel data modifications. + * For example, images given to {@link org.apache.sis.coverage.grid.GridCoverage2D} constructor + * are often used as sources of {@link org.apache.sis.image.ImageProcessor} operations, + * which listen to tile changes in order to flush the cache of invalidated tiles.</p> + * + * <p>Second, this class can compute the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY} + * property when first needed. We use this class even when the property value is known in advance + * because it has the desired side-effect of not letting {@link #getSubimage(int, int, int, int)} + * inherit that property.</p> * - * <p>This class should be used in preference to {@link BufferedImage} when the image may be the - * source of {@link org.apache.sis.image.ImageProcessor} operations. It is the case In particular - * when this image is given to {@link org.apache.sis.coverage.grid.GridCoverage2D} constructor. - * We cannot prevent {@link BufferedImage} to implement {@link WritableRenderedImage}, but we - * can give a change to Apache SIS to be notified about modifications to pixel data.</p> + * <p>This class provides also static helper methods for {@link WritableRenderedImage} implementations.</p> * * @author Martin Desruisseaux (Geomatys) */ -public class ObservableImage extends BufferedImage { +public final class WritableUntiledImage extends BufferedImage { /** * The observers, or {@code null} if none. This is a copy-on-write array: * values are never modified after construction (new arrays are created). @@ -56,10 +72,20 @@ public class ObservableImage extends BufferedImage { /** * Number of times that the tile has been acquired for writing and not yet released. - * Write operations on this field should be in synchronized blocks. + * Write operations on this field should be done inside synchronized blocks. */ private volatile int writeCount; + /** + * The value associated to the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY} key. + * May be a {@code Function<RenderedImage, GridGeometry} if the grid geometry is computed when + * first requested. This is {@code null} if there is no such property. + * + * This property is stored as a {@code WritableUntiledImage} field rather than a {@code Hashtable} + * entry for preventing inheritance by {@link #getSubimage(int, int, int, int)}. + */ + private Object gridGeometry; + /** * Creates an image of the specified type. * @@ -67,20 +93,92 @@ public class ObservableImage extends BufferedImage { * @param height image height. * @param type one of {@code TYPE_*} constants. */ - public ObservableImage(int width, int height, int type) { + public WritableUntiledImage(int width, int height, int type) { super(width, height, type); } /** * Creates an image using the specified raster. * - * @param colors color model of the new image. - * @param raster the singleton raster for the image data. - * @param isRasterPremultiplied whether data in the raster has been premultiplied with alpha. - * @param properties image properties as ({@code String}, {@code Object}) entries. + * @param colors color model of the new image. + * @param raster the singleton raster for the image data. + * @param premultiplied whether data in the raster has been premultiplied with alpha. + * @param properties image properties as ({@code String}, {@code Object}) entries. + */ + @SuppressWarnings("UseOfObsoleteCollectionType") + public WritableUntiledImage(final ColorModel colors, + final WritableRaster raster, + final boolean premultiplied, + final Hashtable<?,?> properties) + { + super(colors, raster, premultiplied, properties); + } + + /** + * Sets the value associated to the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY} key. + * If the grid geometry is known in advance, it will be used. Otherwise the grid geometry will be + * computed when first requested using the given supplier. + * + * @param ifKnown the grid geometry, or {@code null} if no known in advance. + * @param supplier the function to execute when first needed, or {@code null} if none. + * @return {@code this} for method call chaining. + */ + public WritableUntiledImage setGridGeometry(final GridGeometry ifKnown, final Function<RenderedImage, GridGeometry> supplier) { + gridGeometry = (ifKnown != null) ? ifKnown : supplier; + return this; + } + + /** + * Returns the names of properties that this image can provide. + */ + @Override + public String[] getPropertyNames() { + String[] names = super.getPropertyNames(); // May be null. + if (gridGeometry != null) { + if (names == null) { + names = new String[] {GRID_GEOMETRY_KEY}; + } else { + names = ArraysExt.append(names, GRID_GEOMETRY_KEY); + } + } + return names; + } + + /** + * Returns the property associated to the given key. + * If the key is {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}, + * then the {@link GridGeometry} will be computed when first needed. + * + * @param name name of the property to get. + * @return property value associated to the given name, or {@link #UndefinedProperty} if none. + * @throws ImagingOpException if the property value cannot be computed. */ - public ObservableImage(ColorModel colors, WritableRaster raster, boolean isRasterPremultiplied, Hashtable<?,?> properties) { - super(colors, raster, isRasterPremultiplied, properties); + @Override + @SuppressWarnings("unchecked") + public Object getProperty(final String name) { + if (GRID_GEOMETRY_KEY.equals(name)) { + synchronized (this) { + if (gridGeometry != null) { + if (gridGeometry instanceof GridGeometry) { + return (GridGeometry) gridGeometry; + } + // `ClassCastException` should never occur here. + return gridGeometry = ((Function<RenderedImage, GridGeometry>) gridGeometry).apply(this); + } + } + } + return super.getProperty(name); + } + + /** + * Adds an observer to be notified when a tile is checked out for writing. + * If the observer is already present, it will receive multiple notifications. + * + * @param observer the observer to notify. + */ + @Override + public synchronized void addTileObserver(final TileObserver observer) { + observers = addTileObserver(observers, observer); } /** @@ -103,6 +201,18 @@ public class ObservableImage extends BufferedImage { return observers; } + /** + * Removes an observer from the list of observers notified when a tile is checked out for writing. + * If the observer was not registered, nothing happens. If the observer was registered for multiple + * notifications, it will now be registered for one fewer. + * + * @param observer the observer to stop notifying. + */ + @Override + public synchronized void removeTileObserver(final TileObserver observer) { + observers = removeTileObserver(observers, observer); + } + /** * Returns a new array with the specified observer removed from the specified array of observers. * If the observer was not registered, nothing happens and the given array is returned as-is. @@ -123,6 +233,19 @@ public class ObservableImage extends BufferedImage { return observers; } + /** + * Notifies all listeners that the specified tile has been checked out for writing or has been released. + * The notifications are sent only if the given {@code count} is zero. + * + * @param count value of {@link #writeCount} before increment or after decrement. + * @param willBeWritable if {@code true}, the tile will be grabbed for writing; otherwise it is being released. + */ + private void fireTileUpdate(final int count, final boolean willBeWritable) { + if (count == 0) { + fireTileUpdate(observers, this, 0, 0, willBeWritable); + } + } + /** * Notifies all listeners that the specified tile has been checked out for writing or has been released. * @@ -142,42 +265,6 @@ public class ObservableImage extends BufferedImage { } } - /** - * Adds an observer to be notified when a tile is checked out for writing. - * If the observer is already present, it will receive multiple notifications. - * - * @param observer the observer to notify. - */ - @Override - public synchronized void addTileObserver(final TileObserver observer) { - observers = addTileObserver(observers, observer); - } - - /** - * Removes an observer from the list of observers notified when a tile is checked out for writing. - * If the observer was not registered, nothing happens. If the observer was registered for multiple - * notifications, it will now be registered for one fewer. - * - * @param observer the observer to stop notifying. - */ - @Override - public synchronized void removeTileObserver(final TileObserver observer) { - observers = removeTileObserver(observers, observer); - } - - /** - * Notifies all listeners that the specified tile has been checked out for writing or has been released. - * The notifications are sent only if the given {@code count} is zero. - * - * @param count value of {@link #writeCount} before increment or after decrement. - * @param willBeWritable if {@code true}, the tile will be grabbed for writing; otherwise it is being released. - */ - private void fireTileUpdate(final int count, final boolean willBeWritable) { - if (count == 0) { - fireTileUpdate(observers, this, 0, 0, willBeWritable); - } - } - /** * Checks out a tile for writing. If the same tile is checked out many times * before to be released, only the first checkout is notified to listeners. diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/RasterReader.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/RasterReader.java index 0ef89b2d3d..ff53cdda2b 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/RasterReader.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/RasterReader.java @@ -46,7 +46,7 @@ import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.image.internal.shared.ColorModelBuilder; import org.apache.sis.image.internal.shared.ColorModelFactory; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.internal.shared.AffineTransform2D; import org.apache.sis.io.stream.ChannelDataInput; @@ -336,7 +336,7 @@ public final class RasterReader extends RasterFormat { } cm = ColorModelFactory.createGrayScale(dataType, numBands, visibleBand, minimum, maximum); } - return new ObservableImage(cm, raster, false, null); + return new WritableUntiledImage(cm, raster, false, null); } /** diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java index 2edabbc94b..8a814d4bef 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java @@ -45,7 +45,7 @@ import org.apache.sis.storage.base.PRJDataStore; import org.apache.sis.storage.metadata.MetadataBuilder; import org.apache.sis.image.internal.shared.ColorModelFactory; import org.apache.sis.image.internal.shared.ColorModelBuilder; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; import org.apache.sis.coverage.internal.shared.RangeArgument; import org.apache.sis.util.CharSequences; import org.apache.sis.util.ArraysExt; @@ -464,7 +464,7 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource cm = ColorModelFactory.createGrayScale(data.getSampleModel(), VISIBLE_BAND, band.getSampleRange().orElse(null)); } } - return new GridCoverage2D(domain, Arrays.asList(bands), new ObservableImage(cm, data, false, properties)); + return new GridCoverage2D(domain, Arrays.asList(bands), new WritableUntiledImage(cm, data, false, properties)); } /** diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/image/internal/BufferedImages.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/image/internal/BufferedImages.java index 7df7bfcf6d..06d42077e4 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/image/internal/BufferedImages.java +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/image/internal/BufferedImages.java @@ -42,7 +42,7 @@ import org.apache.sis.image.PixelIterator; import org.apache.sis.image.PlanarImage; import org.apache.sis.image.WritablePixelIterator; import org.apache.sis.image.internal.shared.ColorModelFactory; -import org.apache.sis.image.internal.shared.ObservableImage; +import org.apache.sis.image.internal.shared.WritableUntiledImage; import org.apache.sis.image.internal.shared.FillValues; import org.apache.sis.storage.util.TriFunction; import org.apache.sis.util.ArgumentChecks; @@ -97,7 +97,7 @@ public class BufferedImages { if (cm == null) { cm = ColorModelFactory.createGrayScale(dataType, nbBand, 0, 0, 1); } - final BufferedImage resultImage = new ObservableImage(cm, raster, cm.isAlphaPremultiplied(), null); + final BufferedImage resultImage = new WritableUntiledImage(cm, raster, cm.isAlphaPremultiplied(), null); return resultImage; } else { //we need to create a new image @@ -126,7 +126,7 @@ public class BufferedImages { //create a temporary fallback colormodel which will always work //extract grayscale min/max from sample dimension final ColorModel graycm = ColorModelFactory.createGrayScale(dataType, nbBand, 0, 0, 1); - return new ObservableImage(graycm, raster, false, null); + return new WritableUntiledImage(graycm, raster, false, null); } public static WritableRaster createRaster(int width, int height, int nbBand, int dataType, Point upperLeft) throws IllegalArgumentException{
