JDK 9 High Dpi отключить для конкретной панели

С JDK 9 мое приложение Swing хорошо работает на Windows с 4k highdpi и обычным разрешением 1080p dpi. Ярлыки, комбинированные списки и т. Д. Все выглядит красиво и масштабируется на экране 4k. Но так же и моя JPanel, где я рисую собственные изображения. Могу ли я отключить масштабирование для этого JPanel, чтобы обрабатывать рисование самостоятельно? Я использую apache-commons бикубическую интерполяцию, чтобы нарисовать больше деталей о высоком немасштабированном разрешении, но, поскольку оно масштабируется из коробки, у меня просто есть "нормальные" размеры для рисования.

С уважением

1 ответ

Масштабирование в Java 9 выглядит следующим образом: ваши методы paint(Component)() получают объект Graphics2D, который уже масштабирован. Кроме того, размеры компонента (например, myJFrame.setSize(), myJPanel.getWidth()) незаметно масштабируются для программы, что означает, что когда вы скажете setSize(800 600) на рабочем столе 200%, компонент будет 1600x1200, но getWidth/getHeight вернется 800/600.

Могу ли я отключить масштабирование для этого JPanel, чтобы обрабатывать рисование самостоятельно?

Чтобы "сбросить" графический объект до масштабирования 1, сделайте следующее:

final Graphics2D g = (Graphics2D) graphics;
final AffineTransform t = g.getTransform();
final double scaling = t.getScaleX(); // Assuming square pixels :P
t.setToScale(1, 1);
g.setTransform(t);

Чтобы получить правильные размеры, например, чтобы залить весь фон чернотой перед рисованием:

final int w = (int) Math.round(getWidth() * scaling);

Если вы сделаете это так, вы должны получить желаемый результат на Java 9 и Java 8.


Я только что создал класс для разработчиков Java, которые стремятся к более индивидуальному проектированию компонентов и / или первичному чертежу, где должно быть известно масштабирование отображения системы и часто необходимо ручное масштабирование. Это должно решить все проблемы масштабирования на Java 8 и Java 9. Вот оно:

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;




/**
 * TL;DR:
 * <p>
 * Call GUIScaling.initialize() at application start on the Swing thread.
 * <p>
 * If you set your own Component font sizes or border sizes or window sizes, multiply them by
 * GUIScaling.GUISCALINGFACTOR_COMPONENTSANDFONTS and/or use the helper methods newDimension() and scaleForComponent().
 * Works on Java 8 and 9.
 * <p>
 * If you do your own custom graphics and want to have control down to the actual pixel, create an instance of
 * GUIScalingCustomGraphics to obtain your Graphics2D at scaling 1 and your component's true physical width and height
 * (Which Java 9 reports differently!), and scale all your graphics using GUIScaling.GUISCALINGFACTOR_CUSTOMGRAPHICS
 * and/or use the helper method scaleForCustom(). The helper method scaleForRealComponentSize() can transform your mouse
 * coordinates to the real physical coordinate, which Java 9 reports differently!
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * GUIScaling class v[1, 2017-10-08 11!00 UTC] by dreamspace-president.com
 * <p>
 * This Swing class detects the system's display scaling setting, which is important to make your GUI and custom
 * graphics scale properly like the user wants it. On a 4K display, for example, you'd probably set 200% in your
 * system.
 * <p>
 * Not tested with Java less than 8!
 * <p>
 * On Java 8 (and with most but not all (e.g. no the default) LooksAndFeels), component sizes (e.g. JButton) and their
 * font sizes will scale automatically, but if you have a certain border width in mind, or decided for a certain min and
 * default window size or a certain font size, you have to upscale those on a non-100%-system. With this class, just
 * multiply the values with GUISCALINGFACTOR_COMPONENTSANDFONTS. Done. newDimension() and scaleForComponent() help with
 * that.
 * <p>
 * On Java 9, component sizes and their font sizes DO NOT SCALE from the perspective of the application, but in reality
 * they are scaled: A window set to 800x600 size will really be 1600x1200, but it will still report half this size when
 * asked. A border of 50 pixels will really be 100 pixels. A Graphics2D object (paint method etc.) will have a scaling
 * of 2! (Not if you just create a BufferedImage object and do createGraphics(), the scale here will be 1.) So, you
 * don't have to bother with GUI scaling here at all. YOU CAN STILL USE GUISCALINGFACTOR_COMPONENTSANDFONTS, because
 * this class will set it to 1 on Java 9. This is detected by indeed checking the scaling of a Graphics2D object. So,
 * your Java 8 and 9 component/font code will be exactly the same in regards to scaling.
 * <p>
 * CUSTOM GRAPHICS: If you do your own painting and want to insist on true physical pixels (in which case obviously
 * you'd have to scale your fonts with GUISCALINGFACTOR_CUSTOMGRAPHICS instead of GUISCALINGFACTOR_COMPONENTSANDFONTS),
 * on Java 9 you have to reset the scaling of the Graphics2D object the paint(Component)() method gives you from 2 to 1,
 * and (also Java 9) you have to adjust the width/height reported by your component. Both is done by making an instance
 * of GUIScalingCustomGraphics. You can do this blindly on Java 8 and 9, your code will stay the same. And then, apply
 * this class' GUISCALINGFACTOR_CUSTOMGRAPHICS to scale everything according to system settings. Or, instead of
 * insisting on true physical pixels, you could trust Java 9 and not mess with the initial scaling - but then you'd have
 * to distinguish whether you're dealing with Java 8 or 9, because on 8, you'd still have to scale your custom graphics.
 * In case you decide for this, use GUISCALINGFACTOR_COMPONENTSANDFONTS for your custom graphics instead of
 * GUISCALINGFACTOR_CUSTOMGRAPHICS because the former will be ***1*** on Java 9 but will be proper (e.g. 2.0 for a 200%
 * system) on Java 8.
 * <p>
 * A weird problem that comes with Java 9: If you use the mouse coordinates as reported by the system (instead of, say,
 * quasi-fix the physical mouse pointer invisibly at the screen center and make your own pointer based on coordinate
 * differences), you will have HALF THE USUAL RESOLUTION. On Java 8, a 3840x2160 screen will give you according mouse
 * coordinates, but on Java 9, you get half these coordinates (if the system is set to scaling 200%). While
 * scaleForRealComponentSize() helps correct this, a custom drawn mouse pointer will now step in 2 pixel distances, it
 * can not reach every individual pixel any longer. I wish they had updated the MouseEvent class accordingly with
 * additional float methods.
 */
final public class GUIScaling { // INITIAL TOUCHING of this class MUST be on Swing thread!


    /**
     * Call this at the start of your application ON THE SWING THREAD. This initializes the class and hence its values.
     */
    public static void initialize() {

        System.err.println(""); // To make sure an obfuscator doesn't remove this method and its calls.
    }


    /**
     * By calling this, you ALSO initialize the class, so you don't HAVE TO use initialize() in that case (but it really
     * doesn't matter). And you can indeed set a LookAndFeel of your choice, even though initialization of this class
     * also sets AND TEMPORARILY USES a LookAndFeel.
     *
     * @param intendedLAFIs ANYTHING, but ideally a LookAndFeel name or several. The first value that equalsIgnoreCase
     *                      an installed LookAndFeelInfo.getName() will be used.
     */
    public static void setLookAndFeel(final String... intendedLAFIs) {

        if (intendedLAFIs != null) {
            final UIManager.LookAndFeelInfo[] installedLAFIs = UIManager.getInstalledLookAndFeels();
            LAFILOOP:
            for (String intendedLAFI : intendedLAFIs) {
                for (final UIManager.LookAndFeelInfo lafi : UIManager.getInstalledLookAndFeels()) {
                    if (lafi.getName().equalsIgnoreCase(intendedLAFI)) {
                        try {
                            UIManager.setLookAndFeel(lafi.getClassName());
                            break LAFILOOP;
                        } catch (Exception e) {
                            continue LAFILOOP;
                        }
                    }
                }
            }
        }
    }


    /**
     * Convenience method, compatible with Java 8 and 9.
     */
    public static Dimension newDimension(final int w, final int h) {

        return new Dimension(scaleForComponent(w), scaleForComponent(h));
    }


    /**
     * @param x E.g. the width of a component, or the size of a border.
     * @return x scaled by the necessary display scaling factor for components and fonts, compatible with Java 8 and 9.
     */
    public static int scaleForComponent(final double x) {

        return (int) Math.round(x * GUISCALINGFACTOR_COMPONENTSANDFONTS);
    }


    /**
     * @param x E.g. the width of a rectangle being drawn in a paint() or paintComponent() override.
     * @return x scaled by the necessary display scaling factor for custom graphics, compatible with Java 8 and 9.
     */
    public static int scaleForCustom(final double x) {

        return (int) Math.round(x * GUISCALINGFACTOR_CUSTOMGRAPHICS);
    }


    /**
     * @param x E.g. the width as reported by a component.
     * @return x scaled so that it represents real physical pixels, compatible with Java 8 and 9.
     */
    public static int scaleForRealComponentSize(final double x) {

        return (int) Math.round(x * GUISCALINGFACTOR_REALCOMPONENTSIZE);
    }


    /**
     * For Java 9, but can blindly be used in Java 8, too. Ensures that the scaling of a paint(Component)()'s Graphics2D
     * object is 1. Conveniently does the usual casting, too.
     * <p>
     * Also calculates the physical pixel width/height of the component, which is reported differently on Java 9 if the
     * display scaling is not 100%.
     */
    final public static class GUIScalingCustomGraphics {


        final public Component component; // Just for convenience. You can hand the whole instance down your paint call hierarchy.
        final public int w; // The physical pixel width of the component.
        final public int h; // dto. height
        final public Graphics2D g; // Scale will be 1, even on Java 9 with a non-100% display scaling.


        /**
         * @param component NOT NULL. The component (e.g. JPanel or JFrame) whose paint() method you're overriding.
         * @param graphics  NOT NULL. The Graphics argument given to your paint() method.
         */
        public GUIScalingCustomGraphics(final Component component, final Graphics graphics) {

            this.component = component;
            w = scaleForRealComponentSize(component.getWidth());
            h = scaleForRealComponentSize(component.getHeight());

            g = (Graphics2D) graphics;
            final AffineTransform t = g.getTransform();
            t.setToScale(1, 1);
            g.setTransform(t);
        }


    }




    final private static double JBUTTONFONTSIZE_ON_100PERCENTSCALE_JAVA8_W10_WITH_LOOKANDFEEL_WINDOWSORSYSTEMORXPLATFORMORWINCLASSIC = 11.0;

    final public static double GUISCALINGFACTOR_SYSTEM; // The scaling set in the system.
    final public static double GUISCALINGFACTOR_COMPONENTSANDFONTS; // The scaling necessary if you set component/font sizes yourself.
    final public static double GUISCALINGFACTOR_CUSTOMGRAPHICS; // The scaling necessary if you want your custom graphics, too, to be scaled according to System settings.
    final public static double GUISCALINGFACTOR_REALCOMPONENTSIZE; // The factor by which getWidth() and such return values have to be multiplied, because Java 9 reports them differently.

    static {

        // The last three (Nimbus etc.) DO NOT automatically scale their font sizes with the system's GUI scaling,
        // so using the font size in those cases to derive the scaling WILL FAIL.
        // Btw., the JButton font size at 100% Windows 10 system scaling is 11.0 in all cases but the last three.
        GUIScaling.setLookAndFeel("Windows", UIManager.getSystemLookAndFeelClassName(), UIManager.getCrossPlatformLookAndFeelClassName(), "Windows Classic", "Nimbus", "Metal", "CDE/Motif");

        final float jButtonFontSize_on_unknownScale_unknownJava_unknownOS_withLookAndFeelWindows = new JButton().getFont().getSize2D();  // 21.0 on 200% desktop on Java 8 // 11.0 on 100% desktop on Java 8

        final Integer[] paintScalingInPercent = new Integer[1];

        final JDialog bruteForceJava9ScalingCheck = new JDialog((Frame) null, "", true) {

            {
                setLocation(-1000, -1000); // Outamysight!

                final Runnable fallbackInCaseOlderJavaVersionDoesNotEndUpClosingThisWindow = () -> {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    SwingUtilities.invokeLater(() -> {
                        paintScalingInPercent[0] = 100;
                        dispose();
                    });
                };
                final Thread t = new Thread(fallbackInCaseOlderJavaVersionDoesNotEndUpClosingThisWindow);
                t.setDaemon(true);
                t.setName("GUI scaling detector fallback thread");
                t.start();
            }

            @Override
            public void paint(final Graphics graphics) {

                final Graphics2D g = (Graphics2D) graphics;
                final AffineTransform originalTransform = g.getTransform();
                paintScalingInPercent[0] = (int) Math.round(originalTransform.getScaleX() * 100);
                dispose();
            }
        };
        bruteForceJava9ScalingCheck.setVisible(true); // This call blocks until dispose() is reached.

        if (paintScalingInPercent[0] == null) {

            throw new Error("Unexpected behavior: Modal dialog did not block!");

        } else if (paintScalingInPercent[0] != 100) {

            GUISCALINGFACTOR_SYSTEM = paintScalingInPercent[0] * 0.01;
            GUISCALINGFACTOR_COMPONENTSANDFONTS = 1; // Java 9 does everything. The developer's considerations are made unnecessary/harmless by this "1".
            GUISCALINGFACTOR_CUSTOMGRAPHICS = GUISCALINGFACTOR_SYSTEM;

        } else {

            final double factorPreliminary = jButtonFontSize_on_unknownScale_unknownJava_unknownOS_withLookAndFeelWindows / JBUTTONFONTSIZE_ON_100PERCENTSCALE_JAVA8_W10_WITH_LOOKANDFEEL_WINDOWSORSYSTEMORXPLATFORMORWINCLASSIC;

            // If we just divide the two, we get 1.454545... on a 150% desktop, because the font sizes
            // chosen by Java are integer values, so we experience a rounding error.
            // The crappy but probably in most cases nicely working solution is: We round the result to .25 steps!

            GUISCALINGFACTOR_SYSTEM = Math.round(factorPreliminary * 4) / 4d;
            GUISCALINGFACTOR_COMPONENTSANDFONTS = GUISCALINGFACTOR_SYSTEM;
            GUISCALINGFACTOR_CUSTOMGRAPHICS = GUISCALINGFACTOR_SYSTEM;
        }

        GUISCALINGFACTOR_REALCOMPONENTSIZE = GUISCALINGFACTOR_CUSTOMGRAPHICS / GUISCALINGFACTOR_COMPONENTSANDFONTS;

        System.err.println("GUISCALINGFACTOR_SYSTEM             = " + GUISCALINGFACTOR_SYSTEM);
        System.err.println("GUISCALINGFACTOR_COMPONENTSANDFONTS = " + GUISCALINGFACTOR_COMPONENTSANDFONTS);
        System.err.println("GUISCALINGFACTOR_CUSTOMGRAPHICS     = " + GUISCALINGFACTOR_CUSTOMGRAPHICS);
        System.err.println("GUISCALINGFACTOR_REALCOMPONENTSIZE  = " + GUISCALINGFACTOR_REALCOMPONENTSIZE);

    }

}
Другие вопросы по тегам