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 e3a2732 Add integer overflow analysis in GridCoverage2D.render(…) method for BufferedImage case. Detect when existing BufferedImage can be returned as-is instead of invoking `getSubImage`. Add test case for https://issues.apache.org/jira/browse/SIS-495 e3a2732 is described below commit e3a27327f3c9012df3cf05787a9ffd2e9c4b1ec1 Author: Martin Desruisseaux AuthorDate: Thu Jun 4 14:08:01 2020 +0200 Add integer overflow analysis in GridCoverage2D.render(…) method for BufferedImage case. Detect when existing BufferedImage can be returned as-is instead of invoking `getSubImage`. Add test case for https://issues.apache.org/jira/browse/SIS-495 --- .../apache/sis/coverage/grid/GridCoverage2D.java | 20 ++++++-- .../sis/coverage/grid/ResampledGridCoverage.java | 5 +- .../sis/coverage/grid/GridCoverage2DTest.java | 57 ++++++++-------------- .../coverage/grid/ResampledGridCoverageTest.java | 33 ++++++++++++- 4 files changed, 69 insertions(+), 46 deletions(-) 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 5da66ab..42e6834 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 @@ -578,13 +578,25 @@ public class GridCoverage2D extends GridCoverage { * upper-left point is inside the image. */ if (data instanceof BufferedImage) { - // BufferedImage origin should be (0, 0), but for consistency over image API, we consider it variable + // BufferedImage origin should be (0, 0), but for consistency with image API, we consider it variable. final long ix = data.getMinX(); final long iy = data.getMinY(); if (xmin >= ix && ymin >= iy) { - return ((BufferedImage) data).getSubimage(toIntExact(xmin), toIntExact(ymin), - toIntExact(min(xmax + 1, ix + data.getWidth() ) - xmin), - toIntExact(min(ymax + 1, iy + data.getHeight()) - ymin)); + final int width = data.getWidth(); + final int height = data.getHeight(); + /* + * Result of `ix + width` requires at most 33 bits for any `ix` value (same for y axis). + * Subtractions by `xmin` and `ymin` never overflow if `ix` and `iy` are zero or positive, + * which should always be the case with BufferedImage. The +1 is applied after subtraction + * instead than on `xmax` and `ymax` for avoiding overflow, since the result of `min(…)` + * uses at most 33 bits. + */ + final int nx = toIntExact(min(xmax, ix + width - 1) - xmin + 1); + final int ny = toIntExact(min(ymax, iy + height - 1) - ymin + 1); + if ((ix | iy) == 0 && nx == width && ny == height) { + return data; + } + return ((BufferedImage) data).getSubimage(toIntExact(xmin), toIntExact(ymin), nx, ny); } } /* diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java index 9f8474d..898b2d8 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java @@ -456,9 +456,8 @@ final class ResampledGridCoverage extends GridCoverage { bounds = new Rectangle(Math.toIntExact(sliceExtent.getSize(resampledDimensions[0])), Math.toIntExact(sliceExtent.getSize(resampledDimensions[1]))); /* - * The transform needs to be two-dimensional. The `toSourceCenter` transform does not met that condition, - * otherwise `specialize(…)` would have replaced this ResampledGridCoverage by a GridCoverage2D instance. - * Try to extract a two-dimensional part operating only on the slice dimensions having an extent larger + * The transform inputs must be two-dimensional (outputs may be more flexible). If this is not the case, + * try to extract a two-dimensional part operating only on the slice dimensions having an extent larger * than one cell. The choice of dimensions may vary between different calls to this `render(…)` method, * depending on `sliceExtent` value. */ 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 b2ef925..ce621f9 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 @@ -16,9 +16,9 @@ */ package org.apache.sis.coverage.grid; -import java.awt.image.RenderedImage; import java.util.List; import java.util.Collections; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.Raster; @@ -38,7 +38,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.test.TestCase; import org.junit.Test; -import static org.junit.Assert.*; +import static org.apache.sis.test.FeatureAssert.*; /** @@ -201,43 +201,26 @@ public strictfp class GridCoverage2DTest extends TestCase { @Test public void render_of_subextent() { final GridCoverage coverage = createTestCoverage(); - final Raster completeRendering = coverage.render(null).getTile(0, 0); - + /* + * Row extraction: + * - Expected size (2,1) is verified by `assertPixelsEqual(…)`. + * - Bounds of expected values is Rectangle(translation, size). + * - Pixel source(0, 1) → output(0, 0) + * - Pixel source(1, 1) → output(1, 0) + */ final GridExtent singleRow = new GridExtent(2, 1).translate(0, 1); - RenderedImage subset = coverage.render(singleRow); - assertEquals("Rendering width", 2, subset.getWidth()); - assertEquals("Rendering height", 1, subset.getHeight()); - Raster subsetTile = subset.getTile(0, 0); - assertArrayEquals( - "Row extraction, pixel source(0, 1) -> output(0, 0)", - completeRendering.getPixel(0, 1, (double[])null), - subsetTile.getPixel(0, 0, (double[])null), - 1e-1 - ); - assertArrayEquals( - "Row extraction, pixel source(1, 1) -> output(1, 0)", - completeRendering.getPixel(1, 1, (double[])null), - subsetTile.getPixel(1, 0, (double[])null), - 1e-1 - ); - + assertPixelsEqual(coverage.render(null), new Rectangle(0, 1, 2, 1), // Expected values. + coverage.render(singleRow), null); // Actual values to test. + /* + * Column extraction: + * - Expected size (1,2) is verified by `assertPixelsEqual(…)`. + * - Bounds of expected values is Rectangle(translation, size). + * - Pixel source(1, 0) → output(0, 0) + * - Pixel source(1, 1) → output(0, 1) + */ final GridExtent singleCol = new GridExtent(1, 2).translate(1, 0); - subset = coverage.render(singleCol); - assertEquals("Rendering width", 1, subset.getWidth()); - assertEquals("Rendering height", 2, subset.getHeight()); - subsetTile = subset.getTile(0, 0); - assertArrayEquals( - "Column extraction, pixel source(1, 0) -> output(0, 0)", - completeRendering.getPixel(1, 0, (double[])null), - subsetTile.getPixel(0, 0, (double[])null), - 1e-1 - ); - assertArrayEquals( - "Column extraction, pixel source(1, 1) -> output(0, 1)", - completeRendering.getPixel(1, 1, (double[])null), - subsetTile.getPixel(0, 1, (double[])null), - 1e-1 - ); + assertPixelsEqual(coverage.render(null), new Rectangle(1, 0, 1, 2), // Expected values. + coverage.render(singleCol), null); // Actual values to test. } /** diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java index c4a3156..53a78e2 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java @@ -36,6 +36,7 @@ import org.apache.sis.image.Interpolation; import org.apache.sis.image.TiledImageMock; import org.apache.sis.internal.coverage.j2d.TiledImage; import org.apache.sis.internal.referencing.Formulas; +import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.crs.HardCodedCRS; import org.apache.sis.referencing.operation.HardCodedConversions; @@ -61,6 +62,7 @@ import static org.apache.sis.test.FeatureAssert.*; * * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) + * @author Johann Sorel (Geomatys) * @version 1.1 * @since 1.1 * @module @@ -520,7 +522,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase { * @throws TransformException if some coordinates can not be transformed to the target grid geometry. */ @Test - public void crs3D_to_crs4D() throws TransformException { + public void testDimensionalityIncrease() throws TransformException { final GridCoverage source3D = createCoverageND(false); final GridGeometry target4D = createGridGeometryND(HardCodedCRS.WGS84_4D, 0, 1, 2, 3, false); final GridCoverage result = resample(source3D, target4D); @@ -534,7 +536,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase { * @throws TransformException if some coordinates can not be transformed to the target grid geometry. */ @Test - public void crs4D_to_crs3D() throws TransformException { + public void testDimensionalityReduction() throws TransformException { final GridGeometry target3D = createGridGeometryND(HardCodedCRS.WGS84_3D, 0, 1, 2, 3, false); final GridCoverage source4D = createCoverageND(true); final GridCoverage result = resample(source4D, target3D); @@ -543,6 +545,33 @@ public final strictfp class ResampledGridCoverageTest extends TestCase { } /** + * Tests resampling with a target domain larger than the source domain. + * Pixel outside the source domain shall be set to fill value, which is 0. + * + * @throws TransformException if some coordinates can not be transformed to the target grid geometry. + * + * @see SIS-495 + */ + @Test + public void testDomainIncrease() throws TransformException { + final int size = 2; + final CoordinateReferenceSystem crs = HardCodedCRS.WGS84; + final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_GRAY); + image.getRaster().setDataElements(0, 0, size, size, new byte[] {10, 12, 16, 14}); + final AffineTransform2D gridToCRS = new AffineTransform2D(1, 0, 0, -1, 0, 0); + final GridGeometry sourceGrid = new GridGeometry(null, CELL_CENTER, gridToCRS, crs); + final GridGeometry targetGrid = new GridGeometry(new GridExtent(4, 4), CELL_CENTER, gridToCRS, crs); + final GridCoverage source = new GridCoverage2D(sourceGrid, null, image); + final GridCoverage target = resample(source, targetGrid); + assertValuesEqual(target.render(null).getData(), 0, new int[][] { + {10, 12, 0, 0}, + {16, 14, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0} + }); + } + + /** * Returns an image with only the queries part of the given image. * This is an helper tools which can be invoked during debugging * session in IDE capable to display images.