From b8a3736fbfa95c9005446ff7465feb3b81b79db3 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 25 Jun 2017 23:51:21 +0200 Subject: [PATCH] Code polish/cleanup. Moved icon size determination to another class --- .../systemTray/util/ImageResizeUtil.java | 395 ++++++++++ src/dorkbox/systemTray/util/ImageUtils.java | 731 ------------------ 2 files changed, 395 insertions(+), 731 deletions(-) create mode 100644 src/dorkbox/systemTray/util/ImageResizeUtil.java delete mode 100644 src/dorkbox/systemTray/util/ImageUtils.java diff --git a/src/dorkbox/systemTray/util/ImageResizeUtil.java b/src/dorkbox/systemTray/util/ImageResizeUtil.java new file mode 100644 index 0000000..db133c3 --- /dev/null +++ b/src/dorkbox/systemTray/util/ImageResizeUtil.java @@ -0,0 +1,395 @@ +/* + * 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.util.ImageUtil.getBufferedImage; + +import java.awt.Dimension; +import java.awt.Image; +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.net.URL; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; + +import dorkbox.systemTray.SystemTray; +import dorkbox.util.CacheUtil; +import dorkbox.util.IO; +import dorkbox.util.ImageUtil; + +public +class ImageResizeUtil { + public static final File TEMP_DIR = new File(CacheUtil.TEMP_DIR, "SystemTrayImages"); + + public static + File getTransparentImage() { + // here, it doesn't matter what size the image is, as long as there is an image, the text in the menu will be shifted correctly + // it is HIGHLY unlikely that the menu entry will be smaller than 4px. + + // NOTE: this does not need to be called on the EDT + try { + final File newFile = new File(TEMP_DIR, 4 + "_empty.png").getAbsoluteFile(); + return ImageUtil.getTransparentImage(4, newFile); + } catch (IOException e) { + throw new RuntimeException("Unable to generate transparent image! Something is severely wrong!"); + } + } + + private static + File getErrorImage(int size) { + if (size == 0) { + // default size + size = 32; + } + + try { + InputStream imageStream = ImageResizeUtil.class.getResource("error_32.png").openStream(); + + // have to resize the image to be whatever size we specify + imageStream = makeByteArrayInputStream(imageStream); + 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 = getIfCached(cacheName); + if (check != null) { + return check; + } + + // we have to hop through hoops. + File resizedFile = resizeFileNoCheck(size, imageStream); + + // now cache that file + File save = CacheUtil.save(cacheName, resizedFile); + 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 getIfCached(final String cacheName) { + try { + final File check = CacheUtil.check(cacheName); + if (check != null) { + return check; + } + } catch (Exception ignored) { + } + return null; + } + + private static synchronized + File resizeAndCache(final int size, final File file) { + return resizeAndCache(size, file.getAbsolutePath()); + } + + private 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); + } + } + + @SuppressWarnings("Duplicates") + private static synchronized + File resizeAndCache(final int size, InputStream imageStream) { + if (imageStream == null) { + return null; + } + + final String cacheName; + + // no cached file, so we resize then save the new one. + boolean needsResize = true; + try { + imageStream = makeByteArrayInputStream(imageStream); + imageStream.mark(0); + + // check if we already have this file information saved to disk, based on size + hash of data + 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 = getIfCached(cacheName); + if (check != null) { + return check; + } + + + imageStream.mark(0); + Dimension imageSize = ImageUtil.getImageSize(imageStream); + //noinspection NumericCastThatLosesPrecision + if (size == ((int) imageSize.getWidth())) { + // 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 getting image size. Using error icon instead", e); + return getErrorImage(size); + } 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(size); + } + + } catch (Exception e) { + // have to serve up the error image instead. + SystemTray.logger.error("Error resizing image. Using error icon instead", e); + return getErrorImage(size); + } + + } 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(size); + } + } + } + + // if this input stream is NOT a ByteArrayInputStream, make it one. + private static + InputStream makeByteArrayInputStream(InputStream imageStream) throws IOException { + if (!(imageStream instanceof ByteArrayInputStream)) { + // have to make a copy of the inputStream, but only if necessary + ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream); + imageStream.close(); + + imageStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + } + return imageStream; + } + + + /** + * 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 = ImageUtil.getImageImmediate(ImageIO.read(inputStream)); + image = image.getScaledInstance(size, -1, Image.SCALE_SMOOTH); + + // 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; + } + + + + + + + + public static + File shouldResizeOrCache(final boolean isTrayImage, final File imageFile) { + if (imageFile == null) { + return null; + } + + if (SystemTray.AUTO_SIZE) { + return ImageResizeUtil.resizeAndCache(getSize(isTrayImage), imageFile); + } else { + return imageFile; + } + } + + + public static + File shouldResizeOrCache(final boolean isTrayImage, final String imagePath) { + if (imagePath == null) { + return null; + } + + if (SystemTray.AUTO_SIZE) { + return ImageResizeUtil.resizeAndCache(getSize(isTrayImage), imagePath); + } else { + return new File(imagePath); + } + } + + public static + File shouldResizeOrCache(final boolean isTrayImage, final URL imageUrl) { + if (imageUrl == null) { + return null; + } + + try { + if (SystemTray.AUTO_SIZE) { + InputStream inputStream = imageUrl.openStream(); + File file = resizeAndCache(getSize(isTrayImage), inputStream); + inputStream.close(); + + return file; + } else { + return CacheUtil.save(imageUrl); + } + } catch (Exception e) { + // have to serve up the error image instead. + SystemTray.logger.error("Error reading image. Using error icon instead", e); + return getErrorImage(getSize(isTrayImage)); + } + } + + public static + File shouldResizeOrCache(final boolean isTrayImage, final InputStream imageStream) { + if (imageStream == null) { + return null; + } + + if (SystemTray.AUTO_SIZE) { + return ImageResizeUtil.resizeAndCache(getSize(isTrayImage), imageStream); + } else { + try { + return CacheUtil.save(imageStream); + } catch (IOException e) { + SystemTray.logger.error("Error checking cache for information. Using error icon instead", e); + return getErrorImage(0); + } + } + } + + + public static + File shouldResizeOrCache(final boolean isTrayImage, final Image image) { + if (image == null) { + return null; + } + + try { + final Image trayImage = ImageUtil.getImageImmediate(image); + + BufferedImage bufferedImage = getBufferedImage(trayImage); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, "png", os); + InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray()); + + + if (SystemTray.AUTO_SIZE) { + File file = resizeAndCache(getSize(isTrayImage), imageInputStream); + imageInputStream.close(); // BAOS doesn't do anything, but here for completeness + documentation + + return file; + } else { + File file = CacheUtil.save(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(getSize(isTrayImage)); + } + } + + public static + File shouldResizeOrCache(final boolean isTrayImage, final ImageInputStream imageStream) { + if (imageStream == null) { + return null; + } + + try { + ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream); + ByteArrayInputStream fileStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + + if (SystemTray.AUTO_SIZE) { + return resizeAndCache(getSize(isTrayImage), fileStream); + } else { + return CacheUtil.save(fileStream); + } + } catch (Exception e) { + // have to serve up the error image instead. + SystemTray.logger.error("Error reading image. Using error icon instead", e); + return getErrorImage(getSize(isTrayImage)); + } + } + + private static + int getSize(final boolean isTrayImage) { + int size; + if (isTrayImage) { + // system tray image + size = SizeAndScalingUtil.TRAY_SIZE; + } else { + // menu image + size = SizeAndScalingUtil.TRAY_MENU_SIZE; + } + + return size; + } +} diff --git a/src/dorkbox/systemTray/util/ImageUtils.java b/src/dorkbox/systemTray/util/ImageUtils.java deleted file mode 100644 index d6e38c5..0000000 --- a/src/dorkbox/systemTray/util/ImageUtils.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * 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.Graphics2D; -import java.awt.Image; -import java.awt.RenderingHints; -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.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; - - 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; - } - - 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); - } - } - - - - @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."); - } -}