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 67a64fdf85a73ec1dd182b1bf301f9e024d97700 Merge: f87196f 7b1ee54 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Nov 8 17:12:45 2021 +0100 Merge branch 'fix/mask-image' into geoapi-4.0 .../java/org/apache/sis/image/MaskedImageTest.java | 255 +++++++++++++++++++++ .../apache/sis/test/suite/FeatureTestSuite.java | 3 +- 2 files changed, 257 insertions(+), 1 deletion(-) diff --cc core/sis-feature/src/test/java/org/apache/sis/image/MaskedImageTest.java index 0000000,dc9e8ec..441574c mode 000000,100644..100644 --- a/core/sis-feature/src/test/java/org/apache/sis/image/MaskedImageTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/image/MaskedImageTest.java @@@ -1,0 -1,237 +1,255 @@@ ++/* ++ * 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.image; + + import java.awt.Color; + import java.awt.Graphics; ++import java.awt.Point; + import java.awt.Polygon; + import java.awt.Rectangle; + import java.awt.image.BufferedImage; + import java.awt.image.DataBuffer; + import java.awt.image.IndexColorModel; + import java.awt.image.Raster; + import java.awt.image.RenderedImage; + import java.awt.image.WritableRaster; + import org.apache.sis.internal.coverage.j2d.WritableTiledImage; + import org.apache.sis.test.FeatureAssert; + import org.apache.sis.util.ArgumentChecks; ++import org.apache.sis.test.TestCase; + import org.junit.Test; + + /** + * TODO: test with big images + * TODO: when upgrading to Junit 5 use parameterized tests to avoid duplication of test methods + */ -public class MaskedImageTest { ++public final strictfp class MaskedImageTest extends TestCase { + + private static final Polygon CONVEX_POLYGON = new Polygon( + new int[] {3, 8, 8, 3, 3}, + new int[] {3, 3, 6, 6, 3}, + 5); + + private static final Polygon CONCAVE_POLYGON = new Polygon( + new int[] { 1, 1, 7, 7, 5, 5, 2, 2 }, + new int[] { 1, 6, 6, 3, 3, 4, 4, 1 }, + 8); + + private static final int[] BASE_CANVAS = { + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, + 3, 3, 3, 3, 2, 2, 2, 2, + 3, 3, 3, 3, 2, 2, 2, 2, + 3, 3, 3, 3, 2, 2, 2, 2, + 3, 3, 3, 3, 2, 2, 2, 2 + }; + + @Test + public void noErrorOnEmptyMasks() { + final BufferedImage source = monoTile(); + final RenderedImage masked = new MaskedImage(source, new Polygon(), true, new Number[]{ 127 }); + FeatureAssert.assertPixelsEqual(source, null, masked, null); + } + + @Test + public void fill_MONO_tile_INside_conVEX_polygon() { + maskInsideConvexPolygon(monoTile()); + } + + @Test + public void fill_MONO_tile_OUTside_conVEX_polygon() { + maskOutsideConvexPolygon(monoTile()); + } + + @Test + public void fill_MULTI_tile_INside_conVEX_polygon() { + maskInsideConvexPolygon(multiTile()); + } + + @Test + public void fill_MULTI_tile_OUTside_conVEX_polygon() { + maskOutsideConvexPolygon(multiTile()); + } + + @Test + public void fill_MONO_tile_INside_conCAVE_polygon() { + fillInsideConcavePolygon(monoTile()); + } + + @Test + public void fill_MONO_tile_OUTside_conCAVE_polygon() { + fillOutsideConcavePolygon(monoTile()); + } + + @Test + public void fill_MULTI_tile_INside_conCAVE_polygon() { + fillInsideConcavePolygon(multiTile()); + } + + @Test + public void fill_MULTI_tile_OUTside_conCAVE_polygon() { + fillOutsideConcavePolygon(multiTile()); + } + + private void maskInsideConvexPolygon(RenderedImage source) { + final RenderedImage masked = new MaskedImage(source, CONVEX_POLYGON, true, new Number[]{ 4 }); + final RenderedImage expected = monoTile(new int[] { + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 0, 4, 4, 4, 4, 4, + 3, 3, 3, 4, 4, 4, 4, 4, + 3, 3, 3, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 2, 2, 2, 2, + 3, 3, 3, 3, 2, 2, 2, 2 + }); + FeatureAssert.assertPixelsEqual(expected, null, masked, null); + } + + private void maskOutsideConvexPolygon(final RenderedImage source) { + final RenderedImage masked = new MaskedImage(source, CONVEX_POLYGON, false, new Number[]{ 4 }); + final RenderedImage expected = monoTile(new int[] { + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 0, 1, 1, 1, 1, + 4, 4, 4, 3, 2, 2, 2, 2, + 4, 4, 4, 3, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4 + }); + FeatureAssert.assertPixelsEqual(expected, null, masked, null); + } + + private void fillInsideConcavePolygon(final RenderedImage source) { + final RenderedImage masked = new MaskedImage(source, CONCAVE_POLYGON, true, new Number[]{ 4 }); + final RenderedImage expected = monoTile(new int[] { + 0, 0, 0, 0, 1, 1, 1, 1, + 0, 4, 0, 0, 1, 1, 1, 1, + 0, 4, 0, 0, 1, 1, 1, 1, + 0, 4, 0, 0, 1, 4, 4, 1, + 3, 4, 4, 4, 4, 4, 4, 2, + 3, 4, 4, 4, 4, 4, 4, 2, + 3, 3, 3, 3, 2, 2, 2, 2, + 3, 3, 3, 3, 2, 2, 2, 2 + }); + FeatureAssert.assertPixelsEqual(expected, null, masked, null); + } + + private void fillOutsideConcavePolygon(final RenderedImage source) { + final RenderedImage masked = new MaskedImage(source, CONCAVE_POLYGON, false, new Number[]{ 4 }); + final RenderedImage expected = monoTile(new int[] { + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 0, 4, 4, 4, 4, 4, 4, + 4, 0, 4, 4, 4, 4, 4, 4, + 4, 0, 4, 4, 4, 1, 1, 4, + 4, 3, 3, 3, 2, 2, 2, 4, + 4, 3, 3, 3, 2, 2, 2, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4 + }); + FeatureAssert.assertPixelsEqual(expected, null, masked, null); + } + + private RenderedImage multiTile() { + return new WritableTiledImage(null, colorPalette(), 8, 8, 0, 0, - tile(0), tile(1), tile(3), tile(2)); ++ tile(0, 0), tile(1, 1), tile(2, 3), tile(3, 2)); + } + - private WritableRaster tile(int fillValue) { - final WritableRaster tile = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 4, 4, 1, null); ++ private static WritableRaster tile(final int n, final int fillValue) { ++ final WritableRaster tile = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 4, 4, 1, new Point((n % 2)*4, (n / 2)*4)); + final WritablePixelIterator it = new PixelIterator.Builder().createWritable(tile); + while (it.next()) it.setSample(0, fillValue); + return tile; + } + + /** + * Base colors are shades of blue. The last (fifth) color used for masks is green. + * @return + */ + private static IndexColorModel colorPalette() { + byte[] reds = { 0, 0, 0, 0, 0 }; + byte[] greens = { 0, 0, 0, 0, 127 }; + byte[] blues = { 0, 50, 95, 127, 0 }; + return new IndexColorModel(Byte.SIZE, 5, reds, greens, blues); + } + + /** + * + * @return Base test image, based on {@link #colorPalette() indexed color model}. + */ + private static BufferedImage monoTile() { + return monoTile(BASE_CANVAS); + } + + private static BufferedImage monoTile(int[] pixels) { + ArgumentChecks.ensureDimensionMatches("Input raster must be 8x8", 64, pixels); + final BufferedImage image = new BufferedImage(8, 8, BufferedImage.TYPE_BYTE_INDEXED, colorPalette()); + final WritableRaster raster = image.getRaster(); + raster.setPixels(0, 0, 8, 8, pixels); + return image; + } + + /** + * Draw given polygon into source image, to display expected result in debugger. + */ + private static BufferedImage debugGeometryImage(final Polygon geometry) { + final BufferedImage source = monoTile(); + final BufferedImage debugImg = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); + final Graphics painter = debugImg.getGraphics(); + painter.drawImage(source, 0, 0, null); + + final Rectangle bounds = geometry.getBounds(); + final Rectangle enlargedBounds = new Rectangle(bounds.x - 1, bounds.y - 1, bounds.width + 2, bounds.height + 2); + final Rectangle intersection = enlargedBounds.intersection(debugImg.getRaster().getBounds()); + painter.setColor(Color.RED); + painter.fillRect(intersection.x, intersection.y, intersection.width, intersection.height); + painter.setColor(Color.GREEN); + painter.fillPolygon(geometry); + painter.dispose(); + + return debugImg; + } + + + /** + * Useful in IntelliJ: allow to display input image in debugger: Add a new watch calling this method on wanted image. + * <em>WARNINGS:</em> works only for current test case: + * <ul> + * <li> it assume input image is compatible with test {@link #colorPalette() color palette}.</li> + * <li>Work only on single tile images.</li> + * </ul> + * + * @param source The image to display. + * @return The image directly displayable through debugger. + */ + private static BufferedImage debug(final RenderedImage source) { + Raster tile = source.getTile(source.getMinTileX(), source.getMinTileY()); + final int width, height; + + tile = tile.createTranslatedChild(0, 0); + width = tile.getWidth(); + height = tile.getHeight(); + + final BufferedImage view = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, colorPalette()); + view.getRaster().setRect(tile); + + return view; + } + } diff --cc core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java index 32834e5,32834e5..9f2cf45 --- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java +++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java @@@ -26,7 -26,7 +26,7 @@@ import org.junit.runners.Suite * * @author Martin Desruisseaux (Geomatys) * @author Johann Sorel (Geomatys) -- * @version 1.1 ++ * @version 1.2 * @since 0.5 * @module */ @@@ -97,6 -97,6 +97,7 @@@ org.apache.sis.image.InterpolationTest.class, org.apache.sis.image.ResamplingGridTest.class, org.apache.sis.image.ResampledImageTest.class, ++ org.apache.sis.image.MaskedImageTest.class, org.apache.sis.image.BandedSampleConverterTest.class, org.apache.sis.image.ImageCombinerTest.class, org.apache.sis.image.ImageProcessorTest.class,