001package jmri.util.davidflanagan; 002 003import java.awt.Graphics2D; 004import java.awt.Image; 005import java.awt.RenderingHints; 006import java.awt.Transparency; 007import java.awt.image.BufferedImage; 008import java.awt.image.ColorModel; 009import java.awt.image.PixelGrabber; 010 011public class ImageUtils { 012 /** 013 * Scale an image down to a specified maximum width and height. This does multiple downscaling steps to avoid 014 * rendering issues with large images. 015 * 016 * @param img The image to scale down. 017 * @param maxWidth The maximum width of the scaled image. 018 * @param maxHeight The maximum height of the scaled image. 019 * @return The scaled image. 020 */ 021 public static Image getScaledInstance(Image img, int maxWidth, int maxHeight) { 022 int w = img.getWidth(null); 023 int h = img.getHeight(null); 024 025 // 1. Determine the final target dimensions immediately 026 double scale = Math.min((double) maxWidth / w, (double) maxHeight / h); 027 028 // Optional: Prevent upscaling if the image is already smaller than the target 029 scale = Math.min(scale, 1.0); 030 031 int targetW = (int) Math.round(w * scale); 032 int targetH = (int) Math.round(h * scale); 033 034 Image currentImg = img; 035 int currentW = w; 036 int currentH = h; 037 038 int type = isImageTransparent(img) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB; 039 040 // 2. Multistep downscaling loop 041 while (currentW > targetW || currentH > targetH) { 042 // Drop by 50% or jump straight to target if we're close 043 currentW = Math.max(targetW, currentW / 2); 044 currentH = Math.max(targetH, currentH / 2); 045 046 BufferedImage tmp = new BufferedImage(currentW, currentH, type); 047 Graphics2D g2 = tmp.createGraphics(); 048 049 // High-quality settings 050 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 051 g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 052 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 053 054 g2.drawImage(currentImg, 0, 0, currentW, currentH, null); 055 g2.dispose(); 056 057 currentImg = tmp; 058 } 059 060 return currentImg; 061 } 062 063 /** 064 * Check if an image is transparent. 065 * 066 * @param img The image to check. 067 * @return true if the image is transparent, false otherwise. 068 */ 069 private static boolean isImageTransparent(Image img) { 070 if (img instanceof BufferedImage) { 071 return ((BufferedImage) img).getTransparency() != Transparency.OPAQUE; 072 } 073 074 // Use a PixelGrabber to peek at the ColorModel 075 PixelGrabber pg = new PixelGrabber(img, 0, 0, 1, 1, false); 076 try { 077 pg.grabPixels(); // We only need to grab the metadata/first pixel 078 ColorModel cm = pg.getColorModel(); 079 return cm != null && cm.hasAlpha(); 080 } catch (InterruptedException e) { 081 return true; // Assume transparent if interrupted to be safe 082 } 083 } 084}