sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/03: Fix ColorModel creation when there is a color palette or in the interleaved case.
Date Sun, 11 Jul 2021 20:17:10 GMT
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 <martin.desruisseaux@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;
     }

Mime
View raw message