/* * Copyright (c) 2008 Los Alamos National Security, LLC. * * Los Alamos National Laboratory Research Library Digital Library Research & * Prototyping Team * * 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 gov.lanl.adore.djatoka.kdu; import gov.lanl.adore.djatoka.DjatokaEncodeParam; import gov.lanl.adore.djatoka.DjatokaException; import gov.lanl.adore.djatoka.ICompress; import gov.lanl.adore.djatoka.util.IOUtils; import gov.lanl.adore.djatoka.util.ImageProcessingUtils; import gov.lanl.adore.djatoka.util.ImageRecord; import gov.lanl.adore.djatoka.util.ImageRecordUtils; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import com.martiansoftware.jsap.CommandLineTokenizer; /** * Java bridge for kdu_compress application * * @author Ryan Chute * @author Kevin S. Clarke <a * href="mailto:ksclarke@gmail.com">ksclarke@gmail.com</a> */ public class KduCompressExe implements ICompress { private static Logger LOGGER = LoggerFactory .getLogger(KduCompressExe.class); private static boolean isWindows = false; private static String env; private static String exe; private static String[] envParams; /** Compress App Name "kdu_compress" */ public static final String KDU_COMPRESS_EXE = "kdu_compress"; /** UNIX/Linux Standard Out Path: "/dev/stdout" */ public static String STDOUT = "/dev/stdout"; static { env = System.getProperty("kakadu.home") + System.getProperty("file.separator"); exe = env + ((System.getProperty("os.name").startsWith("Win")) ? KDU_COMPRESS_EXE + ".exe" : KDU_COMPRESS_EXE); if (System.getProperty("os.name").startsWith("Mac")) { envParams = new String[] { "DYLD_LIBRARY_PATH=" + System.getProperty("DYLD_LIBRARY_PATH") }; } else if (System.getProperty("os.name").startsWith("Win")) { isWindows = true; } else if (System.getProperty("os.name").startsWith("Linux")) { envParams = new String[] { "LD_LIBRARY_PATH=" + System.getProperty("LD_LIBRARY_PATH") }; } else if (System.getProperty("os.name").startsWith("Solaris")) { envParams = new String[] { "LD_LIBRARY_PATH=" + System.getProperty("LD_LIBRARY_PATH") }; } LOGGER.debug("envParams: " + ((envParams != null) ? envParams[0] + " | " : "") + exe); } /** * Constructor which expects the following system properties to be defined * and exported. * <p/> * (Win/Linux/UNIX) LD_LIBRARY_PATH=$DJATOKA_HOME/lib/$DJATOKA_OS * <p/> * (Mac OS-X) DYLD_LIBRARY_PATH=$DJATOKA_HOME/lib/$DJATOKA_OS * * @throws Exception */ public KduCompressExe() throws Exception { env = System.getProperty("kakadu.home"); if (env == null) { LOGGER.error("kakadu.home is not defined"); System.err.println("kakadu.home is not defined"); throw new Exception("kakadu.home is not defined"); } } /** * Compress input BufferedImage using provided DjatokaEncodeParam * parameters. * * @param bi * in-memory image to be compressed * @param output * absolute file path for output file. * @param params * DjatokaEncodeParam containing compression parameters. * @throws DjatokaException */ public void compressImage(BufferedImage bi, String output, DjatokaEncodeParam params) throws DjatokaException { if (params == null) params = new DjatokaEncodeParam(); if (params.getLevels() == 0) params.setLevels(ImageProcessingUtils.getLevelCount(bi.getWidth(), bi.getHeight())); File in = null; try { in = IOUtils.createTempTiff(bi); compressImage(in.getAbsolutePath(), output, params); } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } catch (Exception e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } finally { if (in != null) in.delete(); } } /** * Compress input BufferedImage using provided DjatokaEncodeParam * parameters. * * @param bi * in-memory image to be compressed * @param output * OutputStream to serialize compressed image. * @param params * DjatokaEncodeParam containing compression parameters. * @throws DjatokaException */ public void compressImage(BufferedImage bi, OutputStream output, DjatokaEncodeParam params) throws DjatokaException { if (params == null) params = new DjatokaEncodeParam(); if (params.getLevels() == 0) params.setLevels(ImageProcessingUtils.getLevelCount(bi.getWidth(), bi.getHeight())); File in = null; File out = null; try { in = IOUtils.createTempTiff(bi); out = File.createTempFile("tmp", ".jp2"); compressImage(in.getAbsolutePath(), out.getAbsolutePath(), params); IOUtils.copyStream(new FileInputStream(out), output); } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } catch (Exception e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } if (in != null) in.delete(); if (out != null) out.delete(); } /** * Compress input using provided DjatokaEncodeParam parameters. * * @param input * InputStream containing TIFF image bitstream * @param output * absolute file path for output file. * @param params * DjatokaEncodeParam containing compression parameters. * @throws DjatokaException */ public void compressImage(InputStream input, String output, DjatokaEncodeParam params) throws DjatokaException { if (params == null) params = new DjatokaEncodeParam(); File inputFile; try { inputFile = File.createTempFile("tmp", ".tif"); inputFile.deleteOnExit(); IOUtils.copyStream(input, new FileOutputStream(inputFile)); if (params.getLevels() == 0) { ImageRecord dim = ImageRecordUtils.getImageDimensions(inputFile .getAbsolutePath()); params.setLevels(ImageProcessingUtils.getLevelCount( dim.getWidth(), dim.getHeight())); dim = null; } } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } compressImage(inputFile.getAbsolutePath(), output, params); if (inputFile != null) inputFile.delete(); } /** * Compress input using provided DjatokaEncodeParam parameters. * * @param input * InputStream containing TIFF image bitstream * @param output * OutputStream to serialize compressed image. * @param params * DjatokaEncodeParam containing compression parameters. * @throws DjatokaException */ public void compressImage(InputStream input, OutputStream output, DjatokaEncodeParam params) throws DjatokaException { if (params == null) params = new DjatokaEncodeParam(); File inputFile = null; try { inputFile = File.createTempFile("tmp", ".tif"); IOUtils.copyStream(input, new FileOutputStream(inputFile)); if (params.getLevels() == 0) { ImageRecord dim = ImageRecordUtils.getImageDimensions(inputFile .getAbsolutePath()); params.setLevels(ImageProcessingUtils.getLevelCount( dim.getWidth(), dim.getHeight())); dim = null; } } catch (IOException e1) { LOGGER.error("Unexpected file format; expecting uncompressed TIFF", e1); throw new DjatokaException( "Unexpected file format; expecting uncompressed TIFF"); } String out = STDOUT; File winOut = null; if (isWindows) { try { winOut = File.createTempFile("pipe_", ".jp2"); } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } out = winOut.getAbsolutePath(); } String command = getKduCompressCommand(inputFile.getAbsolutePath(), out, params); String[] cmdParts = CommandLineTokenizer.tokenize(command); Runtime rt = Runtime.getRuntime(); try { final Process process = rt.exec(cmdParts, envParams, new File(env)); if (out.equals(STDOUT)) { IOUtils.copyStream(process.getInputStream(), output); } else if (isWindows) { FileInputStream fis = new FileInputStream(out); IOUtils.copyStream(fis, output); fis.close(); } process.waitFor(); if (process != null) { String errorCheck = null; try { errorCheck = new String(IOUtils.getByteArray(process .getErrorStream())); } catch (Exception e1) { LOGGER.error(e1.getMessage(), e1); } process.getInputStream().close(); process.getOutputStream().close(); process.getErrorStream().close(); process.destroy(); if (errorCheck != null) throw new DjatokaException(errorCheck); } } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } catch (InterruptedException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } if (inputFile != null) inputFile.delete(); if (winOut != null) winOut.delete(); } /** * Compress input using provided DjatokaEncodeParam parameters. * * @param input * absolute file path for input file. * @param output * absolute file path for output file. * @param params * DjatokaEncodeParam containing compression parameters. * @throws DjatokaException */ public void compressImage(String input, String output, DjatokaEncodeParam params) throws DjatokaException { if (params == null) params = new DjatokaEncodeParam(); boolean tmp = false; File inputFile = null; if ((input.toLowerCase().endsWith(".tif") || input.toLowerCase().endsWith(".tiff") || ImageProcessingUtils .checkIfTiff(input)) && ImageProcessingUtils.isUncompressedTiff(input)) { LOGGER.debug("Processing TIFF: " + input); inputFile = new File(input); } else { if (LOGGER.isDebugEnabled() && (input.toLowerCase().endsWith(".tif") || input .toLowerCase().endsWith(".tiff"))) { LOGGER.debug(input + " : Is tiff? {} | Is uncompressed? {}", ImageProcessingUtils.checkIfTiff(input), ImageProcessingUtils.isUncompressedTiff(input)); } try { inputFile = IOUtils.createTempTiff(input); tmp = true; input = inputFile.getAbsolutePath(); } catch (Exception e) { throw new DjatokaException("Unrecognized file format: " + e.getMessage()); } } if (params.getLevels() == 0) { ImageRecord dim = ImageRecordUtils.getImageDimensions(inputFile .getAbsolutePath()); params.setLevels(ImageProcessingUtils.getLevelCount(dim.getWidth(), dim.getHeight())); dim = null; } File outFile = new File(output); String command = getKduCompressCommand( new File(input).getAbsolutePath(), outFile.getAbsolutePath(), params); String[] cmdParts = CommandLineTokenizer.tokenize(command); Runtime rt = Runtime.getRuntime(); try { final Process process = rt.exec(cmdParts, envParams, new File(env)); process.waitFor(); if (process != null) { String errorCheck = null; try { InputStream inStream = process.getErrorStream(); errorCheck = new String(IOUtils.getByteArray(inStream)); } catch (Exception e1) {} process.getInputStream().close(); process.getOutputStream().close(); process.getErrorStream().close(); process.destroy(); if (errorCheck != null && !errorCheck.equals("")) { throw new DjatokaException(errorCheck); } } } catch (IOException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } catch (InterruptedException e) { LOGGER.error(e.getMessage(), e); throw new DjatokaException(e.getMessage(), e); } finally { if (tmp) { inputFile.delete(); } } if (!outFile.getAbsolutePath().equals(STDOUT) && !outFile.exists()) { throw new DjatokaException( "Unknown error occurred during processing."); } } /** * Get kdu_compress command line for specified input, output, params. * * @param input * absolute file path for input file. * @param output * absolute file path for output file. * @param params * DjatokaEncodeParam containing compression parameters. * @return kdu_compress command line for specified input, output, params */ public static final String getKduCompressCommand(String input, String output, DjatokaEncodeParam params) { StringBuffer command = new StringBuffer(exe); command.append(" -quiet -i ").append( escape(new File(input).getAbsolutePath())); command.append(" -o ").append( escape(new File(output).getAbsolutePath())); command.append(" ").append(toKduCompressArgs(params)); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Compress command: {}", command.toString()); } return command.toString(); } private static final String escape(String path) { if (path.contains(" ")) { return "\"" + path + "\""; } return path; } private static String toKduCompressArgs(DjatokaEncodeParam params) { StringBuffer sb = new StringBuffer(); if (params.getRate() != null) sb.append("-rate ").append(params.getRate()).append(" "); else sb.append("-slope ").append(params.getSlope()).append(" "); if (params.getLevels() > 0) { sb.append("Clevels=").append(params.getLevels()).append(" "); } if (params.getPrecincts() != null) { sb.append("Cprecincts=").append(escape(params.getPrecincts())); sb.append(" "); } if (params.getLayers() > 0) sb.append("Clayers=").append(params.getLayers()).append(" "); if (params.getProgressionOrder() != null) sb.append("Corder=").append(params.getProgressionOrder()) .append(" "); if (params.getPacketDivision() != null) sb.append("ORGtparts=").append(params.getPacketDivision()) .append(" "); if (params.getCodeBlockSize() != null) sb.append("Cblk=").append(escape(params.getCodeBlockSize())) .append(" "); sb.append("ORGgen_plt=").append((params.getInsertPLT()) ? "yes" : "no") .append(" "); sb.append("Creversible=") .append((params.getUseReversible()) ? "yes" : "no").append(" "); if (params.getJP2ColorSpace() != null && !params.getJP2ColorSpace().isEmpty()) { sb.append("-jp2_space ").append(params.getJP2ColorSpace()); sb.append(' '); } return sb.toString(); } }