/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 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.utils.imageoverviews;
import it.geosolutions.imageio.plugins.tiff.BaselineTIFFTagSet;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageMetadata;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageWriter;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageWriterSpi;
import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.BorderExtender;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationBicubic;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.RenderedOp;
import javax.media.jai.TileCache;
import org.apache.commons.cli2.Option;
import org.apache.commons.cli2.validation.InvalidArgumentException;
import org.apache.commons.cli2.validation.Validator;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geotools.image.io.ImageIOExt;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.utils.CoverageToolsConstants;
import org.geotools.utils.WriteProgressListenerAdapter;
import org.geotools.utils.progress.BaseArgumentsManager;
import org.geotools.utils.progress.ExceptionEvent;
import org.geotools.utils.progress.ProcessingEvent;
import org.geotools.utils.progress.ProcessingEventListener;
/**
* <pre>
* Example of usage:
* <code>
* OverviewsEmbedder -s "/usr/home/tmp" -w *.tiff -t "512,512" -f 32 -n 8 -a nn -c 512
* </code>
* <pre>
*
* <p>
* HINT: Take more memory as the 64Mb default by using the following Java Options<BR/>
* <code>
* -Xmx1024M - Xms512M
* </code>
* </p>
* @author Simone Giannecchini (GeoSolutions)
* @author Alessio Fabiani (GeoSolutions)
* @since 2.3.x
* @version 0.4
*
*
*
* @source $URL$
*/
public class OverviewsEmbedder extends BaseArgumentsManager implements Runnable, ProcessingEventListener {
public static enum SubsampleAlgorithm{
Nearest, Bilinear, Bicubic, Average, Filtered;
// public abstract RenderedImage scale();
}
/**
*
* @author Simone Giannecchini
* @since 2.3.x
*
*/
private class OverviewsEmbedderWriteProgressListener extends
WriteProgressListenerAdapter {
/*
* (non-Javadoc)
*
* @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageComplete(javax.imageio.ImageWriter)
*/
public void imageComplete(ImageWriter source) {
OverviewsEmbedder.this.fireEvent(new StringBuilder(
"Started with writing out overview number ").append(
overviewInProcess + 1.0).toString(),
(overviewInProcess + 1 / numSteps) * 100.0);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageProgress(javax.imageio.ImageWriter,
* float)
*/
public void imageProgress(ImageWriter source, float percentageDone) {
OverviewsEmbedder.this.fireEvent(new StringBuilder(
"Writing out overview ").append(overviewInProcess + 1)
.toString(), (overviewInProcess / numSteps + percentageDone
/ (100 * numSteps)) * 100.0);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageStarted(javax.imageio.ImageWriter,
* int)
*/
public void imageStarted(ImageWriter source, int imageIndex) {
OverviewsEmbedder.this.fireEvent(new StringBuilder(
"Completed writing out overview number ").append(
overviewInProcess + 1).toString(), (overviewInProcess)
/ numSteps * 100.0);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.pyramids.DefaultWriteProgressListener#warningOccurred(javax.imageio.ImageWriter,
* int, java.lang.String)
*/
public void warningOccurred(ImageWriter source, int imageIndex,
String warning) {
OverviewsEmbedder.this.fireEvent(new StringBuilder(
"Warning at overview ").append((overviewInProcess + 1))
.toString(), 0);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.pyramids.DefaultWriteProgressListener#writeAborted(javax.imageio.ImageWriter)
*/
public void writeAborted(ImageWriter source) {
OverviewsEmbedder.this.fireEvent(new StringBuilder(
"Aborted writing process.").toString(), 100.0);
}
}
/**
* The default listener for checking the progress of the writing process.
*/
private final OverviewsEmbedderWriteProgressListener writeProgressListener = new OverviewsEmbedderWriteProgressListener();
private final static String NAME = "OverviewsEmbedder";
/** Program Version */
private final static String VERSION = "0.3";
/** Commons-cli option for the input location. */
private Option locationOpt;
/** Commons-cli option for the tile dimension. */
private Option tileDimOpt;
/** Commons-cli option for the scale algorithm. */
private Option scaleAlgorithmOpt;
/** Commons-cli option for the wild card to use. */
private Option wildcardOpt;
/** Commons-cli option for the tile numbe of subsample step to use. */
private Option numStepsOpt;
/** Commons-cli option for the scale factor to use. */
private Option scaleFactorOpt;
private Option compressionRatioOpt;
private Option compressionTypeOpt;
/** Tile width. */
private int tileW = -1;
/** Tile height. */
private int tileH = -1;
/** Scale algorithm. */
private String scaleAlgorithm;
/** Logger for this class. */
private final static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(OverviewsEmbedder.class.toString());
/** Default border extender. */
private BorderExtender borderExtender = CoverageToolsConstants.DEFAULT_BORDER_EXTENDER;
/** Downsampling step. */
private int downsampleStep;
/** Low pass filter. */
private float[] lowPassFilter = CoverageToolsConstants.DEFAULT_KERNEL_GAUSSIAN;
/**
* The source path. It could point to a single file or to a directory when
* we want to embed overwies into a set of files having a certain name.
*/
private String sourcePath;
private String compressionScheme = null;
private double compressionRatio = CoverageToolsConstants.DEFAULT_COMPRESSION_RATIO;
private int numSteps;
private String wildcardString = "*.*";
private int fileBeingProcessed;
private int overviewInProcess;
/**
* Simple constructor for a pyramid generator. Use the input string in order
* to read an image.
*
*
*/
@SuppressWarnings("unchecked")
public OverviewsEmbedder() {
super(NAME, VERSION);
// /////////////////////////////////////////////////////////////////////
// Options for the command line
// /////////////////////////////////////////////////////////////////////
locationOpt = optionBuilder
.withShortName("s")
.withLongName("source")
.withArgument(
argumentBuilder.withName("source").withMinimum(1)
.withMaximum(1).withValidator(new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException("Source can be a single file or directory ");
final File source = new File(
(String) args.get(0));
if (!source.exists())
throw new InvalidArgumentException(
new StringBuilder(
"The provided source is invalid! ")
.toString());
}
}).create()).withDescription(
"path where files are located").withRequired(true)
.create();
tileDimOpt = optionBuilder.withShortName("t").withLongName(
"tiled_dimension").withArgument(
argumentBuilder.withName("t").withMinimum(0).withMaximum(1)
.create()).withDescription(
"tile dimensions as a couple width,height in pixels")
.withRequired(false).create();
scaleFactorOpt = optionBuilder
.withShortName("f")
.withLongName("scale_factor")
.withArgument(
argumentBuilder.withName("f").withMinimum(1)
.withMaximum(1).withValidator(new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException(
"Only one scale factor at a time can be chosen");
int factor = Integer
.parseInt((String) args.get(0));
if (factor <= 0)
throw new InvalidArgumentException(
new StringBuilder(
"The provided scale factor is negative! ")
.toString());
if (factor == 1) {
LOGGER
.warning("The scale factor is 1, program will exit!");
System.exit(0);
}
}
}).create()).withDescription(
"integer scale factor")
.withRequired(true).create();
wildcardOpt = optionBuilder.withShortName("w").withLongName(
"wildcardOpt").withArgument(
argumentBuilder.withName("wildcardOpt").withMinimum(0)
.withMaximum(1).create()).withDescription(
"wildcardOpt to use for selecting files").withRequired(false)
.create();
numStepsOpt = optionBuilder.withShortName("n")
.withLongName("num_steps").withArgument(
argumentBuilder.withName("n").withMinimum(1)
.withMaximum(1).withValidator(new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException(
"Only one number of step at a time can be chosen");
int steps = Integer
.parseInt((String) args.get(0));
if (steps <= 0)
throw new InvalidArgumentException(
new StringBuilder(
"The provided number of step is negative! ")
.toString());
}
}).create()).withDescription(
"integer scale factor").withRequired(true).create();
scaleAlgorithmOpt = optionBuilder
.withShortName("a")
.withLongName("scaling_algorithm")
.withArgument(
argumentBuilder.withName("a").withMinimum(0)
.withMaximum(1).withValidator(new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException(
"Only one scaling algorithm at a time can be chosen");
final SubsampleAlgorithm algorithm=SubsampleAlgorithm.valueOf((String)args.get(0));
if (algorithm!=null)
throw new InvalidArgumentException(
new StringBuilder(
"The scaling algorithm ")
.append(args.get(0))
.append(
" is not supported")
.toString());
}
}).create())
.withDescription(
"name of the scaling algorithm, eeither one of average (a), filtered (f), bilinear (bil), nearest neigbhor (nn)")
.withRequired(false).create();
compressionTypeOpt = optionBuilder
.withShortName("z")
.withLongName("compressionType")
.withDescription("compression type.")
.withArgument(
argumentBuilder.withName("compressionType")
.withMinimum(0).withMaximum(1).withValidator(
new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException(
"Only one scaling algorithm at a time can be chosen");
}
}).create()).withRequired(false)
.create();
compressionRatioOpt = optionBuilder
.withShortName("r")
.withLongName("compressionRatio")
.withDescription("compression ratio.")
.withArgument(
argumentBuilder.withName("compressionRatio")
.withMinimum(0).withMaximum(1).withValidator(
new Validator() {
public void validate(List args)
throws InvalidArgumentException {
final int size = args.size();
if (size > 1)
throw new InvalidArgumentException(
"Only one scaling algorithm at a time can be chosen");
final String val = (String) args
.get(0);
final double value = Double
.parseDouble(val);
if (value <= 0 || value > 1)
throw new InvalidArgumentException(
"Invalid compressio ratio");
}
}).create()).withRequired(false)
.create();
addOption(locationOpt);
addOption(tileDimOpt);
addOption(scaleFactorOpt);
addOption(scaleAlgorithmOpt);
addOption(numStepsOpt);
addOption(wildcardOpt);
addOption(compressionTypeOpt);
addOption(compressionRatioOpt);
// /////////////////////////////////////////////////////////////////////
//
// Help Formatter
//
// /////////////////////////////////////////////////////////////////////
finishInitialization();
}
/**
* This method retiles the original image using a specified tile width and
* height.
*
* @param Original
* image to be tiled or retiled.
* @param tileWidth
* Tile width.
* @param tileHeight
* Tile height.
* @param tileGrdiOffseX
* @param tileGrdiOffseY
* @param interpolation
* Interpolation method used.
*
* @return RenderedOp containing the chain to obtain the tiled image.
*/
private static ImageLayout createTiledLayout(
final int tileWidth,
final int tileHeight,
final int tileGrdiOffseX,
final int tileGrdiOffseY) {
// //
//
// creating a new layout for this image
// using tiling
//
// //
ImageLayout layout = new ImageLayout();
// //
//
// changing parameters related to the tiling
//
//
// //
layout.setTileGridXOffset(tileGrdiOffseX);
layout.setTileGridYOffset(tileGrdiOffseY);
layout.setValid(ImageLayout.TILE_GRID_X_OFFSET_MASK);
layout.setValid(ImageLayout.TILE_GRID_Y_OFFSET_MASK);
layout.setTileWidth(tileWidth);
layout.setTileHeight(tileHeight);
layout.setValid(ImageLayout.TILE_HEIGHT_MASK);
layout.setValid(ImageLayout.TILE_WIDTH_MASK);
return layout;
}
/**
* This methods built up a RenderedOp for subsampling an image in order to
* create various previes. I wanted to use the filtered subsample but It was
* giving me problems in the native libraries therefore I am doing a two
* steps downsampling:
*
* Step 1: low pass filtering.
*
* Step 2: Subsampling.
*
* @param src
* Image to subsample.
* @param scaleTC
* @param scale
* Scale factor.
* @param interpolation
* Interpolation method used.
* @param tileHints
* Hints provided.
*
* @return The subsampled RenderedOp.
*/
private RenderedOp subsample(RenderedOp src, TileCache scaleTC, final Interpolation interpolation) {
final RenderingHints newHints = new RenderingHints(JAI.KEY_TILE_CACHE,scaleTC);
newHints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
newHints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, this.borderExtender));
// using filtered subsample operator to do a subsampling
final ParameterBlockJAI pb = new ParameterBlockJAI("filteredsubsample");
pb.addSource(src);
pb.setParameter("scaleX", new Integer(downsampleStep));
pb.setParameter("scaleY", new Integer(downsampleStep));
pb.setParameter("qsFilterArray", new float[] { 1.0f });
pb.setParameter("Interpolation", interpolation);
// remember to add the hint to avoid replacement of the original
// IndexColorModel
// in future versions we might want to make this parametrix XXX TODO
// @task
return JAI.create("filteredsubsample", pb, newHints);
}
public int getDownsampleStep() {
return downsampleStep;
}
public void setDownsampleStep(int downsampleWH) {
this.downsampleStep = downsampleWH;
}
public String getSourcePath() {
return sourcePath;
}
public void setSourcePath(String sourcePath) {
this.sourcePath = sourcePath;
}
public int getTileHeight() {
return tileH;
}
public void setTileHeight(int tileHeight) {
this.tileH = tileHeight;
}
public int getTileWidth() {
return tileW;
}
public void setTileWidth(int tileWidth) {
this.tileW = tileWidth;
}
/**
* Creating the scale operation using the FilteredSubSample operation with a
* null filter, which basically does not do any filtering. This is a hint I
* found on the JAI mailing list, a SUN engineer suggested to use this
* instead of scale since it uses a integer factor which is easier for the
* library to handle than a float scale factor like Scale operation is
* using.
*
* @param src
* Source image to be scaled.
* @param scaleTC
* @param factor
* Scale factor.
* @param interpolation
* Interpolation used.
* @param hints
* Hints provided to this method.
*
* @return The scaled image.
*/
private RenderedOp filteredSubsample(RenderedImage src, TileCache scaleTC) {
final RenderingHints newHints = new RenderingHints(JAI.KEY_TILE_CACHE,scaleTC);
newHints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
// using filtered subsample operator to do a subsampling
final ParameterBlockJAI pb = new ParameterBlockJAI("filteredsubsample");
pb.addSource(src);
pb.setParameter("scaleX", new Integer(downsampleStep));
pb.setParameter("scaleY", new Integer(downsampleStep));
pb.setParameter("qsFilterArray", lowPassFilter);
pb.setParameter("Interpolation", new InterpolationNearest());
return JAI.create("filteredsubsample", pb,newHints);
}
/**
* Creating the scale operation using the FilteredSubSample operation with a
* null filter, which basically does not do any filtering. This is a hint I
* found on the JAI mailing list, a SUN engineer suggested to use this
* instead of scale since it uses a integer factor which is easier for the
* library to handle than a float scale factor like Scale operation is
* using.
*
* @param src
* Source image to be scaled.
* @param scaleTC
* @param factor
* Scale factor.
* @param interpolation
* Interpolation used.
* @param hints
* Hints provided to this method.
*
* @return The scaled image.
*/
private RenderedOp scaleAverage(RenderedImage src, TileCache scaleTC) {
final RenderingHints newHints = new RenderingHints(JAI.KEY_TILE_CACHE,scaleTC);
newHints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
newHints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, this.borderExtender));
// using filtered subsample operator to do a subsampling
final ParameterBlockJAI pb = new ParameterBlockJAI("SubsampleAverage");
pb.addSource(src);
pb.setParameter("scaleX", new Double(1.0 / downsampleStep));
pb.setParameter("scaleY", new Double(1.0 / downsampleStep));
return JAI.create("SubsampleAverage", pb, newHints);
}
public void setBorderExtender(BorderExtender borderExtender) {
this.borderExtender = borderExtender;
}
public float[] getLowPassFilter() {
return lowPassFilter;
}
public void setLowPassFilter(float[] lowPassFilter) {
this.lowPassFilter = lowPassFilter;
}
public void run() {
//
// CHECK INPUT DIRECTORIES/FILES
//
if(sourcePath==null)
{
fireEvent("Provided sourcePath is null", 0);
return;
}
// getting an image input stream to the file
final File file = new File(sourcePath);
final File[] files;
int numFiles = 1;
StringBuilder message;
if(!file.canRead()||!file.exists())
{
fireEvent("Provided file "+file.getAbsolutePath()+" cannot be read or does not exist", 0);
return;
}
if (file.isDirectory()) {
if(wildcardString==null){
fireEvent("Provided wildcardString is null", 0);
return;
}
final FileFilter fileFilter = new WildcardFileFilter(wildcardString);
files = file.listFiles(fileFilter);
numFiles = files.length;
if (numFiles <= 0) {
message = new StringBuilder("No files to process!");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), 100);
}
} else
files = new File[] { file };
if(files==null||files.length==0)
{
fireEvent("Unable to find input files for the provided wildcard "+wildcardString+ " and input path "+sourcePath,0);
return;
}
//
// ADDING OVERVIEWS TO ALL FOUND FILES
//
for (fileBeingProcessed = 0; fileBeingProcessed < numFiles; fileBeingProcessed++) {
message = new StringBuilder("Managing file ").append(fileBeingProcessed).append(" of ").append(files[fileBeingProcessed]).append(" files");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
if (getStopThread()) {
message = new StringBuilder("Stopping requested at file ").append(fileBeingProcessed).append(" of ").append(numFiles).append(" files");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
return;
}
ImageInputStream stream=null;
ImageWriter writer =null;
ImageOutputStream streamOut=null;
TileCache baseTC= null;
TileCache scaleTC=null;
RenderedOp currentImage = null;
RenderedOp newImage=null;
try{
//
// get a stream
//
stream = ImageIO.createImageInputStream(files[fileBeingProcessed]);
if(stream==null)
{
message = new StringBuilder("Unable to create an input stream for file").append(files[fileBeingProcessed]);
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
break;
}
stream.mark();
//
// get a reader
//
final Iterator<ImageReader> it = ImageIO.getImageReaders(stream);
if (!it.hasNext()) {
message = new StringBuilder("Unable to find a reader for file").append(files[fileBeingProcessed]);
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
break;
}
final ImageReader reader = (ImageReader) it.next();
stream.reset();
stream.mark();
// is it a geotiff reader or not?
if(!reader.getFormatName().toLowerCase().startsWith("tif")){
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info("Discarding input file "+files[fileBeingProcessed] + " since it is not a proper tif file.");
}
continue;
}
//
// set input
//
reader.setInput(stream);
ImageLayout layout = null;
// tiling the image if needed
int actualTileW = reader.getTileWidth(0);
int actualTileH = reader.getTileHeight(0);
final int numImages = reader.getNumImages(true);
if (!reader.isImageTiled(0)||
(reader.isImageTiled(0) && (actualTileH != tileH && tileH != -1)|| (actualTileW != tileW && tileW != -1))) {
message = new StringBuilder("Retiling image ").append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
layout = createTiledLayout(tileW, tileH, 0, 0);
}
stream.reset();
reader.reset();
reader.dispose();
//
// output image stream
//
streamOut = ImageIOExt.createImageOutputStream(null, files[fileBeingProcessed]);
if (streamOut == null) {
message = new StringBuilder(
"Unable to acquire an ImageOutputStream for the file ")
.append(files[fileBeingProcessed].toString());
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
break;
}
//
// Preparing to write the set of images. First of all I write
// the first image `
//
// getting a writer for this reader
writer = new TIFFImageWriterSpi().createWriterInstance();//ImageIO.getImageWriter(reader);
writer.setOutput(streamOut);
writer.addIIOWriteProgressListener(writeProgressListener);
writer.addIIOWriteWarningListener(writeProgressListener);
ImageWriteParam param = writer.getDefaultWriteParam();
// can we tile this image? (TIFF or JPEG2K)
if (!(param.canWriteTiles())) {
message = new StringBuilder("This format do not support tiling!");
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
break;
}
// can we write a sequence for these images?
if (!(writer.canInsertImage(numImages))) {
message = new StringBuilder("This format do not support overviews!");
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
break;
}
//
// setting tiling on the first image using writing parameters
//
if (tileH != -1 & tileW != -1) {
param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
param.setTiling(tileW, tileH, 0, 0);
} else {
param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
param.setTiling(actualTileW, actualTileH, 0, 0);
}
if (this.compressionScheme != null && !Double.isNaN(compressionRatio)) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType(compressionScheme);
param.setCompressionQuality((float) this.compressionRatio);
}
//
// creating the image to use for the successive
// subsampling
//
final long tilecacheSize=this.getTileCacheSize()<=0?DEFAULT_TILE_CACHE_SIZE:super.getTileCacheSize();
baseTC= JAI.createTileCache();
baseTC.setMemoryCapacity(tilecacheSize/2);
baseTC.setMemoryThreshold(1.0f);
scaleTC= JAI.createTileCache();
scaleTC.setMemoryCapacity(tilecacheSize/2);
scaleTC.setMemoryThreshold(1.0f);
final RenderingHints newHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
newHints.add(new RenderingHints(JAI.KEY_TILE_CACHE,baseTC));
// read base image
ParameterBlock pbjRead = new ParameterBlock();
pbjRead.add(stream);
pbjRead.add(Integer.valueOf(0));
pbjRead.add(Boolean.FALSE);
pbjRead.add(Boolean.FALSE);
pbjRead.add(Boolean.FALSE);
pbjRead.add(null);
pbjRead.add(null);
pbjRead.add(null);
pbjRead.add(null);
currentImage = JAI.create("ImageRead", pbjRead,newHints);
message = new StringBuilder("Read original image ").append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
for (overviewInProcess = 0; overviewInProcess < numSteps; overviewInProcess++) {
message = new StringBuilder("Subsampling step ").append(overviewInProcess).append(" of image ").append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),((fileBeingProcessed * 100.0) / numFiles));
// paranoiac check
if (currentImage.getWidth() / downsampleStep <= 0|| currentImage.getHeight() / downsampleStep <= 0)
break;
// SCALE
// subsampling the input image using the chosen algorithm
final SubsampleAlgorithm algorithm=SubsampleAlgorithm.valueOf(scaleAlgorithm);
switch (algorithm) {
case Average:
newImage = scaleAverage(currentImage,scaleTC);
break;
case Filtered:
newImage = filteredSubsample(currentImage,scaleTC);
break;
case Bilinear:
newImage = subsample(currentImage,scaleTC,new InterpolationBilinear());
break;
case Bicubic:
newImage = subsample(currentImage,scaleTC,new InterpolationBicubic(2));
break;
case Nearest:
newImage = subsample(currentImage,scaleTC, new InterpolationNearest());
break;
default:
throw new IllegalArgumentException("Invalid scaling algorithm "+scaleAlgorithm);//cannot get here
}
//set relevant metadata
IIOMetadata imageMetadata = null;
if (writer instanceof TIFFImageWriter){
imageMetadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(newImage), param);
if (imageMetadata != null)
((TIFFImageMetadata)imageMetadata).addShortOrLongField(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE, BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION);
}
// write out
writer.writeInsert(-1, new IIOImage(newImage, null, imageMetadata), param);
message = new StringBuilder("Step ").append(
overviewInProcess).append(" of image ").append(
fileBeingProcessed).append(" done!");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
// flushing cache on the old image which we are not going to use anymore
baseTC.flush();
// switching caches and images
final TileCache appo= baseTC;
baseTC=scaleTC;
scaleTC=appo;
currentImage = newImage;
}
// close message
message = new StringBuilder("Done with image ")
.append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),
(((fileBeingProcessed + 1) * 100.0) / numFiles));
}catch (Throwable e) {
fireException(e);
}finally{
// clean up
// clean caches
if(baseTC!=null)
try{
baseTC.flush();
} catch (Exception e) {
}
if(scaleTC!=null)
try{
scaleTC.flush();
} catch (Exception e) {
}
//
// free everything
try {
if(streamOut!=null)
streamOut.flush();
} catch (Throwable e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
try {
if(streamOut!=null)
streamOut.close();
} catch (Throwable e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
try {
if(writer!=null)
writer.dispose();
} catch (Throwable e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
try {
if (currentImage != null)
currentImage.dispose();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
if(newImage!=null)
newImage.dispose();
try {
stream.close();
} catch (Throwable e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
}
}
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Done!!!");
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.utils.progress.ProcessingEventListener#getNotification(it.geosolutions.utils.progress.ProcessingEvent)
*/
public void getNotification(ProcessingEvent event) {
LOGGER.info(new StringBuilder("Progress is at ").append(
event.getPercentage()).append("\n").append(
"attached message is: ").append(event.getMessage()).toString());
}
public void exceptionOccurred(ExceptionEvent event) {
LOGGER.log(Level.SEVERE, "An error occurred during processing", event
.getException());
}
public boolean parseArgs(String[] args) {
if (!super.parseArgs(args))
return false;
// ////////////////////////////////////////////////////////////////
//
// parsing command line parameters and setting up
// Mosaic Index Builder options
//
// ////////////////////////////////////////////////////////////////
sourcePath = (String) getOptionValue(locationOpt);
// tile dim
if (hasOption(tileDimOpt)) {
final String tileDim = (String) getOptionValue(tileDimOpt);
final String[] pairs = tileDim.split(",");
tileW = Integer.parseInt(pairs[0]);
tileH = Integer.parseInt(pairs[1]);
}
// //
//
// scale factor
//
// //
final String scaleF = (String) getOptionValue(scaleFactorOpt);
downsampleStep = Integer.parseInt(scaleF);
// //
//
// wildcard
//
// //
if (hasOption(wildcardOpt))
wildcardString = (String) getOptionValue(wildcardOpt);
// //
//
// scaling algorithm (default to nearest neighbour)
//
// //
scaleAlgorithm = (String) getOptionValue(scaleAlgorithmOpt);
if (scaleAlgorithm == null)
scaleAlgorithm = "nn";
// //
//
// number of steps
//
// //
numSteps = Integer.parseInt((String) getOptionValue(numStepsOpt));
// //
//
// Compression params
//
// //
// index name
if (hasOption(compressionTypeOpt)) {
compressionScheme = (String) getOptionValue(compressionTypeOpt);
if (compressionScheme == "")
compressionScheme = null;
}
if (hasOption(compressionRatioOpt)) {
try {
compressionRatio = Double
.parseDouble((String) getOptionValue(compressionRatioOpt));
} catch (Exception e) {
compressionRatio = Double.NaN;
}
}
return true;
}
/**
* This tool is designed to be used by the command line using this main
* class but it can also be used from an GUI by using the setters and
* getters.
*
* @param args
* @throws IOException
* @throws IllegalArgumentException
* @throws InterruptedException
*/
public static void main(String[] args) throws IllegalArgumentException,
IOException, InterruptedException {
// creating an overviews embedder
final OverviewsEmbedder overviewsEmbedder = new OverviewsEmbedder();
// adding the embedder itself as a listener
overviewsEmbedder.addProcessingEventListener(overviewsEmbedder);
// parsing input argumentBuilder
if (overviewsEmbedder.parseArgs(args)) {
// creating a thread to execute the request process, with the
// provided priority
final Thread t = new Thread(overviewsEmbedder, "OverviewsEmbedder");
t.setPriority(overviewsEmbedder.getPriority());
t.start();
try {
t.join();
} catch (InterruptedException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
} else if (LOGGER.isLoggable(Level.FINE))
LOGGER
.fine("Unable to parse command line argumentBuilder, exiting...");
}
/**
* Sets the wildcar string to use.
*
* @param wildcardString
* the wildcardString to set
*/
public final void setWildcardString(String wildcardString) {
this.wildcardString = wildcardString;
}
public final double getCompressionRatio() {
return compressionRatio;
}
public final String getCompressionScheme() {
return compressionScheme;
}
public void setCompressionRatio(double compressionRatio) {
this.compressionRatio = compressionRatio;
}
public void setCompressionScheme(String compressionScheme) {
this.compressionScheme = compressionScheme;
}
public String getScaleAlgorithm() {
return scaleAlgorithm;
}
public void setScaleAlgorithm(String scaleAlgorithm) {
this.scaleAlgorithm = scaleAlgorithm;
}
public int getNumSteps() {
return numSteps;
}
public void setNumSteps(int numSteps) {
this.numSteps = numSteps;
}
public OverviewsEmbedderWriteProgressListener getWriteProgressListener() {
return writeProgressListener;
}
public String getWildcardString() {
return wildcardString;
}
}