- Timestamp:
- 2020-05-23T21:19:14+02:00 (5 years ago)
- Location:
- trunk
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/tools/GuiSizesHelper.java
r12846 r16486 55 55 public static float getPixelDensity() { 56 56 return getScreenDPI() / 96f; 57 } 58 59 /** 60 * Sets coefficient of monitor pixel density. 61 * @param pixelDensity coefficient of monitor pixel density to be set. 62 */ 63 public static void setPixelDensity(float pixelDensity) { 64 screenDPI = pixelDensity * 96f; 57 65 } 58 66 -
trunk/src/org/openstreetmap/josm/tools/HiDPISupport.java
r16331 r16486 33 33 private static final Constructor<? extends Image> baseMultiResolutionImageConstructor; 34 34 private static final Method resolutionVariantsMethod; 35 private static final Method resolutionVariantMethod; 35 36 36 37 static { … … 38 39 baseMultiResolutionImageConstructor = initBaseMultiResolutionImageConstructor(); 39 40 resolutionVariantsMethod = initResolutionVariantsMethod(); 41 resolutionVariantMethod = initResolutionVariantMethod(); 40 42 } 41 43 … … 57 59 double uiScale = getHiDPIScale(); 58 60 if (uiScale != 1.0 && baseMultiResolutionImageConstructor != null) { 59 ImageIcon zoomed = ir.getImageIcon (new Dimension(61 ImageIcon zoomed = ir.getImageIconAlreadyScaled(new Dimension( 60 62 (int) Math.round(base.getWidth(null) * uiScale), 61 (int) Math.round(base.getHeight(null) * uiScale)), false );63 (int) Math.round(base.getHeight(null) * uiScale)), false, true); 62 64 Image mrImg = getMultiResolutionImage(Arrays.asList(base, zoomed.getImage())); 63 65 if (mrImg != null) return mrImg; … … 139 141 140 142 /** 143 * Wrapper for method <code>java.awt.image.MultiResolutionImage#getResolutionVariant(double destImageWidth, double destImageHeight)</code>. 144 * <p> 145 * Will return the argument, in case it is not a multi-resolution image. 146 * @param img the image 147 * @return if <code>img</code> is a <code>java.awt.image.BaseMultiResolutionImage</code>, 148 * then the result of the method <code>#getResolutionVariant(destImageWidth, destImageHeight)</code>, 149 * otherwise the image itself 150 */ 151 public static Image getResolutionVariant(Image img, double destImageWidth, double destImageHeight) { 152 if (baseMultiResolutionImageClass == null || resolutionVariantsMethod == null) { 153 return img; 154 } 155 if (baseMultiResolutionImageClass.isInstance(img)) { 156 try { 157 return (Image) resolutionVariantMethod.invoke(img, destImageWidth, destImageHeight); 158 } catch (IllegalAccessException | InvocationTargetException ex) { 159 Logging.error("Unexpected error while calling method: " + ex); 160 } 161 } 162 return img; 163 } 164 165 /** 141 166 * Detect the GUI scale for HiDPI mode. 142 167 * <p> … … 145 170 * @return the GUI scale for HiDPI mode, a value of 1.0 means standard mode. 146 171 */ 147 privatestatic double getHiDPIScale() {172 static double getHiDPIScale() { 148 173 if (GraphicsEnvironment.isHeadless()) 149 174 return 1.0; … … 237 262 } 238 263 } 264 265 private static Method initResolutionVariantMethod() { 266 try { 267 return baseMultiResolutionImageClass != null 268 ? baseMultiResolutionImageClass.getMethod("getResolutionVariant", Double.TYPE, Double.TYPE) 269 : null; 270 } catch (NoSuchMethodException ex) { 271 Logging.error("Cannot find expected method: " + ex); 272 return null; 273 } 274 } 239 275 } -
trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java
r12782 r16486 3 3 4 4 import java.awt.Dimension; 5 import java.awt.Image; 5 6 import java.awt.image.BufferedImage; 7 import java.util.List; 6 8 7 9 import javax.swing.ImageIcon; … … 67 69 @Override 68 70 public BufferedImage process(BufferedImage ground) { 71 return process(ground, false); 72 } 73 74 /** 75 * Handle overlay. The image passed as argument is modified! 76 * 77 * @param ground the base image for the overlay (gets modified!) 78 * @param highResolution whether the high resolution variant should be used to overlay 79 * @return the modified image (same as argument) 80 */ 81 BufferedImage process(BufferedImage ground, boolean highResolution) { 69 82 /* get base dimensions for calculation */ 70 83 int w = ground.getWidth(); … … 78 91 height = (int) (h*(offsetBottom-offsetTop)); 79 92 } 80 ImageIcon overlay;81 93 image = new ImageProvider(image).setMaxSize(new Dimension(width, height)); 82 overlay = image.get(); 94 ImageIcon overlay = image.get(); 95 if (highResolution) { 96 List<Image> resolutionVariants = HiDPISupport.getResolutionVariants(overlay.getImage()); 97 if (resolutionVariants.size() >= 2) { 98 overlay = new ImageIcon(resolutionVariants.get(1)); 99 } 100 } 83 101 int x, y; 84 102 if (width == -1 && offsetLeft < 0) { -
trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
r16436 r16486 1318 1318 */ 1319 1319 public static Cursor getCursor(String name, String overlay) { 1320 ImageIcon img = get("cursor", name);1321 if (overlay != null) {1322 img = new ImageProvider("cursor", name).setMaxSize(ImageSizes.CURSOR)1323 .addOverlay(new ImageOverlay(new ImageProvider("cursor/modifier/" + overlay)1324 .setMaxSize(ImageSizes.CURSOROVERLAY))).get();1325 }1326 1320 if (GraphicsEnvironment.isHeadless()) { 1327 1321 Logging.debug("Cursors are not available in headless mode. Returning null for ''{0}''", name); 1328 1322 return null; 1329 1323 } 1330 return Toolkit.getDefaultToolkit().createCustomCursor(img.getImage(), 1331 "crosshair".equals(name) ? new Point(10, 10) : new Point(3, 2), "Cursor"); 1324 1325 Point hotSpot = new Point(); 1326 Image image = getCursorImage(name, overlay, hotSpot); 1327 1328 return Toolkit.getDefaultToolkit().createCustomCursor(image, hotSpot, name); 1329 } 1330 1331 /** 1332 * Load a cursor image with a given file name, optionally decorated with an overlay image 1333 * 1334 * @param name the cursor image filename in "cursor" directory 1335 * @param overlay optional overlay image 1336 * @param hotSpot will be set to the properly scaled hotspot of the cursor 1337 * @return cursor with a given file name, optionally decorated with an overlay image 1338 */ 1339 static Image getCursorImage(String name, String overlay, /* out */ Point hotSpot) { 1340 ImageProvider imageProvider = new ImageProvider("cursor", name); 1341 if (overlay != null) { 1342 imageProvider 1343 .setMaxSize(ImageSizes.CURSOR) 1344 .addOverlay(new ImageOverlay(new ImageProvider("cursor/modifier/" + overlay) 1345 .setMaxSize(ImageSizes.CURSOROVERLAY))); 1346 } 1347 hotSpot.setLocation("crosshair".equals(name) ? new Point(10, 10) : new Point(3, 2)); 1348 ImageIcon imageIcon = imageProvider.get(); 1349 Image image = imageIcon.getImage(); 1350 int width = image.getWidth(null); 1351 int height = image.getHeight(null); 1352 1353 // AWT will resize the cursor to bestCursorSize internally anyway, but miss to scale the hotspot as well 1354 // (bug JDK-8238734). So let's do this ourselves, and also scale the hotspot accordingly. 1355 Dimension bestCursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(width, height); 1356 if (bestCursorSize.width != 0 && bestCursorSize.height != 0) { 1357 // In principle, we could pass the MultiResolutionImage itself to AWT, but due to bug JDK-8240568, 1358 // this results in bad alpha blending and thus jaggy edges. So let's select the best variant ourselves. 1359 image = HiDPISupport.getResolutionVariant(image, bestCursorSize.width, bestCursorSize.height); 1360 if (bestCursorSize.width != image.getWidth(null) || bestCursorSize.height != image.getHeight(null)) { 1361 image = image.getScaledInstance(bestCursorSize.width, bestCursorSize.height, Image.SCALE_DEFAULT); 1362 } 1363 1364 hotSpot.x = hotSpot.x * bestCursorSize.width / width; 1365 hotSpot.y = hotSpot.y * bestCursorSize.height / height; 1366 } 1367 1368 return image; 1332 1369 } 1333 1370 -
trunk/src/org/openstreetmap/josm/tools/ImageResource.java
r16207 r16486 154 154 * @since 12722 155 155 */ 156 public ImageIcon getImageIcon(Dimension dim, boolean multiResolution) { 156 ImageIcon getImageIcon(Dimension dim, boolean multiResolution) { 157 return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false); 158 } 159 160 /** 161 * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed 162 * to be already taken care of, so dim is already scaled accordingly. 163 * @param dim The requested dimensions. Use (-1,-1) for the original size and (width, -1) 164 * to set the width, but otherwise scale the image proportionally. 165 * @param multiResolution If true, return a multi-resolution image 166 * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}. 167 * When running Java 8, this flag has no effect and a plain image will be returned in any case. 168 * @param highResolution whether the high resolution variant should be used for overlays 169 * @return ImageIcon object for the image of this resource, scaled according to dim 170 */ 171 ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, boolean highResolution) { 157 172 CheckParameterUtil.ensureThat((dim.width > 0 || dim.width == -1) && (dim.height > 0 || dim.height == -1), 158 173 () -> dim + " is invalid"); 174 159 175 BufferedImage img = imgCache.get(dim); 160 176 if (img == null) { 161 177 if (svg != null) { 162 Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim); 163 img = ImageProvider.createImageFromSvg(svg, realDim); 178 img = ImageProvider.createImageFromSvg(svg, dim); 164 179 if (img == null) { 165 180 return null; … … 168 183 if (baseImage == null) throw new AssertionError(); 169 184 170 int realWidth = GuiSizesHelper.getSizeDpiAdjusted(dim.width);171 int realHeight = GuiSizesHelper.getSizeDpiAdjusted(dim.height);172 185 ImageIcon icon = new ImageIcon(baseImage); 173 if ( realWidth == -1 && realHeight == -1) {174 realWidth = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth());175 realHeight = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight());176 } else if ( realWidth == -1) {177 realWidth = Math.max(1, icon.getIconWidth() * realHeight / icon.getIconHeight());178 } else if ( realHeight == -1) {179 realHeight = Math.max(1, icon.getIconHeight() * realWidth / icon.getIconWidth());186 if (dim.width == -1 && dim.height == -1) { 187 dim.width = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth()); 188 dim.height = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight()); 189 } else if (dim.width == -1) { 190 dim.width = Math.max(1, icon.getIconWidth() * dim.height / icon.getIconHeight()); 191 } else if (dim.height == -1) { 192 dim.height = Math.max(1, icon.getIconHeight() * dim.width / icon.getIconWidth()); 180 193 } 181 Image i = icon.getImage().getScaledInstance( realWidth, realHeight, Image.SCALE_SMOOTH);182 img = new BufferedImage( realWidth, realHeight, BufferedImage.TYPE_INT_ARGB);194 Image i = icon.getImage().getScaledInstance(dim.width, dim.height, Image.SCALE_SMOOTH); 195 img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); 183 196 img.getGraphics().drawImage(i, 0, 0, null); 184 197 } 185 198 if (overlayInfo != null) { 186 199 for (ImageOverlay o : overlayInfo) { 187 o.process(img );200 o.process(img, highResolution); 188 201 } 189 202 } -
trunk/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java
r15897 r16486 2 2 package org.openstreetmap.josm.tools; 3 3 4 import static java.awt.image.BufferedImage.TYPE_INT_ARGB; 4 5 import static org.junit.Assert.assertEquals; 5 6 import static org.junit.Assert.assertFalse; 6 7 import static org.junit.Assert.assertNotNull; 7 8 import static org.junit.Assert.assertNull; 8 9 import static org.junit.Assert.assertTrue; 10 11 import java.awt.Color; 9 12 import java.awt.Dimension; 13 import java.awt.GraphicsEnvironment; 14 import java.awt.GridLayout; 15 import java.awt.Graphics; 16 import java.awt.Image; 17 import java.awt.Point; 18 import java.awt.Toolkit; 10 19 import java.awt.Transparency; 20 import java.awt.event.MouseEvent; 21 import java.awt.event.MouseListener; 11 22 import java.awt.image.BufferedImage; 12 23 import java.io.File; 13 24 import java.io.IOException; 14 25 import java.util.EnumSet; 26 import java.util.List; 15 27 import java.util.logging.Handler; 16 28 import java.util.logging.LogRecord; … … 18 30 19 31 import javax.swing.ImageIcon; 20 32 import javax.swing.JFrame; 33 import javax.swing.JPanel; 34 35 import org.junit.Before; 21 36 import org.junit.BeforeClass; 37 import org.junit.Ignore; 22 38 import org.junit.Rule; 23 39 import org.junit.Test; … … 72 88 } 73 89 90 @Before 91 public void resetPixelDensity() { 92 GuiSizesHelper.setPixelDensity(1.0f); 93 } 94 74 95 /** 75 96 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/9984">#9984</a> … … 149 170 assertNotNull(ImageProvider.getPadded(OsmUtils.createPrimitive("relation type=route route=railway"), iconSize, noDefault)); 150 171 } 172 173 /** 174 * Test getting a bounded icon given some UI scaling configured. 175 */ 176 @Test 177 public void testGetImageIconBounded() { 178 int scale = 2; 179 GuiSizesHelper.setPixelDensity(scale); 180 181 ImageProvider imageProvider = new ImageProvider("open").setOptional(true); 182 ImageResource resource = imageProvider.getResource(); 183 Dimension iconDimension = ImageProvider.ImageSizes.SMALLICON.getImageDimension(); 184 ImageIcon icon = resource.getImageIconBounded(iconDimension); 185 Image image = icon.getImage(); 186 List<Image> resolutionVariants = HiDPISupport.getResolutionVariants(image); 187 if (resolutionVariants.size() > 1) { 188 assertEquals(2, resolutionVariants.size()); 189 int expectedVirtualWidth = ImageProvider.ImageSizes.SMALLICON.getVirtualWidth(); 190 assertEquals(expectedVirtualWidth * scale, resolutionVariants.get(0).getWidth(null)); 191 assertEquals((int) Math.round(expectedVirtualWidth * scale * HiDPISupport.getHiDPIScale()), 192 resolutionVariants.get(1).getWidth(null)); 193 } 194 } 195 196 public static final int ORIGINAL_CURSOR_SIZE = 32; 197 198 /** 199 * Test getting an image for a crosshair cursor. 200 */ 201 @Test 202 public void testGetCursorImageForCrosshair() { 203 if (GraphicsEnvironment.isHeadless()) { 204 // TODO mock Toolkit.getDefaultToolkit().getBestCursorSize() 205 return; 206 } 207 Point hotSpot = new Point(); 208 Image image = ImageProvider.getCursorImage("crosshair", null, hotSpot); 209 assertCursorDimensionsCorrect(new Point.Double(10.0, 10.0), image, hotSpot); 210 } 211 212 /** 213 * Test getting an image for a custom cursor with overlay. 214 */ 215 @Test 216 public void testGetCursorImageWithOverlay() { 217 if (GraphicsEnvironment.isHeadless()) { 218 // TODO mock Toolkit.getDefaultToolkit().getBestCursorSize() 219 return; 220 } 221 Point hotSpot = new Point(); 222 Image image = ImageProvider.getCursorImage("normal", "selection", hotSpot); 223 assertCursorDimensionsCorrect(new Point.Double(3.0, 2.0), image, hotSpot); 224 BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getWidth(null), TYPE_INT_ARGB); 225 bufferedImage.getGraphics().drawImage(image, 0, 0, null); 226 227 // check that the square of 1/4 size right lower to the center has some non-emtpy pixels 228 boolean nonEmptyPixelExistsRightLowerToCenter = false; 229 for (int x = image.getWidth(null) / 2; x < image.getWidth(null) * 3 / 4; ++x) { 230 for (int y = image.getHeight(null) / 2; y < image.getWidth(null) * 3 / 4; ++y) { 231 if (bufferedImage.getRGB(x, y) != 0) 232 nonEmptyPixelExistsRightLowerToCenter = true; 233 } 234 } 235 assertTrue(nonEmptyPixelExistsRightLowerToCenter); 236 } 237 238 private void assertCursorDimensionsCorrect(Point.Double originalHotspot, Image image, Point hotSpot) { 239 Dimension bestCursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(ORIGINAL_CURSOR_SIZE, ORIGINAL_CURSOR_SIZE); 240 Image bestCursorImage = HiDPISupport.getResolutionVariant(image, bestCursorSize.width, bestCursorSize.height); 241 int bestCursorImageWidth = bestCursorImage.getWidth(null); 242 assertEquals((int) Math.round(bestCursorSize.getWidth()), bestCursorImageWidth); 243 int bestCursorImageHeight = bestCursorImage.getHeight(null); 244 assertEquals((int) Math.round(bestCursorSize.getHeight()), bestCursorImageHeight); 245 assertEquals(originalHotspot.x / ORIGINAL_CURSOR_SIZE * bestCursorImageWidth, hotSpot.x, 1 /* at worst one pixel off */); 246 assertEquals(originalHotspot.y / ORIGINAL_CURSOR_SIZE * bestCursorImageHeight, hotSpot.y, 1 /* at worst one pixel off */); 247 } 248 249 250 /** 251 * Test getting a cursor 252 */ 253 @Ignore("manual execution only, as the look of the cursor cannot be checked automatedly") 254 @Test 255 public void testGetCursor() throws InterruptedException { 256 JFrame frame = new JFrame(); 257 frame.setSize(500, 500); 258 frame.setLayout(new GridLayout(2, 2)); 259 JPanel leftUpperPanel = new JPanel(), rightUpperPanel = new JPanel(), leftLowerPanel = new JPanel(), rightLowerPanel = new JPanel(); 260 leftUpperPanel.setBackground(Color.DARK_GRAY); 261 rightUpperPanel.setBackground(Color.DARK_GRAY); 262 leftLowerPanel.setBackground(Color.DARK_GRAY); 263 rightLowerPanel.setBackground(Color.DARK_GRAY); 264 frame.add(leftUpperPanel); 265 frame.add(rightUpperPanel); 266 frame.add(leftLowerPanel); 267 frame.add(rightLowerPanel); 268 269 leftUpperPanel.setCursor(ImageProvider.getCursor("normal", "select_add")); // contains diagonal sensitive to alpha blending 270 rightUpperPanel.setCursor(ImageProvider.getCursor("crosshair", "joinway")); // combination of overlay and hotspot not top left 271 leftLowerPanel.setCursor(ImageProvider.getCursor("hand", "parallel_remove")); // reasonably nice bitmap cursor 272 rightLowerPanel.setCursor(ImageProvider.getCursor("rotate", null)); // ugly bitmap cursor, cannot do much here 273 274 frame.setVisible(true); 275 276 // hover over the four quadrant to observe different cursors 277 278 // draw red dot at hotspot when clicking 279 frame.addMouseListener(new MouseListener() { 280 @Override 281 public void mouseClicked(MouseEvent e) { 282 Graphics graphics = frame.getGraphics(); 283 graphics.setColor(Color.RED); 284 graphics.drawRect(e.getX(), e.getY(), 1, 1); 285 } 286 287 @Override 288 public void mousePressed(MouseEvent e) { } 289 290 @Override 291 public void mouseReleased(MouseEvent e) { } 292 293 @Override 294 public void mouseEntered(MouseEvent e) { } 295 296 @Override 297 public void mouseExited(MouseEvent e) { } 298 }); 299 Thread.sleep(9000); // test would time out after 10s 300 } 151 301 }
Note:
See TracChangeset
for help on using the changeset viewer.