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 2021/07/11 20:17:10 UTC
[sis] 01/03: Fix ColorModel creation when there is a color palette
or in the interleaved case.
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 a36a492af76faa71d146b5f02f9f076434f41021
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Jul 11 18:23:12 2021 +0200
Fix ColorModel creation when there is a color palette or in the interleaved case.
---
.../org/apache/sis/storage/geotiff/DataSubset.java | 41 +++++++++-----
.../sis/storage/geotiff/ImageFileDirectory.java | 66 ++++++++++++++--------
2 files changed, 70 insertions(+), 37 deletions(-)
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
index 7306a1d..677fe3e 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
@@ -27,6 +27,7 @@ import java.awt.image.WritableRaster;
import org.apache.sis.image.DataType;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.InternalDataStoreException;
import org.apache.sis.internal.storage.io.Region;
import org.apache.sis.internal.storage.io.HyperRectangleReader;
import org.apache.sis.internal.storage.TiledGridCoverage;
@@ -175,7 +176,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
* (too many exception types to list them all).
*/
@Override
- protected final WritableRaster[] readTiles(final AOI iterator) throws IOException, DataStoreContentException {
+ protected final WritableRaster[] readTiles(final AOI iterator) throws IOException, DataStoreException {
/*
* Prepare an array for all tiles to be returned. Tiles that are already in memory will be stored
* in this array directly. Other tiles will be declared in the `missings` array and loaded later.
@@ -241,23 +242,16 @@ class DataSubset extends TiledGridCoverage implements Localized {
* @param location pixel coordinates in the upper-left corner of the tile to return.
* @return image decoded from the GeoTIFF file.
* @throws IOException if an I/O error occurred.
- * @throws DataStoreContentException if the sample model is not supported.
+ * @throws DataStoreException if a logical error occurred.
* @throws RuntimeException if the Java2D image can not be created for another reason
* (too many exception types to list them all).
*/
WritableRaster readSlice(final long offset, final long byteCount, final long[] lower, final long[] upper,
- final int[] subsampling, final Point location) throws IOException, DataStoreContentException
+ final int[] subsampling, final Point location) throws IOException, DataStoreException
{
final int type = model.getDataType();
final long width = subtractExact(upper[0], lower[0]);
final long height = subtractExact(upper[1], lower[1]);
- final long length = multiplyExact(multiplyExact(width, height), DataBuffer.getDataTypeSize(type) / Byte.SIZE);
- if (length > byteCount) {
- // We may have less bytes to read if only a subregion is read.
- throw new DataStoreContentException(source.reader.resources().getString(
- Resources.Keys.UnexpectedTileLength_2, length, byteCount));
- }
- final HyperRectangleReader hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type), source.reader.input, offset);
/*
* "Banks" (in `java.awt.image.DataBuffer` sense) are synonymous to "bands" for planar image only.
* Otherwise there is only one bank not matter the amount of bands. Each bank is read separately.
@@ -268,9 +262,30 @@ class DataSubset extends TiledGridCoverage implements Localized {
numBanks = numInterleaved;
numInterleaved = 1;
}
- final Buffer[] banks = new Buffer[numBanks];
- final long[] size = new long[] {multiplyFull(numInterleaved, getTileSize(0)), getTileSize(1), numBanks};
- final int capacity = multiplyExact(model.getWidth(), model.getHeight());
+ /*
+ * The number of bytes to read should not be greater than `byteCount`. It may be smaller however if only
+ * a subregion is read. Note that the `length` value may be different than `capacity` if the tile to read
+ * is smaller than the "standard" tile size of the image. It happens often when reading the last strip.
+ */
+ final long length = multiplyExact(DataBuffer.getDataTypeSize(type) / Byte.SIZE,
+ multiplyExact(multiplyExact(width, height), numInterleaved));
+ if (length > byteCount) {
+ throw new DataStoreContentException(source.reader.resources().getString(
+ Resources.Keys.UnexpectedTileLength_2, length, byteCount));
+ }
+ final int capacity = multiplyExact(multiplyExact(model.getWidth(), model.getHeight()), numInterleaved);
+ final long[] size = new long[] {multiplyFull(numInterleaved, getTileSize(0)), getTileSize(1), numBanks};
+ /*
+ * If we use an interleaved sample model, each "element" from `HyperRectangleReader` perspective is actually
+ * a group of `numInterleaved` values. Note that in such case, we can not handle subsampling on the first axis.
+ */
+ if (numInterleaved != 1 && subsampling[0] != 1) {
+ throw new InternalDataStoreException();
+ }
+ lower[0] *= numInterleaved;
+ upper[0] *= numInterleaved;
+ final HyperRectangleReader hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type), source.reader.input, offset);
+ final Buffer[] banks = new Buffer[numBanks];
for (int b=0; b<numBanks; b++) {
lower[BIDIMENSIONAL] = b;
upper[BIDIMENSIONAL] = b + 1;
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 305a591..b3a3ae4 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1021,13 +1021,13 @@ final class ImageFileDirectory extends DataCube {
/**
* Multiplies the given value by the number of bytes in one pixel,
- * or return -1 if the result is not an integer.
+ * or return 0 if the result is not an integer.
*
* @throws ArithmeticException if the result overflows.
*/
private long pixelToByteCount(long value) {
value = Math.multiplyExact(value, samplesPerPixel * (int) bitsPerSample);
- return (value % Byte.SIZE == 0) ? value / Byte.SIZE : -1;
+ return (value % Byte.SIZE == 0) ? value / Byte.SIZE : 0;
}
/**
@@ -1047,7 +1047,7 @@ final class ImageFileDirectory extends DataCube {
do if (++i == n) {
// At this point, we verified that all vector values are equal.
final long length = pixelToByteCount(knownSize);
- if (count % length != 0) break;
+ if (length == 0 || (count % length) != 0) break;
return Math.toIntExact(count / length);
} while (tileByteCounts.longValue(i) == n);
}
@@ -1127,7 +1127,7 @@ final class ImageFileDirectory extends DataCube {
* All of tile width, height and length information should be provided. But if only one of them is missing,
* we can compute it provided that the file does not use any compression method. If there is a compression,
* then we set a bit for preventing the `switch` block to perform a calculation but we let the code performs
- * the other checks in order to get an exception to be thrown with a good message.
+ * the other checks in order to get an exception thrown with a better message.
*/
int missing = !isPlanar && compression.equals(Compression.NONE) ? 0 : 0b1000;
if (tileWidth < 0) missing |= 0b0001;
@@ -1150,6 +1150,9 @@ final class ImageFileDirectory extends DataCube {
}
case 0b0100: { // Compute missing tile byte count in uncompressed case.
final long tileByteCount = pixelToByteCount(Math.multiplyExact(tileWidth, tileHeight));
+ if (tileByteCount == 0) {
+ throw missingTag(byteCountsTag);
+ }
final long[] tileByteCountArray = new long[tileOffsets.size()];
Arrays.fill(tileByteCountArray, tileByteCount);
tileByteCounts = Vector.create(tileByteCountArray, true);
@@ -1452,38 +1455,53 @@ final class ImageFileDirectory extends DataCube {
unsupportedTagValue(Tags.PhotometricInterpretation, photometricInterpretation);
break;
}
- case -1: missing = Tags.PhotometricInterpretation; break;
- case 0: break; // WhiteIsZero: 0 is imaged as white.
- case 1: break; // BlackIsZero: 0 is imaged as black.
+ case -1: {
+ missing = Tags.PhotometricInterpretation;
+ break;
+ }
+ case 0: // WhiteIsZero: 0 is imaged as white.
+ case 1: { // BlackIsZero: 0 is imaged as black.
+ final Color[] colors = {Color.BLACK, Color.WHITE};
+ if (photometricInterpretation == 0) {
+ ArraysExt.swap(colors, 0, 1);
+ }
+ colorModel = ColorModelFactory.createColorScale(dataType, samplesPerPixel, visibleBand,
+ Math.max(minValues.doubleValue(visibleBand), 0),
+ Math.min(maxValues.doubleValue(visibleBand), (1 << bitsPerSample) - 1), colors);
+ break;
+ }
+ case 2: { // RGB: (0,0,0) is black and (255,255,255) is white.
+ final int numBands = sm.getNumBands();
+ if (numBands < 3 || numBands > 4) {
+ throw new DataStoreContentException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "numBands", numBands));
+ }
+ final boolean hasAlpha = (numBands >= 4);
+ final boolean packed = sm instanceof SinglePixelPackedSampleModel;
+ colorModel = ColorModelFactory.createRGB(bitsPerSample, packed, hasAlpha);
+ break;
+ }
case 3: { // PaletteColor
if (colorMap == null) {
missing = Tags.ColorMap;
break;
}
- final int[] ARGB = new int[colorMap.size() / 3];
- for (int i=0, j=0; i < ARGB.length; i++) {
- ARGB[i] = ((colorMap.intValue(j++) & 0xFF00) << Byte.SIZE)
- | ((colorMap.intValue(j++) & 0xFF00))
- | ((colorMap.intValue(j++) & 0xFF00) >>> Byte.SIZE);
+ int gi = colorMap.size() / 3;
+ int bi = gi * 2;
+ final int[] ARGB = new int[gi];
+ for (int i=0; i < ARGB.length; i++) {
+ ARGB[i] = 0xFF000000
+ | ((colorMap.intValue(i ) & 0xFF00) << Byte.SIZE)
+ | ((colorMap.intValue(gi++) & 0xFF00))
+ | ((colorMap.intValue(bi++) & 0xFF00) >>> Byte.SIZE);
}
- return colorModel = ColorModelFactory.createIndexColorModel(samplesPerPixel, visibleBand, ARGB,
- Double.isFinite(noData) ? (int) Math.round(noData) : -1);
- }
- case 2: { // RGB: (0,0,0) is black and (255,255,255) is white.
- colorModel = ColorModelFactory.createRGB(bitsPerSample, sm instanceof SinglePixelPackedSampleModel, false);
+ colorModel = ColorModelFactory.createIndexColorModel(samplesPerPixel, visibleBand, ARGB,
+ Double.isFinite(noData) ? (int) Math.round(noData) : -1);
break;
}
}
if (missing != 0) {
missingTag(missing, "GrayScale", false, true);
}
- final Color[] colors = {Color.BLACK, Color.WHITE};
- if (photometricInterpretation == 0) {
- ArraysExt.swap(colors, 0, 1);
- }
- colorModel = ColorModelFactory.createColorScale(dataType, samplesPerPixel, visibleBand,
- Math.max(minValues.doubleValue(visibleBand), 0),
- Math.min(maxValues.doubleValue(visibleBand), (1 << bitsPerSample) - 1), colors);
}
return colorModel;
}