Java tutorial
/* * Copyright (C) 2008-2013, fluid Operations AG * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.fluidops.iwb.deepzoom; /** * Deep Zoom Converter * * A Java application for converting large image files into the tiled images * required by the Microsoft Deep Zoom format and is suitable for use with * Daniel Gasienica's OpenZoom library presently at http://openzoom.org * * This source code is provided in the form of a Java application, but is * designed to be adapted for use in a library. * * To simplify compliation, deployment and packaging I have provided the * original code in a single source file. * * Version: MPL 1.1/GPL 3/LGPL 3 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http: *www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is this Java package called DeepZoomConverter * * The Initial Developer of the Original Code is Glenn Lawrence. * Portions created by the Initial Developer are Copyright (c) 2007-2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Glenn Lawrence <glenn.c.lawrence@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 3 or later (the "GPL"), or * the GNU Lesser General Public License Version 3 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. */ import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import com.fluidops.util.GenUtil; import edu.umd.cs.findbugs.annotations.SuppressWarnings; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.Iterator; import java.util.Vector; import javax.imageio.ImageIO; /** * * @author Glenn Lawrence */ @SuppressWarnings(value = { "LI_LAZY_INIT_STATIC", "DM_DEFAULT_ENCODING" }, justification = "Checked") public class DZConvert { private static final Logger logger = Logger.getLogger(DZConvert.class.getName()); static final String xmlHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; static final String schemaName = "http://schemas.microsoft.com/deepzoom/2009"; private enum CmdParseState { DEFAULT, OUTPUTDIR, TILESIZE, OVERLAP, INPUTFILE }; static Boolean deleteExisting = true; static String tileFormat = "jpg"; // The following can be overriden/set by the indicated command line arguments static int tileSize = 256; // -tilesize static int tileOverlap = 1; // -overlap static File outputDir = null; // -outputdir or -o static Boolean verboseMode = false; // -verbose or -v static Boolean debugMode = false; // -debug static Vector<File> inputFiles = new Vector(); // must follow all other args /** * @param args the command line arguments */ public static void main2(String[] args) { try { try { parseCommandLine(args); if (outputDir == null) outputDir = new File("."); if (debugMode) { System.out.printf("tileSize=%d ", tileSize); System.out.printf("tileOverlap=%d ", tileOverlap); System.out.printf("outputDir=%s%n", outputDir.getPath()); } } catch (Exception e) { System.out.println("Invalid command line: " + e.getMessage()); return; } if (!outputDir.exists()) throw new FileNotFoundException("Output directory does not exist: " + outputDir.getPath()); if (!outputDir.isDirectory()) throw new FileNotFoundException("Output directory is not a directory: " + outputDir.getPath()); Iterator<File> itr = inputFiles.iterator(); while (itr.hasNext()) processImageFile(itr.next(), outputDir); } catch (IOException e) { logger.error(e.getMessage(), e); } } /** * @param args the command line arguments */ public static void main(String[] args) { int counter = 0; try { File inputFiles = new File("webapps/ROOT/wikipedia/source/"); for (File subDir : inputFiles.listFiles()) { for (File imgFile : subDir.listFiles()) { outputDir = new File("webapps/ROOT/wikipedia/dzimages/" + subDir.getName()); System.out.println(counter++ + ": " + imgFile); if (!outputDir.exists()) GenUtil.mkdir(outputDir); try { processImageFile(imgFile, outputDir); } catch (Exception e) { logger.error(e.getMessage(), e); logger.error("Could not handle file: " + imgFile); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } } /** * 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("-verbose") || arg.equals("-v")) verboseMode = true; else if (arg.equals("-debug")) { verboseMode = true; debugMode = 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 state = CmdParseState.INPUTFILE; break; case OUTPUTDIR: outputDir = new File(arg); state = CmdParseState.DEFAULT; break; case TILESIZE: tileSize = Integer.parseInt(arg); state = CmdParseState.DEFAULT; break; case OVERLAP: tileOverlap = Integer.parseInt(arg); state = CmdParseState.DEFAULT; break; default: break; } if (state == CmdParseState.INPUTFILE) { File inputFile = new File(arg); if (!inputFile.exists()) throw new FileNotFoundException("Missing input file: " + inputFile.getPath()); inputFiles.add(inputFile); } } if (inputFiles.size() == 0) 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 file: %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 (debugMode) 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 (deleteExisting) deleteFile(descriptor); else throw new IOException("File already exists in output dir: " + descriptor); } File imgDir = new File(pathWithoutExtension); if (imgDir.exists()) { if (deleteExisting) { if (debugMode) System.out.printf("Deleting directory: %s%n", imgDir); deleteDir(imgDir); } else throw new IOException("Image directory already exists in output dir: " + imgDir); } imgDir = createDir(outputDir, nameWithoutExtension + "_files"); double width = originalWidth; double height = originalHeight; for (int level = nLevels; level >= 0; level--) { int nCols = (int) Math.ceil(width / tileSize); int nRows = (int) Math.ceil(height / tileSize); if (debugMode) System.out.printf("level=%d w/h=%f/%f 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); saveImage(tile, dir + File.separator + col + '_' + row); } } // 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.66, height * 1.66); image = resizeImage(image, width * 1.33, height * 1.33); } image = resizeImage(image, width, height); } saveImageDescriptor(originalWidth, originalHeight, descriptor); } /** * Delete a file * @param file 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 dir 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.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 { BufferedImage result = null; try { result = ImageIO.read(file); } catch (Exception e) { throw new IOException("Cannot read image file: " + file); } 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, getType(img)); Graphics2D g = result.createGraphics(); g.drawImage(img, 0, 0, w, h, x, y, x + w, y + h, null); return result; } static int getType(BufferedImage img) { if (img.getType() == BufferedImage.TYPE_CUSTOM) return BufferedImage.TYPE_INT_ARGB_PRE; else return img.getType(); } /** * 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, getType(img)); 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); 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) */ private static void saveImage(BufferedImage img, String path) throws IOException { File outputFile = new File(path + "." + tileFormat); try { ImageIO.write(img, tileFormat, outputFile); } catch (IOException e) { throw new IOException("Unable to save image file: " + outputFile); } } /** * 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 { Vector lines = new Vector(); lines.add(xmlHeader); lines.add("<Image TileSize=\"" + tileSize + "\" Overlap=\"" + tileOverlap + "\" Format=\"" + tileFormat + "\" ServerFormat=\"Default\" xmlns=\"" + 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(Vector lines, File file) throws IOException { PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream(file); ps = new PrintStream(fos); for (int i = 0; i < lines.size(); i++) ps.println((String) lines.elementAt(i)); } catch (IOException e) { throw new IOException("Unable to write to text file: " + file); } finally { IOUtils.closeQuietly(ps); } } }