You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/08 16:38:09 UTC

[sis] 03/03: Result of "band select" operation should be writable if the image is writable.

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 6c85b283c4009fcd0b658dd44de43984e3125e8a
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 8 16:53:23 2023 +0200

    Result of "band select" operation should be writable if the image is writable.
---
 .../java/org/apache/sis/image/BandSelectImage.java | 76 +++++++++++++++++++++-
 .../java/org/apache/sis/image/ImageProcessor.java  |  9 +++
 .../org/apache/sis/image/BandSelectImageTest.java  | 65 +++++++++++++++---
 3 files changed, 141 insertions(+), 9 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
index 07b4f7cd12..60fbb426ec 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
@@ -25,10 +25,13 @@ import java.awt.image.Raster;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
+import java.awt.image.WritableRenderedImage;
 import java.awt.image.ColorModel;
+import java.awt.image.TileObserver;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 
 
@@ -43,7 +46,7 @@ import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
  * @version 1.4
  * @since   1.1
  */
-final class BandSelectImage extends SourceAlignedImage {
+class BandSelectImage extends SourceAlignedImage {
     /**
      * Properties to inherit from the source image, after bands reduction if applicable.
      *
@@ -119,6 +122,8 @@ final class BandSelectImage extends SourceAlignedImage {
             image = new BufferedImage(cm,
                     bi.getRaster().createWritableChild(0, 0, bi.getWidth(), bi.getHeight(), 0, 0, bands),
                     bi.isAlphaPremultiplied(), properties);
+        } else if (source instanceof WritableRenderedImage) {
+            image = new Writable(source, cm, bands);
         } else {
             image = new BandSelectImage(source, cm, bands);
         }
@@ -189,6 +194,75 @@ final class BandSelectImage extends SourceAlignedImage {
         return parent.createChild(x, y, parent.getWidth(), parent.getHeight(), x, y, bands);
     }
 
+    /**
+     * Applies the band selection on the given writable raster.
+     * The child is created in the same way than {@code computeTile(…)}.
+     */
+    final WritableRaster apply(final WritableRaster parent) {
+        final int x = parent.getMinX();
+        final int y = parent.getMinY();
+        return parent.createWritableChild(x, y, parent.getWidth(), parent.getHeight(), x, y, bands);
+    }
+
+    /**
+     * A {@code BandSelectImage} where the source is a writable rendered image.
+     */
+    private static final class Writable extends BandSelectImage implements WritableRenderedImage {
+        /** Creates a new "band select" operation for the given source. */
+        Writable(final RenderedImage source, final ColorModel cm, final int[] bands) {
+            super(source, cm, bands);
+        }
+
+        /** Returns the source as a writable image. */
+        private WritableRenderedImage target() {
+            return (WritableRenderedImage) getSource();
+        }
+
+        /** Checks out a tile for writing. */
+        @Override public WritableRaster getWritableTile(final int tileX, final int tileY) {
+            markTileWritable(tileX, tileY, true);
+            final WritableRaster parent = target().getWritableTile(tileX, tileY);
+            return apply(parent);
+        }
+
+        /** Relinquishes the right to write to a tile. */
+        @Override public void releaseWritableTile(final int tileX, final int tileY) {
+            target().releaseWritableTile(tileX, tileY);
+            markTileWritable(tileX, tileY, false);
+        }
+
+        /** Adds an observer to be notified when a tile is checked out for writing. */
+        @Override public void addTileObserver(final TileObserver observer) {
+            target().addTileObserver(observer);
+        }
+
+        /** Removes an observer from the list of observers notified when a tile is checked out for writing. */
+        @Override public void removeTileObserver(final TileObserver observer) {
+            target().removeTileObserver(observer);
+        }
+
+        /** Sets a region of the image to the contents of the given raster. */
+        @Override public void setData(final Raster data) {
+            final WritableRenderedImage target = target();
+            final var executor = new TileOpExecutor(target, data.getBounds()) {
+                @Override protected void writeTo(final WritableRaster tile) {
+                    apply(tile).setRect(data);
+                }
+            };
+            executor.writeTo(target);
+        }
+
+        /** Restores the identity behavior for writable image. */
+        @Override public int hashCode() {
+            return System.identityHashCode(this);
+        }
+
+        /** Restores the identity behavior for writable image. */
+        @Override public boolean equals(final Object object) {
+            return object == this;
+        }
+    }
+
     /**
      * Returns a hash code value for this image.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index 56238cb8a2..34d0e60ca3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -899,6 +899,10 @@ public class ImageProcessor implements Cloneable {
      * pixel values are not copied. Consequently, changes in the source image are reflected
      * immediately in the returned image.</p>
      *
+     * <p>If the given image is an instance of {@link WritableRenderedImage},
+     * then the returned image will also be a {@link WritableRenderedImage}.
+     * In such case values written in the returned image will be written directly in the source image.</p>
+     *
      * <h4>Properties used</h4>
      * This operation uses the following properties in addition to method parameters:
      * <ul>
@@ -965,6 +969,11 @@ public class ImageProcessor implements Cloneable {
      * An empty array element (i.e. zero band to select) discards the corresponding source image.
      * In the latter case, the discarded element in the {@code sources} array may be {@code null}.
      *
+     * <p>If all source images are {@link WritableRenderedImage} instances,
+     * then the returned image will also be a {@link WritableRenderedImage}.
+     * In such case values written in the returned image will be copied back
+     * to the source images.</p>
+     *
      * <h4>Restrictions</h4>
      * <ul>
      *   <li>All images shall use the same {@linkplain SampleModel#getDataType() data type}.</li>
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/BandSelectImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/BandSelectImageTest.java
index df68cc3d1d..2a1d0be7ae 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/BandSelectImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/BandSelectImageTest.java
@@ -21,12 +21,14 @@ import java.util.Hashtable;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
+import java.awt.image.WritableRenderedImage;
 import java.awt.image.RenderedImage;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.IndexColorModel;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -37,7 +39,7 @@ import static org.apache.sis.test.FeatureAssert.*;
  * Tests {@link BandSelectImage}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class BandSelectImageTest extends TestCase {
@@ -46,6 +48,11 @@ public final class BandSelectImageTest extends TestCase {
      */
     private static final int WIDTH = 3, HEIGHT = 4;
 
+    /**
+     * Random number generator used for the test.
+     */
+    private Random random;
+
     /**
      * The source image as an instance of custom implementation.
      */
@@ -68,7 +75,7 @@ public final class BandSelectImageTest extends TestCase {
     private void createImage(final int numBands, final int checkedBand, final boolean icm) {
         image = new TiledImageMock(DataBuffer.TYPE_BYTE, numBands, 0, 0, WIDTH, HEIGHT, WIDTH, HEIGHT, 0, 0, false);
         image.initializeAllTiles(checkedBand);
-        final Random random = new Random();
+        random = TestUtilities.createRandomNumberGenerator();
         for (int i=0; i<numBands; i++) {
             if (i != checkedBand) {
                 image.setRandomValues(i, random, 100);
@@ -91,6 +98,18 @@ public final class BandSelectImageTest extends TestCase {
         bufferedImage = new BufferedImage(cm, (WritableRaster) image.getTile(0, 0), false, properties);
     }
 
+    /**
+     * The expected sample values in the determinist band initialized by {@link #createImage(int, int, boolean)}.
+     */
+    private static int[][] expectedSampleValues() {
+        return new int[][] {
+            {100, 101, 102},
+            {110, 111, 112},
+            {120, 121, 122},
+            {130, 131, 132}
+        };
+    }
+
     /**
      * Computes a dummy resolution for the given band.
      */
@@ -110,12 +129,7 @@ public final class BandSelectImageTest extends TestCase {
         assertEquals("numBands", numBands, tile.getNumBands());
         assertEquals("numBands", numBands, ImageUtilities.getNumBands(image));
         assertEquals("sampleModel", image.getSampleModel(), tile.getSampleModel());
-        assertValuesEqual(tile, checkedBand, new int[][] {
-            {100, 101, 102},
-            {110, 111, 112},
-            {120, 121, 122},
-            {130, 131, 132}
-        });
+        assertValuesEqual(tile, checkedBand, expectedSampleValues());
     }
 
     /**
@@ -182,4 +196,39 @@ public final class BandSelectImageTest extends TestCase {
         verifySamples(test, 3, 2);
         verifyProperties(test, 3, 0, 2);
     }
+
+    /**
+     * Tests write operation.
+     */
+    @Test
+    public void testWritable() {
+        createImage(2, 1, true);
+        final ImageProcessor processor = new ImageProcessor();
+        RenderedImage test = processor.selectBands(image, 1);
+        final int[][] expected = expectedSampleValues();
+        final Raster data = test.getData();
+        assertValuesEqual(data, 0, expected);
+        /*
+         * Above code where read operations for making sure that we initialized the test correctly.
+         * Code below is the actual test for write operations.
+         */
+        final WritableRenderedImage writable = (WritableRenderedImage) test;
+        final int tileX = writable.getMinTileX();
+        final int tileY = writable.getMinTileY();
+        final WritableRaster tile = writable.getWritableTile(tileX, tileY);
+        for (int i=0; i<3; i++) {
+            final int x = random.nextInt(tile.getWidth());
+            final int y = random.nextInt(tile.getHeight());
+            final int s = random.nextInt(10);
+            tile.setSample(x, y, 0, s);
+            expected[y][x] = s;
+        }
+        writable.releaseWritableTile(tileX, tileY);
+        assertValuesEqual(writable.getData(), 0, expected);
+        /*
+         * Try to restore orginal values.
+         */
+        writable.setData(data);
+        assertValuesEqual(writable.getData(), 0, expectedSampleValues());
+    }
 }