Code polish/cleanup. Moved icon size determination to another class

This commit is contained in:
nathan 2017-06-25 23:51:21 +02:00
parent 9dbee8cfc5
commit b8a3736fbf
2 changed files with 395 additions and 731 deletions

View File

@ -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;
}
}

View File

@ -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<ImageReader> 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.");
}
}