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 <martin.desruisse...@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()); + } }