0001: package it.geosolutions.utils.imageoverviews;
0002:
0003: import it.geosolutions.utils.DefaultWriteProgressListener;
0004: import it.geosolutions.utils.progress.ExceptionEvent;
0005: import it.geosolutions.utils.progress.ProcessingEvent;
0006: import it.geosolutions.utils.progress.ProcessingEventListener;
0007: import it.geosolutions.utils.progress.ProgressManager;
0008:
0009: import java.awt.RenderingHints;
0010: import java.awt.image.RenderedImage;
0011: import java.awt.image.renderable.ParameterBlock;
0012: import java.io.File;
0013: import java.io.FileFilter;
0014: import java.io.IOException;
0015: import java.util.ArrayList;
0016: import java.util.Iterator;
0017: import java.util.List;
0018: import java.util.Locale;
0019: import java.util.logging.Level;
0020: import java.util.logging.Logger;
0021:
0022: import javax.imageio.IIOImage;
0023: import javax.imageio.ImageIO;
0024: import javax.imageio.ImageReader;
0025: import javax.imageio.ImageWriteParam;
0026: import javax.imageio.ImageWriter;
0027: import javax.imageio.stream.ImageInputStream;
0028: import javax.imageio.stream.ImageOutputStream;
0029: import javax.media.jai.BorderExtender;
0030: import javax.media.jai.ImageLayout;
0031: import javax.media.jai.Interpolation;
0032: import javax.media.jai.InterpolationBicubic;
0033: import javax.media.jai.InterpolationBilinear;
0034: import javax.media.jai.JAI;
0035: import javax.media.jai.ParameterBlockJAI;
0036: import javax.media.jai.RecyclingTileFactory;
0037: import javax.media.jai.RenderedOp;
0038: import javax.media.jai.TileCache;
0039:
0040: import org.apache.commons.cli2.option.DefaultOption;
0041: import org.apache.commons.cli2.option.GroupImpl;
0042: import org.apache.commons.cli2.util.HelpFormatter;
0043: import org.apache.commons.cli2.validation.InvalidArgumentException;
0044: import org.apache.commons.cli2.validation.Validator;
0045: import org.apache.commons.io.filefilter.WildcardFilter;
0046: import org.geotools.resources.image.ImageUtilities;
0047:
0048: import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
0049:
0050: /**
0051: * <pre>
0052: * Example of usage:
0053: * <code>
0054: * OverviewsEmbedder -s "/usr/home/tmp" -w *.tiff -t "512,512" -f 32 -n 8 -a nn -c 512
0055: * </code>
0056: * <pre>
0057: *
0058: * <p>
0059: * HINT: Take more memory as the 64Mb default by using the following Java Options<BR/>
0060: * <code>
0061: * -Xmx1024M - Xms512M
0062: * </code>
0063: * </p>
0064: * @author Simone Giannecchini (GeoSolutions)
0065: * @author Alessio Fabiani (GeoSolutions)
0066: * @since 2.3.x
0067: * @version 0.2
0068: *
0069: */
0070: public class OverviewsEmbedder extends ProgressManager implements
0071: Runnable, ProcessingEventListener {
0072:
0073: /**
0074: *
0075: * @author Simone Giannecchini
0076: * @since 2.3.x
0077: *
0078: */
0079: private class OverviewsEmbedderWriteProgressListener extends
0080: DefaultWriteProgressListener {
0081:
0082: /*
0083: * (non-Javadoc)
0084: *
0085: * @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageComplete(javax.imageio.ImageWriter)
0086: */
0087: public void imageComplete(ImageWriter source) {
0088:
0089: OverviewsEmbedder.this .fireEvent(new StringBuffer(
0090: "Started with writing out overview number ")
0091: .append(overviewInProcess + 1.0).toString(),
0092: (overviewInProcess + 1 / numSteps) * 100.0);
0093: }
0094:
0095: /*
0096: * (non-Javadoc)
0097: *
0098: * @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageProgress(javax.imageio.ImageWriter,
0099: * float)
0100: */
0101: public void imageProgress(ImageWriter source,
0102: float percentageDone) {
0103: OverviewsEmbedder.this .fireEvent(new StringBuffer(
0104: "Writing out overview ").append(
0105: overviewInProcess + 1).toString(),
0106: (overviewInProcess / numSteps + percentageDone
0107: / (100 * numSteps)) * 100.0);
0108: }
0109:
0110: /*
0111: * (non-Javadoc)
0112: *
0113: * @see it.geosolutions.pyramids.DefaultWriteProgressListener#imageStarted(javax.imageio.ImageWriter,
0114: * int)
0115: */
0116: public void imageStarted(ImageWriter source, int imageIndex) {
0117: OverviewsEmbedder.this .fireEvent(new StringBuffer(
0118: "Completed writing out overview number ").append(
0119: overviewInProcess + 1).toString(),
0120: (overviewInProcess) / numSteps * 100.0);
0121: }
0122:
0123: /*
0124: * (non-Javadoc)
0125: *
0126: * @see it.geosolutions.pyramids.DefaultWriteProgressListener#warningOccurred(javax.imageio.ImageWriter,
0127: * int, java.lang.String)
0128: */
0129: public void warningOccurred(ImageWriter source, int imageIndex,
0130: String warning) {
0131: OverviewsEmbedder.this .fireEvent(new StringBuffer(
0132: "Warning at overview ").append(
0133: (overviewInProcess + 1)).toString(), 0);
0134: }
0135:
0136: /*
0137: * (non-Javadoc)
0138: *
0139: * @see it.geosolutions.pyramids.DefaultWriteProgressListener#writeAborted(javax.imageio.ImageWriter)
0140: */
0141: public void writeAborted(ImageWriter source) {
0142: OverviewsEmbedder.this .fireEvent(new StringBuffer(
0143: "Aborted writing process.").toString(), 100.0);
0144: }
0145: }
0146:
0147: /**
0148: * The default listener for checking the progress of the writing process.
0149: */
0150: private final OverviewsEmbedderWriteProgressListener writeProgressListener = new OverviewsEmbedderWriteProgressListener();
0151:
0152: /**
0153: * Default tile cache size.
0154: */
0155: public final static long DEFAULT_TILE_CHACHE_SIZE = 64 * 1024 * 1024;
0156:
0157: /**
0158: * Default imageio caching behaviour.
0159: */
0160: public final boolean DEFAULT_IMAGEIO_CACHING_BEHAVIOUR = false;
0161:
0162: /**
0163: * Default thread priority.
0164: */
0165: public final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY;
0166:
0167: /**
0168: * Default interpolation.
0169: */
0170: public final static Interpolation DEFAULT_INTERPOLATION = Interpolation
0171: .getInstance(Interpolation.INTERP_NEAREST);
0172:
0173: /** Default filter for subsampling averaged. */
0174: public final static float[] DEFAULT_KERNEL_GAUSSIAN = new float[] {
0175: 0.5F, 1.0F / 3.0F, 0.0F, -1.0F / 12.0F };
0176:
0177: /**
0178: * Default border extender.
0179: */
0180: public final static BorderExtender DEFAULT_BORDER_EXTENDER = BorderExtender
0181: .createInstance(BorderExtender.BORDER_COPY);
0182:
0183: /** Static immutable ap for scaling algorithms. */
0184: private static List scalingAlgorithms;
0185: static {
0186: scalingAlgorithms = new ArrayList(4);
0187: scalingAlgorithms.add("nn");
0188: scalingAlgorithms.add("bil");
0189: scalingAlgorithms.add("bic");
0190: scalingAlgorithms.add("avg");
0191: scalingAlgorithms.add("filt");
0192: }
0193:
0194: /** Program Version */
0195: private final static String versionNumber = "0.2";
0196:
0197: /** Commons-cli option for the input location. */
0198: private DefaultOption locationOpt;
0199:
0200: /** Commons-cli option for the tile dimension. */
0201: private DefaultOption tileDimOpt;
0202:
0203: /** Commons-cli option for the scale algorithm. */
0204: private DefaultOption scaleAlgorithmOpt;
0205:
0206: /** Commons-cli option for the wild card to use. */
0207: private DefaultOption wildcardOpt;
0208:
0209: /** Commons-cli option for the tile cache size to use. */
0210: private DefaultOption tileCacheSizeOpt;
0211:
0212: /** Commons-cli option for the tile numbe of subsample step to use. */
0213: private DefaultOption numStepsOpt;
0214:
0215: /** Commons-cli option for the scale factor to use. */
0216: private DefaultOption scaleFactorOpt;
0217:
0218: /** Tile width. */
0219: private int tileW = -1;
0220:
0221: /** Tile height. */
0222: private int tileH = -1;
0223:
0224: /** Scale algorithm. */
0225: private String scaleAlgorithm;
0226:
0227: /** Logger for this class. */
0228: private final static Logger LOGGER = org.geotools.util.logging.Logging
0229: .getLogger(OverviewsEmbedder.class.toString());
0230:
0231: /** Default number of resolution steps.. */
0232: public final int DEFAULT_RESOLUTION_STEPS = 5;
0233:
0234: /** ImageIO caching behvaiour controller. */
0235: private boolean useImageIOCache = DEFAULT_IMAGEIO_CACHING_BEHAVIOUR;
0236:
0237: /** Default border extender. */
0238: private BorderExtender borderExtender = DEFAULT_BORDER_EXTENDER;
0239:
0240: /** Downsampling step. */
0241: private int downsampleStep;
0242:
0243: /** Default tile cache size. */
0244: private long tileCacheSize = DEFAULT_TILE_CHACHE_SIZE;
0245:
0246: /** Low pass filter. */
0247: private float[] lowPassFilter = DEFAULT_KERNEL_GAUSSIAN;
0248:
0249: /**
0250: * The source path. It could point to a single file or to a directory when
0251: * we want to embed overwies into a set of files having a certain name.
0252: */
0253: private String sourcePath;
0254:
0255: /**
0256: *
0257: * Interpolation method used througout all the program.
0258: *
0259: * @TODO make the interpolation method customizable from the user
0260: * perpsective.
0261: *
0262: */
0263: private Interpolation interp = DEFAULT_INTERPOLATION;
0264:
0265: private int numSteps;
0266:
0267: private String wildcardString = "*.*";
0268:
0269: private volatile int fileBeingProcessed;
0270:
0271: private volatile int overviewInProcess;
0272:
0273: /**
0274: * Simple constructor for a pyramid generator. Use the input string in order
0275: * to read an image.
0276: *
0277: *
0278: */
0279: public OverviewsEmbedder() {
0280: // /////////////////////////////////////////////////////////////////////
0281: // Options for the command line
0282: // /////////////////////////////////////////////////////////////////////
0283: helpOpt = optionBuilder.withShortName("h").withShortName("?")
0284: .withLongName("helpOpt").withDescription(
0285: "print this message.").withRequired(false)
0286: .create();
0287:
0288: versionOpt = optionBuilder.withShortName("v").withLongName(
0289: "versionOpt").withDescription("print the versionOpt.")
0290: .withRequired(false).create();
0291:
0292: locationOpt = optionBuilder
0293: .withShortName("s")
0294: .withLongName("source")
0295: .withArgument(
0296: arguments.withName("source").withMinimum(1)
0297: .withMaximum(1).withValidator(
0298: new Validator() {
0299:
0300: public void validate(
0301: List args)
0302: throws InvalidArgumentException {
0303: final int size = args
0304: .size();
0305: if (size > 1)
0306: throw new InvalidArgumentException(
0307: "Source can be a single file or directory ");
0308: final File source = new File(
0309: (String) args
0310: .get(0));
0311: if (!source.exists())
0312: throw new InvalidArgumentException(
0313: new StringBuffer(
0314: "The provided source is invalid! ")
0315:
0316: .toString());
0317: }
0318:
0319: }).create()).withDescription(
0320: "path where files are located").withRequired(
0321: true)
0322: .create();
0323:
0324: tileDimOpt = optionBuilder.withShortName("t").withLongName(
0325: "tiled_dimension").withArgument(
0326: arguments.withName("t").withMinimum(0).withMaximum(1)
0327: .create()).withDescription(
0328: "tile dimensions as a couple width,height in pixels")
0329: .withRequired(false).create();
0330:
0331: scaleFactorOpt = optionBuilder.withShortName("f").withLongName(
0332: "scale_factor").withArgument(
0333: arguments.withName("f").withMinimum(1).withMaximum(1)
0334: .withValidator(new Validator() {
0335:
0336: public void validate(List args)
0337: throws InvalidArgumentException {
0338: final int size = args.size();
0339: if (size > 1)
0340: throw new InvalidArgumentException(
0341: "Only one scaling algorithm at a time can be chosen");
0342: int factor = Integer
0343: .parseInt((String) args.get(0));
0344: if (factor <= 0)
0345: throw new InvalidArgumentException(
0346: new StringBuffer(
0347: "The provided scale factor is negative! ")
0348:
0349: .toString());
0350: if (factor == 1) {
0351: LOGGER
0352: .warning("The scale factor is 1, program will exit!");
0353: System.exit(0);
0354: }
0355: }
0356:
0357: }).create()).withDescription(
0358: "integer scale factor").withRequired(true).create();
0359:
0360: wildcardOpt = optionBuilder.withShortName("w").withLongName(
0361: "wildcardOpt").withArgument(
0362: arguments.withName("wildcardOpt").withMinimum(0)
0363: .withMaximum(1).create()).withDescription(
0364: "wildcardOpt to use for selecting files").withRequired(
0365: false).create();
0366:
0367: numStepsOpt = optionBuilder.withShortName("n").withLongName(
0368: "num_steps").withArgument(
0369: arguments.withName("n").withMinimum(1).withMaximum(1)
0370: .withValidator(new Validator() {
0371:
0372: public void validate(List args)
0373: throws InvalidArgumentException {
0374: final int size = args.size();
0375: if (size > 1)
0376: throw new InvalidArgumentException(
0377: "Only one scaling algorithm at a time can be chosen");
0378: int steps = Integer
0379: .parseInt((String) args.get(0));
0380: if (steps <= 0)
0381: throw new InvalidArgumentException(
0382: new StringBuffer(
0383: "The provided scale factor is negative! ")
0384:
0385: .toString());
0386:
0387: }
0388:
0389: }).create()).withDescription(
0390: "integer scale factor").withRequired(true).create();
0391:
0392: scaleAlgorithmOpt = optionBuilder
0393: .withShortName("a")
0394: .withLongName("scaling_algorithm")
0395: .withArgument(
0396: arguments.withName("a").withMinimum(0)
0397: .withMaximum(1).withValidator(
0398: new Validator() {
0399:
0400: public void validate(
0401: List args)
0402: throws InvalidArgumentException {
0403: final int size = args
0404: .size();
0405: if (size > 1)
0406: throw new InvalidArgumentException(
0407: "Only one scaling algorithm at a time can be chosen");
0408: if (!scalingAlgorithms
0409: .contains(args
0410: .get(0)))
0411: throw new InvalidArgumentException(
0412: new StringBuffer(
0413: "The output format ")
0414: .append(
0415: args
0416: .get(0))
0417: .append(
0418: " is not permitted")
0419: .toString());
0420:
0421: }
0422: }).create())
0423: .withDescription(
0424: "name of the scaling algorithm, eeither one of average (a), filtered (f), bilinear (bil), nearest neigbhor (nn)")
0425: .withRequired(false).create();
0426:
0427: priorityOpt = optionBuilder.withShortName("p").withLongName(
0428: "thread_priority").withArgument(
0429: arguments.withName("thread_priority").withMinimum(0)
0430: .withMaximum(1).create()).withDescription(
0431: "priority for the underlying thread").withRequired(
0432: false).create();
0433:
0434: tileCacheSizeOpt = optionBuilder.withShortName("c")
0435: .withLongName("cache_size").withArgument(
0436: arguments.withName("c").withMinimum(0)
0437: .withMaximum(1).create())
0438: .withDescription("tile cache sized")
0439: .withRequired(false).create();
0440:
0441: cmdOpts.add(locationOpt);
0442: cmdOpts.add(tileDimOpt);
0443: cmdOpts.add(scaleFactorOpt);
0444: cmdOpts.add(scaleAlgorithmOpt);
0445: cmdOpts.add(numStepsOpt);
0446: cmdOpts.add(wildcardOpt);
0447: cmdOpts.add(priorityOpt);
0448: cmdOpts.add(tileCacheSizeOpt);
0449: cmdOpts.add(versionOpt);
0450: cmdOpts.add(helpOpt);
0451:
0452: optionsGroup = new GroupImpl(cmdOpts, "Options",
0453: "All the options", 0, 9);
0454:
0455: // /////////////////////////////////////////////////////////////////////
0456: //
0457: // Help Formatter
0458: //
0459: // /////////////////////////////////////////////////////////////////////
0460: final HelpFormatter cmdHlp = new HelpFormatter("| ", " ",
0461: " |", 75);
0462: cmdHlp.setShellCommand("OverviewsEmbedder");
0463: cmdHlp.setHeader("Help");
0464: cmdHlp.setFooter(new StringBuffer(
0465: "OverviewsEmbedder - GeoSolutions S.a.s (C) 2006 - v ")
0466: .append(OverviewsEmbedder.versionNumber).toString());
0467: cmdHlp
0468: .setDivider("|-------------------------------------------------------------------------|");
0469:
0470: cmdParser.setGroup(optionsGroup);
0471: cmdParser.setHelpOption(helpOpt);
0472: cmdParser.setHelpFormatter(cmdHlp);
0473: }
0474:
0475: /**
0476: * This method retiles the original image using a specified tile wiedth and
0477: * height.
0478: *
0479: * @param Original
0480: * image to be tiled or retiled.
0481: * @param tileWidth
0482: * Tile width.
0483: * @param tileHeight
0484: * Tile height.
0485: * @param tileGrdiOffseX
0486: * @param tileGrdiOffseY
0487: * @param interp
0488: * Interpolation method used.
0489: *
0490: * @return RenderedOp containing the chain to obtain the tiled image.
0491: */
0492: private ImageLayout tile(final int tileWidth, final int tileHeight,
0493: final int tileGrdiOffseX, final int tileGrdiOffseY,
0494: final Interpolation interp) {
0495:
0496: // //
0497: //
0498: // creating a new layout for this image
0499: // using tiling
0500: //
0501: // //
0502: ImageLayout layout = new ImageLayout();
0503:
0504: // //
0505: //
0506: // changing parameters related to the tiling
0507: //
0508: //
0509: // //
0510: layout.setTileGridXOffset(tileGrdiOffseX);
0511: layout.setTileGridYOffset(tileGrdiOffseY);
0512: layout.setValid(ImageLayout.TILE_GRID_X_OFFSET_MASK);
0513: layout.setValid(ImageLayout.TILE_GRID_Y_OFFSET_MASK);
0514: layout.setTileWidth(tileWidth);
0515: layout.setTileHeight(tileHeight);
0516: layout.setValid(ImageLayout.TILE_HEIGHT_MASK);
0517: layout.setValid(ImageLayout.TILE_WIDTH_MASK);
0518:
0519: return layout;
0520: }
0521:
0522: /**
0523: *
0524: * This method is a utlity method for setting various JAi wide hints we will
0525: * use here and afterwards.
0526: *
0527: *
0528: */
0529: private void settingJAIHints() {
0530:
0531: // //
0532: // Imageio caching behaviour in case it is ever needed.
0533: // //
0534: ImageIO.setUseCache(useImageIOCache);
0535:
0536: // //
0537: //
0538: // JAI cache fine tuning
0539: //
0540: // //
0541: final JAI jaiDef = JAI.getDefaultInstance();
0542: // setting the tile cache
0543: final TileCache cache = jaiDef.getTileCache();
0544: cache.setMemoryCapacity(tileCacheSize * 1024 * 1024);
0545: // setting JAI wide hints
0546: jaiDef.setRenderingHint(JAI.KEY_INTERPOLATION, this .interp);
0547: jaiDef.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0548: RenderingHints.VALUE_ANTIALIAS_ON);
0549: jaiDef.setRenderingHint(RenderingHints.KEY_RENDERING,
0550: RenderingHints.VALUE_RENDER_SPEED);
0551: jaiDef.setRenderingHint(JAI.KEY_CACHED_TILE_RECYCLING_ENABLED,
0552: Boolean.TRUE);
0553: // //
0554: //
0555: // tile factory and recycler
0556: //
0557: // //
0558: RecyclingTileFactory recyclingFactory = new RecyclingTileFactory();
0559: jaiDef.setRenderingHint(JAI.KEY_TILE_FACTORY, recyclingFactory);
0560: jaiDef
0561: .setRenderingHint(JAI.KEY_TILE_RECYCLER,
0562: recyclingFactory);
0563:
0564: // //
0565: //
0566: // border extender
0567: //
0568: // //
0569: jaiDef
0570: .setRenderingHint(JAI.KEY_BORDER_EXTENDER,
0571: borderExtender);
0572:
0573: }
0574:
0575: /**
0576: * This methods built up a RenderedOp for subsampling an image in order to
0577: * create various previes. I wanted to use the filtered subsample but It was
0578: * giving me problems in the native libraries therefore I am doing a two
0579: * steps downsampling:
0580: *
0581: * Step 1: low pass filtering.
0582: *
0583: * Step 2: Subsampling.
0584: *
0585: * @param src
0586: * Image to subsample.
0587: * @param scale
0588: * Scale factor.
0589: * @param interp
0590: * Interpolation method used.
0591: * @param tileHints
0592: * Hints provided.
0593: *
0594: * @return The subsampled RenderedOp.
0595: */
0596: private RenderedOp subsample(RenderedOp src) {
0597: // using filtered subsample operator to do a subsampling
0598: final ParameterBlockJAI pb = new ParameterBlockJAI(
0599: "filteredsubsample");
0600: pb.addSource(src);
0601: pb.setParameter("scaleX", new Integer(downsampleStep));
0602: pb.setParameter("scaleY", new Integer(downsampleStep));
0603: pb.setParameter("qsFilterArray", new float[] { 1.0f });
0604: pb.setParameter("Interpolation", interp);
0605: //remember to add the hint to avoid replacement of the original IndexColorModel
0606: //in future versions we might want to make this parametrix XXX TODO @task
0607: return JAI.create("filteredsubsample", pb,
0608: ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
0609: }
0610:
0611: public int getDownsampleStep() {
0612: return downsampleStep;
0613: }
0614:
0615: public void setDownsampleStep(int downsampleWH) {
0616: this .downsampleStep = downsampleWH;
0617: }
0618:
0619: public String getSourcePath() {
0620: return sourcePath;
0621: }
0622:
0623: public void setSourcePath(String sourcePath) {
0624: this .sourcePath = sourcePath;
0625:
0626: }
0627:
0628: public int getTileHeight() {
0629: return tileH;
0630: }
0631:
0632: public void setTileHeight(int tileHeight) {
0633: this .tileH = tileHeight;
0634: }
0635:
0636: public int getTileWidth() {
0637: return tileW;
0638: }
0639:
0640: public void setTileWidth(int tileWidth) {
0641: this .tileW = tileWidth;
0642: }
0643:
0644: /**
0645: * Creating the scale operation using the FilteredSubSample operation with a
0646: * null filter, which basically does not do any filtering. This is a hint I
0647: * found on the JAI mailing list, a SUN engineer suggested to use this
0648: * instead of scale since it uses a integer factor which is easier for the
0649: * library to handle than a float scale factor like Scale operation is
0650: * using.
0651: *
0652: * @param src
0653: * Source image to be scaled.
0654: * @param factor
0655: * Scale factor.
0656: * @param interpolation
0657: * Interpolation used.
0658: * @param hints
0659: * Hints provided to this method.
0660: *
0661: * @return The scaled image.
0662: */
0663: private RenderedOp filteredSubsample(RenderedImage src) {
0664: // using filtered subsample operator to do a subsampling
0665: final ParameterBlockJAI pb = new ParameterBlockJAI(
0666: "filteredsubsample");
0667: pb.addSource(src);
0668: pb.setParameter("scaleX", new Integer(downsampleStep));
0669: pb.setParameter("scaleY", new Integer(downsampleStep));
0670: pb.setParameter("qsFilterArray", lowPassFilter);
0671: pb.setParameter("Interpolation", interp);
0672: return JAI.create("filteredsubsample", pb);
0673: }
0674:
0675: /**
0676: * Creating the scale operation using the FilteredSubSample operation with a
0677: * null filter, which basically does not do any filtering. This is a hint I
0678: * found on the JAI mailing list, a SUN engineer suggested to use this
0679: * instead of scale since it uses a integer factor which is easier for the
0680: * library to handle than a float scale factor like Scale operation is
0681: * using.
0682: *
0683: * @param src
0684: * Source image to be scaled.
0685: * @param factor
0686: * Scale factor.
0687: * @param interpolation
0688: * Interpolation used.
0689: * @param hints
0690: * Hints provided to this method.
0691: *
0692: * @return The scaled image.
0693: */
0694: private RenderedOp scaleAverage(RenderedImage src) {
0695: // using filtered subsample operator to do a subsampling
0696: final ParameterBlockJAI pb = new ParameterBlockJAI(
0697: "SubsampleAverage");
0698: pb.addSource(src);
0699: pb.setParameter("scaleX", new Double(1.0 / downsampleStep));
0700: pb.setParameter("scaleY", new Double(1.0 / downsampleStep));
0701: return JAI.create("SubsampleAverage", pb);
0702: }
0703:
0704: public void setBorderExtender(BorderExtender borderExtender) {
0705: this .borderExtender = borderExtender;
0706: }
0707:
0708: public void setInterp(Interpolation interp) {
0709: this .interp = interp;
0710: }
0711:
0712: public void setTileCacheSize(long tileCacheSize) {
0713: this .tileCacheSize = tileCacheSize;
0714: }
0715:
0716: public void setUseImageIOCache(boolean useImageIOCache) {
0717: this .useImageIOCache = useImageIOCache;
0718: }
0719:
0720: public float[] getLowPassFilter() {
0721: return lowPassFilter;
0722: }
0723:
0724: public void setLowPassFilter(float[] lowPassFilter) {
0725: this .lowPassFilter = lowPassFilter;
0726: }
0727:
0728: public void run() {
0729: try {
0730:
0731: // //
0732: //
0733: // setting JAI wide hints
0734: //
0735: // //
0736: settingJAIHints();
0737: // final TCTool tool = new TCTool();
0738:
0739: // getting an image input stream to the file
0740: final File dir = new File(sourcePath);
0741: final File[] files;
0742: int numFiles = 1;
0743: StringBuffer message;
0744: if (dir.isDirectory()) {
0745: final FileFilter fileFilter = new WildcardFilter(
0746: wildcardString);
0747: files = dir.listFiles(fileFilter);
0748: numFiles = files.length;
0749: if (numFiles <= 0) {
0750: message = new StringBuffer("No files to process!");
0751: if (LOGGER.isLoggable(Level.FINE)) {
0752: LOGGER.fine(message.toString());
0753: }
0754: fireEvent(message.toString(), 100);
0755:
0756: }
0757:
0758: } else
0759: files = new File[] { dir };
0760:
0761: // /////////////////////////////////////////////////////////////////////
0762: //
0763: // Cycling over the features
0764: //
0765: // /////////////////////////////////////////////////////////////////////
0766: RenderedOp currentImage;
0767: ImageReader reader;
0768: ImageInputStream stream;
0769: Iterator it;
0770: ImageLayout layout;
0771: ImageOutputStream streamOut;
0772: RenderingHints newHints;
0773: ParameterBlock pbjRead;
0774: for (fileBeingProcessed = 0; fileBeingProcessed < numFiles; fileBeingProcessed++) {
0775:
0776: message = new StringBuffer("Managing file ").append(
0777: fileBeingProcessed).append(" of ").append(
0778: files[fileBeingProcessed]).append(" files");
0779: if (LOGGER.isLoggable(Level.FINE)) {
0780: LOGGER.fine(message.toString());
0781: }
0782: fireEvent(message.toString(),
0783: ((fileBeingProcessed * 100.0) / numFiles));
0784:
0785: if (getStopThread()) {
0786: message = new StringBuffer(
0787: "Stopping requested at file ").append(
0788: fileBeingProcessed).append(" of ").append(
0789: numFiles).append(" files");
0790: if (LOGGER.isLoggable(Level.FINE)) {
0791: LOGGER.fine(message.toString());
0792: }
0793: fireEvent(message.toString(),
0794: ((fileBeingProcessed * 100.0) / numFiles));
0795: return;
0796: }
0797:
0798: // //
0799: //
0800: // get a stream
0801: //
0802: //
0803: // //
0804: stream = ImageIO
0805: .createImageInputStream(files[fileBeingProcessed]);
0806: stream.mark();
0807:
0808: // //
0809: //
0810: // get a reader
0811: //
0812: //
0813: // //
0814: it = ImageIO.getImageReaders(stream);
0815: if (!it.hasNext()) {
0816:
0817: return;
0818: }
0819: reader = (ImageReader) it.next();
0820: stream.reset();
0821: stream.mark();
0822:
0823: // //
0824: //
0825: // set input
0826: //
0827: // //
0828: reader.setInput(stream);
0829: layout = null;
0830: // tiling the image if needed
0831: int actualTileW = reader.getTileWidth(0);
0832: int actualTileH = reader.getTileHeight(0);
0833: if (reader.isImageTiled(0) && (actualTileH != tileH)
0834: && (actualTileW != tileW) && tileH != -1
0835: && tileW != -1) {
0836:
0837: message = new StringBuffer("Retiling image ")
0838: .append(fileBeingProcessed);
0839: if (LOGGER.isLoggable(Level.FINE)) {
0840: LOGGER.fine(message.toString());
0841: }
0842: fireEvent(message.toString(),
0843: ((fileBeingProcessed * 100.0) / numFiles));
0844: layout = tile(tileW, tileH, 0, 0, interp);
0845: }
0846: stream.reset();
0847: reader.dispose();
0848:
0849: // //
0850: //
0851: // output image stream
0852: //
0853: // //
0854: streamOut = ImageIO
0855: .createImageOutputStream(files[fileBeingProcessed]);
0856: if (streamOut == null) {
0857: message = new StringBuffer(
0858: "Unable to acquire an ImageOutputStream for the file ")
0859: .append(files[fileBeingProcessed]
0860: .toString());
0861: if (LOGGER.isLoggable(Level.SEVERE)) {
0862: LOGGER.severe(message.toString());
0863: }
0864: fireEvent(message.toString(),
0865: ((fileBeingProcessed * 100.0) / numFiles));
0866: return;
0867: }
0868:
0869: // //
0870: //
0871: // Preparing to write the set of images. First of all I write
0872: // the first image `
0873: //
0874: // //
0875: // getting a writer for this reader
0876: ImageWriter writer = ImageIO.getImageWriter(reader);
0877: writer.setOutput(streamOut);
0878: writer
0879: .addIIOWriteProgressListener(writeProgressListener);
0880: ImageWriteParam param = writer.getDefaultWriteParam();
0881:
0882: // can we tile this image? (TIFF or JPEG2K)
0883: if (!(param.canWriteTiles())) {
0884: message = new StringBuffer(
0885: "This format do not support tiling!");
0886: if (LOGGER.isLoggable(Level.SEVERE)) {
0887: LOGGER.severe(message.toString());
0888: }
0889: fireEvent(message.toString(),
0890: ((fileBeingProcessed * 100.0) / numFiles));
0891: return;
0892: }
0893:
0894: // can we write a sequence for these images?
0895: if (!(writer.canInsertImage(1))) {
0896: message = new StringBuffer(
0897: "This format do not support overviews!");
0898: if (LOGGER.isLoggable(Level.SEVERE)) {
0899: LOGGER.severe(message.toString());
0900: }
0901: fireEvent(message.toString(),
0902: ((fileBeingProcessed * 100.0) / numFiles));
0903: return;
0904:
0905: }
0906:
0907: // //
0908: //
0909: // setting tiling on the first image using writing parameters
0910: //
0911: // //
0912: if (tileH != -1 & tileW != -1) {
0913: param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
0914: param.setTiling(tileW, tileH, 0, 0);
0915:
0916: } else {
0917: param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
0918: param.setTiling(actualTileW, actualTileH, 0, 0);
0919: }
0920:
0921: // //
0922: //
0923: // creating the image to use for the successive
0924: // subsampling
0925: //
0926: // //
0927:
0928: newHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,
0929: layout);
0930: pbjRead = new ParameterBlock();
0931: pbjRead
0932: .add(ImageIO
0933: .createImageInputStream(files[fileBeingProcessed]));
0934: pbjRead.add(new Integer(0));
0935: pbjRead.add(Boolean.FALSE);
0936: pbjRead.add(Boolean.FALSE);
0937: pbjRead.add(Boolean.FALSE);
0938: pbjRead.add(null);
0939: pbjRead.add(null);
0940: pbjRead.add(null);
0941: pbjRead.add(null);
0942: currentImage = JAI.create("ImageRead", pbjRead,
0943: newHints);
0944: message = new StringBuffer("Reaad original image ")
0945: .append(fileBeingProcessed);
0946: if (LOGGER.isLoggable(Level.FINE)) {
0947: LOGGER.fine(message.toString());
0948: }
0949: fireEvent(message.toString(),
0950: ((fileBeingProcessed * 100.0) / numFiles));
0951: for (overviewInProcess = 0; overviewInProcess < numSteps; overviewInProcess++) {
0952:
0953: if (overviewInProcess > 0) {
0954:
0955: // re-instantiate the current image from disk
0956: stream = ImageIO
0957: .createImageInputStream(files[fileBeingProcessed]);
0958: pbjRead = new ParameterBlock();
0959: pbjRead.add(stream);
0960: pbjRead.add(new Integer(overviewInProcess));
0961: pbjRead.add(Boolean.FALSE);
0962: pbjRead.add(Boolean.FALSE);
0963: pbjRead.add(Boolean.FALSE);
0964: pbjRead.add(null);
0965: pbjRead.add(null);
0966: pbjRead.add(null);
0967: pbjRead.add(null);
0968: currentImage = JAI.create("ImageRead", pbjRead,
0969: newHints);
0970:
0971: // //
0972: //
0973: // output image stream
0974: //
0975: // //
0976: streamOut = ImageIO
0977: .createImageOutputStream(files[fileBeingProcessed]);
0978:
0979: // //
0980: //
0981: // Preparing to write the set of images. First of all I
0982: // write
0983: // the first image `
0984: //
0985: // //
0986: // getting a writer for this reader
0987: writer = ImageIO.getImageWriter(reader);
0988: writer.setOutput(streamOut);
0989: writer
0990: .addIIOWriteProgressListener(writeProgressListener);
0991: param = writer.getDefaultWriteParam();
0992:
0993: }
0994:
0995: message = new StringBuffer("Subsampling step ")
0996: .append(overviewInProcess).append(
0997: " of image ").append(
0998: fileBeingProcessed);
0999: if (LOGGER.isLoggable(Level.FINE)) {
1000: LOGGER.fine(message.toString());
1001: }
1002: fireEvent(message.toString(),
1003: ((fileBeingProcessed * 100.0) / numFiles));
1004:
1005: // paranoiac check
1006: if (currentImage.getWidth() / downsampleStep <= 0
1007: || currentImage.getHeight()
1008: / downsampleStep <= 0)
1009: break;
1010:
1011: // subsampling the input image using the chosen algorithm
1012: if (scaleAlgorithm.equalsIgnoreCase("avg"))
1013: currentImage = scaleAverage(currentImage);
1014: else if (scaleAlgorithm.equalsIgnoreCase("filt"))
1015: currentImage = filteredSubsample(currentImage);
1016: if (scaleAlgorithm.equalsIgnoreCase("bil"))
1017: currentImage = bilinear(currentImage);
1018: if (scaleAlgorithm.equalsIgnoreCase("nn"))
1019: currentImage = subsample(currentImage);
1020: if (scaleAlgorithm.equalsIgnoreCase("bic"))
1021: currentImage = bicubic(currentImage);
1022:
1023: // write out
1024: writer.writeInsert(overviewInProcess + 1,
1025: new IIOImage(currentImage, null, null),
1026: param);
1027:
1028: message = new StringBuffer("Step ").append(
1029: overviewInProcess).append(" of image ")
1030: .append(fileBeingProcessed)
1031: .append(" done!");
1032: if (LOGGER.isLoggable(Level.FINE)) {
1033: LOGGER.fine(message.toString());
1034: }
1035: fireEvent(message.toString(),
1036: ((fileBeingProcessed * 100.0) / numFiles));
1037:
1038: // flushing cache
1039: JAI.getDefaultInstance().getTileCache().flush();
1040:
1041: // free everything
1042: streamOut.flush();
1043: streamOut.close();
1044: writer.dispose();
1045: currentImage.dispose();
1046: stream.close();
1047:
1048: }
1049: message = new StringBuffer("Done with image ")
1050: .append(fileBeingProcessed);
1051: if (LOGGER.isLoggable(Level.FINE)) {
1052: LOGGER.fine(message.toString());
1053: }
1054: fireEvent(message.toString(),
1055: (((fileBeingProcessed + 1) * 100.0) / numFiles));
1056:
1057: }
1058:
1059: } catch (IOException e) {
1060: fireException(e);
1061: }
1062:
1063: if (LOGGER.isLoggable(Level.FINE))
1064: LOGGER.fine("Done!!!");
1065:
1066: }
1067:
1068: /**
1069: * Performs a bilinear interpolation on the provided image.
1070: *
1071: * @param src
1072: * The source image.
1073: * @return The subsampled image.
1074: */
1075: private RenderedOp bilinear(RenderedOp src) {
1076: // using filtered subsample operator to do a subsampling
1077: final ParameterBlockJAI pb = new ParameterBlockJAI(
1078: "filteredsubsample");
1079: pb.addSource(src);
1080: pb.setParameter("scaleX", new Integer(downsampleStep));
1081: pb.setParameter("scaleY", new Integer(downsampleStep));
1082: pb.setParameter("qsFilterArray", new float[] { 1.0f });
1083: pb.setParameter("Interpolation", new InterpolationBilinear());
1084: return JAI.create("filteredsubsample", pb);
1085: }
1086:
1087: /**
1088: * Performs a bicubic interpolation on the provided image.
1089: *
1090: * @param src
1091: * The source image.
1092: * @return The subsampled image.
1093: */
1094: private RenderedOp bicubic(RenderedOp src) {
1095: // using filtered subsample operator to do a subsampling
1096: final ParameterBlockJAI pb = new ParameterBlockJAI(
1097: "filteredsubsample");
1098: pb.addSource(src);
1099: pb.setParameter("scaleX", new Integer(downsampleStep));
1100: pb.setParameter("scaleY", new Integer(downsampleStep));
1101: pb.setParameter("qsFilterArray", new float[] { 1.0f });
1102: pb.setParameter("Interpolation", new InterpolationBicubic(2));
1103: return JAI.create("filteredsubsample", pb);
1104: }
1105:
1106: /*
1107: * (non-Javadoc)
1108: *
1109: * @see it.geosolutions.utils.progress.ProcessingEventListener#getNotification(it.geosolutions.utils.progress.ProcessingEvent)
1110: */
1111: public void getNotification(ProcessingEvent event) {
1112: LOGGER.info(new StringBuffer("Progress is at ").append(
1113: event.getPercentage()).append("\n").append(
1114: "attached message is: ").append(event.getMessage())
1115: .toString());
1116:
1117: }
1118:
1119: public void exceptionOccurred(ExceptionEvent event) {
1120: LOGGER.log(Level.SEVERE, "An error occurred during processing",
1121: event.getException());
1122: }
1123:
1124: private boolean parseArgs(String[] args) {
1125: cmdLine = cmdParser.parseAndHelp(args);
1126: if (cmdLine != null && cmdLine.hasOption(versionOpt)) {
1127: LOGGER
1128: .fine(new StringBuffer(
1129: "OverviewsEmbedder - GeoSolutions S.a.s (C) 2006 - v")
1130: .append(OverviewsEmbedder.versionNumber)
1131: .toString());
1132: System.exit(1);
1133:
1134: } else if (cmdLine != null) {
1135: // ////////////////////////////////////////////////////////////////
1136: //
1137: // parsing command line parameters and setting up
1138: // Mosaic Index Builder options
1139: //
1140: // ////////////////////////////////////////////////////////////////
1141: sourcePath = (String) cmdLine.getValue(locationOpt);
1142:
1143: // tile dim
1144: if (cmdLine.hasOption(tileDimOpt)) {
1145: final String tileDim = (String) cmdLine
1146: .getValue(tileDimOpt);
1147: final String[] pairs = tileDim.split(",");
1148: tileW = Integer.parseInt(pairs[0]);
1149: tileH = Integer.parseInt(pairs[1]);
1150: }
1151: // //
1152: //
1153: // scale factor
1154: //
1155: // //
1156: final String scaleF = (String) cmdLine
1157: .getValue(scaleFactorOpt);
1158: downsampleStep = Integer.parseInt(scaleF);
1159:
1160: // //
1161: //
1162: // wildcard
1163: //
1164: // //
1165: if (cmdLine.hasOption(wildcardOpt))
1166: wildcardString = (String) cmdLine.getValue(wildcardOpt);
1167:
1168: // //
1169: //
1170: // scaling algorithm (default to nearest neighbour)
1171: //
1172: // //
1173: scaleAlgorithm = (String) cmdLine
1174: .getValue(scaleAlgorithmOpt);
1175: if (scaleAlgorithm == null)
1176: scaleAlgorithm = "nn";
1177:
1178: // //
1179: //
1180: // number of steps
1181: //
1182: // //
1183: numSteps = Integer.parseInt((String) cmdLine
1184: .getValue(numStepsOpt));
1185:
1186: // //
1187: //
1188: // Thread priority
1189: //
1190: // //
1191: // index name
1192: if (cmdLine.hasOption(priorityOpt))
1193: priority = Integer.parseInt((String) cmdLine
1194: .getValue(priorityOpt));
1195:
1196: // //
1197: //
1198: // Tile cache size
1199: //
1200: // //
1201: // index name
1202: if (cmdLine.hasOption(tileCacheSizeOpt)) {
1203: tileCacheSize = Integer.parseInt((String) cmdLine
1204: .getValue(tileCacheSizeOpt));
1205:
1206: }
1207: return true;
1208:
1209: }
1210: return false;
1211:
1212: }
1213:
1214: /**
1215: * This tool is designed to be used by the command line using this main
1216: * class but it can also be used from an GUI by using the setters and
1217: * getters.
1218: *
1219: * @param args
1220: * @throws IOException
1221: * @throws IllegalArgumentException
1222: * @throws InterruptedException
1223: */
1224: public static void main(String[] args)
1225: throws IllegalArgumentException, IOException,
1226: InterruptedException {
1227:
1228: // creating an overviews embedder
1229: final OverviewsEmbedder overviewsEmbedder = new OverviewsEmbedder();
1230: // adding the embedder itself as a listener
1231: overviewsEmbedder.addProcessingEventListener(overviewsEmbedder);
1232: // parsing input arguments
1233: if (overviewsEmbedder.parseArgs(args)) {
1234: // creating a thread to execute the request process, with the
1235: // provided priority
1236: final Thread t = new Thread(overviewsEmbedder,
1237: "OverviewsEmbedder");
1238: t.setPriority(overviewsEmbedder.priority);
1239: t.start();
1240: try {
1241: t.join();
1242: } catch (InterruptedException e) {
1243: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
1244: }
1245:
1246: } else if (LOGGER.isLoggable(Level.FINE))
1247: LOGGER
1248: .fine("Unable to parse command line arguments, exiting...");
1249:
1250: }
1251:
1252: /**
1253: * Sets the wildcar string to use.
1254: *
1255: * @param wildcardString
1256: * the wildcardString to set
1257: */
1258: public final void setWildcardString(String wildcardString) {
1259: this.wildcardString = wildcardString;
1260: }
1261: }
|