Есть ли способ отключить собственное масштабирование DPI в качели с помощью кода или командной строки (JDK 9+)?

Недавно я принял решение перейти на более новую версию JDK с той, которую использовал. (В частности, с jdk1.8.0_261 по jdk-14.0.2). Это обновление прошло довольно гладко, так как большинство функций работают так, как я ожидал, и я смог найти, как приспособиться ко всему, что было не так.

Однако, как показано в заголовке, я столкнулся с проблемой в конкретном приложении, которое я пишу и которое использует Swing. Мой основной монитор — это монитор 4k, и я установил масштабирование системы Windows для этого монитора на 200%. В то время как любое другое приложение Swing, масштабируемое в соответствии с этим параметром DPI, является хорошим, для этого конкретного приложения я хочу отключить это масштабирование и отрисовывать пиксели 1: 1, особенно когда это приложение распространяется.

Есть ли способ отключить это автоматическое масштабирование с помощью аргумента виртуальной машины? Или есть способ отключить масштабирование во время выполнения до того, как будут сделаны какие-либо вызовы в библиотеку свинга? (Подобно тому, как любые изменения внешнего вида должны быть выполнены до любых других вызовов библиотеки.)

Для справки:

Вот код, который я использую для создания своего JFrame и для создания графического объекта, на который я все рисую, что происходит в цикле с синхронизацией вне этого класса и объема этого вопроса.

public class ScreenManager {
    private GraphicsDevice device;
    private final JFrame frame;
    
    private static ScreenManager instance;
    
    public ScreenManager() {
        instance = this;
        
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        this.device = env.getDefaultScreenDevice();
        
        this.frame = new JFrame(game.getGame().gameName());
        this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.frame.setUndecorated(true);
        this.frame.setIgnoreRepaint(true);
        this.frame.setResizable(false);
        
        this.device.setFullScreenWindow(frame);
        this.device.setDisplayMode(device.getDisplayMode());
        
        this.frame.createBufferStrategy(2);
    }

    public Graphics2D getRenderGraphics() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            return g;
        }
        return null;
    }

    public void updateDisplay() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            if (!strategy.contentsLost()) {
                strategy.show();
            }
        }
        Toolkit.getDefaultToolkit().sync();
    }
    // other methods go here
}

Использование AffineTransforms, хотя и приветствуется, на самом деле не поможет решить эту проблему, поскольку мне нужно иметь возможность отображать исходное разрешение любого монитора и уже широко использовать AffineTransforms для объекта Graphics.

EDIT: я безуспешно пробовал System.setProperty("sun.java2d.dpiaware", "false"); в качестве первой строки в main. Свойство неправильное?

EDIT 2: добавление большей ясности:

Мой метод рендеринга выглядит так:

private void render(Graphics2D g) {
    // Black out the screen to prevent old stuff from showing
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, ScreenManager.getScreenWidth(), ScreenManager.getScreenHeight());
    
    // Set the transform for zoom to draw the zoomed stuffs
    AffineTransform saveState = g.getTransform();
    AffineTransform cameraTransform = ScreenManager.getInstance().getCamera().getCurrentTransform();
    g.transform(cameraTransform);
    
    // RENDER UPDATEABLE
    this.game.getController().renderGame(g);
    
    // REDNER STATIC GUI
    g.setTransform(saveState);
    // draw mouse cords
    Point mouse = this.mouseHandler.getFrameMousePoint();
    if (mouse != null) {
        Font f = new Font("Consolas", 0, 40);
        Point2D scaledPos;
        try {
            scaledPos = cameraTransform.inverseTransform(mouse, null);
        } catch (NoninvertibleTransformException e) {
            scaledPos = mouse;
            e.printStackTrace();
        }
        String s1 = mouse.toString();
        String s2 = scaledPos.toString();
        Rectangle2D r = f.getMaxCharBounds(g.getFontRenderContext());
        int yOff = (int) r.getHeight();
        Color c = new Color(100, 100, 100, 191);
        g.setColor(c);
        g.fillRect(mouse.x, mouse.y, (int) (r.getWidth() * (s1.length() > s2.length() ? s1.length() : s2.length())), yOff + yOff + yOff);
        g.setColor(Color.WHITE);
        g.setFont(f);
        g.drawString(s1, mouse.x, mouse.y + yOff);
        g.drawString(s2, mouse.x, mouse.y + yOff + yOff);
    }
}

И этот метод вызывается здесь:

Graphics2D g = screenManager.getRenderGraphics();
render(g);
g.dispose();
screenManager.updateDisplay(); // SYNCS SCREEN WITH VSync

Я распечатал AffineTransform, который находится в графическом объекте при его первом создании, и он печатается вот так AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]], что наводит меня на мысль, что масштабирование выполняется в коде. Однако проблема в том, что объекты, отображаемые с помощью моего пользовательского преобразования ScreenManager.getInstance().getCamera().getCurrentTransform();, также увеличиваются в 2 раза, даже несмотря на то, что я не связываюсь со своим преобразованием и не устанавливаю его в свое. (На самом деле неважно, я понял, что я делаю g.transform в своем преобразовании, а не g.setTransform, хотя я все еще включаю это в рецензию).

Обходной путь решения моей проблемы с рендерингом состоит в том, чтобы сделать g.setTransform(AffineTransform.getScaleInstance(1, 1); Однако это больше, чем я хочу сделать, и это (после попытки в моем коде) не решает проблему с пространством координат awt. Это означает, что рендеринг работает так, как хотелось бы, но положение мыши - нет. Когда мышь находится в правом нижнем углу моего экрана, она дает положение, равное половине исходного разрешения моего монитора. Это решение не работает для меня. Время обработки является ценным, и тем больше вычислений необходимо выполнить, чтобы ОТМЕНИТЬ добавление этой функции. Вот ссылка, в которой обсуждается, как это будет реализовано. Я ссылаюсь на это, чтобы указать на раздел, где упоминается, что разработчик может это сделать, но это потребует много работы со стороны разработчиков.

Я все еще хотел бы знать, есть ли способ:

  • Отключить функцию с помощью аргумента виртуальной машины командной строки.
  • Отключить функцию с помощью фрагмента кода

person Joseph Terribile    schedule 28.07.2020    source источник
comment
поскольку мне нужно иметь возможность отображать в исходном разрешении - я считаю, что Swing обрабатывает масштабирование, устанавливая масштаб AffineTransform. Таким образом, вы можете попробовать заменить масштабирование на свое собственное, которое не масштабируется. и уже широко используют AffineTransforms для объекта Graphics. - вы НЕ должны изменять преобразование объекта Graphics, переданного в метод рисования. Вместо этого вы создаете копию объекта Graphics и конкатенируете () свое преобразование с преобразованием по умолчанию скопированного экземпляра Graphics.   -  person camickr    schedule 29.07.2020
comment
Я просмотрел то, что вы сказали о качании, выполняющем масштабирование с использованием AffineTransforms, вы правы, есть масштабное аффинное преобразование. Однако, не говоря об использовании аффинных преобразований для графического объекта, никакие компоненты Swing не используются, кроме рассматриваемого JFrame. Единственные другие используемые элементы свинга - это четные слушатели для различных входных данных. Таким образом, это не вызовет проблем с другими компонентами. Кроме того, этот кадр игнорирует перерисовку, поскольку механизм рендеринга находится в отдельной части кодовой базы и не зависит от Swing. Я все еще предпочитаю возможность полностью отключить это   -  person Joseph Terribile    schedule 29.07.2020
comment
Вы пытались установить преобразование графического объекта, который вы создаете в getRenderGraphics()?   -  person weisj    schedule 29.07.2020
comment
Когда вы не используете никакой другой компонент Swing, т.е. вообще не зависите от логической системы координат, я не понимаю вашего вопроса. Просто установив преобразование идентичности в Graphics2D в начале, вы получите собственное разрешение. Вам нужно только извлечь исходные значения масштабирования, чтобы узнать, какой фактический размер холста у вас есть в абсолютных числах. Когда вы говорите, что уже используете преобразования, это не усложняет ваш код. Так почему бы не сделать это вместо того, чтобы запрашивать конкретный вариант реализации, который не будет работать в других JRE?   -  person Holger    schedule 29.07.2020
comment
И если вы не используете какую-либо функцию Swing, почему вы вообще используете JFrame, а не только Frame или Window?   -  person Holger    schedule 29.07.2020


Ответы (1)


Вернувшись к этому вопросу через некоторое время работы над чем-то другим, я нашел решение, которое искал, в основном случайно.

Решением, которое я искал, было системное свойство Java или аргумент JVM, который отключил бы осведомленность о DPI компонентов Swing, а это означает, что в системе с масштабированием 200% пространство ввода пользователя и рендеринг компонентов будут соответствовать собственному разрешению экрана и не масштабированное разрешение 200% (например, ожидание считывания ввода мыши и рендеринга на экране с разрешением 3840x2160, а не того, что происходит, когда ввод мыши был ограничен разрешением 1920x1080).

Это решение обсуждалось в этом ответе на похожий вопрос. Я повторю это здесь, чтобы быть полным.

Решение состоит в том, чтобы передать -Dsun.java2d.uiScale=1 в виртуальную машину в качестве аргумента виртуальной машины. Это заставляет масштаб пользовательского интерфейса быть равным 1, игнорируя любое системное масштабирование и рендеринг, а также собирая данные, вводимые мышью, с исходным разрешением. Я также могу подтвердить, что вызов System.setProperty("sun.java2d.uiScale", "1"); перед вызовом любого класса свинга также приведет к результату, который я искал.

person Joseph Terribile    schedule 23.12.2020