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 2b18f36ee90a3d2ecc8f0307bc839e3bc1772f55
Author: Eirik Bakke <[email protected]>
AuthorDate: Sat May 11 23:56:59 2019 +0200

    Support proper HiDPI rendering of splash screen and About box images. (The 
splash screen image was already added in high resolution in a previous commit.)
---
 .../netbeans/core/startup/ScaledBitmapIcon.java    | 128 +++++++++++++++++++++
 .../src/org/netbeans/core/startup/Splash.java      |  84 ++++++++++----
 .../netbeans/core/ui/ProductInformationPanel.java  |   6 +-
 3 files changed, 192 insertions(+), 26 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
new file mode 100644
index 0000000..d902d2e
--- /dev/null
+++ b/platform/core.startup/src/org/netbeans/core/startup/ScaledBitmapIcon.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+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 javax.swing.Icon;
+
+/* 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.
+ */
+final class ScaledBitmapIcon implements Icon {
+    private final Map<Double,Image> cache = new ConcurrentHashMap<>();
+    private final Image sourceImage;
+    private final int width;
+    private final int height;
+
+    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();
+        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;
+    }
+
+    @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;
+        }
+        Image scaledImage = getScaledImage(g.getDeviceConfiguration(), 
dpiScaling);
+        if (dpiScaling != 1.0) {
+            // Scale the image down to its logical dimensions, then draw it at 
the device pixel boundary.
+            AffineTransform tx2 = g.getTransform();
+            g.setTransform(new AffineTransform(1, 0, 0, 1,
+                (int) tx2.getTranslateX(),
+                (int) tx2.getTranslateY()));
+        }
+        g.drawImage(scaledImage, 0, 0, null);
+        g.setTransform(oldTransform);
+    }
+
+    @Override
+    public int getIconWidth() {
+        return width;
+    }
+
+    @Override
+    public int getIconHeight() {
+        return height;
+    }
+}
diff --git a/platform/core.startup/src/org/netbeans/core/startup/Splash.java 
b/platform/core.startup/src/org/netbeans/core/startup/Splash.java
index 8a2e59f..feeb088 100644
--- a/platform/core.startup/src/org/netbeans/core/startup/Splash.java
+++ b/platform/core.startup/src/org/netbeans/core/startup/Splash.java
@@ -92,11 +92,30 @@ public final class Splash implements Stamps.Updater {
     private SplashPainter painter;
     private SplashComponent comp;
     private SplashScreen splashScreen;
+    /**
+     * Indicate if we should try to take advantage of java's "-splash" 
parameter, which allows
+     * the splash screen to be displayed at an earlier stage in the app 
startup sequence. See the
+     * original Buzilla RFE at 
https://netbeans.org/bugzilla/show_bug.cgi?id=60142 . This requires
+     * splash screen image(s) to be written to the cache directory the first 
time NetBeans starts,
+     * to be available during subsequent NetBeans startups. Despite
+     * https://bugs.openjdk.java.net/browse/JDK-8145173 and
+     * https://bugs.openjdk.java.net/browse/JDK-8151787 , as of OpenJDK 10.0.2 
and OpenJDK 12.0.1
+     * I have found no way to make this work properly with HiDPI screens on 
Windows. HiDPI filenames
+     * attempted include "[email protected]", "[email protected]", 
"splash.scale-200.png", and
+     * "splash.java-scale200.png". In all of these cases, the regular 
"splash.png" file is used
+     * instead of one of the 2x-scaled ones (for a system DPI scaling of 
200%), and the splash
+     * screen becomes half the expected size. Thus, to we disable this feature 
for now, in favor of
+     * a slightly delayed splash screen that appears with the correct size and 
resolution on HiDPI
+     * screens.
+     *
+     * <p>See also https://issues.apache.org/jira/browse/NETBEANS-67 .
+     */
+    private static final boolean USE_LAUNCHER_SPLASH = false;
     
     private Splash() {
         Stamps s = Stamps.getModulesJARs();
         if (!CLIOptions.isNoSplash() && !GraphicsEnvironment.isHeadless()) {
-            if (!s.exists("splash.png")) {
+            if (USE_LAUNCHER_SPLASH && !s.exists("splash.png")) {
                 s.scheduleSave(this, "splash.png", false);
             }
             try {
@@ -209,17 +228,29 @@ public final class Splash implements Stamps.Updater {
         c.setBounds(Utilities.findCenterBounds(c.getSize()));
     }
 
-    /** Loads a splash image from its source 
+    /**
+     * Loads a splash image from its source. For high-resolution rendering on 
HiDPI displays, the
+     * returned image should be converted to a HiDPI-aware Icon instance via
+     * {@link ImageUtilities#image2Icon} prior to painting.
+     *
      *  @param about if true then about image is loaded, if false splash image 
is loaded
      */
     public static Image loadContent(boolean about) {
+        return ImageUtilities.icon2Image(loadContentIcon(about));
+    }
+
+    private static Icon loadContentIcon(boolean about) {
+        Image ret = null;
         if (about) {
-            Image img = 
ImageUtilities.loadImage("org/netbeans/core/startup/about.png", true);
-            if (img != null) {
-                return img;
-            }
+            ret = 
ImageUtilities.loadImage("org/netbeans/core/startup/about.png", true);
         }
-        return 
ImageUtilities.loadImage("org/netbeans/core/startup/splash.gif", true);
+        if (ret == null)
+            ret = 
ImageUtilities.loadImage("org/netbeans/core/startup/splash.gif", true);
+        if (ret == null)
+            return null;
+        return new ScaledBitmapIcon(ret,
+                Integer.parseInt(NbBundle.getMessage(Splash.class, 
"SPLASH_WIDTH")),
+                Integer.parseInt(NbBundle.getMessage(Splash.class, 
"SPLASH_HEIGHT")));
     }
 
     @Override
@@ -256,7 +287,7 @@ public final class Splash implements Stamps.Updater {
          */
         @Override
         public void paint(Graphics graphics) {
-            painter.graphics = graphics;
+            painter.graphics = (Graphics2D) graphics;
             painter.paint();
         }
         @Override
@@ -388,9 +419,9 @@ public final class Splash implements Stamps.Updater {
         private int maxSteps = 0;
         private int barStart = 0;
         private int barLength = 0;
-        private Image image;
+        private Icon image;
         private String text;
-        private Graphics graphics;
+        private Graphics2D graphics;
         private final JComponent comp;
         private final boolean about;
 
@@ -399,7 +430,7 @@ public final class Splash implements Stamps.Updater {
          * param about true is this component will be used in about dialog
          */
         public SplashPainter(Graphics graphics, JComponent comp, boolean 
about) {
-            this.graphics = graphics;
+            this.graphics = (Graphics2D) graphics;
             this.comp = comp;
             this.about = about;
         }
@@ -440,7 +471,7 @@ public final class Splash implements Stamps.Updater {
                 bar = new Rectangle(0, 0, 80, 10);
             }
 
-            image = loadContent(about);
+            image = loadContentIcon(about);
 
             if (comp != null)
               comp.setFont(statusBox.font);
@@ -565,12 +596,14 @@ public final class Splash implements Stamps.Updater {
                 int bl = bar.width * progress / maxSteps - barStart;
                 if (bl > 1 || barStart % 2 == 0) {
                     barLength = bl;
-                    bar_inc = new Rectangle(bar.x + barStart, bar.y, barLength 
+ 1, bar.height);
                     SwingUtilities.invokeLater(new Runnable() {
                         @Override
                         public void run() {
                             init();
-                            repaint(bar_inc);
+                            /* Don't try to be smart about which section of 
the bar to repaint.
+                            There can be tricky rounding issues on HiDPI 
screens with non-integral
+                            scaling factors (e.g. 150%). */
+                            repaint(bar);
                         }
                     });
                 }
@@ -600,10 +633,9 @@ public final class Splash implements Stamps.Updater {
         }
        
         void paint() {
-            graphics.drawImage(image, 0, 0, null);
+            image.paintIcon(comp, graphics, 0, 0);
             // turn anti-aliasing on for the splash text
-            Graphics2D g2d = (Graphics2D) graphics;
-            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+            graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 
             if (versionBox != null) {
@@ -618,12 +650,18 @@ public final class Splash implements Stamps.Updater {
             if (!noBar && maxSteps > 0/* && barLength > 0*/) {
                 graphics.setColor(color_bar);
                 graphics.fillRect(bar.x, bar.y, barStart + barLength, 
bar.height);
-                graphics.setColor(color_corner);
-                graphics.drawLine(bar.x, bar.y, bar.x, bar.y + bar.height);
-                graphics.drawLine(bar.x + barStart + barLength, bar.y, bar.x + 
barStart + barLength, bar.y + bar.height);
-                graphics.setColor(color_edge);
-                graphics.drawLine(bar.x, bar.y + bar.height / 2, bar.x, bar.y 
+ bar.height / 2);
-                graphics.drawLine(bar.x + barStart + barLength, bar.y + 
bar.height / 2, bar.x + barStart + barLength, bar.y + bar.height / 2);
+                /* To discourage visual artifacts on HiDPI displays, only 
paint the distinct
+                corner/edge colors if the branding actually calls for them. */
+                if (!color_bar.equals(color_corner)) {
+                  graphics.setColor(color_corner);
+                  graphics.drawLine(bar.x, bar.y, bar.x, bar.y + bar.height);
+                  graphics.drawLine(bar.x + barStart + barLength, bar.y, bar.x 
+ barStart + barLength, bar.y + bar.height);
+                }
+                if (!color_bar.equals(color_edge)) {
+                  graphics.setColor(color_edge);
+                  graphics.drawLine(bar.x, bar.y + bar.height / 2, bar.x, 
bar.y + bar.height / 2);
+                  graphics.drawLine(bar.x + barStart + barLength, bar.y + 
bar.height / 2, bar.x + barStart + barLength, bar.y + bar.height / 2);
+                }
                 barStart += barLength;
                 barLength = 0;
             }
diff --git 
a/platform/o.n.core/src/org/netbeans/core/ui/ProductInformationPanel.java 
b/platform/o.n.core/src/org/netbeans/core/ui/ProductInformationPanel.java
index ea4fa22..a5ad4d2 100644
--- a/platform/o.n.core/src/org/netbeans/core/ui/ProductInformationPanel.java
+++ b/platform/o.n.core/src/org/netbeans/core/ui/ProductInformationPanel.java
@@ -21,7 +21,6 @@ package org.netbeans.core.ui;
 
 import java.awt.Cursor;
 import java.awt.EventQueue;
-import java.awt.Font;
 import java.awt.Window;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -36,7 +35,6 @@ import java.net.URL;
 import java.text.MessageFormat;
 import java.util.Locale;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JEditorPane;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
@@ -51,6 +49,7 @@ import org.openide.awt.HtmlBrowser.URLDisplayer;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
 import org.openide.modules.Places;
+import org.openide.util.ImageUtilities;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle;
 import org.openide.util.NbBundle.Messages;
@@ -121,7 +120,8 @@ public class ProductInformationPanel extends JPanel 
implements HyperlinkListener
         copyright.setBackground(getBackground());
         copyright.putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, 
Boolean.TRUE);
 
-        about = new 
ImageIcon(org.netbeans.core.startup.Splash.loadContent(true));
+        // Use image2Icon to preserve the underlying HiDPI-aware Icon instance.
+        about = 
ImageUtilities.image2Icon(org.netbeans.core.startup.Splash.loadContent(true));
         imageLabel.setIcon(about);
 
         imageLabel.addMouseListener(new MouseAdapter() {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

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

Reply via email to