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 2025/01/24 15:00:06 UTC

(sis) 01/04: Fix an `AssertionError` when writing a `MultiPixelPackedSampleModel`.

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 <ma...@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());
+    }
 }