sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Begin support of WritableRenderedImage in ComputedImage.
Date Mon, 06 Jan 2020 19:16:43 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 6380604  Begin support of WritableRenderedImage in ComputedImage.
6380604 is described below

commit 6380604aa43515d495b7b693139a1e8584b51ac6
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jan 6 20:15:59 2020 +0100

    Begin support of WritableRenderedImage in ComputedImage.
---
 .../sis/coverage/grid/ConvertedGridCoverage.java   |   2 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   2 +-
 .../java/org/apache/sis/image/ComputedImage.java   | 159 +++++++++++++++++----
 .../main/java/org/apache/sis/image/TileCache.java  |   8 ++
 .../coverage/j2d/BandedSampleConverter.java        | 112 ++++++++++++---
 .../coverage/j2d/BandedSampleConverterTest.java    |   2 +-
 6 files changed, 235 insertions(+), 50 deletions(-)

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 531052d..1a9450b 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
@@ -218,7 +218,7 @@ final class ConvertedGridCoverage extends GridCoverage {
          */
         if (image != null) {
             final ColorModel colorModel = createColorModel(VISIBLE_BAND, dataType);
-            image = new BandedSampleConverter(image, null, dataType, colorModel, converters);
+            image = BandedSampleConverter.create(image, null, dataType, colorModel, converters);
         }
         return image;
     }
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 4990ff1..f205ec6 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
@@ -148,7 +148,7 @@ public class GridCoverage2D extends GridCoverage {
         super(source.gridGeometry, range);
         final int dataType = ConvertedGridCoverage.getDataType(range, isConverted);
         final ColorModel colorModel = createColorModel(ConvertedGridCoverage.VISIBLE_BAND,
dataType);
-        data           = new BandedSampleConverter(source.data, null, dataType, colorModel,
converters);
+        data           = BandedSampleConverter.create(source.data, null, dataType, colorModel,
converters);
         gridToImageX   = source.gridToImageX;
         gridToImageY   = source.gridToImageY;
         xDimension     = source.xDimension;
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 c11eb71..88217b3 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
@@ -18,6 +18,8 @@ package org.apache.sis.image;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Vector;
 import java.awt.Insets;
@@ -124,26 +126,19 @@ public abstract class ComputedImage extends PlanarImage {
     public static final String SOURCE_PADDING_PROPERTY = "sourcePadding";
 
     /**
-     * Whether a tile in the cache is ready for use or needs to be recomputed
-     * because one if its sources changed its data.
+     * Whether a tile in the cache is ready for use or needs to be recomputed because one
if its sources
+     * changed its data. If the tile is checkout out for a write operation, the write operation
will have
+     * precedence over the dirty state.
      */
     private enum TileStatus {
-        /** The tile is ready for use. */
-        STORED,
+        /** The tile, if present, is ready for use. */
+        VALID,
 
         /** The tile needs to be recomputed because at least one source changed its data.
*/
         DIRTY,
 
         /** The tile has been checked out for a write operation. */
-        CHECKED,
-
-        /** The tile needs to be recomputed, but it is also checked for write operation by
someone else. */
-        CHECKED_AND_DIRTY;
-
-        /** Remapping function for calls to {@link Map#computeIfPresent(Object, java.util.function.BiFunction)}.
*/
-        static TileStatus dirty(final TileCache.Key key, TileStatus oldValue) {
-            return (oldValue == CHECKED) ? CHECKED_AND_DIRTY : DIRTY;
-        }
+        WRITABLE
     }
 
     /**
@@ -192,24 +187,46 @@ public abstract class ComputedImage extends PlanarImage {
         }
 
         /**
-         * Remembers that the given tile will need to be removed from the cache
-         * when the enclosing image will be garbage-collected.
+         * Sets the status of the specified tile, discarding any previous status.
+         */
+        final void setTileStatus(final TileCache.Key key, final TileStatus status) {
+            synchronized (cachedTiles) {
+                cachedTiles.put(key, status);
+            }
+        }
+
+        /**
+         * Returns the status of the tile at the specified indices.
+         * The main status of interest are:
+         * <ul>
+         *   <li>{@link TileStatus#DIRTY}    — if the tile needs to be recomputed.</li>
+         *   <li>{@link TileStatus#WRITABLE} — if the tile is currently checked out
for writing.</li>
+         * </ul>
          */
-        final void addTile(final TileCache.Key key) {
+        final TileStatus getTileStatus(final TileCache.Key key) {
             synchronized (cachedTiles) {
-                cachedTiles.put(key, TileStatus.STORED);
+                return cachedTiles.get(key);
             }
         }
 
         /**
-         * Returns {@code true} if the specified tile needs to be recomputed.
+         * Adds in the given list the indices of all tiles which are checked out for writing.
+         * If the given list is {@code null}, then this method stops the search at the first
+         * tile checked out.
+         *
+         * @param  indices  the list where to add indices, or {@code null} if none.
+         * @return whether at least one tile is checked out for writing.
          */
-        final boolean isDirty(final TileCache.Key key) {
-            final TileStatus status;
+        final boolean getWritableTileIndices(final List<Point> indices) {
             synchronized (cachedTiles) {
-                status = cachedTiles.get(key);
+                for (final Map.Entry<TileCache.Key, TileStatus> entry : cachedTiles.entrySet())
{
+                    if (entry.getValue() == TileStatus.WRITABLE) {
+                        if (indices == null) return true;
+                        indices.add(entry.getKey().indices());
+                    }
+                }
             }
-            return (status == TileStatus.DIRTY) || (status == TileStatus.CHECKED_AND_DIRTY);
+            return (indices != null) && !indices.isEmpty();
         }
 
         /**
@@ -224,7 +241,7 @@ public abstract class ComputedImage extends PlanarImage {
                 for (int tileY = minTileY; tileY <= maxTileY; tileY++) {
                     for (int tileX = minTileX; tileX <= maxTileX; tileX++) {
                         final TileCache.Key key = new TileCache.Key(this, tileX, tileY);
-                        cachedTiles.computeIfPresent(key, TileStatus::dirty);
+                        cachedTiles.replace(key, TileStatus.VALID, TileStatus.DIRTY);
                     }
                 }
             }
@@ -497,6 +514,13 @@ public abstract class ComputedImage extends PlanarImage {
      *       If an error occurred, an {@link ImagingOpException} is thrown.</li>
      * </ol>
      *
+     * <h4>Race conditions with write operations</h4>
+     * If this image implements the {@link WritableRenderedImage} interface, then a user
may have acquired
+     * the tile for a write operation outside the {@link #computeTile computeTile(…)} method.
In such case,
+     * there is no consistency guarantees on sample values: the tile returned by this method
may show data
+     * in an unspecified stage during the write operation. This situation may be detected
by checking if
+     * {@link #isTileWritable(int, int) isTileWritable(tileX, tileY)} returns {@code true}.
+     *
      * @param  tileX  the column index of the tile to get.
      * @param  tileY  the row index of the tile to get.
      * @return the tile at the given index (never null).
@@ -508,14 +532,14 @@ public abstract class ComputedImage extends PlanarImage {
         final TileCache.Key key = new TileCache.Key(reference, tileX, tileY);
         final Cache<TileCache.Key,Raster> cache = TileCache.GLOBAL;
         Raster tile = cache.peek(key);
-        if (tile == null || reference.isDirty(key)) {
+        if (tile == null || reference.getTileStatus(key) == TileStatus.DIRTY) {
             int min;
             ArgumentChecks.ensureBetween("tileX", (min = getMinTileX()), min + getNumXTiles()
- 1, tileX);
             ArgumentChecks.ensureBetween("tileY", (min = getMinTileY()), min + getNumYTiles()
- 1, tileY);
             final Cache.Handler<Raster> handler = cache.lock(key);
             try {
                 tile = handler.peek();
-                if (tile == null || reference.isDirty(key)) {
+                if (tile == null || reference.getTileStatus(key) == TileStatus.DIRTY) {
                     final WritableRaster previous = (tile instanceof WritableRaster) ? (WritableRaster)
tile : null;
                     Exception cause = null;
                     tile = null;
@@ -530,7 +554,7 @@ public abstract class ComputedImage extends PlanarImage {
                         throw (ImagingOpException) new ImagingOpException(Resources.format(
                                 Resources.Keys.CanNotComputeTile_2, tileX, tileY)).initCause(cause);
                     }
-                    reference.addTile(key);
+                    reference.setTileStatus(key, TileStatus.VALID);
                 }
             } finally {
                 handler.putAndUnlock(tile);     // Must be invoked even if an exception occurred.
@@ -580,6 +604,89 @@ public abstract class ComputedImage extends PlanarImage {
     }
 
     /**
+     * Returns whether any tile is checked out for writing.
+     * This method always returns {@code false} for read-only images, but may return {@code
true}
+     * if this {@code ComputedImage} is also a {@link WritableRenderedImage}.
+     *
+     * @return {@code true} if any tiles are checked out for writing; {@code false} otherwise.
+     *
+     * @see #markWritableTile(int, int, boolean)
+     * @see WritableRenderedImage#hasTileWriters()
+     */
+    public boolean hasTileWriters() {
+        return reference.getWritableTileIndices(null);
+    }
+
+    /**
+     * Returns whether a tile is currently checked out for writing.
+     * This method always returns {@code false} for read-only images, but may return {@code
true}
+     * if this {@code ComputedImage} is also a {@link WritableRenderedImage}.
+     *
+     * @param  tileX the X index of the tile.
+     * @param  tileY the Y index of the tile.
+     * @return {@code true} if specified tile is checked out for writing; {@code false} otherwise.
+     *
+     * @see #markWritableTile(int, int, boolean)
+     * @see WritableRenderedImage#isTileWritable(int, int)
+     */
+    public boolean isTileWritable(final int tileX, final int tileY) {
+        return reference.getTileStatus(new TileCache.Key(reference, tileX, tileY)) == TileStatus.WRITABLE;
+    }
+
+    /**
+     * Returns an array of Point objects indicating which tiles are checked out for writing,
or {@code null} if none.
+     * This method always returns {@code null} for read-only images, but may return a non-empty
array
+     * if this {@code ComputedImage} is also a {@link WritableRenderedImage}.
+     *
+     * @return an array containing the locations of tiles that are checked out for writing,
or {@code null} if none.
+     *
+     * @see #markWritableTile(int, int, boolean)
+     * @see WritableRenderedImage#getWritableTileIndices()
+     */
+    public Point[] getWritableTileIndices() {
+        final List<Point> indices = new ArrayList<>();
+        if (reference.getWritableTileIndices(indices)) {
+            return indices.toArray(new Point[indices.size()]);
+        }
+        return null;
+    }
+
+    /**
+     * Marks a tile as checkout out for writing. This method is provided for subclasses that
also implement
+     * the {@link WritableRenderedImage} interface. This method can be used as below:
+     *
+     * {@preformat java
+     *     class MyImage extends ComputedImage implements WritableRenderedImage {
+     *         // Constructor omitted for brevity.
+     *
+     *         &#64;Override
+     *         public WritableRaster getWritableTile(int tileX, int tileY) {
+     *             WritableRaster raster = ...;             // Get the writable tile here.
+     *             markWritableTile(tileX, tileY, true);
+     *             return raster;
+     *         }
+     *
+     *         &#64;Override
+     *         public void releaseWritableTile(int tileX, int tileY) {
+     *             markWritableTile(tileX, tileY, false);
+     *             // Release the raster here.
+     *         }
+     *     }
+     * }
+     *
+     * @param  tileX    the X index of the tile to acquire or release.
+     * @param  tileY    the Y index of the tile to acquire or release.
+     * @param  writing  {@code true} for acquiring the tile, or {@code false} for releasing
it.
+     *
+     * @see WritableRenderedImage#getWritableTile(int, int)
+     * @see WritableRenderedImage#releaseWritableTile(int, int)
+     */
+    protected void markWritableTile(final int tileX, final int tileY, final boolean writing)
{
+        final TileCache.Key key = new TileCache.Key(reference, tileX, tileY);
+        reference.setTileStatus(key, writing ? TileStatus.WRITABLE : TileStatus.VALID);
+    }
+
+    /**
      * Marks all tiles in the given range of indices as in need of being recomputed.
      * The tiles will not be recomputed immediately, but only on next invocation of
      * {@link #getTile(int, int) getTile(tileX, tileY)} if the {@code (tileX, tileY)} indices
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/TileCache.java b/core/sis-feature/src/main/java/org/apache/sis/image/TileCache.java
index 7562101..ce49d25 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/TileCache.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/TileCache.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.image;
 
+import java.awt.Point;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.lang.ref.Reference;
@@ -100,6 +101,13 @@ final class TileCache extends Cache<TileCache.Key, Raster> {
         }
 
         /**
+         * Returns the tile indices.
+         */
+        final Point indices() {
+            return new Point(tileX, tileY);
+        }
+
+        /**
          * Removes the raster associated to this key. This method is invoked
          * for all tiles in an image being disposed.
          */
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 a7cdca4..de3ddaa 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
@@ -20,13 +20,17 @@ import java.awt.Dimension;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
 import java.awt.image.RenderedImage;
+import java.awt.image.WritableRenderedImage;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.ColorModel;
+import java.awt.image.TileObserver;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.image.ComputedImage;
+import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.Workaround;
+import org.apache.sis.util.logging.Logging;
 
 
 /**
@@ -44,15 +48,16 @@ import org.apache.sis.util.Workaround;
  *   <li>Calculation is performed on {@code float} or {@code double} numbers.</li>
  * </ul>
  *
- * Subclasses may relax those restrictions at the cost of more complex {@link #computeTile(int,
int, WritableRaster)}
- * implementation. Those restrictions may also be relaxed in future versions of this class.
+ * If the given source is writable and the transform are invertible, then the {@code BandedSampleConverter}
+ * returned by the {@link #create create(…)} method will implement {@link WritableRenderedImage}
interface.
+ * In such case, writing converted values will cause the corresponding source values to be
updated too.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  * @since   1.1
  * @module
  */
-public final class BandedSampleConverter extends ComputedImage {
+public class BandedSampleConverter extends ComputedImage {
     /**
      * The transfer functions to apply on each band of the source image.
      */
@@ -64,6 +69,22 @@ public final class BandedSampleConverter extends ComputedImage {
     private final ColorModel colorModel;
 
     /**
+     * Creates a new image which will compute values using the given converters.
+     *
+     * @param  source       the image for which to convert sample values.
+     * @param  sampleModel  the sample model shared by all tiles in this image.
+     * @param  colorModel   the color model for from the expected range of values, or {@code
null}.
+     * @param  converters   the transfer functions to apply on each band of the source image.
+     */
+    BandedSampleConverter(final RenderedImage source,  final BandedSampleModel sampleModel,
+                          final ColorModel colorModel, final MathTransform1D[] converters)
+    {
+        super(sampleModel, source);
+        this.colorModel = colorModel;
+        this.converters = converters;
+    }
+
+    /**
      * Creates a new image of the given data type which will compute values using the given
converters.
      * The number of bands is the length of the {@code converters} array, which must be greater
than 0
      * and not greater than the number of bands in the source image.
@@ -73,31 +94,38 @@ public final class BandedSampleConverter extends ComputedImage {
      * @param  targetType  the type of this image resulting from conversion of given image.
      * @param  colorModel  the color model for from the expected range of values, or {@code
null}.
      * @param  converters  the transfer functions to apply on each band of the source image.
+     * @return the image which compute converted values from the given source.
      */
-    public BandedSampleConverter(final RenderedImage source, final ImageLayout layout,
-                                 final int targetType, final ColorModel colorModel,
-                                 final MathTransform1D... converters)
-    {
-        super(createSampleModel(targetType, converters.length, layout, source), source);
-        this.colorModel = colorModel;
-        this.converters = converters;
-    }
-
-    /**
-     * Returns the sample model to use for this image. This is a workaround for RFE #4093999
-     * ("Relax constraint on placement of this()/super() call in constructors").
-     */
-    @Workaround(library="JDK", version="1.8")
-    private static BandedSampleModel createSampleModel(final int targetType,
-            final int numBands, ImageLayout layout, final RenderedImage source)
+    public static BandedSampleConverter create(final RenderedImage source, ImageLayout layout,
+                                               final int targetType, final ColorModel colorModel,
+                                               final MathTransform1D... converters)
     {
         ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("converters", converters);
+        final int numBands = converters.length;
         ArgumentChecks.ensureSizeBetween("converters", 1, source.getSampleModel().getNumBands(),
numBands);
         if (layout == null) {
             layout = ImageLayout.DEFAULT;
         }
         final Dimension tile = layout.suggestTileSize(source);
-        return RasterFactory.unique(new BandedSampleModel(targetType, tile.width, tile.height,
numBands));
+        final BandedSampleModel sampleModel = RasterFactory.unique(
+                new BandedSampleModel(targetType, tile.width, tile.height, numBands));
+        /*
+         * If the source image is writable, then changes in the converted image may be retro-propagated
+         * to that source image. If we fail to compute the required inverse transforms, log
a notice at
+         * a low level because this is not a serious problem; writable BandedSampleConverter
is a plus
+         * but not a requirement.
+         */
+        if (source instanceof WritableRenderedImage) try {
+            final MathTransform1D[] inverses = new MathTransform1D[numBands];
+            for (int i=0; i<numBands; i++) {
+                inverses[i] = converters[i].inverse();
+            }
+            return new Writable((WritableRenderedImage) source, sampleModel, colorModel,
converters, inverses);
+        } catch (NoninvertibleTransformException e) {
+            Logging.recoverableException(Logging.getLogger(Modules.RASTER), BandedSampleConverter.class,
"create", e);
+        }
+        return new BandedSampleConverter(source, sampleModel, colorModel, converters);
     }
 
     /**
@@ -195,4 +223,46 @@ public final class BandedSampleConverter extends ComputedImage {
         Transferer.create(getSource(0), target).compute(converters);
         return target;
     }
+
+    /**
+     * A {@code BandedSampleConverter} capable to retro-propagate the changes to the source
coverage.
+     */
+    private static final class Writable extends BandedSampleConverter implements WritableRenderedImage
{
+        /**
+         * The converters for computing the source values from a converted value.
+         */
+        private final MathTransform1D[] inverses;
+
+        /**
+         * Creates a new writable image which will compute values using the given converters.
+         */
+        Writable(final WritableRenderedImage source,  final BandedSampleModel sampleModel,
+                 final ColorModel colorModel, final MathTransform1D[] converters, final MathTransform1D[]
inverses)
+        {
+            super(source, sampleModel, colorModel, converters);
+            this.inverses = inverses;
+        }
+
+        @Override
+        public void addTileObserver(final TileObserver observer) {
+        }
+
+        @Override
+        public void removeTileObserver(final TileObserver observer) {
+        }
+
+        @Override
+        public WritableRaster getWritableTile(final int tileX, final int tileY) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void releaseWritableTile(final int tileX, final int tileY) {
+        }
+
+        @Override
+        public void setData(final Raster data) {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverterTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverterTest.java
index f443450..95e3cf0 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverterTest.java
@@ -70,7 +70,7 @@ public final strictfp class BandedSampleConverterTest extends TestCase {
                 random.nextInt(20) - 10);       // minTileY
         source.validate();
         source.initializeAllTiles(0);
-        image = new BandedSampleConverter(source, null, targetType, null,
+        image = BandedSampleConverter.create(source, null, targetType, null,
                 (MathTransform1D) MathTransforms.linear(scale, 0));
     }
 


Mime
View raw message