This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits 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 d1a6399c25 Initial support of "tili" image type in GeoHEIF files.
d1a6399c25 is described below

commit d1a6399c251f354f104c69b3fc7bbf2aa809dfe7
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jun 15 17:29:28 2026 +0200

    Initial support of "tili" image type in GeoHEIF files.
---
 .../sis/storage/geoheif/CoverageBuilder.java       |  81 ++++++++++++---
 .../org/apache/sis/storage/geoheif/ImageModel.java |  15 +--
 .../apache/sis/storage/geoheif/ImageResource.java  |   1 +
 .../sis/storage/geoheif/ResourceBuilder.java       |  12 +++
 .../org/apache/sis/storage/geoheif/TiledImage.java | 115 +++++++++++++++++++++
 .../sis/storage/geoheif/UncompressedImage.java     |  65 +++++++++---
 .../apache/sis/storage/isobmff/base/ItemData.java  |  10 +-
 .../sis/storage/isobmff/base/ItemInfoEntry.java    |   3 +-
 .../{geo => image}/TiledImageConfiguration.java    |   3 +-
 .../isobmff/mpeg/CompressedUnitsItemInfo.java      |   2 +-
 10 files changed, 261 insertions(+), 46 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
index 7b51f15fe5..551fc2fec8 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
@@ -21,6 +21,7 @@ import java.util.UUID;
 import java.util.Set;
 import java.util.Map;
 import java.util.List;
+import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Collection;
 import java.util.StringJoiner;
@@ -28,6 +29,7 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.io.IOException;
 import java.nio.ByteOrder;
+import java.awt.Dimension;
 import java.awt.image.SampleModel;
 import org.opengis.util.GenericName;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -43,6 +45,7 @@ import org.apache.sis.storage.modifier.CoverageModifier;
 import org.apache.sis.storage.isobmff.Box;
 import org.apache.sis.storage.isobmff.base.ItemInfoEntry;
 import org.apache.sis.storage.isobmff.base.ItemProperties;
+import org.apache.sis.storage.isobmff.image.TiledImageConfiguration;
 import org.apache.sis.storage.isobmff.geo.ModelTransformation;
 import org.apache.sis.storage.isobmff.geo.ModelCRS;
 import org.apache.sis.storage.isobmff.mpeg.ComponentType;
@@ -100,6 +103,14 @@ final class CoverageBuilder implements Emptiable {
      */
     private int width, height;
 
+    /**
+     * Information about where to find the tiles when the image type is {@code 
"tili"}.
+     * May be {@code null} if no such information was found.
+     *
+     * @see #tiling()
+     */
+    private TiledImageConfiguration tiling;
+
     /**
      * Coefficients of the matrix that defines the "grid to <abbr>CRS</abbr>" 
coordinate conversion.
      * May be {@code null} if no such information was found.
@@ -186,7 +197,7 @@ final class CoverageBuilder implements Emptiable {
      *
      * @param  owner            the resource which is creating a grid coverage.
      * @param  imageIndex       an index of the image, for information purpose 
only.
-     * @param  properties       source of coverage properties for this 
coverage item.
+     * @param  properties       source of coverage properties for this 
coverage item, or {@code null} if none.
      * @param  duplicatedBoxes  names of boxes that were duplicated. Used for 
logging a warning only once per type of box.
      */
     CoverageBuilder(final ResourceBuilder owner,
@@ -197,16 +208,26 @@ final class CoverageBuilder implements Emptiable {
         this.owner = owner;
         this.imageIndex = imageIndex;
         unknownBoxes = new LinkedHashMap<>();
-        if (properties == null) {
-            return;
-        }
-        if (properties.missingItem) {
-            // Some boxes could not be handled, but we don't have their 
identifiers.
-            unknownBoxes.put(null, properties.missingEssential);
+        if (properties != null) {
+            if (properties.missingItem) {
+                // Some boxes could not be handled, but we don't have their 
identifiers.
+                unknownBoxes.put(null, properties.missingEssential);
+            }
+            load(properties, duplicatedBoxes);
         }
+    }
+
+    /**
+     * Collects from the given boxes the data that this builder will need.
+     * This method may invoke itself recursively if a box is a container for 
other boxes.
+     *
+     * @param  properties       source of coverage properties for this 
coverage item.
+     * @param  duplicatedBoxes  names of boxes that were duplicated. Used for 
logging a warning only once per type of box.
+     */
+    private void load(final List<Box> properties, final Set<String> 
duplicatedBoxes) {
         final int count = properties.size();
         for (int i=0; i<count; i++) {
-            boolean duplicated;
+            final boolean duplicated;
             final Box property = properties.get(i);
             switch (property.type()) {
                 /*
@@ -277,8 +298,19 @@ final class CoverageBuilder implements Emptiable {
                     if (!duplicated) palette = c;
                     break;
                 }
+                case TiledImageConfiguration.BOXTYPE: {
+                    var c = (TiledImageConfiguration) property;
+                    duplicated = (tiling != null);
+                    if (!duplicated) {
+                        tiling = c;     // Before `load(…)` for safety against 
nested tiling.
+                        load(Arrays.asList(c.tileImageProperties), 
duplicatedBoxes);
+                    }
+                    break;
+                }
                 default: {
-                    unknownBoxes.merge(property.typeKey(), 
properties.essential(i), Boolean::logicalOr);
+                    boolean essential = (properties instanceof 
ItemProperties.ForID)
+                                && ((ItemProperties.ForID) 
properties).essential(i);
+                    unknownBoxes.merge(property.typeKey(), essential, 
Boolean::logicalOr);
                     continue;
                 }
             }
@@ -291,6 +323,14 @@ final class CoverageBuilder implements Emptiable {
         }
     }
 
+    /**
+     * Returns information about where to find the tiles when the image type 
is {@code "tili"}.
+     * This is used for reading the positions of tiles in the file and the 
number of bytes to read.
+     */
+    final TiledImageConfiguration tiling() {
+        return tiling;
+    }
+
     /**
      * Returns the compression method, or 0 if none.
      * The returned value should be one of the {@code 
CompressionConfiguration.COMPRESSION_*} constants.
@@ -313,7 +353,7 @@ final class CoverageBuilder implements Emptiable {
         }
         if (compression.unitType == UnitType.IMAGE_TILE) {
             if (compressedUnits == null) {
-                throw new DataStoreContentException("Missing compressed 
unit.");
+                return null;
             }
             final CompressedUnitsItemInfo.Unit[] units = compressedUnits.units;
             if (units.length == 1) {
@@ -471,11 +511,18 @@ final class CoverageBuilder implements Emptiable {
                 case 1: return model.numTileRows;
             }
         } else if (!isEmpty()) {
-            final SampleModel sampleModel = imageModel().sampleModel;
-            if (sampleModel != null) {
+            if (tiling != null) {
                 switch (dimension) {
-                    case 0: return JDK18.ceilDiv(width,  
sampleModel.getWidth());
-                    case 1: return JDK18.ceilDiv(height, 
sampleModel.getHeight());
+                    case 0: return JDK18.ceilDiv(width,  tiling.tileWidth);
+                    case 1: return JDK18.ceilDiv(height, tiling.tileHeight);
+                }
+            } else {
+                final SampleModel sampleModel = imageModel().sampleModel;
+                if (sampleModel != null) {
+                    switch (dimension) {
+                        case 0: return JDK18.ceilDiv(width,  
sampleModel.getWidth());
+                        case 1: return JDK18.ceilDiv(height, 
sampleModel.getHeight());
+                    }
                 }
             }
         }
@@ -517,7 +564,11 @@ final class CoverageBuilder implements Emptiable {
      */
     final ImageModel imageModel() throws DataStoreException {
         if (imageModel == null) {
-            imageModel = new ImageModel(width, height, model, componentTypes, 
bitsPerChannel, palette, this);
+            Dimension tileSize = null;
+            if (tiling != null) {
+                tileSize = new Dimension(tiling.tileWidth, tiling.tileHeight);
+            }
+            imageModel = new ImageModel(width, height, tileSize, model, 
componentTypes, bitsPerChannel, palette, this);
             if (imageModel.sampleModel == null && tileBuilder != null) {
                 imageModel = tileBuilder.imageModel();
             }
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
index 9c285ab3d0..f11be26a60 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
@@ -90,6 +90,7 @@ final class ImageModel {
      *
      * @param  width           the width  (in pixels) of the reconstructed 
image.
      * @param  height          the height (in pixels) of the reconstructed 
image.
+     * @param  tileSize        the tile size if known, or {@code null} for 
computing from other information.
      * @param  model           information about the sample model (data type, 
<i>etc.</i>), or {@code null}.
      * @param  componentTypes  pixel data displaying as {@link ComponentType}, 
{@link URI} or {@link Integer}.
      * @param  bitsPerChannel  number of bits per channel in the reconstructed 
image, or {@code null}.
@@ -100,6 +101,7 @@ final class ImageModel {
      */
     ImageModel(final int width,
                final int height,
+               Dimension tileSize,
                final UncompressedFrameConfig model,
                final Object[] componentTypes,
                final byte[] bitsPerChannel,
@@ -206,12 +208,13 @@ final class ImageModel {
          */
         this.dataType = dataType;
         if (dataType != null) {
-            InterleavingMode interleaveType = InterleavingMode.COMPONENT;
-            final var tileSize = new Dimension(width, height);
-            if (model != null) {
-                tileSize.width  /= model.numTileCols;
-                tileSize.height /= model.numTileRows;
-                interleaveType = model.interleaveType;
+            final InterleavingMode interleaveType = (model != null) ? 
model.interleaveType : InterleavingMode.COMPONENT;
+            if (tileSize == null) {
+                tileSize = new Dimension(width, height);
+                if (model != null) {
+                    tileSize.width  /= model.numTileCols;
+                    tileSize.height /= model.numTileRows;
+                }
             }
             final boolean isBanded;
             switch (interleaveType) {
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
index b0e49061fe..1d54bd888f 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
@@ -396,6 +396,7 @@ final class ImageResource extends TiledGridCoverageResource 
implements StoreReso
              * @param  readers   an initially empty map where to store image 
readers for reuse.
              * @param  buffer    an initially empty reference to a buffer.
              * @param  owner     the resource for which to read a tile.
+             * @throws DataStoreException if an error occurred while computing 
the range of bytes.
              */
             @SuppressWarnings("LeakingThisInConstructor")
             private ReadContext(final AOI iterator,
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
index a86c6508c3..f632022d99 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
@@ -443,6 +443,18 @@ final class ResourceBuilder {
                     }
                     break;
                 }
+                /*
+                 * Tiled image with offsets and lengths specified in a 
separated array.
+                 * This is an encoding more efficient than `GRID` because it 
avoids to
+                 * repeat the image color model and ample model for each tile.
+                 */
+                case ItemInfoEntry.TILI: {
+                    final ByteRanges.Reader locator = 
getLocationByIdentifier(itemID);
+                    if (locator != null) {
+                        image = new TiledImage(coverage, locator, name);
+                    }
+                    break;
+                }
             }
             if (image == null) {
                 warning("No data found for the \"{0}\" resource.", name);
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/TiledImage.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/TiledImage.java
new file mode 100644
index 0000000000..6c881f00bc
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/TiledImage.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geoheif;
+
+import java.io.IOException;
+import java.awt.image.RasterFormatException;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.isobmff.ByteRanges;
+import org.apache.sis.storage.isobmff.base.ItemLocation;
+import org.apache.sis.storage.isobmff.mpeg.CompressedUnitsItemInfo;
+import org.apache.sis.storage.isobmff.image.TiledImageConfiguration;
+
+
+/**
+ * A tiled image ({@code 'tili'} item type) from the <abbr>HEIF</abbr> file.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+final class TiledImage extends UncompressedImage {
+    /**
+     * Positions relative to the beginning of the box where to find the tile 
data.
+     */
+    private final long[] tileOffsets;
+
+    /**
+     * Number of bytes of the compressed data of each tile, or {@code null} if 
not stored.
+     * The length of this array shall be equal to the {@link #tileOffsets} 
array length.
+     * If {@code null}, then the lengths must be computed from the offsets.
+     */
+    private final long[] tileSizes;
+
+    /**
+     * Creates a new tile.
+     *
+     * @param  builder  helper class for building the grid geometry and sample 
dimensions.
+     * @param  locator  the provider of bytes to read from the 
<abbr>ISOBMFF</abbr> box.
+     * @param  name     a name that identifies this image, for debugging 
purpose.
+     * @throws RasterFormatException if the sample model cannot be created.
+     * @throws IOException if an error occurred while reading bytes from the 
input stream.
+     */
+    TiledImage(final CoverageBuilder builder, final ByteRanges.Reader locator, 
final String name)
+            throws DataStoreException, IOException
+    {
+        super(builder, locator, name);
+        final int numTileCols = builder.numTiles(0);
+        final int numTileRows = builder.numTiles(1);
+        final int numTiles = Math.multiplyExact(numTileCols, numTileRows);
+        final TiledImageConfiguration tiling = builder.tiling();
+        final int offsetFieldLength = tiling.offsetFieldLength();
+        final int sizeFieldLength   = tiling.sizeFieldLength();
+        final ChannelDataInput input = builder.store().ensureOpen();
+        input.seek(((ItemLocation.Item) locator).baseOffset);
+        tileOffsets = new long[numTiles];
+        tileSizes = (sizeFieldLength != 0) ? new long[numTiles] : null;
+        for (int i = 0; i < numTiles; i++) {
+            tileOffsets[i] = input.readBits(offsetFieldLength);
+            if (tileSizes != null) {
+                tileSizes[i] = input.readBits(sizeFieldLength);
+            }
+        }
+    }
+
+    /**
+     * Computes the range of bytes that will be needed for reading the tile at 
the specified index.
+     * The result is stored in the given {@code context} argument.
+     *
+     * @param  tileIndex  index of the tile for which to compute the range of 
bytes.
+     * @param  tileSize   ignored (replaced by the tile size which is stored 
or computed from the offset).
+     * @param  context    where to add the ranges of bytes to read as offsets 
relatives to the beginning of the file.
+     * @throws DataStoreException if an error occurred with the data in the 
boxes.
+     * @throws ArithmeticException if an integer overflow occurred.
+     */
+    @Override
+    protected void computeByteRanges(final long tileIndex, long tileSize, 
final ByteRanges context)
+            throws DataStoreException
+    {
+        final int i = Math.toIntExact(tileIndex);
+        final long offset = tileOffsets[i];
+        if (tileSizes != null) {
+            tileSize = tileSizes[i];
+        } else {
+            long next = tileOffsets[Math.incrementExact(i)];     // TODO: 
handle the case of the last tile.
+            tileSize = next - offset;
+        }
+        locator.resolve(offset, tileSize, context);
+    }
+
+    /**
+     * Returns the compression units which contains tile data.
+     *
+     * @param  tileIndex  index of the tile for which to get the compression 
unit.
+     * @return the compression unit for the tile at the given index.
+     */
+    @Override
+    protected CompressedUnitsItemInfo.Unit compressedImageUnit(final long 
tileIndex) {
+        final int i = Math.toIntExact(tileIndex);
+        return new CompressedUnitsItemInfo.Unit(tileOffsets[i], tileSizes[i]);
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
index 28a7dfa0bd..47a6477193 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
@@ -30,6 +30,7 @@ import org.apache.sis.io.stream.HyperRectangleReader;
 import org.apache.sis.io.stream.Region;
 import org.apache.sis.io.stream.inflater.ComputedByteChannel;
 import org.apache.sis.io.stream.inflater.Deflate;
+import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.UnsupportedEncodingException;
 import org.apache.sis.storage.isobmff.ByteRanges;
@@ -53,7 +54,7 @@ import 
org.apache.sis.storage.isobmff.mpeg.CompressionConfiguration;
  * @author Johann Sorel (Geomatys)
  * @author Martin Desruisseaux (Geomatys)
  */
-final class UncompressedImage extends Image {
+class UncompressedImage extends Image {
     /**
      * The type of sample values in the raster.
      */
@@ -67,13 +68,15 @@ final class UncompressedImage extends Image {
     private final SampleModel sampleModel;
 
     /**
-     * The type of compression, or 0 if none.
+     * The type of compression, or 0 if the image is uncompressed.
      * Should be one of the {@code CompressionConfiguration.COMPRESSION_*} 
constants.
      */
     private final int compressionType;
 
     /**
-     * The compression units which contains all image data, or {@code null} if 
the image is uncompressed.
+     * The compression units which contains all image data, or {@code null} if 
none.
+     * A compressed image may have no compression unit if the location of data 
are
+     * specified by another type of box.
      */
     private final CompressedUnitsItemInfo.Unit compressedImageUnit;
 
@@ -97,15 +100,15 @@ final class UncompressedImage extends Image {
 
     /**
      * Returns a channel which will decompress data on-the-fly.
-     * This method shall be invoked only if {@link #compressedImageUnit} is 
non-null.
      * Callers shall invoke {@link ComputedByteChannel#setInputRegion(long, 
long)} on the returned object.
      *
      * @param  input  the channel of compressed data.
-     * @return the channel of uncompressed data.
+     * @return the channel of uncompressed data, or {@code null} if the data 
are uncompressed.
      * @throws UnsupportedEncodingException if the compression type is not 
supported.
      */
     private ComputedByteChannel inflater(final ChannelDataInput input) throws 
UnsupportedEncodingException {
         switch (compressionType) {
+            case 0: return null;
             case CompressionConfiguration.COMPRESSION_ZLIB:    return new 
Deflate(input, listeners, false);
             case CompressionConfiguration.COMPRESSION_DEFLATE: return new 
Deflate(input, listeners, true);
             default: throw new UnsupportedEncodingException("The \"" +
@@ -140,15 +143,47 @@ final class UncompressedImage extends Image {
         return new Region(sourceSize, new long[2], regionUpper, new long[] {1, 
1});
     }
 
+    /**
+     * Computes the range of bytes that will be needed for reading the tile at 
the specified index.
+     * The result is stored in the given {@code context} argument.
+     *
+     * @param  tileIndex  index of the tile for which to compute the range of 
bytes.
+     * @param  tileSize   number of bytes of uncompressed data in each tile.
+     * @param  context    where to add the ranges of bytes to read as offsets 
relatives to the beginning of the file.
+     * @throws DataStoreException if an error occurred with the data in the 
boxes.
+     * @throws ArithmeticException if an integer overflow occurred.
+     */
+    protected void computeByteRanges(final long tileIndex, final long 
tileSize, final ByteRanges context)
+            throws DataStoreException
+    {
+        locator.resolve(multiplyExact(tileIndex, tileSize), tileSize, context);
+    }
+
+    /**
+     * Returns the compression units which contains tile data.
+     *
+     * @param  tileIndex  index of the tile for which to get the compression 
unit.
+     * @return the compression unit for the tile at the given index.
+     * @throws DataStoreException if the compression unit cannot be obtained.
+     */
+    protected CompressedUnitsItemInfo.Unit compressedImageUnit(final long 
tileIndex) throws DataStoreException {
+        if (compressedImageUnit == null) {
+            throw new DataStoreContentException("Missing compressed unit.");
+        }
+        return compressedImageUnit;
+    }
+
     /**
      * Computes the range of bytes that will be needed for reading a single 
tile of this image.
+     * This method is invoked in a code synchronized on {@link 
ImageResource#getSynchronizationLock()}.
      *
      * @param  context  where to store the ranges of bytes.
      * @return the function to invoke later for reading the tile.
      * @throws DataStoreException if an error occurred while computing the 
range of bytes.
+     * @throws ArithmeticException if an offset or index overflows the 
capacity of 32-bits integers.
      */
     @Override
-    protected Reader computeByteRanges(final 
ImageResource.Coverage.ReadContext context) throws DataStoreException {
+    protected final Reader computeByteRanges(final 
ImageResource.Coverage.ReadContext context) throws DataStoreException {
         final long[] sourceSize = size(sampleModel);
         /*
          * In the current implementation, we read the whole tile. If a future 
implementation allows
@@ -156,22 +191,18 @@ final class UncompressedImage extends Image {
          * `Buffer` with an arbitrary position in argument.
          */
         final var  region    = region(sourceSize, sourceSize);
-        final int  dataSize  = dataType.bytes();
-        final long tileSize  = multiplyExact(region.length, dataSize);
+        final long tileSize  = multiplyExact(region.length, dataType.bytes());
         final long tileIndex = addExact(multiplyExact(context.subTileY, 
numXTiles), context.subTileX);
-        final long offset    = multiplyExact(tileIndex, tileSize);
-        locator.resolve(offset, tileSize, context);
+        computeByteRanges(tileIndex, tileSize, context);
         return (ChannelDataInput input) -> {
             long origin = context.offset();
-            final ComputedByteChannel inflater;
-            final CompressedUnitsItemInfo.Unit unit = compressedImageUnit;
-            if (unit != null) {
-                inflater = inflater(input);
+            final ComputedByteChannel inflater = inflater(input);
+            if (inflater != null) {
+                final CompressedUnitsItemInfo.Unit unit = 
compressedImageUnit(tileIndex);
                 inflater.setInputRegion(addExact(origin, unit.offset), 
unit.size);
-                input = inflater.createDataInput(context.reuseBuffer(), (int) 
sourceSize[0]);    // (int) cast okay even if inexact.
+                // The following (int) cast is okay even if inexact because it 
is only a hint.
+                input = inflater.createDataInput(context.reuseBuffer(), (int) 
sourceSize[0]);
                 origin = 0;
-            } else {
-                inflater = null;
             }
             /*
              * Now read all banks and store the values in the image buffer.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
index c044b827f8..4432a05528 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
@@ -18,7 +18,7 @@ package org.apache.sis.storage.isobmff.base;
 
 import java.io.IOException;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.isobmff.ByteRanges;
 import org.apache.sis.storage.isobmff.Reader;
 import org.apache.sis.storage.isobmff.Box;
@@ -58,9 +58,8 @@ public class ItemData extends Box implements 
ByteRanges.Reader {
 
     /**
      * Number of bytes to read, or -1 for reading until the end of file.
-     * Note that -1 is also the maximal unsigned value.
+     * Note that -1 is also the maximal value when treated as unsigned integer.
      */
-    @Interpretation(Type.UNSIGNED)
     public final long size;
 
     /**
@@ -82,17 +81,18 @@ public class ItemData extends Box implements 
ByteRanges.Reader {
      * @param  offset  offset of the first byte to read relative to the data 
stored in this item.
      * @param  length  maximum number of bytes to read, starting at the 
offset, or a negative value for reading all.
      * @param  addTo   where to add the range of bytes to read as offsets 
relatives to the beginning of the file.
+     * @throws DataStoreContentException if the length is unspecified.
      * @throws ArithmeticException if an integer overflow occurred.
      */
     @Override
-    public void resolve(long offset, long length, ByteRanges addTo) throws 
DataStoreException {
+    public void resolve(long offset, long length, ByteRanges addTo) throws 
DataStoreContentException {
         if (size >= 0) {
             ArgumentChecks.ensureBetween("offset", 0, size - Math.max(0, 
length), offset);
             if (length > size || length < 0) {
                 length = size;
             }
         } else if (length < 0) {
-            throw new DataStoreException("Stream of unknown length.");
+            throw new DataStoreContentException("Stream of unknown length.");
         }
         offset = Math.addExact(payloadOffset, offset);
         addTo.addRange(offset, Math.addExact(offset, length));
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemInfoEntry.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemInfoEntry.java
index ae01af1687..ec648b0edd 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemInfoEntry.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemInfoEntry.java
@@ -72,7 +72,8 @@ public final class ItemInfoEntry extends FullBox {
                             URI  = ((((('u' << 8) | 'r') << 8) | 'i') << 8) | 
' ',
                             UNCI = ((((('u' << 8) | 'n') << 8) | 'c') << 8) | 
'i',
                             JPEG = ((((('j' << 8) | 'p') << 8) | 'e') << 8) | 
'g',
-                            GRID = ((((('g' << 8) | 'r') << 8) | 'i') << 8) | 
'd';
+                            GRID = ((((('g' << 8) | 'r') << 8) | 'i') << 8) | 
'd',
+                            TILI = ((((('t' << 8) | 'i') << 8) | 'l') << 8) | 
'i';
 
     /**
      * Item type indicator such as {@link #MIME} or {@link #URI}.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/TiledImageConfiguration.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/image/TiledImageConfiguration.java
similarity index 97%
rename from 
incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/TiledImageConfiguration.java
rename to 
incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/image/TiledImageConfiguration.java
index 94b9bb993f..955b7cf8f8 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/TiledImageConfiguration.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/image/TiledImageConfiguration.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.storage.isobmff.geo;
+package org.apache.sis.storage.isobmff.image;
 
 import java.io.IOException;
 import org.apache.sis.io.stream.ChannelDataInput;
@@ -132,6 +132,7 @@ public final class TiledImageConfiguration extends FullBox {
 
     /**
      * Returns the number of bits used to store the length of the image data 
of a specific tile.
+     * Value 0 means that size is not stored. Instead, it is computed from 
offsets.
      *
      * @return the number of bits for tile length.
      */
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressedUnitsItemInfo.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressedUnitsItemInfo.java
index 7385bb2752..bfc4fcdd46 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressedUnitsItemInfo.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressedUnitsItemInfo.java
@@ -92,7 +92,7 @@ public final class CompressedUnitsItemInfo extends FullBox {
         public final long size;
 
         /** Creates a new unit with the given offset and size. */
-        Unit(final long offset, final long size) {
+        public Unit(final long offset, final long size) {
             this.offset = offset;
             this.size   = size;
         }

Reply via email to