4

I'm trying to capture the screen and then paint the image to a JFrame recursively while scaling the image (to create that effect you get when you look at a mirror in a mirror).

I'm having trouble with my code - it doesn't paint any graphics. What am I doing wrong?

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: This is what I tried after @andrew-thompson 's answer:

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?
    }
}

I still have the same problem where I don't know how to make the BufferedImage paint to the graphics.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Ozzy
  • 8,244
  • 7
  • 55
  • 95
  • `LocalWindowListener` - no need for that. Simply call `JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)` – Andrew Thompson Mar 18 '12 at 14:37
  • Oh I added that before when EXIT_ON_CLOSE wasn't working either; because there was a problem with my updateScale loop going on forever. After I fixed the loop I forgot to change this back. – Ozzy Mar 18 '12 at 14:46

4 Answers4

6

would separate out your Swing code from your recursive image creation code. In fact consider creating a static method that creates and returns the BufferedImage and that has no Swing code in it. Then have your GUI call the method when it wishes, and take the image and either write it to disk or display it in a JLabel's ImageIcon.

When I did this (today in fact), I created a recursive method with this signature

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

and with this method body (in pseudo-code)

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

This algorithm resulted in images like: enter image description here

For example:

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();
         }
      });
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • haha you are Fubarable. I didn't want to post this question in java-forums post in case someone said I was spoon-feeding the other guy. – Ozzy Mar 18 '12 at 16:43
  • My program recurses fine and the output strings tells me the new size of each recursion (it scales downwards until it reaches near 10x10 pixels). I'm having trouble actually drawing the graphics, especially ontop of another. – Ozzy Mar 18 '12 at 16:51
  • @user1031312: The drawing on top of each other is done on the BufferedImage, not on the component. Once your BufferedImage is done, you can display it in a ImageIcon held by a JLabel. – Hovercraft Full Of Eels Mar 18 '12 at 17:07
5
 Graphics g = recursiveDraw(screenCapture, getGraphics());

Don't call getGraphics(). Override paint(Graphics)1 & use the supplied Graphics instance.

  1. When using Swing, it is actually best to override the paintComponent(Graphics) method of a JComponent or JPanel. Then add that to the top-level container.
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Thanks for your answer. I'm not sure how to implement it though - please check the new code after the EDIT? I'm not sure what to do in the paintComponent method with the Graphics object to make it work. – Ozzy Mar 18 '12 at 14:44
2

Is this what you are hoping for :

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();
            }
        });
    }
}

Here is the output :

ScreenShot over another

nIcE cOw
  • 24,468
  • 7
  • 50
  • 143
  • I know how to display an Image in Swing but my question is how to paint a graphic over a graphic or create an image and paint a smaller image over that one. I know there are other methods to achieve the same effect but you are moving away from my original question. I'm learning how to paint. – Ozzy Mar 18 '12 at 15:59
1

Get rid of the recursion. Create a single buffered image of the correct size and create a Graphics object for it. Just use a loop to draw progressively smaller scaled images down to whatever threshold you choose. Finally use g.drawImage() inside paintComponent() to draw your image to the screen. If you keep the recursion you need to pass the image and continually overlay the scaled down image. You do not need to return anything from the method.

nicktalbot
  • 416
  • 2
  • 6
  • `Just use a loop to draw progressively smaller scaled images down to whatever threshold you choose` That is actually my question right now. If I knew that, I wouldn't need alternative methods. I moved `g.drawImage(scaledImage, 0, 0, null)` into `paintComponent` but it rubs over the background of the old image, so I'm getting a smaller image but with a grey background. – Ozzy Mar 18 '12 at 15:55