Class AnnotatedImage

All Implemented Interfaces:
RenderedImage
Direct Known Subclasses:
StatisticsCalculator

abstract class AnnotatedImage extends ImageAdapter
An image which wraps an existing image unchanged, except for properties which are computed on the fly when first requested. All RenderedImage methods delegate to the wrapped image except ImageAdapter.getSources() and the methods for getting the property names or values.

The name of the computed property is given by getComputedPropertyName(). If an exception is thrown during calculation and failOnException is false, then AnnotatedImage automatically creates another property with the same name and ".warnings" suffix. That property will contain the exception encapsulated in a LogRecord in order to retain additional information such as the instant when the first error occurred.

The computation results are cached by this class. The cache strategy assumes that the property value depend only on sample values, not on properties of the source image.

Design note: most non-abstract methods are final because PixelIterator (among others) relies on the fact that it can unwrap this image and still get the same pixel values.
Since:
1.1
Version:
1.2
  • Field Details

    • WARNINGS_SUFFIX

      public static final String WARNINGS_SUFFIX
      The suffix to add to property name for errors that occurred during computation. A property with suffix is automatically created if an exception is thrown during computation and failOnException is false.
      See Also:
    • NULL

      private static final Object NULL
      An arbitrary value that we use for storing null values in the cache.
    • CACHE

      private static final WeakHashMap<RenderedImage,Cache<Object,Object>> CACHE
      Cache of properties already computed for images. That map shall contain computation results only, never the AnnotatedImage instances that computed those results, as doing so would create memory leak (because of the ImageAdapter.source reference preventing the key to be garbage-collected). All accesses to this cache shall be synchronized on the CACHE instance.

      In current implementation we cache only the values that have been computed without warnings. We do that because otherwise, an AnnotatedImage with failOnException flag set could wrongly return a partially computed value if that value has been cached by another image instance with the failOnException flag unset. As a consequence of this policy, if the computation failed for some tiles, that computation will be redone again for the same property every time it is requested, until it eventually fully succeeds and the result become cached.

    • cache

      private final Cache<Object,Object> cache
      Cache of property values computed for the ImageAdapter.source image. This is an entry from the global CACHE. This cache is shared by all AnnotatedImage instances wrapping the same ImageAdapter.source image in order to avoid computing the same property many times if an AnnotatedImage wrapper is recreated many times for the same operation on the same image.

      Note that null is a valid result. Since Cache cannot store null values, those results are replaced by NULL.

      Keys are String instances containing directly the property name when areaOfInterest and getExtraParameter() are null, or AnnotatedImage.CacheKey instances otherwise.

    • areaOfInterest

      protected final Shape areaOfInterest
      Pixel coordinates of the region for which to compute the values, or null for the whole image. If non-null, the Shape.contains(double, double) method may be invoked for testing if a pixel shall be included in the computation or not.

      This shape should not be modified, either by this class or by the caller who provided the shape. The Shape implementation shall be thread-safe, assuming its state stay unmodified, unless the parallel argument specified to the constructor was false.

      If areaOfInterest is null, then boundsOfInterest is always null. However, the converse is not necessarily true.

    • boundsOfInterest

      protected final Rectangle boundsOfInterest
      Bounds of areaOfInterest intersected with image bounds, or null for the whole image. If the area of interest fully contains those bounds, then areaOfInterest is set to the same reference than boundsOfInterest. Subclasses can use areaOfInterest == boundsOfInterest for quickly testing if the area of interest is rectangular.

      If areaOfInterest is null, then boundsOfInterest is always null. However, the converse is not necessarily true.

    • errors

      private volatile ErrorHandler.Report errors
      The errors that occurred while computing the result, or null if none or not yet determined. This field is never set if failOnException is true.
    • parallel

      private final boolean parallel
      Whether parallel execution is authorized for the ImageAdapter.source image. If true, then RenderedImage.getTile(int, int) implementation should be concurrent.
    • failOnException

      private final boolean failOnException
      Whether errors occurring during computation should be propagated instead of wrapped in a LogRecord.
  • Constructor Details

    • AnnotatedImage

      protected AnnotatedImage(RenderedImage source, Shape areaOfInterest, boolean parallel, boolean failOnException)
      Creates a new annotated image wrapping the given image. The annotations are the additional properties computed by the subclass.
      Parameters:
      source - the image to wrap for adding properties (annotations).
      areaOfInterest - pixel coordinates of AOI, or null for the whole image.
      parallel - whether parallel execution is authorized.
      failOnException - whether errors occurring during computation should be propagated.
  • Method Details

    • getExtraParameter

      Object[] getExtraParameter()
      Returns an optional parameter specific to subclass. This is used for caching purpose and for equals(Object) and hashCode() method implementations only, i.e. for distinguishing between two AnnotatedImage instances that are identical except for subclass-defined parameters.
      API note: the return value is an array because there is typically one parameter value per band. This method will not modify the returned array.
      Returns:
      subclass specific extra parameter, or null if none.
    • unique

      final RenderedImage unique()
      If the source image is the same operation for the same area of interest, returns that source. Otherwise returns this or a previous instance doing the same operation than this.
      See Also:
    • getCacheKey

      private Object getCacheKey(String property)
      Returns the key to use for entries in the cache map.
      Parameters:
      property - value of getPropertyNames().
    • getComputedPropertyName

      protected abstract String getComputedPropertyName()
      Returns the name of the property which is computed by this image.
      Returns:
      name of property computed by this image. Shall not be null.
    • getPropertyNames

      public String[] getPropertyNames()
      Returns an array of names recognized by getProperty(String). The default implementation returns the ImageAdapter.source properties names followed by getComputedPropertyName(). If that property has already been computed and an error occurred, then the names returned by this method will include the property name with ".warnings" suffix.
      Specified by:
      getPropertyNames in interface RenderedImage
      Overrides:
      getPropertyNames in class ImageAdapter
      Returns:
      all recognized property names.
    • isErrorProperty

      private static boolean isErrorProperty(String cn, String name)
      Returns whether the given name is the name of the error property. The implementation of this method avoids the creation of concatenated string.
      Parameters:
      cn - name of the computed property.
      name - the property name to test (may be null).
      Returns:
      whether name is cn + ".warnings".
    • getProperty

      public final Object getProperty(String name)
      Gets a property from this image or from its source. If the given name is for the property to be computed by this class and if that property has not been computed before, then this method invokes computeProperty() and caches its result.
      Specified by:
      getProperty in interface RenderedImage
      Overrides:
      getProperty in class ImageAdapter
      Parameters:
      name - name of the property to get.
      Returns:
      the property for the given name (null is a valid result), or Image.UndefinedProperty if the given name is not a recognized property name.
    • logAndClearError

      final void logAndClearError(Class<?> classe, String method, ErrorHandler handler)
      If an error occurred, logs the message with the specified class and method as the source. The classe and method arguments overwrite the LogRecord.getSourceClassName() and LogRecord.getSourceMethodName() values. The log record is cleared by this method call and will no longer be reported, unless the property is recomputed.

      Context of use

      This method should be invoked only on images that are going to be disposed after the caller extracted the computed property value. This method should not be invoked on image accessible by the user, because clearing the error may be surprising.
      Parameters:
      classe - the class to report as the source of the logging message.
      method - the method to report as the source of the logging message.
      handler - where to send the log message.
    • computeProperty

      protected Object computeProperty() throws Exception
      Invoked when the property needs to be computed. If the property cannot be computed, then the result will be null and the exception thrown by this method will be wrapped in a property of the same name with the ".warnings" suffix.

      The default implementation makes the following choice:

      • If parallel is true, collector() returns a non-null value and the area of interest covers at least two tiles, then this method distributes calculation on many threads using the functions provided by the collector. See collector() Javadoc for more information.
      • Otherwise this method delegates to computeSequentially().
      Returns:
      the computed property value. Note that null is a valid result.
      Throws:
      Exception - if an error occurred while computing the property.
    • computeSequentially

      protected abstract Object computeSequentially() throws Exception
      Invoked when the property needs to be computed sequentially (all computations in current thread). If the property cannot be computed, then the result will be null and the exception thrown by this method will be wrapped in a property of the same name with the ".warnings" suffix.

      This method is invoked when this class does not support parallel execution (collector() returned null), or when it is not worth to parallelize (image has only one tile), or when the ImageAdapter.source image may be non-thread safe (parallel is false).

      Returns:
      the computed property value. Note that null is a valid result.
      Throws:
      Exception - if an error occurred while computing the property.
    • collector

      protected Collector<? super Raster,?,?> collector()
      Returns the function to execute for computing the property value, together with other required functions (supplier of accumulator, combiner, finisher). Those functions allow multi-threaded property calculation. This collector is used in a way similar to Stream.collect(Collector). A typical approach is two define 3 private methods in the subclass as below (where P is the type of the property to compute):
      Returns:
      functions for multi-threaded computation of property value, or null if unsupported.
    • cloneProperty

      protected Object cloneProperty(String name, Object value)
      Invoked when a property of the given name has been requested and that property is cached. If the property is mutable, subclasses may want to clone it before to return it to users. The default implementation returns value unchanged.
      Parameters:
      name - the property name.
      value - the property value (never null).
      Returns:
      the property value to give to user.
    • appendStringContent

      final Class<AnnotatedImage> appendStringContent(StringBuilder buffer)
      Appends the name of the computed property in the ImageAdapter.toString() representation, after the class name and before the string representation of the wrapped image.
      Specified by:
      appendStringContent in class ImageAdapter
      Parameters:
      buffer - where to start writing content of ImageAdapter.toString() representation.
      Returns:
      name of the class to show in the ImageAdapter.toString() representation.
    • hashCode

      public final int hashCode()
      Returns a hash code value for this image. This method should be quick; it should not compute the hash code from sample values.
      Overrides:
      hashCode in class ImageAdapter
      Returns:
      a hash code value based on a description of the operation performed by this image.
    • equals

      public final boolean equals(Object object)
      Compares the given object with this image for equality. This method should be quick and compare how images compute their values from their sources; it should not compare the actual pixel values.

      Requirements for subclasses

      Subclasses should override getExtraParameter() instead of this method.
      Overrides:
      equals in class ImageAdapter
      Parameters:
      object - the object to compare with this image.
      Returns:
      true if the given object is an image performing the same calculation than this image.
      See Also:
    • equalParameters

      private boolean equalParameters(AnnotatedImage other)
      Returns true if the area of interest and some other fields are equal. The boundsOfInterest is omitted because it is derived from areaOfInterest. The errors is omitted because it is part of computation results.