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 ebcc3ec698 Fix an `AssertionError` when using `BandedSampleConverter`
with a tile size which is not a divisor of the source image size.
ebcc3ec698 is described below
commit ebcc3ec698c6cda9c21ab1161fea8ddea7d298b0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Dec 2 12:00:14 2025 +0100
Fix an `AssertionError` when using `BandedSampleConverter` with a tile size
which is not a divisor of the source image size.
---
.../apache/sis/image/BandedSampleConverter.java | 63 ++++++++++++++++------
.../main/org/apache/sis/image/ImageProcessor.java | 10 ++--
.../main/org/apache/sis/image/MaskedImage.java | 4 +-
.../main/org/apache/sis/image/Transferer.java | 23 +++++---
.../main/org/apache/sis/image/Visualization.java | 2 +-
.../sis/image/BandedSampleConverterTest.java | 3 +-
.../apache/sis/storage/geotiff/GeoTiffStore.java | 5 +-
7 files changed, 78 insertions(+), 32 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedSampleConverter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedSampleConverter.java
index a5a0c78fc7..e85f4c51a5 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedSampleConverter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedSampleConverter.java
@@ -36,6 +36,7 @@ import
org.apache.sis.coverage.internal.shared.SampleDimensions;
import org.apache.sis.image.internal.shared.ImageUtilities;
import org.apache.sis.image.internal.shared.TileOpExecutor;
import org.apache.sis.image.internal.shared.ColorScaleBuilder;
+import org.apache.sis.image.internal.shared.FillValues;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.Disposable;
import org.apache.sis.util.logging.Logging;
@@ -104,6 +105,12 @@ class BandedSampleConverter extends WritableComputedImage {
*/
private final double[] sampleResolutions;
+ /**
+ * Values to assign to the pixels outside source image.
+ * Can be {@code null} if the target image is fully contained inside the
source image.
+ */
+ private final FillValues fillValues;
+
/**
* Creates a new image which will compute values using the given
converters.
*
@@ -114,17 +121,29 @@ class BandedSampleConverter extends WritableComputedImage
{
* @param converters the transfer functions to apply on each band of
the source image.
* If this array was a user-provided parameter,
should be cloned by caller.
* @param sampleDimensions description of conversion result, or {@code
null} if unknown.
+ * @param fillValues values to assign to pixels outside source image,
or {@code null} for default values.
*/
- private BandedSampleConverter(final RenderedImage source, final
BandedSampleModel sampleModel,
- final ColorModel colorModel, final
NumberRange<?>[] ranges,
- final MathTransform1D[] converters,
- final List<SampleDimension> sampleDimensions)
+ private BandedSampleConverter(final RenderedImage source,
+ final BandedSampleModel sampleModel,
+ final ColorModel colorModel,
+ final NumberRange<?>[] ranges,
+ final MathTransform1D[] converters,
+ final List<SampleDimension> sampleDimensions,
+ final Number[] fillValues)
{
super(sampleModel, source);
- this.colorModel = colorModel;
- this.converters = converters;
+ this.colorModel = colorModel;
+ this.converters = converters;
this.sampleDimensions = sampleDimensions;
ensureCompatible(sampleModel, colorModel);
+ FillValues fill = null;
+ if (!ImageUtilities.getBounds(source).contains(super.getBounds())) {
+ fill = new FillValues(sampleModel, fillValues, true);
+ if (fill.isFullyZero) {
+ fill = null;
+ }
+ }
+ this.fillValues = fill;
/*
* Get an estimation of the resolution, arbitrarily looking in the
middle of the range of values.
* If the converters are linear (which is the most common case), the
middle value does not matter
@@ -197,13 +216,18 @@ class BandedSampleConverter extends WritableComputedImage
{
* @param converters the transfer functions to apply on each band of
the source image.
* @param targetType the type of this image resulting from conversion
of given image.
* @param colorizer provider of color model for the expected range of
values, or {@code null}.
+ * @param fillValues values to assign to pixels outside source image,
or {@code null} for default values.
* @return the image which compute converted values from the given source.
*
* @see ImageProcessor#convert(RenderedImage, NumberRange[],
MathTransform1D[], DataType)
*/
- static BandedSampleConverter create(RenderedImage source, final
ImageLayout layout,
- final NumberRange<?>[] sourceRanges, final MathTransform1D[]
converters,
- final DataType targetType, final Colorizer colorizer)
+ static BandedSampleConverter create(RenderedImage source,
+ final ImageLayout layout,
+ final NumberRange<?>[] sourceRanges,
+ final MathTransform1D[] converters,
+ final DataType targetType,
+ final Colorizer colorizer,
+ final Number[] fillValues)
{
/*
* Since this operation applies its own ColorModel anyway, skip
operation that was doing nothing else
@@ -250,11 +274,12 @@ class BandedSampleConverter extends WritableComputedImage
{
for (int i=0; i<numBands; i++) {
inverses[i] = converters[i].inverse();
}
- return new Writable((WritableRenderedImage) source, sampleModel,
colorModel, sourceRanges, converters, inverses, sampleDimensions);
+ return new Writable((WritableRenderedImage) source, sampleModel,
colorModel,
+ sourceRanges, converters, inverses,
sampleDimensions, fillValues);
} catch (NoninvertibleTransformException e) {
Logging.recoverableException(LOGGER, ImageProcessor.class,
"convert", e);
}
- return new BandedSampleConverter(source, sampleModel, colorModel,
sourceRanges, converters, sampleDimensions);
+ return new BandedSampleConverter(source, sampleModel, colorModel,
sourceRanges, converters, sampleDimensions, fillValues);
}
/**
@@ -391,7 +416,7 @@ class BandedSampleConverter extends WritableComputedImage {
if (target == null) {
target = createTile(tileX, tileY);
}
- Transferer.create(getSource(), target).compute(converters);
+ Transferer.create(getSource(), target, fillValues).compute(converters);
return target;
}
@@ -424,12 +449,16 @@ class BandedSampleConverter extends WritableComputedImage
{
/**
* Creates a new writable image which will compute values using the
given converters.
*/
- Writable(final WritableRenderedImage source, final BandedSampleModel
sampleModel,
- final ColorModel colorModel, final NumberRange<?>[] ranges,
- final MathTransform1D[] converters, final MathTransform1D[]
inverses,
- final List<SampleDimension> sampleDimensions)
+ Writable(final WritableRenderedImage source,
+ final BandedSampleModel sampleModel,
+ final ColorModel colorModel,
+ final NumberRange<?>[] ranges,
+ final MathTransform1D[] converters,
+ final MathTransform1D[] inverses,
+ final List<SampleDimension> sampleDimensions,
+ final Number[] fillValues)
{
- super(source, sampleModel, colorModel, ranges, converters,
sampleDimensions);
+ super(source, sampleModel, colorModel, ranges, converters,
sampleDimensions, fillValues);
this.inverses = inverses;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
index 174f50b33d..3202254ded 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
@@ -1115,13 +1115,15 @@ public class ImageProcessor implements Cloneable {
ArgumentChecks.ensureNonNullElement("converters", i,
converters[i]);
}
final ImageLayout layout;
- final Colorizer colorizer;
+ final Colorizer colorizer;
+ final Number[] fillValues;
synchronized (this) {
- layout = this.layout;
- colorizer = this.colorizer;
+ layout = this.layout;
+ colorizer = this.colorizer;
+ fillValues = this.fillValues;
}
// No need to clone `sourceRanges` because it is not stored by
`BandedSampleConverter`.
- return unique(BandedSampleConverter.create(source, layout,
sourceRanges, converters, targetType, colorizer));
+ return unique(BandedSampleConverter.create(source, layout,
sourceRanges, converters, targetType, colorizer, fillValues));
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/MaskedImage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/MaskedImage.java
index 2c1412ed79..a3f325f1b1 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/MaskedImage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/MaskedImage.java
@@ -180,6 +180,7 @@ final class MaskedImage extends SourceAlignedImage {
private synchronized ByteBuffer getMask() {
ByteBuffer mask;
if (maskRef == null || (mask = maskRef.get()) == null) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final Rectangle maskBounds = this.maskBounds;
int size = ceilDiv(maskBounds.width, Byte.SIZE) *
maskBounds.height;
final int r = size & (Long.BYTES - 1);
@@ -250,6 +251,7 @@ final class MaskedImage extends SourceAlignedImage {
* Tile may intersect the mask. Computation is necessary, but we may
discover at
* the end of this method that the result is still an empty tile or
source tile.
*/
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final Rectangle maskBounds = this.maskBounds;
final LongBuffer mask = getMask().asLongBuffer();
final int xmax = xmin + source.getTileWidth();
@@ -466,7 +468,7 @@ complete: for (int border = 0; ; border++) {
@Override
public boolean equals(final Object object) {
if (super.equals(object)) {
- final MaskedImage other = (MaskedImage) object;
+ final var other = (MaskedImage) object;
return clip.equals(other.clip) &&
fillValues.equals(other.fillValues);
}
return false;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Transferer.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Transferer.java
index 79c7aedeb4..c86e539bdc 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Transferer.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Transferer.java
@@ -29,6 +29,7 @@ import java.awt.image.RasterFormatException;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.image.internal.shared.ImageUtilities;
+import org.apache.sis.image.internal.shared.FillValues;
import org.apache.sis.system.Configuration;
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.util.internal.shared.Numerics;
@@ -537,17 +538,27 @@ abstract class Transferer {
* Suggests a strategy for transferring data from the given source image
to the given target.
* This method assumes that the source image uses the same pixel
coordinate system than the
* target raster (i.e. that pixels at the same coordinates are at the same
location on Earth).
- * It also assumes that the target tile is fully included in the bounds of
a single source tile.
- * That later condition is met if the target grid tiles has been created
by {@link ImageLayout}.
*
- * @param source image from which to read sample values.
- * @param target image tile where to write sample values after
processing.
+ * <p>The target tile should be fully included in the bounds of a single
source image's tile.
+ * That later condition is met if the target tile matrix was computed by
{@link ImageLayout}.
+ * However, the target tile may be larger in the last row and last column
of the tile matrix
+ * if {@link ImageLayout#isImageBoundsAdjustmentAllowed} was {@code true}.
This method clips
+ * the area of interest for that reason.</p>
+ *
+ * @param source image from which to read sample values.
+ * @param target image tile where to write sample values after
processing.
+ * @param fillValues the fill values, or {@code null} if none.
* @return object to use for applying the operation.
*/
- static Transferer create(final RenderedImage source, final WritableRaster
target) {
+ static Transferer create(final RenderedImage source, final WritableRaster
target, final FillValues fillValues) {
+ final Rectangle aoi = target.getBounds();
+ ImageUtilities.clipBounds(source, aoi);
+ if (fillValues != null && (aoi.width != target.getWidth() ||
aoi.height != target.getHeight())) {
+ fillValues.fill(target);
+ }
int tileX = ImageUtilities.pixelToTileX(source, target.getMinX());
int tileY = ImageUtilities.pixelToTileY(source, target.getMinY());
- return create(source.getTile(tileX, tileY), target,
target.getBounds());
+ return create(source.getTile(tileX, tileY), target, aoi);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Visualization.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Visualization.java
index 6a7b97239c..002563ee75 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Visualization.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Visualization.java
@@ -529,7 +529,7 @@ final class Visualization extends ResampledImage {
tile = createTile(tileX, tileY);
}
// Conversion only, when no resampling is needed.
- Transferer.create(getSource(), tile).compute(converters);
+ Transferer.create(getSource(), tile, null).compute(converters);
return tile;
}
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedSampleConverterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedSampleConverterTest.java
index 42ffbd71c8..38ffe78401 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedSampleConverterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedSampleConverterTest.java
@@ -32,6 +32,7 @@ import static
org.apache.sis.feature.Assertions.assertValuesEqual;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class BandedSampleConverterTest extends ImageTestCase {
/**
* Size of tiles in this test. The width should be different than the
height
@@ -70,7 +71,7 @@ public final class BandedSampleConverterTest extends
ImageTestCase {
source.initializeAllTiles(0);
image = BandedSampleConverter.create(source, ImageLayout.DEFAULT, null,
new MathTransform1D[] {(MathTransform1D)
MathTransforms.linear(scale, 0)},
- targetType, null);
+ targetType, null, null);
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 84004a13d4..f806703e7d 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -702,13 +702,14 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
*
* @since 1.5
*/
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
public synchronized GridCoverageResource append(final RenderedImage image,
final GridGeometry grid, final Metadata metadata)
throws DataStoreException
{
final int index;
try {
- @SuppressWarnings("LocalVariableHidesMemberVariable") final Writer
writer = writer();
- @SuppressWarnings("LocalVariableHidesMemberVariable") final Reader
reader = this.reader;
+ final Reader reader = this.reader;
+ final Writer writer = writer();
writer.synchronize(reader, false);
final long offsetIFD;
try {