001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import java.awt.Image;
005import java.awt.Rectangle;
006import java.awt.image.BufferedImage;
007import java.util.Objects;
008
009import javax.swing.ImageIcon;
010
011import org.openstreetmap.josm.Main;
012import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProvider;
013import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProviderResult;
014import org.openstreetmap.josm.gui.util.GuiHelper;
015import org.openstreetmap.josm.tools.ImageProvider;
016import org.openstreetmap.josm.tools.ImageProvider.ImageCallback;
017import org.openstreetmap.josm.tools.Utils;
018
019public class MapImage {
020
021    private static final int MAX_SIZE = 48;
022
023    /**
024     * ImageIcon can change while the image is loading.
025     */
026    private BufferedImage img;
027
028    /**
029     * The 5 following fields are only used to check for equality.
030     */
031    public int alpha = 255;
032    public String name;
033    public StyleSource source;
034    public int width = -1;
035    public int height = -1;
036
037    private boolean temporary;
038    private Image disabledImgCache;
039
040    public MapImage(String name, StyleSource source) {
041        this.name = name;
042        this.source = source;
043    }
044
045    public Image getDisabled() {
046        if (disabledImgCache != null)
047                return disabledImgCache;
048        if (img == null)
049            getImage(); // fix #7498 ?
050        disabledImgCache = GuiHelper.getDisabledImage(img);
051        return disabledImgCache;
052    }
053
054    public BufferedImage getImage() {
055        if (img != null)
056            return img;
057        temporary = false;
058        new ImageProvider(name)
059                .setDirs(MapPaintStyles.getIconSourceDirs(source))
060                .setId("mappaint."+source.getPrefName())
061                .setArchive(source.zipIcons)
062                .setInArchiveDir(source.getZipEntryDirName())
063                .setWidth(width)
064                .setHeight(height)
065                .setOptional(true)
066                .getInBackground(new ImageCallback() {
067                    @Override
068                    public void finished(ImageIcon result) {
069                        synchronized (MapImage.this) {
070                            if (result == null) {
071                                ImageIcon noIcon = MapPaintStyles.getNoIcon_Icon(source);
072                                img = noIcon == null ? null : (BufferedImage) noIcon.getImage();
073                            } else {
074                                img = (BufferedImage) result.getImage();
075                            }
076                            if (temporary) {
077                                disabledImgCache = null;
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                Objects.equals(name, other.name) &&
189                Objects.equals(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}