/*
* 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 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;
import it.geosolutions.imageio.plugins.tiff.BaselineTIFFTagSet;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageMetadata;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageWriter;
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.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
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.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.RenderedOp;
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.WildcardFilter;
import org.geotools.resources.image.ImageUtilities;
/**
* <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.3
*
*
* @source $URL$
*/
public class OverviewsEmbedder extends BaseArgumentsManager implements
Runnable, ProcessingEventListener {
/**
*
* @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 StringBuffer(
"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 StringBuffer(
"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 StringBuffer(
"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 StringBuffer(
"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 StringBuffer(
"Aborted writing process.").toString(), 100.0);
}
}
/**
* The default listener for checking the progress of the writing process.
*/
private final OverviewsEmbedderWriteProgressListener writeProgressListener = new OverviewsEmbedderWriteProgressListener();
/** Static immutable ap for scaling algorithms. */
public static final List<String> scalingAlgorithms;
static {
ArrayList<String> list = new ArrayList<String>();
list.add("nn");
list.add("bil");
list.add("bic");
list.add("avg");
list.add("filt");
scalingAlgorithms=Collections.unmodifiableList(list);
}
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;
/**
*
* Interpolation method used througout all the program.
*
* @TODO make the interpolation method customizable from the user
* perpsective.
*
*/
private Interpolation interp = CoverageToolsConstants.DEFAULT_INTERPOLATION;
private String compressionScheme = CoverageToolsConstants.DEFAULT_COMPRESSION_SCHEME;
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.
*
*
*/
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 StringBuffer(
"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 StringBuffer(
"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 StringBuffer(
"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");
if (!scalingAlgorithms.contains(args
.get(0)))
throw new InvalidArgumentException(
new StringBuffer(
"The scaling algorithm ")
.append(args.get(0))
.append(
" is not permitted")
.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 interp
* Interpolation method used.
*
* @return RenderedOp containing the chain to obtain the tiled image.
*/
private ImageLayout tile(final int tileWidth, final int tileHeight,
final int tileGrdiOffseX, final int tileGrdiOffseY,
final Interpolation interp) {
// //
//
// 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 scale
* Scale factor.
* @param interp
* Interpolation method used.
* @param tileHints
* Hints provided.
*
* @return The subsampled RenderedOp.
*/
private RenderedOp subsample(RenderedOp src) {
// 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", interp);
// 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
final RenderingHints hints = new RenderingHints(
JAI.KEY_BORDER_EXTENDER, this.borderExtender);
hints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
return JAI.create("filteredsubsample", pb, hints);
}
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 factor
* Scale factor.
* @param interpolation
* Interpolation used.
* @param hints
* Hints provided to this method.
*
* @return The scaled image.
*/
private RenderedOp filteredSubsample(RenderedImage src) {
// 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", interp);
return JAI.create("filteredsubsample", pb);
}
/**
* 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 factor
* Scale factor.
* @param interpolation
* Interpolation used.
* @param hints
* Hints provided to this method.
*
* @return The scaled image.
*/
private RenderedOp scaleAverage(RenderedImage src) {
// 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, new RenderingHints(
JAI.KEY_BORDER_EXTENDER, this.borderExtender));
}
public void setBorderExtender(BorderExtender borderExtender) {
this.borderExtender = borderExtender;
}
public void setInterp(Interpolation interp) {
this.interp = interp;
}
public float[] getLowPassFilter() {
return lowPassFilter;
}
public void setLowPassFilter(float[] lowPassFilter) {
this.lowPassFilter = lowPassFilter;
}
public void run() {
try {
// getting an image input stream to the file
final File dir = new File(sourcePath);
final File[] files;
int numFiles = 1;
StringBuffer message;
if (dir.isDirectory()) {
final FileFilter fileFilter = new WildcardFilter(wildcardString);
files = dir.listFiles(fileFilter);
numFiles = files.length;
if (numFiles <= 0) {
message = new StringBuffer("No files to process!");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), 100);
}
} else
files = new File[] { dir };
// /////////////////////////////////////////////////////////////////////
//
// Cycling over the features
//
// /////////////////////////////////////////////////////////////////////
for (fileBeingProcessed = 0; fileBeingProcessed < numFiles; fileBeingProcessed++) {
message = new StringBuffer("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 StringBuffer("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;
}
// //
//
// get a stream
//
//
// //
ImageInputStream stream = ImageIO
.createImageInputStream(files[fileBeingProcessed]);
stream.mark();
// //
//
// get a reader
//
//
// //
final Iterator<ImageReader> it = ImageIO
.getImageReaders(stream);
if (!it.hasNext()) {
return;
}
final ImageReader reader = (ImageReader) it.next();
stream.reset();
stream.mark();
// //
//
// 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) && (actualTileH != tileH)
&& (actualTileW != tileW) && tileH != -1 && tileW != -1) {
message = new StringBuffer("Retiling image ")
.append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
layout = tile(tileW, tileH, 0, 0, interp);
}
stream.reset();
reader.dispose();
// //
//
// output image stream
//
// //
ImageOutputStream streamOut = ImageIO
.createImageOutputStream(files[fileBeingProcessed]);
if (streamOut == null) {
message = new StringBuffer(
"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));
return;
}
// //
//
// Preparing to write the set of images. First of all I write
// the first image `
//
// //
// getting a writer for this reader
ImageWriter writer = 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 StringBuffer(
"This format do not support tiling!");
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
return;
}
// can we write a sequence for these images?
if (!(writer.canInsertImage(numImages))) {
message = new StringBuffer(
"This format do not support overviews!");
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.severe(message.toString());
}
fireEvent(message.toString(),
((fileBeingProcessed * 100.0) / numFiles));
return;
}
// //
//
// 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 RenderingHints newHints = new RenderingHints(
JAI.KEY_IMAGE_LAYOUT, layout);
ParameterBlock pbjRead = new ParameterBlock();
pbjRead.add(ImageIO
.createImageInputStream(files[fileBeingProcessed]));
pbjRead.add(new Integer(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);
RenderedOp currentImage = JAI.create("ImageRead", pbjRead,
newHints);
message = new StringBuffer("Reaad 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++) {
// if (overviewInProcess > 0) {
//
// // re-instantiate the current image from disk
// stream = ImageIO
// .createImageInputStream(files[fileBeingProcessed]);
// pbjRead = new ParameterBlock();
// pbjRead.add(stream);
// pbjRead.add(new Integer(overviewInProcess));
// 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);
//
// // //
// //
// // output image stream
// //
// // //
// streamOut = ImageIO
// .createImageOutputStream(files[fileBeingProcessed]);
//
// // //
// //
// // Preparing to write the set of images. First of all I
// // write the first image `
// //
// // //
// // getting a writer for this reader
// writer.setOutput(streamOut);
// writer
// .addIIOWriteProgressListener(writeProgressListener);
//
// }
message = new StringBuffer("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;
RenderedOp newImage;
// subsampling the input image using the chosen algorithm
if (scaleAlgorithm.equalsIgnoreCase("avg"))
newImage = scaleAverage(currentImage);
else if (scaleAlgorithm.equalsIgnoreCase("filt"))
newImage = filteredSubsample(currentImage);
else if (scaleAlgorithm.equalsIgnoreCase("bil"))
newImage = bilinear(currentImage);
else if (scaleAlgorithm.equalsIgnoreCase("nn"))
newImage = subsample(currentImage);
else if (scaleAlgorithm.equalsIgnoreCase("bic"))
newImage = bicubic(currentImage);
else
throw new IllegalStateException();
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 StringBuffer("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
JAI.getDefaultInstance().getTileCache().removeTiles(
currentImage);
currentImage = newImage;
//
// // free everything
// streamOut.flush();
// streamOut.close();
// writer.reset();
// currentImage.dispose();
// stream.close();
}
message = new StringBuffer("Done with image ")
.append(fileBeingProcessed);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(),
(((fileBeingProcessed + 1) * 100.0) / numFiles));
}
} catch (IOException e) {
fireException(e);
}
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Done!!!");
}
/**
* Performs a bilinear interpolation on the provided image.
*
* @param src
* The source image.
* @return The subsampled image.
*/
private RenderedOp bilinear(RenderedOp src) {
// 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", new InterpolationBilinear());
return JAI.create("filteredsubsample", pb,
ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
}
/**
* Performs a bicubic interpolation on the provided image.
*
* @param src
* The source image.
* @return The subsampled image.
*/
private RenderedOp bicubic(RenderedOp src) {
// 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", new InterpolationBicubic(2));
return JAI.create("filteredsubsample", pb,
ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.utils.progress.ProcessingEventListener#getNotification(it.geosolutions.utils.progress.ProcessingEvent)
*/
public void getNotification(ProcessingEvent event) {
LOGGER.info(new StringBuffer("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 final void setCompressionRatio(double compressionRatio) {
this.compressionRatio = compressionRatio;
}
public final void setCompressionScheme(String compressionScheme) {
this.compressionScheme = compressionScheme;
}
public int getTileW() {
return tileW;
}
public void setTileW(int tileW) {
this.tileW = tileW;
}
public int getTileH() {
return tileH;
}
public void setTileH(int tileH) {
this.tileH = tileH;
}
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;
}
public static List<String> getScalingAlgorithms() {
return scalingAlgorithms;
}
public Interpolation getInterp() {
return interp;
}
}