On 16 January 2012 18:23, Jan Martin <[email protected]> wrote: > Hi all, > > I am looking for a java coder to add an option to DeepZoomTiler.jar: > > -zoomlevels 12,11,10,9 > to generate only the zoom levels one really wants. In this case: 12,11,10 > and 9. > Without it behavior should remain unchanged. > > Right now all zoomlevels are created all the time. > And I delete later. A waste of resources. > > Any one interested in this? > > DeepZoomTiler.jar source is here: > https://github.com/mstandio/SaladoConverter/tree/master/DeepZoomTiler > > Thanks, > Jan > > -- > http://www.DIY-streetview.org > > -- > You received this message because you are subscribed to the Google Groups > "Hugin and other free panoramic software" group. > A list of frequently asked questions is available at: > http://wiki.panotools.org/Hugin_FAQ > To post to this group, send email to [email protected] > To unsubscribe from this group, send email to > [email protected] > For more options, visit this group at > http://groups.google.com/group/hugin-ptx
Well, I made some changes which hopefully implements the demanded functionality, but it's completely untested. I didn't even compile it, because there were some junit errors in some of the other salado modules. Attached is the edited source file. Lukas -- You received this message because you are subscribed to the Google Groups "Hugin and other free panoramic software" group. A list of frequently asked questions is available at: http://wiki.panotools.org/Hugin_FAQ To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/hugin-ptx
package deepzoomtiler; /* Copyright 2009 Zephyr Renner This file is part of EquirectangulartoCubic.java. EquirectangulartoCubic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. EquirectangulartoCubic 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 General Public License for more details. You should have received a copy of the GNU General Public License along with EquirectangulartoCubic. If not, see http://www.gnu.org/licenses/. This code is modified from DeepJZoom, courtesy of Glenn Lawrence, which is also licensed under the GPL. Thank you! */ import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.io.IOException; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Iterator; import java.awt.image.BufferedImage; import java.awt.Graphics2D; import java.awt.RenderingHints; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.imageio.*; import javax.imageio.stream.*; import com.sun.media.jai.codec.FileSeekableStream; /** * @author Glenn Lawrence */ public class DeepZoomTiler { static final String help = "\nDeepZoomTiler v1.4 \n\n Usage: \n\n" + "java [-java_options] -jar path/to/DeepZoomTiler.jar [-options] [args...]\n" + "For a list of java options try: java -help or java -X for a list of less\n" + "common options. Loading large images for conversion takes a lot of RAM so\n" + "you will find the -Xmx option useful to raise Java's maximum heap size.\n" + "The -Xmx command is followed immediately by an integer specifying RAM size\n" + "and a unit indicator. For example, -Xmx1024m means to use 1024 megabytes.\n" + "If you see an error about heap size, then you will need to increase this \n" + "value.\n\n" + " Basic usage example for the jar file:\n\n" + "java -Xmx1024m -jar path/to/DeepZoomTiler.jar path/to/directory/of/images/\n" + "This will generate a folder of tiles beside the input directory or file \n" + "with 'tiles_' prepended onto the name. So in the basic example above, \n" + "the output files would be in path/to/directory/of/tiles_images/.\n" + "\n" + " Options:\n\n" + "-overlap: number of pixels of overlap added around the tiles. A value\n" + "\tof 1 causes the tiles to be as the size of the tileSize para-\n" + "\t-meter plus 1. The tiles around the edge of the input image\n" + "\twill NOT have overlap added to their outside edge or edges in\n" + "\tthe case of corner tiles.\n" + "\tDefault is 1. \n\n" + "-quality: output JPEG compression. Value must be between 0.0 and 1.0.\n" + "\t0.0 is maximum compression, lowest quality, smallest file.\n" + "\t1.0 is least compression, highest quality, tlargest file.\n" + "\tDefault is 0.8.\n\n" + "-tilesize: target pixel tile size. Tiling starts at the top left \n" + "\tof an image, so tiles at the right and bottom to the image\n" + "\tmay not be this width or height, respectively, unless the\n" + "\tinput image's size is divisible by the tileSize. The tileSize\n" + "\tdoes NOT include the overlap. Overlap pixels are add to the\n" + "\tdimensions of the tile.\n" + "\tDefault is 512.\n\n" + "-outputdir or -o: the output directory for the converted images. It\n" + "\tneed not exist. Default is a folder next to the input folder\n" + "\tor file, with 'tiles_' prepended to the name of the input\n" + "\t(input files will have the extension removed). \n\n" + "-simpleoutput or -s: '_tiles' parent directory for output files is not\n" + "\tcreated. Both .xml file and folder containing tiles are saved\n" + "\tdirectly into output folder.\n\n" + "-zoomlevels: generate only the specified zoom levels.\n" + "\tthis parameter is optional. When ommited, all zoom levels are generated.\n" + "\tTo generate multiple zoom levels, pass a comma separated list\n\n" + "-verbose or -v: makes the utility more 'chatty' during processing. \n\n" + "-debug: print various debugging messages during processing. \n\n" + " Arguments:\n\n" + "The arguments following any options are the input images or folders.\n" + "(or both) If there are multiple input folders or images, each should\n" + "be separated by a space. Input folders will not be NOT be recursed.\n" + "Only images immediately inside the folder will be processed.\n" + "All inputs will be processed into the one output directory,\n" + "so general usage is to process one folder containing multiples images\n" + "or to process one singe image file. \n"; private enum CmdParseState { DEFAULT, OUTPUTDIR, TILESIZE, OVERLAP, QUALITY, ZOOMLEVELS, INPUTFILE } // The following can be overriden/set by the indicated command line arguments static boolean showHelp = false; // -help | -h static int tileSize = 512; // -tilesize static int tileOverlap = 1; // -overlap static float quality = 0.8f; // -quality (0.0 to 1.0) static File outputDir = null; // -outputdir | -o static boolean simpleoutput = false; // -simpleoutput | -s static boolean verboseMode = false; // -verbose static boolean debugMode = false; // -debug static ArrayList<File> inputFiles = new ArrayList<File>(); // must follow all other args static ArrayList<File> outputFiles = new ArrayList<File>(); static ArrayList<Integer> zoomLevels = null; /** * @param args the command line arguments */ public static void main(String[] args) { try { try { parseCommandLine(args); if (showHelp) { System.out.println(help); return; } //when output file is given, output are folders with names of input file in that directory if (outputFiles.size() == 1) { outputDir = outputFiles.get(0); outputFiles.clear(); if (!outputDir.exists() || !outputDir.isDirectory()) { if (!outputDir.mkdir()) { throw new IOException("Unable to create directory: " + outputDir); } } Iterator<File> itr = inputFiles.iterator(); while (itr.hasNext()) { File inputFile = itr.next(); String fileName = inputFile.getName(); String nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.')); if (simpleoutput) { outputFiles.add(outputDir); } else { File outputFile = createDir(outputDir, "tiles_" + nameWithoutExtension); outputFiles.add(outputFile); } } } // default location for output files is folder beside input files with the name of the input file if (outputFiles.isEmpty()) { Iterator<File> itr = inputFiles.iterator(); while (itr.hasNext()) { File inputFile = itr.next(); File parentFile = inputFile.getAbsoluteFile().getParentFile(); String fileName = inputFile.getName(); String nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.')); if (simpleoutput) { outputFiles.add(parentFile); } else { File outputFile = createDir(parentFile, "tiles_" + nameWithoutExtension); outputFiles.add(outputFile); } } } if (debugMode) { System.out.printf("tileSize=%d ", tileSize); System.out.printf("tileOverlap=%d ", tileOverlap); System.out.printf("quality=%.2f ", quality); } } catch (Exception e) { System.out.println("Invalid command: " + e.getMessage()); System.out.println("For avaible options type: -help or -h"); return; } System.setProperty("com.sun.media.jai.disableMediaLib", "true"); // it probably can be problematic in non-admin accounts // java -D com.sun.media.jai.disableMediaLib=true YourApp for (int i = 0; i < inputFiles.size(); i++) { processImageFile(inputFiles.get(i), outputFiles.get(i)); } } catch (IOException e) { e.printStackTrace(); } } /** * Process the command line arguments * @param args the command line arguments */ private static void parseCommandLine(String[] args) throws Exception { CmdParseState state = CmdParseState.DEFAULT; for (int count = 0; count < args.length; count++) { String arg = args[count]; switch (state) { case DEFAULT: if (arg.equals("-h") || arg.equals("-help")) { showHelp = true; return; } else if (arg.equals("-verbose")) { verboseMode = true; } else if (arg.equals("-debug")) { verboseMode = true; debugMode = true; } else if (arg.equals("-simpleoutput") || arg.equals("-s")) { simpleoutput = true; } else if (arg.equals("-outputdir") || arg.equals("-o")) { state = CmdParseState.OUTPUTDIR; } else if (arg.equals("-tilesize")) { state = CmdParseState.TILESIZE; } else if (arg.equals("-overlap")) { state = CmdParseState.OVERLAP; } else if (arg.equals("-quality")) { state = CmdParseState.QUALITY; } else if (arg.equals("-zoomlevels")) { state = CmdParseState.ZOOMLEVELS; } else { state = CmdParseState.INPUTFILE; } break; case OUTPUTDIR: outputFiles.add(new File(args[count])); state = CmdParseState.DEFAULT; break; case TILESIZE: tileSize = Integer.parseInt(args[count]); state = CmdParseState.DEFAULT; break; case OVERLAP: tileOverlap = Integer.parseInt(args[count]); state = CmdParseState.DEFAULT; break; case QUALITY: float qtmp = Float.parseFloat(args[count]); if (qtmp < 0 || qtmp > 1) { throw new Exception("-quality"); } quality = qtmp; state = CmdParseState.DEFAULT; break; case ZOOMLEVELS: zoomLevels = new ArrayList<Integer>(); String[] splited = args[count].split(","); for (String str : splited) { zoomLevels.add(Integer.parseInt(str)); } state = CmdParseState.DEFAULT; break; } if (state == CmdParseState.INPUTFILE) { File inputFile = new File(arg); if (!inputFile.exists()) { throw new FileNotFoundException("Missing input: " + inputFile.getPath()); } ArrayList<String> exts = new ArrayList<String>(); exts.add("bmp"); exts.add("jpg"); exts.add("jpeg"); exts.add("png"); exts.add("gif"); exts.add("tif"); exts.add("tiff"); if (inputFile.isDirectory()) { FilenameFilter select = new FileListFilter(exts); File[] files = inputFile.listFiles(select); java.util.List fileList = java.util.Arrays.asList(files); for (java.util.Iterator itr = fileList.iterator(); itr.hasNext();) { File f = (File) itr.next(); inputFiles.add((File) f); } } else { String fExt = inputFile.getAbsolutePath().substring(inputFile.getAbsolutePath().lastIndexOf(".") + 1); for (String ext : exts) { if (ext.equals(fExt)) { inputFiles.add(inputFile); break; } } } } } if (inputFiles.isEmpty()) { throw new Exception("No input files given."); } } /** * Process the given image file, producing its Deep Zoom output files * in a subdirectory of the given output directory. * @param inFile the file containing the image * @param outputDir the output directory */ private static void processImageFile(File inFile, File outputDir) throws IOException { if (verboseMode) { System.out.printf("Processing image: %s\n", inFile); } String fileName = inFile.getName(); String nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.')); String pathWithoutExtension = outputDir + File.separator + nameWithoutExtension; BufferedImage image = loadImage(inFile); int originalWidth = image.getWidth(); int originalHeight = image.getHeight(); double maxDim = Math.max(originalWidth, originalHeight); int nLevels = (int) Math.ceil(Math.log(maxDim) / Math.log(2)); if (verboseMode) { System.out.printf("nLevels=%d\n", nLevels); } // Delete any existing output files and folders for this image File descriptor = new File(pathWithoutExtension + ".xml"); if (descriptor.exists()) { if (verboseMode) { System.out.printf("Deleting descriptor: %s\n", descriptor); } deleteFile(descriptor); } File imgDir = new File(pathWithoutExtension); if (imgDir.exists()) { if (verboseMode) { System.out.printf("Deleting directory: %s\n", imgDir); } deleteDir(imgDir); } imgDir = createDir(outputDir, nameWithoutExtension); System.out.printf("Writing into directory: %s\n", imgDir); Double width = new Double(originalWidth); Double height = new Double(originalHeight); if (zoomLevels == null) { for (int level = nLevels; level >= 0; level--) { createLevel(width, height, level, imgDir, image); } } else { for (Integer level : zoomLevels) { createLevel(width, height, level, imgDir, image); } } saveImageDescriptor(originalWidth, originalHeight, descriptor); } private static void createLevel(Double width, Double height, int level, File imgDir, BufferedImage image) throws IOException { int nCols = (int) Math.ceil(width / tileSize); int nRows = (int) Math.ceil(height / tileSize); if (debugMode) { System.out.printf("level=%d \t w/h=%.0f/%.0f \t cols/rows=%d/%d\n", level, width, height, nCols, nRows); } File dir = createDir(imgDir, Integer.toString(level)); for (int col = 0; col < nCols; col++) { for (int row = 0; row < nRows; row++) { BufferedImage tile = getTile(image, row, col); saveImageAtQuality(tile, dir + File.separator + col + '_' + row, quality); } } // Scale down image for next level width = Math.ceil(width / 2); height = Math.ceil(height / 2); if (width > 10 && height > 10) { // resize in stages to improve quality image = resizeImage(image, width * 1.66d, height * 1.66d); image = resizeImage(image, width * 1.33d, height * 1.33d); } image = resizeImage(image, width, height); } /** * Delete a file * @param path the path of the directory to be deleted */ private static void deleteFile(File file) throws IOException { if (!file.delete()) { throw new IOException("Failed to delete file: " + file); } } /** * Recursively deletes a directory * @param path the path of the directory to be deleted */ private static void deleteDir(File dir) throws IOException { if (!dir.isDirectory()) { deleteFile(dir); } else { for (File file : dir.listFiles()) { if (file.isDirectory()) { deleteDir(file); } else { deleteFile(file); } } if (!dir.delete()) { throw new IOException("Failed to delete directory: " + dir); } } } /** * Creates a directory * @param parent the parent directory for the new directory * @param name the new directory name */ private static File createDir(File parent, String name) throws IOException { assert (parent.isDirectory()); File result = new File(parent + File.separator + name); if (!(result.exists() || result.mkdir())) { throw new IOException("Unable to create directory: " + result); } return result; } /** * Loads image from file * @param file the file containing the image */ private static BufferedImage loadImage(File file) throws IOException { FileSeekableStream stream = null; BufferedImage result = null; try { stream = new FileSeekableStream(file); PlanarImage planarImage = JAI.create("stream", stream); //PlanarImage planarImage = JAI.create("fileload", file.getAbsolutePath()); result = planarImage.getAsBufferedImage(); } catch (Exception e) { e.printStackTrace(); throw new IOException("Cannot read image file: " + file.getAbsolutePath()); } finally { if (stream != null) { stream.close(); } } return result; } /** * Gets an image containing the tile at the given row and column * for the given image. * @param img - the input image from whihc the tile is taken * @param row - the tile's row (i.e. y) index * @param col - the tile's column (i.e. x) index */ private static BufferedImage getTile(BufferedImage img, int row, int col) { int x = col * tileSize - (col == 0 ? 0 : tileOverlap); int y = row * tileSize - (row == 0 ? 0 : tileOverlap); int w = tileSize + (col == 0 ? 1 : 2) * tileOverlap; int h = tileSize + (row == 0 ? 1 : 2) * tileOverlap; if (x + w > img.getWidth()) { w = img.getWidth() - x; } if (y + h > img.getHeight()) { h = img.getHeight() - y; } if (debugMode) { System.out.printf("getTile: row=%d, col=%d, x=%d, y=%d, w=%d, h=%d\n", row, col, x, y, w, h); } assert (w > 0); assert (h > 0); BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics2D g = result.createGraphics(); g.drawImage(img, 0, 0, w, h, x, y, x + w, y + h, null); return result; } /** * Returns resized image * NB - useful reference on high quality image resizing can be found here: * http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html * @param width the required width * @param height the frequired height * @param img the image to be resized */ private static BufferedImage resizeImage(BufferedImage img, double width, double height) { int w = (int) width; int h = (int) height; BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics2D g = result.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(img, 0, 0, w, h, 0, 0, img.getWidth(), img.getHeight(), null); //surprisingly this gives worse results //RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); //BufferedImage result = JAI.create("SubsampleAverage", img, width / (double) img.getWidth(), height / (double) img.getHeight(), qualityHints).getAsBufferedImage(); return result; } /** * Saves image to the given file * @param img the image to be saved * @param path the path of the file to which it is saved (less the extension) * @param quality the compression quality to use (0-1) */ private static void saveImageAtQuality(BufferedImage img, String path, float quality) throws IOException { File outputFile = new File(path + ".jpg"); Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); ImageWriter writer = (ImageWriter) iter.next(); ImageWriteParam iwp = writer.getDefaultWriteParam(); iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality(quality); FileImageOutputStream output = new FileImageOutputStream(outputFile); writer.setOutput(output); IIOImage image = new IIOImage(img, null, null); try { writer.write(null, image, iwp); } catch (IOException e) { throw new IOException("Unable to save image file: " + outputFile); } finally { if (writer != null) { writer.dispose(); } if (output != null) { output.close(); } } } /** * Write image descriptor XML file * @param width image width * @param height image height * @param file the file to which it is saved */ private static void saveImageDescriptor(int width, int height, File file) throws IOException { String xmlHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; String schemaName = "http://schemas.microsoft.com/deepzoom/2009"; ArrayList<String> lines = new ArrayList<String>(); lines.add(xmlHeader); lines.add("<Image TileSize=\"" + tileSize + "\" Overlap=\"" + tileOverlap + "\" Format=\"jpg\" ServerFormat=\"Default\" xmnls=\"" + schemaName + "\">"); lines.add("<Size Width=\"" + width + "\" Height=\"" + height + "\" />"); lines.add("</Image>"); saveText(lines, file); } /** * Saves strings as text to the given file * @param lines the image to be saved * @param file the file to which it is saved */ private static void saveText(ArrayList lines, File file) throws IOException { if (verboseMode) { System.out.printf("Writing file: %s\n", file); } FileOutputStream fos = null; try { fos = new FileOutputStream(file); PrintStream ps = new PrintStream(fos); for (int i = 0; i < lines.size(); i++) { ps.println((String) lines.get(i)); } } catch (IOException e) { throw new IOException("Unable to write to text file: " + file); } finally { if (fos != null) { fos.close(); } } } } class FileListFilter implements FilenameFilter { private ArrayList<String> extensions; public FileListFilter(ArrayList<String> extensions) { this.extensions = extensions; } public boolean accept(File directory, String filename) { if (extensions != null) { Iterator<String> itr = extensions.iterator(); while (itr.hasNext()) { String ext = (String) itr.next(); if (filename.toLowerCase().endsWith('.' + ext)) { return true; } } } return false; } }
