/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-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.jai;
import java.util.List;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import java.awt.image.renderable.RenderedImageFactory;
import javax.media.jai.JAI;
import javax.media.jai.OperationRegistry;
import javax.media.jai.registry.RIFRegistry;
import javax.media.jai.registry.RenderedRegistryMode;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ImageReaderWriterSpi;
import org.geotoolkit.lang.Static;
import org.geotoolkit.lang.Workaround;
import org.geotoolkit.lang.Configuration;
import org.geotoolkit.resources.Loggings;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.internal.image.jai.*;
import static org.geotoolkit.image.internal.Setup.PRODUCT_NAME;
/**
* A set of static methods for managing JAI's {@linkplain OperationRegistry operation registry}.
* Also provides convenience methods for setting the preferred order between standard and JAI
* {@link javax.imageio.ImageReader}/{@link javax.imageio.ImageWriter}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.00
*
* @since 2.2
* @module
*/
public final class Registry extends Static {
/**
* Do not allows instantiation of this class.
*/
private Registry() {
}
/**
* Unconditionally registers all JAI operations provided in the {@link org.geotoolkit.image.jai}
* package. This method usually don't need to be invoked, since JAI should parse automatically
* the {@code META-INF/registryFile.jai} file at startup time. However, this default mechanism
* may fail when the Geotk JAR file is unreachable from the JAI class loader, in which case
* the {@link org.geotoolkit.coverage.processing} package will invoke this method as a fallback.
* <p>
* Note to module maintainer: if this method is updated, remember to update the
* {@code META-INF/registryFile.jai} file accordingly.
*
* @param registry The operation registry to register with.
* @return {@code true} if all registrations have been successful.
*/
@Configuration
public static boolean registerGeotoolkitServices(final OperationRegistry registry) {
LogRecord record;
String op = PRODUCT_NAME;
try {
op = Combine.OPERATION_NAME;
registry.registerDescriptor(new CombineDescriptor());
RIFRegistry.register(registry, op, PRODUCT_NAME, new CombineCRIF());
op = Hysteresis.OPERATION_NAME;
registry.registerDescriptor(new HysteresisDescriptor());
RIFRegistry.register(registry, op, PRODUCT_NAME, new HysteresisCRIF());
op = NodataFilter.OPERATION_NAME;
registry.registerDescriptor(new NodataFilterDescriptor());
RIFRegistry.register(registry, op, PRODUCT_NAME, new NodataFilterCRIF());
op = SilhouetteMask.OPERATION_NAME;
registry.registerDescriptor(new SilhouetteMaskDescriptor());
RIFRegistry.register(registry, op, PRODUCT_NAME, new SilhouetteMaskCRIF());
record = Loggings.format(Level.CONFIG, Loggings.Keys.RegisteredJaiOperations);
op = null;
} catch (IllegalArgumentException exception) {
/*
* Logs a message with the WARNING level, because DefaultProcessing class initialization
* is likely to fails (since it tries to load operations declared in META-INF/services,
* and some of them depend on JAI operations).
*/
record = Loggings.getResources(null).getLogRecord(Level.WARNING,
Loggings.Keys.CantRegisterJaiOperation_1, op);
record.setThrown(exception);
}
log("registerGeotoolkitServices", record);
return op == null;
}
/**
* Allows or disallows native acceleration for the specified operation on the given JAI instance.
* By default, JAI uses hardware accelerated methods when available. For example, it makes use of
* MMX instructions on Intel processors. Unfortunately, some native methods crash the Java Virtual
* Machine under some circumstances. For example on JAI 1.1.2, the {@code "Affine"} operation on
* an image with float data type, bilinear interpolation and an {@link javax.media.jai.ImageLayout}
* rendering hint cause an exception in medialib native code. Disabling the native acceleration
* (i.e using the pure Java version) is a convenient workaround until Sun fix the bug.
*
* {@note The current implementation assumes that factories for native implementations are
* declared in the <code>com.sun.media.jai.mlib</code> package, while factories for pure Java
* implementations are declared in the <code>com.sun.media.jai.opimage</code> package. It works
* for Sun's 1.1.2 implementation, but may change in future versions. If this method doesn't
* recognize the package, it does nothing.}
*
* @param operation The operation name (e.g. {@code "Affine"}).
* @param allowed {@code false} to disallow native acceleration.
* @param jai The instance of {@link JAI} we are going to work on. This argument can be
* omitted for the {@linkplain JAI#getDefaultInstance default JAI instance}.
*
* @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4906854">JAI bug report 4906854</a>
*
* @since 2.5
*/
@Configuration
@Workaround(library="JAI", version="1.1.2")
public static synchronized void setNativeAccelerationAllowed(final String operation,
final boolean allowed, final JAI jai)
{
final String product = "com.sun.media.jai";
final OperationRegistry registry = jai.getOperationRegistry();
@SuppressWarnings("unchecked")
final List<RenderedImageFactory> factories = registry.getOrderedFactoryList(
RenderedRegistryMode.MODE_NAME, operation, product);
if (factories != null) {
RenderedImageFactory javaFactory = null;
RenderedImageFactory nativeFactory = null;
Boolean currentState = null;
for (final RenderedImageFactory factory : factories) {
final String pack = factory.getClass().getPackage().getName();
if (pack.equals("com.sun.media.jai.mlib")) {
nativeFactory = factory;
if (javaFactory != null) {
currentState = Boolean.FALSE;
}
}
if (pack.equals("com.sun.media.jai.opimage")) {
javaFactory = factory;
if (nativeFactory != null) {
currentState = Boolean.TRUE;
}
}
}
if (currentState != null && currentState.booleanValue() != allowed) {
RIFRegistry.unsetPreference(registry, operation, product,
allowed ? javaFactory : nativeFactory,
allowed ? nativeFactory : javaFactory);
RIFRegistry.setPreference(registry, operation, product,
allowed ? nativeFactory : javaFactory,
allowed ? javaFactory : nativeFactory);
final LogRecord record = Loggings.format(Level.CONFIG,
Loggings.Keys.NativeAccelerationState_2,
operation, Integer.valueOf(allowed ? 1 : 0));
log("setNativeAccelerationAllowed", record);
}
}
}
/**
* Allows or disallows native acceleration for the specified operation on the
* {@linkplain JAI#getDefaultInstance default JAI instance}. This method is
* a shortcut for <code>{@linkplain #setNativeAccelerationAllowed(String,boolean,JAI)
* setNativeAccelerationAllowed}(operation, allowed, JAI.getDefaultInstance())</code>.
*
* @param operation The operation name (e.g. {@code "Affine"}).
* @param allowed {@code false} to disallow native acceleration.
*
* @see #setNativeAccelerationAllowed(String, boolean, JAI)
*/
@Configuration
@Workaround(library="JAI", version="1.1.2")
public static void setNativeAccelerationAllowed(final String operation, final boolean allowed) {
setNativeAccelerationAllowed(operation, allowed, JAI.getDefaultInstance());
}
/**
* 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. Unfortunately,
* those native codecs have bug in their 1.0 version. Invoking this method will force the use
* of standard codecs provided in J2SE 1.4.
*
* {@note The current implementation assumes that JAI codec class name start with
* <code>"com.sun.media"</code> package name. It works for JAI Image I/O 1.1
* implementation, but may change in future versions. If this method doesn't
* recognize the class name, then it does nothing.}
*
* @param <T> The category ({@code ImageReaderSpi} or {@code ImageWriterSpi}).
* @param format The format name (e.g. {@code "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.
*
* @since 3.00
*/
@Configuration
public static synchronized <T extends ImageReaderWriterSpi> void setNativeCodecAllowed(
final String format, final Class<T> category, final boolean allowed)
{
T standard = null;
T codeclib = null;
final ServiceRegistry 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)) {
/*
* NOTE: The following method uses the same rule for identifying JAI codecs.
* If we change the way to identify those codecs here, we should do the
* same for the other method.
*
* org.geotoolkit.internal.image.io.Formats.getReaderByFormatName(String)
*/
final String classname = provider.getClass().getName();
if (classname.startsWith("com.sun.media.")) {
codeclib = provider;
break;
}
if (classname.startsWith("com.sun.imageio.")) {
standard = provider;
break;
}
}
}
}
if (standard != null && codeclib != null) {
final boolean changed;
if (allowed) {
changed = registry.setOrdering(category, codeclib, standard);
} else {
changed = registry.setOrdering(category, standard, codeclib);
}
if (changed) {
final LogRecord record = Loggings.format(Level.CONFIG,
Loggings.Keys.NativeCodecState_3,
format, Integer.valueOf(allowed ? 1 : 0),
Integer.valueOf(ImageWriterSpi.class.isAssignableFrom(category) ? 1 : 0));
log("setNativeCodecAllowed", record);
}
}
}
/**
* Sets the default preferred order for JAI and Java standard codec. This method invokes
* {@link #setNativeCodecAllowed setNativeCodecAllowed} for some well-known formats.
* <p>
* <strong>TIP:</strong> Experience suggests that on some platforms, new codecs appear
* magically after an AWT window has been created (e.g. "Standard TIFF image reader").
* If this method is to be invoked for a graphical application, it is recommended to
* invoke this method <strong>after</strong> the {@linkplain java.awt.Window} has been
* created but before it is made visible.
*
* @see org.geotoolkit.image.io
* @see org.geotoolkit.lang.Setup
*
* @since 3.00
*/
@Configuration
public static synchronized void setDefaultCodecPreferences() {
/*
* NOTE: If the rules below are modified, then the rules in
* the following method shall be modified accordingly:
*
* org.geotoolkit.internal.image.io.Formats.getReaderByFormatName(String)
*/
setNativeCodecAllowed("GIF", ImageReaderSpi.class, false);
setNativeCodecAllowed("GIF", ImageWriterSpi.class, false);
setNativeCodecAllowed("PNG", ImageReaderSpi.class, false);
setNativeCodecAllowed("PNG", ImageWriterSpi.class, false);
setNativeCodecAllowed("BMP", ImageReaderSpi.class, false);
setNativeCodecAllowed("BMP", ImageWriterSpi.class, false);
setNativeCodecAllowed("WBMP", ImageReaderSpi.class, false);
setNativeCodecAllowed("WBMP", ImageWriterSpi.class, false);
setNativeCodecAllowed("JPEG", ImageReaderSpi.class, false);
setNativeCodecAllowed("JPEG", ImageWriterSpi.class, false);
setNativeCodecAllowed("TIFF", ImageReaderSpi.class, true);
setNativeCodecAllowed("TIFF", ImageWriterSpi.class, true);
}
/**
* Logs the specified record.
*/
private static void log(final String method, final LogRecord record) {
record.setSourceClassName(Registry.class.getName());
record.setSourceMethodName(method);
final Logger logger = Logging.getLogger("org.geotoolkit.image.jai");
record.setLoggerName(logger.getName());
logger.log(record);
}
}