All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.util.ImageUtil Maven / Gradle / Ivy

There is a newer version: 1.48
Show newest version
/*
 * Copyright 2016 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dorkbox.util;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.slf4j.LoggerFactory;

import dorkbox.os.OS;

@SuppressWarnings("WeakerAccess")
public
class ImageUtil {

    /**
     * @return returns an image, where the aspect ratio is kept, but the maximum size is maintained.
     */
    public static
    BufferedImage clampMaxImageSize(final BufferedImage image, final int size) {
        int width = image.getWidth(null);
        int height = image.getHeight(null);

        if (width <= size && height <= size) {
            return image;
        }

        // scale width/height
        if (width > size) {
            double scaleRatio = (double) size / (double) width;
            width = size;
            height = (int) (height * scaleRatio);
        }

        if (height > size) {
            double scaleRatio = (double) size / (double) height;
            height = size;
            width = (int) (width * scaleRatio);
        }

        int type = image.getType();
        if (type == 0) {
            type = BufferedImage.TYPE_INT_ARGB;
        }

        BufferedImage resizedImage = new BufferedImage(width, height, type);
        Graphics2D g = resizedImage.createGraphics();
        g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        g.drawImage(image, 0, 0, width, height, null);
        g.dispose();

        return resizedImage;
    }

    /**
     * There are issues with scaled images on Windows. This correctly scales the image.
     */
    public static
    BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
        int originalHeight = originalImage.getHeight();
        int originalWidth = originalImage.getWidth();
        double ratio = (double) originalWidth / (double) originalHeight;

        if (width == -1 && height == -1) {
            // no resizing, so just use the original size.
            width = originalWidth;
            height = originalHeight;
        }
        else if (width == -1) {
            width = (int) (height * ratio);
        }
        else if (height == -1) {
            height = (int) (width / ratio);
        }


        int type = originalImage.getType();
        if (type == 0) {
            type = BufferedImage.TYPE_INT_ARGB;
        }

        BufferedImage resizedImage = new BufferedImage(width, height, type);
        Graphics2D g = resizedImage.createGraphics();
        g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();

        return resizedImage;
    }

    /**
     * Resizes the image, as either a a FILE on disk, or as a RESOURCE name, and saves the new size as a file on disk. This new file will
     * replace any other file with the same name.
     *
     * @return the file string on disk that is the resized icon
     */
    public static
    String resizeFileOrResource(final int size, final String fileName) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(fileName);

        Dimension imageSize = getImageSize(fileInputStream);
        //noinspection NumericCastThatLosesPrecision
        if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
            // we can reuse this file.
            return fileName;
        }

        // have to resize the file (and return the new path)

        String extension = FileUtil.INSTANCE.getExtension(fileName);
        if (extension.isEmpty()) {
            extension = "png"; // made up
        }

        // now have to resize this file.
        File newFile = new File(OS.INSTANCE.getTEMP_DIR(), "temp_resize." + extension).getAbsoluteFile();
        Image image;

        // is file sitting on drive
        File iconTest = new File(fileName);
        if (iconTest.isFile() && iconTest.canRead()) {
            final String absolutePath = iconTest.getAbsolutePath();
            image = new ImageIcon(absolutePath).getImage();
        }
        else {
            // suck it out of a URL/Resource (with debugging if necessary)
            final URL systemResource = LocationResolver.getResource(fileName);
            image = new ImageIcon(systemResource).getImage();
        }

        ImageUtil.waitForImageLoad(image);

        // make whatever dirs we need to.
        boolean mkdirs = newFile.getParentFile()
                                .mkdirs();

        if (!mkdirs) {
            throw new IOException("Unable to create directories for " + newFile.getParentFile());
        }

        // if it's already there, we have to delete it
        boolean delete = newFile.delete();
        if (!delete) {
            throw new IOException("Temporary file already in use, cannot delete it " + newFile);
        }

        // the smaller dimension have padding, so the larger dimension is the size of this image.
        BufferedImage bufferedImage = getSquareBufferedImage(image);

        // now write out the new one
        ImageIO.write(bufferedImage, extension, newFile);

        return newFile.getAbsolutePath();
    }


    /**
     * Creates an image of the specified size, and saves the PNG to disk
     *
     * @param size the size of the image to create
     * @param color the color to use. NULL to create a transparent image
     *
     * @return the PNG File output the created image (size + color specified)
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static
    File createImage(final int size, final File fileToUse, final Color color) throws IOException {
        if (fileToUse.canRead() && fileToUse.isFile()) {
            return fileToUse.getAbsoluteFile();
        }

        // make sure the directory exists
        fileToUse.getParentFile().mkdirs();

        final BufferedImage image = createImageAsBufferedImage(size, color);
        ImageIO.write(image, "png", fileToUse);
        return fileToUse.getAbsoluteFile();
    }

    /**
     * Creates an image of the specified size.
     *
     * @param size the size of the image to create
     * @param color the color to use. NULL to create a transparent image
     *
     * @return a BufferedImage of the size + color specified.
     */
    @SuppressWarnings("WeakerAccess")
    public static
    BufferedImage createImageAsBufferedImage(final int size, final Color color) {
        return createImageAsBufferedImage(size, size, color);
    }

    /**
     * Creates an image of the specified size.
     *
     * @param width the width of the image to create
     * @param height the height of the image to create
     * @param color the color to use. NULL to create a transparent image
     *
     * @return a BufferedImage of the size + color specified.
     */
    @SuppressWarnings("WeakerAccess")
    public static
    BufferedImage createImageAsBufferedImage(final int width, final int height, final Color color) {
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = image.createGraphics();
        if (color == null) {
            g2d.setColor(new Color(0, 0, 0, 0));
        } else {
            g2d.setColor(color);
        }
        g2d.fillRect(0, 0, width, height);
        g2d.dispose();

        return image;
    }

    /**
     * This will always return a square image, with whatever value is smaller to have padding (so it will be centered), and the larger
     * dimension will be the size of the image.
     *
     * @return the image as a SQUARE Buffered Image
     */
    public static
    BufferedImage getSquareBufferedImage(Image image) {
        int width = image.getWidth(null);
        int height = image.getHeight(null);

        int paddingX = 0;
        int paddingY = 0;

        int size = width;

        if (width < height) {
            size = height;
            paddingX = (height - width) / 2;
        } else {
            paddingY = (width - height) / 2;
        }

        BufferedImage bimage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = bimage.createGraphics();
        g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        g.drawImage(image, paddingX, paddingY, null);
        g.dispose();

        // Return the buffered image
        return bimage;
    }

    /**
     * @return the image, unmodified, as a Buffered Image
     */
    public static
    BufferedImage getBufferedImage(Image image) {
        if (image instanceof BufferedImage) {
            return (BufferedImage) image;
        }

        BufferedImage bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = bimage.createGraphics();
        g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        g.drawImage(image, 0, 0, null);
        g.dispose();

        // Return the buffered image
        return bimage;
    }

    /**
     * @return the icon, unmodified, as a Buffered Image
     */
    public static
    BufferedImage getBufferedImage(Icon icon) {
        if (icon instanceof BufferedImage) {
            return (BufferedImage) icon;
        }

        BufferedImage bimage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = bimage.createGraphics();
        g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        icon.paintIcon(null, g, 0, 0);
        g.dispose();

        return bimage;
    }

    /**
     * Converts an image to a byte array
     *
     * @return the PNG File output the created buffered image, as a byte array
     */
    public static
    byte[] toBytes(final BufferedImage image) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "PNG", byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * Reads the image size information from the specified file, without loading the entire file.
     *
     * @param fileStream the input stream of the file
     *
     * @return the image size dimensions. IOException if it could not be read
     */
    public static
    Dimension getImageSize(InputStream fileStream) throws IOException {
        ImageInputStream in = null;
        ImageReader reader = null;
        try {
            // This will ONLY work for File, InputStream, and RandomAccessFile
            in = ImageIO.createImageInputStream(fileStream);

            final Iterator readers = ImageIO.getImageReaders(in);
            if (readers.hasNext()) {
                reader = readers.next();
                reader.setInput(in);

                return new Dimension(reader.getWidth(0), reader.getHeight(0));
            }
        } finally {
            // `ImageInputStream` is not a closeable in 1.6, so we do this manually.
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignored) {
                }
            }

            if (reader != null) {
                reader.dispose();
            }
        }

        throw new IOException("Unable to read file inputStream for image size data.");
    }


    private static final Object mediaTrackerLock = new Object();
    private static final AtomicInteger imageTrackerIndex = new AtomicInteger(0);
    private static MediaTracker tracker = null;

    /**
     * Wait until the image is fully loaded and then init the graphics.
     *
     * @param image the image you want load immediately
     */
    public static
    void waitForImageLoad(final Image image) {
        int imageId = imageTrackerIndex.getAndIncrement();

        // make sure the image if fully loaded
        synchronized (mediaTrackerLock) {
            if (tracker == null) {
                tracker = new MediaTracker(new Component() {});
            }

            tracker.addImage(image, imageId);
        }

        try {
            tracker.waitForID(imageId);
            if (tracker.isErrorID(imageId)) {
                LoggerFactory.getLogger(ImageUtil.class).error("Error loading image!");
            }
        }
        catch (InterruptedException ignored) {

        } finally {
            synchronized (mediaTrackerLock) {
                tracker.removeImage(image);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy