Убогий текст при отрисовке закадровых изображений в Java

Я застрял (за пределами удовольствия) в попытке исправить качество текста с помощью двойной буферизации закадрового изображения. Снимок экрана стоит тысячи слов.

  • Уродливый String рисуется на закадровом изображении, а затем копируется в аргумент Graphics paintComponent.
  • Красивый String записывается непосредственно в аргумент Graphics paintComponent, минуя закадровое изображение.

Оба экземпляра Graphics (экранный и закадровый) одинаково настроены с точки зрения качества рендеринга, сглаживания и так далее...

Заранее большое спасибо за вашу мудрость.


Далее следует очень простой код:

public class AcceleratedPanel extends JPanel {
    private Dimension     osd; //offscreen dimension
    private BufferedImage osi; //offscreen image
    private Graphics      osg; //offscreen graphic

    public AcceleratedPanel() {
        super();
    }

    @Override
    public final void paintComponent(Graphics g) {
        super.paintComponent(g); 

        // --------------------------------------
        //OffScreen painting

        Graphics2D osg2D = getOffscreenGraphics();
        setupGraphics(osg2D);
        osg2D.drawString("Offscreen painting", 10, 20);

        //Dump offscreen buffer to screen
        g.drawImage(osi, 0, 0, this);

        // --------------------------------------
        // OnScreen painting
        Graphics2D gg = (Graphics2D)g;
        setupGraphics(gg);
        gg.drawString("Direct painting", 10, 35);
    }

    /*
    To make sure same settings are used in different Graphics instances,
    a unique setup procedure is used.
    */
    private void setupGraphics(Graphics2D g) {
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    }

    private Graphics2D getOffscreenGraphics() {
        //Graphics Acceleration
        Dimension currentDimension = getSize();
        if (osi == null || !currentDimension.equals(osd)) {
            osi = (BufferedImage)createImage(currentDimension.width, currentDimension.height);
            osg = osi.createGraphics();
            osd = currentDimension;
        }
        return (Graphics2D) osg;
    }

} //End of mistery

person Enrique Pérez Soler    schedule 18.07.2017    source источник
comment
... за гранью веселья. Добро пожаловать в работу.   -  person Mad Physicist    schedule 18.07.2017
comment
Метод прямого рисования выигрывает от того, что контекст Graphics был настроен для экранного устройства — он может представлять более высокий DPI, тогда как, напротив, ваш BufferedImage, вероятно, работает между 72-92 (?) DPI — К сожалению, в последний раз Я проверил (это было некоторое время назад), нет простого способа получить DPI экранного устройства (это могло недавно измениться), так что вы можете потратить немного времени, угадывая, какой лучший размер для вашего BufferedImage   -  person MadProgrammer    schedule 19.07.2017


Ответы (2)


Вы не рисуете две строки одним цветом. Цвет по умолчанию для внеэкранной графики — rgb(0, 0, 0) (то есть чисто черный), в то время как Swing установит цвет объекта Graphics на цвет внешнего вида по умолчанию, который для меня в Windows 7, используя тему по умолчанию, — rgb (51, 51, 51) или темно-серый.

Попробуйте поместить g.setColor(Color.BLACK); в свой метод setupGraphics, чтобы обе строки отображались одним цветом.

person VGR    schedule 18.07.2017

Спасибо за ответы.

Упомянув DPI, MadProgrammer привел меня к рабочему исправлению, которое я предлагаю здесь скорее как обходной путь, чем как «чистое» решение, которым можно гордиться. Во всяком случае, это решает проблему.

Я заметил, что хотя разрешение моего экрана составляет 2880x1800 (дисплей Retina), метод MouseEvent getPoint() считывает x=1440, y=900 в правом нижнем углу экрана. Затем размер JPanel составляет половину разрешения экрана, хотя и охватывает весь экран.

Как видно, решение выглядит следующим образом:

  • во-первых, создайте закадровое изображение, соответствующее разрешению экрана, а не JPanel.getSize(), как предлагается в десятках статей о двойной буферизации.

  • затем нарисуйте закадровое изображение, применяя увеличительное преобразование, большее, чем необходимо, в частности, масштабирование на r = отношение размера экрана к размеру панели.

  • наконец, скопируйте уменьшенную версию закадрового изображения обратно на экран, применив коэффициент сжатия r (или коэффициент масштабирования 1/r).


Реализация решения разбита на два метода:

  • Исправленная версия первоначального paintComponent, опубликованного ранее,
  • вспомогательный метод getDPIFactor() объясняется позже.

Измененный метод paintComponent выглядит следующим образом:

public final void paintComponent(Graphics g) {
    super.paintComponent(g); 
    double dpiFactor = getDPIFactor();
    // --------------------------------------
    //OffScreen painting

    Graphics2D osg2D = getOffscreenGraphics();
    setupGraphics(osg2D);
    //Paint stuff bigger than needed
    osg2D.setTransform(AffineTransform.getScaleInstance(dpiFactor, dpiFactor));
    //Custom painting
    performPainting(osg2D);

    //Shrink offscreen buffer to screen. 
    ((Graphics2D)g).drawImage(
            osi, 
            AffineTransform.getScaleInstance(1.0/dpiFactor, 1.0/dpiFactor), 
            this);

    // --------------------------------------
    // OnScreen painting
    Graphics2D gg = (Graphics2D)g;
    setupGraphics(gg);
    gg.drawString("Direct painting", 10, 35);
}

Для выполнения задания необходимо получить разрешение экрана.

Вызов Toolkit.getDefaultToolkit().getScreenResolution() не решает проблему, так как возвращает размер JPanel, покрывающий весь экран. Как видно выше, эта цифра не соответствует реальному размеру экрана в физических точках.

Сержант Бош разъяснил, как получить эту датум, в этот пост stackoverflow. Я адаптировал его код для реализации последней части головоломки, getDPIFactor().


/*
 * Adapted from Sarge Bosch post in StackOverflow.
 * https://stackoverflow.com/questions/40399643/how-to-find-real-display-density-dpi-from-java-code
 */
private Double getDPIFactor() {
    GraphicsDevice defaultScreenDevice = 
            GraphicsEnvironment.getLocalGraphicsEnvironment()
                    .getDefaultScreenDevice();

    // on OS X, it would be CGraphicsDevice
    if (defaultScreenDevice instanceof CGraphicsDevice) {
        CGraphicsDevice device = (CGraphicsDevice) defaultScreenDevice;

        // this is the missing correction factor.
        // It's equal to 2 on HiDPI a.k.a. Retina displays
        int scaleFactor = device.getScaleFactor();

        // now we can compute the real DPI of the screen
        return scaleFactor * (device.getXResolution() + device.getYResolution()) / 2
                / Toolkit.getDefaultToolkit().getScreenResolution();
    } else
        return 1.0;
}

Этот код решает проблему для дисплеев Mac Retina, но я боюсь, что нигде больше, так как CGraphicsDevice является явным упоминанием проприетарной реализации GraphicsDevice.

У меня нет другого оборудования HDPI, с которым можно было бы поиграть, чтобы предложить более широкое решение.

person Enrique Pérez Soler    schedule 19.07.2017