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}