/*
* 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.util.Locale;
import java.util.Set;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.event.IIOWriteProgressListener;
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.apache.sis.util.Classes;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.image.io.SpatialImageReader.Spi.getMetadataFormatCode;
/**
* Base class for writers which delegate most of their work to an other {@link ImageWriter}.
* This is used for reusing an existing image writer while adding some extra functionalities,
* like encoding geographic information found in {@link SpatialMetadata}.
* <p>
* The wrapped image writer is called the {@linkplain #main} writer. The output given to that
* writer is determined by the {@link #createOutput(String)} method - it may or may not be the
* same output than the one given to this {@code ImageWriterAdapter} {@link #setOutput(Object)
* setOutput(Object)} method.
* <p>
* The amount of methods declared in this class is large, but the only new methods are:
* <p>
* <ul>
* <li>{@link #createOutput(String)}</li>
* <li>{@link #initialize()}</li>
* <li>{@link #writeStreamMetadata(IIOMetadata)}</li>
* <li>{@link #writeImageMetadata(IIOMetadata, int, ImageWriteParam)}</li>
* </ul>
* <p>
* All other methods override existing methods declared in parent classes, mostly
* {@link ImageWriter} and {@link SpatialImageWriter}. The default implementation of
* most methods delegate directly to the {@linkplain #main} writer.
*
* {@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 ImageWriterAdapter} class can be used for wrapping a TIFF image writer, augmented with
* the formatting of TFW and PRJ files. The information written in those files are fetched from
* the {@link SpatialMetadata} object given to the {@link #write(IIOImage)} method in this class.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.18
*
* @see ImageReaderAdapter
*
* @since 3.07
* @module
*/
@Decorator(ImageWriter.class)
public abstract class ImageWriterAdapter extends SpatialImageWriter {
/**
* The writer to use for writing the pixel values.
*/
protected final ImageWriter main;
/**
* The output types accepted by both the {@linkplain #main} writer and this writer.
* If the output types are unspecified, then this field is {@code null}.
*/
private final Class<?>[] outputTypes;
/**
* {@code true} if the {@link #main} writer accepts outputs of kind {@link ImageOutputStream}.
*/
private final boolean acceptStream;
/**
* Constructs a new image writer. The provider argument is mandatory for this constructor.
* If the provider is unknown, use the next constructor below instead.
*
* @param provider The {@link ImageWriterSpi} that is constructing this object.
* @throws IOException If an error occurred while creating the {@linkplain #main} writer.
*/
protected ImageWriterAdapter(final Spi provider) throws IOException {
this(provider, provider.main.createWriterInstance());
}
/**
* Constructs a new image writer wrapping the given writer.
*
* @param provider The {@link ImageWriterSpi} that is constructing this object, or {@code null}.
* @param main The writer to use for writing the pixel values.
*/
protected ImageWriterAdapter(final Spi provider, final ImageWriter main) {
super(provider);
this.main = main;
ensureNonNull("main", main);
if (provider != null) {
outputTypes = provider.getMainTypes();
acceptStream = provider.acceptStream;
} else {
outputTypes = null;
acceptStream = true;
}
}
/**
* Creates the output to give to the {@linkplain #main} writer, or to an other writer identified
* by the {@code writerID} argument. The default {@code ImageWriterAdapter} implementation
* invokes this method with a {@code writerID} argument value equals to {@code "main"} when
* the {@linkplain #main} writer needs to be used for the first time. This may happen at any
* time after the {@link #setOutput(Object) setOutput} method has been invoked.
* <p>
* The only {@code writerID} 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 writer types. The
* table below summarizes a few types supported by this writer and different subclasses:
* <p>
* <table border="1">
* <tr bgcolor="lightblue">
* <th>Writer type</th>
* <th>Defined by</th>
* <th>Usage</th>
* </tr><tr>
* <td> {@code "main"} </td>
* <td> {@code ImageWriterAdapter} </td>
* <td> The output to be given to the {@linkplain #main} writer. </td>
* </tr><tr>
* <td> {@code "tfw"} </td>
* <td> {@link org.geotoolkit.image.io.plugin.WorldFileImageWriter} </td>
* <td> The output for the <cite>World File</cite>. </td>
* </tr><tr>
* <td> {@code "prj"} </td>
* <td> {@link org.geotoolkit.image.io.plugin.WorldFileImageWriter} </td>
* <td> The output for the <cite>Map Projection</cite> file. </td>
* </tr>
* </table>
* <p>
* The default implementation first checks if the {@linkplain #main} writer accepts directly
* the {@linkplain #output output} of this writer. If so, then that output is returned with
* no change. Otherwise the output is wrapped in an {@linkplain ImageOutputStream}, which is
* returned. The output stream assigned to the {@linkplain #main} writer will be closed by
* the {@link #close()} method.
*
* @param writerID Identifier of the writer for which an output is needed.
* @return The output to give to the identified writer, or {@code null} if this method can
* not create such output.
* @throws IllegalStateException if the {@linkplain #output output} has not been set.
* @throws IOException If an error occurred while creating the output for the writer.
*
* @see ImageReaderAdapter#createInput(String)
*/
protected Object createOutput(final String writerID) throws IllegalStateException, IOException {
final Object output = this.output;
if (output == null) {
throw new IllegalStateException(getErrorResources().getString(Errors.Keys.NoImageOutput));
}
if (!"main".equalsIgnoreCase(writerID)) {
return null;
}
if (outputTypes != null) {
for (final Class<?> type : outputTypes) {
if (type.isInstance(output)) {
return output;
}
}
}
ImageOutputStream out = null;
if (acceptStream) {
out = ImageIO.createImageOutputStream(output);
if (out == null) {
final Object alternate = IOUtilities.tryToFile(output);
if (alternate != output) {
out = ImageIO.createImageOutputStream(alternate);
}
}
}
return out;
}
/**
* Invoked automatically when the {@linkplain #main} writer has been given a new output.
* When this method is invoked, the main writer output has already been set to the value
* returned by <code>{@linkplain #createOutput(String) createOutput}("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 writer.
*/
protected void initialize() throws IOException {
}
/**
* Ensures that the output of the {@linkplain #main} writer is initialized.
* If not, this method tries to create an informative error message.
*/
private void ensureOutputInitialized() throws IOException {
if (main.getOutput() == null) {
final Object mainOutput = createOutput("main");
if (mainOutput == null) {
throw new InvalidImageStoreException(getErrorResources(), output, outputTypes, true);
}
main.setOutput(mainOutput);
initialize();
}
}
/**
* Returns a default parameter object appropriate for this format.
*/
@Override
public SpatialImageWriteParam getDefaultWriteParam() {
final ImageWriteParam param = main.getDefaultWriteParam();
if (param instanceof SpatialImageWriteParam) {
return (SpatialImageWriteParam) param;
}
return new ImageWriteParamAdapter(this, main.getDefaultWriteParam());
}
/**
* If the given parameter object is an instance of {@link ImageWriteParamAdapter},
* returns the wrapped parameters.
*
* @since 3.18
*/
private static ImageWriteParam unwrap(ImageWriteParam param) {
if (param instanceof ImageWriteParamAdapter) {
param = ((ImageWriteParamAdapter) param).param;
}
return param;
}
/**
* Returns a metadata object containing default values for encoding a stream of images.
* The default implementation returns the union of the metadata formats declared by the
* {@linkplain #main} writer, and the Geotk
* {@linkplain SpatialMetadataFormat#getStreamInstance(String) stream metadata format}.
* <p>
* Subclasses can override the {@link #writeStreamMetadata(IIOMetadata)} method
* for writing those metadata to the output.
*/
@Override
public SpatialMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
final IIOMetadata metadata = main.getDefaultStreamMetadata(unwrap(param));
if (metadata == null && !isSpatialMetadataSupported(true)) {
return null;
}
return new SpatialMetadata(true, this, metadata);
}
/**
* Returns a metadata object containing default values for encoding an image of the given type.
* The default implementation returns the union of the metadata formats declared by the
* {@linkplain #main} writer, and the Geotk
* {@linkplain SpatialMetadataFormat#getImageInstance(String) image metadata format}.
* <p>
* Subclasses can override the {@link #writeImageMetadata(IIOMetadata, int, ImageWriteParam)}
* method for writing those metadata to the output.
*/
@Override
public SpatialMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType,
final ImageWriteParam param)
{
final IIOMetadata metadata = main.getDefaultImageMetadata(imageType, unwrap(param));
if (metadata == null && !isSpatialMetadataSupported(false)) {
return null;
}
return new SpatialMetadata(false, this, metadata);
}
/**
* Invoked by the {@code write} methods when stream metadata needs to be written.
* The metadata written by the {@linkplain #main} writer are not concerned by this method.
* <p>
* The default implementation does nothing. Subclasses can override this method for
* writing spatial metadata.
*
* @param metadata The stream metadata, or {@code null} if none.
* @throws IOException If an error occurred while writing the metadata.
*
* @see org.geotoolkit.image.io.metadata.MetadataHelper
*/
protected void writeStreamMetadata(IIOMetadata metadata) throws IOException {
}
/**
* Invoked by the {@code write} methods when image metadata needs to be written.
* The metadata written by the {@linkplain #main} writer are not concerned by this method.
* <p>
* The default implementation does nothing. Subclasses can override this method for
* writing spatial metadata.
*
* @param metadata The stream metadata, or {@code null} if none.
* @param imageIndex The index of the image being written.
* @param param The user-specified parameter, or {@code null} if none.
* @throws IOException If an error occurred while writing the metadata.
*
* @see org.geotoolkit.image.io.metadata.MetadataHelper
*/
protected void writeImageMetadata(IIOMetadata metadata, int imageIndex, ImageWriteParam param)
throws IOException
{
}
/**
* Appends a complete image stream containing a single image and associated stream and image
* metadata and thumbnails to the output. The default implementation performs the following
* steps:
* <p>
* <ol>
* <li>Ensures that the {@linkplain #main} writer has been
* {@linkplain #initialize() initialized}</li>
* <li>Invokes {@link #writeStreamMetadata(IIOMetadata)} with the given
* {@code streamMetadata} argument.</li>
* <li>Invokes {@link #writeImageMetadata(IIOMetadata, int, ImageWriteParam)}
* with the metadata obtained from the {@code image} argument.</li>
* <li>Delegates the writing of pixel values to the {@linkplain #main} writer.</li>
* </ol>
*/
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param)
throws IOException
{
ensureOutputInitialized();
writeStreamMetadata(streamMetadata);
writeImageMetadata(image.getMetadata(), 0, param);
main.write(streamMetadata, image, unwrap(param));
}
/**
* Returns true if the methods that take an {@link IIOImage} parameter are capable of dealing
* with a {@link Raster}. The default implementation delegates to the {@linkplain #main} writer.
* No output needs to be set for this method.
*/
@Override
public boolean canWriteRasters() {
return main.canWriteRasters();
}
/**
* Returns {@code true} if the writer is able to append an image to an image stream that
* already contains header information and possibly prior images. The default implementation
* delegates to the {@linkplain #main} writer. No output needs to be set for this method.
*/
@Override
public boolean canWriteSequence() {
return main.canWriteSequence();
}
/**
* Prepares a stream to accept a series of subsequent {@link #writeToSequence writeToSequence}
* calls. The default implementation performs the following steps:
* <p>
* <ol>
* <li>Ensures that the {@linkplain #main} writer has been
* {@linkplain #initialize() initialized}</li>
* <li>Invokes {@link #writeStreamMetadata(IIOMetadata)} with the given
* {@code streamMetadata} argument.</li>
* <li>Delegates to the {@linkplain #main} writer.</li>
* </ol>
*/
@Override
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
ensureOutputInitialized();
writeStreamMetadata(streamMetadata);
main.prepareWriteSequence(streamMetadata);
}
/**
* Appends a single image and possibly associated metadata to the output.
* The default implementation performs the following steps:
* <p>
* <ol>
* <li>Invokes {@link #writeImageMetadata(IIOMetadata, int, ImageWriteParam)}
* with the metadata obtained from the {@code image} argument.</li>
* <li>Delegates to the {@linkplain #main} writer.</li>
* </ol>
*/
@Override
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
writeImageMetadata(image.getMetadata(), imageIndex, param);
main.writeToSequence(image, unwrap(param));
imageIndex++;
}
/**
* Completes the writing of a sequence of images begun with {@link #prepareWriteSequence
* prepareWriteSequence}. The default implementation delegates to the {@linkplain #main}
* writer.
*/
@Override
public void endWriteSequence() throws IOException {
main.endWriteSequence();
}
/**
* Returns {@code true} if the writer allows pixels of the given image to be replaced
* using the {@code replacePixels} methods. The default implementation ensures that the
* {@linkplain #main} writer has been {@linkplain #initialize() initialized}, then
* delegates to that writer.
*/
@Override
public boolean canReplacePixels(int imageIndex) throws IOException {
ensureOutputInitialized();
return main.canReplacePixels(imageIndex);
}
/**
* Prepares the writer to handle a series of calls to the replacePixels methods.
* The default implementation ensures that the {@linkplain #main} writer has been
* {@linkplain #initialize() initialized}, then delegates to that writer.
*/
@Override
public void prepareReplacePixels(int imageIndex, Rectangle region) throws IOException {
ensureOutputInitialized();
main.prepareReplacePixels(imageIndex, region);
}
/**
* Replaces a portion of an image already present in the output with a portion of the
* given image. The default implementation delegates to the {@linkplain #main} writer.
*/
@Override
public void replacePixels(RenderedImage image, ImageWriteParam param) throws IOException {
main.replacePixels(image, unwrap(param));
}
/**
* Replaces a portion of an image already present in the output with a portion of the
* given raster. The default implementation delegates to the {@linkplain #main} writer.
*/
@Override
public void replacePixels(Raster raster, ImageWriteParam param) throws IOException {
main.replacePixels(raster, unwrap(param));
}
/**
* Terminates a sequence of calls to {@code replacePixels}. The default implementation
* delegates to the {@linkplain #main} writer.
*/
@Override
public void endReplacePixels() throws IOException {
main.endReplacePixels();
}
/**
* Returns the number of thumbnails suported by the format being written, or -1 if unknown.
* The default implementation delegates to the {@linkplain #main} writer. No output needs to
* be set for this method.
*/
@Override
public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, ImageWriteParam param,
IIOMetadata streamMetadata, IIOMetadata imageMetadata)
{
return main.getNumThumbnailsSupported(imageType, unwrap(param), streamMetadata, imageMetadata);
}
/**
* Returns the legal size ranges for thumbnail images as they will be encoded in the output
* file or stream. The default implementation delegates to the {@linkplain #main} writer.
* No output needs to be set for this method.
*/
@Override
public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType,
ImageWriteParam param, IIOMetadata streamMetadata, IIOMetadata imageMetadata)
{
return main.getPreferredThumbnailSizes(imageType, unwrap(param), streamMetadata, imageMetadata);
}
/**
* Returns the locales that may be used to localize warning listeners.
* The default implementation delegates to the {@linkplain #main} writer.
* No output 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} writer.
* No output 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} writer.
* No output 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 writer and to the {@linkplain #main} writer.
*/
@Override
public void addIIOWriteWarningListener(final IIOWriteWarningListener listener) {
super.addIIOWriteWarningListener(listener);
main .addIIOWriteWarningListener(listener);
}
/**
* Removes the given listener from the list of registered warning listeners.
*/
@Override
public void removeIIOWriteWarningListener(final IIOWriteWarningListener listener) {
super.removeIIOWriteWarningListener(listener);
main .removeIIOWriteWarningListener(listener);
}
/**
* Removes all currently registered warning listeners.
*/
@Override
public void removeAllIIOWriteWarningListeners() {
super.removeAllIIOWriteWarningListeners();
main .removeAllIIOWriteWarningListeners();
}
/**
* Adds the given listener to the list of registered progress listeners. This method
* adds the listener only to the {@linkplain #main} writer, not to this writer, in
* order to ensure that progress methods are invoked only once.
*/
@Override
public void addIIOWriteProgressListener(final IIOWriteProgressListener listener) {
main.addIIOWriteProgressListener(listener);
}
/**
* Removes the given listener from the list of registered progress listeners.
*/
@Override
public void removeIIOWriteProgressListener(final IIOWriteProgressListener listener) {
super.removeIIOWriteProgressListener(listener); // As a safety.
main .removeIIOWriteProgressListener(listener);
}
/**
* Removes all currently registered progress listeners.
*/
@Override
public void removeAllIIOWriteProgressListeners() {
super.removeAllIIOWriteProgressListeners(); // As a safety.
main .removeAllIIOWriteProgressListeners();
}
/**
* Requests that any current write operation be aborted. The default implementation delegates
* to both the {@linkplain #main} writer and to the super-class method.
*/
@Override
public void abort() {
super.abort();
main.abort();
}
/**
* Restores the {@code ImageWriter} to its initial state. The default implementation
* delegates to both the {@linkplain #main} writer 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} writer and to the super-class method.
*/
@Override
public void dispose() {
super.dispose();
main.dispose();
}
/**
* Closes the output stream created by {@link #createOutput(String)}. This method does nothing
* if the output used by the {@linkplain #main} writer is the one given by the user to the
* {@link #setOutput(Object) setOutput} method. Otherwise, if the output of the main writer
* is an instance of {@link ImageOutputStream} or {@link Closeable}, then it is closed.
* <p>
* This method is invoked automatically by {@link #setOutput(Object) setOutput(...)},
* {@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 mainOutput = main.getOutput();
main.setOutput(null);
if (mainOutput != null && mainOutput != output) {
IOUtilities.close(mainOutput);
}
}
/**
* Invokes the {@link #close()} method when this writer is garbage-collected.
* Note that this will actually close the stream only if it has been created
* by this writer, rather than supplied by the user.
*/
@Override
protected void finalize() throws Throwable {
closeSilently();
super.finalize();
}
/**
* Service provider interface (SPI) for {@link ImageWriterAdapter}s. The constructor of this
* class initializes the {@link ImageWriterSpi#outputTypes} field to types that can represent
* a filename, like {@link File} or {@link URL}, rather than the usual
* {@linkplain #STANDARD_OUTPUT_TYPE standard output 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 ImageWriterAdapter} wrapping the {@code "tiff"} image writer
* 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 #outputTypes} </td>
* <td> {@link String}, {@link File}, {@link URI}, {@link URL}<!--,
* {@link ImageOutputStream} TODO --> </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 ImageReaderAdapter.Spi
*
* @since 3.07
* @module
*/
public abstract static class Spi extends SpatialImageWriter.Spi {
/**
* List of legal input and output types for {@link ImageWriterAdapter}.
* The {@link ImageOutputStream} type is mandatory because this type is
* created by {@link javax.imageio} package and we have no way to filter
* {@code ImageWriter} by output stream.
*/
private static final Class<?>[] TYPES = ImageReaderAdapter.Spi.TYPES; //.clone();
// static {
//TODO int n = TYPES.length;
//GEOTK-231 TYPES[--n] = ImageOutputStream.class;
// TYPES[--n] = OutputStream.class;
// }
/**
* The provider of the writers to use for writing the pixel values.
* This is the provider specified at the construction time.
*/
protected final ImageWriterSpi main;
/**
* {@code true} if the {@link #main} provider accepts outputs of kind {@link ImageOutputStream}.
*/
final boolean acceptStream;
/**
* Creates an {@code ImageWriterAdapter.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 outputTypes} 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 writers to use for writing the pixel values.
*/
protected Spi(final ImageWriterSpi main) {
ensureNonNull("main", main);
this.main = main;
names = main.getFormatNames();
suffixes = main.getFileSuffixes();
MIMETypes = main.getMIMETypes();
outputTypes = TYPES;
supportsStandardStreamMetadataFormat = main.isStandardStreamMetadataFormatSupported();
supportsStandardImageMetadataFormat = main.isStandardImageMetadataFormatSupported();
nativeStreamMetadataFormatName = main.getNativeStreamMetadataFormatName();
nativeImageMetadataFormatName = main.getNativeImageMetadataFormatName();
extraStreamMetadataFormatNames = main.getExtraStreamMetadataFormatNames();
extraImageMetadataFormatNames = main.getExtraImageMetadataFormatNames();
acceptStream = Classes.isAssignableToAny(ImageOutputStream.class, main.getOutputTypes());
}
/**
* Creates a provider which will use the given format for writing 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 writing the pixel values.
* @throws IllegalArgumentException If no provider is found for the given format.
*/
protected Spi(final String format) {
this(Formats.getWriterByFormatName(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) {
ImageReaderAdapter.Spi.addFormatNameSuffix(names, MIMETypes, suffix);
}
/**
* Returns the output types accepted by the {@linkplain #main} provider which are also
* accepted by this provider, or {@code null} if none.
*/
final Class<?>[] getMainTypes() {
return ImageReaderAdapter.Spi.getMainTypes(outputTypes, main.getOutputTypes());
}
/**
* {@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 {@code true} if the format that this writer outputs preserves pixel data
* bit-accurately. The default implementation delegates to the {@linkplain #main}
* provider.
*/
@Override
public boolean isFormatLossless() {
return main.isFormatLossless();
}
/**
* Returns {@code true} if the writer implementation associated with this service provider
* is able to encode an image with the given layout. The default implementation delegates
* to the {@linkplain #main} provider.
*
* @param type The layout of the image to be written.
*/
@Override
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return main.canEncodeImage(type);
}
/**
* Returns {@code true} if the writer implementation associated with this service provider
* is able to encode the given image. The default implementation delegates to the
* {@linkplain #main} provider.
*
* @param image The image to be written.
*/
@Override
public boolean canEncodeImage(final RenderedImage image) {
return main.canEncodeImage(image);
}
/**
* Returns the kind of information that this wrapper will add or modify compared to the
* {@linkplain #main} writer. If this method returns an empty set, then there is no
* raison to use this adapter instead than the main writer.
* <p>
* The default implementation conservatively returns all of the {@link InformationType}
* enum values. Subclasses should return more accurate information when possible.
*
* @param type The layout of the image to be written.
* @return The set of information to be written or modified by this adapter.
*
* @since 3.20
*/
public Set<InformationType> getModifiedInformation(final ImageTypeSpecifier type) {
return ImageReaderAdapter.Spi.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#getImageWritersBySuffix(String)
* image writer 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 ImageWriterAdapter.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 writing TFW, PRJ or similar files is avoided.
*
* @param spi An image writer provider, or {@code null}.
* @return The wrapped image writer provider, or {@code null}.
*
* @since 3.14
*/
public static ImageWriterSpi unwrap(ImageWriterSpi spi) {
while (spi instanceof Spi) {
spi = ((Spi) spi).main;
}
return spi;
}
}
}