Package org.apache.sis.image

# Class ComputedImage

Object
PlanarImage
ComputedImage

All Implemented Interfaces:
  `RenderedImage`, `Disposable`

Direct Known Subclasses:
  `ResampledImage`

public abstract class ComputedImage extends PlanarImage implements Disposable
An image with tiles computed on-the-fly and cached for future reuse. Computations are performed on a tile-by-tile basis (potentially in different threads) and the results are stored in a cache shared by all images in the runtime environment. Tiles may be discarded at any time or may become dirty if a source has been modified, in which case those tiles will be recomputed when needed again.

`ComputedImage` may have an arbitrary number of source images, including zero. A `TileObserver` is automatically registered to all sources that are instances of `WritableRenderedImage`. If one of those sources sends a change event, then all `ComputedImage` tiles that may be impacted by that change are marked as dirty and will be computed again when needed.

When this `ComputedImage` is garbage collected, all cached tiles are discarded and the above-cited `TileObserver` is automatically removed from all sources. This cleanup can be requested without waiting for garbage collection by invoking the `dispose()` method, but that call should be done only if the caller is certain that this `ComputedImage` will not be used anymore.

## Pixel coordinate system

Default implementation assumes that the pixel in upper-left left corner is located at coordinates (0,0). This assumption is consistent with `GridCoverage​.render(GridExtent)` contract, which produces an image located at (0,0) when the image region matches the `GridExtent`. However subclasses can use a non-zero origin by overriding the methods documented in the Sub-classing section below.

If this `ComputedImage` does not have any `WritableRenderedImage` source, then there is no other assumption on the pixel coordinate system. But if there is writable sources, then the default implementation assumes that source images occupy the same region as this `ComputedImage`: all pixels at coordinates (x, y) in this `ComputedImage` depend on pixels at the same (x, y) coordinates in the source images, possibly shifted or expanded to neighborhood pixels as described in `SOURCE_PADDING_KEY`. If this assumption does not hold, then subclasses should override the `sourceTileChanged(RenderedImage, int, int)` method.

## Sub-classing

Subclasses need to implement at least the following methods:

- `RenderedImage​.getWidth()` — the image width in pixels.
- `RenderedImage​.getHeight()` — the image height in pixels.
- `computeTile(int, int, WritableRaster)` — invoked when a requested tile is not in the cache or needs to be updated.

If pixel coordinates or tile indices do not start at zero, then subclasses shall also override the following methods:

- `PlanarImage​.getMinX()` — the minimum x coordinate (inclusive) of the image.
- `PlanarImage​.getMinY()` — the minimum y coordinate (inclusive) of the image.
- `PlanarImage​.getMinTileX()` — the minimum tile index in the x direction.
- `PlanarImage​.getMinTileY()` — the minimum tile index in the y direction.

## Writable computed images

`ComputedImage` can itself be a `WritableRenderedImage` if subclasses decide so. A writable computed image is an image which can retro-propagate sample value changes to the source images. This class provides `hasTileWriters()`, `getWritableTileIndices()`, `isTileWritable(int, int)` and `markTileWritable(int, int, boolean)` methods for `WritableRenderedImage` implementations convenience.

Apache SIS does not yet define a synchronization policy between `getTile(…)` method and `WritableRenderedImage​​.getWritableTile`/`releaseWritableTile(…)` methods. For example if a call to `getTile(tileX, tileY)` is followed by a call to `getWritableTile(tileX, tileY)` in another thread, there is no guarantees about whether or not the sample values seen in the `Raster` would be isolated from the write operations done concurrently in the `WritableRaster`. A future SIS version may define a policy (possibly based on `ReadWriteLock`).

Note that despite above-cited issue, all methods in this `ComputedImage` class are thread-safe. What is not thread-safe is writing into a `WritableRaster` from outside the `computeTile(…)` method, or reading a `Raster` after it became dirty if the change to dirty state happened after the call to `getTile(…)`.

Since:
  1.1

Defined in the `sis-feature` module

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `protected final SampleModel`
  `sampleModel`
  The sample model shared by all tiles in this image.
  `static final String`
  `SOURCE_PADDING_KEY`
  The property for declaring the amount of additional source pixels needed on each side of a destination pixel.

  ### Fields inherited from class PlanarImage

  `GRID_GEOMETRY_KEY, MASK_KEY, POSITIONAL_ACCURACY_KEY, SAMPLE_RESOLUTIONS_KEY, STATISTICS_KEY`

- ## Constructor Summary

  Constructors
  Modifier
  Constructor
  Description
  `protected`
  `ComputedImage(SampleModel sampleModel, RenderedImage... sources)`
  Creates an initially empty image with the given sample model.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `protected boolean`
  `clearErrorFlags(Rectangle tiles)`
  Clears the error status of all tiles in the given range of indices.
  `protected abstract Raster`
  `computeTile(int tileX, int tileY, WritableRaster previous)`
  Invoked when a tile need to be computed or updated.
  `protected WritableRaster`
  `createTile(int tileX, int tileY)`
  Creates an initially empty tile at the given tile grid position.
  `void`
  `dispose()`
  Advises this image that its tiles will no longer be requested.
  `SampleModel`
  `getSampleModel()`
  Returns the sample model associated with this image.
  `protected final RenderedImage`
  `getSource(int index)`
  Returns the source at the given index.
  `Vector<RenderedImage>`
  `getSources()`
  Returns the immediate sources of image data for this image (may be `null`).
  `final Raster`
  `getTile(int tileX, int tileY)`
  Returns a tile of this image, computing it when needed.
  `int`
  `getTileHeight()`
  Returns the height of tiles in this image.
  `int`
  `getTileWidth()`
  Returns the width of tiles in this image.
  `Point[]`
  `getWritableTileIndices()`
  Returns the indices of all tiles under computation or checked out for writing, or `null` if none.
  `boolean`
  `hasTileWriters()`
  Returns whether any tile is under computation or is checked out for writing.
  `boolean`
  `isTileWritable(int tileX, int tileY)`
  Returns whether the specified tile is currently under computation or checked out for writing.
  `protected boolean`
  `markDirtyTiles(Rectangle tiles)`
  Marks all tiles in the given range of indices as in need of being recomputed.
  `protected boolean`
  `markTileWritable(int tileX, int tileY, boolean writing)`
  Sets or clears whether a tile is checked out for writing.
  `protected void`
  `sourceTileChanged(RenderedImage source, int tileX, int tileY)`
  Invoked when a tile of a source image has been updated.

  ### Methods inherited from class PlanarImage

  `copyData, getBounds, getData, getData, getMinTileX, getMinTileY, getMinX, getMinY, getNumXTiles, getNumYTiles, getProperty, getPropertyNames, getTileGridXOffset, getTileGridYOffset, toString, verify`

  ### Methods inherited from class Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait`

  ### Methods inherited from interface RenderedImage

  `getColorModel, getHeight, getWidth`

- ## Field Details

  - ### SOURCE_PADDING_KEY

    public static final String SOURCE_PADDING_KEY
    The property for declaring the amount of additional source pixels needed on each side of a destination pixel. This property can be used for calculations that require only a fixed rectangular source region around a source pixel in order to compute each destination pixel. A given destination pixel (x, y) may be computed from the neighborhood of source pixels beginning at (x - `Insets​.left`, y - `Insets​.top`) and extending to (x + `Insets​.right`, y + `Insets​.bottom`) inclusive. Those `left`, `top`, `right` and `bottom` attributes can be positive, zero or negative, but their sums shall be positive with (`left` + `right`) ≥ 0 and (`top` + `bottom`) ≥ 0.

    The property value shall be an instance of `Insets` or `Insets[]`. The array form can be used when a different padding is required for each source image. In that case, the image source index is used as the index for accessing the `Insets` element in the array. Null or undefined elements mean that no padding is applied. If the array length is shorter than the number of source images, missing elements are considered as null.

    See Also:
      - `PlanarImage​.getProperty(String)`
      - `sourceTileChanged(RenderedImage, int, int)`
      - Constant Field Values

  - ### sampleModel

    protected final SampleModel sampleModel
    The sample model shared by all tiles in this image. The sample model width determines this image tile width, and the sample model height determines this image tile height.
    Design note: `ComputedImage` requires the sample model to have exactly the desired tile size otherwise tiles created by `createTile(int, int)` will consume more memory than needed.

- ## Constructor Details

  - ### ComputedImage

    protected ComputedImage(SampleModel sampleModel, RenderedImage... sources)
    Creates an initially empty image with the given sample model. The default tile size will be the width and height of the given sample model (this default setting minimizes the amount of memory consumed by `createTile(int, int)`). This constructor automatically registers a `TileObserver` for all sources that are `WritableRenderedImage` instances.
    Parameters:
      `sampleModel` - the sample model shared by all tiles in this image.
      `sources` - sources of this image (may be an empty array), or a null array if unknown.

- ## Method Details

  - ### getSource

    protected final RenderedImage getSource(int index)
    Returns the source at the given index.
    Parameters:
      `index` - index of the desired source.
    Returns:
      source at the given index.
    Throws:
      `IndexOutOfBoundsException` - if the given index is out of bounds.

  - ### getSources

    public Vector<RenderedImage> getSources()
    Returns the immediate sources of image data for this image (may be `null`). This method returns all sources specified at construction time.
    Specified by:
      `getSources` in interface `RenderedImage`
    Overrides:
      `getSources` in class `PlanarImage`
    Returns:
      the immediate sources, or an empty vector is none, or `null` if unknown.

  - ### getSampleModel

    public SampleModel getSampleModel()
    Returns the sample model associated with this image. All rasters returned from this image will have this sample model. In `ComputedImage` implementation, the sample model determines the tile size (this is not necessarily true for all `RenderedImage` implementations).
    Specified by:
      `getSampleModel` in interface `RenderedImage`
    Returns:
      the sample model of this image.

  - ### getTileWidth

    public int getTileWidth()
    Returns the width of tiles in this image. The default implementation returns `SampleModel​.getWidth()`.
    Note: a raster can have a smaller width than its sample model, for example when a raster is a view over a subregion of another raster. But this is not recommended in the particular case of this `ComputedImage` class, because it would cause `createTile(int, int)` to consume more memory than necessary.
    Specified by:
      `getTileWidth` in interface `RenderedImage`
    Returns:
      the width of this image in pixels.

  - ### getTileHeight

    public int getTileHeight()
    Returns the height of tiles in this image. The default implementation returns `SampleModel​.getHeight()`.
    Note: a raster can have a smaller height than its sample model, for example when a raster is a view over a subregion of another raster. But this is not recommended in the particular case of this `ComputedImage` class, because it would cause `createTile(int, int)` to consume more memory than necessary.
    Specified by:
      `getTileHeight` in interface `RenderedImage`
    Returns:
      the height of this image in pixels.

  - ### getTile

    public final Raster getTile(int tileX, int tileY)
    Returns a tile of this image, computing it when needed. This method performs the first of the following actions that apply:
    1. If the requested tile is present in the cache and is not dirty, then that tile is returned immediately.
    2. Otherwise if the requested tile is being computed in another thread, then this method blocks until the other thread completed its work and returns its result. If the other thread failed to compute the tile, an `ImagingOpException` is thrown.
    3. Otherwise this method computes the tile and caches the result before to return it. If an error occurred, an `ImagingOpException` is thrown.

    #### Race conditions with write operations

    If this image implements the `WritableRenderedImage` interface, then a user may acquire the same tile for a write operation after this method returned. In such case there is no consistency guarantees on sample values: the tile returned by this method may show data in an unspecified stage during the write operation. A synchronization policy may be defined in a future Apache SIS version.
    Specified by:
      `getTile` in interface `RenderedImage`
    Parameters:
      `tileX` - the column index of the tile to get.
      `tileY` - the row index of the tile to get.
    Returns:
      the tile at the given index (never null).
    Throws:
      `IllegalArgumentException` - if a given tile index is out of bounds.
      `ImagingOpException` - if an error occurred while computing the image.

  - ### computeTile

    protected abstract Raster computeTile(int tileX, int tileY, WritableRaster previous) throws Exception
    Invoked when a tile need to be computed or updated. This method is invoked by `getTile(int, int)` when the requested tile is not in the cache, or when a writable source notified us that its data changed. The returned tile will be automatically cached.

    A typical implementation is as below:

    >     @Override
    >     protected Raster computeTile(int tileX, int tileY, WritableRaster tile) {
    >         if (tile == null) {
    >             tile = createTile(tileX, tileY);
    >         }
    >         // Do calculation here and write results in tile.
    >         return tile;
    >     }

    #### Error handling

    If this method throws an exception or returns `null`, then `getTile(…)` will set an error flag on the tile and throw an `ImagingOpException` with the exception thrown by `computeTile(…)` as its cause. Future invocations of `getTile(tileX, tileY)` with the same tile indices will cause an `ImagingOpException` to be thrown immediately without invocation of `compute(tileX, tileY)`. If the error has been fixed, then users can invoke `clearErrorFlags(Rectangle)` for allowing the tile to be computed again.
    Parameters:
      `tileX` - the column index of the tile to compute.
      `tileY` - the row index of the tile to compute.
      `previous` - if the tile already exists but needs to be updated, the tile to update. Otherwise `null`.
    Returns:
      computed tile for the given indices. May be the `previous` tile after update but can not be null.
    Throws:
      `Exception` - if an error occurred while computing the tile.

  - ### createTile

    protected WritableRaster createTile(int tileX, int tileY)
    Creates an initially empty tile at the given tile grid position. This is a helper method for `computeTile(int, int, WritableRaster)` implementations.
    Parameters:
      `tileX` - the column index of the tile to create.
      `tileY` - the row index of the tile to create.
    Returns:
      initially empty tile for the given indices (can not be null).

  - ### hasTileWriters

    public boolean hasTileWriters()
    Returns whether any tile is under computation or is checked out for writing. There is two reasons why this method may return `true`:
    - At least one `computeTile(…)` call is running in another thread.
    - There is at least one call to `markTileWritable(tileX, tileY, true)` call without matching call to `markTileWritable(tileX, tileY, false)`. This second case may happen if this `ComputedImage` is also a `WritableRenderedImage`.
    Returns:
      whether any tiles are under computation or checked out for writing.
    See Also:
      - `markTileWritable(int, int, boolean)`
      - `WritableRenderedImage​.hasTileWriters()`

  - ### isTileWritable

    public boolean isTileWritable(int tileX, int tileY)
    Returns whether the specified tile is currently under computation or checked out for writing. There is two reasons why this method may return `true`:
    - `computeTile(tileX, tileY, …)` is running in another thread.
    - There is at least one call to `markTileWritable(tileX, tileY, true)` call without matching call to `markTileWritable(tileX, tileY, false)`. This second case may happen if this `ComputedImage` is also a `WritableRenderedImage`.
    Parameters:
      `tileX` - the X index of the tile to check.
      `tileY` - the Y index of the tile to check.
    Returns:
      whether the specified tile is under computation or checked out for writing.
    See Also:
      - `markTileWritable(int, int, boolean)`
      - `WritableRenderedImage​.isTileWritable(int, int)`

  - ### getWritableTileIndices

    public Point[] getWritableTileIndices()
    Returns the indices of all tiles under computation or checked out for writing, or `null` if none. This method lists all tiles for which the condition documented in `isTileWritable(int, int)` is `true`.
    Returns:
      an array containing the indices of tiles that are under computation or checked out for writing, or `null` if none.
    See Also:
      - `markTileWritable(int, int, boolean)`
      - `WritableRenderedImage​.getWritableTileIndices()`

  - ### markTileWritable

    protected boolean markTileWritable(int tileX, int tileY, boolean writing)
    Sets or clears whether a tile is checked out for writing. This method is provided for subclasses that implement the `WritableRenderedImage` interface. This method can be used as below:

    >     class MyImage extends ComputedImage implements WritableRenderedImage {
    >         // Constructor omitted for brevity.

    >         @Override
    >         public WritableRaster getWritableTile(int tileX, int tileY) {
    >             WritableRaster raster = ...;             // Get the writable tile here.
    >             markTileWritable(tileX, tileY, true);
    >             return raster;
    >         }

    >         @Override
    >         public void releaseWritableTile(int tileX, int tileY) {
    >             markTileWritable(tileX, tileY, false);
    >             // Release the raster here.
    >         }
    >     }

    Parameters:
      `tileX` - the x index of the tile to acquire or release.
      `tileY` - the y index of the tile to acquire or release.
      `writing` - `true` for acquiring the tile, or `false` for releasing it.
    Returns:
      `true` if the tile goes from having no writers to having one writer (may happen if `writing` is `true`), or goes from having one writer to no writers (may happen if `writing` is `false`).
    See Also:
      - `WritableRenderedImage​.getWritableTile(int, int)`
      - `WritableRenderedImage​.releaseWritableTile(int, int)`

  - ### markDirtyTiles

    protected boolean markDirtyTiles(Rectangle tiles)
    Marks all tiles in the given range of indices as in need of being recomputed. The tiles will not be recomputed immediately, but only on next invocation of `getTile(tileX, tileY)` if the `(tileX, tileY)` indices are contained if the specified rectangle.

    Subclasses can invoke this method when the tiles in the given range depend on source data that changed, typically (but not necessarily) source images. Note that there is no need to invoke this method if the source images are instances of `WritableRenderedImage`, because `ComputedImage` already has `TileObserver` for them.

    Parameters:
      `tiles` - indices of tiles to mark as dirty.
    Returns:
      `true` if at least one tile has been marked dirty.

  - ### clearErrorFlags

    protected boolean clearErrorFlags(Rectangle tiles)
    Clears the error status of all tiles in the given range of indices. Those tiles will be marked as dirty and recomputed next time the the `getTile(int, int)` method is invoked. The status of valid tiles is unchanged by this method call.
    Parameters:
      `tiles` - indices of tiles for which to clear the error status.
    Returns:
      `true` if at least one tile got its error flag cleared.
    See Also:
      - `computeTile(int, int, WritableRaster)`

  - ### sourceTileChanged

    protected void sourceTileChanged(RenderedImage source, int tileX, int tileY)
    Invoked when a tile of a source image has been updated. This method should mark as dirty all tiles of this `ComputedImage` that depend on the updated tile.

    The default implementation assumes that source images use pixel coordinate systems aligned with this `ComputedImage` in such a way that all pixels at coordinates (x, y) in the `source` image are used for calculation of pixels at the same (x, y) coordinates in this `ComputedImage`, possibly expanded to neighborhood pixels if the "org.apache.sis.SourcePadding" property is defined. If this assumption does not hold, then subclasses should override this method and invoke `markDirtyTiles(Rectangle)` themselves.

    Parameters:
      `source` - the image that own the tile which has been updated.
      `tileX` - the x index of the tile that has been updated.
      `tileY` - the y index of the tile that has been updated.

  - ### dispose

    public void dispose()
    Advises this image that its tiles will no longer be requested. This method removes all tiles from the cache and stops observation of `WritableRenderedImage` sources. This image should not be used anymore after this method call.

    Note: keep in mind that this image may be referenced as a source of other images. In case of doubt, it may be safer to rely on the garbage collector instead of invoking this method.

    Specified by:
      `dispose` in interface `Disposable`