nl.b3p.imagetool.ImageTool.java Source code

Java tutorial

Introduction

Here is the source code for nl.b3p.imagetool.ImageTool.java

Source

/*
 * B3P Kaartenbalie is a OGC WMS/WFS proxy that adds functionality
 * for authentication/authorization, pricing and usage reporting.
 *
 * Copyright 2006, 2007, 2008 B3Partners BV
 *
 * This file is part of B3P Kaartenbalie.
 *
 * B3P Kaartenbalie 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.
 *
 * B3P Kaartenbalie 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 B3P Kaartenbalie.  If not, see <http://www.gnu.org/licenses/>.
 */
package nl.b3p.imagetool;

import com.sun.imageio.plugins.png.PNGMetadata;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geotools.geometry.jts.LiteShape;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.RendererUtilities;

public class ImageTool {

    private static final Log log = LogFactory.getLog(ImageTool.class);
    private BufferedImage bi;
    public static final String TIFF = "image/tiff";
    public static final String GIF = "image/gif";
    public static final String JPEG = "image/jpeg";
    public static final String PNG = "image/png";

    /**
     * Reads an image from an http input stream.
     *
     * @param method Apache HttpClient GetMethod object
     * @param mime String representing the mime type of the image.
     *
     * @return BufferedImage
     *
     * @throws Exception
     */
    // <editor-fold defaultstate="" desc="readImage(GetMethod method, String mime) method.">
    public static BufferedImage readImage(InputStream is, String mime) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int bytesRead = 0;
        byte[] buffer = new byte[2048];
        while (bytesRead != -1) {
            bytesRead = is.read(buffer, 0, buffer.length);
            if (bytesRead > 0) {
                baos.write(buffer, 0, bytesRead);
            }
        }
        ImageReader ir = null;
        BufferedImage i = null;
        try {
            if (mime.indexOf(";") != -1) {
                mime = mime.substring(0, mime.indexOf(";"));
            }
            String mimeType = getMimeType(mime);

            /* TODO: Kijken waarom er geen mime type meer binnenkomt. Wellicht door de
             * HttpClient vernieuwing in kaartenbalie ? */
            if (mimeType == null) {
                mimeType = "image/png";
            }

            if (mimeType == null) {
                log.error("Response from server not understood (mime = " + mime + "): " + baos.toString());
                throw new Exception(
                        "Response from server not understood (mime = " + mime + "): " + baos.toString());
            }

            ir = getReader(mimeType);
            if (ir == null) {
                log.error("no reader available for imageformat: "
                        + mimeType.substring(mimeType.lastIndexOf("/") + 1));
                throw new Exception("no reader available for imageformat: "
                        + mimeType.substring(mimeType.lastIndexOf("/") + 1));
            }
            //TODO Make smarter.. Possibly faster... But keep reporting!
            ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(baos.toByteArray()));
            ir.setInput(stream, true);
            i = ir.read(0);
            //if image is a png, has no alpha and has a tRNS then make that color transparent.
            if (!i.getColorModel().hasAlpha() && ir.getImageMetadata(0) instanceof PNGMetadata) {
                PNGMetadata metadata = (PNGMetadata) ir.getImageMetadata(0);
                if (metadata.tRNS_present) {
                    int alphaPix = (metadata.tRNS_red << 16) | (metadata.tRNS_green << 8) | (metadata.tRNS_blue);
                    BufferedImage tmp = new BufferedImage(i.getWidth(), i.getHeight(), BufferedImage.TYPE_INT_ARGB);
                    for (int x = 0; x < i.getWidth(); x++) {
                        for (int y = 0; y < i.getHeight(); y++) {
                            int rgb = i.getRGB(x, y);
                            rgb = (rgb & 0xFFFFFF) == alphaPix ? alphaPix : rgb;
                            tmp.setRGB(x, y, rgb);
                        }
                    }
                    i = tmp;
                }
            }
        } finally {
            if (ir != null) {
                ir.dispose();
            }
        }
        return i;
    }
    // </editor-fold>

    /**
     * First combines the given images to one image and then sends this image
     * back to the client.
     *
     * @param images BufferedImage array with the images tha have to be combined
     * and sent to the client.
     * @param mime String representing the mime type of the image.
     * @param dw DataWrapper object in which the request object is stored.
     *
     * @throws Exception
     */
    // <editor-fold defaultstate="" desc="writeImage(BufferedImage [] images, String mime, DataWrapper dw) method.">
    public static void writeImage(BufferedImage image, String mime, OutputStream os) throws Exception {

        String mimeType = getMimeType(mime);
        if (mimeType == null) {
            log.error("unsupported mime type: " + mime);
            throw new Exception("unsupported mime type: " + mime);
        }

        if (mime.equals(TIFF)) {
            writeTIFFImage(image, os);
        } else {
            writeOtherImage(image, os, mimeType.substring(mimeType.lastIndexOf("/") + 1));
        }
    }

    public static BufferedImage drawGeometries(BufferedImage bi, CombineImageSettings settings) throws Exception {
        int srid = 28992;
        if (settings.getSrid() != null) {
            srid = ((int) settings.getSrid());
        }
        int width = 500;
        int height = 500;
        ImageBbox imbbox = settings.getRequestBbox();
        if (imbbox.getWidth() != null && imbbox.getHeight() != null) {
            width = imbbox.getWidth();
            height = imbbox.getHeight();
        } else {
            Integer[] hw = settings.getWidthAndHeightFromUrls();
            if (hw != null && hw.length == 2) {
                width = hw[0];
                height = hw[1];
            }
        }
        Bbox bbox = imbbox.getBbox();
        if (bbox == null) {
            bbox = settings.getBbox();
        }
        if (bbox == null) {
            bbox = settings.getBboxFromUrls();
        }
        if (bbox == null) {
            log.error("No BBOX found");
            throw new Exception("Can't find bbox in settings or URL");
        }
        return drawGeometries(bi, settings, srid, bbox, width, height);

    }

    public static BufferedImage drawGeometries(BufferedImage bi, CombineImageSettings settings, int srid, Bbox bbox,
            int width, int height) throws Exception {
        List wktGeoms = settings.getWktGeoms();
        if (wktGeoms == null || wktGeoms.size() <= 0) {
            return bi;
        }
        BufferedImage newBufIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
        //        BufferedImage newBufIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D gbi = newBufIm.createGraphics();
        gbi.drawImage(bi, 0, 0, null);
        for (int i = 0; i < wktGeoms.size(); i++) {
            gbi.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
            CombineImageWkt ciw = (CombineImageWkt) wktGeoms.get(i);
            Color color = settings.getDefaultWktGeomColor();
            if (ciw.getColor() != null) {
                color = ciw.getColor();
            }
            gbi.setColor(color);
            String wktGeom = ciw.getWktGeom();
            Geometry geom = geometrieFromText(wktGeom, srid);
            Shape shape = createImage(geom, srid, bbox, width, height);
            Point centerPoint = null;
            if (geom instanceof Polygon) {
                gbi.fill(shape);
            } else if (geom instanceof com.vividsolutions.jts.geom.Point) {
                centerPoint = calculateCenter(shape, srid, bbox, width, height);
                gbi.draw(new Ellipse2D.Double(centerPoint.getX(), centerPoint.getY(), 4, 4));
            } else {
                gbi.setStroke(new BasicStroke(3));
                gbi.draw(shape);
            }
            if (ciw.getLabel() != null) {
                if (centerPoint == null) {
                    centerPoint = calculateCenter(shape, srid, bbox, width, height);
                }
                gbi.setColor(Color.black);
                gbi.drawString(ciw.getLabel(), (float) centerPoint.getX(), (float) centerPoint.getY());
            }
        }
        gbi.dispose();
        return newBufIm;
    }

    private static Point calculateCenter(Shape shape, int srid, Bbox bbox, int width, int height) throws Exception {
        Point centerPoint = new Point();
        double x = shape.getBounds2D().getCenterX();
        double y = shape.getBounds2D().getCenterY();
        centerPoint.setLocation(x, y);
        centerPoint = transformToScreen(centerPoint, srid, bbox, width, height);
        return centerPoint;
    }

    public static Shape createImage(Geometry geometrie, int bboxSrid, Bbox bbox, int width, int height)
            throws Exception {
        ReferencedEnvelope re = new ReferencedEnvelope(bbox.getMinx(), bbox.getMaxx(), bbox.getMiny(),
                bbox.getMaxy(), CRS.decode("EPSG:" + bboxSrid));
        AffineTransform transform = RendererUtilities.worldToScreenTransform(re, new Rectangle(width, height));
        LiteShape ls = new LiteShape(geometrie, transform, false);
        return ls;
    }

    public static Point transformToScreen(Point source, int bboxSrid, Bbox bbox, int width, int height)
            throws Exception {
        ReferencedEnvelope re = new ReferencedEnvelope(bbox.getMinx(), bbox.getMaxx(), bbox.getMiny(),
                bbox.getMaxy(), CRS.decode("EPSG:" + bboxSrid));
        AffineTransform transform = RendererUtilities.worldToScreenTransform(re, new Rectangle(width, height));
        Point result = new Point();
        transform.transform(source, result);
        return result;
    }

    public static Geometry geometrieFromText(String wktgeom, int srid) {
        WKTReader wktreader = new WKTReader(new GeometryFactory(new PrecisionModel(), srid));
        try {
            Geometry geom = wktreader.read(wktgeom);
            return geom;
        } catch (ParseException p) {
            log.error("Can't create geomtry from wkt: " + wktgeom, p);
        }
        return null;
    }

    // </editor-fold>
    /**
     * Writes a TIFF image to the outputstream.
     *
     * @param bufferedImage BufferedImage created from the given images.
     * @param dw DataWrapper object in which the request object is stored.
     *
     * @throws Exception
     */
    // <editor-fold defaultstate="" desc="getOnlineData(DataWrapper dw, ArrayList urls, boolean overlay, String REQUEST_TYPE) method.">
    private static void writeTIFFImage(BufferedImage bufferedImage, OutputStream os) throws Exception {
        //log.info("Writing TIFF using ImageIO.write");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, "tif", baos);
        os.write(baos.toByteArray());
    }
    // </editor-fold>

    /**
     * Writes a JPEG, GIF or PNG image to the outputstream.
     *
     * @param bufferedImage BufferedImage created from the given images.
     * @param dw DataWrapper object in which the request object is stored.
     * @param extension String with the extension of the file
     *
     * @throws Exception
     */
    // <editor-fold defaultstate="" desc="writeOtherImage(BufferedImage bufferedImage, DataWrapper dw, String extension) method.">
    private static void writeOtherImage(BufferedImage bufferedImage, OutputStream os, String extension)
            throws Exception {
        //log.info("Writing JPG, GIF or PNG using ImageIO.write");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
        ImageIO.write(bufferedImage, extension, ios);
        os.write(baos.toByteArray());
        ios.flush();
        ios.close();
    }
    // </editor-fold>

    /**
     * Method which handles the combining of the images. This method redirects
     * to the right method for the different images, since not every image can
     * be combined in the same way.
     *
     * @param images BufferedImage array with the images tha have to be
     * combined.
     * @param mime String representing the mime type of the image.
     * @param width the width of the result image (or null if the width of the
     * first image must be used)
     * @param height the height of the result image (or null if the width of the
     * first image must be used)
     * @return BufferedImage
     */
    public static BufferedImage combineImages(List<ReferencedImage> images, String mime, Integer width,
            Integer height) throws Exception {
        if (mime.equals(JPEG)) {
            return combineJPGImages(images, width, height);
        } else {
            return combineOtherImages(images, width, height);
        }
    }
    // </editor-fold>

    /**
     * Combines JPG images. Combining JPG images is different from the other
     * image types since JPG has to use an other imageType:
     * BufferedImage.TYPE_INT_RGB.
     *
     * @param images the referenced Images
     * @param width the width of the result image (or null if the width of the
     * first image must be used)
     * @param height the height of the result image (or null if the width of the
     * first image must be used)
     *
     * @return BufferedImage
     */
    private static BufferedImage combineJPGImages(List<ReferencedImage> images, Integer width, Integer height)
            throws Exception {
        if (images.get(0) != null) {
            BufferedImage bi = images.get(0).getImage();
            if (width == null) {
                width = bi.getWidth();
            }
            if (height == null) {
                height = bi.getHeight();
            }
        }

        BufferedImage newBufIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D gbi = newBufIm.createGraphics();
        for (ReferencedImage image : images) {
            drawImage(gbi, image);
            image.dispose();
        }
        return newBufIm;
    }

    /**
     * Combines GIF, TIFF or PNG images. Combining these images is different
     * from the JPG image types since these has to use an other imageType:
     * BufferedImage.TYPE_INT_ARGB_PRE.
     *
     * @param images the referenced Images
     * @param width the width of the result image (or null if the width of the
     * first image must be used)
     * @param height the height of the result image (or null if the width of the
     * first image must be used)
     *
     * @return BufferedImage
     */
    private static BufferedImage combineOtherImages(List<ReferencedImage> images, Integer width, Integer height)
            throws Exception {
        if (images != null && images.get(0) != null) {
            BufferedImage bi = images.get(0).getImage();
            //if no height / width use the height/widht of the first image.
            if (height == null) {
                height = bi.getHeight();
            }
            if (width == null) {
                width = bi.getWidth();
            }
        }

        BufferedImage newBufIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);

        Graphics2D gbi = newBufIm.createGraphics();
        gbi.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

        if (images != null) {
            for (ReferencedImage image : images) {
                drawImage(gbi, image);
                image.dispose();
            }
        }

        return newBufIm;
    }

    /**
     * Draws the image to the graphics object.
     *
     * @param gbi graphics object
     * @param image the referenced image.
     */
    private static void drawImage(Graphics2D gbi, ReferencedImage image) throws Exception {
        if (image.getAlpha() != null) {
            gbi.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, image.getAlpha()));
        } else {
            gbi.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        }
        Integer x = image.getX();
        Integer y = image.getY();
        if (x == null) {
            x = 0;
        }
        if (y == null) {
            y = 0;
        }
        if (image.getHeight() != null && image.getWidth() != null) {
            gbi.drawImage(image.getImage(), x, y, image.getWidth(), image.getHeight(), null);
        } else {
            gbi.drawImage(image.getImage(), x, y, null);
        }
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported MIME types to check if a
     * certain MIME is supported.
     *
     * @param mime String with the MIME to find.
     *
     * @return a String with the found MIME or null if no MIME was found.
     */
    // <editor-fold defaultstate="" desc="getMimeType(String mime) method.">
    public static String getMimeType(String mime) {
        /*Crap ESRI, image/jpg is not a content type, needs to be image/jpeg*/
        if ("image/jpg".equalsIgnoreCase(mime)) {
            mime = "image/jpeg";
        }
        String[] mimeTypes = ImageIO.getReaderMIMETypes();
        for (int i = 0; i < mimeTypes.length; i++) {
            if (mimeTypes[i].equalsIgnoreCase(mime)) {
                return mimeTypes[i];
            }
        }
        return null;
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image readers to check
     * if a there is a reader which handles the specified MIME.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageReader which can handle the specified MIME or null if no
     * reader was found.
     */
    // <editor-fold defaultstate="" desc="getReader(String mime) method.">
    private static ImageReader getReader(String mime) {
        if (mime.equals(JPEG) || mime.equals(PNG)) {
            return getJPGOrPNGReader(mime);
        } else {
            return getGIFOrTIFFReader(mime);
        }
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image readers to check
     * if a there is a reader which handles the specified MIME. This method
     * checks spe- cifically for JPG or PNG images because Sun's Java supports
     * two kind of readers for these particular formats. And because one of
     * these readers doesn't function well, we need to be sure we have the right
     * reader.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageReader which can handle the specified MIME or null if no
     * reader was found.
     */
    // <editor-fold defaultstate="" desc="getJPGOrPNGReader(String mime) method.">
    private static ImageReader getJPGOrPNGReader(String mime) {
        Iterator it = ImageIO.getImageReadersByMIMEType(mime);
        ImageReader imTest = null;
        String name = null;
        while (it.hasNext()) {
            imTest = (ImageReader) it.next();
            name = imTest.getClass().getPackage().getName();
            String generalPackage = name.substring(0, name.lastIndexOf("."));
            if (generalPackage.equalsIgnoreCase("com.sun.media.imageioimpl.plugins")) {
                continue;
            }
        }
        //log.info("Using ImageReader: " + name);
        return imTest;
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image readers to check
     * if a there is a reader which handles the specified MIME. This method
     * checks spe- cifically for GIF or TIFF images.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageReader which can handle the specified MIME or null if no
     * reader was found.
     */
    // <editor-fold defaultstate="" desc="getGIFOrTIFFReader(String mime) method.">
    private static ImageReader getGIFOrTIFFReader(String mime) {
        Iterator it = ImageIO.getImageReadersByMIMEType(mime);
        ImageReader imTest = null;
        String name = null;
        while (it.hasNext()) {
            imTest = (ImageReader) it.next();
            name = imTest.getClass().getPackage().getName();
        }
        //log.info("Using ImageReader: " + name);
        return imTest;
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image writers to check
     * if a there is a writers which handles the specified MIME.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageWriter which can handle the specified MIME or null if no
     * writer was found.
     */
    // <editor-fold defaultstate="" desc="getWriter(String mime) method.">
    private ImageWriter getWriter(String mime) {
        if (mime.equals(JPEG) || mime.equals(PNG)) {
            return getJPGOrPNGWriter(mime);
        } else {
            return getGIFOrTIFFWriter(mime);
        }
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image writers to check
     * if a there is a writers which handles the specified MIME. This method
     * checks spe- cifically for JPG or PNG images because Sun's Java supports
     * two kind of writers for these particular formats. And because one of
     * these writers doesn't function well, we need to be sure we have the right
     * writers.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageWriter which can handle the specified MIME or null if no
     * writer was found.
     */
    // <editor-fold defaultstate="" desc="getJPGOrPNGWriter(String mime) method.">
    private ImageWriter getJPGOrPNGWriter(String mime) {
        Iterator it = ImageIO.getImageReadersByMIMEType(mime);
        ImageWriter imTest = null;
        while (it.hasNext()) {
            imTest = (ImageWriter) it.next();
            String name = imTest.getClass().getPackage().getName();
            String generalPackage = name.substring(0, name.lastIndexOf("."));
            if (generalPackage.equalsIgnoreCase("com.sun.media.imageioimpl.plugins")) {
                continue;
            }
        }
        return imTest;
    }
    // </editor-fold>

    /**
     * Private method which seeks through the supported image writers to check
     * if a there is a writers which handles the specified MIME. This method
     * checks spe- cifically for GIF or TIFF images.
     *
     * @param mime String with the MIME to find.
     *
     * @return ImageWriter which can handle the specified MIME or null if no
     * writer was found.
     */
    // <editor-fold defaultstate="" desc="getGIFOrTIFFWriter(String mime) method.">
    private ImageWriter getGIFOrTIFFWriter(String mime) {
        Iterator it = ImageIO.getImageReadersByMIMEType(mime);
        ImageWriter imTest = null;
        while (it.hasNext()) {
            imTest = (ImageWriter) it.next();
        }
        return imTest;
    }

    /**
     *
     */
    public static BufferedImage changeColor(BufferedImage im, Color color, Color newColor) {
        for (int x = 0; x < im.getWidth(); x++) {
            for (int y = 0; y < im.getHeight(); y++) {
                if (im.getRGB(x, y) == color.getRGB()) {
                    im.setRGB(x, y, newColor.getRGB());
                }
            }
        }
        return im;
    }
    // </editor-fold>
}