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

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

fix #17276 - catch and ignore ImagingOpException when applying image filters

  • Property svn:eol-style set to native
File size: 11.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6
7import java.awt.Component;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.image.BufferedImage;
11import java.awt.image.BufferedImageOp;
12import java.awt.image.ImagingOpException;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.List;
16import java.util.Locale;
17
18import javax.swing.AbstractAction;
19import javax.swing.Action;
20import javax.swing.BorderFactory;
21import javax.swing.Icon;
22import javax.swing.JCheckBoxMenuItem;
23import javax.swing.JComponent;
24import javax.swing.JLabel;
25import javax.swing.JMenu;
26import javax.swing.JMenuItem;
27import javax.swing.JPanel;
28import javax.swing.JPopupMenu;
29import javax.swing.JSeparator;
30import javax.swing.JTextField;
31
32import org.openstreetmap.josm.data.ProjectionBounds;
33import org.openstreetmap.josm.data.imagery.ImageryInfo;
34import org.openstreetmap.josm.data.preferences.IntegerProperty;
35import org.openstreetmap.josm.data.projection.ProjectionRegistry;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.MapView;
38import org.openstreetmap.josm.gui.MenuScroller;
39import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
40import org.openstreetmap.josm.gui.widgets.UrlLabel;
41import org.openstreetmap.josm.tools.GBC;
42import org.openstreetmap.josm.tools.ImageProcessor;
43import org.openstreetmap.josm.tools.ImageProvider;
44import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
45import org.openstreetmap.josm.tools.Logging;
46
47/**
48 * Abstract base class for background imagery layers ({@link WMSLayer}, {@link TMSLayer}, {@link WMTSLayer}).
49 *
50 * Handles some common tasks, like image filters, image processors, etc.
51 */
52public abstract class ImageryLayer extends Layer {
53
54 /**
55 * The default value for the sharpen filter for each imagery layer.
56 */
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 protected final ImageryInfo info;
62
63 protected Icon icon;
64
65 private final ImageryFilterSettings filterSettings = new ImageryFilterSettings();
66
67 /**
68 * Constructs a new {@code ImageryLayer}.
69 * @param info imagery info
70 */
71 public ImageryLayer(ImageryInfo info) {
72 super(info.getName());
73 this.info = info;
74 if (info.getIcon() != null) {
75 icon = new ImageProvider(info.getIcon()).setOptional(true).
76 setMaxSize(ImageSizes.LAYER).get();
77 }
78 if (icon == null) {
79 icon = ImageProvider.get("imagery_small");
80 }
81 for (ImageProcessor processor : filterSettings.getProcessors()) {
82 addImageProcessor(processor);
83 }
84 filterSettings.setSharpenLevel(1 + PROP_SHARPEN_LEVEL.get() / 2f);
85 }
86
87 public double getPPD() {
88 if (!MainApplication.isDisplayingMapView())
89 return ProjectionRegistry.getProjection().getDefaultZoomInPPD();
90 MapView mapView = MainApplication.getMap().mapView;
91 ProjectionBounds bounds = mapView.getProjectionBounds();
92 return mapView.getWidth() / (bounds.maxEast - bounds.minEast);
93 }
94
95 /**
96 * Returns imagery info.
97 * @return imagery info
98 */
99 public ImageryInfo getInfo() {
100 return info;
101 }
102
103 @Override
104 public Icon getIcon() {
105 return icon;
106 }
107
108 @Override
109 public boolean isMergable(Layer other) {
110 return false;
111 }
112
113 @Override
114 public void mergeFrom(Layer from) {
115 }
116
117 @Override
118 public Object getInfoComponent() {
119 JPanel panel = new JPanel(new GridBagLayout());
120 panel.add(new JLabel(getToolTipText()), GBC.eol());
121 if (info != null) {
122 List<List<String>> content = new ArrayList<>();
123 content.add(Arrays.asList(tr("Name"), info.getName()));
124 content.add(Arrays.asList(tr("Type"), info.getImageryType().getTypeString().toUpperCase(Locale.ENGLISH)));
125 content.add(Arrays.asList(tr("URL"), info.getUrl()));
126 content.add(Arrays.asList(tr("Id"), info.getId() == null ? "-" : info.getId()));
127 if (info.getMinZoom() != 0) {
128 content.add(Arrays.asList(tr("Min. zoom"), Integer.toString(info.getMinZoom())));
129 }
130 if (info.getMaxZoom() != 0) {
131 content.add(Arrays.asList(tr("Max. zoom"), Integer.toString(info.getMaxZoom())));
132 }
133 if (info.getDescription() != null) {
134 content.add(Arrays.asList(tr("Description"), info.getDescription()));
135 }
136 for (List<String> entry: content) {
137 panel.add(new JLabel(entry.get(0) + ':'), GBC.std());
138 panel.add(GBC.glue(5, 0), GBC.std());
139 panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL));
140 }
141 }
142 return panel;
143 }
144
145 protected JComponent createTextField(String text) {
146 if (text != null && text.matches("https?://.*")) {
147 return new UrlLabel(text);
148 }
149 JTextField ret = new JTextField(text);
150 ret.setEditable(false);
151 ret.setBorder(BorderFactory.createEmptyBorder());
152 return ret;
153 }
154
155 /**
156 * Create a new imagery layer
157 * @param info The imagery info to use as base
158 * @return The created layer
159 */
160 public static ImageryLayer create(ImageryInfo info) {
161 switch(info.getImageryType()) {
162 case WMS:
163 case WMS_ENDPOINT:
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 private static class ApplyOffsetAction extends AbstractAction {
177 private final transient OffsetMenuEntry menuEntry;
178
179 ApplyOffsetAction(OffsetMenuEntry menuEntry) {
180 super(menuEntry.getLabel());
181 this.menuEntry = menuEntry;
182 }
183
184 @Override
185 public void actionPerformed(ActionEvent ev) {
186 menuEntry.actionPerformed();
187 //TODO: Use some form of listeners for this.
188 MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
189 }
190 }
191
192 public class OffsetAction extends AbstractAction implements LayerAction {
193 @Override
194 public void actionPerformed(ActionEvent e) {
195 // Do nothing
196 }
197
198 @Override
199 public Component createMenuComponent() {
200 return getOffsetMenuItem();
201 }
202
203 @Override
204 public boolean supportLayers(List<Layer> layers) {
205 return false;
206 }
207 }
208
209 /**
210 * Create the menu item that should be added to the offset menu.
211 * It may have a sub menu of e.g. bookmarks added to it.
212 * @return The menu item to add to the imagery menu.
213 */
214 public JMenuItem getOffsetMenuItem() {
215 JMenu subMenu = new JMenu(trc("layer", "Offset"));
216 subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
217 return (JMenuItem) getOffsetMenuItem(subMenu);
218 }
219
220 /**
221 * Create the submenu or the menu item to set the offset of the layer.
222 *
223 * If only one menu item for this layer exists, it is returned by this method.
224 *
225 * If there are multiple, this method appends them to the subMenu and then returns the reference to the subMenu.
226 * @param subMenu The subMenu to use
227 * @return A single menu item to adjust the layer or the passed subMenu to which the menu items were appended.
228 */
229 public JComponent getOffsetMenuItem(JComponent subMenu) {
230 JMenuItem adjustMenuItem = new JMenuItem(getAdjustAction());
231 List<OffsetMenuEntry> usableBookmarks = getOffsetMenuEntries();
232 if (usableBookmarks.isEmpty()) {
233 return adjustMenuItem;
234 }
235
236 subMenu.add(adjustMenuItem);
237 subMenu.add(new JSeparator());
238 int menuItemHeight = 0;
239 for (OffsetMenuEntry b : usableBookmarks) {
240 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
241 item.setSelected(b.isActive());
242 subMenu.add(item);
243 menuItemHeight = item.getPreferredSize().height;
244 }
245 if (menuItemHeight > 0) {
246 if (subMenu instanceof JMenu) {
247 MenuScroller.setScrollerFor((JMenu) subMenu);
248 } else if (subMenu instanceof JPopupMenu) {
249 MenuScroller.setScrollerFor((JPopupMenu) subMenu);
250 }
251 }
252 return subMenu;
253 }
254
255 protected abstract Action getAdjustAction();
256
257 protected abstract List<OffsetMenuEntry> getOffsetMenuEntries();
258
259 /**
260 * Gets the settings for the filter that is applied to this layer.
261 * @return The filter settings.
262 * @since 10547
263 */
264 public ImageryFilterSettings getFilterSettings() {
265 return filterSettings;
266 }
267
268 /**
269 * This method adds the {@link ImageProcessor} to this Layer if it is not {@code null}.
270 *
271 * @param processor that processes the image
272 *
273 * @return true if processor was added, false otherwise
274 */
275 public boolean addImageProcessor(ImageProcessor processor) {
276 return processor != null && imageProcessors.add(processor);
277 }
278
279 /**
280 * This method removes given {@link ImageProcessor} from this layer
281 *
282 * @param processor which is needed to be removed
283 *
284 * @return true if processor was removed
285 */
286 public boolean removeImageProcessor(ImageProcessor processor) {
287 return imageProcessors.remove(processor);
288 }
289
290 /**
291 * Wraps a {@link BufferedImageOp} to be used as {@link ImageProcessor}.
292 * @param op the {@link BufferedImageOp}
293 * @param inPlace true to apply filter in place, i.e., not create a new {@link BufferedImage} for the result
294 * (the {@code op} needs to support this!)
295 * @return the {@link ImageProcessor} wrapper
296 */
297 public static ImageProcessor createImageProcessor(final BufferedImageOp op, final boolean inPlace) {
298 return image -> op.filter(image, inPlace ? image : null);
299 }
300
301 /**
302 * This method gets all {@link ImageProcessor}s of the layer
303 *
304 * @return list of image processors without removed one
305 */
306 public List<ImageProcessor> getImageProcessors() {
307 return imageProcessors;
308 }
309
310 /**
311 * Applies all the chosen {@link ImageProcessor}s to the image
312 *
313 * @param img - image which should be changed
314 *
315 * @return the new changed image
316 */
317 public BufferedImage applyImageProcessors(BufferedImage img) {
318 for (ImageProcessor processor : imageProcessors) {
319 try {
320 img = processor.process(img);
321 } catch (ImagingOpException e) {
322 Logging.error(e);
323 }
324 }
325 return img;
326 }
327
328 /**
329 * An additional menu entry in the imagery offset menu.
330 * @author Michael Zangl
331 * @see ImageryLayer#getOffsetMenuEntries()
332 * @since 13243
333 */
334 public interface OffsetMenuEntry {
335 /**
336 * Get the label to use for this menu item
337 * @return The label to display in the menu.
338 */
339 String getLabel();
340
341 /**
342 * Test whether this bookmark is currently active
343 * @return <code>true</code> if it is active
344 */
345 boolean isActive();
346
347 /**
348 * Load this bookmark
349 */
350 void actionPerformed();
351 }
352
353 @Override
354 public String toString() {
355 return getClass().getSimpleName() + " [info=" + info + ']';
356 }
357}
Note: See TracBrowser for help on using the repository browser.