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.OsmPrimitive; 010import org.openstreetmap.josm.data.osm.Relation; 011import org.openstreetmap.josm.data.osm.Way; 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(OsmPrimitive osm, MapPaintSettings paintSettings, StyledMapRenderer painter, 123 boolean selected, boolean outermember, boolean member) { 124 Color myColor = color; 125 if (osm instanceof Way) { 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((Way) 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}