001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.styleelement;
003
004import java.awt.Color;
005import java.awt.Image;
006import java.awt.image.BufferedImage;
007import java.util.Objects;
008
009import org.openstreetmap.josm.data.osm.IPrimitive;
010import org.openstreetmap.josm.data.osm.IWay;
011import org.openstreetmap.josm.data.osm.Relation;
012import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
013import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
014import org.openstreetmap.josm.data.preferences.IntegerProperty;
015import org.openstreetmap.josm.gui.mappaint.Cascade;
016import org.openstreetmap.josm.gui.mappaint.Environment;
017import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
018import org.openstreetmap.josm.spi.preferences.Config;
019import org.openstreetmap.josm.tools.CheckParameterUtil;
020import org.openstreetmap.josm.tools.ColorHelper;
021import org.openstreetmap.josm.tools.HiDPISupport;
022import org.openstreetmap.josm.tools.Utils;
023import org.openstreetmap.josm.tools.bugreport.BugReport;
024
025/**
026 * This is the style that defines how an area is filled.
027 */
028public class AreaElement extends StyleElement {
029
030    /**
031     * The default opacity for the fill. For historical reasons in range 0.255.
032     */
033    private static final IntegerProperty DEFAULT_FILL_ALPHA = new IntegerProperty("mappaint.fillalpha", 50);
034
035    /**
036     * If fillImage == null, color is the fill-color, otherwise
037     * an arbitrary color value sampled from the fillImage.
038     *
039     * The color may be fully transparent to indicate that the area should not be filled.
040     */
041    public Color color;
042
043    /**
044     * An image to cover this area. May be null to disable this feature.
045     */
046    public MapImage fillImage;
047
048    /**
049     * Fill the area only partially from the borders
050     * <p>
051     * Public access is discouraged.
052     * @see StyledMapRenderer#drawArea
053     */
054    public Float extent;
055
056    /**
057     * Areas smaller than this are filled no matter what value {@link #extent} has.
058     * <p>
059     * Public access is discouraged.
060     * @see StyledMapRenderer#drawArea
061     */
062    public Float extentThreshold;
063
064    protected AreaElement(Cascade c, Color color, MapImage fillImage, Float extent, Float extentThreshold) {
065        super(c, 1f);
066        CheckParameterUtil.ensureParameterNotNull(color);
067        this.color = color;
068        this.fillImage = fillImage;
069        this.extent = extent;
070        this.extentThreshold = extentThreshold;
071    }
072
073    /**
074     * Create a new {@link AreaElement}
075     * @param env The current style definitions
076     * @return The area element or <code>null</code> if the area should not be filled.
077     */
078    public static AreaElement create(final Environment env) {
079        final Cascade c = env.getCascade();
080        MapImage fillImage = null;
081        Color color;
082
083        IconReference iconRef = c.get(FILL_IMAGE, null, IconReference.class);
084        if (iconRef != null) {
085            fillImage = new MapImage(iconRef.iconName, iconRef.source, false);
086            Image img = fillImage.getImage(false);
087            // get base image from possible multi-resolution image, so we can
088            // cast to BufferedImage and get pixel value at the center of the image
089            img = HiDPISupport.getBaseImage(img);
090            try {
091                color = new Color(((BufferedImage) img).getRGB(
092                        img.getWidth(null) / 2, img.getHeight(null) / 2)
093                );
094            } catch (ArrayIndexOutOfBoundsException e) {
095                throw BugReport.intercept(e).put("env.osm", env.osm).put("iconRef", iconRef).put("fillImage", fillImage).put("img", img);
096            }
097
098            fillImage.alpha = Utils.clamp(Config.getPref().getInt("mappaint.fill-image-alpha", 255), 0, 255);
099            Integer pAlpha = ColorHelper.float2int(c.get(FILL_OPACITY, null, float.class));
100            if (pAlpha != null) {
101                fillImage.alpha = pAlpha;
102            }
103        } else {
104            color = c.get(FILL_COLOR, null, Color.class);
105            if (color != null) {
106                float defaultOpacity = ColorHelper.int2float(DEFAULT_FILL_ALPHA.get());
107                float opacity = c.get(FILL_OPACITY, defaultOpacity, Float.class);
108                color = ColorHelper.alphaMultiply(color, opacity);
109            }
110        }
111
112        if (color != null) {
113            Float extent = c.get(FILL_EXTENT, null, float.class);
114            Float extentThreshold = c.get(FILL_EXTENT_THRESHOLD, null, float.class);
115
116            return new AreaElement(c, color, fillImage, extent, extentThreshold);
117        } else {
118            return null;
119        }
120    }
121
122    @Override
123    public void paintPrimitive(IPrimitive osm, MapPaintSettings paintSettings, StyledMapRenderer painter,
124            boolean selected, boolean outermember, boolean member) {
125        Color myColor = color;
126        if (osm instanceof IWay) {
127            if (color != null) {
128                if (selected) {
129                    myColor = paintSettings.getSelectedColor(color.getAlpha());
130                } else if (outermember) {
131                    myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
132                }
133            }
134            painter.drawArea((IWay<?>) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled());
135        } else if (osm instanceof Relation) {
136            if (color != null && (selected || outermember)) {
137                myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
138            }
139            painter.drawArea((Relation) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled());
140        }
141    }
142
143    @Override
144    public boolean equals(Object obj) {
145        if (this == obj) return true;
146        if (obj == null || getClass() != obj.getClass()) return false;
147        if (!super.equals(obj)) return false;
148        AreaElement that = (AreaElement) obj;
149        return Objects.equals(color, that.color) &&
150                Objects.equals(fillImage, that.fillImage) &&
151                Objects.equals(extent, that.extent) &&
152                Objects.equals(extentThreshold, that.extentThreshold);
153    }
154
155    @Override
156    public int hashCode() {
157        return Objects.hash(super.hashCode(), color, fillImage, extent, extentThreshold);
158    }
159
160    @Override
161    public String toString() {
162        return "AreaElemStyle{" + super.toString() + "color=" + ColorHelper.color2html(color) +
163                " fillImage=[" + fillImage + "] extent=[" + extent + "] extentThreshold=[" + extentThreshold + "]}";
164    }
165}