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