org.structr.web.common.ImageHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.web.common.ImageHelper.java

Source

/**
 * Copyright (C) 2010-2016 Structr GmbH
 *
 * This file is part of Structr <http://structr.org>.
 *
 * Structr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Structr 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Structr.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.structr.web.common;

import com.mortennobel.imagescaling.ResampleOp;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.structr.common.error.FrameworkException;
import org.structr.web.entity.Image;
import org.structr.util.Base64;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils;
import org.structr.common.SecurityContext;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.property.PropertyMap;
import static org.structr.web.common.FileHelper.setFileData;

/**
 *
 *
 */

import org.structr.dynamic.File;

public abstract class ImageHelper extends FileHelper {

    private static final Logger logger = Logger.getLogger(ImageHelper.class.getName());
    //private static Thumbnail tn        = new Thumbnail();

    //~--- methods --------------------------------------------------------

    /**
     * Create a new image node from the given image data
     *
     * @param securityContext
     * @param imageStream
     * @param contentType
     * @param imageType defaults to Image.class if null
     * @param name
     * @param markAsThumbnail
     * @return image
     * @throws FrameworkException
     * @throws IOException
     */
    public static Image createImage(final SecurityContext securityContext, final InputStream imageStream,
            final String contentType, final Class<? extends Image> imageType, final String name,
            final boolean markAsThumbnail) throws FrameworkException, IOException {

        return createImage(securityContext, IOUtils.toByteArray(imageStream), contentType, imageType, name,
                markAsThumbnail);

    }

    /**
     * Create a new image node from the given image data
     *
     * @param securityContext
     * @param imageData
     * @param contentType
     * @param imageType defaults to Image.class if null
     * @param name
     * @param markAsThumbnail
     * @return image
     * @throws FrameworkException
     * @throws IOException
     */
    public static Image createImage(final SecurityContext securityContext, final byte[] imageData,
            final String contentType, final Class<? extends Image> imageType, final String name,
            final boolean markAsThumbnail) throws FrameworkException, IOException {

        PropertyMap props = new PropertyMap();

        props.put(AbstractNode.type, imageType == null ? Image.class.getSimpleName() : imageType.getSimpleName());
        props.put(Image.isThumbnail, markAsThumbnail);
        props.put(AbstractNode.name, name);

        Image newImage = StructrApp.getInstance(securityContext).create(imageType, props);

        if (imageData != null && imageData.length > 0) {

            setFileData(newImage, imageData, contentType);

        }

        return newImage;

    }

    /**
     * Write image data to the given image node and set checksum and size.
     *
     * @param img
     * @param imageData
     * @param contentType
     * @throws FrameworkException
     * @throws IOException
     */
    public static void setImageData(final Image img, final byte[] imageData, final String contentType)
            throws FrameworkException, IOException {

        setFileData(img, imageData, contentType);

    }

    public static Thumbnail createThumbnail(final Image originalImage, final int maxWidth, final int maxHeight) {

        return createThumbnail(originalImage, maxWidth, maxHeight, false);

    }

    public static Thumbnail createThumbnail(final Image originalImage, final int maxWidth, final int maxHeight,
            final boolean crop) {

        // String contentType = (String) originalImage.getProperty(Image.CONTENT_TYPE_KEY);
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final Thumbnail tn = new Thumbnail();

        try (final InputStream in = originalImage.getInputStream()) {

            if (in == null) {

                logger.log(Level.FINE, "InputStream of original image {0} ({1}) is null",
                        new Object[] { originalImage.getName(), originalImage.getId() });
                return null;
            }

            final long start = System.nanoTime();
            BufferedImage source = null;

            // read image
            source = ImageIO.read(in);
            if (source != null) {

                int sourceWidth = source.getWidth();
                int sourceHeight = source.getHeight();

                // Update image dimensions
                originalImage.setProperty(Image.width, sourceWidth);
                originalImage.setProperty(Image.height, sourceHeight);

                // float aspectRatio = sourceWidth/sourceHeight;
                float scaleX = 1.0f * sourceWidth / maxWidth;
                float scaleY = 1.0f * sourceHeight / maxHeight;
                float scale;

                if (crop) {

                    scale = Math.min(scaleX, scaleY);
                } else {

                    scale = Math.max(scaleX, scaleY);
                }

                // Don't scale up
                if (scale > 1.0) {

                    final int destWidth = Math.max(4, Math.round(sourceWidth / scale));
                    final int destHeight = Math.max(4, Math.round(sourceHeight / scale));

                    //System.out.println(destWidth + " / " + destHeight);

                    ResampleOp resampleOp = new ResampleOp(destWidth, destHeight);
                    BufferedImage resampled = resampleOp.filter(source, null);
                    BufferedImage result = null;

                    if (crop) {

                        int offsetX = Math.abs(maxWidth - destWidth) / 2;
                        int offsetY = Math.abs(maxHeight - destHeight) / 2;

                        logger.log(Level.FINE, "Offset and Size (x,y,w,h): {0},{1},{2},{3}",
                                new Object[] { offsetX, offsetY, maxWidth, maxHeight });

                        result = resampled.getSubimage(offsetX, offsetY, maxWidth, maxHeight);

                        tn.setWidth(maxWidth);
                        tn.setHeight(maxHeight);

                    } else {

                        result = resampled;

                        tn.setWidth(destWidth);
                        tn.setHeight(destHeight);

                    }

                    ImageIO.write(result, Thumbnail.FORMAT, baos);

                } else {

                    // Thumbnail is source image
                    ImageIO.write(source, Thumbnail.FORMAT, baos);
                    tn.setWidth(sourceWidth);
                    tn.setHeight(sourceHeight);
                }

            } else {

                logger.log(Level.FINE, "Thumbnail could not be created");
                return null;
            }

            long end = System.nanoTime();
            long time = (end - start) / 1000000;

            logger.log(Level.FINE, "Thumbnail created. Reading, scaling and writing took {0} ms", time);
            tn.setBytes(baos.toByteArray());

            return tn;

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Unable to create thumbnail for image with ID {0}.", originalImage.getUuid());
        }

        return null;
    }

    /**
     * Let ImageIO read and write a JPEG image. This should normalize all types of weird
     * image sub formats, e.g. when extracting images from a flash file.
     *
     * Some images can not be read by ImageIO (or the browser) because they
     * have an JPEG EOI and SOI marker at the beginning of the file.
     *
     * This method detects and removes the bytes, so that the image
     * can be read again.
     *
     * @param original
     * @return normalized image
     */
    public static byte[] normalizeJpegImage(final byte[] original) {

        if (original == null) {

            return new byte[] {};
        }

        ByteArrayInputStream in = new ByteArrayInputStream(original);

        // If JPEG image starts with ff d9 ffd8, strip this sequence from the beginning
        // FF D9 = EOI (end of image)
        // FF D8 = SOI (start of image)
        if ((original[0] == (byte) 0xff) && (original[1] == (byte) 0xd9) && (original[2] == (byte) 0xff)
                && (original[3] == (byte) 0xd8)) {

            in.skip(4);
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BufferedImage source;

        try {

            source = ImageIO.read(in);

            // If ImageIO cannot read it, return original
            if (source == null) {

                return original;
            }

            ImageIO.write(source, "jpeg", out);

        } catch (IOException ex) {

            logger.log(Level.SEVERE, null, ex);

            if (in != null) {

                try {

                    in.close();

                } catch (IOException ignore) {
                }

            }

        } finally {
        }

        return out.toByteArray();

    }

    //~--- get methods ----------------------------------------------------

    public static String getBase64String(final File file) {

        try {
            InputStream dataStream = file.getInputStream();

            if (dataStream != null)
                return Base64.encodeToString(IOUtils.toByteArray(file.getInputStream()), false);

        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Could not get base64 string from file ", ex);
        }

        return null;
    }

    /**
     * Check if url points to an image by extension
     *
     * TODO: Improve method to check file type by peeping at the
     * content
     *
     * @param urlString
     * @return true if is of image type
     */
    public static boolean isImageType(final String urlString) {

        if ((urlString == null) || StringUtils.isBlank(urlString)) {

            return false;
        }

        String extension = urlString.toLowerCase().substring(urlString.lastIndexOf(".") + 1);
        String[] imageExtensions = {

                "png", "gif", "jpg", "jpeg", "bmp", "tif", "tiff" };

        for (String ext : imageExtensions) {

            if (ext.equals(extension)) {

                return true;
            }

        }

        return false;

    }

    /**
     * Check if url points to an Flash object by extension
     *
     * TODO: Improve method to check file type by peeping at the
     * content
     *
     * @param urlString
     * @return true if is swf file
     */
    public static boolean isSwfType(final String urlString) {

        if ((urlString == null) || StringUtils.isBlank(urlString)) {

            return false;
        }

        String extension = urlString.toLowerCase().substring(urlString.lastIndexOf(".") + 1);
        String[] imageExtensions = { "swf" };

        for (String ext : imageExtensions) {

            if (ext.equals(extension)) {

                return true;
            }

        }

        return false;

    }

    //~--- inner classes --------------------------------------------------

    public static class Base64URIData {

        private String contentType;
        private String data;

        //~--- constructors -------------------------------------------

        public Base64URIData(final String rawData) {

            String[] parts = StringUtils.split(rawData, ",");

            data = parts[1];
            contentType = StringUtils.substringBetween(parts[0], "data:", ";base64");

        }

        //~--- get methods --------------------------------------------

        public String getContentType() {

            return contentType;

        }

        public String getData() {

            return data;

        }

        public byte[] getBinaryData() {

            return Base64.decode(data);

        }

    }

    public static class Thumbnail {

        public static final String FORMAT = "png";

        //~--- fields -------------------------------------------------

        private byte[] bytes;
        private int height;
        private int width;

        //~--- constructors -------------------------------------------

        public Thumbnail() {
        }

        public Thumbnail(final byte[] bytes) {

            this.bytes = bytes;

        }

        public Thumbnail(final int width, final int height) {

            this.width = width;
            this.height = height;

        }

        public Thumbnail(final byte[] bytes, final int width, final int height) {

            this.bytes = bytes;
            this.width = width;
            this.height = height;

        }

        //~--- get methods --------------------------------------------

        public byte[] getBytes() {

            return bytes;

        }

        public int getWidth() {

            return width;

        }

        public int getHeight() {

            return height;

        }

        //~--- set methods --------------------------------------------

        public void setBytes(final byte[] bytes) {

            this.bytes = bytes;

        }

        public void setWidth(final int width) {

            this.width = width;

        }

        public void setHeight(final int height) {

            this.height = height;

        }

    }

}