You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/11/03 11:50:45 UTC
[sis] 02/03: Replace `Visualization.create(…)` method by a builder. It simplifies some code and made easier to fix a bug related to mismatched tile layout in the `converters == null` optimization case.
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 381fe9d9cade15ffdbc74511d78d18b698b106d5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Nov 3 11:36:23 2021 +0100
Replace `Visualization.create(…)` method by a builder.
It simplifies some code and made easier to fix a bug related to mismatched tile layout in the `converters == null` optimization case.
---
.../java/org/apache/sis/image/ImageProcessor.java | 51 +--
.../java/org/apache/sis/image/Visualization.java | 420 +++++++++++++--------
.../sis/internal/coverage/j2d/ImageLayout.java | 40 ++
3 files changed, 321 insertions(+), 190 deletions(-)
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 08866cb..866e754 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
@@ -983,11 +983,7 @@ public class ImageProcessor implements Cloneable {
public RenderedImage visualize(final RenderedImage source, final Map<NumberRange<?>,Color[]> colors) {
ArgumentChecks.ensureNonNull("source", source);
ArgumentChecks.ensureNonNull("colors", colors);
- try {
- return Visualization.create(this, null, source, null, null, colors.entrySet());
- } catch (IllegalStateException | NoninvertibleTransformException e) {
- throw new IllegalArgumentException(Resources.format(Resources.Keys.UnconvertibleSampleValues), e);
- }
+ return visualize(new Visualization.Builder(source, colors.entrySet()));
}
/**
@@ -1024,11 +1020,7 @@ public class ImageProcessor implements Cloneable {
*/
public RenderedImage visualize(final RenderedImage source, final List<SampleDimension> ranges) {
ArgumentChecks.ensureNonNull("source", source);
- try {
- return Visualization.create(this, null, source, null, ranges, null);
- } catch (IllegalStateException | NoninvertibleTransformException e) {
- throw new IllegalArgumentException(Resources.format(Resources.Keys.UnconvertibleSampleValues), e);
- }
+ return visualize(new Visualization.Builder(null, source, null, ranges));
}
/**
@@ -1076,38 +1068,25 @@ public class ImageProcessor implements Cloneable {
ArgumentChecks.ensureNonNull("bounds", bounds);
ArgumentChecks.ensureNonNull("toSource", toSource);
ensureNonEmpty(bounds);
- try {
- return Visualization.create(this, bounds, source, toSource, ranges, null);
- } catch (IllegalStateException | NoninvertibleTransformException e) {
- throw new IllegalArgumentException(Resources.format(Resources.Keys.UnconvertibleSampleValues), e);
- }
+ return visualize(new Visualization.Builder(bounds, source, toSource, ranges));
}
/**
- * Callback method for {@link Visualization}.
- *
- * @param source image to be resampled and converted.
- * @param toSource conversion of pixel coordinates of this image to pixel coordinates of {@code source} image.
- * @param converters transfer functions to apply on each band of the source image. This array is not cloned.
- * @param bounds domain of pixel coordinates of this image, or {@code null} if same as {@code source} image.
- * @param colorModel color model of the image to create.
+ * Finishes builder configuration and creates the {@link Visualization} image.
*/
- final RenderedImage resampleAndConvert(final RenderedImage source, final MathTransform toSource,
- final MathTransform1D[] converters, final Rectangle bounds, final ColorModel colorModel)
- {
- final ImageLayout layout;
- final Interpolation interpolation;
- final Number[] fillValues;
- final Quantity<?>[] positionalAccuracyHints;
- ensureNonEmpty(bounds);
+ private RenderedImage visualize(final Visualization.Builder builder) {
synchronized (this) {
- layout = this.layout;
- interpolation = this.interpolation;
- fillValues = this.fillValues;
- positionalAccuracyHints = this.positionalAccuracyHints;
+ builder.layout = layout;
+ builder.interpolation = interpolation;
+ builder.categoryColors = colors;
+ builder.fillValues = fillValues;
+ builder.positionalAccuracyHints = positionalAccuracyHints;
+ }
+ try {
+ return builder.create(this);
+ } catch (IllegalStateException | NoninvertibleTransformException e) {
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.UnconvertibleSampleValues), e);
}
- return unique(new Visualization(source, layout, bounds, toSource, toSource.isIdentity(),
- interpolation, converters, fillValues, colorModel, positionalAccuracyHints));
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
index 807f3ba..785056c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
@@ -19,17 +19,20 @@ package org.apache.sis.image;
import java.util.Map;
import java.util.List;
import java.util.Collection;
+import java.util.function.Function;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
+import java.awt.image.SampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.RenderedImage;
import java.awt.image.DataBuffer;
import java.nio.DoubleBuffer;
import javax.measure.Quantity;
+import org.apache.sis.coverage.Category;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
@@ -50,65 +53,252 @@ import org.apache.sis.util.collection.BackingStoreException;
* Image generated for visualization purposes only (not to be used for computation purposes).
* This class merges {@link ResampledImage}, {@link BandedSampleConverter} and {@link RecoloredImage} operations
* in a single operation for efficiency. This merge avoids creating intermediate tiles of {@code float} values.
- * By writing directly {@code byte} values, we save memory and CPU since {@link WritableRaster#setPixel(int, int, int[])}
- * has more efficient implementations for integers.
+ * By writing directly {@code byte} values, we save memory and CPU because
+ * {@link WritableRaster#setPixel(int, int, int[])} has more efficient implementations for integers.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
final class Visualization extends ResampledImage {
/**
- * Transfer functions to apply on each band of the source image, or {@code null} if those conversions are done
- * by {@link InterpConvert}. Non-null array is used for allowing {@link #computeTile(int, int, WritableRaster)}
- * to use a shortcut avoiding {@link ResampledImage} cost. Outputs should be values in the [0 … 255] range;
- * values outside that ranges will be clamped.
+ * Builds an image where all sample values are indices of colors in an {@link IndexColorModel}.
+ * If the given image stores sample values as unsigned bytes or short integers, then those values
+ * are used as-is (they are not copied or converted). Otherwise {@link Visualization} will convert
+ * sample values to unsigned bytes in order to enable the use of {@link IndexColorModel}.
+ *
+ * <p>This builder accepts two kinds of input:</p>
+ * <ul>
+ * <li>Non-null {@code sourceBands} and {@link ImageProcessor#getCategoryColors()}.</li>
+ * <li>Non-null {@code rangesAndColors}.</li>
+ * </ul>
+ *
+ * The resulting image is suitable for visualization purposes but should not be used for computation purposes.
+ * There is no guarantees about the number of bands in returned image and the formulas used for converting
+ * floating point values to integer values.
+ *
+ * <h2>Resampling</h2>
+ * {@link Visualization} can optionally be combined with a {@link ResampledImage} operation.
+ * This can be done by providing a non-null value to the {@code toSource} argument.
+ *
+ * @see ImageProcessor#visualize(RenderedImage, Map)
*/
- private final MathTransform1D[] converters;
+ static final class Builder {
+ /** Number of bands of the image to create. */
+ private static final int NUM_BANDS = 1;
- /**
- * The color model for the expected range of values. Typically an {@link IndexColorModel} for byte values.
- * May be {@code null} if the color model is unknown.
- */
- private final ColorModel colorModel;
+ /** Band to make visible. */
+ private static final int VISIBLE_BAND = 0;
- /**
- * Creates a new image which will resample and convert values of the given image.
- * See parent class for more details on arguments.
- *
- * @param source image to be resampled and converted.
- * @param layout computer of tile size.
- * @param bounds domain of pixel coordinates of this image, or {@code null} if same as {@code source} image.
- * @param toSource conversion of pixel coordinates of this image to pixel coordinates of {@code source} image.
- * @param isIdentity value of {@code toSource.isIdentity()}.
- * @param interpolation object to use for performing interpolations.
- * @param converters transfer functions to apply on each band of the source image. This array is not cloned.
- * @param fillValues values to use for pixels in this image that can not be mapped to pixels in source image.
- * @param colorModel color model of this image.
- * @param accuracy values of {@value #POSITIONAL_ACCURACY_KEY} property, or {@code null} if none.
- */
- Visualization(final RenderedImage source, final ImageLayout layout, final Rectangle bounds,
- final MathTransform toSource, final boolean isIdentity, final Interpolation interpolation,
- final MathTransform1D[] converters, final Number[] fillValues, final ColorModel colorModel,
- final Quantity<?>[] accuracy)
- {
- super(source,
- layout.createBandedSampleModel(Colorizer.TYPE_COMPACT, converters.length, source, bounds),
- layout.getMinTile(), (bounds != null) ? bounds : ImageUtilities.getBounds(source),
- toSource,
- isIdentity ? Interpolation.NEAREST : combine(interpolation, converters),
- fillValues,
- accuracy);
-
- this.colorModel = colorModel;
- this.converters = isIdentity ? converters : null;
+ //// ┌─────────────────────────────────────┐
+ //// │ Arguments given by user │
+ //// └─────────────────────────────────────┘
+
+ /** Pixel coordinates of the visualization image, or {@code null} if same as {@link #source} image. */
+ private Rectangle bounds;
+
+ /** Image to be resampled and converted. */
+ private RenderedImage source;
+
+ /** Conversion from pixel coordinates of visualization image to pixel coordinates of {@link #source} image. */
+ private MathTransform toSource;
+
+ /** Description of {@link #source} bands, or {@code null} if none. */
+ private List<SampleDimension> sourceBands;
+
+ /** Colors to apply for range of sample values in source image, or {@code null} if none. */
+ private Collection<Map.Entry<NumberRange<?>,Color[]>> rangesAndColors;
+
+ //// ┌─────────────────────────────────────┐
+ //// │ Given by ImageProcesor.configure(…) │
+ //// └─────────────────────────────────────┘
+
+ /** Computer of tile size. */
+ ImageLayout layout;
+
+ /** Object to use for performing interpolations. */
+ Interpolation interpolation;
+
+ /** The colors to use for given categories of sample values, or {@code null} is unspecified. */
+ Function<Category,Color[]> categoryColors;
+
+ /** Values to use for pixels in this image that can not be mapped to pixels in source image. */
+ Number[] fillValues;
+
+ /** Values of {@value #POSITIONAL_ACCURACY_KEY} property, or {@code null} if none. */
+ Quantity<?>[] positionalAccuracyHints;
+
+ //// ┌─────────────────────────────────────┐
+ //// │ Computed by `create(…)` │
+ //// └─────────────────────────────────────┘
+
+ /** Transfer functions to apply on each band of the source image. */
+ private MathTransform1D[] converters;
+
+ /** Sample model of {@link Visualization} image. */
+ private SampleModel sampleModel;
+
+ /** Color model of {@link Visualization} image. */
+ private ColorModel colorModel;
+
+ /**
+ * Creates a builder for a visualization image with colors inferred from sample dimensions.
+ *
+ * @param bounds desired domain of pixel coordinates, or {@code null} if same as {@code source} image.
+ * @param source the image for which to replace the color model.
+ * @param toSource pixel coordinates conversion to {@code source} image, or {@code null} if none.
+ * @param sourceBands description of {@code source} bands.
+ */
+ Builder(final Rectangle bounds, final RenderedImage source, final MathTransform toSource,
+ final List<SampleDimension> sourceBands)
+ {
+ this.bounds = bounds;
+ this.source = source;
+ this.toSource = toSource;
+ this.sourceBands = sourceBands;
+ }
+
+ /**
+ * Creates a builder for a visualization image with colors specified for range of values.
+ * Current version assumes that target image bounds are the same than source image bounds
+ * and that there is no change of pixel coordinates, but this is not a real restriction.
+ * The {@code bounds} and {@code toSource} arguments could be added back in the future if useful.
+ *
+ * @param source the image for which to replace the color model.
+ * @param rangesAndColors range of sample values in source image associated to colors to apply.
+ */
+ Builder(final RenderedImage source, final Collection<Map.Entry<NumberRange<?>,Color[]>> rangesAndColors) {
+ this.source = source;
+ this.rangesAndColors = rangesAndColors;
+ }
+
+ /**
+ * Returns an image where all sample values are indices of colors in an {@link IndexColorModel}.
+ * If the source image stores sample values as unsigned bytes or short integers, then those values
+ * are used as-is (they are not copied or converted). Otherwise this operation will convert sample
+ * values to unsigned bytes in order to enable the use of {@link IndexColorModel}.
+ *
+ * <p>The resulting image is suitable for visualization but should not be used for computational purposes.
+ * There is no guarantees about the number of bands in returned image and the formulas used for converting
+ * floating point values to integer values.</p>
+ *
+ * <h4>Resampling</h4>
+ * This operation can optionally be combined with a {@link ResampledImage} operation.
+ * This can be done by providing a non-null value to the {@link #toSource} field.
+ *
+ * @param processor the processor invoking this constructor.
+ * @return resampled and recolored image for visualization purposes only.
+ * @throws NoninvertibleTransformException if sample values in source image
+ * can not be converted to sample values in the recolored image.
+ */
+ RenderedImage create(final ImageProcessor processor) throws NoninvertibleTransformException {
+ final int visibleBand = ImageUtilities.getVisibleBand(source);
+ if (visibleBand < 0) {
+ // This restriction may be relaxed in a future version if we implement conversion to RGB images.
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.OperationRequiresSingleBand));
+ }
+ /*
+ * Get a `Colorizer` which will compute the `ColorModel` of destination image.
+ * There is different ways to create colorizer, depending on which arguments
+ * were supplied by user. In precedence order:
+ *
+ * - rangesAndColor : Collection<Map.Entry<NumberRange<?>,Color[]>>
+ * - sourceBands : List<SampleDimension>
+ * - statistics
+ */
+ boolean initialized;
+ final Colorizer colorizer;
+ if (rangesAndColors != null) {
+ colorizer = new Colorizer(rangesAndColors);
+ initialized = true;
+ } else {
+ /*
+ * Ranges of sample values were not specified explicitly. Instead we will try to infer them
+ * in various ways: sample dimensions, scaled color model, statistics in last resort.
+ */
+ colorizer = new Colorizer(categoryColors);
+ initialized = (sourceBands != null) && colorizer.initialize(sourceBands.get(visibleBand));
+ if (initialized) {
+ /*
+ * If we have been able to configure Colorizer using the SampleModel, apply an adjustment based
+ * on the ScaledColorModel if it exists. Use case: an image is created with an IndexColorModel
+ * determined by the SampleModel, then user enhanced contrast by a call to `stretchColorRamp(…)`
+ * above. We want to preserve that contrast enhancement.
+ */
+ colorizer.rescaleMainRange(source.getColorModel());
+ } else {
+ /*
+ * If we have not been able to use the SampleDimension, try to use the ColorModel or SampleModel.
+ * There is no call to `rescaleMainRange(…)` because the following code already uses the range
+ * specified by the ColorModel, if available.
+ */
+ initialized = colorizer.initialize(source.getColorModel()) ||
+ colorizer.initialize(source.getSampleModel(), visibleBand);
+ }
+ }
+ source = BandSelectImage.create(source, new int[] {visibleBand}); // Make single-banded.
+ if (!initialized) {
+ /*
+ * If none of above Colorizer configurations worked, use statistics in last resort. We do that
+ * after we reduced the image to a single band, in order to reduce the amount of calculations.
+ */
+ final Statistics statistics = processor.valueOfStatistics(source, null)[VISIBLE_BAND];
+ colorizer.initialize(statistics.minimum(), statistics.maximum());
+ }
+ /*
+ * At this point we finished to configure the colorizer; we are ready to build the `ColorModel`.
+ * If the source image uses unsigned integer types and there is no resampling operation, we can
+ * update the color model without changing sample values. This is much cheaper and as accurate.
+ */
+ final int dataType = source.getSampleModel().getDataType();
+ if (dataType == DataBuffer.TYPE_BYTE || dataType == DataBuffer.TYPE_USHORT) {
+ if (toSource != null && !toSource.isIdentity()) {
+ source = processor.resample(source, bounds, toSource);
+ }
+ return RecoloredImage.create(source, colorizer.createColorModel(dataType, NUM_BANDS, VISIBLE_BAND));
+ }
+ /*
+ * If we reach this point, sample values need to be converted to integers in [0 … 255] range.
+ * Skip any previous `RecoloredImage` since we are replacing the `ColorModel` by a new one.
+ */
+ while (source instanceof RecoloredImage) {
+ source = ((RecoloredImage) source).source;
+ }
+ colorModel = colorizer.compactColorModel(NUM_BANDS, VISIBLE_BAND);
+ converters = new MathTransform1D[] {
+ colorizer.getSampleToIndexValues() // Must be after `compactColorModel(…)`.
+ };
+ /*
+ * If there is no conversion of pixel coordinates, there is no need for interpolations.
+ * In such case the `Visualization.computeTile(…)` implementation takes a shortcut which
+ * requires the tile layout of destination image to be the same as source image.
+ */
+ if (toSource == null) {
+ toSource = MathTransforms.identity(BIDIMENSIONAL);
+ }
+ if (toSource.isIdentity() && (bounds == null || ImageUtilities.getBounds(source).contains(bounds))) {
+ layout = ImageLayout.fixedSize(source);
+ interpolation = Interpolation.NEAREST;
+ } else {
+ interpolation = combine(interpolation, converters);
+ converters = null;
+ }
+ /*
+ * Final image creation after the tile layout has been chosen.
+ */
+ sampleModel = layout.createBandedSampleModel(Colorizer.TYPE_COMPACT, NUM_BANDS, source, bounds);
+ if (bounds == null) {
+ bounds = ImageUtilities.getBounds(source);
+ }
+ return ImageProcessor.unique(new Visualization(this));
+ }
}
/**
* Combines the given interpolation method with the given sample conversion.
*/
- static Interpolation combine(final Interpolation interpolation, final MathTransform1D[] converters) {
+ private static Interpolation combine(final Interpolation interpolation, final MathTransform1D[] converters) {
final MathTransform converter = CompoundTransform.create(converters);
if (converter.isIdentity()) {
return interpolation;
@@ -203,121 +393,43 @@ final class Visualization extends ResampledImage {
}
/**
- * Returns {@code true} if this image can not have mask.
+ * Transfer functions to apply on each band of the source image, or {@code null} if those conversions are done
+ * by {@link InterpConvert}. Non-null array is used for allowing {@link #computeTile(int, int, WritableRaster)}
+ * to use a shortcut avoiding {@link ResampledImage} cost. Outputs should be values in the [0 … 255] range;
+ * values outside that ranges will be clamped.
*/
- @Override
- final boolean hasNoMask() {
- return !(interpolation instanceof InterpConvert) && super.hasNoMask();
+ private final MathTransform1D[] converters;
+
+ /**
+ * The color model for the expected range of values. Typically an {@link IndexColorModel} for byte values.
+ * May be {@code null} if the color model is unknown.
+ */
+ private final ColorModel colorModel;
+
+ /**
+ * Creates a new image which will resample and convert values of the given image.
+ * See parent class for more details about arguments.
+ */
+ private Visualization(final Builder builder) {
+ super(builder.source,
+ builder.sampleModel,
+ builder.layout.getMinTile(),
+ builder.bounds,
+ builder.toSource,
+ builder.interpolation,
+ builder.fillValues,
+ builder.positionalAccuracyHints);
+
+ this.colorModel = builder.colorModel;
+ this.converters = builder.converters;
}
/**
- * Returns an image where all sample values are indices of colors in an {@link IndexColorModel}.
- * If the given image stores sample values as unsigned bytes or short integers, then those values
- * are used as-is (they are not copied or converted). Otherwise this operation will convert sample
- * values to unsigned bytes in order to enable the use of {@link IndexColorModel}.
- *
- * <p>This method accepts two kinds of input. Use only one of the followings:</p>
- * <ul>
- * <li>Non-null {@code sourceBands} and {@link ImageProcessor#getCategoryColors()}.</li>
- * <li>Non-null {@code rangesAndColors}.</li>
- * </ul>
- *
- * The resulting image is suitable for visualization purposes but should not be used for computation purposes.
- * There is no guarantees about the number of bands in returned image and the formulas used for converting
- * floating point values to integer values.
- *
- * <h4>Resampling</h4>
- * This operation can optionally be combined with a {@link ResampledImage} operation.
- * This can be done by providing a non-null value to the {@code toSource} argument.
- *
- * @param processor the processor invoking this method.
- * @param bounds desired domain of pixel coordinates, or {@code null} if same as {@code source} image.
- * @param source the image for which to replace the color model.
- * @param toSource pixel coordinates conversion to {@code source} image, or {@code null} if none.
- * @param sourceBands description of {@code source} bands, or {@code null} if none.
- * @param rangesAndColors range of sample values in source image associated to colors to apply,
- * or {@code null} for using {@code sourceBands} instead.
- * @return resampled and recolored image for visualization purposes only.
- * @throws NoninvertibleTransformException if sample values in source image can not be converted
- * to sample values in the recolored image.
- *
- * @see ImageProcessor#visualize(RenderedImage, Map)
+ * Returns {@code true} if this image can not have mask.
*/
- static RenderedImage create(final ImageProcessor processor, final Rectangle bounds,
- RenderedImage source, MathTransform toSource,
- final List<SampleDimension> sourceBands,
- final Collection<Map.Entry<NumberRange<?>,Color[]>> rangesAndColors)
- throws NoninvertibleTransformException
- {
- final int visibleBand = ImageUtilities.getVisibleBand(source);
- if (visibleBand < 0) {
- // This restriction may be relaxed in a future version if we implement conversion to RGB images.
- throw new IllegalArgumentException(Resources.format(Resources.Keys.OperationRequiresSingleBand));
- }
- boolean initialized;
- final Colorizer colorizer;
- if (rangesAndColors != null) {
- colorizer = new Colorizer(rangesAndColors);
- initialized = true;
- } else {
- /*
- * Ranges of sample values were not specified explicitly. Instead we will try to infer them
- * in various ways: sample dimensions, scaled color model, statistics in last resort.
- */
- colorizer = new Colorizer(processor.getCategoryColors());
- initialized = (sourceBands != null) && colorizer.initialize(sourceBands.get(visibleBand));
- if (initialized) {
- /*
- * If we have been able to configure Colorizer using the SampleModel, apply an adjustment based
- * on the ScaledColorModel if it exists. Use case: an image is created with an IndexColorModel
- * determined by the SampleModel, then user enhanced contrast by a call to `stretchColorRamp(…)`
- * above. We want to preserve that contrast enhancement.
- */
- colorizer.rescaleMainRange(source.getColorModel());
- } else {
- /*
- * If we have not been able to use the SampleDimension, try to use the ColorModel or SampleModel.
- * There is no call to `rescaleMainRange(…)` because the following code already uses the range
- * specified by the ColorModel, if available.
- */
- initialized = colorizer.initialize(source.getColorModel()) ||
- colorizer.initialize(source.getSampleModel(), visibleBand);
- }
- }
- source = BandSelectImage.create(source, new int[] {visibleBand}); // Make single-banded.
- if (!initialized) {
- /*
- * If none of above Colorizer configurations worked, use statistics in last resort. We do that
- * after we reduced the image to a single band, in order to reduce the amount of calculations.
- */
- final Statistics statistics = processor.valueOfStatistics(source, null)[0];
- colorizer.initialize(statistics.minimum(), statistics.maximum());
- }
- /*
- * If the source image uses unsigned integer types and there is no resampling operation, we can
- * update the color model without changing sample values. This is much cheaper and as accurate.
- */
- final int dataType = source.getSampleModel().getDataType();
- if (dataType == DataBuffer.TYPE_BYTE || dataType == DataBuffer.TYPE_USHORT) {
- if (toSource != null && !toSource.isIdentity()) {
- source = processor.resample(source, bounds, toSource);
- }
- return RecoloredImage.create(source, colorizer.createColorModel(dataType, 1, 0));
- }
- /*
- * If we reach this point, sample values need to be converted to integers in [0 … 255] range.
- * Skip any previous `RecoloredImage` since we are replacing the `ColorModel` by a new one.
- */
- while (source instanceof RecoloredImage) {
- source = ((RecoloredImage) source).source;
- }
- if (toSource == null) {
- toSource = MathTransforms.identity(BIDIMENSIONAL);
- }
- final ColorModel colorModel = colorizer.compactColorModel(1, 0); // Must be first.
- final MathTransform1D converter = colorizer.getSampleToIndexValues();
- return processor.resampleAndConvert(source, toSource,
- new MathTransform1D[] {converter}, bounds, colorModel);
+ @Override
+ final boolean hasNoMask() {
+ return !(interpolation instanceof InterpConvert) && super.hasNoMask();
}
/**
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 243fd3b..9bc28c9 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
@@ -99,6 +99,46 @@ public class ImageLayout {
}
/**
+ * Creates a new layout with exactly the tile size of given image.
+ *
+ * @param source image from which to take tile size and indices.
+ * @return layout giving exactly the tile size and indices of given image.
+ */
+ public static ImageLayout fixedSize(final RenderedImage source) {
+ return new FixedSize(source);
+ }
+
+ /**
+ * Override preferred tile size with a fixed size.
+ */
+ private static final class FixedSize extends ImageLayout {
+ /** Indices of the first tile. */
+ private final int xmin, ymin;
+
+ /** Creates a new layout with exactly the tile size of given image. */
+ FixedSize(final RenderedImage source) {
+ super(new Dimension(source.getTileWidth(), source.getTileHeight()), false);
+ xmin = source.getMinTileX();
+ ymin = source.getMinTileY();
+ }
+
+ /** Returns the fixed tile size. All parameters are ignored. */
+ @Override public Dimension suggestTileSize(int imageWidth, int imageHeight, boolean allowPartialTiles) {
+ return getPreferredTileSize();
+ }
+
+ /** Returns the fixed tile size. All parameters are ignored. */
+ @Override public Dimension suggestTileSize(RenderedImage image, Rectangle bounds, boolean allowPartialTiles) {
+ return getPreferredTileSize();
+ }
+
+ /** Returns indices of the first tile. */
+ @Override public Point getMinTile() {
+ return new Point(xmin, ymin);
+ }
+ }
+
+ /**
* Returns the preferred tile size. This is the dimension values specified at construction time.
*
* @return the preferred tile size.