001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.awt.Dimension;
005import java.awt.Graphics2D;
006import java.awt.Image;
007import java.awt.RenderingHints;
008import java.awt.image.BufferedImage;
009import java.util.function.Consumer;
010
011/**
012 * Determines how the image is sized/resized in {@link ImageResource#getImageIcon(Dimension, boolean, ImageResizeMode)}.
013 */
014enum ImageResizeMode {
015
016    /**
017     * Calculate proportional dimensions that best fit into the target width and height, retain aspect ratio
018     */
019    AUTO {
020        @Override
021        Dimension computeDimension(Dimension dim, Dimension icon) {
022            CheckParameterUtil.ensureThat((dim.width > 0 || dim.width == -1) && (dim.height > 0 || dim.height == -1),
023                    () -> dim + " is invalid");
024            if (dim.width == -1 && dim.height == -1) {
025                return new Dimension(GuiSizesHelper.getSizeDpiAdjusted(icon.width), GuiSizesHelper.getSizeDpiAdjusted(icon.height));
026            } else if (dim.width == -1) {
027                return new Dimension(Math.max(1, icon.width * dim.height / icon.height), dim.height);
028            } else if (dim.height == -1) {
029                return new Dimension(dim.width, Math.max(1, icon.height * dim.width / icon.width));
030            } else if (icon.getWidth() / dim.getWidth() > icon.getHeight() / dim.getHeight()) {
031                return computeDimension(new Dimension(dim.width, -1), icon);
032            } else {
033                return computeDimension(new Dimension(-1, dim.height), icon);
034            }
035        }
036    },
037
038    /**
039     * Calculate dimensions for the largest image that fit within the bounding box, retain aspect ratio
040     */
041    BOUNDED {
042        @Override
043        Dimension computeDimension(Dimension dim, Dimension icon) {
044            CheckParameterUtil.ensureThat((dim.width > 0 || dim.width == -1) && (dim.height > 0 || dim.height == -1),
045                    () -> dim + " is invalid");
046            final int maxWidth = Math.min(dim.width, icon.width);
047            final int maxHeight = Math.min(dim.height, icon.height);
048            return AUTO.computeDimension(new Dimension(maxWidth, maxHeight), icon);
049        }
050    },
051
052    /**
053     * Position an appropriately scaled image within the bounding box, retain aspect ratio
054     */
055    PADDED {
056        @Override
057        Dimension computeDimension(Dimension dim, Dimension icon) {
058            CheckParameterUtil.ensureThat(dim.width > 0 && dim.height > 0, () -> dim + " is invalid");
059            return dim;
060        }
061
062        @Override
063        void prepareGraphics(Dimension icon, BufferedImage image, Graphics2D g) {
064            g.setClip(0, 0, image.getWidth(), image.getHeight());
065            final double scale = Math.min(image.getWidth() / icon.getWidth(), image.getHeight() / icon.getHeight());
066            g.translate((image.getWidth() - icon.getWidth() * scale) / 2, (image.getHeight() - icon.getHeight() * scale) / 2);
067            g.scale(scale, scale);
068        }
069    };
070
071    /**
072     * Computes the dimension for the resulting image
073     * @param dim the desired image dimension
074     * @param icon the dimensions of the image to resize
075     * @return the dimension for the resulting image
076     */
077    abstract Dimension computeDimension(Dimension dim, Dimension icon);
078
079    /**
080     * Creates a new buffered image and applies the rendering function
081     * @param dim the desired image dimension
082     * @param icon the dimensions of the image to resize
083     * @param renderer the rendering function
084     * @param sourceIcon the source icon to draw
085     * @return a new buffered image
086     * @throws IllegalArgumentException if renderer or sourceIcon is null
087     */
088    BufferedImage createBufferedImage(Dimension dim, Dimension icon, Consumer<Graphics2D> renderer, Image sourceIcon) {
089        final Dimension real = computeDimension(dim, icon);
090        final BufferedImage bufferedImage = new BufferedImage(real.width, real.height, BufferedImage.TYPE_INT_ARGB);
091        final Graphics2D g = bufferedImage.createGraphics();
092        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
093        if (renderer != null) {
094            prepareGraphics(icon, bufferedImage, g);
095            renderer.accept(g);
096        } else if (sourceIcon != null) {
097            sourceIcon = sourceIcon.getScaledInstance(real.width, real.height, Image.SCALE_SMOOTH);
098            g.drawImage(sourceIcon, 0, 0, null);
099        } else {
100            throw new IllegalArgumentException("renderer or sourceIcon");
101        }
102        return bufferedImage;
103    }
104
105    /**
106     * Prepares the graphics object for rendering the given image
107     * @param icon the dimensions of the image to resize
108     * @param image the image to render afterwards
109     * @param g graphics
110     */
111    void prepareGraphics(Dimension icon, BufferedImage image, Graphics2D g) {
112        g.setClip(0, 0, image.getWidth(), image.getHeight());
113        g.scale(image.getWidth() / icon.getWidth(), image.getHeight() / icon.getHeight());
114    }
115
116    /**
117     * Returns a cache key for this mode and the given dimension
118     * @param dim the desired image dimension
119     * @return a cache key
120     */
121    int cacheKey(Dimension dim) {
122        return (ordinal() << 28) | ((dim.width & 0xfff) << 16) | (dim.height & 0xfff);
123    }
124
125}