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 1bcda2bd6b Use TIFF strips instead of tiles when the rows are not 
divided in tiles. The intend is to avoid the restriction about tile size 
multiple of 16 bytes.
1bcda2bd6b is described below

commit 1bcda2bd6b2aa10c43315781204df8a5287c0e1c
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Oct 31 23:25:03 2023 +0100

    Use TIFF strips instead of tiles when the rows are not divided in tiles.
    The intend is to avoid the restriction about tile size multiple of 16 bytes.
---
 .../org/apache/sis/storage/geotiff/Writer.java     | 56 ++++++++++++++++------
 .../sis/storage/geotiff/writer/TileMatrix.java     |  9 ++++
 .../org/apache/sis/storage/geotiff/WriterTest.java | 34 ++++++++-----
 3 files changed, 71 insertions(+), 28 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
index b0ec5408cc..95d3d27a08 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
@@ -106,12 +106,12 @@ final class Writer extends IOBase implements Flushable {
     }
 
     /**
-     * Minimal number of tags which will be written. This amount is for 
grayscale images with no metadata
-     * and no statistics. For RGB images, there is one more tag. For color 
maps, there is two more tags.
-     * This number is only a hint for avoiding the need to update this 
information if the number appears
-     * to be right.
+     * Common number of tags which will be written. This amount is for tiled 
grayscale images with no metadata
+     * and no statistics. For stripped images, there is one less tag. For RGB 
images, there is one more tag.
+     * For color maps, there is two more tags. This number is only a hint for 
avoiding the need to update
+     * this information if the number appears to be right.
      */
-    static final int MINIMAL_NUMBER_OF_TAGS = 16;
+    static final int COMMON_NUMBER_OF_TAGS = 16;
 
     /**
      * The processor to use for transforming the image before to write it.
@@ -329,7 +329,7 @@ final class Writer extends IOBase implements Flushable {
          * because the tags need to be written in increasing code order, which 
causes ColorModel-related tags
          * (for example) to be interleaved with other aspects.
          */
-        numberOfTags = MINIMAL_NUMBER_OF_TAGS;      // Only a guess at this 
stage. Real number computed later.
+        numberOfTags = COMMON_NUMBER_OF_TAGS;       // Only a guess at this 
stage. Real number computed later.
         if (compression.usePredictor()) numberOfTags++;
         final int colorInterpretation = image.getColorInterpretation();
         if (colorInterpretation == PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
@@ -381,6 +381,9 @@ final class Writer extends IOBase implements Flushable {
         final UpdatableWrite<?> tagCountWriter =
                 isBigTIFF ? UpdatableWrite.of(output, (long)  numberOfTags)
                           : UpdatableWrite.of(output, (short) numberOfTags);
+
+        final var tiling = new TileMatrix(image.visibleBands, numPlanes, 
bitsPerSample, offsetIFD,
+                                          compression.method, 
compression.level, compression.predictor);
         numberOfTags = 0;
         writeTag((short) TAG_NEW_SUBFILE_TYPE,           (short) 
TIFFTag.TIFF_LONG,  overview ? 1 : 0);
         writeTag((short) TAG_IMAGE_WIDTH,                (short) 
TIFFTag.TIFF_LONG,  image.visibleBands.getWidth());
@@ -391,7 +394,10 @@ final class Writer extends IOBase implements Flushable {
         writeTag((short) TAG_DOCUMENT_NAME,              /* TIFF_ASCII */      
      mf.series);
         writeTag((short) TAG_IMAGE_DESCRIPTION,          /* TIFF_ASCII */      
      mf.title);
         writeTag((short) TAG_MODEL,                      /* TIFF_ASCII */      
      mf.instrument);
+        writeTag((short) TAG_STRIP_OFFSETS,              /* TIFF_LONG  */      
      tiling, true);
         writeTag((short) TAG_SAMPLES_PER_PIXEL,          (short) 
TIFFTag.TIFF_SHORT, numBands);
+        writeTag((short) TAG_ROWS_PER_STRIP,             /* TIFF_LONG  */      
      tiling, true);
+        writeTag((short) TAG_STRIP_BYTE_COUNTS,          /* TIFF_LONG  */      
      tiling, true);
         writeTag((short) TAG_MIN_SAMPLE_VALUE,           /* TIFF_SHORT */      
      shortStats[0]);
         writeTag((short) TAG_MAX_SAMPLE_VALUE,           /* TIFF_SHORT */      
      shortStats[1]);
         writeTag((short) TAG_X_RESOLUTION,               /* TIFF_RATIONAL */   
      xres);
@@ -408,15 +414,13 @@ final class Writer extends IOBase implements Flushable {
         if (colorInterpretation == PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
             writeColorPalette((IndexColorModel) 
image.visibleBands.getColorModel(), 1L << bitsPerSample[0]);
         }
-        final var tiling = new TileMatrix(image.visibleBands, numPlanes, 
bitsPerSample, offsetIFD,
-                                          compression.method, 
compression.level, compression.predictor);
-        writeTag((short) TAG_TILE_WIDTH,  (short) TIFFTag.TIFF_LONG, 
tiling.tileWidth);
-        writeTag((short) TAG_TILE_LENGTH, (short) TIFFTag.TIFF_LONG, 
tiling.tileHeight);
-        tiling.offsetsTag = writeTag((short) TAG_TILE_OFFSETS, tiling.offsets);
-        tiling.lengthsTag = writeTag((short) TAG_TILE_BYTE_COUNTS, (short) 
TIFFTag.TIFF_LONG, tiling.lengths);
-        writeTag((short) TAG_SAMPLE_FORMAT,      (short) TIFFTag.TIFF_SHORT, 
sampleFormat);
-        writeTag((short) TAG_S_MIN_SAMPLE_VALUE, (short) TIFFTag.TIFF_FLOAT, 
statistics[0]);
-        writeTag((short) TAG_S_MAX_SAMPLE_VALUE, (short) TIFFTag.TIFF_FLOAT, 
statistics[1]);
+        writeTag((short) TAG_TILE_WIDTH,                 /* TIFF_LONG */       
      tiling, false);
+        writeTag((short) TAG_TILE_LENGTH,                /* TIFF_LONG */       
      tiling, false);
+        writeTag((short) TAG_TILE_OFFSETS,               /* TIFF_LONG */       
      tiling, false);
+        writeTag((short) TAG_TILE_BYTE_COUNTS,           /* TIFF_LONG */       
      tiling, false);
+        writeTag((short) TAG_SAMPLE_FORMAT,              (short) 
TIFFTag.TIFF_SHORT, sampleFormat);
+        writeTag((short) TAG_S_MIN_SAMPLE_VALUE,         (short) 
TIFFTag.TIFF_FLOAT, statistics[0]);
+        writeTag((short) TAG_S_MAX_SAMPLE_VALUE,         (short) 
TIFFTag.TIFF_FLOAT, statistics[1]);
         if (geoKeys != null) {
             writeTag((short) TAG_MODEL_TRANSFORMATION, (short) 
TIFFTag.TIFF_DOUBLE, geoKeys.modelTransformation());
             writeTag((short) TAG_GEO_KEY_DIRECTORY,    /* TIFF_SHORT */        
     geoKeys.keyDirectory());
@@ -437,6 +441,28 @@ final class Writer extends IOBase implements Flushable {
         return tiling;
     }
 
+    /**
+     * Writes a tag related to the location of the data. We use a separated 
method instead of
+     * inlining this code inside the {@code writeImageFileDirectory(…)} method 
for readability.
+     * It allows us to keep {@code writeImageFileDirectory(…)} formatted more 
like a table.
+     */
+    private void writeTag(final short tag, final TileMatrix tiling, final 
boolean useStrips) throws IOException {
+        if (tiling.useStrips() == useStrips) {
+            final int value;
+            switch (tag) {
+                case TAG_TILE_WIDTH:        value = tiling.tileWidth;  break;
+                case TAG_TILE_LENGTH:
+                case TAG_ROWS_PER_STRIP:    value = tiling.tileHeight; break;
+                case TAG_TILE_OFFSETS:
+                case TAG_STRIP_OFFSETS:     tiling.offsetsTag = writeTag(tag, 
tiling.offsets); return;
+                case TAG_TILE_BYTE_COUNTS:
+                case TAG_STRIP_BYTE_COUNTS: tiling.lengthsTag = writeTag(tag, 
(short) TIFFTag.TIFF_LONG, tiling.lengths); return;
+                default: throw new AssertionError(tag);
+            }
+            writeTag(tag, (short) TIFFTag.TIFF_LONG, value);
+        }
+    }
+
     /**
      * Writes a 32-bits or 64-bits offset, depending on whether the format is 
classic TIFF or BigTIFF.
      *
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
index 1ddf9f6dee..1bf8b24f65 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
@@ -149,6 +149,15 @@ public final class TileMatrix {
         Arrays.fill(lengths, tileSize);
     }
 
+    /**
+     * {@return whether to use strips instead of tiles}.
+     * This is {@code true} if image rows are not separated in tiles.
+     * The purpose of using strips is to avoid the restriction that tile size 
must be multiple of 16 bytes.
+     */
+    public boolean useStrips() {
+        return numXTiles == 1;
+    }
+
     /**
      * Rewrites the offsets and lengths arrays in the IFD.
      * This method shall be invoked after all tiles have been written.
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
index 663557865c..9acb11ed76 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
@@ -185,8 +185,9 @@ public final class WriterTest extends TestCase {
         initialize(DataType.BYTE, ByteOrder.BIG_ENDIAN, false, 1, 1, 1);
         writeImage();
         verifyHeader(false, IOBase.BIG_ENDIAN);
-        verifyImageFileDirectory(Writer.MINIMAL_NUMBER_OF_TAGS, 
PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
-                                 new short[] {Byte.SIZE});
+        verifyImageFileDirectory(Writer.COMMON_NUMBER_OF_TAGS - 1,             
 // One less tag because stripped layout.
+                                 PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
+                                 new short[] {Byte.SIZE}, false);
         verifySampleValues(1);
         store.close();
     }
@@ -202,8 +203,9 @@ public final class WriterTest extends TestCase {
         initialize(DataType.BYTE, ByteOrder.LITTLE_ENDIAN, false, 1, 1, 1, 
FormatModifier.BIG_TIFF);
         writeImage();
         verifyHeader(true, IOBase.LITTLE_ENDIAN);
-        verifyImageFileDirectory(Writer.MINIMAL_NUMBER_OF_TAGS, 
PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
-                                 new short[] {Byte.SIZE});
+        verifyImageFileDirectory(Writer.COMMON_NUMBER_OF_TAGS - 1,          // 
One less tag because stripped layout.
+                                 PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
+                                 new short[] {Byte.SIZE}, false);
         verifySampleValues(1);
         store.close();
     }
@@ -220,8 +222,9 @@ public final class WriterTest extends TestCase {
         initialize(DataType.BYTE, ByteOrder.LITTLE_ENDIAN, false, 1, 3, 4);
         writeImage();
         verifyHeader(false, IOBase.LITTLE_ENDIAN);
-        verifyImageFileDirectory(Writer.MINIMAL_NUMBER_OF_TAGS, 
PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
-                                 new short[] {Byte.SIZE});
+        verifyImageFileDirectory(Writer.COMMON_NUMBER_OF_TAGS,
+                                 PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
+                                 new short[] {Byte.SIZE}, true);
         verifySampleValues(1);
         store.close();
     }
@@ -238,8 +241,9 @@ public final class WriterTest extends TestCase {
         
image.setColorModel(ColorModelFactory.createRGB(image.getSampleModel()));
         writeImage();
         verifyHeader(false, IOBase.LITTLE_ENDIAN);
-        verifyImageFileDirectory(Writer.MINIMAL_NUMBER_OF_TAGS, 
PHOTOMETRIC_INTERPRETATION_RGB,
-                                 new short[] {Byte.SIZE, Byte.SIZE, 
Byte.SIZE});
+        verifyImageFileDirectory(Writer.COMMON_NUMBER_OF_TAGS - 1,          // 
One less tag because stripped layout.
+                                 PHOTOMETRIC_INTERPRETATION_RGB,
+                                 new short[] {Byte.SIZE, Byte.SIZE, 
Byte.SIZE}, false);
         verifySampleValues(3);
         store.close();
     }
@@ -256,8 +260,8 @@ public final class WriterTest extends TestCase {
         createGridGeometry();
         writeImage();
         verifyHeader(false, IOBase.LITTLE_ENDIAN);
-        verifyImageFileDirectory(Writer.MINIMAL_NUMBER_OF_TAGS + 3,            
         // GeoTIFF adds 3 tags.
-                PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO, new short[] 
{Byte.SIZE});
+        verifyImageFileDirectory(Writer.COMMON_NUMBER_OF_TAGS + 3 - 1,         
     // 3 more for RGB, 1 less for strips.
+                PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO, new short[] 
{Byte.SIZE}, false);
         verifySampleValues(1);
         store.close();
     }
@@ -303,11 +307,11 @@ public final class WriterTest extends TestCase {
      * @param tagCount        expected number of tags.
      * @param interpretation  one of {@code PHOTOMETRIC_INTERPRETATION_} 
constants.
      * @param bitsPerSample   expected number of bits per sample. The array 
length is the number of bands.
+     * @param isTiled         whether the image uses tiles instead of strips.
      */
-    private void verifyImageFileDirectory(int tagCount, final int 
interpretation, final short[] bitsPerSample) {
+    private void verifyImageFileDirectory(int tagCount, final int 
interpretation, final short[] bitsPerSample, final boolean isTiled) {
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final ByteBuffer data     = this.data;
-        final boolean isTiled     = true;
         final boolean isBigTIFF   = 
store.getModifiers().contains(FormatModifier.BIG_TIFF);
         final boolean isBigEndian = ByteOrder.BIG_ENDIAN.equals(data.order());
         assertEquals(tagCount, isBigTIFF ? data.getLong() : data.getShort());
@@ -349,9 +353,13 @@ public final class WriterTest extends TestCase {
                 case TAG_SAMPLES_PER_PIXEL:          expected = (short) 
bitsPerSample.length; break;
                 case TAG_RESOLUTION_UNIT:            expected = (short) 
RESOLUTION_UNIT_NONE; break;
                 case TAG_TILE_WIDTH:                 expected = TILE_WIDTH;    
               break;
-                case TAG_TILE_LENGTH:                expected = TILE_HEIGHT;   
               break;
+                case TAG_TILE_LENGTH:
+                case TAG_ROWS_PER_STRIP:             expected = TILE_HEIGHT;   
               break;
+                case TAG_STRIP_BYTE_COUNTS:
                 case TAG_TILE_BYTE_COUNTS:           expected = 
expectedTileByteCounts();     break;
+                case TAG_STRIP_OFFSETS:
                 case TAG_TILE_OFFSETS: {
+                    assertNull(tileOffsets);
                     tileOffsets = getIntegers(value, count, isBigTIFF ? 
Writer.TIFF_ULONG : TIFFTag.TIFF_LONG);
                     continue;
                 }

Reply via email to