Безголовая живопись

Я хотел бы нарисовать JPanel в BufferedImage в безголовом режиме (вообще без графического интерфейса на экране).

final JPanel panel = createPanel();
panel.setSize(panel.getPreferredSize());
panel.validate();

//  JFrame frame = new JFrame();
//  frame.getContentPane().add(panel);
//  frame.pack();
//  frame.setVisible(true);

final BufferedImage image = new BufferedImage(
            panel.getBounds().width, 
            panel.getBounds().height, 
            BufferedImage.TYPE_INT_ARGB
);

final Graphics2D gc = image.createGraphics();
gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

try {
    panel.paint(gc);
    ...save the image somewhere...
} finally {
    gc.dispose();
}

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

Вот SSCCE:

    public class Example {

    private static JPanel createPanel() {
        final JPanel panel = new JPanel(new GridBagLayout());           
        final JLabel label = new JLabel("Yeah, it's working!", SwingConstants.CENTER);
        label.setFont(new Font("Arial", Font.PLAIN, 12));           
        final GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;
        constraints.weightx = 1;
        panel.add(label, constraints);          
        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                final JPanel panel = createPanel();
                panel.setSize(panel.getPreferredSize());
                panel.validate();

    //              JFrame frame = new JFrame();
    //              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //              frame.getContentPane().add(panel);
    //              frame.pack();
    //              frame.setVisible(true);

                final BufferedImage image = new BufferedImage(
                        panel.getBounds().width, 
                        panel.getBounds().height, 
                        BufferedImage.TYPE_INT_ARGB
                );    
                final Graphics2D gc = image.createGraphics();
                gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);    
                try {
                    panel.paint(gc);
                    ImageIO.write(image, "png", new File("image.png"));
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    gc.dispose();
                }                   
            }
        });
    }    
}

person Behnil    schedule 10.06.2013    source источник
comment
Традиционный способ сделать это под Linux — запустить поддельный провайдер X-window, который на самом деле ничего не отображает. Но это делает Java счастливой.   -  person Lee Meador    schedule 10.06.2013
comment
Чтобы быстрее получить помощь, опубликуйте SSCCE.   -  person Guillaume Polet    schedule 10.06.2013
comment
См. Почему заголовок JTable не отображается на изображении? советы по рисованию нереализованных компонентов. Если вы не можете заставить его работать на основе этого, следуйте совету @GuillaumePolet и опубликуйте SSCCE.   -  person Andrew Thompson    schedule 10.06.2013
comment
Проблема в том, что ваша этикетка имеет нулевой размер. Смотрите мой отредактированный ответ с простым исправлением (назовите doLayout вместо validate()). См. также ответ camickr   -  person Guillaume Polet    schedule 10.06.2013


Ответы (4)


Компоненты имеют нулевой размер до тех пор, пока компонент не будет реализован, поэтому методы рисования не работают.

Ознакомьтесь с изображением экрана. Он решит эту проблему за вас, вызвав doLayout() на панели, чтобы убедиться, что все компоненты имеют допустимый размер.

person camickr    schedule 10.06.2013
comment
Моя ошибка, это проблема нулевого размера, но на ее подкомпонентах. Ваш ответ абсолютно правильный: +1 - person Guillaume Polet; 10.06.2013
comment
Да, это работает. Мне интересно, почему я должен рекурсивно вызывать doLayout() самостоятельно. Это должно быть сделано путем простой проверки компонента. ИМХО, еще одна ловушка Swing. - person Behnil; 11.06.2013

Вот фрагмент, который рисует простую метку в файле изображения, а затем файл изображения открывается (если на настольном компьютере).

import java.awt.Desktop;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JLabel;

public class Test {

    public static void main(String[] args) throws IOException {
        JLabel label = new JLabel("Hello world");
        label.setSize(label.getPreferredSize());
        BufferedImage image = new BufferedImage(label.getWidth(), label.getHeight(), BufferedImage.TYPE_INT_ARGB);
        label.paint(image.getGraphics());
        File output = new File("C:\\test\\hello world.png");
        if (!output.getParentFile().exists()) {
            output.getParentFile().mkdirs();
        }
        ImageIO.write(image, "png", output);
        Desktop.getDesktop().open(output);
    }

}

РЕДАКТИРОВАТЬ (с вашим SSCCE):

Не вызывайте validate(), а doLayout() на своей панели (если у вас есть вложенные панели, обязательно вызывайте их рекурсивно):

import java.awt.Desktop;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

public class Example {

    private static JLabel label;

    private static JPanel createPanel() {
        final JPanel panel = new JPanel(new GridBagLayout());

        label = new JLabel("Yeah, it's working!", SwingConstants.CENTER);
        label.setFont(new Font("Arial", Font.PLAIN, 12));

        final GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;
        constraints.weightx = 1;
        panel.add(label, constraints);

        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                final JPanel panel = createPanel();
                panel.setSize(panel.getPreferredSize());
                panel.doLayout();
                System.err.println(label.getSize() + "");
                // JFrame frame = new JFrame();
                // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                // frame.getContentPane().add(panel);
                // frame.pack();
                // frame.setVisible(true);

                final BufferedImage image = new BufferedImage(panel.getBounds().width, panel.getBounds().height,
                        BufferedImage.TYPE_INT_ARGB);

                final Graphics2D gc = image.createGraphics();
                gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

                try {
                    panel.paint(gc);
                    File output = new File("image.png");
                    ImageIO.write(image, "png", output);
                    Desktop.getDesktop().open(output);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    gc.dispose();
                }

            }
        });
    }
}
person Guillaume Polet    schedule 10.06.2013
comment
Опять же, doLayout() работает, только если у вас нет вложенных панелей. ScreenImage делает doLayout() рекурсивно, чтобы убедиться, что все компоненты на всех панелях имеют размер. - person camickr; 10.06.2013
comment
@camickr Да, действительно, это нужно делать рекурсивно. Я перешел по вашей ссылке и прочитал код ScreenImage. - person Guillaume Polet; 10.06.2013

Вызов addNotify для корневого компонента, который вы пытаетесь распечатать/рисовать, также решит проблему. Суть проблемы, по-видимому, заключается в том, что проверка вызывает короткое замыкание, если у контейнера нет «однорангового узла». Вызов addNotify инициализирует одноранговый узел, что позволяет последующим вызовам Component.validate функционировать так, как они обычно работают в небезголовом сценарии.

Отправьте это как альтернативное решение, потому что вызов doLayout не будет работать в случаях, когда компоненты более глубоко вложены из-за того, что doLayout не размещает подкомпоненты. (Хотя класс ScreenImage, упомянутый в ответе camickr, решает эту проблему, рекурсивно вызывая doLayout.)

person BonusLord    schedule 30.08.2017

Вместо этого попробуйте создать экземпляр BufferedImage с помощью одного из его конструкторов. GraphicsEnvironment класс, вероятно, должен использоваться с реальной GraphicsEnvironment (экран,...)

new  BufferedImage(panel.getBounds().width, panel.getBounds().height, BufferedImage.TYPE_INT_ARGB )
person gma    schedule 10.06.2013
comment
GraphicsEnvironment.getLocalGraphicsEnvironment() выдает исключение в безголовом режиме - person Guillaume Polet; 10.06.2013
comment
@GuillaumePolet Да, но я предполагаю, что @Behnil тестирует компьютер с LocalGraphicsEnvironment, но может генерировать неправильное изображение, если нет графического интерфейса (то есть JFrame). - person gma; 10.06.2013
comment
Это означает, что даже если у него есть настоящий GE, его код не работает. В конце концов, это вообще не будет работать в безголовой среде. Так что проблема не исходит оттуда напрямую (хотя я согласен, что в конечном итоге это не сработает). - person Guillaume Polet; 10.06.2013