Ticket #9995: hidpi-support.patch

File hidpi-support.patch, 33.1 KB (added by bastiK, 7 weeks ago)
  • src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

     
    8181import org.openstreetmap.josm.tools.ImageProvider;
    8282import org.openstreetmap.josm.tools.JosmRuntimeException;
    8383import org.openstreetmap.josm.tools.Logging;
     84import org.openstreetmap.josm.tools.HiDPISupport;
    8485import org.openstreetmap.josm.tools.Utils;
    8586import org.openstreetmap.josm.tools.bugreport.BugReport;
    8687
     
    416417                    g.setClip(oldClip);
    417418                }
    418419            } else {
    419                 TexturePaint texture = new TexturePaint(fillImage.getImage(disabled),
     420                Image img = fillImage.getImage(disabled);
     421                // TexturePaint requires BufferedImage -> get base image from
     422                // possible multi-resolution image
     423                img = HiDPISupport.getBaseImage(img);
     424                TexturePaint texture = new TexturePaint((BufferedImage) img,
    420425                        new Rectangle(0, 0, fillImage.getWidth(), fillImage.getHeight()));
    421426                g.setPaint(texture);
    422427                Float alpha = fillImage.getAlphaFloat();
     
    656661
    657662        double startOffset = computeStartOffset(phase, repeat);
    658663
    659         BufferedImage image = pattern.getImage(disabled);
     664        Image image = pattern.getImage(disabled);
    660665
    661666        path.visitClippedLine(repeat, (inLineOffset, start, end, startIsOldEnd) -> {
    662667            final double segmentLength = start.distanceToInView(end);
     
    11491154        }
    11501155        displayText(() -> {
    11511156            AffineTransform defaultTransform = g.getTransform();
    1152             g.setTransform(at);
     1157            g.transform(at);
    11531158            g.setFont(text.font);
    11541159            g.drawString(name, 0, 0);
    11551160            g.setTransform(defaultTransform);
  • src/org/openstreetmap/josm/gui/MapView.java

     
    88import java.awt.Graphics2D;
    99import java.awt.Point;
    1010import java.awt.Rectangle;
     11import java.awt.Shape;
    1112import java.awt.event.ComponentAdapter;
    1213import java.awt.event.ComponentEvent;
    1314import java.awt.event.KeyEvent;
     
    1415import java.awt.event.MouseAdapter;
    1516import java.awt.event.MouseEvent;
    1617import java.awt.event.MouseMotionListener;
     18import java.awt.geom.AffineTransform;
    1719import java.awt.geom.Area;
    1820import java.awt.image.BufferedImage;
    1921import java.beans.PropertyChangeEvent;
     
    510512    }
    511513
    512514    private void drawMapContent(Graphics g) {
     515        Graphics2D gg = (Graphics2D) g;
     516        AffineTransform trOrig = gg.getTransform();
     517        double uiScaleX = gg.getTransform().getScaleX();
     518        double uiScaleY = gg.getTransform().getScaleY();
     519        int width = (int) Math.round(getWidth() * uiScaleX);
     520        int height = (int) Math.round(getHeight() * uiScaleY);
     521
     522        AffineTransform trDef = AffineTransform.getScaleInstance(uiScaleX, uiScaleY);
     523        Shape scaledClip = trDef.createTransformedShape(g.getClip());
     524       
    513525        List<Layer> visibleLayers = layerManager.getVisibleLayersInZOrder();
    514526
    515527        int nonChangedLayersCount = 0;
     
    528540                && lastClipBounds.contains(g.getClipBounds())
    529541                && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size()));
    530542
    531         if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
    532             offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
     543        if (null == offscreenBuffer || offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) {
     544            offscreenBuffer = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    533545        }
    534546
    535         Graphics2D tempG = offscreenBuffer.createGraphics();
    536         tempG.setClip(g.getClip());
    537 
    538547        if (!canUseBuffer || nonChangedLayersBuffer == null) {
    539548            if (null == nonChangedLayersBuffer
    540                     || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
    541                 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
     549                    || nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) {
     550                nonChangedLayersBuffer = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    542551            }
    543552            Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
    544             g2.setClip(g.getClip());
     553            g2.setClip(scaledClip);
     554            g2.setTransform(trDef);
    545555            g2.setColor(PaintColors.getBackgroundColor());
    546             g2.fillRect(0, 0, getWidth(), getHeight());
     556            g2.fillRect(0, 0, width, height);
    547557
    548558            for (int i = 0; i < nonChangedLayersCount; i++) {
    549559                paintLayer(visibleLayers.get(i), g2);
     
    552562            // Maybe there were more unchanged layers then last time - draw them to buffer
    553563            if (nonChangedLayers.size() != nonChangedLayersCount) {
    554564                Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
    555                 g2.setClip(g.getClip());
     565                g2.setClip(scaledClip);
     566                g2.setTransform(trDef);
    556567                for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
    557568                    paintLayer(visibleLayers.get(i), g2);
    558569                }
     
    564575        lastViewID = getViewID();
    565576        lastClipBounds = g.getClipBounds();
    566577
     578        Graphics2D tempG = offscreenBuffer.createGraphics();
     579        tempG.setClip(scaledClip);
     580        tempG.setTransform(new AffineTransform());
    567581        tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
    568 
     582        tempG.setTransform(trDef);
     583       
    569584        for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
    570585            paintLayer(visibleLayers.get(i), tempG);
    571586        }
    572587
    573588        try {
    574             drawTemporaryLayers(tempG, getLatLonBounds(g.getClipBounds()));
     589            drawTemporaryLayers(tempG, getLatLonBounds(new Rectangle(
     590                    (int) Math.round(g.getClipBounds().x * uiScaleX),
     591                    (int) Math.round(g.getClipBounds().y * uiScaleY))));
    575592        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
    576593            BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn();
    577594        }
     
    596613        }
    597614
    598615        try {
    599             g.drawImage(offscreenBuffer, 0, 0, null);
     616            gg.setTransform(new AffineTransform(1, 0, 0, 1, trOrig.getTranslateX(), trOrig.getTranslateY()));
     617            gg.drawImage(offscreenBuffer, 0, 0, null);
    600618        } catch (ClassCastException e) {
    601619            // See #11002 and duplicate tickets. On Linux with Java >= 8 Many users face this error here:
    602620            //
     
    622640            //
    623641            // But the application seems to work fine after, so let's just log the error
    624642            Logging.error(e);
     643        } finally {
     644            gg.setTransform(trOrig);
    625645        }
    626646    }
    627647
  • src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaElement.java

     
    22package org.openstreetmap.josm.gui.mappaint.styleelement;
    33
    44import java.awt.Color;
     5import java.awt.Image;
     6import java.awt.image.BufferedImage;
    57import java.util.Objects;
    68
    79import org.openstreetmap.josm.Main;
     
    1517import org.openstreetmap.josm.gui.mappaint.Environment;
    1618import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
    1719import org.openstreetmap.josm.tools.CheckParameterUtil;
     20import org.openstreetmap.josm.tools.HiDPISupport;
    1821import org.openstreetmap.josm.tools.Utils;
    1922
    2023/**
     
    7881        IconReference iconRef = c.get(FILL_IMAGE, null, IconReference.class);
    7982        if (iconRef != null) {
    8083            fillImage = new MapImage(iconRef.iconName, iconRef.source, false);
    81 
    82             color = new Color(fillImage.getImage(false).getRGB(
     84            Image img = fillImage.getImage(false);
     85            // get base image from possible multi-resolution image, so we can
     86            // cast to BufferedImage and get pixel value at the center of the image
     87            img = HiDPISupport.getBaseImage(img);
     88            color = new Color(((BufferedImage) img).getRGB(
    8389                    fillImage.getWidth() / 2, fillImage.getHeight() / 2)
    8490            );
    8591
  • src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java

     
    3131    /**
    3232     * ImageIcon can change while the image is loading.
    3333     */
    34     private BufferedImage img;
     34    private Image img;
    3535
    3636    /**
    3737     * The alpha (opacity) value of the image. It is multiplied to the image alpha channel.
     
    101101     * @param disabled {@code} true to request disabled version, {@code false} for the standard version
    102102     * @return the image
    103103     */
    104     public BufferedImage getImage(boolean disabled) {
     104    public Image getImage(boolean disabled) {
    105105        if (disabled) {
    106106            return getDisabled();
    107107        } else {
     
    109109        }
    110110    }
    111111
    112     private BufferedImage getDisabled() {
     112    private Image getDisabled() {
    113113        if (disabledImgCache != null)
    114114                return disabledImgCache;
    115115        if (img == null)
     
    126126        return disabledImgCache;
    127127    }
    128128
    129     private BufferedImage getImage() {
     129    private Image getImage() {
    130130        if (img != null)
    131131            return img;
    132132        temporary = false;
     
    143143                        if (result == null) {
    144144                            source.logWarning(tr("Failed to locate image ''{0}''", name));
    145145                            ImageIcon noIcon = MapPaintStyles.getNoIconIcon(source);
    146                             img = noIcon == null ? null : (BufferedImage) noIcon.getImage();
     146                            img = noIcon == null ? null : noIcon.getImage();
    147147                        } else {
    148                             img = (BufferedImage) rescale(result.getImage());
     148                            img = rescale(result.getImage());
    149149                        }
    150150                        if (temporary) {
    151151                            disabledImgCache = null;
     
    159159        );
    160160        synchronized (this) {
    161161            if (img == null) {
    162                 img = (BufferedImage) ImageProvider.get("clock").getImage();
     162                img = ImageProvider.get("clock").getImage();
    163163                temporary = true;
    164164            }
    165165        }
  • src/org/openstreetmap/josm/gui/util/GuiHelper.java

     
    1111import java.awt.DisplayMode;
    1212import java.awt.Font;
    1313import java.awt.Frame;
     14import java.awt.GraphicsConfiguration;
    1415import java.awt.GraphicsDevice;
    1516import java.awt.GraphicsEnvironment;
    1617import java.awt.GridBagLayout;
     
    2324import java.awt.event.KeyEvent;
    2425import java.awt.event.MouseAdapter;
    2526import java.awt.event.MouseEvent;
     27import java.awt.geom.AffineTransform;
    2628import java.awt.image.FilteredImageSource;
    2729import java.lang.reflect.InvocationTargetException;
    2830import java.util.Arrays;
     
    6567import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
    6668import org.openstreetmap.josm.tools.LanguageInfo;
    6769import org.openstreetmap.josm.tools.Logging;
     70import org.openstreetmap.josm.tools.Utils;
    6871import org.openstreetmap.josm.tools.bugreport.BugReport;
    6972import org.openstreetmap.josm.tools.bugreport.ReportedException;
    7073
  • src/org/openstreetmap/josm/tools/HiDPISupport.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import java.awt.Dimension;
     5import java.awt.GraphicsConfiguration;
     6import java.awt.GraphicsEnvironment;
     7import java.awt.Image;
     8import java.awt.geom.AffineTransform;
     9import java.lang.reflect.Constructor;
     10import java.lang.reflect.InvocationTargetException;
     11import java.lang.reflect.Method;
     12import java.util.Arrays;
     13import java.util.Collections;
     14import java.util.List;
     15import java.util.Optional;
     16import java.util.function.Function;
     17import java.util.stream.Collectors;
     18import java.util.stream.IntStream;
     19
     20import javax.swing.ImageIcon;
     21
     22/**
     23 * Helper class for HiDPI support.
     24 *
     25 * Gives access to the class <code>BaseMultiResolutionImage</code> via reflection,
     26 * in case it is on classpath. This is to be expected for Java 9, but not for Java 8
     27 * runtime.
     28 *
     29 * @since xxx
     30 */
     31public class HiDPISupport {
     32
     33    private static volatile Optional<Class<? extends Image>> baseMultiResolutionImageClass;
     34    private static volatile Optional<Constructor<? extends Image>> baseMultiResolutionImageConstructor;
     35    private static volatile Optional<Method> resolutionVariantsMethod;
     36   
     37    public static Image getMultiResolutionImage(Image base, ImageResource ir) {
     38        double uiScale = getHiDPIScale();
     39        if (uiScale != 1.0 && getBaseMultiResolutionImageConstructor().isPresent()) {
     40            ImageIcon zoomed = ir.getImageIconBasic(new Dimension(
     41                    (int) Math.round(base.getWidth(null) * uiScale),
     42                    (int) Math.round(base.getHeight(null) * uiScale)));
     43            Image mrImg = getMultiResolutionImage(Arrays.asList(base, zoomed.getImage()));
     44            if (mrImg != null) return mrImg;
     45        }
     46        return base;
     47    }
     48
     49    public static Image getMultiResolutionImage(List<Image> imgs) {
     50        if (getBaseMultiResolutionImageConstructor().isPresent()) {
     51            Constructor<? extends Image> c = getBaseMultiResolutionImageConstructor().get();
     52            try {
     53                return c.newInstance((Object) imgs.toArray(new Image[0]));
     54            } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
     55                Logging.error("Unexpected error while instantiating object of class BaseMultiResolutionImage: " + ex);
     56            }
     57        }
     58        return imgs.get(0);
     59    }
     60
     61    public static Image getBaseImage(Image img) {
     62        if (!getBaseMultiResolutionImageClass().isPresent() || !getResolutionVariantsMethod().isPresent()) {
     63            return img;
     64        }
     65        if (getBaseMultiResolutionImageClass().get().isInstance(img)) {
     66            try {
     67                @SuppressWarnings("unchecked")
     68                List<Image> imgVars = (List) getResolutionVariantsMethod().get().invoke(img);
     69                if (!imgVars.isEmpty()) {
     70                    return imgVars.get(0);
     71                }
     72            } catch (IllegalAccessException | InvocationTargetException ex) {
     73                Logging.error("Unexpected error while calling method: " + ex);
     74            }
     75        }
     76        return img;
     77    }
     78   
     79    public static List<Image> getResolutionVariants(Image img) {
     80        if (!getBaseMultiResolutionImageClass().isPresent() || !getResolutionVariantsMethod().isPresent()) {
     81            return Collections.singletonList(img);
     82        }
     83        if (getBaseMultiResolutionImageClass().get().isInstance(img)) {
     84            try {
     85                @SuppressWarnings("unchecked")
     86                List<Image> imgVars = (List) getResolutionVariantsMethod().get().invoke(img);
     87                if (!imgVars.isEmpty()) {
     88                    return imgVars;
     89                }
     90            } catch (IllegalAccessException | InvocationTargetException ex) {
     91                Logging.error("Unexpected error while calling method: " + ex);
     92            }
     93        }
     94        return Collections.singletonList(img);
     95    }
     96   
     97    private static double getHiDPIScale() {
     98        GraphicsConfiguration gc = GraphicsEnvironment
     99                .getLocalGraphicsEnvironment()
     100                .getDefaultScreenDevice().
     101                getDefaultConfiguration();       
     102        AffineTransform transform = gc.getDefaultTransform();
     103        if (!Utils.equalsEpsilon(transform.getScaleX(), transform.getScaleY())) {
     104            Logging.warn("Unexpected ui transform: " + transform);
     105        }
     106        return transform.getScaleX();
     107    }
     108
     109    private static Optional<Class<? extends Image>> getBaseMultiResolutionImageClass() {
     110        if (baseMultiResolutionImageClass == null) {
     111            synchronized (HiDPISupport.class) {
     112                if (baseMultiResolutionImageClass == null) {
     113                    try {
     114                        @SuppressWarnings("unchecked")
     115                        Class<? extends Image> c = (Class) Class.forName("java.awt.image.BaseMultiResolutionImage");
     116                        baseMultiResolutionImageClass = Optional.ofNullable(c);
     117                    } catch (ClassNotFoundException ex) {
     118                        // class is not present in Java 8
     119                        baseMultiResolutionImageClass = Optional.empty();
     120                    }
     121                }
     122            }
     123        }
     124        return baseMultiResolutionImageClass;
     125    }
     126   
     127   
     128    private static Optional<Constructor<? extends Image>> getBaseMultiResolutionImageConstructor() {
     129        if (baseMultiResolutionImageConstructor == null) {
     130            synchronized (HiDPISupport.class) {
     131                if (baseMultiResolutionImageConstructor == null) {
     132                    getBaseMultiResolutionImageClass().ifPresent(klass -> {
     133                        try {
     134                            Constructor<? extends Image> constr = klass.getConstructor(Image[].class);
     135                            baseMultiResolutionImageConstructor = Optional.ofNullable(constr);
     136                        } catch (NoSuchMethodException ex) {
     137                            Logging.error("Cannot find expected constructor: " + ex);
     138                        }
     139                    });
     140                    if (baseMultiResolutionImageConstructor == null) {
     141                        baseMultiResolutionImageConstructor = Optional.empty();
     142                    }
     143                }
     144            }
     145        }
     146        return baseMultiResolutionImageConstructor;
     147    }
     148
     149    private static Optional<Method> getResolutionVariantsMethod() {
     150        if (resolutionVariantsMethod == null) {
     151            synchronized (HiDPISupport.class) {
     152                if (resolutionVariantsMethod == null) {
     153                    getBaseMultiResolutionImageClass().ifPresent(klass -> {
     154                        try {
     155                            Method m = klass.getMethod("getResolutionVariants");
     156                            resolutionVariantsMethod = Optional.ofNullable(m);
     157                        } catch (NoSuchMethodException ex) {
     158                            Logging.error("Cannot find expected method: "+ex);
     159                        }
     160                    });
     161                    if (resolutionVariantsMethod == null) {
     162                        resolutionVariantsMethod = Optional.empty();
     163                    }
     164                }
     165            }
     166        }
     167        return resolutionVariantsMethod;
     168    }
     169 
     170    public static Image processMRImage(Image img, Function<Image, Image> processor) {
     171        return processMRImages(Collections.singletonList(img), imgs -> processor.apply(imgs.get(0)));
     172    }
     173
     174    /**
     175     * Perform an operation on multi-resolution images.
     176     *
     177     * When input images are not multi-resolution, it will simply apply the processor once.
     178     * Otherwise, the processor will be called for each resolution variant and the
     179     * resulting images assembled to become the output multi-resolution image.
     180     * @param imgs input images, possibly multi-resolution
     181     * @param processor processor taking a list of plain images as input and returning
     182     * a single plain image as output
     183     * @return multi-resolution image assembled from the output of calls to <code>processor</code>
     184     * for each resolution variant
     185     */
     186    public static Image processMRImages(List<Image> imgs, Function<List<Image>, Image> processor) {
     187        CheckParameterUtil.ensureThat(imgs.size() >= 1, "at least on element expected");
     188        if (!getBaseMultiResolutionImageClass().isPresent()) {
     189            return processor.apply(imgs);
     190        }
     191        List<List<Image>> allVars = imgs.stream().map(HiDPISupport::getResolutionVariants).collect(Collectors.toList());
     192        int maxVariants = allVars.stream().mapToInt(lst -> lst.size()).max().getAsInt();
     193        if (maxVariants == 1)
     194            return processor.apply(imgs);
     195        List<Image> imgsProcessed = IntStream.range(0, maxVariants)
     196                .mapToObj(
     197                        k -> processor.apply(
     198                                allVars.stream().map(vars -> vars.get(k)).collect(Collectors.toList())
     199                        )
     200                ).collect(Collectors.toList());
     201        return getMultiResolutionImage(imgsProcessed);
     202    }
     203   
     204   
     205}
  • src/org/openstreetmap/josm/tools/ImageOverlay.java

     
    8080            height = (int) (h*(offsetBottom-offsetTop));
    8181        }
    8282        ImageIcon overlay;
    83         if (width != -1 || height != -1) {
    84             image = new ImageProvider(image).resetMaxSize(new Dimension(width, height));
    85         }
     83        image = new ImageProvider(image).setMaxSize(new Dimension(width, height));
    8684        overlay = image.get();
    8785        int x, y;
    8886        if (width == -1 && offsetLeft < 0) {
  • src/org/openstreetmap/josm/tools/ImageProvider.java

     
    277277    /** <code>true</code> if icon must be grayed out */
    278278    protected boolean isDisabled;
    279279
     280    protected boolean multiResolution = true;
     281   
    280282    private static SVGUniverse svgUniverse;
    281283
    282284    /**
     
    287289    /**
    288290     * Caches the image data for rotated versions of the same image.
    289291     */
    290     private static final Map<Image, Map<Long, ImageResource>> ROTATE_CACHE = new HashMap<>();
     292    private static final Map<Image, Map<Long, Image>> ROTATE_CACHE = new HashMap<>();
    291293
    292294    private static final ExecutorService IMAGE_FETCHER =
    293295            Executors.newSingleThreadExecutor(Utils.newThreadFactory("image-fetcher-%d", Thread.NORM_PRIORITY));
     
    333335        this.additionalClassLoaders = image.additionalClassLoaders;
    334336        this.overlayInfo = image.overlayInfo;
    335337        this.isDisabled = image.isDisabled;
     338        this.multiResolution = image.multiResolution;
    336339    }
    337340
    338341    /**
     
    594597        return this;
    595598    }
    596599
     600    public ImageProvider setMultiResolution(boolean multiResolution) {
     601        this.multiResolution = multiResolution;
     602        return this;
     603    }
     604   
    597605    /**
    598606     * Execute the image request and scale result.
    599607     * @return the requested image or null if the request failed
     
    605613            return null;
    606614        }
    607615        if (virtualMaxWidth != -1 || virtualMaxHeight != -1)
    608             return ir.getImageIconBounded(new Dimension(virtualMaxWidth, virtualMaxHeight));
     616            return ir.getImageIconBounded(new Dimension(virtualMaxWidth, virtualMaxHeight), multiResolution);
    609617        else
    610             return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight));
     618            return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight), multiResolution);
    611619    }
    612620
    613621    /**
     
    12751283    }
    12761284
    12771285    /**
    1278      * Creates a rotated version of the input image, scaled to the given dimension.
     1286     * Creates a rotated version of the input image.
    12791287     *
    12801288     * @param img the image to be rotated.
    12811289     * @param rotatedAngle the rotated angle, in degree, clockwise. It could be any double but we
    12821290     * will mod it with 360 before using it. More over for caching performance, it will be rounded to
    12831291     * an entire value between 0 and 360.
    1284      * @param dimension The requested dimensions. Use (-1,-1) for the original size
    1285      * and (width, -1) to set the width, but otherwise scale the image proportionally.
     1292     * @param dimension ignored
    12861293     * @return the image after rotating and scaling.
    12871294     * @since 6172
    12881295     */
     
    12901297        CheckParameterUtil.ensureParameterNotNull(img, "img");
    12911298
    12921299        // convert rotatedAngle to an integer value from 0 to 360
    1293         Long originalAngle = Math.round(rotatedAngle % 360);
    1294         if (rotatedAngle != 0 && originalAngle == 0) {
    1295             originalAngle = 360L;
    1296         }
     1300        Long angleLong = Math.round(rotatedAngle % 360);
     1301        Long originalAngle = rotatedAngle != 0 && angleLong == 0 ? 360L : angleLong;
    12971302
    1298         ImageResource imageResource;
    1299 
    13001303        synchronized (ROTATE_CACHE) {
    1301             Map<Long, ImageResource> cacheByAngle = ROTATE_CACHE.get(img);
     1304            Map<Long, Image> cacheByAngle = ROTATE_CACHE.get(img);
    13021305            if (cacheByAngle == null) {
    13031306                cacheByAngle = new HashMap<>();
    13041307                ROTATE_CACHE.put(img, cacheByAngle);
    13051308            }
    13061309
    1307             imageResource = cacheByAngle.get(originalAngle);
     1310            Image rotatedImg = cacheByAngle.get(originalAngle);
    13081311
    1309             if (imageResource == null) {
     1312            if (rotatedImg == null) {
    13101313                // convert originalAngle to a value from 0 to 90
    13111314                double angle = originalAngle % 90;
    13121315                if (originalAngle != 0 && angle == 0) {
    13131316                    angle = 90.0;
    13141317                }
    1315 
    13161318                double radian = Utils.toRadians(angle);
    13171319
    1318                 new ImageIcon(img); // load completely
    1319                 int iw = img.getWidth(null);
    1320                 int ih = img.getHeight(null);
    1321                 int w;
    1322                 int h;
     1320                rotatedImg = HiDPISupport.processMRImage(img, img0 -> {
     1321                    new ImageIcon(img0); // load completely
     1322                    int iw = img0.getWidth(null);
     1323                    int ih = img0.getHeight(null);
     1324                    int w;
     1325                    int h;
    13231326
    1324                 if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
    1325                     w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
    1326                     h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
    1327                 } else {
    1328                     w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
    1329                     h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
    1330                 }
    1331                 Image image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    1332                 imageResource = new ImageResource(image);
    1333                 cacheByAngle.put(originalAngle, imageResource);
    1334                 Graphics g = image.getGraphics();
    1335                 Graphics2D g2d = (Graphics2D) g.create();
     1327                    if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
     1328                        w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
     1329                        h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
     1330                    } else {
     1331                        w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
     1332                        h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
     1333                    }
     1334                    Image image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
     1335                    Graphics g = image.getGraphics();
     1336                    Graphics2D g2d = (Graphics2D) g.create();
    13361337
    1337                 // calculate the center of the icon.
    1338                 int cx = iw / 2;
    1339                 int cy = ih / 2;
     1338                    // calculate the center of the icon.
     1339                    int cx = iw / 2;
     1340                    int cy = ih / 2;
    13401341
    1341                 // move the graphics center point to the center of the icon.
    1342                 g2d.translate(w / 2, h / 2);
     1342                    // move the graphics center point to the center of the icon.
     1343                    g2d.translate(w / 2, h / 2);
    13431344
    1344                 // rotate the graphics about the center point of the icon
    1345                 g2d.rotate(Utils.toRadians(originalAngle));
     1345                    // rotate the graphics about the center point of the icon
     1346                    g2d.rotate(Utils.toRadians(originalAngle));
    13461347
    1347                 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    1348                 g2d.drawImage(img, -cx, -cy, null);
     1348                    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
     1349                    g2d.drawImage(img0, -cx, -cy, null);
    13491350
    1350                 g2d.dispose();
    1351                 new ImageIcon(image); // load completely
     1351                    g2d.dispose();
     1352                    new ImageIcon(image); // load completely
     1353                    return image;
     1354                });
     1355                cacheByAngle.put(originalAngle, rotatedImg);
    13521356            }
    1353             return imageResource.getImageIcon(dimension).getImage();
     1357            return rotatedImg;
    13541358        }
    13551359    }
    13561360
     
    14111415                                BufferedImage.TYPE_INT_ARGB);
    14121416                        double scaleFactor = Math.min(backgroundRealWidth / (double) iconRealWidth, backgroundRealHeight
    14131417                                / (double) iconRealHeight);
    1414                         BufferedImage iconImage = icon.getImage(false);
     1418                        Image iconImage = icon.getImage(false);
    14151419                        Image scaledIcon;
    14161420                        final int scaledWidth;
    14171421                        final int scaledHeight;
  • src/org/openstreetmap/josm/tools/ImageResource.java

     
    156156     * @return ImageIcon object for the image of this resource, scaled according to dim
    157157     */
    158158    public ImageIcon getImageIcon(Dimension dim) {
     159        return getImageIcon(dim, true);
     160    }
     161   
     162    public ImageIcon getImageIcon(Dimension dim, boolean multiResolution) {
     163        ImageIcon img0 = getImageIconBasic(dim);
     164        if (!multiResolution)
     165            return img0;
     166        try {
     167            Image mrImg = HiDPISupport.getMultiResolutionImage(img0.getImage(), this);
     168            return new ImageIcon(mrImg);
     169        } catch (NoClassDefFoundError e) {
     170            return img0;
     171        }
     172    }
     173   
     174    ImageIcon getImageIconBasic(Dimension dim) {
    159175        if (dim.width < -1 || dim.width == 0 || dim.height < -1 || dim.height == 0)
    160176            throw new IllegalArgumentException(dim+" is invalid");
    161177        Image img = imgCache.get(dim);
     
    216232     * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
    217233     */
    218234    public ImageIcon getImageIconBounded(Dimension maxSize) {
     235        return getImageIconBounded(maxSize, true);
     236    }
     237   
     238    public ImageIcon getImageIconBounded(Dimension maxSize, boolean multiResolution) {
    219239        if (maxSize.width < -1 || maxSize.width == 0 || maxSize.height < -1 || maxSize.height == 0)
    220240            throw new IllegalArgumentException(maxSize+" is invalid");
    221241        float sourceWidth;
     
    239259        }
    240260
    241261        if (maxWidth == -1 && maxHeight == -1)
    242             return getImageIcon(DEFAULT_DIMENSION);
     262            return getImageIcon(DEFAULT_DIMENSION, multiResolution);
    243263        else if (maxWidth == -1)
    244             return getImageIcon(new Dimension(-1, maxHeight));
     264            return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
    245265        else if (maxHeight == -1)
    246             return getImageIcon(new Dimension(maxWidth, -1));
     266            return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
    247267        else if (sourceWidth / maxWidth > sourceHeight / maxHeight)
    248             return getImageIcon(new Dimension(maxWidth, -1));
     268            return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
    249269        else
    250             return getImageIcon(new Dimension(-1, maxHeight));
     270            return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
    251271   }
    252272}