/* * 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 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. return getTransparentImage(4); } public static File getTransparentImage(final int imageSize) { // NOTE: this does not need to be called on the EDT try { final File newFile = CacheUtil.create(imageSize + "_empty.png"); return ImageUtil.getTransparentImage(imageSize, 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 = CacheUtil.check(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 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 = CacheUtil.check(cacheName); if (check != null) { return check; } imageStream.mark(0); Dimension imageSize = ImageUtil.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 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 = CacheUtil.create("temp_resize.png"); Image image; // resize the image, keep aspect image = ImageUtil.getImageImmediate(ImageIO.read(inputStream)); image = image.getScaledInstance(size, -1, Image.SCALE_SMOOTH); // 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; } }