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 ae19e93ae89372ea2bc668f3131ba3b52e7e795a Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Dec 31 19:06:48 2019 +0100 Base class for image operations. --- .../java/org/apache/sis/image/PlanarImage.java | 36 +--- .../sis/internal/coverage/j2d/CachedImage.java | 16 +- .../sis/internal/coverage/j2d/ImageOperation.java | 234 +++++++++++++++++++++ .../sis/internal/coverage/j2d/ImageUtilities.java | 43 ++++ .../org/apache/sis/internal/feature/Resources.java | 10 + .../sis/internal/feature/Resources.properties | 2 + .../sis/internal/feature/Resources_fr.properties | 2 + .../org/apache/sis/internal/util/Numerics.java | 14 +- .../main/java/org/apache/sis/measure/Scalar.java | 3 +- 9 files changed, 324 insertions(+), 36 deletions(-) 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 c924ad4..e8e51dc 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 @@ -22,9 +22,7 @@ import java.awt.color.ColorSpace; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.SampleModel; -import java.awt.image.DataBuffer; import java.awt.image.Raster; -import java.awt.image.RasterFormatException; import java.awt.image.WritableRaster; import java.awt.image.RenderedImage; import java.util.Vector; @@ -84,12 +82,6 @@ import org.apache.sis.internal.coverage.j2d.ScaledColorSpace; */ public abstract class PlanarImage implements RenderedImage { /** - * Approximate size of the buffer to use for copying data from the image to a raster, in bits. - * The actual buffer size may be smaller or larger, depending on the actual tile size. - */ - private static final int BUFFER_SIZE = 8192 * Byte.SIZE; - - /** * Creates a new rendered image. */ protected PlanarImage() { @@ -261,22 +253,6 @@ public abstract class PlanarImage implements RenderedImage { } /** - * Returns the size in bits of the transfer type, or an arbitrary value if that type is unknown. - * For this class it is okay if the value is not accurate; this method is used only for adjusting - * the {@link #BUFFER_SIZE} value. - * - * @param raster the raster for which to get transfer type size. - * @return size in bits of transfer type. May be an arbitrary size. - */ - private static int getTransferTypeSize(final Raster raster) { - try { - return DataBuffer.getDataTypeSize(raster.getTransferType()); - } catch (IllegalArgumentException e) { - return Short.SIZE; - } - } - - /** * Returns a copy of this image as one large tile. * The returned raster will not be updated if this image is changed. * @@ -352,6 +328,8 @@ public abstract class PlanarImage implements RenderedImage { * 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. * The buffer will be reused for each transfer, unless its size is insufficient. + * 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; @@ -359,15 +337,7 @@ public abstract class PlanarImage implements RenderedImage { for (int tx = minTileX; tx <= maxTileX; tx++) { final Raster tile = getTile(tx, ty); final Rectangle tb = aoi.intersection(tile.getBounds()); // Bounds of transfer buffer. - if (tb.isEmpty()) { - /* - * Should never happen since we iterate only over the tiles - * that intersect the given area of interest. - */ - throw new RasterFormatException("Inconsistent tile matrix."); - } - final int afterLastRow = Math.addExact(tb.y, tb.height); - tb.height = Math.max(1, Math.min(BUFFER_SIZE / (getTransferTypeSize(tile) * tb.width), tb.height)); + final int afterLastRow = ImageUtilities.prepareTransferRegion(tb, tile.getTransferType()); final int transferCapacity = tb.width * tb.height; if (transferCapacity > bufferCapacity) { bufferCapacity = transferCapacity; diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/CachedImage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/CachedImage.java index f518429..8436b8e 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/CachedImage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/CachedImage.java @@ -26,6 +26,7 @@ import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.lang.ref.WeakReference; import org.apache.sis.internal.system.ReferenceQueueConsumer; +import org.apache.sis.internal.feature.Resources; import org.apache.sis.image.PlanarImage; import org.apache.sis.util.collection.Cache; import org.apache.sis.util.ArgumentChecks; @@ -161,6 +162,19 @@ public abstract class CachedImage extends PlanarImage { } /** + * Returns the sample model associated with this image. + * All rasters returned from this image will have this sample model. + * In {@code CachedImage} implementation, the sample model determines the tile size + * (this is not necessarily true for all {@link RenderedImage} implementations). + * + * @return the sample model of this image. + */ + @Override + public SampleModel getSampleModel() { + return sampleModel; + } + + /** * Returns the width of tiles in this image. The default implementation returns {@link SampleModel#getWidth()}. * * <div class="note"><b>Note:</b> @@ -221,7 +235,7 @@ public abstract class CachedImage extends PlanarImage { if (tile == null) { tile = computeTile(tileX, tileY); if (tile == null) { - throw new ImagingOpException("No data"); // TODO + throw new ImagingOpException(Resources.format(Resources.Keys.CanNotComputeTile_2, tileX, tileY)); } } } finally { diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageOperation.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageOperation.java new file mode 100644 index 0000000..f46ccaf --- /dev/null +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageOperation.java @@ -0,0 +1,234 @@ +/* + * 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.util.Vector; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.ColorModel; +import org.apache.sis.util.ArgumentChecks; + + +/** + * An image which is computed on-the-fly, usually from other images. + * Computations are performed on a tile-by-tile basis and the result + * is stored in a cache shared by all images on the platform. + * + * @todo Add an API providing operation parameters. + * + * <p>Subclasses need to implement the following methods:</p> + * <ul> + * <li>{@link #computeTile(int, int)}</li> + * </ul> + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +public abstract class ImageOperation extends CachedImage { + /** + * The sources of this image. Never null and does not contain any null element. + */ + private final RenderedImage[] sources; + + /** + * The color model associated to this image, or {@code null} if unspecified. + */ + private final ColorModel colorModel; + + /** + * Image size in pixels. + */ + private final int width, height; + + /** + * Coordinate of the pixel in upper-left corner. + */ + private final int minX, minY; + + /** + * Coordinate of the tile in the upper-left corner. + */ + private final int minTileX, minTileY; + + /** + * Creates a new operation with a single image as the main source. + * This {@code ImageOperation} will use the same color model, data type, + * tile size, image size and minimum coordinates than the given image. + * + * @param image the main source of this operation. + * @param others additional sources, or {@code null} if none. + */ + protected ImageOperation(final RenderedImage image, final RenderedImage... others) { + super(image); + if (others == null) { + sources = new RenderedImage[] {image}; + } else { + sources = new RenderedImage[others.length + 1]; + sources[0] = image; + System.arraycopy(others, 0, sources, 1, others.length); + for (int i=1; i<sources.length; i++) { + ArgumentChecks.ensureNonNullElement("others", i-1, sources[i]); + } + } + colorModel = image.getColorModel(); + width = image.getWidth(); + height = image.getHeight(); + minX = image.getMinX(); + minY = image.getMinY(); + minTileX = image.getMinTileX(); + minTileY = image.getMinTileY(); + } + + /** + * Creates a new operation with an arbitrary amount of images as the sources. + * The tile size will be the width and height of the given sample model. + * + * @param sampleModel the sample model shared by all tiles in this image. + * @param colorModel the color model for all rasters, or {@code null} if unspecified. + * @param width the image width in pixels, as a strictly positive number. + * @param height the image height in pixels, as a strictly positive number. + * @param minX <var>x</var> coordinate (column) of the pixel in upper-left corner. + * @param minY <var>y</var> coordinate (row) of the pixel in upper-left corner. + * @param minTileX <var>x</var> index of the tile in upper-left corner. + * @param minTileY <var>y</var> index of the tile in upper-left corner. + * @param sources all sources of this image. May be an empty array. + */ + protected ImageOperation(final SampleModel sampleModel, final ColorModel colorModel, + final int width, final int height, final int minX, final int minY, + final int minTileX, final int minTileY, RenderedImage... sources) + { + super(sampleModel); + this.colorModel = colorModel; + this.width = width; + this.height = height; + this.minX = minX; + this.minY = minY; + this.minTileX = minTileX; + this.minTileY = minTileY; + ArgumentChecks.ensureStrictlyPositive("width", width); + ArgumentChecks.ensureStrictlyPositive("height", height); + ArgumentChecks.ensureNonNull("sources", sources); + sources = sources.clone(); + this.sources = sources; + for (int i=0; i<sources.length; i++) { + ArgumentChecks.ensureNonNullElement("sources", i, sources[i]); + } + } + + /** + * Returns the source at the given index. + * + * @param index index of the desired source. + * @return source at the given index. + * @throws IndexOutOfBoundsException if the given index is out of bounds. + */ + protected final RenderedImage getSource(final int index) { + return sources[index]; + } + + /** + * Returns the immediate sources of image data for this image. + * This method returns the source specified at construction time. + * + * @return the immediate sources, or an empty vector is none. + */ + @Override + @SuppressWarnings("UseOfObsoleteCollectionType") + public Vector<RenderedImage> getSources() { + return new Vector<>(Arrays.asList(sources)); + } + + /** + * Returns the color model associated with this image (may be null). + * All rasters returned from this image will have this color model. + * + * @return the color model of this image, or {@code null} if unspecified. + */ + @Override + public ColorModel getColorModel() { + return colorModel; + } + + /** + * Returns the width of this image in pixels. + * This value is set at construction time. + * + * @return the width of this image. + */ + @Override + public int getWidth() { + return width; + } + + /** + * Returns the height of this image in pixels. + * This value is set at construction time. + * + * @return the height of this image. + */ + @Override + public int getHeight() { + return height; + } + + /** + * Returns the minimum <var>x</var> coordinate (inclusive) of this image. + * This value is set at construction time. + * + * @return the minimum <var>x</var> coordinate (column) of this image. + */ + @Override + public int getMinX() { + return minX; + } + + /** + * Returns the minimum <var>y</var> coordinate (inclusive) of this image. + * This value is set at construction time. + * + * @return the minimum <var>y</var> coordinate (row) of this image. + */ + @Override + public int getMinY() { + return minY; + } + + /** + * Returns the minimum tile index in the <var>x</var> direction. + * This value is set at construction time. + * + * @return the minimum tile index in the <var>x</var> direction. + */ + @Override + public int getMinTileX() { + return minTileX; + } + + /** + * Returns the minimum tile index in the <var>y</var> direction. + * This value is set at construction time. + * + * @return the minimum tile index in the <var>y</var> direction. + */ + @Override + public int getMinTileY() { + return minTileY; + } +} 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 6289f3b..6cd8807 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 @@ -26,6 +26,7 @@ import java.awt.image.IndexColorModel; import java.awt.image.PackedColorModel; import java.awt.image.RenderedImage; import java.awt.image.Raster; +import java.awt.image.RasterFormatException; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import org.apache.sis.internal.feature.Resources; @@ -44,6 +45,20 @@ import org.apache.sis.util.resources.Vocabulary; */ public final class ImageUtilities { /** + * Default width and height of tiles, in pixels. + */ + public static final int DEFAULT_TILE_SIZE = 256; + + /** + * Approximate size of the buffer to use for copying data from/to a raster, in bits. + * The actual buffer size may be smaller or larger, depending on the actual tile size. + * This value does not need to be very large. The current value is 8 kb. + * + * @see #prepareTransferRegion(Rectangle, int) + */ + private static final int BUFFER_SIZE = 32 * DEFAULT_TILE_SIZE * Byte.SIZE; + + /** * Do not allow instantiation of this class. */ private ImageUtilities() { @@ -274,4 +289,32 @@ public final class ImageUtilities { } return keys; } + + /** + * Suggests the height of a transfer region for a tile of the given size. The given region should be + * contained inside {@link Raster#getBounds()}. This method modifies {@link Rectangle#height} in-place. + * The {@link Rectangle#width} value is never modified, so caller can iterate on all raster rows without + * the need to check if the row is incomplete. + * + * @param bounds on input, the region of interest. On output, the suggested transfer region bounds. + * @param dataType one of {@link DataBuffer} constant. It is okay if an unknown constant is used since + * this information is used only as a hint for adjusting the {@link #BUFFER_SIZE} value. + * @return the maximum <var>y</var> value plus 1. This can be used as stop condition for iterating over rows. + * @throws ArithmeticException if the maximum <var>y</var> value overflows 32 bits integer capacity. + * @throws RasterFormatException if the given bounds is empty. + */ + public static int prepareTransferRegion(final Rectangle bounds, final int dataType) { + if (bounds.isEmpty()) { + throw new RasterFormatException(Resources.format(Resources.Keys.EmptyTileOrImageRegion)); + } + final int afterLastRow = Math.addExact(bounds.y, bounds.height); + int size; + try { + size = DataBuffer.getDataTypeSize(dataType); + } catch (IllegalArgumentException e) { + size = Short.SIZE; // Arbitrary value is okay because this is only a hint for choosing a buffer size. + } + bounds.height = Math.max(1, Math.min(BUFFER_SIZE / (size * bounds.width), bounds.height)); + return afterLastRow; + } } 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 6c2a399..97d152c 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 @@ -70,6 +70,11 @@ public final class Resources extends IndexedResourceBundle { public static final short CanNotAssignCharacteristics_1 = 2; /** + * Can not compute tile ({0}, {1}). + */ + public static final short CanNotComputeTile_2 = 66; + + /** * Can not create a two-dimensional reference system from the “{0}” system. */ public static final short CanNotCreateTwoDimensionalCRS_1 = 60; @@ -130,6 +135,11 @@ public final class Resources extends IndexedResourceBundle { public static final short DependencyNotFound_3 = 8; /** + * Empty tile or image region. + */ + public static final short EmptyTileOrImageRegion = 67; + + /** * Indices ({3}) are outside grid coverage. The value in dimension {0} shall be between * {1,number} and {2,number} inclusive. */ 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 2355891..23f473b 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 @@ -21,6 +21,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}). 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. @@ -33,6 +34,7 @@ CategoryRangeOverlap_4 = The two categories \u201c{0}\u201d and \u201 CharacteristicsAlreadyExists_2 = Characteristics \u201c{1}\u201d already exists in attribute \u201c{0}\u201d. CharacteristicsNotFound_2 = No characteristics named \u201c{1}\u201d has been found in \u201c{0}\u201d attribute. DependencyNotFound_3 = Operation \u201c{0}\u201d requires a \u201c{1}\u201d property, but no such property has been found in \u201c{2}\u201d. +EmptyTileOrImageRegion = Empty tile or image region. GridCoordinateOutsideCoverage_4 = Indices ({3}) are outside grid coverage. The value in dimension {0} shall be between {1,number} and {2,number} inclusive. GridEnvelopeMustBeNDimensional_1 = The grid envelope must have at least {0} dimensions. GridEnvelopeOutsideCoverage_5 = Envelope is outside grid coverage. Indices [{3,number} \u2026 {4,number}] in dimension {0} do not intersect the [{1,number} \u2026 {2,number}] grid extent. 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 bd333d7..ba6fac4 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 @@ -26,6 +26,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}). 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. @@ -38,6 +39,7 @@ CategoryRangeOverlap_4 = Les deux cat\u00e9gories \u00ab\u202f{0}\u20 CharacteristicsAlreadyExists_2 = La caract\u00e9ristique \u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019attribut \u00ab\u202f{0}\u202f\u00bb. CharacteristicsNotFound_2 = Aucune caract\u00e9ristique nomm\u00e9e \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans l\u2019attribut \u00ab\u202f{0}\u202f\u00bb. DependencyNotFound_3 = L\u2019op\u00e9ration \u00ab\u202f{0}\u202f\u00bb n\u00e9cessite une propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb, mais cette propri\u00e9t\u00e9 n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans \u00ab\u202f{2}\u202f\u00bb. +EmptyTileOrImageRegion = La tuile ou la r\u00e9gion de l\u2019image est vide. GridCoordinateOutsideCoverage_4 = Les indices ({3}) sont en dehors du domaine de la grille. La valeur dans la dimension {0} doit \u00eatre entre {1,number} et {2,number} inclusivement. GridEnvelopeMustBeNDimensional_1 = L\u2019enveloppe de la grille doit avoir au moins {0} dimensions. GridEnvelopeOutsideCoverage_5 = L\u2019enveloppe est en dehors du domaine de la grille. Les indices [{3,number} \u2026 {4,number}] dans la dimension {0} n\u2019interceptent pas l\u2019\u00e9tendue [{1,number} \u2026 {2,number}] de la grille. diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java index 296d78c..6222ef7 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java @@ -40,7 +40,7 @@ import static java.lang.Math.ulp; * Miscellaneous utilities methods working on floating point numbers. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.1 * @since 0.3 * @module */ @@ -222,6 +222,18 @@ public final class Numerics extends Static { } /** + * Returns the given value clamped to the range on 32 bits integer. + * + * @param value the value to clamp. + * @return the value clamped to the range of 32 bits integer. + */ + public static int clamp(final long value) { + if (value < Integer.MIN_VALUE) return Integer.MIN_VALUE; + if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE; + return (int) value; + } + + /** * If the given value is presents in the cache, returns the cached value. * Otherwise returns the given value as-is. * diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Scalar.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Scalar.java index e13f0cb..a425cb7 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/measure/Scalar.java +++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Scalar.java @@ -22,6 +22,7 @@ import javax.measure.Quantity; import javax.measure.UnitConverter; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.StringBuilders; +import org.apache.sis.internal.util.Numerics; /** @@ -155,7 +156,7 @@ abstract class Scalar<Q extends Quantity<Q>> extends Number implements Quantity< */ @Override public final int intValue() { - return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, longValue())); + return Numerics.clamp(longValue()); } /**
