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.Icon; 014import javax.swing.ImageIcon; 015import javax.swing.JPanel; 016import javax.swing.UIManager; 017 018import org.openstreetmap.josm.gui.util.GuiSizesHelper; 019 020import com.kitfox.svg.SVGDiagram; 021 022/** 023 * Holds data for one particular image. 024 * It can be backed by a svg or raster image. 025 * 026 * In the first case, <code>svg</code> is not <code>null</code> and in the latter case, 027 * <code>baseImage</code> is not <code>null</code>. 028 * @since 4271 029 */ 030public class ImageResource { 031 032 /** 033 * Caches the image data for resized versions of the same image. 034 */ 035 private final Map<Dimension, Image> imgCache = new HashMap<>(); 036 /** 037 * SVG diagram information in case of SVG vector image. 038 */ 039 private SVGDiagram svg; 040 /** 041 * Use this dimension to request original file dimension. 042 */ 043 public static final Dimension DEFAULT_DIMENSION = new Dimension(-1, -1); 044 /** 045 * ordered list of overlay images 046 */ 047 protected List<ImageOverlay> overlayInfo; 048 /** 049 * <code>true</code> if icon must be grayed out 050 */ 051 protected boolean isDisabled = false; 052 /** 053 * The base raster image for the final output 054 */ 055 private Image baseImage; 056 057 /** 058 * Constructs a new {@code ImageResource} from an image. 059 * @param img the image 060 */ 061 public ImageResource(Image img) { 062 CheckParameterUtil.ensureParameterNotNull(img); 063 baseImage = scaleBaseImageIfNeeded(img); 064 } 065 066 /** Scale image according to screen DPI if needed. 067 * 068 * @param img an image loaded from file (it's width and height are virtual pixels) 069 * @return original img if virtual size is the same as real size or new image resized to real pixels 070 */ 071 private static Image scaleBaseImageIfNeeded(Image img) { 072 int imgWidth = img.getWidth(null); 073 int imgHeight = img.getHeight(null); 074 int realWidth = GuiSizesHelper.getSizeDpiAdjusted(imgWidth); 075 int realHeight = GuiSizesHelper.getSizeDpiAdjusted(imgHeight); 076 if (realWidth != -1 && realHeight != -1 && imgWidth != realWidth && imgHeight != realHeight) { 077 Image realImage = img.getScaledInstance(realWidth, realHeight, Image.SCALE_SMOOTH); 078 BufferedImage bimg = new BufferedImage(realWidth, realHeight, BufferedImage.TYPE_INT_ARGB); 079 bimg.getGraphics().drawImage(realImage, 0, 0, null); 080 return bimg; 081 } 082 return img; 083 } 084 085 /** 086 * Constructs a new {@code ImageResource} from SVG data. 087 * @param svg SVG data 088 */ 089 public ImageResource(SVGDiagram svg) { 090 CheckParameterUtil.ensureParameterNotNull(svg); 091 this.svg = svg; 092 } 093 094 /** 095 * Constructs a new {@code ImageResource} from another one and sets overlays. 096 * @param res the existing resource 097 * @param overlayInfo the overlay to apply 098 * @since 8095 099 */ 100 public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) { 101 this.svg = res.svg; 102 this.baseImage = res.baseImage; 103 this.overlayInfo = overlayInfo; 104 } 105 106 /** 107 * Set, if image must be filtered to grayscale so it will look like disabled icon. 108 * 109 * @param disabled true, if image must be grayed out for disabled state 110 * @return the current object, for convenience 111 * @since 10428 112 */ 113 public ImageResource setDisabled(boolean disabled) { 114 this.isDisabled = disabled; 115 return this; 116 } 117 118 /** 119 * Set both icons of an Action 120 * @param a The action for the icons 121 * @since 10369 122 */ 123 public void attachImageIcon(AbstractAction a) { 124 Dimension iconDimension = ImageProvider.ImageSizes.SMALLICON.getImageDimension(); 125 ImageIcon icon = getImageIconBounded(iconDimension); 126 a.putValue(Action.SMALL_ICON, icon); 127 128 iconDimension = ImageProvider.ImageSizes.LARGEICON.getImageDimension(); 129 icon = getImageIconBounded(iconDimension); 130 a.putValue(Action.LARGE_ICON_KEY, icon); 131 } 132 133 /** 134 * Set both icons of an Action 135 * @param a The action for the icons 136 * @param addresource Adds an resource named "ImageResource" if <code>true</code> 137 * @since 10369 138 */ 139 public void attachImageIcon(AbstractAction a, boolean addresource) { 140 attachImageIcon(a); 141 if (addresource) { 142 a.putValue("ImageResource", this); 143 } 144 } 145 146 /** 147 * Returns the image icon at default dimension. 148 * @return the image icon at default dimension 149 */ 150 public ImageIcon getImageIcon() { 151 return getImageIcon(DEFAULT_DIMENSION); 152 } 153 154 /** 155 * Get an ImageIcon object for the image of this resource 156 * @param dim The requested dimensions. Use (-1,-1) for the original size and (width, -1) 157 * to set the width, but otherwise scale the image proportionally. 158 * @return ImageIcon object for the image of this resource, scaled according to dim 159 */ 160 public ImageIcon getImageIcon(Dimension dim) { 161 if (dim.width < -1 || dim.width == 0 || dim.height < -1 || dim.height == 0) 162 throw new IllegalArgumentException(dim+" is invalid"); 163 Image img = imgCache.get(dim); 164 if (img != null) { 165 return new ImageIcon(img); 166 } 167 BufferedImage bimg; 168 if (svg != null) { 169 Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim); 170 bimg = ImageProvider.createImageFromSvg(svg, realDim); 171 if (bimg == null) { 172 return null; 173 } 174 } else { 175 if (baseImage == null) throw new AssertionError(); 176 177 int realWidth = GuiSizesHelper.getSizeDpiAdjusted(dim.width); 178 int realHeight = GuiSizesHelper.getSizeDpiAdjusted(dim.height); 179 ImageIcon icon = new ImageIcon(baseImage); 180 if (realWidth == -1 && realHeight == -1) { 181 realWidth = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth()); 182 realHeight = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight()); 183 } else if (realWidth == -1) { 184 realWidth = Math.max(1, icon.getIconWidth() * realHeight / icon.getIconHeight()); 185 } else if (realHeight == -1) { 186 realHeight = Math.max(1, icon.getIconHeight() * realWidth / icon.getIconWidth()); 187 } 188 Image i = icon.getImage().getScaledInstance(realWidth, realHeight, Image.SCALE_SMOOTH); 189 bimg = new BufferedImage(realWidth, realHeight, BufferedImage.TYPE_INT_ARGB); 190 bimg.getGraphics().drawImage(i, 0, 0, null); 191 } 192 if (overlayInfo != null) { 193 for (ImageOverlay o : overlayInfo) { 194 o.process(bimg); 195 } 196 } 197 if (isDisabled) { 198 //Use default Swing functionality to make icon look disabled by applying grayscaling filter. 199 Icon disabledIcon = UIManager.getLookAndFeel().getDisabledIcon(null, new ImageIcon(bimg)); 200 201 //Convert Icon to ImageIcon with BufferedImage inside 202 bimg = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); 203 disabledIcon.paintIcon(new JPanel(), bimg.getGraphics(), 0, 0); 204 } 205 imgCache.put(dim, bimg); 206 return new ImageIcon(bimg); 207 } 208 209 /** 210 * Get image icon with a certain maximum size. The image is scaled down 211 * to fit maximum dimensions. (Keeps aspect ratio) 212 * 213 * @param maxSize The maximum size. One of the dimensions (width or height) can be -1, 214 * which means it is not bounded. 215 * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize 216 */ 217 public ImageIcon getImageIconBounded(Dimension maxSize) { 218 if (maxSize.width < -1 || maxSize.width == 0 || maxSize.height < -1 || maxSize.height == 0) 219 throw new IllegalArgumentException(maxSize+" is invalid"); 220 float sourceWidth; 221 float sourceHeight; 222 int maxWidth = maxSize.width; 223 int maxHeight = maxSize.height; 224 if (svg != null) { 225 sourceWidth = svg.getWidth(); 226 sourceHeight = svg.getHeight(); 227 } else { 228 if (baseImage == null) throw new AssertionError(); 229 ImageIcon icon = new ImageIcon(baseImage); 230 sourceWidth = icon.getIconWidth(); 231 sourceHeight = icon.getIconHeight(); 232 if (sourceWidth <= maxWidth) { 233 maxWidth = -1; 234 } 235 if (sourceHeight <= maxHeight) { 236 maxHeight = -1; 237 } 238 } 239 240 if (maxWidth == -1 && maxHeight == -1) 241 return getImageIcon(DEFAULT_DIMENSION); 242 else if (maxWidth == -1) 243 return getImageIcon(new Dimension(-1, maxHeight)); 244 else if (maxHeight == -1) 245 return getImageIcon(new Dimension(maxWidth, -1)); 246 else if (sourceWidth / maxWidth > sourceHeight / maxHeight) 247 return getImageIcon(new Dimension(maxWidth, -1)); 248 else 249 return getImageIcon(new Dimension(-1, maxHeight)); 250 } 251}