/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.image.io; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.spi.ImageReaderWriterSpi; import javax.imageio.stream.FileCacheImageOutputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; import org.geotools.resources.Classes; import org.geotools.util.Utilities; import com.sun.media.imageioimpl.common.PackageUtil; /** * Provides an alternative source of image input and output streams that uses optimized behavior. * <p> * Currently implemented optimizations: * <ul> * <li>wrap an OutputStream into a {@link MemoryCacheImageOutputStream} or a * {@link FileCacheImageOutputStream} based on a image size threshold</li> * </ul> * </p> * * @author Andrea Aime - GeoSolutions * @since 2.7.2 * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/library/coverage/src/main/java/org/geotools/image/io/ImageIOExt.java $ */ public class ImageIOExt { static Long filesystemThreshold = null; static File cacheDirectory = null; /** * Builds a {@link ImageOutputStream} writing to <code>destination</code>, based on logic that * involves the image size * * @param image * the image to be written on the destination (can be null) * @param destination * the destination * @return * @throws IOException */ public static ImageOutputStream createImageOutputStream(RenderedImage image, Object destination) throws IOException { // already what we need? if (destination instanceof ImageOutputStream) { return (ImageOutputStream) destination; } // generate the ImageOutputStream if (destination instanceof OutputStream && filesystemThreshold != null && image != null) { OutputStream stream = (OutputStream) destination; // if going to wrap a output stream and we have a threshold set long imageSize = computeImageSize(image); if (imageSize > filesystemThreshold) { File cacheDirectory = getCacheDirectory(); return new FileCacheImageOutputStream(stream, cacheDirectory); } else { return new MemoryCacheImageOutputStream(stream); } } else { return ImageIO.createImageOutputStream(destination); } } /** * Returns a {@link ImageOutputStream} suitable for writing on the specified <code>input</code> * * @param destination * @return * @throws IOException */ public static ImageInputStream createImageInputStream(Object input) throws IOException { return ImageIO.createImageInputStream(input); } /** * Returns the cache directory used by ImageIOExt, either the manually configured one, or the * result of calling {@link ImageIO#getCacheDirectory()} */ public static File getCacheDirectory() { File cacheDir = cacheDirectory; if (cacheDir == null) { cacheDir = ImageIO.getCacheDirectory(); } return cacheDir; } /** * Sets the directory where cache files are to be created. If set to null (the default value) * {@link ImageIO#getCacheDirectory()} will be used as the value * * @param cacheDirectory * a <code>File</code> specifying a directory. */ public static void setCacheDirectory(File cache) { ImageIOExt.cacheDirectory = cache; } /** * The threshold at which the class will flip from {@link MemoryCacheImageOutputStream} to * {@link FileCacheImageOutputStream}. If the in memory, uncompressed image size is lower than * the threshold a {@link MemoryCacheImageOutputStream} will be returned, otherwise a * {@link FileCacheImageOutputStream} will be used instead * * @return */ public static Long getFilesystemThreshold() { return filesystemThreshold; } /** * Sets the memory/file usage threshold (or null to have the code fall back on ImageIO behavior) * * @param filesystemThreshold * @see #getFilesystemThreshold() */ public static void setFilesystemThreshold(Long filesystemThreshold) { ImageIOExt.filesystemThreshold = filesystemThreshold; } /** * Allows or disallows native acceleration for the specified image format. By default, the * image I/O extension for JAI provides native acceleration for PNG and JPEG. Unfortunatly, * those native codec has bug in their 1.0 version. Invoking this method will force the use * of standard codec provided in J2SE 1.4. * <p> * <strong>Implementation note:</strong> the current implementation assume that JAI codec * class name start with "CLib". It work for Sun's 1.0 implementation, but may change in * future versions. If this method doesn't recognize the class name, it does nothing. * * @param format The format name (e.g. "png"). * @param category {@code ImageReaderSpi.class} to set the reader, or * {@code ImageWriterSpi.class} to set the writer. * @param allowed {@code false} to disallow native acceleration. */ public static synchronized <T extends ImageReaderWriterSpi> void allowNativeCodec( final String format, final Class<T> category, final boolean allowed) { T standard = null; T codeclib = null; final IIORegistry registry = IIORegistry.getDefaultInstance(); for (final Iterator<T> it = registry.getServiceProviders(category, false); it.hasNext();) { final T provider = it.next(); final String[] formats = provider.getFormatNames(); for (int i=0; i<formats.length; i++) { if (formats[i].equalsIgnoreCase(format)) { if (Classes.getShortClassName(provider).startsWith("CLib")) { codeclib = provider; } else { standard = provider; } break; } } } if (standard!=null && codeclib!=null) { if (allowed) { registry.setOrdering(category, codeclib, standard); } else { registry.setOrdering(category, standard, codeclib); } } } /** * Get a proper {@link ImageInputStreamSpi} instance for the provided {@link Object} input without * trying to create an {@link ImageInputStream}. * * @see #getImageInputStreamSPI(Object, boolean) */ public final static ImageInputStreamSpi getImageInputStreamSPI(final Object input) { return getImageInputStreamSPI(input, true); } /** * Get a proper {@link ImageInputStreamSpi} instance for the provided {@link Object} input. * * @param input the input object for which we need to find a proper {@link ImageInputStreamSpi} instance * @param streamCreationCheck if <code>true</code>, when a proper {@link ImageInputStreamSpi} have been found * for the provided input, use it to try creating an {@link ImageInputStream} on top of the input. * * @return an {@link ImageInputStreamSpi} instance. */ public final static ImageInputStreamSpi getImageInputStreamSPI(final Object input, final boolean streamCreationCheck) { Iterator<ImageInputStreamSpi> iter; // Ensure category is present try { iter = IIORegistry.getDefaultInstance().getServiceProviders(ImageInputStreamSpi.class, true); } catch (IllegalArgumentException e) { return null; } boolean usecache = ImageIO.getUseCache(); ImageInputStreamSpi spi = null; while (iter.hasNext()) { spi = (ImageInputStreamSpi) iter.next(); if (spi.getInputClass().isInstance(input)) { // Stream creation check if (streamCreationCheck){ ImageInputStream stream = null; try { stream = spi.createInputStreamInstance(input, usecache, ImageIO.getCacheDirectory()); break; } catch (IOException e) { return null; } finally { //Make sure to close the created stream if (stream != null){ try { stream.close(); } catch (Throwable t){ //eat exception } } } } else { break; } } } return spi; } /** * Tells me whether or not the native libraries for JAI/ImageIO are active or not. * * @return <code>false</code> in case the JAI/ImageIO native libs are not in the path, <code>true</code> otherwise. */ public static boolean isCLibAvailable() { return PackageUtil.isCodecLibAvailable(); } /** * Look for an {@link ImageReader} instance that is able to read the * provided {@link ImageInputStream}, which must be non null. * * <p> * In case no reader is found, <code>null</code> is returned. * * @param inStream * an instance of {@link ImageInputStream} for which we need to * find a suitable {@link ImageReader}. * @return a suitable instance of {@link ImageReader} or <code>null</code> * if one cannot be found. */ public static ImageReader getImageioReader(final ImageInputStream inStream) { Utilities.ensureNonNull("inStream", inStream); // get a reader inStream.mark(); final Iterator<ImageReader> readersIt = ImageIO .getImageReaders(inStream); if (!readersIt.hasNext()) { return null; } return readersIt.next(); } /** * Computes the image size based on the SampleModel and image dimension * * @param image * @return */ static long computeImageSize(RenderedImage image) { long bits = 0; final int bands = image.getSampleModel().getNumBands(); for (int i = 0; i < bands; i++) { bits += image.getSampleModel().getSampleSize(i); } return (long) Math.ceil(bits / 8) * image.getWidth() * image.getHeight(); } }