Попытка нарисовать изображение в JFrame с помощью Java BufferedImage, Graphics

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

У меня проблема с моим кодом - он не рисует графику. Что я делаю неправильно?

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;


public class ScreenCapture extends JFrame {

    BufferedImage screenCapture;
    Graphics screenCaptureGraphics;
    private static int recurseCount = 0;
    private static float $scale = 0.9f;
    private static float scale = 1.0f;
    private static int height;
    private static int width;

    ScreenCapture() {
        try {
            screenCapture = new Robot().createScreenCapture(
                       new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
            height = screenCapture.getHeight();
            width = screenCapture.getWidth();
            setSize(new Dimension(width, height));
            addWindowListener(new LocalWindowListener());
            Graphics g = recursiveDraw(screenCapture, getGraphics());
            paint(g);
        } catch (HeadlessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (AWTException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private Graphics recursiveDraw(BufferedImage img, Graphics imgG) {
        updateScale(++recurseCount);
        float newWidth = scale*width;
        float newHeight = scale*height;
        int w = (int) newWidth;
        int h = (int) newHeight;
        System.out.println("W: " + w + "; H: " + h);
        if (w >= 10 && h >= 10) {
            //scale image
            System.out.print("Creating scaled Image...");
            Image scaled = img.getScaledInstance(w, h, Image.SCALE_SMOOTH);
            BufferedImage resized = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            imgG = resized.createGraphics();
            imgG.drawImage(scaled, 0, 0, null);
            System.out.println("...Image drawn to graphics");
            //return new graphics
            return recursiveDraw(resized, imgG);
        } else {
            //otherwise return old graphics
            System.out.println("Completed.");
            return imgG;
        }
    }


    private void updateScale(int count) {
        for (int i=0; i<count; i++) {
            scale *= $scale;
        }
        System.out.println("Updated scale: " + scale + "; Recurse count: " + recurseCount);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ScreenCapture().setVisible(true);
            }
        });
    }

    private class LocalWindowListener extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent e) {
            System.exit(0); return;
        }
    }

}

EDIT: Это то, что я пробовал после ответа @andrew-thompson:

ScreenCapture() {
    try {
        screenCapture = new Robot().createScreenCapture(
                   new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
        height = screenCapture.getHeight();
        width = screenCapture.getWidth();
        setSize(new Dimension(width, height));
        addWindowListener(new LocalWindowListener());
        setLayout(new GridLayout());
        add(new PaintPanel());
    } catch (HeadlessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (AWTException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

private class PaintPanel extends JPanel {
    @Override
    public void paintComponent(Graphics g) {
        g=recursiveDraw(screenCapture, g);
        //what to do with g?
    }
}

У меня все еще есть та же проблема, когда я не знаю, как заставить BufferedImage рисовать графику.


person Ozzy    schedule 18.03.2012    source источник
comment
LocalWindowListener - в этом нет необходимости. Просто позвоните JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)   -  person Andrew Thompson    schedule 18.03.2012
comment
О, я добавил это раньше, когда EXIT_ON_CLOSE тоже не работал; потому что была проблема с моим циклом updateScale, продолжающимся вечно. После того, как я исправил петлю, я забыл изменить это обратно.   -  person Ozzy    schedule 18.03.2012


Ответы (4)


отделит ваш код Swing от вашего кода создания рекурсивного изображения. На самом деле рассмотрите возможность создания статического метода, который создает и возвращает BufferedImage и в котором нет кода Swing. Затем пусть ваш графический интерфейс вызовет метод, когда захочет, возьмет изображение и либо запишет его на диск, либо отобразит в ImageIcon JLabel.

Когда я сделал это (фактически сегодня), я создал рекурсивный метод с этой сигнатурой

private static void recursiveDraw(BufferedImage img, Graphics imgG, double scale) { 

и с этим телом метода (в псевдокоде)

start recursiveDraw method 
   // most important: all recursions must have a good ending condition: 
   get img height and width. If either <= a min, return 
   create a BufferedImage, smlImg, for the smaller image using the height, 
        width and scale factor 
   Get the Graphics object, smlG, from the small image 
   Use smlG.drawImage(...) overload to draw the big image in shrunken 
        form onto the little image 
   recursively call recursiveDraw passing in smlImg, smlG, and scale. 
   dispose of smlG 
   draw smlImg (the small image) onto the bigger one using the bigger's 
        Graphics object (passed into this method) and a different 
        overload of the drawImage method. 
end recursiveDraw method

Этот алгоритм привел к таким изображениям, как: введите описание изображения здесь

Например:

import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class RecursiveDrawTest {
   private static final Color BACKGRND_1 = Color.green;
   private static final Color BACKGRND_2 = Color.MAGENTA;
   private static final Color FOREGRND_1 = Color.blue;
   private static final Color FOREGRND_2 = Color.RED;

   private static void createAndShowGui() {
      final JPanel mainPanel = new JPanel(new BorderLayout());
      final JSlider slider = new JSlider(50, 90, 65);
      slider.setMajorTickSpacing(10);
      slider.setMinorTickSpacing(5);
      slider.setPaintLabels(true);
      slider.setPaintTicks(true);

      JPanel southPanel = new JPanel();
      southPanel.add(new JLabel("Percent Size Reduction:"));
      southPanel.add(slider);
      southPanel.add(new JButton(new AbstractAction("Create Recursive Image") {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            try {
               double scale = slider.getValue() / 100.0;
               BufferedImage img = createRecursiveImg(scale);
               ImageIcon icon = new ImageIcon(img);
               JLabel label = new JLabel(icon);

               Window win = SwingUtilities.getWindowAncestor(mainPanel);
               JDialog dialog = new JDialog(win, "Image", ModalityType.MODELESS);
               dialog.getContentPane().add(label);
               dialog.pack();
               dialog.setLocationRelativeTo(null);
               dialog.setVisible(true);
            } catch (AWTException e) {
               e.printStackTrace();
            }
         }
      }));

      mainPanel.add(new JScrollPane(new JLabel(new ImageIcon(createLabelImg()))), 
            BorderLayout.CENTER);
      mainPanel.add(southPanel, BorderLayout.PAGE_END);

      JFrame frame = new JFrame("RecursiveDrawTest");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   // create a background image to display in a JLabel so that the GUI
   // won't be boring.
   private static BufferedImage createLabelImg() {
      Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
      int width = (5 * d.width) / 6;
      int height = (5 * d.height) / 6;
      BufferedImage img = new BufferedImage(width, height, 
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(new GradientPaint(0, 0, BACKGRND_1, 40, 40, BACKGRND_2, true));
      g2.fillRect(0, 0, width, height);
      g2.setPaint(new GradientPaint(0, height, FOREGRND_1, 40, height - 40, FOREGRND_2, true));
      g2.fillOval(0, 0, 2 * width, 2 * height);
      g2.dispose();
      return img;
   }

   // non-recursive image to get the initial image that will be drawn recursively
   public static BufferedImage createRecursiveImg(double scale) throws AWTException {
      Robot robot = new Robot();
      Dimension screenSz = Toolkit.getDefaultToolkit().getScreenSize();
      Rectangle screenRect = new Rectangle(screenSz);
      BufferedImage img = robot.createScreenCapture(screenRect);
      Graphics g = img.getGraphics();
      recursiveDraw(img, g, scale); // call recursive method
      g.dispose();
      return img;
   }

   // recursive method to draw image inside of image
   private static void recursiveDraw(BufferedImage img, Graphics g, double scale) {
      int w = img.getWidth();
      int h = img.getHeight();

      int smlW = (int)(w * scale);
      int smlH = (int)(h * scale);
      // criteria to end recursion
      if (smlW <= 1 || smlH <= 1) {
         return; 
      }

      BufferedImage smlImg = new BufferedImage(smlW, smlH, BufferedImage.TYPE_INT_ARGB);
      Graphics smlG = smlImg.getGraphics();
      // draw big image in little image, scaled to little image
      smlG.drawImage(img, 0, 0, smlW, smlH, null);

      // recursive call
      recursiveDraw(smlImg, smlG, scale);
      smlG.dispose(); // clear resources when done with them

      // these guys center the smaller img on the bigger
      int smlX = (w - smlW) / 2;
      int smlY = (h - smlH) / 2;
      // draw small img on big img
      g.drawImage(smlImg, smlX, smlY, smlW, smlH, null);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
person Hovercraft Full Of Eels    schedule 18.03.2012
comment
ха-ха ты Fubarable. Я не хотел публиковать этот вопрос в посте на java-форумах на случай, если кто-то скажет, что я кормлю другого парня с ложечки. - person Ozzy; 18.03.2012
comment
Моя программа отлично рекурсирует, и выходные строки сообщают мне новый размер каждой рекурсии (он масштабируется вниз, пока не достигнет размера около 10x10 пикселей). У меня проблемы с рисованием графики, особенно поверх другой. - person Ozzy; 18.03.2012
comment
@user1031312: user1031312: Рисование друг над другом выполняется на BufferedImage, а не на компоненте. Как только ваш BufferedImage будет готов, вы можете отобразить его в ImageIcon, хранящемся в JLabel. - person Hovercraft Full Of Eels; 18.03.2012

 Graphics g = recursiveDraw(screenCapture, getGraphics());

Не звони getGraphics(). Переопределите paint(Graphics)1 и используйте предоставленный экземпляр Graphics.

  1. При использовании Swing лучше всего переопределить метод paintComponent(Graphics) метода JComponent или JPanel. Затем добавьте это в контейнер верхнего уровня.
person Andrew Thompson    schedule 18.03.2012
comment
Спасибо за Ваш ответ. Я не уверен, как это реализовать - пожалуйста, проверьте новый код после EDIT? Я не уверен, что делать в методе paintComponent с объектом Graphics, чтобы он работал. - person Ozzy; 18.03.2012

Это то, на что вы надеетесь:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Toolkit;

import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

import javax.swing.*;

public class CaptureScreen extends JPanel
{
    private BufferedImage screenShot;
    private JLabel imageLabel;
    private BufferedImage secondScreenShot;

    public void createAndDisplayGUI()
    {
        JFrame frame = new JFrame("CAPTURE SCREEN");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationByPlatform(true);

        //imageLabel = new JLabel();
        //getImageForLabel();
        //add(imageLabel);

        frame.getContentPane().add(this);
        frame.setSize(600, 600);
        frame.setVisible(true);
    }

    private void getImageForLabel()
    {
        Robot robot = null;
        try
        {
            robot = new Robot();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }

        screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
        ImageIcon icon = new ImageIcon(screenShot);
        imageLabel.setIcon(icon);
    }

    public void paintComponent(Graphics g)
    {
        Robot robot = null;
        try
        {
            robot = new Robot();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }

        screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
        secondScreenShot = getScaledImage((Image) screenShot, 300, 300);

        g.drawImage(screenShot, 0, 0, null);
        g.drawImage(secondScreenShot, 150, 150, null);
    }

    private BufferedImage getScaledImage(Image srcImg, int w, int h)
    {
        BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TRANSLUCENT);
        Graphics2D g2 = resizedImg.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(srcImg, 0, 0, w, h, null);
        g2.dispose();
        return resizedImg;
    }

    public static void main(String... args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new CaptureScreen().createAndDisplayGUI();
            }
        });
    }
}

Вот результат:

Снимок экрана поверх другого

person nIcE cOw    schedule 18.03.2012
comment
Я знаю, как отображать изображение в Swing, но мой вопрос заключается в том, как рисовать графику поверх графики или создавать изображение и рисовать меньшее изображение поверх него. Я знаю, что есть другие методы для достижения того же эффекта, но вы уходите от моего первоначального вопроса. Я учусь рисовать. - person Ozzy; 18.03.2012

Избавьтесь от рекурсии. Создайте одно буферизованное изображение нужного размера и создайте для него объект Graphics. Просто используйте цикл для рисования постепенно уменьшающихся изображений до любого порога, который вы выберете. Наконец, используйте g.drawImage() внутри paintComponent(), чтобы нарисовать изображение на экране. Если вы сохраняете рекурсию, вам нужно передать изображение и постоянно накладывать уменьшенное изображение. Вам не нужно ничего возвращать из метода.

person nicktalbot    schedule 18.03.2012
comment
Just use a loop to draw progressively smaller scaled images down to whatever threshold you choose Собственно, это мой вопрос прямо сейчас. Если бы я знал это, мне не понадобились бы альтернативные методы. Я переместил g.drawImage(scaledImage, 0, 0, null) в paintComponent, но он натирает фон старого изображения, поэтому я получаю изображение меньшего размера, но с серым фоном. - person Ozzy; 18.03.2012