source: josm/trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java@ 8625

Last change on this file since 8625 was 8625, checked in by bastiK, 9 years ago

applied #11713 - JOSM raster layers filters plugin development (patch by Nipel-Crumple, slightly modified)

  • Property svn:eol-style set to native
File size: 12.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trc;
7
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.Font;
11import java.awt.Graphics2D;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.font.FontRenderContext;
15import java.awt.font.LineBreakMeasurer;
16import java.awt.font.TextAttribute;
17import java.awt.font.TextLayout;
18import java.awt.image.BufferedImage;
19import java.awt.image.BufferedImageOp;
20import java.awt.image.ConvolveOp;
21import java.awt.image.Kernel;
22import java.text.AttributedCharacterIterator;
23import java.text.AttributedString;
24import java.util.ArrayList;
25import java.util.Hashtable;
26import java.util.List;
27import java.util.Map;
28
29import javax.swing.AbstractAction;
30import javax.swing.Icon;
31import javax.swing.JCheckBoxMenuItem;
32import javax.swing.JComponent;
33import javax.swing.JLabel;
34import javax.swing.JMenu;
35import javax.swing.JMenuItem;
36import javax.swing.JPanel;
37import javax.swing.JPopupMenu;
38import javax.swing.JSeparator;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.actions.ImageryAdjustAction;
42import org.openstreetmap.josm.data.ProjectionBounds;
43import org.openstreetmap.josm.data.imagery.ImageryInfo;
44import org.openstreetmap.josm.data.imagery.OffsetBookmark;
45import org.openstreetmap.josm.data.preferences.ColorProperty;
46import org.openstreetmap.josm.data.preferences.IntegerProperty;
47import org.openstreetmap.josm.gui.MenuScroller;
48import org.openstreetmap.josm.gui.widgets.UrlLabel;
49import org.openstreetmap.josm.tools.GBC;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.Utils;
52
53public abstract class ImageryLayer extends Layer {
54
55 public static final ColorProperty PROP_FADE_COLOR = new ColorProperty(marktr("Imagery fade"), Color.white);
56 public static final IntegerProperty PROP_FADE_AMOUNT = new IntegerProperty("imagery.fade_amount", 0);
57 public static final IntegerProperty PROP_SHARPEN_LEVEL = new IntegerProperty("imagery.sharpen_level", 0);
58
59 private final List<ImageProcessor> imageProcessors = new ArrayList<>();
60
61 public static Color getFadeColor() {
62 return PROP_FADE_COLOR.get();
63 }
64
65 public static Color getFadeColorWithAlpha() {
66 Color c = PROP_FADE_COLOR.get();
67 return new Color(c.getRed(), c.getGreen(), c.getBlue(), PROP_FADE_AMOUNT.get()*255/100);
68 }
69
70 protected final ImageryInfo info;
71
72 protected Icon icon;
73
74 protected double dx = 0.0;
75 protected double dy = 0.0;
76
77 protected int sharpenLevel;
78
79 private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
80
81 /**
82 * Constructs a new {@code ImageryLayer}.
83 * @param info imagery info
84 */
85 public ImageryLayer(ImageryInfo info) {
86 super(info.getName());
87 this.info = info;
88 if (info.getIcon() != null) {
89 icon = new ImageProvider(info.getIcon()).setOptional(true).
90 setMaxHeight(ICON_SIZE).setMaxWidth(ICON_SIZE).get();
91 }
92 if (icon == null) {
93 icon = ImageProvider.get("imagery_small");
94 }
95 this.sharpenLevel = PROP_SHARPEN_LEVEL.get();
96 }
97
98 public double getPPD() {
99 if (!Main.isDisplayingMapView()) return Main.getProjection().getDefaultZoomInPPD();
100 ProjectionBounds bounds = Main.map.mapView.getProjectionBounds();
101 return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast);
102 }
103
104 public double getDx() {
105 return dx;
106 }
107
108 public double getDy() {
109 return dy;
110 }
111
112 public void setOffset(double dx, double dy) {
113 this.dx = dx;
114 this.dy = dy;
115 }
116
117 public void displace(double dx, double dy) {
118 setOffset(this.dx += dx, this.dy += dy);
119 }
120
121 public ImageryInfo getInfo() {
122 return info;
123 }
124
125 @Override
126 public Icon getIcon() {
127 return icon;
128 }
129
130 @Override
131 public boolean isMergable(Layer other) {
132 return false;
133 }
134
135 @Override
136 public void mergeFrom(Layer from) {
137 }
138
139 @Override
140 public Object getInfoComponent() {
141 JPanel panel = new JPanel(new GridBagLayout());
142 panel.add(new JLabel(getToolTipText()), GBC.eol());
143 if (info != null) {
144 String url = info.getUrl();
145 if (url != null) {
146 panel.add(new JLabel(tr("URL: ")), GBC.std().insets(0, 5, 2, 0));
147 panel.add(new UrlLabel(url), GBC.eol().insets(2, 5, 10, 0));
148 }
149 if (dx != 0 || dy != 0) {
150 panel.add(new JLabel(tr("Offset: ") + dx + ";" + dy), GBC.eol().insets(0, 5, 10, 0));
151 }
152 }
153 return panel;
154 }
155
156 public static ImageryLayer create(ImageryInfo info) {
157 switch(info.getImageryType()) {
158 case WMS:
159 case HTML:
160 return new WMSLayer(info);
161 case WMTS:
162 return new WMTSLayer(info);
163 case TMS:
164 case BING:
165 case SCANEX:
166 return new TMSLayer(info);
167 default:
168 throw new AssertionError(tr("Unsupported imagery type: {0}", info.getImageryType()));
169 }
170 }
171
172 class ApplyOffsetAction extends AbstractAction {
173 private transient OffsetBookmark b;
174
175 ApplyOffsetAction(OffsetBookmark b) {
176 super(b.name);
177 this.b = b;
178 }
179
180 @Override
181 public void actionPerformed(ActionEvent ev) {
182 setOffset(b.dx, b.dy);
183 Main.main.menu.imageryMenu.refreshOffsetMenu();
184 Main.map.repaint();
185 }
186 }
187
188 public class OffsetAction extends AbstractAction implements LayerAction {
189 @Override
190 public void actionPerformed(ActionEvent e) {
191 }
192
193 @Override
194 public Component createMenuComponent() {
195 return getOffsetMenuItem();
196 }
197
198 @Override
199 public boolean supportLayers(List<Layer> layers) {
200 return false;
201 }
202 }
203
204 public JMenuItem getOffsetMenuItem() {
205 JMenu subMenu = new JMenu(trc("layer", "Offset"));
206 subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
207 return (JMenuItem) getOffsetMenuItem(subMenu);
208 }
209
210 public JComponent getOffsetMenuItem(JComponent subMenu) {
211 JMenuItem adjustMenuItem = new JMenuItem(adjustAction);
212 if (OffsetBookmark.allBookmarks.isEmpty()) return adjustMenuItem;
213
214 subMenu.add(adjustMenuItem);
215 subMenu.add(new JSeparator());
216 boolean hasBookmarks = false;
217 int menuItemHeight = 0;
218 for (OffsetBookmark b : OffsetBookmark.allBookmarks) {
219 if (!b.isUsable(this)) {
220 continue;
221 }
222 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
223 if (Utils.equalsEpsilon(b.dx, dx) && Utils.equalsEpsilon(b.dy, dy)) {
224 item.setSelected(true);
225 }
226 subMenu.add(item);
227 menuItemHeight = item.getPreferredSize().height;
228 hasBookmarks = true;
229 }
230 if (menuItemHeight > 0) {
231 if (subMenu instanceof JMenu) {
232 MenuScroller.setScrollerFor((JMenu) subMenu);
233 } else if (subMenu instanceof JPopupMenu) {
234 MenuScroller.setScrollerFor((JPopupMenu) subMenu);
235 }
236 }
237 return hasBookmarks ? subMenu : adjustMenuItem;
238 }
239
240 public BufferedImage sharpenImage(BufferedImage img) {
241 if (sharpenLevel <= 0) return img;
242 int width = img.getWidth(null);
243 int height = img.getHeight(null);
244 BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
245 tmp.getGraphics().drawImage(img, 0, 0, null);
246 Kernel kernel;
247 if (sharpenLevel == 1) {
248 kernel = new Kernel(3, 3, new float[] {-0.25f, -0.5f, -0.25f, -0.5f, 4, -0.5f, -0.25f, -0.5f, -0.25f});
249 } else {
250 kernel = new Kernel(3, 3, new float[] {-0.5f, -1, -0.5f, -1, 7, -1, -0.5f, -1, -0.5f});
251 }
252 BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
253 return op.filter(tmp, null);
254 }
255
256 /**
257 * This method adds the {@link ImageProcessor} to this Layer
258 *
259 * @param processor that processes the image
260 *
261 * @return true if processor was added, false otherwise
262 */
263 public boolean addImageProcessor(ImageProcessor processor) {
264 return imageProcessors.add(processor);
265 }
266
267 /**
268 * This method removes given {@link ImageProcessor} from this layer
269 *
270 * @param processor which is needed to be removed
271 *
272 * @return true if processor was removed
273 */
274 public boolean removeImageProcessor(ImageProcessor processor) {
275 return imageProcessors.remove(processor);
276 }
277
278 /**
279 * This method gets all {@link ImageProcessor}s of the layer
280 *
281 * @return list of image processors without removed one
282 */
283 public List<ImageProcessor> getImageProcessors() {
284 return imageProcessors;
285 }
286
287 /**
288 * Applies all the chosen {@link ImageProcessor}s to the image
289 *
290 * @param img - image which should be changed
291 *
292 * @return the new changed image
293 */
294 public BufferedImage applyImageProcessors(BufferedImage img) {
295 for (ImageProcessor processor : imageProcessors) {
296 img = processor.process(img);
297 }
298 return img;
299 }
300
301 /**
302 * Draws a red error tile when imagery tile cannot be fetched.
303 * @param img The buffered image
304 * @param message Additional error message to display
305 */
306 public void drawErrorTile(BufferedImage img, String message) {
307 Graphics2D g = (Graphics2D) img.getGraphics();
308 g.setColor(Color.RED);
309 g.fillRect(0, 0, img.getWidth(), img.getHeight());
310 g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(24.0f));
311 g.setColor(Color.BLACK);
312
313 String text = tr("ERROR");
314 g.drawString(text, (img.getWidth() - g.getFontMetrics().stringWidth(text)) / 2, g.getFontMetrics().getHeight()+5);
315 if (message != null) {
316 float drawPosY = 2.5f*g.getFontMetrics().getHeight()+10;
317 if (!message.contains(" ")) {
318 g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(18.0f));
319 g.drawString(message, 5, (int) drawPosY);
320 } else {
321 // Draw message on several lines
322 Map<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();
323 map.put(TextAttribute.FAMILY, "Serif");
324 map.put(TextAttribute.SIZE, new Float(18.0));
325 AttributedString vanGogh = new AttributedString(message, map);
326 // Create a new LineBreakMeasurer from the text
327 AttributedCharacterIterator paragraph = vanGogh.getIterator();
328 int paragraphStart = paragraph.getBeginIndex();
329 int paragraphEnd = paragraph.getEndIndex();
330 FontRenderContext frc = g.getFontRenderContext();
331 LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
332 // Set break width to width of image with some margin
333 float breakWidth = img.getWidth()-10;
334 // Set position to the index of the first character in the text
335 lineMeasurer.setPosition(paragraphStart);
336 // Get lines until the entire paragraph has been displayed
337 while (lineMeasurer.getPosition() < paragraphEnd) {
338 // Retrieve next layout
339 TextLayout layout = lineMeasurer.nextLayout(breakWidth);
340
341 // Compute pen x position
342 float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance();
343
344 // Move y-coordinate by the ascent of the layout
345 drawPosY += layout.getAscent();
346
347 // Draw the TextLayout at (drawPosX, drawPosY)
348 layout.draw(g, drawPosX, drawPosY);
349
350 // Move y-coordinate in preparation for next layout
351 drawPosY += layout.getDescent() + layout.getLeading();
352 }
353 }
354 }
355 }
356
357 @Override
358 public void destroy() {
359 super.destroy();
360 adjustAction.destroy();
361 }
362}
Note: See TracBrowser for help on using the repository browser.