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

dorkbox.systemTray.util.ImageUtils Maven / Gradle / Ivy

Go to download

Cross-platform SystemTray support for Swing/AWT, GtkStatusIcon, and AppIndicator on Java 8+

There is a newer version: 4.4
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.systemTray.util;

import static dorkbox.systemTray.jna.windows.Gdi32.GetDeviceCaps;
import static dorkbox.systemTray.jna.windows.Gdi32.LOGPIXELSX;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.Arrays;
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.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;

import com.sun.jna.Pointer;

import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.windows.User32;
import dorkbox.util.CacheUtil;
import dorkbox.util.FileUtil;
import dorkbox.util.IO;
import dorkbox.util.LocationResolver;
import dorkbox.util.OS;
import dorkbox.util.OSUtil;
import dorkbox.util.SwingUtil;
import dorkbox.util.process.ShellProcessBuilder;

public
class ImageUtils {

    private static final File TEMP_DIR = new File(CacheUtil.TEMP_DIR, "ResizedImages");

    // tray/menu-entry size.
    // for more complete info on the linux side of things...
    // https://wiki.archlinux.org/index.php/HiDPI
    public static volatile int TRAY_SIZE = 0;
    public static volatile int ENTRY_SIZE = 0;

    // the menu entry font size ALSO must be detected, and it is a little bit tricky to figure this out.
    // Only exists (and is necessary) for SWING menus
    public static volatile Font ENTRY_FONT = null;

    public static
    void determineIconSize() {
        double trayScalingFactor = 0;
        double menuScalingFactor = 0;

        if (SystemTray.AUTO_TRAY_SIZE) {
            if (OS.isWindows()) {
                int[] version = OSUtil.Windows.getVersion();

                // windows 8/8.1/10 are the only windows OSes to do scaling properly (XP/Vista/7 do DPI scaling, which is terrible anyways)
                // we are going to let windows manage scaling the icon correctly, but we are BY DEFAULT going to give it a large size to scale

                // vista - 8.0 - only global DPI settings
                // 8.1 - 10 - global + per-monitor DPI settings

                // 1 = 16
                // 2 = 32
                // 4 = 64
                // 8 = 128

                // scaling 1
                if (version[0] <= 5) {
                    // Windows XP               5.1.2600  (2001-10-25)
                    // Windows Server 2003      5.2.3790  (2003-04-24)
                    // Windows Home Server		5.2.3790  (2007-06-16)
                    trayScalingFactor = 2;
                } else if (version[0] == 6 && version[1] == 0) {
                    // Windows Vista	        6.0.6000  (2006-11-08)
                    // Windows Server 2008 SP1	6.0.6001  (2008-02-27)
                    // Windows Server 2008 SP2	6.0.6002  (2009-04-28)
                    trayScalingFactor = 2;

                } else if (version[0] == 6 && version[1] <= 2) {
                    // Windows 7                    6.1.7600  (2009-10-22)
                    // Windows Server 2008 R2       6.1.7600  (2009-10-22)
                    // Windows Server 2008 R2 SP1   6.1.7601  (?)
                    //
                    // Windows Home Server 2011		6.1.8400  (2011-04-05)
                    //
                    // Windows 8                    6.2.9200  (2012-10-26)
                    // Windows Server 2012	        6.2.9200  (2012-09-04)
                    trayScalingFactor = 4;
                } else {
                    // Windows 8.1                  6.3.9600  (2013-10-18)
                    // Windows Server 2012 R2       6.3.9600  (2013-10-18)
                    //
                    // Windows 10	                10.0.10240  (2015-07-29)
                    // Windows 10	                10.0.10586  (2015-11-12)
                    // Windows 10	                10.0.14393  (2016-07-18)
                    //
                    // Windows Server 2016          10.0.14393  (2016-10-12)
                    trayScalingFactor = 4;
                }

                // get's the HARDWARE DEVICE logical resolution. This will be set by the monitor, and will never change -  even if
                // scaling is changed via the control panel
                Pointer screen = User32.GetDC(null);
                int dpiX = GetDeviceCaps(screen, LOGPIXELSX);
                User32.ReleaseDC(null, screen);

                // 96 DPI = 100% scaling
                // 120 DPI = 125% scaling
                // 144 DPI = 150% scaling
                // 192 DPI = 200% scaling

                // just a note on scaling...
                // We want to scale the image as best we can beforehand, so there is an attempt to have it look good. Java by default
                // does not scale this correctly.
                if (dpiX != 96) {
                    // so there are additional scaling settings...
                    // casting around for rounding/math stuff
                    menuScalingFactor =  ((double) dpiX) / 96.0;
                }


                if (SystemTray.DEBUG) {
                    SystemTray.logger.debug("Windows version: '{}'", Arrays.toString(version));
                    SystemTray.logger.debug("Windows DPI settings: '{}'", dpiX);
                }
            } else if (OS.isLinux() || OS.isUnix()) {
                // GtkStatusIcon will USUALLY automatically scale the icon
                // AppIndicator MIGHT scale the icon (depends on the OS)


                // KDE is bonkers. Gnome is "ok"
                String XDG = System.getenv("XDG_CURRENT_DESKTOP");
                if (XDG == null) {
                    // Check if plasmashell is running, if it is -- then we are most likely KDE
                    double plasmaVersion = OSUtil.DesktopEnv.getPlasmaVersion();
                    if (plasmaVersion > 0) {
                        XDG = "kde";
                    }
                }

                if ("kde".equalsIgnoreCase(XDG)) {
                    double plasmaVersion = OSUtil.DesktopEnv.getPlasmaVersion();

                    // 1 = 16
                    // 2 = 32
                    // 4 = 64
                    // 8 = 128
                    if (plasmaVersion > 0) {
                        trayScalingFactor = 2;
//                        menuScalingFactor = 1.4;
                    } else if (SystemTray.DEBUG) {
                        SystemTray.logger.error("Cannot check plasmashell version");
                    }
                } else {
                    // it's likely a Gnome/unity environment

                    try {
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
                        PrintStream outputStream = new PrintStream(byteArrayOutputStream);

                        // gsettings get org.gnome.desktop.interface scaling-factor
                        final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
                        shellVersion.setExecutable("gsettings");
                        shellVersion.addArgument("get");
                        shellVersion.addArgument("org.gnome.desktop.interface");
                        shellVersion.addArgument("scaling-factor");
                        shellVersion.start();

                        String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);

                        if (!output.isEmpty()) {
                            if (SystemTray.DEBUG) {
                                SystemTray.logger.debug("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output);
                            }

                            // DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well.
                            // should be: uint32 0  or something
                            if (output.contains("uint32")) {
                                String value = output.substring(output.indexOf("uint")+7, output.length());
                                trayScalingFactor = Integer.parseInt(value);
                                menuScalingFactor = Integer.parseInt(value);

                                // 0 is disabled (no scaling)
                                // 1 is enabled (default scale)
                                // 2 is 2x scale
                                // 3 is 3x scale
                                // etc


                                // A setting of 2, 3, etc, which is all you can do with scaling-factor
                                // To enable HiDPI, use gsettings:
                                // gsettings set org.gnome.desktop.interface scaling-factor 2
                            }
                        }
                    } catch (Throwable e) {
                        if (SystemTray.DEBUG) {
                            SystemTray.logger.error("Cannot check scaling factor", e);
                        }
                    }

                    // fedora 23+ has a different size for the indicator (NOT default 16px)
                    int fedoraVersion = OSUtil.Linux.getFedoraVersion();
                    if (trayScalingFactor == 0 && fedoraVersion >= 23) {
                        if (SystemTray.DEBUG) {
                            SystemTray.logger.debug("Adjusting tray/menu scaling for FEDORA " + fedoraVersion);
                        }

                        trayScalingFactor = 2;
                    }
                }
            } else if (OS.isMacOsX()) {
                // let's hope this wasn't just hardcoded like windows...
                int height;

                if (!SwingUtilities.isEventDispatchThread()) {
                    // MacOS must execute this on the EDT... It is very strict compared to win/linux
                    final AtomicInteger h = new AtomicInteger(0);
                    SwingUtil.invokeAndWaitQuietly(new Runnable() {
                        @Override
                        public
                        void run() {
                            h.set((int) java.awt.SystemTray.getSystemTray()
                                                           .getTrayIconSize()
                                                           .getHeight());
                        }
                    });
                    height = h.get();
                } else {
                    height = (int) java.awt.SystemTray.getSystemTray()
                                                      .getTrayIconSize()
                                                      .getHeight();
                }

                if (height < 32) {
                    // lock in at 32
                    trayScalingFactor = 2;
                }
                else if ((height & (height - 1)) == 0) {
                    // is this a power of 2 number? If so, we can use it
                    trayScalingFactor = height/SystemTray.DEFAULT_TRAY_SIZE;
                }
                else {
                    // don't know how exactly to determine this, but we are going to assume very high "HiDPI" for this...
                    // the OS should go up/down as needed.
                    trayScalingFactor = 8;
                }
            }
        }

        // windows, mac, linux(GtkStatusIcon) will automatically scale the tray size
        //  the menu entry icon size will NOT get scaled (it will show whatever we specify)
        // we want to make sure our "scaled" size is appropriate for the OS.

        // the DEFAULT scale is 16
        if (trayScalingFactor > 1) {
            TRAY_SIZE = (int) (SystemTray.DEFAULT_TRAY_SIZE * trayScalingFactor);
        } else {
            TRAY_SIZE = SystemTray.DEFAULT_TRAY_SIZE;
        }

        if (menuScalingFactor > 1) {
            ENTRY_SIZE = (int) (SystemTray.DEFAULT_MENU_SIZE * menuScalingFactor);
        } else {
            ENTRY_SIZE = SystemTray.DEFAULT_MENU_SIZE;
        }

        // this must be a JMenuItem component, because that is the component we are setting the font on.
        // this is only important to do if we are a swing tray type, which ONLY happens in Windows
        if (OS.isWindows()) {
            // must be a plain style font
            Font font = new JMenuItem().getFont().deriveFont(Font.PLAIN);

            if (menuScalingFactor > 1) {
                font = ImageUtils.getFontForSpecificHeight(font, ENTRY_SIZE);
                if (SystemTray.DEBUG) {
                    SystemTray.logger.debug("Menu entry font size '{}' found for requested size '{}'", font.getSize(), ENTRY_SIZE);
                }
            } else if (SystemTray.DEBUG) {
                SystemTray.logger.debug("Menu entry font size '{}'. Not scaling for requested size '{}'", font.getSize(), ENTRY_SIZE);
            }

            ENTRY_FONT = font;
        }

        if (SystemTray.DEBUG) {
            SystemTray.logger.debug("ScalingFactor is '{}', tray icon size is '{}'.", trayScalingFactor, TRAY_SIZE);
            SystemTray.logger.debug("ScalingFactor is '{}', tray menu size is '{}'.", menuScalingFactor, ENTRY_SIZE);
        }
    }

    /**
     * Gets the correct font (in GENERAL) for a specified pixel height.
     * @param font the font we are checking
     * @param height the height in pixels we want to get as close as possible to
     *
     * @return the font (derived from the specified font) that is as close as possible to the requested height
     */
    private static
    Font getFontForSpecificHeight(final Font font, final int height) {
        int size = font.getSize();
        Boolean lastAction = null;
        BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();

        while (true) {
            Font fontCheck = new Font(font.getName(), Font.PLAIN, size);

            FontMetrics metrics = g.getFontMetrics(fontCheck);
            Rectangle2D rect = metrics.getStringBounds("Tj|", g);
            int testHeight = (int) rect.getHeight();

            if (testHeight < height && lastAction != Boolean.FALSE) {
                size++;
                lastAction = Boolean.TRUE;
            } else if (testHeight > height && lastAction != Boolean.TRUE) {
                size--;
                lastAction = Boolean.FALSE;
            } else {
                // either we are the exact size, or we are ONE font size to big/small (depending on what our initial guess was)
                return fontCheck;
            }
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static
    File getTransparentImage(final int size) {
        final File newFile = new File(TEMP_DIR, size + "_empty.png").getAbsoluteFile();

        if (newFile.canRead() && newFile.isFile()) {
            return newFile;
        }

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

        try {
            final BufferedImage image = getTransparentImageAsBufferedImage(size);
            ImageIO.write(image, "png", newFile);
        } catch (Exception e) {
            SystemTray.logger.error("Error creating transparent image for size: {}", size, e);
        }

        return newFile;
    }

    @SuppressWarnings("WeakerAccess")
    public static
    BufferedImage getTransparentImageAsBufferedImage(final int size) {
        final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = image.createGraphics();
        g2d.setColor(new Color(0,0,0,0));
        g2d.fillRect(0, 0, size, size);
        g2d.dispose();

        return image;
    }

    private static
    File getErrorImage(final String cacheName) {
        try {
            final File save = CacheUtil.save(cacheName, ImageUtils.class.getResource("error_32.png"));
            // since it's the error file, we want to delete it on exit!
            save.deleteOnExit();
            return save;
        } catch (Exception e) {
            // this must be thrown
            throw new RuntimeException("Serious problems! Unable to extract error image, this should NEVER happen!", e);
        }
    }

    private static
    File getIfCachedOrError(final String cacheName) {
        try {
            final File check = CacheUtil.check(cacheName);
            if (check != null) {
                return check;
            }
        } catch (Exception e) {
            SystemTray.logger.error("Error checking cache for information. Using error icon instead", e);
            return getErrorImage(cacheName);
        }
        return null;
    }

    public static synchronized
    File resizeAndCache(final int size, final File file) {
        return resizeAndCache(size, file.getAbsolutePath());
    }

    public static synchronized
    File resizeAndCache(final int size, final String fileName) {
        if (fileName == null) {
            return null;
        }

        try {
            FileInputStream fileInputStream = new FileInputStream(fileName);
            File file = resizeAndCache(size, fileInputStream);
            fileInputStream.close();

            return file;
        } catch (Exception e) {
            // have to serve up the error image instead.
            SystemTray.logger.error("Error reading image. Using error icon instead", e);
            return getErrorImage(size + "default");
        }
    }

    @SuppressWarnings("Duplicates")
    public static synchronized
    File resizeAndCache(final int size, final URL imageUrl) {
        if (imageUrl == null) {
            return null;
        }

        try {
            InputStream inputStream = imageUrl.openStream();
            File file = resizeAndCache(size, inputStream);
            inputStream.close();

            return file;
        } catch (Exception e) {
            // have to serve up the error image instead.
            SystemTray.logger.error("Error reading image. Using error icon instead", e);
            return getErrorImage(size + "default");
        }
    }
    @SuppressWarnings("Duplicates")
    public static synchronized
    File resizeAndCache(final int size, final Image image) {
        if (image == null) {
            return null;
        }

        // stupid java won't scale it right away, so we have to do this twice to get the correct size
        final Image trayImage = new ImageIcon(image).getImage();
        trayImage.flush();

        try {
            BufferedImage bufferedImage = getBufferedImage(trayImage);

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", os);
            InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray());

            File file = resizeAndCache(size, imageInputStream);
            imageInputStream.close(); // BAOS doesn't do anything, but here for completeness + documentation

            return file;
        } catch (Exception e) {
            // have to serve up the error image instead.
            SystemTray.logger.error("Error reading image. Using error icon instead", e);
            return getErrorImage(size + "default");
        }
    }

    @SuppressWarnings("Duplicates")
    public static synchronized
    File resizeAndCache(final int size, final ImageInputStream imageStream) {
        if (imageStream == null) {
            return null;
        }

        try {
            ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream);
            return resizeAndCache(size, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        } catch (Exception e) {
            // have to serve up the error image instead.
            SystemTray.logger.error("Error reading image. Using error icon instead", e);
            return getErrorImage(size + "default");
        }
    }

    @SuppressWarnings("Duplicates")
    public static synchronized
    File resizeAndCache(final int size, InputStream imageStream) {
        if (imageStream == null) {
            return null;
        }

        if (!(imageStream instanceof ByteArrayInputStream)) {
            // have to make a copy of the inputStream, but only if necessary
            try {
                ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream);
                imageStream.close();

                imageStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            } catch (Exception e) {
                // this must be thrown
                throw new RuntimeException("Unable to read from inputStream.", e);
            }
        }
        imageStream.mark(0);

        // check if we already have this file information saved to disk, based on size + hash of data
        final String cacheName = size + "_" + CacheUtil.createNameAsHash(imageStream);
        ((ByteArrayInputStream) imageStream).reset();  // casting to avoid unnecessary try/catch for IOException


        // if we already have this fileName, reuse it
        final File check = getIfCachedOrError(cacheName);
        if (check != null) {
            return check;
        }

        // no cached file, so we resize then save the new one.
        boolean needsResize = true;
        try {
            imageStream.mark(0);
            Dimension imageSize = getImageSize(imageStream);
            //noinspection NumericCastThatLosesPrecision
            if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
                // we can reuse this URL (it's the correct size).
                needsResize = false;
            }
        } catch (Exception e) {
            // have to serve up the error image instead.
            SystemTray.logger.error("Error resizing image. Using error icon instead", e);
            return getErrorImage(cacheName);
        } finally {
            ((ByteArrayInputStream) imageStream).reset();  // casting to avoid unnecessary try/catch for IOException
        }

        if (needsResize) {
            // we have to hop through hoops.
            try {
                File resizedFile = resizeFileNoCheck(size, imageStream);

                // now cache that file
                try {
                    return CacheUtil.save(cacheName, resizedFile);
                } catch (Exception e) {
                    // have to serve up the error image instead.
                    SystemTray.logger.error("Error caching image. Using error icon instead", e);
                    return getErrorImage(cacheName);
                }

            } catch (Exception e) {
                // have to serve up the error image instead.
                SystemTray.logger.error("Error resizing image. Using error icon instead", e);
                return getErrorImage(cacheName);
            }

        } else {
            // no resize necessary, just cache as is.
            try {
                return CacheUtil.save(cacheName, imageStream);
            } catch (Exception e) {
                // have to serve up the error image instead.
                SystemTray.logger.error("Error caching image. Using error icon instead", e);
                return getErrorImage(cacheName);
            }
        }
    }

    /**
     * Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
     *
     * @return the file on disk that is the resized icon
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static
    File resizeFileNoCheck(final int size, final URL imageUrl) throws IOException {
        String extension = FileUtil.getExtension(imageUrl.getPath());
        if (extension.isEmpty()) {
            extension = "png"; // made up
        }

        InputStream inputStream = imageUrl.openStream();

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

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


        // resize the image, keep aspect
        image = new ImageIcon(ImageIO.read(inputStream)).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
        image.flush();

        // have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
        image = new ImageIcon(image).getImage();
        image.flush();

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

        // if it's already there, we have to delete it
        newFile.delete();

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

        return newFile;
    }

    /**
     * Resizes the given InputStream to the specified size. No checks are performed if it's the correct size to begin with.
     *
     * @return the file on disk that is the resized icon
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static
    File resizeFileNoCheck(final int size, InputStream inputStream) throws IOException {
        // have to resize the file (and return the new path)

        // now have to resize this file.
        File newFile = new File(TEMP_DIR, "temp_resize.png").getAbsoluteFile();
        Image image;


        // resize the image, keep aspect
        image = new ImageIcon(ImageIO.read(inputStream)).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
        image.flush();

        // have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
        image = new ImageIcon(image).getImage();
        image.flush();

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

        // if it's already there, we have to delete it
        newFile.delete();

        // now write out the new one
        BufferedImage bufferedImage = getBufferedImage(image);
        ImageIO.write(bufferedImage, "png", newFile); // made up extension

        return newFile;
    }


    /**
     * Resizes the image (as a FILE on disk, or as a RESOURCE name), saves it as a file on disk. This file will be OVER-WRITTEN by any
     * operation that calls this method.
     *
     * @return the file string on disk that is the resized icon
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static
    String resizeFile(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.getExtension(fileName);
        if (extension.isEmpty()) {
            extension = "png"; // made up
        }

        // now have to resize this file.
        File newFile = new File(TEMP_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();

            // resize the image, keep aspect
            image = new ImageIcon(absolutePath).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
            image.flush();
        }
        else {
            // suck it out of a URL/Resource (with debugging if necessary)
            final URL systemResource = LocationResolver.getResource(fileName);

            // resize the image, keep aspect
            image = new ImageIcon(systemResource).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
            image.flush();
        }

        // have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
        image = new ImageIcon(image).getImage();
        image.flush();

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

        // if it's already there, we have to delete it
        newFile.delete();


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

        return newFile.getAbsolutePath();
    }

    private 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 bGr = bimage.createGraphics();
        bGr.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING,
                                                 RenderingHints.VALUE_RENDER_QUALITY));
        bGr.drawImage(image, 0, 0, null);
        bGr.dispose();

        // Return the buffered image
        return bimage;
    }


    /**
     * 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
     */
    private 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.");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy