Class ComputedImage

java.lang.Object
org.apache.sis.image.PlanarImage
org.apache.sis.image.ComputedImage
All Implemented Interfaces:
RenderedImage, Disposable
Direct Known Subclasses:
BandedSampleConverter, BatchComputedImage, ResampledImage, SourceAlignedImage

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:

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

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 guarantee 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
Version:
1.2
  • 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:
    • reference

      private final ComputedTiles reference
      Weak reference to this image, also used as a cleaner when the image is garbage-collected. This reference is retained in TileCache.GLOBAL. Note that if that cache does not cache any tile for this image, then that ComputedTiles may be garbage-collected at the same time than this image and its ComputedTiles.dispose() method may never be invoked.
    • sources

      private final RenderedImage[] sources
      The sources of this image, or null if unknown. This array contains all sources. By contrast the ComputedTiles.sources array contains only the modifiable sources, for which we listen for changes.
      See Also:
    • destination

      private WritableRenderedImage destination
      If the computed image shall be written in an existing image, that image. Otherwise null. If non-null, the sample model of this image shall be equal to sampleModel and the tile indices & pixel coordinate systems shall be aligned.

      The destination image may be larger or smaller than this ComputedImage, by containing more or less tiles (the presence or absence of a tile is a "all or nothing" decision). When this class needs to compute a tile, one of the following choices is executed:

      If this field is set to a non-null value, then this assignment should be done soon after construction time before any tile computation started.
      Note on interaction with tile cache
      The use of a destination image may produce unexpected result if computeTile(int, int, WritableRaster) is invoked two times or more for the same destination tile. It may look like a problem because computed tiles can be discarded and recomputed at any time. However, this problem should not happen because tiles computed by this ComputedImage will not be discarded as long as destination has a reference to that tile. If a ComputedImage tile has been discarded, then it implies that the corresponding destination tile has been discarded as well, in which case the tile computation will restart from scratch; it will not be a recomputation of only this ComputedImage on top of an old destination tile.
      See Also:
    • 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

    • reference

      final Reference<ComputedImage> reference()
      Returns a weak reference to this image. Using weak reference instead of strong reference may help to reduce memory usage when recomputing the image is cheap. This method should not be public because the returned instance implements public interfaces that caller could invoke.
    • setDestination

      final void setDestination(WritableRenderedImage target)
      Sets an existing image where to write the computation result. The sample model of specified image shall be equal to sampleModel and the tile indices & pixel coordinate systems shall be aligned. However, the target image may be larger or smaller than this ComputedImage, by containing more or less tiles (the presence or absence of a tile is a "all or nothing" decision). When this class needs to compute a tile, one of the following choices is executed: If this method is invoked, then is should be done soon after construction time before any tile computation starts.
    • getDestination

      final WritableRenderedImage getDestination()
      Returns the destination, or null if none.
    • getSource

      final RenderedImage getSource()
      Returns the source at index 0 without any check. This method is invoked by subclasses who know that a single source exist. This method should not be in public API because of the absence of verification.
    • 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.
    • getProperty

      private <T> T getProperty(Class<T> type, String name, RenderedImage source)
      Returns the property of the given name if it is of the given type, or null otherwise. If the property value depends on the source image, then it can be an array of type T[], in which case this method will return the element at the source index.
      Type Parameters:
      T - compile-tile value of type argument.
      Parameters:
      type - class of the property to get.
      name - name of the property to get.
      source - the source image if the property may depend on the source.
      Returns:
      requested property if it is an instance of the specified type, or null otherwise.
    • 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.
    • checkTileIndex

      private static void checkTileIndex(String name, int min, int count, int value)
      Verifies that an index is inside the expected range of tile indices. If the index is out of bounds, then this method throws an IndexOutOfBoundsException for consistency with BufferedImage.getTile(int, int) public contract.
      Throws:
      IndexOutOfBoundsException - if the given tile index is out of bounds.
    • 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 guarantee 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:
      IndexOutOfBoundsException - 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:

      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 cannot 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 (cannot be null).
    • prefetch

      protected Disposable prefetch(Rectangle tiles)
      Notifies this image that tiles will be computed soon in the given region. This method is invoked by ImageProcessor.prefetch(RenderedImage, Rectangle) before to request (potentially in multi-threads) all tiles in the area of interest. If the returned Disposable is non-null, ImageProcessor guarantees that the Disposable.dispose() method will be invoked after the prefetch operation completed, successfully or not.

      The default implementation does nothing. Subclasses can override this method if they need to allocate and release resources once for a group of tiles.

      Overrides:
      prefetch in class PlanarImage
      Parameters:
      tiles - indices of the tiles which will be prefetched.
      Returns:
      handler on which to invoke dispose() after the prefetch operation completed (successfully or not), or null if none.
      Since:
      1.2
    • 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:
    • 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:
    • 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

      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:
      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:
    • 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:
    • 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
    • hashCodeBase

      final int hashCodeBase()
      Returns a hash code value based on the fields known to this base class. This is a helper method for Object.hashCode() implementation in subclasses. It should not be used by WritableRenderedImage implementations, because those images have listeners that are attached to a specific instance.
    • equalsBase

      final boolean equalsBase(Object object)
      Compares the given object with this image for equality using the fields known to this base class. This is a helper method for Object.equals(Object) implementation in subclasses. It should not be used by WritableRenderedImage implementations, because those images have listeners that are attached to a specific instance.