Changeset 16946 in josm


Ignore:
Timestamp:
2020-08-28T20:16:25+02:00 (4 years ago)
Author:
simon04
Message:

fix #19706, fix #19725 - ImageProvider/ImageResource: extract ImageResizeMode, add HiDPI support to bounded/padded icons

Location:
trunk
Files:
2 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/tools/HiDPISupport.java

    r16877 r16946  
    5454     * @param base the base image
    5555     * @param ir a corresponding image resource
     56     * @param resizeMode how to size/resize the image
    5657     * @return multi-resolution image if necessary and possible, the base image otherwise
    5758     */
    58     public static Image getMultiResolutionImage(Image base, ImageResource ir) {
     59    public static Image getMultiResolutionImage(Image base, ImageResource ir, ImageResizeMode resizeMode) {
    5960        double uiScale = getHiDPIScale();
    6061        if (uiScale != 1.0 && baseMultiResolutionImageConstructor != null) {
    6162            ImageIcon zoomed = ir.getImageIconAlreadyScaled(new Dimension(
    6263                    (int) Math.round(base.getWidth(null) * uiScale),
    63                     (int) Math.round(base.getHeight(null) * uiScale)), false, true);
     64                    (int) Math.round(base.getHeight(null) * uiScale)), false, true, resizeMode);
    6465            Image mrImg = getMultiResolutionImage(Arrays.asList(base, zoomed.getImage()));
    6566            if (mrImg != null) return mrImg;
  • trunk/src/org/openstreetmap/josm/tools/ImageProvider.java

    r16924 r16946  
    660660        }
    661661        if (virtualMaxWidth != -1 || virtualMaxHeight != -1)
    662             return ir.getImageIconBounded(new Dimension(virtualMaxWidth, virtualMaxHeight), multiResolution);
     662            return ir.getImageIcon(new Dimension(virtualMaxWidth, virtualMaxHeight), multiResolution, ImageResizeMode.BOUNDED);
    663663        else
    664             return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight), multiResolution);
     664            return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight), multiResolution, ImageResizeMode.AUTO);
    665665    }
    666666
     
    14721472    }
    14731473
    1474     static BufferedImage createPaddedIcon(Image icon, Dimension iconSize) {
    1475         int backgroundRealWidth = GuiSizesHelper.getSizeDpiAdjusted(iconSize.width);
    1476         int backgroundRealHeight = GuiSizesHelper.getSizeDpiAdjusted(iconSize.height);
    1477         int iconRealWidth = icon.getWidth(null);
    1478         int iconRealHeight = icon.getHeight(null);
    1479         BufferedImage image = new BufferedImage(backgroundRealWidth, backgroundRealHeight, BufferedImage.TYPE_INT_ARGB);
    1480         double scaleFactor = Math.min(
    1481                 backgroundRealWidth / (double) iconRealWidth,
    1482                 backgroundRealHeight / (double) iconRealHeight);
    1483         Image scaledIcon;
    1484         final int scaledWidth;
    1485         final int scaledHeight;
    1486         if (scaleFactor < 1) {
    1487             // Scale icon such that it fits on background.
    1488             scaledWidth = (int) (iconRealWidth * scaleFactor);
    1489             scaledHeight = (int) (iconRealHeight * scaleFactor);
    1490             scaledIcon = icon.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
    1491         } else {
    1492             // Use original size, don't upscale.
    1493             scaledWidth = iconRealWidth;
    1494             scaledHeight = iconRealHeight;
    1495             scaledIcon = icon;
    1496         }
    1497         image.getGraphics().drawImage(scaledIcon,
    1498                 (backgroundRealWidth - scaledWidth) / 2,
    1499                 (backgroundRealHeight - scaledHeight) / 2, null);
    1500 
    1501         return image;
    1502     }
    1503 
    15041474    /**
    15051475     * Constructs an image from the given SVG data.
    15061476     * @param svg the SVG data
    15071477     * @param dim the desired image dimension
     1478     * @param resizeMode how to size/resize the image
    15081479     * @return an image from the given SVG data at the desired dimension.
    15091480     */
    1510     public static BufferedImage createImageFromSvg(SVGDiagram svg, Dimension dim) {
     1481    static BufferedImage createImageFromSvg(SVGDiagram svg, Dimension dim, ImageResizeMode resizeMode) {
    15111482        if (Logging.isTraceEnabled()) {
    15121483            Logging.trace("createImageFromSvg: {0} {1}", svg.getXMLBase(), dim);
     
    15141485        final float sourceWidth = svg.getWidth();
    15151486        final float sourceHeight = svg.getHeight();
    1516         final float realWidth;
    1517         final float realHeight;
    1518         if (dim.width >= 0) {
    1519             realWidth = dim.width;
    1520             if (dim.height >= 0) {
    1521                 realHeight = dim.height;
    1522             } else {
    1523                 realHeight = sourceHeight * realWidth / sourceWidth;
    1524             }
    1525         } else if (dim.height >= 0) {
    1526             realHeight = dim.height;
    1527             realWidth = sourceWidth * realHeight / sourceHeight;
    1528         } else {
    1529             realWidth = GuiSizesHelper.getSizeDpiAdjusted(sourceWidth);
    1530             realHeight = GuiSizesHelper.getSizeDpiAdjusted(sourceHeight);
    1531         }
    1532 
    1533         int roundedWidth = Math.round(realWidth);
    1534         int roundedHeight = Math.round(realHeight);
    1535         if (roundedWidth <= 0 || roundedHeight <= 0 || roundedWidth >= Integer.MAX_VALUE || roundedHeight >= Integer.MAX_VALUE) {
    1536             Logging.error("createImageFromSvg: {0} {1} realWidth={2} realHeight={3}",
    1537                     svg.getXMLBase(), dim, Float.toString(realWidth), Float.toString(realHeight));
     1487        if (sourceWidth <= 0 || sourceHeight <= 0) {
     1488            Logging.error("createImageFromSvg: {0} {1} sourceWidth={2} sourceHeight={3}", svg.getXMLBase(), dim, sourceWidth, sourceHeight);
    15381489            return null;
    15391490        }
    1540         BufferedImage img = new BufferedImage(roundedWidth, roundedHeight, BufferedImage.TYPE_INT_ARGB);
    1541         Graphics2D g = img.createGraphics();
    1542         g.setClip(0, 0, img.getWidth(), img.getHeight());
    1543         g.scale(realWidth / sourceWidth, realHeight / sourceHeight);
    1544         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    1545         try {
    1546             synchronized (getSvgUniverse()) {
    1547                 svg.render(g);
    1548             }
    1549         } catch (SVGException ex) {
    1550             Logging.log(Logging.LEVEL_ERROR, "Unable to load svg:", ex);
    1551             return null;
    1552         }
    1553         return img;
     1491        return resizeMode.createBufferedImage(dim, new Dimension((int) sourceWidth, (int) sourceHeight), g -> {
     1492            try {
     1493                synchronized (getSvgUniverse()) {
     1494                    svg.render(g);
     1495                }
     1496            } catch (SVGException ex) {
     1497                Logging.log(Logging.LEVEL_ERROR, "Unable to load svg:", ex);
     1498            }
     1499        });
    15541500    }
    15551501
  • trunk/src/org/openstreetmap/josm/tools/ImageResource.java

    r16926 r16946  
    2929
    3030    /**
    31      * Caches the image data for resized versions of the same image.
    32      */
    33     private final Map<Dimension, BufferedImage> imgCache = new ConcurrentHashMap<>(4);
     31     * Caches the image data for resized versions of the same image. The key is obtained using {@link ImageResizeMode#cacheKey(Dimension)}.
     32     */
     33    private final Map<Integer, BufferedImage> imgCache = new ConcurrentHashMap<>(4);
    3434    /**
    3535     * SVG diagram information in case of SVG vector image.
     
    138138     *         to set the width, but otherwise scale the image proportionally.
    139139     * @return ImageIcon object for the image of this resource, scaled according to dim
    140      * @see #getImageIconBounded(java.awt.Dimension, boolean)
     140     * @see #getImageIconBounded(java.awt.Dimension)
    141141     */
    142142    public ImageIcon getImageIcon(Dimension dim) {
    143         return getImageIcon(dim, true);
     143        return getImageIcon(dim, true, ImageResizeMode.AUTO);
    144144    }
    145145
     
    151151     * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}.
    152152     * When running Java 8, this flag has no effect and a plain image will be returned in any case.
     153     * @param resizeMode how to size/resize the image
    153154     * @return ImageIcon object for the image of this resource, scaled according to dim
    154155     * @since 12722
    155156     */
    156     ImageIcon getImageIcon(Dimension dim, boolean multiResolution) {
    157         return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false);
     157    ImageIcon getImageIcon(Dimension dim, boolean multiResolution, ImageResizeMode resizeMode) {
     158        return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode);
    158159    }
    159160
     
    167168     * When running Java 8, this flag has no effect and a plain image will be returned in any case.
    168169     * @param highResolution whether the high resolution variant should be used for overlays
     170     * @param resizeMode how to size/resize the image
    169171     * @return ImageIcon object for the image of this resource, scaled according to dim
    170172     */
    171     ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, boolean highResolution) {
     173    ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, boolean highResolution, ImageResizeMode resizeMode) {
    172174        CheckParameterUtil.ensureThat((dim.width > 0 || dim.width == -1) && (dim.height > 0 || dim.height == -1),
    173175                () -> dim + " is invalid");
    174176
    175         BufferedImage img = imgCache.get(dim);
     177        final int cacheKey = resizeMode.cacheKey(dim);
     178        BufferedImage img = imgCache.get(cacheKey);
    176179        if (img == null) {
    177180            if (svg != null) {
    178                 img = ImageProvider.createImageFromSvg(svg, dim);
     181                img = ImageProvider.createImageFromSvg(svg, dim, resizeMode);
    179182                if (img == null) {
    180183                    return null;
     
    182185            } else {
    183186                if (baseImage == null) throw new AssertionError();
    184 
    185187                ImageIcon icon = new ImageIcon(baseImage);
    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());
    193                 }
    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);
    196                 img.getGraphics().drawImage(i, 0, 0, null);
     188                img = resizeMode.createBufferedImage(dim, new Dimension(icon.getIconWidth(), icon.getIconHeight()),
     189                        g -> g.drawImage(icon.getImage(), 0, 0, null));
    197190            }
    198191            if (overlayInfo != null) {
     
    212205                disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0);
    213206            }
    214             imgCache.put(dim, img);
     207            imgCache.put(cacheKey, img);
    215208        }
    216209
     
    219212        else {
    220213            try {
    221                 Image mrImg = HiDPISupport.getMultiResolutionImage(img, this);
     214                Image mrImg = HiDPISupport.getMultiResolutionImage(img, this, resizeMode);
    222215                return new ImageIcon(mrImg);
    223216            } catch (NoClassDefFoundError e) {
     
    237230     * which means it is not bounded.
    238231     * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
    239      * @see #getImageIconBounded(java.awt.Dimension, boolean)
    240232     */
    241233    public ImageIcon getImageIconBounded(Dimension maxSize) {
    242         return getImageIconBounded(maxSize, true);
    243     }
    244 
    245     /**
    246      * Get image icon with a certain maximum size. The image is scaled down
    247      * to fit maximum dimensions. (Keeps aspect ratio)
    248      *
    249      * @param maxSize The maximum size. One of the dimensions (width or height) can be -1,
    250      * which means it is not bounded.
    251      * @param  multiResolution If true, return a multi-resolution image
    252      * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}.
    253      * When running Java 8, this flag has no effect and a plain image will be returned in any case.
    254      * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
    255      * @since 12722
    256      */
    257     public ImageIcon getImageIconBounded(Dimension maxSize, boolean multiResolution) {
    258         CheckParameterUtil.ensureThat((maxSize.width > 0 || maxSize.width == -1) && (maxSize.height > 0 || maxSize.height == -1),
    259                 () -> maxSize + " is invalid");
    260         float sourceWidth;
    261         float sourceHeight;
    262         int maxWidth = maxSize.width;
    263         int maxHeight = maxSize.height;
    264         if (svg != null) {
    265             sourceWidth = svg.getWidth();
    266             sourceHeight = svg.getHeight();
    267         } else {
    268             if (baseImage == null) throw new AssertionError();
    269             ImageIcon icon = new ImageIcon(baseImage);
    270             sourceWidth = icon.getIconWidth();
    271             sourceHeight = icon.getIconHeight();
    272             if (sourceWidth <= maxWidth) {
    273                 maxWidth = -1;
    274             }
    275             if (sourceHeight <= maxHeight) {
    276                 maxHeight = -1;
    277             }
    278         }
    279 
    280         if (maxWidth == -1 && maxHeight == -1)
    281             return getImageIcon(DEFAULT_DIMENSION, multiResolution);
    282         else if (maxWidth == -1)
    283             return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
    284         else if (maxHeight == -1)
    285             return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
    286         else if (sourceWidth / maxWidth > sourceHeight / maxHeight)
    287             return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
    288         else
    289             return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
     234        return getImageIcon(maxSize, true, ImageResizeMode.BOUNDED);
    290235    }
    291236
     
    297242     */
    298243    public ImageIcon getPaddedIcon(Dimension iconSize) {
    299         final ImageIcon imageIcon = getImageIcon(iconSize);
    300         if (imageIcon.getIconWidth() == iconSize.width && imageIcon.getIconHeight() == iconSize.height) {
    301             // fast path for square and svg icons
    302             return imageIcon;
    303         }
    304 
    305         final Dimension cacheKey = new Dimension(-iconSize.width, -iconSize.height); // use negative width/height for differentiation
    306         BufferedImage image = imgCache.get(cacheKey);
    307         if (image == null) {
    308             image = ImageProvider.createPaddedIcon(getImageIcon().getImage(), iconSize);
    309             imgCache.put(cacheKey, image);
    310         }
    311         return new ImageIcon(image);
     244        return getImageIcon(iconSize, true, ImageResizeMode.PADDED);
    312245    }
    313246
  • trunk/test/unit/org/openstreetmap/josm/tools/OsmPrimitiveImageProviderTest.java

    r16926 r16946  
    6161        assertNotNull(OsmPrimitiveImageProvider.getResource(OsmUtils.createPrimitive("way waterway=stream"), noDefault));
    6262        assertNotNull(OsmPrimitiveImageProvider.getResource(OsmUtils.createPrimitive("relation type=route route=railway"), noDefault));
    63         // a non-square svg icon
     63    }
     64
     65    /**
     66     * Unit test of {@link OsmPrimitiveImageProvider#getResource} for non-square images.
     67     */
     68    @Test
     69    public void testGetResourceNonSquare() {
    6470        final ImageIcon bankIcon = OsmPrimitiveImageProvider
    6571                .getResource(OsmUtils.createPrimitive("node amenity=bank"), Options.DEFAULT)
     
    6773        assertEquals(ImageProvider.ImageSizes.LARGEICON.getVirtualWidth(), bankIcon.getIconWidth());
    6874        assertEquals(ImageProvider.ImageSizes.LARGEICON.getVirtualHeight(), bankIcon.getIconHeight());
     75        final ImageIcon addressIcon = OsmPrimitiveImageProvider
     76                .getResource(OsmUtils.createPrimitive("node \"addr:housenumber\"=123"), Options.DEFAULT)
     77                .getPaddedIcon(ImageProvider.ImageSizes.LARGEICON.getImageDimension());
     78        assertEquals(ImageProvider.ImageSizes.LARGEICON.getVirtualWidth(), addressIcon.getIconWidth());
     79        assertEquals(ImageProvider.ImageSizes.LARGEICON.getVirtualHeight(), addressIcon.getIconHeight());
    6980    }
    7081}
Note: See TracChangeset for help on using the changeset viewer.