/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.image.io; import java.net.URI; import java.net.URL; import java.io.File; import java.io.Closeable; import java.io.IOException; import java.nio.file.Path; import java.util.Locale; import java.util.Set; import java.util.EnumSet; import java.util.Iterator; import java.util.Collections; import java.awt.image.Raster; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ServiceRegistry; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormat; import javax.imageio.stream.ImageInputStream; import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadWarningListener; import javax.imageio.event.IIOReadProgressListener; import org.opengis.coverage.grid.GridEnvelope; import org.apache.sis.util.ArraysExt; import org.geotoolkit.image.io.metadata.SpatialMetadata; import org.geotoolkit.image.io.metadata.SpatialMetadataFormat; import org.geotoolkit.lang.Decorator; import org.geotoolkit.resources.Errors; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.internal.image.io.Formats; import org.geotoolkit.internal.image.io.CheckedImageInputStream; import org.geotoolkit.util.Strings; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; /** * Base class for readers which delegate most of their work to an other {@link ImageReader}. * This is used for reusing an existing image reader while adding some extra functionalities, * like adding geographic information in {@link SpatialMetadata}. * <p> * The wrapped image reader is called the {@linkplain #main} reader. The input given to that * reader is determined by the {@link #createInput(String)} method - it may or may not be the * same input than the one given to this {@code ImageReaderAdapter}. Most methods like * {@link #getWidth(int)}, {@link #getHeight(int)} and {@link #read(int)} delegate directly * to the main reader. * <p> * The amount of methods declared in this class is large, but the only new methods are * {@link #createInput(String)} and {@link #initialize()}. All other methods override * existing methods declared in parent classes, mostly {@link ImageReader} and * {@link SpatialImageReader}. * <p> * Subclasses typically need to implement of override the following methods only: * <p> * <ul> * <li>{@link #initialize()} as a hook available for custom initialization.</li> * <li>{@link #createInput(String)} for defining how, given a "main" file, * to find the additional files (TFW, PRJ, DIM, <i>etc.</i>).</li> * <li>{@link #createMetadata(int)} for creating the {@link IIOMetadata} objects.</li> * </ul> * * {@section Example} * The <cite>World File</cite> format is composed of a classical image file (usually with the * {@code ".tiff"}, {@code ".jpg"} or {@code ".png"} extension) together with a small text file * containing geolocalization information (often with the {@code ".tfw"} extension) and an other * small text file containing projection information ({@code ".prj"} extension). * This {@code ImageReaderAdapter} class can be used for wrapping a TIFF image reader, * augmented with the parsing of TFW and PRJ files, as below: * * {@preformat java * class MyReader extends ImageReaderAdapter { * MyReader(Spi provider) throws IOException { * super(provider); * } * * // Gets the input field with a different file suffix. * private File getInputWithNewSuffix(String newSuffix) { * File file = (File) input; * String name = file.getName(); * name = name.substring(0, name.lastIndexOf('.'); * return new File(file.getParent(), name + suffix); * } * * @Override * protected Object createInput(String readerID) throws IOException { * switch (readerID) { * case "tfw": return getInputWithNewSuffix(".tfw"); * case "prj": return getInputWithNewSuffix(".prj"); * } * return super.createInput(readerID); * } * * @Override * protected SpatialMetadata createMetadata(int imageIndex) throws IOException { * SpatialMetadata metadata = super.createMetadata(imageIndex); * Object inputTFW = createInput("tfw"); * if ((inputTFW instanceof File) && ((File) inputTFW).isFile()) { * // Read the "TFW" file and complete the metadata here. * } * return metadata; * } * } * } * * The information fetched from the TFW and PRJ files are stored in the {@link SpatialMetadata} * object returned by the {@link #createMetadata(int)} method. * * @author Martin Desruisseaux (Geomatys) * @version 3.19 * * @see ImageWriterAdapter * * @since 3.07 * @module */ @Decorator(ImageReader.class) public abstract class ImageReaderAdapter extends SpatialImageReader { /** * The reader to use for reading the pixel values. */ protected final ImageReader main; /** * The input types accepted by both the {@linkplain #main} reader and this reader. */ private final Class<?>[] inputTypes; /** * {@code true} if the {@link #main} reader accepts inputs of kind {@link ImageInputStream}. * If the input types are unspecified, then this field is {@code null}. */ private final boolean acceptStream; /** * Constructs a new image reader. The provider argument is mandatory for this constructor. * If the provider is unknown, use the next constructor below instead. * * @param provider The {@link ImageReaderSpi} that is constructing this object. * @throws IOException If an error occurred while creating the {@linkplain #main} reader. */ protected ImageReaderAdapter(final Spi provider) throws IOException { this(provider, provider.main.createReaderInstance()); } /** * Constructs a new image reader wrapping the given reader. * * @param provider The {@link ImageReaderSpi} that is constructing this object, or {@code null}. * @param main The reader to use for reading the pixel values. */ protected ImageReaderAdapter(final Spi provider, final ImageReader main) { super(provider); this.main = main; ensureNonNull("main", main); if (provider != null) { inputTypes = provider.getMainTypes(); acceptStream = provider.acceptStream; } else { inputTypes = null; acceptStream = true; } } /** * Creates the input to give to the {@linkplain #main} reader, or to an other reader identified * by the {@code readerID} argument. The default {@code ImageReaderAdapter} implementation * invokes this method with a {@code readerID} argument value equals to {@code "main"} when * the {@linkplain #main} reader needs to be used for the first time. This may happen at any * time after the {@link #setInput(Object, boolean, boolean) setInput} method has been invoked. * <p> * The only {@code readerID} argument value accepted by the default implementation is * {@code "main"}; any other argument value causes a {@code null} value to be returned. * However subclasses can override this method for supporting more reader types. The * table below summarizes a few types supported by this reader and different subclasses: * <p> * <table border="1"> * <tr bgcolor="lightblue"> * <th>Reader type</th> * <th>Defined by</th> * <th>Usage</th> * </tr><tr> * <td> {@code "main"} </td> * <td> {@code ImageReaderAdapter} </td> * <td> The input to be given to the {@linkplain #main} reader. </td> * </tr><tr> * <td> {@code "tfw"} </td> * <td> {@link org.geotoolkit.image.io.plugin.WorldFileImageReader} </td> * <td> The input for the <cite>World File</cite>. </td> * </tr><tr> * <td> {@code "prj"} </td> * <td> {@link org.geotoolkit.image.io.plugin.WorldFileImageReader} </td> * <td> The input for the <cite>Map Projection</cite> file. </td> * </tr> * </table> * <p> * The default implementation first checks if the {@linkplain #main} reader accepts directly * the {@linkplain #input input} of this reader. If so, then that input is returned with no * change. Otherwise the input is wrapped in an {@linkplain ImageInputStream}, which is * returned. The input stream assigned to the {@linkplain #main} reader will be closed by * the {@link #close()} method. * * @param readerID Identifier of the reader for which an input is needed. * @return The input to give to the identified reader, or {@code null} if this * method can not create such input. * @throws IllegalStateException if the {@linkplain #input input} source has not been set. * @throws IOException If an error occurred while creating the input for the reader. * * @see ImageWriterAdapter#createOutput(String) */ protected Object createInput(final String readerID) throws IllegalStateException, IOException { final Object input = this.input; if (input == null) { throw new IllegalStateException(getErrorResources().getString(Errors.Keys.NoImageInput)); } if (!"main".equalsIgnoreCase(readerID)) { return null; } if (inputTypes != null) { for (final Class<?> type : inputTypes) { if (type.isInstance(input)) { return input; } } } ImageInputStream in = null; if (acceptStream) { in = ImageIO.createImageInputStream(input); if (in == null) { final Object alternate = IOUtilities.tryToPath(input); if (alternate != input) { in = ImageIO.createImageInputStream(alternate); } } } assert CheckedImageInputStream.isValid(in = // Intentional side effect. CheckedImageInputStream.wrap(in)); return in; } /** * Invoked automatically when the {@linkplain #main} reader has been given a new input. * When this method is invoked, the main reader input has already been set to the value * returned by <code>{@linkplain #createInput(String) createInput}("main")</code>. * <p> * The default implementation does nothing. Subclasses can override this method * for performing additional initialization. * * @throws IOException If an error occurred while initializing the main reader. */ protected void initialize() throws IOException { } /** * Ensures that the input of the {@linkplain #main} reader is initialized. * If not, this method tries to create an informative error message. */ private void ensureInitialized() throws IOException { if (main.getInput() == null) { final Object mainInput = createInput("main"); if (mainInput == null) { throw new InvalidImageStoreException(getErrorResources(), input, inputTypes, false); } main.setInput(mainInput, seekForwardOnly, ignoreMetadata); initialize(); } sync(); } /** * Synchronizes the state of this reader with the state of the {@linkplain #main} reader. */ private void sync() { minIndex = main.getMinIndex(); } /** * Returns the number of images available from the current input source. * The default implementation ensures that the {@linkplain #main} reader * has been {@linkplain #initialize() initialized}, then delegates to that reader. */ @Override public int getNumImages(final boolean allowSearch) throws IllegalStateException, IOException { ensureInitialized(); final int n = main.getNumImages(allowSearch); sync(); return n; } /** * Returns the number of thumbnail preview images associated with the given image. The default * implementation delegates to the {@linkplain #main} reader. Note that the main reader is * indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #hasThumbnails(int) */ @Override public int getNumThumbnails(final int imageIndex) throws IllegalStateException, IOException { checkImageIndex(imageIndex); final int n = main.getNumThumbnails(imageIndex); sync(); return n; } /** * Returns the number of bands available for the specified image. The default implementation * delegates to the {@linkplain #main} reader if it is an instance of {@link SpatialImageReader}, * or returns the number of bands of the {@linkplain ImageReader#getRawImageType(int) raw image * type} otherwise. * <p> * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public int getNumBands(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n; if (main instanceof SpatialImageReader) { n = ((SpatialImageReader) main).getNumBands(imageIndex); } else { n = main.getRawImageType(imageIndex).getNumBands(); } sync(); return n; } /** * Returns the number of dimension of the image at the given index. The default implementation * delegates to the {@linkplain #main} reader if it is an instance of {@link SpatialImageReader}, * or returns 2 otherwise. * <p> * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public int getDimension(final int imageIndex) throws IOException { final int n; if (main instanceof SpatialImageReader) { checkImageIndex(imageIndex); n = ((SpatialImageReader) main).getDimension(imageIndex); } else { n = super.getDimension(imageIndex); } sync(); return n; } /** * Returns the grid range of the image at the given index. The default implementation * delegates to the {@linkplain #main} reader if it is an instance of {@link SpatialImageReader}, * or to the super-class otherwise. * <p> * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. * * @since 3.19 */ @Override public GridEnvelope getGridEnvelope(final int imageIndex) throws IOException { final GridEnvelope range; if (main instanceof SpatialImageReader) { checkImageIndex(imageIndex); range = ((SpatialImageReader) main).getGridEnvelope(imageIndex); } else { range = super.getGridEnvelope(imageIndex); } sync(); return range; } /** * Returns the aspect ratio of the given image. The default implementation delegates to the * {@linkplain #main} reader. Note that the main reader is indirectly initialized by an * implicit call to {@link #getNumImages(boolean)}. * * @see #getWidth(int) * @see #getHeight(int) */ @Override public float getAspectRatio(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final float ar = main.getAspectRatio(imageIndex); sync(); return ar; } /** * Returns the width in pixels of the given image within the input source. The default * implementation delegates to the {@linkplain #main} reader. Note that the main reader * is indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #getTileWidth(int) * @see #getThumbnailWidth(int, int) */ @Override public int getWidth(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getWidth(imageIndex); sync(); return n; } /** * Returns the height in pixels of the given image within the input source. The default * implementation delegates to the {@linkplain #main} reader. Note that the main reader * is indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #getTileHeight(int) * @see #getThumbnailHeight(int, int) */ @Override public int getHeight(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getHeight(imageIndex); sync(); return n; } /** * Returns the width of a tile in the given image. The default implementation delegates to * the {@linkplain #main} reader. Note that the main reader is indirectly initialized by an * implicit call to {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) * @see #getTileGridXOffset(int) */ @Override public int getTileWidth(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getTileWidth(imageIndex); sync(); return n; } /** * Returns the height of a tile in the given image. The default implementation delegates to * the {@linkplain #main} reader. Note that the main reader is indirectly initialized by an * implicit call to {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) * @see #getTileGridYOffset(int) */ @Override public int getTileHeight(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getTileHeight(imageIndex); sync(); return n; } /** * Returns the X coordinate of the upper-left corner of tile (0, 0) in the given image. * The default implementation delegates to the {@linkplain #main} reader. Note that the * main reader is indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) */ @Override public int getTileGridXOffset(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getTileGridXOffset(imageIndex); sync(); return n; } /** * Returns the Y coordinate of the upper-left corner of tile (0, 0) in the given image. * The default implementation delegates to the {@linkplain #main} reader. Note that the * main reader is indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) */ @Override public int getTileGridYOffset(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getTileGridYOffset(imageIndex); sync(); return n; } /** * Returns the width of the thumbnail preview image associated to the given image. The default * implementation delegates to the {@linkplain #main} reader. Note that the main reader is * indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #hasThumbnails(int) * @see #getNumThumbnails(int) */ @Override public int getThumbnailWidth(final int imageIndex, int thumbnailIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getThumbnailWidth(imageIndex, thumbnailIndex); sync(); return n; } /** * Returns the height of the thumbnail preview image associated to the given image. The default * implementation delegates to the {@linkplain #main} reader. Note that the main reader is * indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #hasThumbnails(int) * @see #getNumThumbnails(int) */ @Override public int getThumbnailHeight(final int imageIndex, int thumbnailIndex) throws IOException { checkImageIndex(imageIndex); final int n = main.getThumbnailHeight(imageIndex, thumbnailIndex); sync(); return n; } /** * Returns {@code true} if the given image has thumbnail preview images associated with it. * The default implementation delegates to the {@linkplain #main} reader. Note that the main * reader is indirectly initialized by an implicit call to {@link #getNumImages(boolean)}. * * @see #getNumThumbnails(int) * @see #readerSupportsThumbnails() * @see #getThumbnailWidth(int, int) * @see #getThumbnailHeight(int, int) */ @Override public boolean hasThumbnails(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final boolean b = main.hasThumbnails(imageIndex); sync(); return b; } /** * Returns {@code true} if the image is organized into tiles, that is, equal-sized * non-overlapping rectangles. The default implementation delegates to the {@linkplain #main} * reader. Note that the main reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. * * @see #getTileWidth(int) * @see #getTileHeight(int) * @see #getTileGridXOffset(int) * @see #getTileGridYOffset(int) */ @Override public boolean isImageTiled(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final boolean b = main.isImageTiled(imageIndex); sync(); return b; } /** * Returns {@code true} if the storage format of the given image places no inherent impediment * on random access to pixels. The default implementation delegates to the {@linkplain #main} * reader. Note that the main reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public boolean isRandomAccessEasy(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final boolean b = main.isRandomAccessEasy(imageIndex); sync(); return b; } /** * Returns metadata associated with the input source as a whole. The default implementation * ensures that the {@linkplain #main} reader is initialized, then delegates to the * {@linkplain #createMetadata(int)} method as documented in the * {@linkplain SpatialImageReader#getStreamMetadata() super-class method}. * <p> * Subclasses should consider overriding the {@link #createMetadata(int)} method instead * than this one. */ @Override public SpatialMetadata getStreamMetadata() throws IOException { ensureInitialized(); final SpatialMetadata metadata = super.getStreamMetadata(); sync(); return metadata; } /** * Returns metadata associated with the given image. The default implementation ensures * (indirectly, though a call to {@link #getNumImages(boolean)}) that the {@linkplain #main} * reader is initialized, then delegates to the {@linkplain #createMetadata(int)} method as * documented in the {@linkplain SpatialImageReader#getImageMetadata(int) super-class method}. * <p> * Subclasses should consider overriding the {@link #createMetadata(int)} method instead * than this one. */ @Override public SpatialMetadata getImageMetadata(final int imageIndex) throws IOException { final SpatialMetadata metadata = super.getImageMetadata(imageIndex); sync(); return metadata; } /** * Creates a new stream or image metadata. This method is invoked by the public * {@link #getStreamMetadata()} and {@link #getImageMetadata(int)} methods. The * default implementation delegates to the corresponding method of the * {@linkplain #main} reader. Then there is a choice: * <p> * <ul> * <li>If the metadata is null or already an instance of {@link SpatialMetadata}, * returns it unchanged.</li> * <li>Otherwise wraps the result in a new {@link SpatialMetadata}, which will * delegate the request for any metadata format other than * {@value org.geotoolkit.image.io.metadata.SpatialMetadataFormat#GEOTK_FORMAT_NAME} * to the wrapped format.</li> * </ul> */ @Override protected SpatialMetadata createMetadata(final int imageIndex) throws IOException { if (imageIndex >= 0) { final IIOMetadata metadata = main.getImageMetadata(imageIndex); if (metadata != null) { if (metadata instanceof SpatialMetadata) { final SpatialMetadata sm = (SpatialMetadata) metadata; sm.setReadOnly(false); return sm; } return new SpatialMetadata(false, this, metadata); } } else { final IIOMetadata metadata = main.getStreamMetadata(); if (metadata != null) { if (metadata instanceof SpatialMetadata) { final SpatialMetadata sm = (SpatialMetadata) metadata; sm.setReadOnly(false); return sm; } return new SpatialMetadata(true, this, metadata); } } return null; } /** * Returns {@code true} if the image at the given index has a color palette. The default * implementation delegates to the {@linkplain #main} reader if it is an instance of * {@link SpatialImageReader}, or returns {@code true} otherwise (on the assumption that * the wrapped reader is for some standard format like PNG). * * @since 3.11 */ @Override public boolean hasColors(final int imageIndex) throws IOException { checkImageIndex(imageIndex); if (main instanceof SpatialImageReader) { return ((SpatialImageReader) main).hasColors(imageIndex); } return true; } /** * Returns a collection of {@link ImageTypeSpecifier} containing possible image types * to which the given image may be decoded. The default implementation delegates to the * {@linkplain #main} image reader. Note that the {@linkplain #main} reader is indirectly * initialized by an implicit call to {@link #getNumImages(boolean)}. */ @Override public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final Iterator<ImageTypeSpecifier> it = main.getImageTypes(imageIndex); sync(); return it; } /** * Returns an image type specifier indicating the {@link java.awt.image.SampleModel} and * {@link java.awt.image.ColorModel} which most closely represents the "raw" internal format * of the image. The default implementation delegates to the {@linkplain #main} image reader. * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final ImageTypeSpecifier type = main.getRawImageType(imageIndex); sync(); return type; } /** * Returns a default parameter object appropriate for this format. */ @Override public SpatialImageReadParam getDefaultReadParam() { final ImageReadParam param = main.getDefaultReadParam(); if (param instanceof SpatialImageReadParam) { return (SpatialImageReadParam) param; } return new ImageReadParamAdapter(this, param); } /** * If the given parameter object is an instance of {@link ImageReadParamAdapter}, * returns the wrapped parameters. * * @since 3.18 */ private static ImageReadParam unwrap(ImageReadParam param) { if (param instanceof ImageReadParamAdapter) { param = ((ImageReadParamAdapter) param).param; } return param; } /** * Reads the image indexed by {@code imageIndex} using a default {@link ImageReadParam}. * The default implementation delegates to the {@linkplain #main} image reader. Note that * the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public BufferedImage read(final int imageIndex) throws IOException { checkImageIndex(imageIndex); final BufferedImage image = main.read(imageIndex); sync(); return image; } /** * Reads the image indexed by {@code imageIndex} using the given parameters. * The default implementation delegates to the {@linkplain #main} image reader. * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException { checkImageIndex(imageIndex); final BufferedImage image = main.read(imageIndex, unwrap(param)); sync(); return image; } /** * Reads the image indexed by {@code imageIndex} as a rendered image. * The default implementation delegates to the {@linkplain #main} image reader. * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. */ @Override public RenderedImage readAsRenderedImage(int imageIndex, ImageReadParam param) throws IOException { checkImageIndex(imageIndex); final RenderedImage image = main.readAsRenderedImage(imageIndex, unwrap(param)); sync(); return image; } /** * Reads the tile indicated by the {@code tileX} and {@code tileY} arguments. * The default implementation delegates to the {@linkplain #main} image reader. * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) */ @Override public BufferedImage readTile(final int imageIndex, int tileX, int tileY) throws IOException { checkImageIndex(imageIndex); final BufferedImage image = main.readTile(imageIndex, tileX, tileY); sync(); return image; } /** * Returns a new raster containing the raw pixel data from the image stream, without any color * conversion applied. The default implementation delegates to the {@linkplain #main} image * reader. Note that the {@linkplain #main} reader is indirectly initialized by an implicit * call to {@link #getNumImages(boolean)}. * * @see #canReadRaster() */ @Override public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException { checkImageIndex(imageIndex); final Raster image = main.readRaster(imageIndex, unwrap(param)); sync(); return image; } /** * Reads the raster indicated by the {@code tileX} and {@code tileY} arguments, without any * color conversion applied. The default implementation delegates to the {@linkplain #main} * image reader. Note that the {@linkplain #main} reader is indirectly initialized by an * implicit call to {@link #getNumImages(boolean)}. * * @see #isImageTiled(int) * @see #canReadRaster() */ @Override public Raster readTileRaster(final int imageIndex, int tileX, int tileY) throws IOException { checkImageIndex(imageIndex); final Raster image = main.readTileRaster(imageIndex, tileX, tileY); sync(); return image; } /** * Returns the thumbnail preview image indexed by {@code thumbnailIndex}, associated with the * given image. The default implementation delegates to the {@linkplain #main} image reader. * Note that the {@linkplain #main} reader is indirectly initialized by an implicit call to * {@link #getNumImages(boolean)}. * * @see #hasThumbnails(int) */ @Override public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex) throws IOException { checkImageIndex(imageIndex); final BufferedImage image = main.readThumbnail(imageIndex, thumbnailIndex); sync(); return image; } /** * Returns {@code true} if the image format supports thumbnail preview images associated * with it. The default implementation delegates to the {@linkplain #main} reader. * No input needs to be set for this method. * * @see #hasThumbnails(int) */ @Override public boolean readerSupportsThumbnails() { return main.readerSupportsThumbnails(); } /** * Returns {@code true} if this plug-in supports reading just a {@link Raster} of pixel data. * The default implementation delegates to the {@linkplain #main} reader. * No input needs to be set for this method. */ @Override public boolean canReadRaster() { return main.canReadRaster(); } /** * Returns the locales that may be used to localize warning listeners. * The default implementation delegates to the {@linkplain #main} reader. * No input needs to be set for this method. */ @Override public Locale[] getAvailableLocales() { return main.getAvailableLocales(); } /** * Returns the locale used to localize warning listeners. * The default implementation delegates to the {@linkplain #main} reader. * No input needs to be set for this method. */ @Override public Locale getLocale() { return main.getLocale(); } /** * Sets the locale used to localize warning listeners. * The default implementation delegates to the {@linkplain #main} reader. * No input needs to be set for this method. */ @Override public void setLocale(final Locale locale) { main.setLocale(locale); this.locale = locale; // Bypass the check for available locale. } /** * Adds the given listener to the list of registered warning listeners. * Thie listener is added both to this reader and to the {@linkplain #main} reader. */ @Override public void addIIOReadWarningListener(final IIOReadWarningListener listener) { super.addIIOReadWarningListener(listener); main .addIIOReadWarningListener(listener); } /** * Removes the given listener from the list of registered warning listeners. */ @Override public void removeIIOReadWarningListener(final IIOReadWarningListener listener) { super.removeIIOReadWarningListener(listener); main .removeIIOReadWarningListener(listener); } /** * Removes all currently registered warning listeners. */ @Override public void removeAllIIOReadWarningListeners() { super.removeAllIIOReadWarningListeners(); main .removeAllIIOReadWarningListeners(); } /** * Adds the given listener to the list of registered progress listeners. This method * adds the listener only to the {@linkplain #main} reader, not to this reader, in * order to ensure that progress methods are invoked only once. */ @Override public void addIIOReadProgressListener(final IIOReadProgressListener listener) { main.addIIOReadProgressListener(listener); } /** * Removes the given listener from the list of registered progress listeners. */ @Override public void removeIIOReadProgressListener(final IIOReadProgressListener listener) { super.removeIIOReadProgressListener(listener); // As a safety. main .removeIIOReadProgressListener(listener); } /** * Removes all currently registered progress listeners. */ @Override public void removeAllIIOReadProgressListeners() { super.removeAllIIOReadProgressListeners(); // As a safety. main .removeAllIIOReadProgressListeners(); } /** * Adds the given listener to the list of registered update listeners. This method * adds the listener only to the {@linkplain #main} reader, not to this reader, in * order to ensure that update methods are invoked only once. */ @Override public void addIIOReadUpdateListener(final IIOReadUpdateListener listener) { main.addIIOReadUpdateListener(listener); } /** * Removes the given listener from the list of registered update listeners. */ @Override public void removeIIOReadUpdateListener(final IIOReadUpdateListener listener) { super.removeIIOReadUpdateListener(listener); // As a safety. main .removeIIOReadUpdateListener(listener); } /** * Removes all currently registered update listeners. */ @Override public void removeAllIIOReadUpdateListeners() { super.removeAllIIOReadUpdateListeners(); // As a safety. main .removeAllIIOReadUpdateListeners(); } /** * Requests that any current read operation be aborted. The default implementation delegates * to both the {@linkplain #main} reader and to the super-class method. */ @Override public void abort() { super.abort(); main.abort(); } /** * Restores the {@code ImageReader} to its initial state. The default implementation * delegates to both the {@linkplain #main} reader and to the super-class method. */ @Override public void reset() { super.reset(); main.reset(); } /** * Allows any resources held by this object to be released. The default implementation * delegates to both the {@linkplain #main} reader and to the super-class method. */ @Override public void dispose() { super.dispose(); main.dispose(); } /** * Closes the input stream created by {@link #createInput(String)}. This method does nothing * if the input used by the {@linkplain #main} reader is the one given by the user to the * {@link #setInput(Object, boolean, boolean) setInput} method. Otherwise, if the input of * the main reader is an instance of {@link ImageInputStream} or {@link Closeable}, then it * is closed. * <p> * This method is invoked automatically by {@link #setInput(Object, boolean, boolean) * setInput(...)}, {@link #reset() reset()}, {@link #dispose() dispose()} or * {@link #finalize()} methods and doesn't need to be invoked explicitly. * It has protected access only in order to allow overriding by subclasses. * Overriding methods shall make sure that {@code super.close()} is invoked * even in case of failure. * * @throws IOException if an error occurred while closing the stream. */ @Override protected void close() throws IOException { super.close(); final Object mainInput = main.getInput(); main.setInput(null); if (mainInput != null && mainInput != input) { IOUtilities.close(mainInput); } sync(); } /** * Invokes the {@link #close()} method when this reader is garbage-collected. * Note that this will actually close the stream only if it has been created * by this reader, rather than supplied by the user. */ @Override protected void finalize() throws Throwable { closeSilently(); super.finalize(); } /** * Service provider interface (SPI) for {@link ImageReaderAdapter}s. The constructor of this * class initializes the {@link ImageReaderSpi#inputTypes} field to types that can represent * a filename, like {@link File} or {@link URL}, rather than the usual * {@linkplain #STANDARD_INPUT_TYPE standard input type}. The {@link #names names} and * {@link #MIMETypes MIMETypes} fields are set to the values of the wrapped provider, * suffixed with the string given to the {@link #addFormatNameSuffix(String)} method. * <p> * <b>Example:</b> An {@code ImageReaderAdapter} wrapping the {@code "tiff"} image reader * with the {@code "-wf"} suffix will have the {@code "tiff-wf"} format name and the * {@code "image/x-tiff-wf"} MIME type. * <p> * The table below summarizes the initial values. * Those values can be modified by subclass constructors. * <p> * <table border="1"> * <tr bgcolor="lightblue"> * <th>Field</th> * <th>Value</th> * </tr><tr> * <td> {@link #names} </td> * <td> Same values than the {@linkplain #main} provider, suffixed by the given string. </td> * </tr><tr> * <td> {@link #suffixes} </td> * <td> Same values than the {@linkplain #main} provider. </td> * </tr><tr> * <td> {@link #MIMETypes} </td> * <td> Same values than the {@linkplain #main} provider, suffixed by the given string. </td> * </tr><tr> * <td> {@link #inputTypes} </td> * <td> {@link String}, {@link File}, {@link URI}, {@link URL} </td> * </tr> * </table> * <p> * It is up to subclass constructors to initialize all other instance variables * in order to provide working versions of every methods. * * @author Martin Desruisseaux (Geomatys) * @version 3.20 * * @see ImageWriterAdapter.Spi * * @since 3.07 * @module */ public abstract static class Spi extends SpatialImageReader.Spi { /** * List of legal input and output types for {@link ImageReaderAdapter} and * {@link ImageWriterAdapter} - except the two last elements which are * different for writers. */ static final Class<?>[] TYPES = new Class<?>[] { Path.class, File.class, URI.class, URL.class, String.class, // To be interpreted as file path. // TODO InputStream.class, // GEOTK-231 ImageInputStream.class }; /** * The value to be returned by {@link #getModifiedInformation(Object)}. */ static final Set<InformationType> INFO = Collections.unmodifiableSet(EnumSet.allOf(InformationType.class)); /** * The provider of the readers to use for reading the pixel values. * This is the provider specified at the construction time. */ protected final ImageReaderSpi main; /** * {@code true} if the {@link #main} provider accepts inputs of kind {@link ImageInputStream}. */ final boolean acceptStream; /** * Creates an {@code ImageReaderAdapter.Spi} wrapping the given provider. The fields are * initialized as documented in the <a href="#skip-navbar_top">class javadoc</a>. It is up * to the subclass to initialize all other instance variables in order to provide working * versions of all methods. * <p> * For efficiency reasons, the {@code inputTypes} field is initialized to a shared array. * Subclasses can assign new arrays, but should not modify the default array content. * * @param main The provider of the readers to use for reading the pixel values. */ protected Spi(final ImageReaderSpi main) { ensureNonNull("main", main); this.main = main; names = main.getFormatNames(); suffixes = main.getFileSuffixes(); MIMETypes = main.getMIMETypes(); inputTypes = TYPES; supportsStandardStreamMetadataFormat = main.isStandardStreamMetadataFormatSupported(); supportsStandardImageMetadataFormat = main.isStandardImageMetadataFormatSupported(); nativeStreamMetadataFormatName = main.getNativeStreamMetadataFormatName(); nativeImageMetadataFormatName = main.getNativeImageMetadataFormatName(); extraStreamMetadataFormatNames = main.getExtraStreamMetadataFormatNames(); extraImageMetadataFormatNames = main.getExtraImageMetadataFormatNames(); boolean acceptStream = false; for (final Class<?> type : main.getInputTypes()) { if (type.isAssignableFrom(ImageInputStream.class)) { acceptStream = true; break; } } this.acceptStream = acceptStream; } /** * Creates a provider which will use the given format for reading pixel values. * This is a convenience constructor for the above constructor with a provider * fetched from the given format name. * * @param format The name of the provider to use for reading the pixel values. * @throws IllegalArgumentException If no provider is found for the given format. */ protected Spi(final String format) { this(Formats.getReaderByFormatName(format, Spi.class)); } /** * Adds the given suffix to all {@linkplain #names format names} and * {@linkplain #MIMETypes MIME types}. Subclasses should invoke this * method in their constructor. * * @param suffix The suffix to append to format names and MIME types. * * @since 3.20 */ protected void addFormatNameSuffix(final String suffix) { addFormatNameSuffix(names, MIMETypes, suffix); } /** * Appends the given suffix in the given names array if the array is non-null, * then updates the MIME type arrays. The replacement is performed in-place. */ static void addFormatNameSuffix(final String[] names, final String[] MIMETypes, final String suffix) { if (names != null && !suffix.isEmpty()) { final String upper = suffix.toUpperCase(Locale.ENGLISH); final boolean[] replaced = new boolean[(MIMETypes != null) ? MIMETypes.length : 0]; for (int i=0; i<names.length; i++) { final String oldName = names[i]; final String newName = oldName.concat(Strings.isUpperCase(oldName) ? upper : suffix); for (int j=0; j<replaced.length; j++) { if (!replaced[j]) { MIMETypes[j] = MIMETypes[j].replace(oldName, newName); replaced[j] = true; } } names[i] = newName; } } } /** * {@inheritDoc} */ @Override public IIOMetadataFormat getStreamMetadataFormat(final String formatName) { switch (getMetadataFormatCode(formatName, nativeStreamMetadataFormatName, nativeStreamMetadataFormatClassName, extraStreamMetadataFormatNames, extraStreamMetadataFormatClassNames)) { case 1: return SpatialMetadataFormat.getStreamInstance(formatName); case 2: return super.getStreamMetadataFormat(formatName); default: return main.getStreamMetadataFormat(formatName); } } /** * {@inheritDoc} */ @Override public IIOMetadataFormat getImageMetadataFormat(final String formatName) { switch (getMetadataFormatCode(formatName, nativeImageMetadataFormatName, nativeImageMetadataFormatClassName, extraImageMetadataFormatNames, extraImageMetadataFormatClassNames)) { case 1: return SpatialMetadataFormat.getImageInstance(formatName); case 2: return super.getImageMetadataFormat(formatName); default: return main.getImageMetadataFormat(formatName); } } /** * Returns the input types accepted by the {@linkplain #main} provider which are also * accepted by this provider, or {@code null} if none. */ final Class<?>[] getMainTypes() { return getMainTypes(inputTypes, main.getInputTypes()); } /** * Implementation of {@link #getMainTypes()}. The {@code types} argument must be a copy * of the main types array, because it will be modified in-place. */ static Class<?>[] getMainTypes(final Class<?>[] adapterTypes, final Class<?>[] types) { int count = 0; for (int i=0; i<types.length; i++) { final Class<?> mainType = types[i]; for (final Class<?> thisType : adapterTypes) { if (mainType.isAssignableFrom(thisType)) { types[count++] = mainType; break; } } } return (count != 0) ? ArraysExt.resize(types, count) : null; } /** * Returns {@code true} if the supplied source object appears to be of the format supported * by this reader. The default implementation checks if the given source is an instance of * one of the {@link ImageReaderSpi#inputTypes inputTypes}, then delegates to the provider * given at construction time. A temporary {@link ImageInputStream} is created if needed. * * @param source The input (typically a {@link File}) to be decoded. * @return {@code true} if it is likely that the file can be decoded. * @throws IOException If an error occurred while reading the file. */ @Override public boolean canDecodeInput(final Object source) throws IOException { ensureNonNull("source", source); for (final Class<?> type : inputTypes) { if (type.isInstance(source)) { if (main.canDecodeInput(source)) { return true; } if (acceptStream && !ImageInputStream.class.isInstance(source)) { final ImageInputStream in = Formats.createUncachedImageInputStream(source); if (in != null) try { return main.canDecodeInput(in); } finally { in.close(); } } break; } } return false; } /** * Returns the kind of information that this wrapper will add or modify compared to the * {@linkplain #main} reader. If this method returns an empty set, then there is no * raison to use this adapter instead than the main reader. * <p> * The default implementation conservatively returns all of the {@link InformationType} * enum values. Subclasses should return more accurate information when possible. * * @param source The input (typically a {@link File}) to be decoded. * @return The set of information to be read or modified by this adapter. * @throws IOException If an error occurred while reading the file. * * @since 3.20 */ public Set<InformationType> getModifiedInformation(final Object source) throws IOException { return INFO; } /** * A callback that will be called exactly once after the {@code Spi} class has been * instantiated and registered in a {@code ServiceRegistry}. The default implementation * conservatively gives precedence to the {@linkplain #main} provider, using the code * below: * * {@preformat java * registry.setOrdering(category, main, this); * } * * The plugin order matter when an {@linkplain javax.imageio.ImageIO#getImageReadersBySuffix(String) * image reader is selected by file suffix}, because the {@linkplain #getFileSuffixes() file suffixes} * of this adapter are the same than the file suffixes of the {@linkplain #main} provider by default. * * @see ServiceRegistry#setOrdering(Class, Object, Object) */ @Override @SuppressWarnings({"unchecked","rawtypes"}) public void onRegistration(final ServiceRegistry registry, final Class<?> category) { registry.setOrdering((Class) category, main, this); } /** * If the given provider is an instance of {@code ImageReaderAdapter.Spi}, returns the * underlying {@linkplain #main} provider. Otherwise returns the given provider unchanged. * <p> * This method is convenient when the caller is not interested in spatial metadata, * in order to ensure that the cost of parsing TFW, PRJ or similar files is avoided. * * @param spi An image reader provider, or {@code null}. * @return The wrapped image reader provider, or {@code null}. * * @since 3.14 */ public static ImageReaderSpi unwrap(ImageReaderSpi spi) { while (spi instanceof Spi) { spi = ((Spi) spi).main; } return spi; } } }