org.scilla.converter.JAIConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.scilla.converter.JAIConverter.java

Source

/*
 * scilla
 *
 * Copyright (C) 2001  R.W. van 't Veer
 *
 * This program 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 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

package org.scilla.converter;

import java.io.FileOutputStream;
import java.util.*;

import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import java.awt.image.renderable.ParameterBlock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.scilla.*;
import org.scilla.util.*;

/**
 * The <STRONG>incomplete</STRONG> "Java Advanced Imaging" Converter.
 * Currently this converter only supports the <CODE>size</CODE>
 * parameter.
 *
 * @author R.W. van 't Veer
 * @version $Revision: 1.23 $
 */
public class JAIConverter implements Converter {
    private static Log log = LogFactory.getLog(JAIConverter.class);
    private static final Config config = Config.getInstance();

    /** parameter name to force the use of this converter */
    public final static String THIS_CONVERTER_PARAMETER = "jai";

    /** max runners configuration key */
    public static final String MAX_RUNNERS_KEY = "converters.jai.runners.sem";
    /** allow only X concurrent conversions */
    private static Semaphore sem = null;
    /** default maximum concurrent runners */
    private static int maxRunners = 2;
    static {
        // get maxRunners from scilla configuration
        try {
            maxRunners = Integer.parseInt(config.getString(MAX_RUNNERS_KEY));
        } catch (Exception ex) {
            log.warn(MAX_RUNNERS_KEY + " not available, defaulting to: " + maxRunners);
        }
        // initialized semaphore
        sem = new Semaphore(maxRunners);
    }

    Request request = null;
    String outputFile = null;

    volatile String errorMessage = null;
    volatile int exitValue = 0; // 0 means success
    volatile boolean finished = false;
    volatile boolean started = false;

    static final String[] inputTypeList = new String[] { "image/gif", "image/jpeg", "image/pjpeg", "image/png",
            "image/tiff", "image/x-ms-bmp", "image/x-portable-anymap", "image/x-portable-graymap" };
    static final String[] outputTypeList = new String[] { "image/jpeg", "image/pjpeg", "image/png", "image/tiff",
            "image/x-ms-bmp", "image/x-portable-anymap", "image/x-portable-graymap" };
    static final String[] parameterList = new String[] { THIS_CONVERTER_PARAMETER, Request.OUTPUT_TYPE_PARAMETER,
            "scale", "crop", "rotate", "negate" };
    static Map typeMap = new HashMap();
    static {
        typeMap.put("image/gif", "gif");
        typeMap.put("image/jpeg", "jpeg");
        typeMap.put("image/pjpeg", "jpeg");
        typeMap.put("image/png", "png");
        typeMap.put("image/tiff", "tiff");
        typeMap.put("image/x-ms-bmp", "bmp");
        typeMap.put("image/x-portable-anymap", "pnm");
        typeMap.put("image/x-portable-graymap", "pgm");
    }

    public void convert() {
        started = true;
        try {
            sem.decr();
            log.debug("converter started");

            // lets hope JAI will recognized the input type
            PlanarImage img = JAI.create("fileload", request.getInputFile());

            // loop through parameters
            for (Iterator it = request.getParameters().iterator(); it.hasNext();) {
                RequestParameter rp = (RequestParameter) it.next();

                // outputtype is handled outside this loop
                // thisconverter is skipped
                if (rp.key.equals(Request.OUTPUT_TYPE_PARAMETER) || rp.key.equals(THIS_CONVERTER_PARAMETER)) {
                    continue;
                }
                img = handleConversion(img, rp);
            }

            // recode to output format
            String type = (String) typeMap.get(request.getOutputType());
            FileOutputStream out = new FileOutputStream(outputFile);
            PlanarImage nimg = JAI.create("encode", img, out, type, null);
            img.dispose();
            nimg.dispose();
        } catch (Exception ex) {
            log.warn("conversion failed", ex);
            exitValue = 1;
            errorMessage = ex.getMessage();
        } finally {
            log.debug("converter finished");
            sem.incr();
        }
        finished = true;
    }

    public boolean exitSuccess() {
        if (!finished) {
            throw new IllegalStateException();
        }
        return exitValue == 0;
    }

    public String getErrorMessage() {
        if (!finished) {
            throw new IllegalStateException();
        }
        return errorMessage;
    }

    public void setOutputFile(String fn) {
        if (started) {
            throw new IllegalStateException();
        }
        outputFile = fn;
    }

    public String getOutputFile() {
        return outputFile;
    }

    public boolean hasFinished() {
        return finished;
    }

    public boolean canConvert(Request req) {
        boolean flag;

        // can handle input?
        final String inType = req.getInputType();
        flag = false;
        for (int i = 0; i < inputTypeList.length; i++) {
            if (inType.equals(inputTypeList[i])) {
                flag = true;
                break;
            }
        }
        if (!flag) {
            return false;
        }

        // can handle output?
        final String outType = req.getOutputType();
        flag = false;
        for (int i = 0; i < outputTypeList.length; i++) {
            if (outType.equals(outputTypeList[i])) {
                flag = true;
                break;
            }
        }
        if (!flag) {
            return false;
        }

        // supports all parameters?
        Iterator it = req.getParameterKeys().iterator();
        while (it.hasNext()) {
            String key = (String) it.next();
            flag = false;
            for (int i = 0; i < parameterList.length; i++) {
                if (key.equals(parameterList[i])) {
                    flag = true;
                }
            }
            if (!flag) {
                return false;
            }
        }

        return true;
    }

    public void setRequest(Request req) {
        request = req;
    }

    /**
     * Handle step in conversion.
     * @param img source image
     * @param rp conversion parameter
     * @return result image
     */
    PlanarImage handleConversion(PlanarImage img, RequestParameter rp) {
        PlanarImage nimg = null;
        if (rp.key.equals("scale")) {
            nimg = scale(img, new GeometryParameter(rp.val));
        } else if (rp.key.equals("crop")) {
            nimg = crop(img, new GeometryParameter(rp.val));
        } else if (rp.key.equals("rotate")) {
            nimg = rotate(img, rp.val);
        } else if (rp.key.equals("negate")) {
            nimg = negate(img);
        } else {
            // TODO should throw exception here!
            log.warn("handleConversion: param '" + rp.key + "' NOT YET IMPLEMENTED");
        }

        // drop old version
        img.dispose();

        return nimg;
    }

    /**
     * Scale image complying to ImageMagick geometry format.
     * Format: <CODE>WxH[+-]X[+-]Y[%][!][&lt;&gt;]</CODE>
     * @param img source image
     * @param geom geometry conversion parameter
     * @return result image
     */
    PlanarImage scale(PlanarImage img, GeometryParameter geom) {
        float w = geom.width;
        float h = geom.height != 0 ? geom.height : geom.width;
        int iw = img.getWidth();
        int ih = img.getHeight();

        if (geom.hasOption('<') && w < iw && h < ih) {
            return img;
        }
        if (geom.hasOption('>') && w > iw && h > ih) {
            return img;
        }

        if (geom.hasOption('%')) {
            w = (float) w / 100;
            h = (float) h / 100;
        } else {
            w = (float) w / iw;
            h = (float) h / ih;
        }

        if (!geom.hasOption('!')) {
            // keep ratio
            if (w < h) {
                h = w;
            } else {
                w = h;
            }
        }

        float x = geom.x;
        float y = geom.y;

        ParameterBlock pars = new ParameterBlock();
        pars.addSource(img);
        pars.add(w);
        pars.add(h);
        pars.add(x);
        pars.add(y);

        return JAI.create("scale", pars);
    }

    /**
     * Crop image.
     * Format: <CODE>WxH[+-]X[+-]Y</CODE>
     * @param img source image
     * @param geom geometry conversion parameter
     * @return result image
     */
    PlanarImage crop(PlanarImage img, GeometryParameter geom) {
        ParameterBlock pars = new ParameterBlock();
        pars.addSource(img);
        pars.add((float) geom.x);
        pars.add((float) geom.y);
        pars.add((float) geom.width);
        pars.add((float) geom.height);
        return JAI.create("crop", pars);
    }

    /**
     * Rotate image.
     * @param img source image
     * @param val rotation description; format: <tt>D+X+Y</tt>
     * @return result image
     */
    PlanarImage rotate(PlanarImage img, String val) {
        // determine degrees
        double d = 0;
        int p1 = val.indexOf('+');
        if (p1 == -1) {
            d = Double.parseDouble(val);
        } else {
            d = Double.parseDouble(val.substring(0, p1));
        }
        // determine rotation point
        float x = 0;
        float y = 0;
        int p2 = val.indexOf('+', p1 + 1);
        if (p1 == -1) {
            x = img.getWidth() / 2;
            y = img.getHeight() / 2;
        } else if (p2 == -1) {
            x = Float.parseFloat(val.substring(p1 + 1));
            y = x;
        } else {
            x = Float.parseFloat(val.substring(p1 + 1, p2));
            y = Float.parseFloat(val.substring(p2 + 1));
        }
        if (log.isDebugEnabled()) {
            log.debug("rotate " + d + "+" + x + "+" + y);
        }

        ParameterBlock pars = new ParameterBlock();
        pars.addSource(img);
        pars.add(x);
        pars.add(y);
        pars.add((float) Math.toRadians(d));
        return JAI.create("rotate", pars);
    }

    /**
     * Negate image.
     * @param img source image
     * @return result image
     */
    PlanarImage negate(PlanarImage img) {
        ParameterBlock pars = new ParameterBlock();
        pars.addSource(img);
        return JAI.create("not", pars);
    }
}

/**
 * Class for mapping ImageMagick geometry format.
 */
class GeometryParameter {
    int width;
    int height;
    int x;
    int y;
    String options;

    /**
     * Parse ImageMagick like geometry format.
     */
    GeometryParameter(String in) {
        int i;
        char[] d = in.toCharArray();
        StringBuffer b;

        do {
            // width
            b = new StringBuffer();
            for (i = 0; i < d.length && Character.isDigit(d[i]); i++) {
                b.append(d[i]);
            }
            if (b.length() > 0) {
                width = Integer.parseInt(b.toString());
            }

            // height
            if (i >= d.length) {
                break;
            }
            if (d[i] == 'x' || d[i] == 'X') {
                b = new StringBuffer();
                for (i++; i < d.length && Character.isDigit(d[i]); i++) {
                    b.append(d[i]);
                }
                if (b.length() > 0) {
                    height = Integer.parseInt(b.toString());
                }
            }

            // x
            if (i >= d.length) {
                break;
            }
            if (d[i] == '+' || d[i] == '-') {
                b = new StringBuffer();
                if (d[i] == '-') {
                    b.append(d[i]);
                }
                for (i++; i < d.length && Character.isDigit(d[i]); i++) {
                    b.append(d[i]);
                }
                if (b.length() > 0) {
                    x = Integer.parseInt(b.toString());
                }
            }

            // y
            if (i >= d.length) {
                break;
            }
            if (d[i] == '+' || d[i] == '-') {
                b = new StringBuffer();
                if (d[i] == '-') {
                    b.append(d[i]);
                }
                for (i++; i < d.length && Character.isDigit(d[i]); i++) {
                    b.append(d[i]);
                }
                y = Integer.parseInt(b.toString());
            }

            // options
            if (i < d.length) {
                options = new String(d, i, d.length - i);
            }
        } while (false); // only ones
    }

    boolean hasOption(char c) {
        return options != null && options.indexOf(c) != -1;
    }
}