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: Writing in ConvertedGridCoverage can now retro-propagate the changes to original WritableRenderedImage.
Date Wed, 08 Jan 2020 18:59:10 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 4322a73  Writing in ConvertedGridCoverage can now retro-propagate the changes to
original WritableRenderedImage.
4322a73 is described below

commit 4322a732dd359d5b582e72c3bba192bf89e758c5
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Jan 8 19:58:40 2020 +0100

    Writing in ConvertedGridCoverage can now retro-propagate the changes to original WritableRenderedImage.
---
 .../java/org/apache/sis/image/ComputedImage.java   |   4 +-
 .../java/org/apache/sis/image/PlanarImage.java     |  23 ++--
 .../coverage/j2d/BandedSampleConverter.java        |  91 ++++++++++++-
 .../sis/internal/coverage/j2d/ImageUtilities.java  |  23 ++++
 .../sis/internal/coverage/j2d/TileOpExecutor.java  | 146 +++++++++++++++++++++
 .../sis/internal/coverage/j2d/WriteSupport.java    | 101 ++++++++++++++
 .../org/apache/sis/internal/feature/Resources.java |   5 +
 .../sis/internal/feature/Resources.properties      |   1 +
 .../sis/internal/feature/Resources_fr.properties   |   1 +
 .../sis/coverage/grid/GridCoverage2DTest.java      |   6 +-
 .../internal/coverage/j2d/ImageUtilitiesTest.java  |  24 ++++
 11 files changed, 401 insertions(+), 24 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
index 62bcd15..f99775d 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
@@ -522,8 +522,8 @@ public abstract class ComputedImage extends PlanarImage {
      *     }
      * }
      *
-     * @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  tileX    the <var>x</var> index of the tile to acquire or release.
+     * @param  tileY    the <var>y</var> index of the tile to acquire or release.
      * @param  writing  {@code true} for acquiring the tile, or {@code false} for releasing
it.
      * @return {@code true} if the tile goes from having no writers to having one writer
      *         (may happen if {@code writing} is {@code true}), or goes from having one
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 6b0a741..02b4c6b 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
@@ -31,6 +31,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 
 
@@ -341,14 +342,6 @@ public abstract class PlanarImage implements RenderedImage {
      * @param  raster  the raster to hold a copy of this image, or {@code null}.
      */
     private void copyData(final Rectangle aoi, final WritableRaster raster) {
-        final int  tileWidth       = getTileWidth();
-        final int  tileHeight      = getTileHeight();
-        final long tileGridXOffset = getTileGridXOffset();          // We want 64 bits arithmetic
in operations below.
-        final long tileGridYOffset = getTileGridYOffset();
-        final int  minTileX = Math.toIntExact(Math.floorDiv(aoi.x                     - tileGridXOffset,
tileWidth));
-        final int  minTileY = Math.toIntExact(Math.floorDiv(aoi.y                     - tileGridYOffset,
tileHeight));
-        final int  maxTileX = Math.toIntExact(Math.floorDiv(aoi.x + (aoi.width  - 1L) - tileGridXOffset,
tileWidth));
-        final int  maxTileY = Math.toIntExact(Math.floorDiv(aoi.y + (aoi.height - 1L) - tileGridYOffset,
tileHeight));
         /*
          * Iterate over all tiles that interesect the area of interest. For each tile,
          * copy a few rows in a temporary buffer, then copy that buffer to destination.
@@ -356,11 +349,12 @@ public abstract class PlanarImage implements RenderedImage {
          * Note that `tb` should never be empty since we restrict iteration to the tiles
          * that intersect the given area of interest.
          */
-        Object buffer = null;
-        int bufferCapacity = 0;
-        for (int ty = minTileY; ty <= maxTileY; ty++) {
-            for (int tx = minTileX; tx <= maxTileX; tx++) {
-                final Raster tile = getTile(tx, ty);
+        final TileOpExecutor executor = new TileOpExecutor(this, aoi) {
+            /** For copying data using data type specified by raster. */ private Object buffer;
+            /** For detecting if {@link #buffer} size is sufficient.  */ private int bufferCapacity;
+
+            /** Invoked for each tile to copy to target raster. */
+            @Override protected void readFrom(final Raster tile) {
                 final Rectangle tb = aoi.intersection(tile.getBounds());        // Bounds
of transfer buffer.
                 final int afterLastRow = ImageUtilities.prepareTransferRegion(tb, tile.getTransferType());
                 final int transferCapacity = tb.width * tb.height;
@@ -375,7 +369,8 @@ public abstract class PlanarImage implements RenderedImage {
                     tb.y += height;
                 }
             }
-        }
+        };
+        executor.readFrom(this);
     }
 
     /**
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 de3ddaa..582262a 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
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.coverage.j2d;
 
 import java.awt.Dimension;
+import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
 import java.awt.image.RenderedImage;
@@ -76,8 +77,8 @@ public class BandedSampleConverter extends ComputedImage {
      * @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)
+    private BandedSampleConverter(final RenderedImage source,  final BandedSampleModel sampleModel,
+                                  final ColorModel colorModel, final MathTransform1D[] converters)
     {
         super(sampleModel, source);
         this.colorModel = colorModel;
@@ -226,6 +227,7 @@ public class BandedSampleConverter extends ComputedImage {
 
     /**
      * A {@code BandedSampleConverter} capable to retro-propagate the changes to the source
coverage.
+     * This class contains the inverse of all {@link MathTransform1D} given to the parent
class.
      */
     private static final class Writable extends BandedSampleConverter implements WritableRenderedImage
{
         /**
@@ -234,6 +236,17 @@ public class BandedSampleConverter extends ComputedImage {
         private final MathTransform1D[] inverses;
 
         /**
+         * 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 #markTileWritable(int, int, boolean)}. Since this is a copy-on-write array,
+         * it is okay to omit synchronization for that method but we still need the memory
effect.
+         */
+        @SuppressWarnings("VolatileArrayField")
+        private volatile TileObserver[] observers;
+
+        /**
          * Creates a new writable image which will compute values using the given converters.
          */
         Writable(final WritableRenderedImage source,  final BandedSampleModel sampleModel,
@@ -243,26 +256,92 @@ public class BandedSampleConverter extends ComputedImage {
             this.inverses = inverses;
         }
 
+        /**
+         * 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 void addTileObserver(final TileObserver observer) {
+        public synchronized void removeTileObserver(final TileObserver observer) {
+            observers = WriteSupport.removeTileObserver(observers, observer);
         }
 
+        /**
+         * Sets or clears whether a tile is checked out for writing and notifies the listener
if needed.
+         *
+         * @param  tileX    the <var>x</var> index of the tile to acquire or
release.
+         * @param  tileY    the <var>y</var> index of the tile to acquire or
release.
+         * @param  writing  {@code true} for acquiring the tile, or {@code false} for releasing
it.
+         */
         @Override
-        public void removeTileObserver(final TileObserver observer) {
+        protected boolean markTileWritable(final int tileX, final int tileY, final boolean
writing) {
+            final boolean notify = super.markTileWritable(tileX, tileY, writing);
+            if (notify) {
+                WriteSupport.fireTileUpdate(observers, this, tileX, tileY, writing);
+            }
+            return notify;
         }
 
+        /**
+         * Checks out a tile for writing.
+         *
+         * @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) {
-            throw new UnsupportedOperationException();
+            final WritableRaster tile = (WritableRaster) getTile(tileX, tileY);
+            markTileWritable(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) {
+            if (markTileWritable(tileX, tileY, false)) {
+                setData(getTile(tileX, tileY));
+            }
         }
 
+        /**
+         * 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) {
-            throw new UnsupportedOperationException();
+            final Rectangle aoi = data.getBounds();
+            final WritableRenderedImage target = (WritableRenderedImage) getSource(0);
+            ImageUtilities.clipBounds(target, aoi);
+            final TileOpExecutor executor = new TileOpExecutor(target, aoi) {
+                @Override protected void writeTo(final WritableRaster target) throws TransformException
{
+                    Transferer.create(data, target).compute(inverses);
+                }
+            };
+            executor.writeTo(target);
         }
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
index 437b60b..6afdb3d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
@@ -30,6 +30,7 @@ import java.awt.image.SampleModel;
 import java.awt.image.SinglePixelPackedSampleModel;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Vocabulary;
@@ -79,6 +80,28 @@ public final class ImageUtilities extends Static {
     }
 
     /**
+     * Clips the given rectangle to the bounds of the given image.
+     *
+     * @param  image  the image.
+     * @param  aoi    a region of interest to clip to the image bounds.
+     */
+    public static void clipBounds(final RenderedImage image, final Rectangle aoi) {
+        int low = aoi.x;
+        int min = image.getMinX();
+        if (low < min) aoi.x = min;
+        aoi.width = Numerics.clamp(Math.min(
+                ((long) min) + image.getWidth(),
+                ((long) low) + aoi.width) - aoi.x);
+
+        low = aoi.y;
+        min = image.getMinY();
+        if (low < min) aoi.y = min;
+        aoi.height = Numerics.clamp(Math.min(
+                ((long) min) + image.getHeight(),
+                ((long) low) + aoi.height) - aoi.y);
+    }
+
+    /**
      * Returns the data type of the given raster.
      *
      * @param  raster  the raster for which to get the data type, or {@code null}.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
new file mode 100644
index 0000000..5fe1882
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.coverage.j2d;
+
+import java.awt.Rectangle;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.awt.image.WritableRenderedImage;
+import java.awt.image.ImagingOpException;
+import org.apache.sis.internal.feature.Resources;
+
+
+/**
+ * An action to execute on each tile of an image.
+ * Subclasses should override one of the following methods:
+ *
+ * <ul>
+ *   <li>{@link #readFrom(Raster)}</li>
+ *   <li>{@link #writeTo(WritableRaster)}</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public abstract class TileOpExecutor {
+    /**
+     * Minimum/maximum index of tiles to process, inclusive.
+     */
+    private final int minTileX, minTileY, maxTileX, maxTileY;
+
+    /**
+     * Creates a new operation for tiles in the specified region of the specified image.
+     * It is caller responsibility to ensure that {@code aoi} is contained in {@code image}
bounds.
+     *
+     * @param  image  the image from which tiles will be fetched.
+     * @param  aoi    region of interest.
+     */
+    protected TileOpExecutor(final RenderedImage image, final Rectangle aoi) {
+        final int  tileWidth       = image.getTileWidth();
+        final int  tileHeight      = image.getTileHeight();
+        final long tileGridXOffset = image.getTileGridXOffset();   // We want 64 bits arithmetic
in operations below.
+        final long tileGridYOffset = image.getTileGridYOffset();
+        minTileX = Math.toIntExact(Math.floorDiv(aoi.x                     - tileGridXOffset,
tileWidth));
+        minTileY = Math.toIntExact(Math.floorDiv(aoi.y                     - tileGridYOffset,
tileHeight));
+        maxTileX = Math.toIntExact(Math.floorDiv(aoi.x + (aoi.width  - 1L) - tileGridXOffset,
tileWidth));
+        maxTileY = Math.toIntExact(Math.floorDiv(aoi.y + (aoi.height - 1L) - tileGridYOffset,
tileHeight));
+    }
+
+    /**
+     * Executes the read operation on the given tile.
+     * The default implementation does nothing.
+     *
+     * @param  source  the tile to read.
+     */
+    protected void readFrom(Raster source) {
+    }
+
+    /**
+     * Executes the write operation on the given tile.
+     * The default implementation does nothing.
+     *
+     * @param  target  the tile where to write.
+     * @throws Exception if an error occurred while computing the values to write.
+     */
+    protected void writeTo(WritableRaster target) throws Exception {
+    }
+
+    /**
+     * Executes the read action on tiles of the specified source image.
+     * The given source should be the same than the image specified at construction time.
+     * Only tiles intersecting the area of interest will be processed.
+     *
+     * <p>If a tile processing throws an exception, then this method stops immediately;
+     * remaining tiles are not processed. This policy is suited to the cases where the
+     * caller will not return any result in case of error.</p>
+     *
+     * <p>Current implementation does not parallelize tile operations, because this
method is
+     * invoked in contexts where it should apply on exactly one tile most of the times.</p>
+     *
+     * @param  source  the image to read. Should be the image specified at construction time.
+     */
+    public final void readFrom(final RenderedImage source) {
+        for (int ty = minTileY; ty <= maxTileY; ty++) {
+            for (int tx = minTileX; tx <= maxTileX; tx++) {
+                readFrom(source.getTile(tx, ty));
+            }
+        }
+    }
+
+    /**
+     * Executes the write action on tiles of the specified target image.
+     * The given target should be the same than the image specified at construction time.
+     * Only tiles intersecting the area of interest will be processed.
+     *
+     * <p>If a tile processing throws an exception, then this method continues processing
other tiles
+     * and will rethrow the exception only after all tiles have been processed. This policy
is suited
+     * to the cases where the target image will continue to exist after this method call
and we want
+     * to have a relatively consistent state.</p>
+     *
+     * <p>Current implementation does not parallelize tile operations, because this
method is
+     * invoked in contexts where it should apply on exactly one tile most of the times.</p>
+     *
+     * @param  target  the image where to write. Should be the image specified at construction
time.
+     * @throws ImagingOpException if a {@link #writeTo(WritableRaster)} call threw an exception.
+     */
+    public final void writeTo(final WritableRenderedImage target) {
+        ImagingOpException error = null;
+        for (int ty = minTileY; ty <= maxTileY; ty++) {
+            for (int tx = minTileX; tx <= maxTileX; tx++) {
+                final WritableRaster tile = target.getWritableTile(tx, ty);
+                try {
+                    writeTo(tile);
+                } catch (Exception e) {
+                    if (error == null) {
+                        error = new ImagingOpException(Resources.format(Resources.Keys.CanNotUpdateTile_2,
tx, ty));
+                        error.initCause(e);
+                    } else {
+                        error.addSuppressed(e);
+                    }
+                } finally {
+                    target.releaseWritableTile(tx, ty);
+                }
+            }
+        }
+        if (error != null) {
+            throw error;
+        }
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WriteSupport.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WriteSupport.java
new file mode 100644
index 0000000..694c68a
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WriteSupport.java
@@ -0,0 +1,101 @@
+/*
+ * 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.Arrays;
+import java.awt.image.TileObserver;
+import java.awt.image.WritableRenderedImage;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * Helper methods for {@link WritableRenderedImage} implementations.
+ *
+ * <p>A future version of this class may extends {@code PlanarImage} or {@code ComputedImage}.
+ * We have not yet decided which case would be useful.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class WriteSupport {
+    /**
+     * Do not allow (for now) instantiation of this class.
+     */
+    private WriteSupport() {
+    }
+
+    /**
+     * Returns a new array with the specified observer added to the array of observers.
+     * If the observer is already present, it will receive multiple notifications.
+     *
+     * @param  observers  the array where to add the observer, or {@code null}.
+     * @param  observer   the observer to add. Null values are ignored.
+     * @return the updated array of observers.
+     */
+    static TileObserver[] addTileObserver(TileObserver[] observers, final TileObserver observer)
{
+        if (observer != null) {
+            if (observers == null) {
+                return new TileObserver[] {observer};
+            }
+            final int n = observers.length;
+            observers = Arrays.copyOf(observers, n+1);
+            observers[n] = observer;
+        }
+        return observers;
+    }
+
+    /**
+     * 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.
+     * If the observer was registered for multiple notifications, it will now be registered
for one fewer.
+     *
+     * @param  observers  the array where to remove the observer, or {@code null}.
+     * @param  observer   the observer to remove.
+     * @return the updated array of observers.
+     */
+    static TileObserver[] removeTileObserver(final TileObserver[] observers, final TileObserver
observer) {
+        if (observers != null) {
+            for (int i=observers.length; --i >= 0;) {
+                if (observers[i] == observer) {
+                    return ArraysExt.remove(observers, i, 1);
+                }
+            }
+        }
+        return observers;
+    }
+
+    /**
+     * Notifies all listeners that the specified tile has been checked out for writing.
+     *
+     * @param observers       the observers to notify, or {@code null} if none.
+     * @param image           the image that owns the tile.
+     * @param tileX           the <var>x</var> index of the tile that is being
updated.
+     * @param tileY           the <var>y</var> index of the tile that is being
updated.
+     * @param willBeWritable  if {@code true}, the tile will be grabbed for writing; otherwise
it is being released.
+     */
+    static void fireTileUpdate(final TileObserver[] observers, final WritableRenderedImage
image,
+                               final int tileX, final int tileY, final boolean willBeWritable)
+    {
+        if (observers != null) {
+            for (final TileObserver observer : observers) {
+                observer.tileUpdate(image, tileX, tileY, willBeWritable);
+            }
+        }
+    }
+}
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 a221314..4d3ed57 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
@@ -115,6 +115,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotSimplifyTransferFunction_1 = 26;
 
         /**
+         * Can not update tile ({0}, {1}).
+         */
+        public static final short CanNotUpdateTile_2 = 69;
+
+        /**
          * The two categories “{0}” and “{2}” have overlapping ranges: {1} and {3}
respectively.
          */
         public static final short CategoryRangeOverlap_4 = 27;
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 e611708..3a66b14 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
@@ -22,6 +22,7 @@
 AbstractFeatureType_1             = Feature type \u2018{0}\u2019 is abstract.
 CanNotAssignCharacteristics_1     = Can not assign characteristics to the \u201c{0}\u201d
property.
 CanNotComputeTile_2               = Can not compute tile ({0}, {1}).
+CanNotUpdateTile_2                = Can not update tile ({0}, {1}).
 CanNotCreateTwoDimensionalCRS_1   = Can not create a two-dimensional reference system from
the \u201c{0}\u201d system.
 CanNotEnumerateValuesInRange_1    = Can not enumerate values in the {0} range.
 CanNotInstantiateProperty_1       = Property \u201c{0}\u201d is not a type that can be instantiated.
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 972d7c8..5a6009b 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
@@ -27,6 +27,7 @@
 AbstractFeatureType_1             = Le type d\u2019entit\u00e9 \u2018{0}\u2019 est abstrait.
 CanNotAssignCharacteristics_1     = Ne peut pas assigner des caract\u00e9ristiques \u00e0
la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb.
 CanNotComputeTile_2               = Ne peut pas calculer la tuile ({0}, {1}).
+CanNotUpdateTile_2                = Ne peut pas mettre \u00e0 jour la tuile ({0}, {1}).
 CanNotCreateTwoDimensionalCRS_1   = Ne peut pas cr\u00e9er un syst\u00e8me de r\u00e9f\u00e9rence
bidimensionnel \u00e0 partir du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb.
 CanNotEnumerateValuesInRange_1    = Ne peut pas \u00e9num\u00e9rer les valeurs dans la plage
{0}.
 CanNotInstantiateProperty_1       = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est
pas d\u2019un type qui peut \u00eatre instanci\u00e9.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 83ccc1d..9d70119 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -21,6 +21,7 @@ import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
+import java.awt.image.WritableRenderedImage;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.operation.MathTransform1D;
@@ -106,7 +107,6 @@ public final strictfp class GridCoverage2DTest extends TestCase {
      * Tests writing values in {@link GridCoverage2D#forConvertedValues(boolean)}.
      */
     @Test
-    @org.junit.Ignore("BandedSampleConverter is not yet writable")
     public void testWriteConvertedValues() {
         GridCoverage coverage = createTestCoverage();
         coverage = coverage.forConvertedValues(true);
@@ -120,11 +120,13 @@ public final strictfp class GridCoverage2DTest extends TestCase {
          *
          *   70 = p * 0.5 + 100   →   (70-100)/0.5 = p   →   p = -60
          */
-        final WritableRaster raster = ((BufferedImage) coverage.render(null)).getRaster();
+        final WritableRenderedImage image = (WritableRenderedImage) coverage.render(null);
+        final WritableRaster raster = image.getWritableTile(0, 0);
         raster.setSample(0, 0, 0,  70);
         raster.setSample(1, 0, 0,   2.5);
         raster.setSample(0, 1, 0,  -8);
         raster.setSample(1, 1, 0, -90);
+        image.releaseWritableTile(0, 0);
         assertSamplesEqual(coverage.forConvertedValues(false), new double[][] {
             { -60, -195},
             {-216, -380}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
index 8d5f3e1..456d57f 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.coverage.j2d;
 
+import java.awt.Rectangle;
 import java.awt.image.ColorModel;
 import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
@@ -39,6 +40,29 @@ import static org.junit.Assert.*;
  */
 public final strictfp class ImageUtilitiesTest extends TestCase {
     /**
+     * Tests {@link ImageUtilities#getBounds(RenderedImage)} and {@link ImageUtilities#clipBounds(RenderedImage,
Rectangle)}.
+     */
+    @Test
+    public void testClipBounds() {
+        final BufferedImage image = new BufferedImage(5, 4, BufferedImage.TYPE_BYTE_GRAY);
+        final Rectangle bounds = ImageUtilities.getBounds(image);
+        assertEquals(0, bounds.x);
+        assertEquals(0, bounds.y);
+        assertEquals(5, bounds.width);
+        assertEquals(4, bounds.height);
+
+        bounds.x      = -4;
+        bounds.y      =  1;
+        bounds.width  =  8;
+        bounds.height =  7;
+        ImageUtilities.clipBounds(image, bounds);
+        assertEquals(0, bounds.x);
+        assertEquals(1, bounds.y);
+        assertEquals(4, bounds.width);
+        assertEquals(3, bounds.height);
+    }
+
+    /**
      * Tests {@link ImageUtilities#getDataTypeName(SampleModel)}.
      */
     @Test


Mime
View raw message