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

Reply via email to