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