001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import static org.openstreetmap.josm.tools.Utils.equal;
005
006import java.awt.Image;
007import java.awt.Rectangle;
008import java.awt.image.BufferedImage;
009
010import javax.swing.ImageIcon;
011
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProvider;
014import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProviderResult;
015import org.openstreetmap.josm.gui.util.GuiHelper;
016import org.openstreetmap.josm.tools.ImageProvider;
017import org.openstreetmap.josm.tools.ImageProvider.ImageCallback;
018import org.openstreetmap.josm.tools.Utils;
019
020public class MapImage {
021    
022    private static final int MAX_SIZE = 48;
023    
024    /**
025     * ImageIcon can change while the image is loading.
026     */
027    private BufferedImage img;
028
029    /**
030     * The 5 following fields are only used to check for equality.
031     */
032    public int alpha = 255;
033    public String name;
034    public StyleSource source;
035    public int width = -1;
036    public int height = -1;
037
038    private boolean temporary;
039    private Image disabledImg;
040
041    public MapImage(String name, StyleSource source) {
042        this.name = name;
043        this.source = source;
044    }
045
046    public Image getDisabled() {
047        if (disabledImg != null)
048            return disabledImg;
049        if (img == null)
050            getImage(); // fix #7498 ?
051        disabledImg = GuiHelper.getDisabledImage(img);
052        return disabledImg;
053    }
054
055    public BufferedImage getImage() {
056        if (img != null)
057            return img;
058        temporary = false;
059        new ImageProvider(name)
060                .setDirs(MapPaintStyles.getIconSourceDirs(source))
061                .setId("mappaint."+source.getPrefName())
062                .setArchive(source.zipIcons)
063                .setInArchiveDir(source.getZipEntryDirName())
064                .setWidth(width)
065                .setHeight(height)
066                .setOptional(true)
067                .getInBackground(new ImageCallback() {
068                    @Override
069                    public void finished(ImageIcon result) {
070                        synchronized (MapImage.this) {
071                            if (result == null) {
072                                ImageIcon noIcon = MapPaintStyles.getNoIcon_Icon(source);
073                                img = noIcon == null ? null : (BufferedImage) noIcon.getImage();
074                            } else {
075                                img = (BufferedImage) result.getImage();
076                            }
077                            if (temporary) {
078                                Main.map.mapView.preferenceChanged(null); // otherwise repaint is ignored, because layer hasn't changed
079                                Main.map.mapView.repaint();
080                            }
081                            temporary = false;
082                        }
083                    }
084                }
085        );
086        synchronized (this) {
087            if (img == null) {
088                img = (BufferedImage) ImageProvider.get("clock").getImage();
089                temporary = true;
090            }
091        }
092        return img;
093    }
094
095    public int getWidth() {
096        return getImage().getWidth(null);
097    }
098
099    public int getHeight() {
100        return getImage().getHeight(null);
101    }
102
103    public float getAlphaFloat() {
104        return Utils.color_int2float(alpha);
105    }
106
107    /**
108     * Returns true, if image is not completely loaded and getImage() returns a temporary image.
109     */
110    public boolean isTemporary() {
111        return temporary;
112    }
113
114    protected class MapImageBoxProvider implements BoxProvider {
115        @Override
116        public BoxProviderResult get() {
117            return new BoxProviderResult(box(), temporary);
118        }
119
120        private Rectangle box() {
121            int w = getWidth(), h = getHeight();
122            if (mustRescale(getImage())) {
123                w = 16;
124                h = 16;
125            }
126            return new Rectangle(-w/2, -h/2, w, h);
127        }
128
129        private MapImage getParent() {
130            return MapImage.this;
131        }
132
133        @Override
134        public int hashCode() {
135            return MapImage.this.hashCode();
136        }
137
138        @Override
139        public boolean equals(Object obj) {
140            if (!(obj instanceof BoxProvider))
141                return false;
142            if (obj instanceof MapImageBoxProvider) {
143                MapImageBoxProvider other = (MapImageBoxProvider) obj;
144                return MapImage.this.equals(other.getParent());
145            } else if (temporary) {
146                return false;
147            } else {
148                final BoxProvider other = (BoxProvider) obj;
149                BoxProviderResult resultOther = other.get();
150                if (resultOther.isTemporary()) return false;
151                return box().equals(resultOther.getBox());
152            }
153        }
154    }
155
156    public BoxProvider getBoxProvider() {
157        return new MapImageBoxProvider();
158    }
159    
160    /**
161     * Returns the really displayed node icon for this {@code MapImage}.
162     * @param disabled {@code} true to request disabled version, {@code false} for the standard version
163     * @return The scaled down version to 16x16 pixels if the image height and width exceeds 48 pixels and no size has been explicitely specified
164     * @since 6174
165     */
166    public Image getDisplayedNodeIcon(boolean disabled) {
167        final Image image = disabled ? getDisabled() : getImage();
168        // Scale down large (.svg) images to 16x16 pixels if no size is explicitely specified
169        if (mustRescale(image)) {
170            return ImageProvider.createBoundedImage(image, 16);
171        } else {
172            return image;
173        }
174    }
175    
176    private boolean mustRescale(Image image) {
177        return ((width  == -1 && image.getWidth(null) > MAX_SIZE) 
178             && (height == -1 && image.getHeight(null) > MAX_SIZE));
179    }
180
181    @Override
182    public boolean equals(Object obj) {
183        if (obj == null || getClass() != obj.getClass())
184            return false;
185        final MapImage other = (MapImage) obj;
186        // img changes when image is fully loaded and can't be used for equality check.
187        return  alpha == other.alpha &&
188                equal(name, other.name) &&
189                equal(source, other.source) &&
190                width == other.width &&
191                height == other.height;
192    }
193
194    @Override
195    public int hashCode() {
196        int hash = 7;
197        hash = 67 * hash + alpha;
198        hash = 67 * hash + name.hashCode();
199        hash = 67 * hash + source.hashCode();
200        hash = 67 * hash + width;
201        hash = 67 * hash + height;
202        return hash;
203    }
204
205    @Override
206    public String toString() {
207        return name;
208    }
209}