/*
* ImageI/O-Ext - OpenSource Java Image translation Library
* http://www.geo-solutions.it/
* http://java.net/projects/imageio-ext/
* (C) 2008, GeoSolutions
*
* 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;
* either version 3 of the License, or (at your option) any later version.
*
* 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 it.geosolutions.util;
import it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageReadParam;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageWriteParam;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageReaderWriterSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
import kdu_jni.KduException;
import kdu_jni.Kdu_global;
import kdu_jni.Kdu_message_formatter;
/**
* Class with utility methods.
*
* @author Daniele Romagnoli, GeoSolutions
* @author Simone Giannecchini, GeoSolutions
*
*/
public class KakaduUtilities {
public static final double DOUBLE_TOLERANCE = 1E-6;
private static final Logger LOGGER = Logger
.getLogger("it.geosolutions.util");
/** is Kakadu available on this machine?. */
private static boolean available;
private static boolean init = false;
public static final double BIT_TO_BYTE_FACTOR = 0.125;
private KakaduUtilities() {
}
/**
* Find the optimal subsampling factor, given a specified subsampling factor
* as input parameter, as well as the number of DWT levels which may be
* discarded. Let iSS be the input subsampling factor and let L be the
* number of available source DWT levels.
*
* The optimal subsampling factor is oSS = 2^level, where: level is not
* greater than L, and oSS is not greater than iSS.
*
* @param sourceDWTLevels
* the number of DWT levels in the source image
* @param newSubSamplingFactor
* the specified subsampling factor for which we need to find
* an optimal subsampling factor
* @return an int array containing the optimalSubSamplingFactor as first
* element, and the number of levels to be discarded as second
* element
*/
public static int[] findOptimalResolutionInfo(final int sourceDWTLevels,
final int newSubSamplingFactor) {
// Within a loop, using a local variable instead of an instance field
// is preferred to improve performances.
final int levels = sourceDWTLevels;
int optimalSubSamplingFactor = 1;
// finding the available subsampling factors from the number of
// resolution levels
int discardLevels = 0;
for (int level = 0; level < levels + 1; level++) {
// double the subSamplingFactor until it is lower than the
// input subSamplingFactor
if (optimalSubSamplingFactor < newSubSamplingFactor)
optimalSubSamplingFactor = 1 << level;
// if the calculated subSamplingFactor is greater than the input
// subSamplingFactor, we need to step back by halving it.
else if (optimalSubSamplingFactor > newSubSamplingFactor) {
optimalSubSamplingFactor = optimalSubSamplingFactor >> 1;
break;
} else if (optimalSubSamplingFactor == newSubSamplingFactor) {
break;
}
}
int decreasingSSF = optimalSubSamplingFactor;
for (discardLevels = 0; discardLevels < levels && decreasingSSF > 1; discardLevels++) {
decreasingSSF = decreasingSSF >> 1;
}
return new int[] { optimalSubSamplingFactor, discardLevels };
}
/**
* Initializing kakadu messages as stated in the KduRender.java example
*/
public static void initializeKakaduMessagesManagement() {
try {
// ////
// Customize error and warning services
// ////
// Non-throwing message printer
Kdu_sysout_message sysout = new Kdu_sysout_message(false);
// Exception-throwing message printer
Kdu_sysout_message syserr = new Kdu_sysout_message(true);
// /////
// Initialize formatted message printer
// ////
// Non-throwing printer
Kdu_message_formatter pretty_sysout = new Kdu_message_formatter(
sysout);
// Throwing printer
Kdu_message_formatter pretty_syserr = new Kdu_message_formatter(
syserr);
Kdu_global.Kdu_customize_warnings(pretty_sysout);
Kdu_global.Kdu_customize_errors(pretty_syserr);
} catch (KduException e) {
throw new RuntimeException(
"Error caused by a Kakadu exception during creation of key objects! ",
e);
}
}
public static List<ImageReaderWriterSpi> getJDKImageReaderWriterSPI(
ServiceRegistry registry, String formatName, boolean isReader) {
if (registry == null || !(registry instanceof IIORegistry))
throw new IllegalArgumentException("Illegal registry provided");
IIORegistry iioRegistry = (IIORegistry) registry;
Class<? extends ImageReaderWriterSpi> spiClass;
if (isReader)
spiClass = ImageReaderSpi.class;
else
spiClass = ImageWriterSpi.class;
final Iterator<? extends ImageReaderWriterSpi> iter = iioRegistry
.getServiceProviders(spiClass, true); // useOrdering
final ArrayList<ImageReaderWriterSpi> list = new ArrayList<ImageReaderWriterSpi>();
while (iter.hasNext()) {
final ImageReaderWriterSpi provider = (ImageReaderWriterSpi) iter
.next();
// Get the formatNames supported by this Spi
final String[] formatNames = provider.getFormatNames();
for (int i = 0; i < formatNames.length; i++) {
if (formatNames[i].equalsIgnoreCase(formatName)) {
// Must be a JDK provided ImageReader/ImageWriter
list.add(provider);
break;
}
}
}
return list;
}
/**
* Transforms the provided <code>BufferedImage</code> and returns a new
* one in compliance with the required destination bimage properties,
* adopting the specified interpolation algorithm
*
* @param cm
* the <code>ColorModel</code> to be used in the warping
* @param bi
* the original BufferedImage
* @param destinationRegion.width
* the required destination image width
* @param destinationRegion.height
* the required destination image height
* @param interpolationType
* the specified interpolation type
* @return a <code>BufferedImage</code> having size =
* destinationRegion.width*destinationRegion.height which is the
* result of the WarpAffineresu.
*/
public static BufferedImage subsampleImage(ColorModel cm, BufferedImage bi,
final int destinationWidth, final int destinationHeight,
final int interpolationType) {
final WritableRaster raster = cm.createCompatibleWritableRaster(
destinationWidth, destinationHeight);
final BufferedImage finalImage = new BufferedImage(cm, raster, false, null);
final Graphics2D gc2D = finalImage.createGraphics();
gc2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
gc2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
gc2D.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
gc2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
interpolationType == JP2KKakaduImageReadParam.INTERPOLATION_NEAREST ? RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
: RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gc2D.drawImage(bi, 0, 0, destinationWidth, destinationHeight, 0, 0, bi.getWidth(), bi.getHeight(), null);
gc2D.dispose();
bi.flush();
bi = null;
return finalImage;
}
/**
* Returns <code>true</code> if the Kakadu native library has been loaded.
* <code>false</code> otherwise.
*
* @return <code>true</code> only if the Kakadu native library has been
* loaded.
*/
public static boolean isKakaduAvailable() {
loadKakadu();
return available;
}
/**
* Forces loading of Kakadu libs.
*/
public synchronized static void loadKakadu() {
if (init == false)
init = true;
else
return;
try {
System.loadLibrary("kdu_jni");
available = true;
} catch (UnsatisfiedLinkError e) {
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.warning("Failed to load the Kakadu native libs. This is not a problem unless you need to use the Kakadu plugin: it won't be enabled. " + e.toString());
}
available = false;
}
}
/**
* Compute the source region and destination dimensions taking any parameter
* settings into account.
*/
public static void computeRegions(final Rectangle sourceBounds,
Dimension destSize, ImageWriteParam param) {
int periodX = 1;
int periodY = 1;
if (param != null) {
final int[] sourceBands = param.getSourceBands();
if (sourceBands != null
&& (sourceBands.length != 1 || sourceBands[0] != 0)) {
throw new IllegalArgumentException("Cannot sub-band image!");
// TODO: Actually, sourceBands is ignored!!
}
// ////////////////////////////////////////////////////////////////
//
// Get source region and subsampling settings
//
// ////////////////////////////////////////////////////////////////
Rectangle sourceRegion = param.getSourceRegion();
if (sourceRegion != null) {
// Clip to actual image bounds
sourceRegion = sourceRegion.intersection(sourceBounds);
sourceBounds.setBounds(sourceRegion);
}
// Get subsampling factors
periodX = param.getSourceXSubsampling();
periodY = param.getSourceYSubsampling();
// Adjust for subsampling offsets
int gridX = param.getSubsamplingXOffset();
int gridY = param.getSubsamplingYOffset();
sourceBounds.x += gridX;
sourceBounds.y += gridY;
sourceBounds.width -= gridX;
sourceBounds.height -= gridY;
}
// ////////////////////////////////////////////////////////////////////
//
// Compute output dimensions
//
// ////////////////////////////////////////////////////////////////////
destSize.setSize((sourceBounds.width + periodX - 1) / periodX,
(sourceBounds.height + periodY - 1) / periodY);
if (destSize.width <= 0 || destSize.height <= 0) {
throw new IllegalArgumentException("Empty source region!");
}
}
public static boolean notEqual(double value, double reference) {
return (Math.abs(value - reference) > KakaduUtilities.DOUBLE_TOLERANCE);
}
}