sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 07/10: Add a WritableTiledImage implementation.
Date Wed, 29 Jul 2020 16:19:00 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

commit 951c06a7c0f74c72dffd47d0974136a563a34a1d
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Jul 28 16:12:33 2020 +0200

    Add a WritableTiledImage implementation.
---
 .../sis/coverage/grid/GridCoverageBuilder.java     |  10 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    |  28 ++-
 .../sis/internal/coverage/j2d/TiledImage.java      |  23 ++-
 .../internal/coverage/j2d/WritableTiledImage.java  | 218 +++++++++++++++++++++
 .../org/apache/sis/internal/feature/Resources.java |   7 +-
 .../sis/internal/feature/Resources.properties      |   3 +-
 .../sis/internal/feature/Resources_fr.properties   |   3 +-
 7 files changed, 273 insertions(+), 19 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index 4133641..bef443c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -36,6 +36,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.coverage.j2d.Colorizer;
 import org.apache.sis.internal.coverage.j2d.TiledImage;
+import org.apache.sis.internal.coverage.j2d.WritableTiledImage;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
@@ -474,8 +475,13 @@ public class GridCoverageBuilder {
                  * Create an image from the raster. We favor BufferedImage instance when
possible,
                  * and fallback on TiledImage only if the BufferedImage can not be created.
                  */
-                if (colors != null && raster instanceof WritableRaster &&
(raster.getMinX() | raster.getMinY()) == 0) {
-                    image = new BufferedImage(colors, (WritableRaster) raster, false, properties);
+                if (raster instanceof WritableRaster) {
+                    final WritableRaster wr = (WritableRaster) raster;
+                    if (colors != null && (wr.getMinX() | wr.getMinY()) == 0) {
+                        image = new BufferedImage(colors, wr, false, properties);
+                    } else {
+                        image = new WritableTiledImage(properties, colors, wr.getWidth(),
wr.getHeight(), 0, 0, wr);
+                    }
                 } else {
                     image = new TiledImage(properties, colors, raster.getWidth(), raster.getHeight(),
0, 0, raster);
                 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index 231ff8f..68c6200 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -29,6 +29,7 @@ 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 org.opengis.util.FactoryException;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
@@ -38,6 +39,7 @@ import org.apache.sis.internal.coverage.j2d.Colorizer;
 import org.apache.sis.internal.coverage.j2d.DeferredProperty;
 import org.apache.sis.internal.coverage.j2d.RasterFactory;
 import org.apache.sis.internal.coverage.j2d.TiledImage;
+import org.apache.sis.internal.coverage.j2d.WritableTiledImage;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.util.NullArgumentException;
@@ -571,13 +573,14 @@ public class ImageRenderer {
     /**
      * Creates a raster with the data specified by the last call to a {@code setData(…)}
method.
      * The raster upper-left corner is located at the position given by {@link #getBounds()}.
+     * The returned raster is often an instance of {@link WritableRaster}, but read-only
rasters are also allowed.
      *
-     * @return the raster.
+     * @return the raster, usually (but not necessarily) an instance of {@link WritableRaster}.
      * @throws IllegalStateException if no {@code setData(…)} method has been invoked before
this method call.
-     * @throws RasterFormatException if a call to a {@link WritableRaster} factory method
failed.
+     * @throws RasterFormatException if a call to a {@link Raster} factory method failed.
      * @throws ArithmeticException if a property of the raster to construct exceeds the capacity
of 32 bits integers.
      */
-    public WritableRaster raster() {
+    public Raster raster() {
         if (buffer == null) {
             throw new IllegalStateException(Resources.format(Resources.Keys.UnspecifiedRasterData));
         }
@@ -614,14 +617,18 @@ public class ImageRenderer {
      * The two-dimensional {@linkplain #getImageGeometry(int) image geometry} is stored as
      * a property associated to the {@value org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}
key.
      *
+     * <p>The default implementation returns an instance of {@link java.awt.image.WritableRenderedImage}
+     * if the {@link #raster()} return value is an instance of {@link WritableRaster}, or
a read-only
+     * {@link RenderedImage} otherwise.</p>
+     *
      * @return the image.
      * @throws IllegalStateException if no {@code setData(…)} method has been invoked before
this method call.
-     * @throws RasterFormatException if a call to a {@link WritableRaster} factory method
failed.
+     * @throws RasterFormatException if a call to a {@link Raster} factory method failed.
      * @throws ArithmeticException if a property of the image to construct exceeds the capacity
of 32 bits integers.
      */
     @SuppressWarnings("UseOfObsoleteCollectionType")
     public RenderedImage image() {
-        final WritableRaster raster = raster();
+        final Raster raster = raster();
         final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
         final ColorModel colors;
         if (colorizer.initialize(bands[visibleBand]) || colorizer.initialize(raster.getSampleModel(),
visibleBand)) {
@@ -637,14 +644,19 @@ public class ImageRenderer {
                 supplier = new SliceGeometry(geometry, sliceExtent, gridDimensions, null);
             }
         }
-        if (colors != null && (imageX | imageY) == 0) {
-            return new Untiled(colors, raster, properties, imageGeometry, supplier);
+        final WritableRaster wr = (raster instanceof WritableRaster) ? (WritableRaster) raster
: null;
+        if (wr != null && colors != null && (imageX | imageY) == 0) {
+            return new Untiled(colors, wr, properties, imageGeometry, supplier);
         }
         if (properties == null) {
             properties = new Hashtable<>();
         }
         properties.putIfAbsent(GRID_GEOMETRY_KEY, (supplier != null) ? new DeferredProperty(supplier)
: imageGeometry);
-        return new TiledImage(properties, colors, width, height, 0, 0, raster);
+        if (wr != null) {
+            return new WritableTiledImage(properties, colors, width, height, 0, 0, wr);
+        } else {
+            return new TiledImage(properties, colors, width, height, 0, 0, raster);
+        }
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
index 1d40cc0..8cce878 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
@@ -25,6 +25,7 @@ import java.awt.image.SampleModel;
 import org.apache.sis.image.PlanarImage;
 import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -41,7 +42,7 @@ import org.apache.sis.util.ArgumentChecks;
  * @since   1.1
  * @module
  */
-public final class TiledImage extends PlanarImage {
+public class TiledImage extends PlanarImage {
     /**
      * The color model, or {@code null} if none.
      */
@@ -80,6 +81,7 @@ public final class TiledImage extends PlanarImage {
      * @param minTileX    minimum tile index in the X direction.
      * @param minTileY    minimum tile index in the Y direction.
      * @param tiles       the tiles. Must contains at least one element.
+     *                    This array is not cloned.
      */
     public TiledImage(final Map<String,Object> properties, final ColorModel colorModel,
                       final int width, final int height, final int minTileX, final int minTileY,
@@ -211,11 +213,20 @@ public final class TiledImage extends PlanarImage {
     public Raster getTile(int tileX, int tileY) {
         final int numXTiles = getNumXTiles();
         final int numYTiles = getNumYTiles();
-        if ((tileX -= minTileX) < 0 || tileX >= numXTiles ||
-            (tileY -= minTileY) < 0 || tileY >= numYTiles)
-        {
-            throw new IndexOutOfBoundsException();
-        }
+        tileX = verifyTileIndex("tileX", tileX, minTileX, numXTiles);
+        tileY = verifyTileIndex("tileY", tileY, minTileY, numYTiles);
         return tiles[tileX + tileY * numXTiles];
     }
+
+    /**
+     * Verifies that the given tile index is inside expected bounds, then returns is zero-based
value.
+     */
+    private static int verifyTileIndex(final String name, final int value, final int min,
final int count) {
+        final int r = value - min;
+        if (r >= 0 && r < count) {
+            return r;
+        }
+        throw new IndexOutOfBoundsException(Errors.format(
+                Errors.Keys.ValueOutOfRange_4, name, min, min + count - 1, value));
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WritableTiledImage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WritableTiledImage.java
new file mode 100644
index 0000000..16b5078
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WritableTiledImage.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.coverage.j2d;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.TileObserver;
+import java.awt.image.WritableRaster;
+import java.awt.image.WritableRenderedImage;
+import org.apache.sis.internal.feature.Resources;
+
+
+/**
+ * A writable version of {@link TiledImage}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class WritableTiledImage extends TiledImage implements WritableRenderedImage {
+    /**
+     * The observers, or {@code null} if none. This is a copy-on-write array:
+     * values are never modified after construction (new arrays are created).
+     *
+     * This field is declared volatile because it is read without synchronization by
+     * {@link #getWritableTile(int, int)} and {@link #releaseWritableTile(int, int)}.
+     * Since this is a copy-on-write array, it is okay to omit synchronization for
+     * those methods but we still need the memory effect.
+     */
+    @SuppressWarnings("VolatileArrayField")
+    private volatile TileObserver[] observers;
+
+    /**
+     * Indices of tiles taken for a write operation.
+     * 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;
+
+    /**
+     * Creates a new tiled image. The first tile in the given array must be the one located
at the minimal tile
+     * indices. All tiles must have the same size and the same sample model and must be sorted
in row-major fashion.
+     *
+     * @param properties  image properties, or {@code null} if none.
+     * @param colorModel  the color model, or {@code null} if none.
+     * @param width       number of pixels along X axis in the whole rendered image.
+     * @param height      number of pixels along Y axis in the whole rendered image.
+     * @param minTileX    minimum tile index in the X direction.
+     * @param minTileY    minimum tile index in the Y direction.
+     * @param tiles       the tiles. Must contains at least one element.
+     *                    This array is not cloned.
+     */
+    public WritableTiledImage(final Map<String,Object> properties, final ColorModel
colorModel,
+                              final int width, final int height, final int minTileX, final
int minTileY,
+                              final WritableRaster... tiles)
+    {
+        super(properties, colorModel, width, height, minTileX, minTileY, tiles);
+        writables = new LinkedHashMap<>();
+    }
+
+    /**
+     * 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 = WriteSupport.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 = WriteSupport.removeTileObserver(observers, observer);
+    }
+
+    /**
+     * 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.
+     *
+     * @param  tileX  the <var>x</var> index of the tile.
+     * @param  tileY  the <var>y</var> index of the tile.
+     * @return the specified tile as a writable tile.
+     */
+    @Override
+    public WritableRaster getWritableTile(final int tileX, final int tileY) {
+        final WritableRaster tile = (WritableRaster) super.getTile(tileX, tileY);
+        final Point key = new Point(tileX, tileY);
+        final Integer count;
+        synchronized (writables) {
+            count = writables.merge(key, 1, (old, one) -> old + 1);
+        }
+        if (count <= 1) {
+            WriteSupport.fireTileUpdate(observers, this, tileX, tileY, true);
+        }
+        return tile;
+    }
+
+    /**
+     * Relinquishes the right to write to a tile. If the tile goes from having one writer
to
+     * having no writers, the values are inverse converted and written in the original image.
+     * If the caller continues to write to the tile, the results are undefined.
+     *
+     * @param  tileX  the <var>x</var> index of the tile.
+     * @param  tileY  the <var>y</var> index of the tile.
+     */
+    @Override
+    public void releaseWritableTile(final int tileX, final int tileY) {
+        final Point key = new Point(tileX, tileY);
+        final Integer count;
+        final boolean close;
+        synchronized (writables) {
+            count = writables.computeIfPresent(key, (k, old) -> old - 1);
+            close = (count != null && count <= 0);
+            if (close) {
+                writables.remove(key);
+            }
+        }
+        if (count == null) {
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.TileNotWritable_2,
tileX, tileY));
+        }
+        if (close) {
+            WriteSupport.fireTileUpdate(observers, this, tileX, tileY, false);
+        }
+    }
+
+    /**
+     * Returns whether a tile is currently checked out for writing.
+     *
+     * @param  tileX  the <var>x</var> index of the tile.
+     * @param  tileY  the <var>y</var> index of the tile.
+     * @return {@code true} if specified tile is checked out for writing, {@code false} otherwise.
+     */
+    @Override
+    public boolean isTileWritable(final int tileX, final int tileY) {
+        final Point key = new Point(tileX, tileY);
+        synchronized (writables) {
+            return writables.containsKey(key);
+        }
+    }
+
+    /**
+     * Returns the indices of all tiles checked out for writing.
+     * Returns null if none are checked out.
+     *
+     * @return indices of tiles that are checked out for writing, or {@code null} if none.
+     */
+    @Override
+    public Point[] getWritableTileIndices() {
+        final Point[] indices;
+        synchronized (writables) {
+            final int n = writables.size();
+            if (n == 0) return null;
+            indices = writables.keySet().toArray(new Point[n]);
+        }
+        for (int i=0; i<indices.length; i++) {
+            indices[i] = new Point(indices[i]);
+        }
+        return indices;
+    }
+
+    /**
+     * Returns whether any tile is checked out for writing.
+     *
+     * @return {@code true} if any tiles are checked out for writing, or {@code false} otherwise.
+     */
+    @Override
+    public boolean hasTileWriters() {
+        synchronized (writables) {
+            return !writables.isEmpty();
+        }
+    }
+
+    /**
+     * Sets a region of the image to the contents of the given raster.
+     * The raster is assumed to be in the same coordinate space as this image.
+     * The operation is clipped to the bounds of this image.
+     *
+     * @param  data  the values to write in this image.
+     */
+    @Override
+    public void setData(final Raster data) {
+        final Rectangle bounds = data.getBounds();
+        ImageUtilities.clipBounds(this, bounds);
+        final TileOpExecutor op = new TileOpExecutor(this, bounds) {
+            @Override protected void writeTo(final WritableRaster target) {
+                target.setRect(data);
+            }
+        };
+        op.writeTo(this);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index bf46750..fa4a909 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -367,11 +367,16 @@ public final class Resources extends IndexedResourceBundle {
         public static final short PropertyNotFound_2 = 16;
 
         /**
-         * Tile ({0}, {1}) has the error flag set.
+         * Tile ({0}, {1}) is unavailable because of error in a previous calculation attempt.
          */
         public static final short TileErrorFlagSet_2 = 68;
 
         /**
+         * Tile ({0}, {1}) is not writable.
+         */
+        public static final short TileNotWritable_2 = 78;
+
+        /**
          * Too many qualitative categories.
          */
         public static final short TooManyQualitatives = 48;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
index 4f08663..1072738 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
@@ -78,7 +78,8 @@ OptionalLibraryNotFound_2         = The {0} optional library is not available.
G
 OutOfIteratorDomain_2             = The ({0,number}, {1,number}) pixel coordinate is outside
iterator domain.
 PropertyAlreadyExists_2           = Property \u201c{1}\u201d already exists in feature \u201c{0}\u201d.
 PropertyNotFound_2                = No property named \u201c{1}\u201d has been found in \u201c{0}\u201d
feature.
-TileErrorFlagSet_2                = Tile ({0}, {1}) has the error flag set.
+TileErrorFlagSet_2                = Tile ({0}, {1}) is unavailable because of error in a
previous calculation attempt.
+TileNotWritable_2                 = Tile ({0}, {1}) is not writable.
 TooManyQualitatives               = Too many qualitative categories.
 TransformDependsOnDimension_1     = Coordinate operation depends on grid dimension {0}.
 UnavailableGeometryLibrary_1      = The {0} geometry library is not available in current
runtime environment.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
index 63935a4..1f31415 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
@@ -84,7 +84,8 @@ OutOfIteratorDomain_2             = La coordonn\u00e9e pixel ({0,number},
{1,num
 PointOutsideCoverageDomain_1      = Le point ({0}) est en dehors du domaine de la couverture
de donn\u00e9es.
 PropertyAlreadyExists_2           = La propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb existe
d\u00e9j\u00e0 dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
 PropertyNotFound_2                = Aucune propri\u00e9t\u00e9 nomm\u00e9e \u00ab\u202f{1}\u202f\u00bb
n\u2019a \u00e9t\u00e9 trouv\u00e9e dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
-TileErrorFlagSet_2                = La tuile ({0}, {1}) est marqu\u00e9e comme ayant une
erreur.
+TileErrorFlagSet_2                = La tuile ({0}, {1}) est indisponible pour cause d\u2019erreur
lors d\u2019une tentative ant\u00e9rieure de calcul.
+TileNotWritable_2                 = La tuile ({0}, {1}) n\u2019est pas d\u00e9clar\u00e9e
en \u00e9criture.
 TooManyQualitatives               = Trop de cat\u00e9gories qualitatives.
 TransformDependsOnDimension_1     = L\u2019op\u00e9ration sur les coordonn\u00e9es d\u00e9pend
de la dimension {0} de la grille.
 UnavailableGeometryLibrary_1      = La biblioth\u00e8que de g\u00e9om\u00e9tries {0} n\u2019est
pas disponible dans l\u2019environnement d\u2019ex\u00e9cution actuel.


Mime
View raw message