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 c5ae48b39368dffc9f148f4cb632db0727c1cafc Author: Martin Desruisseaux AuthorDate: Fri Aug 21 16:12:12 2020 +0200 Replace the `PreferredSize` hack by a public API. --- .../org/apache/sis/gui/coverage/RenderingData.java | 6 +- .../java/org/apache/sis/image/ImageCombiner.java | 2 +- .../java/org/apache/sis/image/ImageProcessor.java | 56 ++++++++++++++++++ .../sis/internal/coverage/j2d/ImageLayout.java | 64 ++++++++++++++++----- .../sis/internal/coverage/j2d/PreferredSize.java | 67 ---------------------- 5 files changed, 111 insertions(+), 84 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java index e5ca996..b7b2f2a 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java @@ -40,7 +40,6 @@ import org.apache.sis.geometry.Shapes2D; import org.apache.sis.image.ImageProcessor; import org.apache.sis.internal.coverage.j2d.ColorModelType; import org.apache.sis.internal.coverage.j2d.ImageUtilities; -import org.apache.sis.internal.coverage.j2d.PreferredSize; import org.apache.sis.internal.system.Modules; import org.apache.sis.math.Statistics; import org.apache.sis.measure.Quantities; @@ -188,6 +187,7 @@ final class RenderingData implements Cloneable { selectedDerivative = Stretching.NONE; processor = new ImageProcessor(); processor.setErrorAction(ImageProcessor.ErrorAction.LOG); + processor.setImageResizingPolicy(ImageProcessor.Resizing.EXPAND); } /** @@ -314,9 +314,9 @@ final class RenderingData implements Cloneable { displayToObjective = AffineTransforms2D.castOrCopy(inverse); final MathTransform cornerToDisplay = MathTransforms.concatenate(cornerToObjective, objectiveToDisplay); final MathTransform displayToCenter = MathTransforms.concatenate(inverse, centerToObjective.inverse()); - final PreferredSize bounds = (PreferredSize) Shapes2D.transform( + final Rectangle bounds = (Rectangle) Shapes2D.transform( MathTransforms.bidimensional(cornerToDisplay), - ImageUtilities.getBounds(recoloredImage), new PreferredSize()); + ImageUtilities.getBounds(recoloredImage), new Rectangle()); /* * Apply a map projection on the image, then convert the floating point results to integer values * that we can use with IndexColorModel. diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageCombiner.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageCombiner.java index c503e0e..3b0d49c 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageCombiner.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageCombiner.java @@ -94,7 +94,7 @@ public class ImageCombiner implements Consumer { /** Creates a new layout which will request the specified sample model. */ Layout(final SampleModel sampleModel) { - super(null); + super(null, false); ArgumentChecks.ensureNonNull("sampleModel", sampleModel); this.sampleModel = sampleModel; minTile = new Point(); 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 eb95181..df2d310 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 @@ -66,6 +66,9 @@ import org.apache.sis.measure.Units; * {@linkplain #setCategoryColors(Function) Category colors} for mapping sample values * (identified by their range, name or unit of measurement) to colors. *
  • + * {@linkplain #setImageResizingPolicy(Resizing) Image resizing policy} to apply + * if a requested image size prevent the image to be tiled. + *
  • * {@linkplain #setPositionalAccuracyHints(Quantity...) Positional accuracy hints} * for enabling the use of faster algorithm when a lower accuracy is acceptable. *
  • @@ -154,6 +157,33 @@ public class ImageProcessor implements Cloneable { private ImageLayout layout; /** + * Whether {@code ImageProcessor} can produce an image of different size compared to requested size. + * An image may be resized if the requested size can not be subdivided into tiles of reasonable size. + * For example if the image width is a prime number, there is no way to divide the image horizontally with + * an integer number of tiles. The only way to get an integer number of tiles is to change the image size. + * + *

    The image resizing policy may be used by any operation that involve a {@linkplain #resample resampling}. + * If a resizing is applied, the new size will be written in the {@code bounds} argument (a {@link Rectangle}).

    + * + * @see #getImageResizingPolicy() + * @see #setImageResizingPolicy(Resizing) + */ + public enum Resizing { + /** + * Image size is unmodified; the requested value is used unconditionally. + * It may result in big tiles (potentially a single tile for the whole image) + * if the image size is not divisible by a tile size. + */ + NONE, + + /** + * Image size can be increased. {@code ImageProcessor} will try to increase + * by the smallest amount of pixels allowing the image to be subdivided in tiles. + */ + EXPAND + } + + /** * Interpolation to use during resample operations. * * @see #getInterpolation() @@ -382,6 +412,26 @@ public class ImageProcessor implements Cloneable { } /** + * Returns whether {@code ImageProcessor} can produce an image of different size compared to requested size. + * If this processor can use a different size, the enumeration value specifies what kind of changes may be applied. + * + * @return the image resizing policy. + */ + public synchronized Resizing getImageResizingPolicy() { + return layout.isBoundsAdjustmentAllowed ? Resizing.EXPAND : Resizing.NONE; + } + + /** + * Sets whether {@code ImageProcessor} can produce an image of different size compared to requested size. + * + * @param policy the new image resizing policy. + */ + public synchronized void setImageResizingPolicy(final Resizing policy) { + ArgumentChecks.ensureNonNull("policy", policy); + layout = (policy == Resizing.EXPAND) ? ImageLayout.SIZE_ADJUST : ImageLayout.DEFAULT; + } + + /** * Returns hints about the desired positional accuracy, in "real world" units or in pixel units. * This is an empty array by default, which means that {@code ImageProcessor} aims for the best * accuracy it can produce. If the returned array is non-empty and contains accuracies large enough, @@ -787,12 +837,15 @@ public class ImageProcessor implements Cloneable { *
      *
    • {@linkplain #getInterpolation() Interpolation method} (nearest neighbor, bilinear, etc).
    • *
    • {@linkplain #getFillValues() Fill values} for pixels outside source image.
    • + *
    • {@linkplain #getImageResizingPolicy() Image resizing policy} to apply + * if {@code bounds} size is not divisible by a tile size.
    • *
    • {@linkplain #getPositionalAccuracyHints() Positional accuracy hints} * for enabling faster resampling at the cost of lower precision.
    • *
    * * @param source the image to be resampled. * @param bounds domain of pixel coordinates of resampled image to create. + * Updated by this method if {@link Resizing#EXPAND} policy is applied. * @param toSource conversion of pixel coordinates from resampled image to {@code source} image. * @return resampled image (may be {@code source}). */ @@ -983,6 +1036,8 @@ public class ImageProcessor implements Cloneable { *
      *
    • {@linkplain #getInterpolation() Interpolation method} (nearest neighbor, bilinear, etc).
    • *
    • {@linkplain #getFillValues() Fill values} for pixels outside source image.
    • + *
    • {@linkplain #getImageResizingPolicy() Image resizing policy} to apply + * if {@code bounds} size is not divisible by a tile size.
    • *
    • {@linkplain #getPositionalAccuracyHints() Positional accuracy hints} * for enabling faster resampling at the cost of lower precision.
    • *
    • {@linkplain #getCategoryColors() Category colors}.
    • @@ -990,6 +1045,7 @@ public class ImageProcessor implements Cloneable { * * @param source the image to be resampled and recolored. * @param bounds domain of pixel coordinates of resampled image to create. + * Updated by this method if {@link Resizing#EXPAND} policy is applied. * @param toSource conversion of pixel coordinates from resampled image to {@code source} image. * @param ranges description of {@code source} bands, or {@code null} if none. This is typically * obtained by {@link org.apache.sis.coverage.grid.GridCoverage#getSampleDimensions()}. diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageLayout.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageLayout.java index 449b1de..dd1656a 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageLayout.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageLayout.java @@ -55,7 +55,12 @@ public class ImageLayout { * The default instance which will target {@value ImageUtilities#DEFAULT_TILE_SIZE} pixels as tile * width and height. */ - public static final ImageLayout DEFAULT = new ImageLayout(null); + public static final ImageLayout DEFAULT = new ImageLayout(null, false); + + /** + * Same as {@link #DEFAULT}, but makes image size an integer amount of tiles. + */ + public static final ImageLayout SIZE_ADJUST = new ImageLayout(null, true); /** * Preferred size for tiles. @@ -65,11 +70,23 @@ public class ImageLayout { private final int preferredTileWidth, preferredTileHeight; /** + * Whether image size can be modified if needed. Changes are applied only if an image can not be tiled + * because {@link #suggestTileSize(int, int, boolean)} can not find a tile size close to the desired size. + * For example if the image width is a prime number, there is no way to divide the image horizontally with + * an integer number of tiles. The only way to get an integer number of tiles is to change the image size. + * + *

      If this flag is {@code true}, then the {@code bounds} argument given to the + * {@link #suggestTileSize(RenderedImage, Rectangle, boolean)} will be modified in-place.

      + */ + public final boolean isBoundsAdjustmentAllowed; + + /** * Creates a new image layout. * - * @param preferredTileSize the preferred tile size, or {@code null} for the default size. + * @param preferredTileSize the preferred tile size, or {@code null} for the default size. + * @param isBoundsAdjustmentAllowed whether image size can be modified if needed. */ - public ImageLayout(final Dimension preferredTileSize) { + public ImageLayout(final Dimension preferredTileSize, final boolean isBoundsAdjustmentAllowed) { if (preferredTileSize != null) { preferredTileWidth = preferredTileSize.width; preferredTileHeight = preferredTileSize.height; @@ -77,6 +94,7 @@ public class ImageLayout { preferredTileWidth = ImageUtilities.DEFAULT_TILE_SIZE; preferredTileHeight = ImageUtilities.DEFAULT_TILE_SIZE; } + this.isBoundsAdjustmentAllowed = isBoundsAdjustmentAllowed; } /** @@ -189,11 +207,12 @@ public class ImageLayout { * * @param image the image for which to derive a tile size, or {@code null}. * @param bounds the bounds of the image to create, or {@code null} if same as {@code image}. + * @param allowPartialTiles whether to allow tiles that are only partially filled. + * This argument is ignored (reset to {@code false}) if the given image is opaque. * @return suggested tile size for the given image. */ - public Dimension suggestTileSize(final RenderedImage image, final Rectangle bounds) { - boolean allowPartialTiles = (bounds instanceof PreferredSize); - if (allowPartialTiles && image != null) { + public Dimension suggestTileSize(final RenderedImage image, final Rectangle bounds, boolean allowPartialTiles) { + if (allowPartialTiles && image != null && !isBoundsAdjustmentAllowed) { final ColorModel cm = image.getColorModel(); allowPartialTiles = (cm != null); if (allowPartialTiles) { @@ -228,15 +247,33 @@ public class ImageLayout { final Dimension tileSize = new Dimension( toTileSize(width, preferredTileWidth, allowPartialTiles & singleXTile), toTileSize(height, preferredTileHeight, allowPartialTiles & singleYTile)); - if (allowPartialTiles) { - ((PreferredSize) bounds).makeDivisible(tileSize); + /* + * Optionally adjust the image bounds for making it divisible by the tile size. + */ + if (isBoundsAdjustmentAllowed && bounds != null && !bounds.isEmpty()) { + final int sx = sizeToAdd(bounds.width, tileSize.width); + final int sy = sizeToAdd(bounds.height, tileSize.height); + if ((bounds.width += sx) < 0) bounds.width -= tileSize.width; // if (overflow) reduce to valid range. + if ((bounds.height += sy) < 0) bounds.height -= tileSize.height; + bounds.translate(-sx/2, -sy/2); } return tileSize; } /** - * Creates a banded sample model of the given type with a {@linkplain #suggestTileSize(RenderedImage, Rectangle) - * the suggested size} for the given image. + * Computes the size to add to the width or height for making it divisible by the given tile size. + */ + private static int sizeToAdd(int size, final int tileSize) { + size %= tileSize; + if (size != 0) { + size = tileSize - size; + } + return size; + } + + /** + * Creates a banded sample model of the given type with + * {@linkplain #suggestTileSize(RenderedImage, Rectangle, boolean) the suggested tile size} for the given image. * * @param type desired data type as a {@link java.awt.image.DataBuffer} constant. * @param numBands desired number of bands. @@ -247,7 +284,7 @@ public class ImageLayout { public BandedSampleModel createBandedSampleModel(final int type, final int numBands, final RenderedImage image, final Rectangle bounds) { - final Dimension tile = suggestTileSize(image, bounds); + final Dimension tile = suggestTileSize(image, bounds, isBoundsAdjustmentAllowed); return RasterFactory.unique(new BandedSampleModel(type, tile.width, tile.height, numBands)); } @@ -265,7 +302,7 @@ public class ImageLayout { */ public SampleModel createCompatibleSampleModel(final RenderedImage image, final Rectangle bounds) { ArgumentChecks.ensureNonNull("image", image); - final Dimension tile = suggestTileSize(image, bounds); + final Dimension tile = suggestTileSize(image, bounds, isBoundsAdjustmentAllowed); SampleModel sm = image.getSampleModel(); if (sm.getWidth() != tile.width || sm.getHeight() != tile.height) { sm = sm.createCompatibleSampleModel(tile.width, tile.height); @@ -292,6 +329,7 @@ public class ImageLayout { @Override public String toString() { return Strings.toString(getClass(), - "preferredTileSize", new StringBuilder().append(preferredTileWidth).append('×').append(preferredTileHeight)); + "preferredTileSize", new StringBuilder().append(preferredTileWidth).append('×').append(preferredTileHeight), + "isBoundsAdjustmentAllowed", isBoundsAdjustmentAllowed); } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/PreferredSize.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/PreferredSize.java deleted file mode 100644 index 06f564d..0000000 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/PreferredSize.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.awt.Dimension; -import java.awt.Rectangle; - - -/** - * Specifies an image size which can be modified by {@link ImageLayout} if needed. Changes are applied only if - * an image can not be tiled because {@link ImageLayout} can not find a tile size close to the desired size. - * For example if the image width is a prime number, there is no way to divide the image horizontally with - * an integer number of tiles. The only way to get an integer number of tiles is to change the image size. - * The use of this class is understood by {@link ImageLayout} as a permission to do so. - * - * @author Martin Desruisseaux (Geomatys) - * @version 1.1 - * @since 1.1 - * @module - */ -@SuppressWarnings("serial") // Not intended to be serialized. -public final class PreferredSize extends Rectangle { - /** - * Creates a new rectangle. - */ - public PreferredSize() { - } - - /** - * Adjusts the bounds for making it divisible by the given tile size. - */ - final void makeDivisible(final Dimension tileSize) { - if (!isEmpty()) { - final int sx = sizeToAdd(width, tileSize.width); - final int sy = sizeToAdd(height, tileSize.height); - if ((width += sx) < 0) width -= tileSize.width; // if (overflow) reduce to valid range. - if ((height += sy) < 0) height -= tileSize.height; - if (x < (x -= sx/2)) x = Integer.MIN_VALUE; // if (overflow) set to minimal value. - if (y < (y -= sy/2)) y = Integer.MIN_VALUE; - } - } - - /** - * Computes the size to add to the width or height for making it divisible by the given tile size. - */ - private static int sizeToAdd(int size, final int tileSize) { - size %= tileSize; - if (size != 0) { - size = tileSize - size; - } - return size; - } -}