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 <[email protected]>
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;
}