You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by eb...@apache.org on 2019/11/27 01:23:31 UTC

[netbeans] 02/02: Simplify/improve the HiDPI splash screen support code.

This is an automated email from the ASF dual-hosted git repository.

ebakke pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git

commit 23ccd9d64f7e4a30316444b56eeea330c161f916
Author: Eirik Bakke <eb...@ultorg.com>
AuthorDate: Thu Jul 25 04:45:33 2019 -0400

    Simplify/improve the HiDPI splash screen support code.
    
    Details:
    * Use the shared caching logic for scaled HiDPI image variants now available by
      subclassing from org.openide.util.CachedHiDPIIcon.
    * Properly wait for the image to load before scaling (via ImageIcon's
      MediaTracker).
    * Rename ScaledBitmapIcon.width/height to sourceWidth/sourceHeight.
    * Document some known image scaling issues on Java.
---
 .../netbeans/core/startup/ScaledBitmapIcon.java    | 127 +++++++--------------
 1 file changed, 42 insertions(+), 85 deletions(-)

diff --git a/platform/core.startup/src/org/netbeans/core/startup/ScaledBitmapIcon.java b/platform/core.startup/src/org/netbeans/core/startup/ScaledBitmapIcon.java
index dec4ee0..2f222f4 100644
--- a/platform/core.startup/src/org/netbeans/core/startup/ScaledBitmapIcon.java
+++ b/platform/core.startup/src/org/netbeans/core/startup/ScaledBitmapIcon.java
@@ -19,110 +19,67 @@
 package org.netbeans.core.startup;
 
 import java.awt.Component;
-import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.GraphicsConfiguration;
 import java.awt.Image;
 import java.awt.RenderingHints;
-import java.awt.Transparency;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.awt.image.ColorModel;
 import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import org.openide.util.CachedHiDPIIcon;
+import org.openide.util.Parameters;
 
-/* Package-private for now. At some later point it might be useful to expose the DPI-based caching
-functionality in this class as a more general utility. */
 /**
  * An Icon implementation that scales a source image to the specified dimensions. Can be used to
  * produce sharp images on HiDPI displays, without relying on MultiResolutionImage, which exists
  * only since JDK 9. This also sidesteps https://bugs.openjdk.java.net/browse/JDK-8212226 on
- * Windows. For HiDPI displays, the source image's dimensions should be at least double those of the
- * icon's logical dimensions. A double-resolution source image will automatically be scaled down
- * to 1x, 1.5x, or other HiDPI scaling factors as needed.
+ * Windows. It's recommended to use a source image with dimensions that are exactly twice those of
+ * the icon's logical dimensions. A double-resolution source image will automatically be scaled
+ * down to 1x, 1.5x, or other HiDPI scaling factors as needed.
  */
-final class ScaledBitmapIcon implements Icon {
-    private final Map<Double,Image> cache = new ConcurrentHashMap<>();
+final class ScaledBitmapIcon extends CachedHiDPIIcon {
     private final Image sourceImage;
-    private final int width;
-    private final int height;
+    private final int sourceWidth;
+    private final int sourceHeight;
 
     public ScaledBitmapIcon(Image sourceImage, int width, int height) {
-        if (sourceImage == null)
-            throw new NullPointerException();
-        if (width <= 0)
-            throw new IllegalArgumentException();
-        if (height <= 0)
-            throw new IllegalArgumentException();
+        super(width, height);
+        Parameters.notNull("sourceImage", sourceImage);
         this.sourceImage = sourceImage;
-        this.width = width;
-        this.height = height;
-    }
-
-    private Image getScaledImage(GraphicsConfiguration gc, double dpiScaling) {
-        Image ret = cache.get(dpiScaling);
-        if (ret != null) {
-            return ret;
-        }
-        final BufferedImage img = gc.createCompatibleImage(
-                (int) Math.ceil(getIconWidth() * dpiScaling),
-                (int) Math.ceil(getIconHeight() * dpiScaling), Transparency.TRANSLUCENT);
-        final double sourceWidth = sourceImage.getWidth(null);
-        final double sourceHeight = sourceImage.getHeight(null);
-        if (sourceWidth >= 1 && sourceHeight >= 1) {
-          final Graphics2D imgG = (Graphics2D) img.getGraphics();
-          try {
-              imgG.setTransform(AffineTransform.getScaleInstance(
-                      dpiScaling * getIconWidth() / sourceWidth,
-                      dpiScaling * getIconHeight() / sourceHeight));
-              imgG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-              imgG.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
-              imgG.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-              imgG.drawImage(sourceImage, 0, 0, null);
-          } finally {
-              imgG.dispose();
-          }
-          if (dpiScaling <= 3.0)
-              cache.put(dpiScaling, img);
-        }
-        return img;
+        /* Like ImageIcon, we block until the image is fully loaded. Just rely on ImageIcon's
+        implementation here (it will ll get a MediaTracker, call waitForID etc.). */
+        Icon imageIcon = new ImageIcon(sourceImage);
+        sourceWidth = imageIcon.getIconWidth();
+        sourceHeight = imageIcon.getIconHeight();
     }
 
     @Override
-    public void paintIcon(Component c, Graphics g0, int x, int y) {
-        final Graphics2D g = (Graphics2D) g0;
-        final AffineTransform oldTransform = g.getTransform();
-        g.translate(x, y);
-        final AffineTransform tx = g.getTransform();
-
-        final double dpiScaling;
-        final int txType = tx.getType();
-        if (txType == AffineTransform.TYPE_UNIFORM_SCALE ||
-            txType == (AffineTransform.TYPE_UNIFORM_SCALE | AffineTransform.TYPE_TRANSLATION))
-        {
-            dpiScaling = tx.getScaleX();
-        } else {
-            dpiScaling = 1.0;
-        }
-        // Scale the image down to its logical dimensions, then draw it at the device pixel boundary.
-        Image scaledImage = getScaledImage(g.getDeviceConfiguration(), dpiScaling);
-        if (dpiScaling != 1.0) {
-            AffineTransform tx2 = g.getTransform();
-            g.setTransform(new AffineTransform(1, 0, 0, 1,
-                (int) tx2.getTranslateX(),
-                (int) tx2.getTranslateY()));
+    protected Image createAndPaintImage(
+        Component c, ColorModel colorModel, int deviceWidth, int deviceHeight, double scale)
+    {
+        final BufferedImage img = createBufferedImage(colorModel, deviceWidth, deviceHeight);
+        if (sourceWidth > 0 && sourceHeight > 0) {
+            final Graphics2D g = img.createGraphics();
+            try {
+                /* Despite these hints, downscaling by more than 2x tends to give low-quality
+                results compared to image resizing in, say, Photoshop or IrfanView. This is a known
+                quality/performance trade-off in Java2D; see
+                https://stackoverflow.com/questions/24745147/java-resize-image-without-losing-quality .
+                Our Javadoc recommendation to use a scaling of exactly 2x should avoid these
+                problems. */
+                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+                g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+                g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+                g.setTransform(AffineTransform.getScaleInstance(
+                        scale * getIconWidth() / (double) sourceWidth,
+                        scale * getIconHeight() / (double) sourceHeight));
+                g.drawImage(sourceImage, 0, 0, null);
+            } finally {
+                g.dispose();
+            }
         }
-        g.drawImage(scaledImage, 0, 0, null);
-        g.setTransform(oldTransform);
-    }
-
-    @Override
-    public int getIconWidth() {
-        return width;
-    }
-
-    @Override
-    public int getIconHeight() {
-        return height;
+        return img;
     }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists