org.fcrepo.localservices.imagemanip.ImageManipulation.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.localservices.imagemanip.ImageManipulation.java

Source

/* The contents of this file are subject to the license and copyright terms
 * detailed in the license directory at the root of the source tree (also 
 * available online at http://fedora-commons.org/license/).
 */

/**
 * Image manipulations (with the exception of the watermarking function)
 * are handled by ImageJ, a Java API written for image processing.
 *
 * Image encoding and decoding is handled by JAI, the Java Advanced Imaging
 * API, with the exception of GIF encoding, which is handled by ImageJ.
 *
 *  ImageJ Information:
 *
 *  Rasband, W.S., ImageJ, National Institutes of Health, Bethesda,
 *  Maryland, USA, http://rsb.info.nih.gov/ij/, 1997-2003.
 *
 *    The GifEncoder portion of ImageJ is copyrighted below:
 *
 *  Transparency handling and variable bit size courtesy of Jack Palevich.
 *
 *  Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *
 *  Visit the ACME Labs Java page for up-to-date versions of this and other
 *  fine Java utilities: http://www.acme.com/java/
 *
 */
package org.fcrepo.localservices.imagemanip;

import ij.ImagePlus;
import ij.io.FileInfo;
import ij.io.GifEncoder;
import ij.process.ImageProcessor;
import ij.process.MedianCut;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.media.jai.JAI;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;

import com.sun.media.jai.codec.BMPEncodeParam;
import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageEncodeParam;
import com.sun.media.jai.codec.JPEGEncodeParam;
import com.sun.media.jai.codec.MemoryCacheSeekableStream;
import com.sun.media.jai.codec.PNGEncodeParam;
import com.sun.media.jai.codec.TIFFEncodeParam;

/**
 * ImageManipulation is a Java servlet that takes a URL of an image as a param
 * and based on other given parameters, can perform a variety of image related
 * manipulations on the object.
 * 
 * <p>After the image is manipulated, it is then sent back as an image/type
 * object to the calling parent, most often a browser or an HTML img tag.
 * 
 * @author Theodore Serbinski
 */
public class ImageManipulation extends HttpServlet {

    private static final long serialVersionUID = 1L;

    private String inputMimeType;

    private boolean alreadyConvertedToRGB = false;

    private final MultiThreadedHttpConnectionManager cManager = new MultiThreadedHttpConnectionManager();

    /**
     * Method automatically called by browser to handle image manipulations.
     * 
     * @param req
     *        Browser request to servlet res Response sent back to browser after
     *        image manipulation
     * @throws IOException
     *         If an input or output exception occurred ServletException If a
     *         servlet exception occurred
     */
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        System.setProperty("java.awt.headless", "true");
        // collect all possible parameters for servlet
        String url = req.getParameter("url");
        String op = req.getParameter("op");
        String newWidth = req.getParameter("newWidth");
        String brightAmt = req.getParameter("brightAmt");
        String zoomAmt = req.getParameter("zoomAmt");
        String wmText = req.getParameter("wmText");
        String cropX = req.getParameter("cropX");
        String cropY = req.getParameter("cropY");
        String cropWidth = req.getParameter("cropWidth");
        String cropHeight = req.getParameter("cropHeight");
        String convertTo = req.getParameter("convertTo");
        if (convertTo != null) {
            convertTo = convertTo.toLowerCase();
        }
        try {
            if (op == null) {
                throw new ServletException("op parameter not specified.");
            }
            String outputMimeType;
            // get the image via url and put it into the ImagePlus processor.
            BufferedImage img = getImage(url);
            // do watermarking stuff
            if (op.equals("watermark")) {
                if (wmText == null) {
                    throw new ServletException("Must specify wmText.");
                }
                Graphics g = img.getGraphics();
                int fontSize = img.getWidth() * 3 / 100;
                if (fontSize < 10) {
                    fontSize = 10;
                }
                g.setFont(new Font("Lucida Sans", Font.BOLD, fontSize));
                FontMetrics fm = g.getFontMetrics();
                int stringWidth = (int) fm.getStringBounds(wmText, g).getWidth();
                int x = img.getWidth() / 2 - stringWidth / 2;
                int y = img.getHeight() - fm.getHeight();
                g.setColor(new Color(180, 180, 180));
                g.fill3DRect(x - 10, y - fm.getHeight() - 4, stringWidth + 20, fm.getHeight() + 12, true);
                g.setColor(new Color(100, 100, 100));
                g.drawString(wmText, x + 2, y + 2);
                g.setColor(new Color(240, 240, 240));
                g.drawString(wmText, x, y);
            }
            ImageProcessor ip = new ImagePlus("temp", img).getProcessor();
            // if the inputMimeType is image/gif, need to convert to RGB in any case
            if (inputMimeType.equals("image/gif")) {
                ip = ip.convertToRGB();
                alreadyConvertedToRGB = true;
            }
            // causes scale() and resize() to do bilinear interpolation
            ip.setInterpolate(true);
            if (!op.equals("convert")) {
                if (op.equals("resize")) {
                    ip = resize(ip, newWidth);
                } else if (op.equals("zoom")) {
                    ip = zoom(ip, zoomAmt);
                } else if (op.equals("brightness")) {
                    ip = brightness(ip, brightAmt);
                } else if (op.equals("watermark")) {
                    // this is now taken care of beforehand (see above)
                } else if (op.equals("grayscale")) {
                    ip = grayscale(ip);
                } else if (op.equals("crop")) {
                    ip = crop(ip, cropX, cropY, cropWidth, cropHeight);
                } else {
                    throw new ServletException("Invalid operation: " + op);
                }
                outputMimeType = inputMimeType;
            } else {
                if (convertTo == null) {
                    throw new ServletException("Neither op nor convertTo was specified.");
                }
                if (convertTo.equals("jpg") || convertTo.equals("jpeg")) {
                    outputMimeType = "image/jpeg";
                } else if (convertTo.equals("gif")) {
                    outputMimeType = "image/gif";
                } else if (convertTo.equals("tiff")) {
                    outputMimeType = "image/tiff";
                } else if (convertTo.equals("bmp")) {
                    outputMimeType = "image/bmp";
                } else if (convertTo.equals("png")) {
                    outputMimeType = "image/png";
                } else {
                    throw new ServletException("Invalid format: " + convertTo);
                }
            }
            res.setContentType(outputMimeType);
            BufferedOutputStream out = new BufferedOutputStream(res.getOutputStream());
            outputImage(ip, out, outputMimeType);
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
            res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    e.getClass().getName() + ": " + e.getMessage());
        }
    }

    /**
     * Gets and deserializes the image at the given URL into an Image object.
     * This method also sets the inputMimeType based on the HTTP Content-Type
     * header so that, if the image needs to be returned in it's original
     * format, the correct mime type can be sent in the response header. If the
     * input image is not a gif, jpg, tiff, bmp, or png (according to the http
     * response header), or some other kind of error occurs while reading the
     * stream from the remote host, a ServletException is thrown.
     * 
     * @param url
     *        The location of the input image.
     * @return Image The image object, if successful.
     * @throws Exception
     *         If any of the aforementioned problems occurs.
     */
    private BufferedImage getImage(String url) throws Exception {
        GetMethod get = null;
        try {
            cManager.getParams().setConnectionTimeout(20000);
            HttpClient client = new HttpClient(cManager);
            get = new GetMethod(url);
            get.setFollowRedirects(true);
            int resultCode = client.executeMethod(get);
            if (resultCode != 200) {
                throw new ServletException(
                        "Could not load image: " + url + ".  Errorcode " + resultCode + " from remote server.");
            }
            inputMimeType = get.getResponseHeader("Content-Type").getValue();
            if (inputMimeType.equals("image/gif") || inputMimeType.equals("image/jpeg")
                    || inputMimeType.equals("image/tiff") || inputMimeType.equals("image/bmp")
                    || inputMimeType.equals("image/x-ms-bmp") || inputMimeType.equals("image/x-bitmap")
                    || inputMimeType.equals("image/png")) {
                if (inputMimeType.endsWith("p")) {
                    inputMimeType = "image/bmp"; // windows bitmaps are most
                }
                // commonly supported with this
                // mime type, even though it's not
                // an IANA-registered image type
                return JAI.create("stream", new MemoryCacheSeekableStream(get.getResponseBodyAsStream()))
                        .getAsBufferedImage();
            } else {
                throw new ServletException("Source image was not a gif, png, " + "bmp, tiff, or jpg.");
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (get != null) {
                get.releaseConnection();
            }
        }

    }

    private void outputImage(ImageProcessor ip, OutputStream out, String outputMimeType) throws Exception {
        if (outputMimeType.equals("image/gif")) {
            if (!alreadyConvertedToRGB) {
                ip = ip.convertToRGB();
            }
            MedianCut mc = new MedianCut((int[]) ip.getPixels(), ip.getWidth(), ip.getHeight());
            ip = mc.convertToByte(256);
            ImagePlus imp = new ImagePlus("temp", ip);
            FileInfo fi = imp.getFileInfo();
            byte pixels[] = (byte[]) imp.getProcessor().getPixels();
            GifEncoder ge = new GifEncoder(fi.width, fi.height, pixels, fi.reds, fi.greens, fi.blues);
            ge.write(out);
        } else {
            ImageEncodeParam param = null;
            String format = null;
            if (outputMimeType.equals("image/jpeg")) {
                param = new JPEGEncodeParam();
                format = "JPEG";
            } else if (outputMimeType.equals("image/tiff")) {
                param = new TIFFEncodeParam();
                format = "TIFF";
            } else if (outputMimeType.equals("image/bmp")) {
                param = new BMPEncodeParam();
                format = "BMP";
            } else if (outputMimeType.equals("image/png")) {
                param = new PNGEncodeParam.RGB();
                format = "PNG";
            }
            ImageCodec.createImageEncoder(format, out, param).encode(JAI.create("AWTImage", ip.createImage()));
        }
    }

    /**
     * Resizes an image to the supplied new width in pixels. The height is
     * reduced proportionally to the new width.
     * 
     * @param ip
     *        The image to resize newWidth The width in pixels to resize the
     *        image to
     * @return The image resized
     */
    private ImageProcessor resize(ImageProcessor ip, String newWidth) {
        if (newWidth != null) {
            try {
                int width = Integer.parseInt(newWidth);

                if (width < 0) {
                    return ip;
                }

                int imgWidth = ip.getWidth();
                int imgHeight = ip.getHeight();

                ip = ip.resize(width, width * imgHeight / imgWidth);
            }
            // no need to do anything with number format exception since the servlet
            // returns only images; just return the original image
            catch (NumberFormatException e) {
            }
        }

        return ip;
    }

    /**
     * Zooms either in or out of an image by a supplied amount. The zooming
     * occurs from the center of the image.
     * 
     * @param ip
     *        The image to zoom zoomAmt The amount to zoom the image. 0 <
     *        zoomAmt < 1 : zoom out 1 = zoomAmt : original image 1 < zoomAmt :
     *        zoom in
     * @return The image zoomed
     */
    private ImageProcessor zoom(ImageProcessor ip, String zoomAmt) {
        if (zoomAmt != null) {
            try {
                float zoom = Float.parseFloat(zoomAmt);

                if (zoom < 0) {
                    return ip;
                }

                ip.scale(zoom, zoom);

                // if the image is being zoomed out, trim the extra whitespace around the image
                if (zoom < 1) {
                    int imgWidth = ip.getWidth();
                    int imgHeight = ip.getHeight();

                    // set a ROI around the image, minus the extra whitespace
                    ip.setRoi(Math.round(imgWidth / 2 - imgWidth * zoom / 2),
                            Math.round(imgHeight / 2 - imgHeight * zoom / 2), Math.round(imgWidth * zoom),
                            Math.round(imgHeight * zoom));
                    ip = ip.crop();
                }

            }

            // no need to do anything with number format exception since the servlet
            // returns only images; just return the original image
            catch (NumberFormatException e) {
            }
        }

        return ip;
    }

    /**
     * Adjusts the brightness of an image by a supplied amount.
     * 
     * @param ip
     *        The image to adjust the brightness of brightAmt The amount to
     *        adjust the brightness of the image by 0 <= brightAmt < 1 : darkens
     *        image 1 = brightAmt : original image 1 < brightAmt : brightens
     *        image
     * @return The image with brightness levels adjusted
     */
    private ImageProcessor brightness(ImageProcessor ip, String brightAmt) {
        if (brightAmt != null) {
            try {
                float bright = Float.parseFloat(brightAmt);

                if (bright < 0) {
                    return ip;
                }

                ip.multiply(bright);

            }

            // no need to do anything with number format exception since the servlet
            // returns only images; just return the original image
            catch (NumberFormatException e) {
            }
        }

        return ip;
    }

    /**
     * Converts an image to gray scale.
     * 
     * @param ip
     *        The image to convert to grayscale
     * @return The image converted to grayscale
     */
    private ImageProcessor grayscale(ImageProcessor ip) {
        ip = ip.convertToByte(true);

        return ip;
    }

    /**
     * Crops an image with supplied starting point and ending point.
     * 
     * @param ip
     *        The image to crop cropX The starting x position; x=0 corresponds
     *        to left side of image cropY The starting y position; y=0
     *        corresponds to top of image cropWidth The width of the crop,
     *        starting from the above x cropHeight The height of the crop,
     *        starting from the above y
     * @return The image cropped
     */
    public ImageProcessor crop(ImageProcessor ip, String cropX, String cropY, String cropWidth, String cropHeight) {
        if (cropX != null && cropY != null) {
            try {
                int x = Integer.parseInt(cropX);
                int y = Integer.parseInt(cropY);
                int width;
                int height;

                // if value for cropWidth is not given, just use the width of the image
                if (cropWidth != null) {
                    width = Integer.parseInt(cropWidth);
                } else {
                    width = ip.getWidth();
                }

                // if value for cropHeight is not given, just use the height of the image
                if (cropHeight != null) {
                    height = Integer.parseInt(cropHeight);
                } else {
                    height = ip.getHeight();
                }

                // if any value is negative, this causes ImageJ to explode, so just return
                if (x < 0 || y < 0 || width < 0 || height < 0) {
                    return ip;
                }

                ip.setRoi(x, y, width, height);
                ip = ip.crop();
            }

            // no need to do anything with number format exception since the servlet
            // returns only images; just return the original image
            catch (NumberFormatException e) {
            }
        }

        return ip;
    }
}