001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.awt.Dimension;
005import java.awt.Image;
006import java.awt.image.BufferedImage;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import javax.swing.AbstractAction;
012import javax.swing.Action;
013import javax.swing.ImageIcon;
014
015import com.kitfox.svg.SVGDiagram;
016
017/**
018 * Holds data for one particular image.
019 * It can be backed by a svg or raster image.
020 *
021 * In the first case, <code>svg</code> is not <code>null</code> and in the latter case,
022 * <code>baseImage</code> is not <code>null</code>.
023 * @since 4271
024 */
025public class ImageResource {
026
027    /**
028     * Caches the image data for resized versions of the same image.
029     */
030    private Map<Dimension, Image> imgCache = new HashMap<>();
031    /**
032     * SVG diagram information in case of SVG vector image.
033     */
034    private SVGDiagram svg;
035    /**
036     * Use this dimension to request original file dimension.
037     */
038    public static final Dimension DEFAULT_DIMENSION = new Dimension(-1, -1);
039    /**
040     * ordered list of overlay images
041     */
042    protected List<ImageOverlay> overlayInfo = null;
043    private Image baseImage = null;
044
045    /**
046     * Constructs a new {@code ImageResource} from an image.
047     * @param img the image
048     */
049    public ImageResource(Image img) {
050        CheckParameterUtil.ensureParameterNotNull(img);
051        this.baseImage = img;
052        imgCache.put(DEFAULT_DIMENSION, img);
053    }
054
055    /**
056     * Constructs a new {@code ImageResource} from SVG data.
057     * @param svg SVG data
058     */
059    public ImageResource(SVGDiagram svg) {
060        CheckParameterUtil.ensureParameterNotNull(svg);
061        this.svg = svg;
062    }
063
064    /**
065     * Constructs a new {@code ImageResource} from another one and sets overlays.
066     * @param res the existing resource
067     * @param overlayInfo the overlay to apply
068     * @since 8095
069     */
070    public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
071        this.svg = res.svg;
072        this.baseImage = res.baseImage;
073        this.overlayInfo = overlayInfo;
074    }
075
076    /**
077     * Returns the image icon at default dimension.
078     * @return the image icon at default dimension
079     */
080    public ImageIcon getImageIcon() {
081        return getImageIcon(DEFAULT_DIMENSION);
082    }
083
084    /**
085     * Set both icons of an Action
086     * @param a The action for the icons
087     * @since 7693
088     */
089    public void getImageIcon(AbstractAction a) {
090        ImageIcon icon = getImageIconBounded(ImageProvider.getImageSizes(ImageProvider.ImageSizes.SMALLICON));
091        a.putValue(Action.SMALL_ICON, icon);
092        icon = getImageIconBounded(ImageProvider.getImageSizes(ImageProvider.ImageSizes.LARGEICON));
093        a.putValue(Action.LARGE_ICON_KEY, icon);
094    }
095
096    /**
097     * Get an ImageIcon object for the image of this resource
098     * @param   dim The requested dimensions. Use (-1,-1) for the original size
099     *          and (width, -1) to set the width, but otherwise scale the image
100     *          proportionally.
101     * @return ImageIcon object for the image of this resource, scaled according to dim
102     */
103    public ImageIcon getImageIcon(Dimension dim) {
104        if (dim.width < -1 || dim.width == 0 || dim.height < -1 || dim.height == 0)
105            throw new IllegalArgumentException(dim+" is invalid");
106        Image img = imgCache.get(dim);
107        if (img != null) {
108            return new ImageIcon(img);
109        }
110        if (svg != null) {
111            BufferedImage bimg = ImageProvider.createImageFromSvg(svg, dim);
112            if (bimg == null) {
113                return null;
114            }
115            if (overlayInfo != null) {
116                for (ImageOverlay o : overlayInfo) {
117                    o.apply(bimg);
118                }
119            }
120            imgCache.put(dim, bimg);
121            return new ImageIcon(bimg);
122        } else {
123            if (baseImage == null) throw new AssertionError();
124
125            int width = dim.width;
126            int height = dim.height;
127            ImageIcon icon = new ImageIcon(baseImage);
128            if (width == -1 && height == -1) {
129                width = icon.getIconWidth();
130                height = icon.getIconHeight();
131            } else if (width == -1) {
132                width = Math.max(1, icon.getIconWidth() * height / icon.getIconHeight());
133            } else if (height == -1) {
134                height = Math.max(1, icon.getIconHeight() * width / icon.getIconWidth());
135            }
136            Image i = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
137            BufferedImage bimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
138            bimg.getGraphics().drawImage(i, 0, 0, null);
139            if (overlayInfo != null) {
140                for (ImageOverlay o : overlayInfo) {
141                    o.apply(bimg);
142                }
143            }
144            imgCache.put(dim, bimg);
145            return new ImageIcon(bimg);
146        }
147    }
148
149    /**
150     * Get image icon with a certain maximum size. The image is scaled down
151     * to fit maximum dimensions. (Keeps aspect ratio)
152     *
153     * @param maxSize The maximum size. One of the dimensions (width or height) can be -1,
154     * which means it is not bounded.
155     * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
156     */
157    public ImageIcon getImageIconBounded(Dimension maxSize) {
158        if (maxSize.width < -1 || maxSize.width == 0 || maxSize.height < -1 || maxSize.height == 0)
159            throw new IllegalArgumentException(maxSize+" is invalid");
160        float realWidth;
161        float realHeight;
162        if (svg != null) {
163            realWidth = svg.getWidth();
164            realHeight = svg.getHeight();
165        } else {
166            if (baseImage == null) throw new AssertionError();
167            ImageIcon icon = new ImageIcon(baseImage);
168            realWidth = icon.getIconWidth();
169            realHeight = icon.getIconHeight();
170        }
171        int maxWidth = maxSize.width;
172        int maxHeight = maxSize.height;
173
174        if (realWidth <= maxWidth) {
175            maxWidth = -1;
176        }
177        if (realHeight <= maxHeight) {
178            maxHeight = -1;
179        }
180
181        if (maxWidth == -1 && maxHeight == -1)
182            return getImageIcon(DEFAULT_DIMENSION);
183        else if (maxWidth == -1)
184            return getImageIcon(new Dimension(-1, maxHeight));
185        else if (maxHeight == -1)
186            return getImageIcon(new Dimension(maxWidth, -1));
187        else if (realWidth / maxWidth > realHeight / maxHeight)
188            return getImageIcon(new Dimension(maxWidth, -1));
189        else
190            return getImageIcon(new Dimension(-1, maxHeight));
191   }
192}