package it.geosolutions.utils.imageoverviews; import it.geosolutions.utils.DefaultWriteProgressListener; import it.geosolutions.utils.progress.ExceptionEvent; import it.geosolutions.utils.progress.ProcessingEvent; import it.geosolutions.utils.progress.ProcessingEventListener; import it.geosolutions.utils.progress.ProgressManager; 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.Iterator; import java.util.List; import java.util.Locale; 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.ImageWriteParam; import javax.imageio.ImageWriter; 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.RecyclingTileFactory; import javax.media.jai.RenderedOp; import javax.media.jai.TileCache; import org.apache.commons.cli2.option.DefaultOption; import org.apache.commons.cli2.option.GroupImpl; import org.apache.commons.cli2.util.HelpFormatter; 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; import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam; /** *
 *  Example of usage:
 * 
 * OverviewsEmbedder -s "/usr/home/tmp" -w *.tiff -t "512,512" -f 32 -n 8 -a nn -c 512
 * 
 *  <pre>
 *  
 * 

* HINT: Take more memory as the 64Mb default by using the following Java Options<BR/> * * -Xmx1024M - Xms512M * *

* @author Simone Giannecchini (GeoSolutions) * @author Alessio Fabiani (GeoSolutions) * @since 2.3.x * @version 0.2 * */ public class OverviewsEmbedder extends ProgressManager implements Runnable, ProcessingEventListener { /** * * @author Simone Giannecchini * @since 2.3.x * */ private class OverviewsEmbedderWriteProgressListener extends DefaultWriteProgressListener { /* * (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(); /** * Default tile cache size. */ public final static long DEFAULT_TILE_CHACHE_SIZE = 64 * 1024 * 1024; /** * Default imageio caching behaviour. */ public final boolean DEFAULT_IMAGEIO_CACHING_BEHAVIOUR = false; /** * Default thread priority. */ public final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY; /** * Default interpolation. */ public final static Interpolation DEFAULT_INTERPOLATION = Interpolation .getInstance(Interpolation.INTERP_NEAREST); /** Default filter for subsampling averaged. */ public final static float[] DEFAULT_KERNEL_GAUSSIAN = new float[] { 0.5F, 1.0F / 3.0F, 0.0F, -1.0F / 12.0F }; /** * Default border extender. */ public final static BorderExtender DEFAULT_BORDER_EXTENDER = BorderExtender .createInstance(BorderExtender.BORDER_COPY); /** Static immutable ap for scaling algorithms. */ private static List scalingAlgorithms; static { scalingAlgorithms = new ArrayList(4); scalingAlgorithms.add("nn"); scalingAlgorithms.add("bil"); scalingAlgorithms.add("bic"); scalingAlgorithms.add("avg"); scalingAlgorithms.add("filt"); } /** Program Version */ private final static String versionNumber = "0.2"; /** Commons-cli option for the input location. */ private DefaultOption locationOpt; /** Commons-cli option for the tile dimension. */ private DefaultOption tileDimOpt; /** Commons-cli option for the scale algorithm. */ private DefaultOption scaleAlgorithmOpt; /** Commons-cli option for the wild card to use. */ private DefaultOption wildcardOpt; /** Commons-cli option for the tile cache size to use. */ private DefaultOption tileCacheSizeOpt; /** Commons-cli option for the tile numbe of subsample step to use. */ private DefaultOption numStepsOpt; /** Commons-cli option for the scale factor to use. */ private DefaultOption scaleFactorOpt; /** 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 = Logger .getLogger(OverviewsEmbedder.class.toString()); /** Default number of resolution steps.. */ public final int DEFAULT_RESOLUTION_STEPS = 5; /** ImageIO caching behvaiour controller. */ private boolean useImageIOCache = DEFAULT_IMAGEIO_CACHING_BEHAVIOUR; /** Default border extender. */ private BorderExtender borderExtender = DEFAULT_BORDER_EXTENDER; /** Downsampling step. */ private int downsampleStep; /** Default tile cache size. */ private long tileCacheSize = DEFAULT_TILE_CHACHE_SIZE; /** Low pass filter. */ private float[] lowPassFilter = 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 = DEFAULT_INTERPOLATION; private int numSteps; private String wildcardString = "*.*"; private volatile int fileBeingProcessed; private volatile int overviewInProcess; /** * Simple constructor for a pyramid generator. Use the input string in order * to read an image. * * */ public OverviewsEmbedder() { // ///////////////////////////////////////////////////////////////////// // Options for the command line // ///////////////////////////////////////////////////////////////////// helpOpt = optionBuilder.withShortName("h").withShortName("?") .withLongName("helpOpt").withDescription("print this message.") .withRequired(false).create(); versionOpt = optionBuilder.withShortName("v") .withLongName("versionOpt").withDescription( "print the versionOpt.").withRequired(false).create(); locationOpt = optionBuilder .withShortName("s") .withLongName("source") .withArgument( arguments.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( arguments.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( arguments.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 scaling algorithm 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( arguments.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( arguments.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 scaling algorithm at a time can be chosen"); int steps = Integer .parseInt((String) args.get(0)); if (steps <= 0) throw new InvalidArgumentException( new StringBuffer( "The provided scale factor is negative! ") .toString()); } }).create()).withDescription( "integer scale factor").withRequired(true).create(); scaleAlgorithmOpt = optionBuilder .withShortName("a") .withLongName("scaling_algorithm") .withArgument( arguments.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 output format ") .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(); priorityOpt = optionBuilder.withShortName("p").withLongName( "thread_priority").withArgument( arguments.withName("thread_priority").withMinimum(0) .withMaximum(1).create()).withDescription( "priority for the underlying thread").withRequired(false) .create(); tileCacheSizeOpt = optionBuilder.withShortName("c").withLongName( "cache_size").withArgument( arguments.withName("c").withMinimum(0).withMaximum(1).create()) .withDescription("tile cache sized").withRequired(false) .create(); cmdOpts.add(locationOpt); cmdOpts.add(tileDimOpt); cmdOpts.add(scaleFactorOpt); cmdOpts.add(scaleAlgorithmOpt); cmdOpts.add(numStepsOpt); cmdOpts.add(wildcardOpt); cmdOpts.add(priorityOpt); cmdOpts.add(tileCacheSizeOpt); cmdOpts.add(versionOpt); cmdOpts.add(helpOpt); optionsGroup = new GroupImpl(cmdOpts, "Options", "All the options", 0, 9); // ///////////////////////////////////////////////////////////////////// // // Help Formatter // // ///////////////////////////////////////////////////////////////////// final HelpFormatter cmdHlp = new HelpFormatter("| ", " ", " |", 75); cmdHlp.setShellCommand("OverviewsEmbedder"); cmdHlp.setHeader("Help"); cmdHlp.setFooter(new StringBuffer( "OverviewsEmbedder - GeoSolutions S.a.s (C) 2006 - v ").append( OverviewsEmbedder.versionNumber).toString()); cmdHlp .setDivider("|-------------------------------------------------------------------------|"); cmdParser.setGroup(optionsGroup); cmdParser.setHelpOption(helpOpt); cmdParser.setHelpFormatter(cmdHlp); } /** * This method retiles the original image using a specified tile wiedth 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 method is a utlity method for setting various JAi wide hints we will * use here and afterwards. * * */ private void settingJAIHints() { // // // Imageio caching behaviour in case it is ever needed. // // ImageIO.setUseCache(useImageIOCache); // // // // JAI cache fine tuning // // // final JAI jaiDef = JAI.getDefaultInstance(); // setting the tile cache final TileCache cache = jaiDef.getTileCache(); cache.setMemoryCapacity(tileCacheSize * 1024 * 1024); // setting JAI wide hints jaiDef.setRenderingHint(JAI.KEY_INTERPOLATION, this.interp); jaiDef.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); jaiDef.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); jaiDef.setRenderingHint(JAI.KEY_CACHED_TILE_RECYCLING_ENABLED, Boolean.TRUE); // // // // tile factory and recycler // // // RecyclingTileFactory recyclingFactory = new RecyclingTileFactory(); jaiDef.setRenderingHint(JAI.KEY_TILE_FACTORY, recyclingFactory); jaiDef.setRenderingHint(JAI.KEY_TILE_RECYCLER, recyclingFactory); // // // // border extender // // // jaiDef.setRenderingHint(JAI.KEY_BORDER_EXTENDER, borderExtender); } /** * 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 return JAI.create("filteredsubsample", pb,ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL); } 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); } public void setBorderExtender(BorderExtender borderExtender) { this.borderExtender = borderExtender; } public void setInterp(Interpolation interp) { this.interp = interp; } public void setTileCacheSize(long tileCacheSize) { this.tileCacheSize = tileCacheSize; } public void setUseImageIOCache(boolean useImageIOCache) { this.useImageIOCache = useImageIOCache; } public float[] getLowPassFilter() { return lowPassFilter; } public void setLowPassFilter(float[] lowPassFilter) { this.lowPassFilter = lowPassFilter; } public void run() { try { // // // // setting JAI wide hints // // // settingJAIHints(); // final TCTool tool = new TCTool(); // 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 // // ///////////////////////////////////////////////////////////////////// RenderedOp currentImage; ImageReader reader; ImageInputStream stream; Iterator it; ImageLayout layout; ImageOutputStream streamOut; RenderingHints newHints; ParameterBlock pbjRead; 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 // // // // stream = ImageIO .createImageInputStream(files[fileBeingProcessed]); stream.mark(); // // // // get a reader // // // // it = ImageIO.getImageReaders(stream); if (!it.hasNext()) { return; } reader = (ImageReader) it.next(); stream.reset(); stream.mark(); // // // // set input // // // reader.setInput(stream); layout = null; // tiling the image if needed int actualTileW = reader.getTileWidth(0); int actualTileH = reader.getTileHeight(0); 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 // // // 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); 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(1))) { 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); } // // // // creating the image to use for the successive // subsampling // // // newHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); 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); 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 = ImageIO.getImageWriter(reader); writer.setOutput(streamOut); writer .addIIOWriteProgressListener(writeProgressListener); param = writer.getDefaultWriteParam(); } 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; // subsampling the input image using the chosen algorithm if (scaleAlgorithm.equalsIgnoreCase("avg")) currentImage = scaleAverage(currentImage); else if (scaleAlgorithm.equalsIgnoreCase("filt")) currentImage = filteredSubsample(currentImage); if (scaleAlgorithm.equalsIgnoreCase("bil")) currentImage = bilinear(currentImage); if (scaleAlgorithm.equalsIgnoreCase("nn")) currentImage = subsample(currentImage); if (scaleAlgorithm.equalsIgnoreCase("bic")) currentImage = bicubic(currentImage); // write out writer.writeInsert(overviewInProcess + 1, new IIOImage( currentImage, null, null), 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().flush(); // free everything streamOut.flush(); streamOut.close(); writer.dispose(); 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); } /** * 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); } /* * (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()); } private boolean parseArgs(String[] args) { cmdLine = cmdParser.parseAndHelp(args); if (cmdLine != null && cmdLine.hasOption(versionOpt)) { LOGGER.fine(new StringBuffer( "OverviewsEmbedder - GeoSolutions S.a.s (C) 2006 - v") .append(OverviewsEmbedder.versionNumber).toString()); System.exit(1); } else if (cmdLine != null) { // //////////////////////////////////////////////////////////////// // // parsing command line parameters and setting up // Mosaic Index Builder options // // //////////////////////////////////////////////////////////////// sourcePath = (String) cmdLine.getValue(locationOpt); // tile dim if (cmdLine.hasOption(tileDimOpt)) { final String tileDim = (String) cmdLine.getValue(tileDimOpt); final String[] pairs = tileDim.split(","); tileW = Integer.parseInt(pairs[0]); tileH = Integer.parseInt(pairs[1]); } // // // // scale factor // // // final String scaleF = (String) cmdLine.getValue(scaleFactorOpt); downsampleStep = Integer.parseInt(scaleF); // // // // wildcard // // // if (cmdLine.hasOption(wildcardOpt)) wildcardString = (String) cmdLine.getValue(wildcardOpt); // // // // scaling algorithm (default to nearest neighbour) // // // scaleAlgorithm = (String) cmdLine.getValue(scaleAlgorithmOpt); if (scaleAlgorithm == null) scaleAlgorithm = "nn"; // // // // number of steps // // // numSteps = Integer.parseInt((String) cmdLine.getValue(numStepsOpt)); // // // // Thread priority // // // // index name if (cmdLine.hasOption(priorityOpt)) priority = Integer.parseInt((String) cmdLine .getValue(priorityOpt)); // // // // Tile cache size // // // // index name if (cmdLine.hasOption(tileCacheSizeOpt)) { tileCacheSize = Integer.parseInt((String) cmdLine .getValue(tileCacheSizeOpt)); } return true; } return false; } /** * 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 arguments 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.priority); 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 arguments, exiting..."); } /** * Sets the wildcar string to use. * * @param wildcardString * the wildcardString to set */ public final void setWildcardString(String wildcardString) { this.wildcardString = wildcardString; } }