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

Last change on this file since 8840 was 8840, checked in by Don-vip, 9 years ago

sonar - squid:S3052 - Fields should not be initialized to default values

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