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 d7d2aed41e4b997b2c5969c579243f2c68e06fd5 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Jan 24 11:15:25 2025 +0100 Fix an `AssertionError` when writing a `MultiPixelPackedSampleModel`. --- .../apache/sis/io/stream/HyperRectangleWriter.java | 34 ++++++++++++++--- .../sis/io/stream/HyperRectangleWriterTest.java | 44 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java index 61125670af..2929424305 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java @@ -28,6 +28,8 @@ import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.SinglePixelPackedSampleModel; import java.awt.image.RasterFormatException; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.pending.jdk.JDK18; /** @@ -113,6 +115,11 @@ public class HyperRectangleWriter { */ private long length; + /** + * The number of bits in a pixel, or 0 if the sample model is not multi-pixel packed. + */ + private int pixelBitStride; + /** * Number of elements (not necessarily bytes) between a pixel and the next pixel. * @@ -213,20 +220,34 @@ public class HyperRectangleWriter { * @return writer for rasters using the specified sample model (never {@code null}). */ private HyperRectangleWriter create(final SampleModel sm, final int[] bandOffsets) { - final long[] sourceSize = {scanlineStride, sm.getHeight()}; + final int width, height; + ArgumentChecks.ensureStrictlyPositive("width", width = sm.getWidth()); + ArgumentChecks.ensureStrictlyPositive("height", height = sm.getHeight()); + ArgumentChecks.ensureStrictlyPositive("scanlineStride", scanlineStride); // May be less than `width`. + ArgumentChecks.ensureBetween("pixelStride", 1, scanlineStride, pixelStride); + final long[] sourceSize = { + scanlineStride, + height + }; if (region == null) { - region = new Rectangle(sm.getWidth(), sm.getHeight()); + region = new Rectangle(width, height); } - final long[] regionLower = new long[] { + final long[] regionLower = { region.x - (long) sampleModelTranslateX, region.y - (long) sampleModelTranslateY }; - final long[] regionUpper = new long[] { + final long[] regionUpper = { regionLower[0] + region.width, regionLower[1] + region.height }; - regionLower[0] = Math.multiplyExact(regionLower[0], pixelStride); - regionUpper[0] = Math.multiplyExact(regionUpper[0], pixelStride); + regionLower[0] *= pixelStride; // Should not overflow for reasonable values of pixel stride. + regionUpper[0] *= pixelStride; + if (pixelBitStride != 0) { + final int dataSize = DataBuffer.getDataTypeSize(sm.getDataType()); + ArgumentChecks.ensureBetween("pixelBitStride", 1, dataSize, pixelBitStride); + regionLower[0] = Math.floorDiv(regionLower[0] * pixelBitStride, dataSize); + regionUpper[0] = JDK18.ceilDiv(regionUpper[0] * pixelBitStride, dataSize); + } var subset = new Region(sourceSize, regionLower, regionUpper, new long[] {1,1}); length = subset.length; if (bandOffsets == null || (bandOffsets.length == pixelStride && ArraysExt.isRange(0, bandOffsets))) { @@ -368,6 +389,7 @@ public class HyperRectangleWriter { bankIndices = new int[1]; // Length is NOT the number of bands. bankOffsets = bankIndices; pixelStride = 1; + pixelBitStride = sm.getPixelBitStride(); scanlineStride = sm.getScanlineStride(); if (isSupported(this, sm)) { return create(sm, null); diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/HyperRectangleWriterTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/HyperRectangleWriterTest.java index 9104b0cbff..5a01052ea7 100644 --- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/HyperRectangleWriterTest.java +++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/HyperRectangleWriterTest.java @@ -22,6 +22,10 @@ import java.lang.reflect.Array; import java.util.Random; import java.util.function.IntFunction; import java.util.function.ToDoubleFunction; +import java.awt.image.Raster; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.util.Arrays; // Test dependencies import org.junit.jupiter.api.Test; @@ -204,4 +208,44 @@ public final class HyperRectangleWriterTest extends TestCase { writer.write(output, source, offset); verifyWrittenBytes(ByteBuffer::getDouble, Double.BYTES); } + + /** + * Tests writing a binary image through the builder. + * + * @throws IOException should never happen since we are writing in memory. + */ + @Test + public void testBinaryImage() throws IOException { + final var image = new BufferedImage(10, 4, BufferedImage.TYPE_BYTE_BINARY); + final var tile = image.getRaster(); + for (int y = tile.getHeight(); --y >= 0;) { + for (int x = tile.getWidth(); --x >= 0;) { + tile.setSample(x, y, 0, x ^ y); + } + } + assertArrayEquals(new byte[] { + (byte) 0b01010101, (byte) 0b01000000, + (byte) 0b10101010, (byte) 0b10000000, + (byte) 0b01010101, (byte) 0b01000000, + (byte) 0b10101010, (byte) 0b10000000, + }, writePixelValues(tile)); + } + + /** + * Writes the pixel values of the given raster and returns the written bytes. + * This method assumes that the raster uses {@link DataBufferByte}. + * + * @param tile the tile to write. + * @return written pixel values. + * @throws IOException should never happen since we are writing in memory. + */ + private byte[] writePixelValues(final Raster tile) throws IOException { + final var container = new ByteArrayChannel(new byte[40], false); + output = new ChannelDataOutput("Test", container, ByteBuffer.allocate(20)); + writer = new HyperRectangleWriter.Builder().create(tile, -1, -1); + writer.write(output, ((DataBufferByte) tile.getDataBuffer()).getData(), 0, false); + output.flush(); + actual = container.toBuffer(); + return Arrays.copyOfRange(actual.array(), 0, actual.limit()); + } }