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}