Java AWT Robot: Why doesn't a smaller screen capture equal a subimage of a capture of the entire screen?

66 Views Asked by At

UPDATE: It seems this is a bug in Java: https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322845

I encountered some weird behavior while experimenting with java.awt.Robot. If I use it to take two screenshots, one of the entire screen and one of just a subsection of the screen, the subsection of the screen has different pixels than a subimage of the original screenshot with the same coordinates as the subsection screenshot. Here is my code:

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ScreenCapTest {
    public static void main(String[] args) throws AWTException, IOException {
        // Construct Robot
        Robot r = new Robot();
        
        // Get dimensions of screen
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        
        // Take screenshot
        BufferedImage screen = r.createScreenCapture(new Rectangle(0,0, (int)screenSize.getWidth(), (int)screenSize.getHeight()));
        
        // Take screenshot of small section of screen
        int x = 5;
        int y = 5;
        int w = 50;
        int h = 50;
        BufferedImage subscreen = r.createScreenCapture(new Rectangle(x,y,w,h));
        
        // Create a subimage of the same section of the screen from the original screenshot
        BufferedImage subimageOfScreen = screen.getSubimage(x,y,w,h);
        
        // Are they equal?
        System.out.println(imgEqual(subimageOfScreen, subscreen));
        
        // Output images for comparison
        ImageIO.write(subimageOfScreen, "png", new File("subimage.png"));
        ImageIO.write(subscreen, "png", new File("subscreen.png"));
    }
    
    public static boolean imgEqual(BufferedImage image1, BufferedImage image2) {
        int width;
        int height;
        boolean imagesEqual = true;

        if( image1.getWidth()  == ( width  = image2.getWidth() ) && 
            image1.getHeight() == ( height = image2.getHeight() ) ){

            for(int x = 0;imagesEqual == true && x < width; x++){
                for(int y = 0;imagesEqual == true && y < height; y++){
                    if( image1.getRGB(x, y) != image2.getRGB(x, y) ){
                        imagesEqual = false;
                    }
                }
            }
        } else {
            imagesEqual = false;
        }
        return imagesEqual;
    }
}

Most of the time, it reports false, meaning that the subimage of a full screenshot from (5,5) to (55,55) is different from the screenshot of the screen from (5,5) to (55,55). Weirdly, for some values of x, y, w, and h, it prints true.

Below is an example pair of BufferedImages for which the code prints false. I can see that they are slightly different, but I don't understand why this behavior exists. What's going on?

Subimage of the full screenshot: Subimage of the full screenshot

Screenshot of the same portion of the screen: Screenshot of the same portion of the screen

1

There are 1 best solutions below

6
camickr On

Modified your code a bit. I didn't like that the (original) code continually created a sub image of your screen image to compare to the actual subImage. Still didn't help finding a match.

My next step was to create the sub image from the original screen image. This is a way to test if the issue is with the logic or the sub image. In this case the sub image matched.

I then went back to testing the two Robot created images and noticed that sub images with smaller width/height would match.

Further testing and the width did not appear to impact the matching of the sub image.

However, I noticed that the height did cause a problem. In my case the height needed to be 31 pixels smaller than the height of the screen. By coincidence 31 pixels is the height of the taskbar on my Windows machine. So my thought is that somehow when using the Robot the second image is not created correctly. Have no idea why.

In any case here is the code I used to test:

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

public class ScreenCapTest2 {
    public static void main(String[] args) throws AWTException {
        Robot r = new Robot();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        BufferedImage screen = r.createScreenCapture(new Rectangle(0,0, (int)screenSize.getWidth(), (int)screenSize.getHeight()));

        int xOffset = 20;
        int yOffset = 50;
        BufferedImage subscreen = screen.getSubimage(xOffset, yOffset, (int)screenSize.getWidth()-xOffset, (int)screenSize.getHeight()-yOffset);
//        BufferedImage subscreen = r.createScreenCapture(new Rectangle(xOffset, yOffset, (int)screenSize.getWidth()-xOffset, (int)screenSize.getHeight()-yOffset));
//        BufferedImage subscreen = r.createScreenCapture( new Rectangle(xOffset, 49, 1900, 1000) );

        System.out.println( screenSize );
        System.out.println(indexOfSubImage(screen, subscreen));
    }

    public static Point indexOfSubImage(BufferedImage img, BufferedImage subimg) {
        for (int x=0; x <= img.getWidth() - subimg.getWidth(); x++) {
            for (int y=0; y <= img.getHeight() - subimg.getHeight(); y++) {
//                if (imgEqual(img.getSubimage(x, y, subimg.getWidth(), subimg.getHeight()), subimg)) {
                if (imgEqual(x, y, img, subimg)) {
                    return new Point(x,y);
                }
            }
        }
        return new Point(-1,-1);
    }

    public static boolean imgEqual(int offsetX, int offsetY, BufferedImage image1, BufferedImage image2) {

        for(int x = 0; x < image2.getWidth(); x++){
            for(int y = 0; y < image2.getHeight(); y++){
                if( image1.getRGB(x + offsetX, y + offsetY) != image2.getRGB(x, y) ){
                    return false;
                }
            }
        }

        return true;
    }
}

The first subImage works for me in different test cases.

The second subImage only works when using (0, 0) as the offsets.

The third subImage seems to work on all "y" offsets up to 49.

Edit:

Using your new code I attempted to automate the testing:

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ScreenCapTest3{
    public static void main(String[] args) throws AWTException, IOException {

        for (int i = 0; i < 1920 - 50; i += 50)
            test( i );
    }

    public static void test(int xOffset) throws AWTException
    {
        // Construct Robot
        Robot r = new Robot();

        // Get dimensions of screen
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

        // Take screenshot
        BufferedImage screen = r.createScreenCapture(new Rectangle(0,0, (int)screenSize.getWidth(), (int)screenSize.getHeight()));

        // Take screenshot of small section of screen
        int x = xOffset;
        int y = 50;
//        int w = 1920 - xOffset;
//        int h = 1000;
        int w = 50;
        int h = 500;

        BufferedImage subscreen = r.createScreenCapture(new Rectangle(x,y,w,h));

        // Create a subimage of the same section of the screen from the original screenshot
        BufferedImage subimageOfScreen = screen.getSubimage(x,y,w,h);

        // Are they equal?
        System.out.println(xOffset + " : " + imgEqual(subimageOfScreen, subscreen));

        // Output images for comparison
//        ImageIO.write(subimageOfScreen, "png", new File("subimage.png"));
//        ImageIO.write(subscreen, "png", new File("subscreen.png"));
    }

    public static boolean imgEqual(BufferedImage image1, BufferedImage image2) {
        int width;
        int height;
        boolean imagesEqual = true;

        if( image1.getWidth()  == ( width  = image2.getWidth() ) &&
            image1.getHeight() == ( height = image2.getHeight() ) ){

            for(int x = 0;imagesEqual == true && x < width; x++){
                for(int y = 0;imagesEqual == true && y < height; y++){
                    if( image1.getRGB(x, y) != image2.getRGB(x, y) ){
//                      System.out.println(x + " : " + y);
                        imagesEqual = false;
                    }
                }
            }
        } else {
            imagesEqual = false;
        }
        return imagesEqual;
    }
}

I ran the code from the command prompt using: java ScreenCapTest3.java and got a mixture of true/false.

I then ran the code using: java ScreenCapTest.java > out.txt

When I look at out.txt everything is true.

So it appears to me that there is a subtle change in the sub image because the System.out.println(...) is displayed on the screen BEFORE the second image is captured?

Another edit:

Created a simple frame to cover the desktop. I used the buttons on the left and a large image in the center to break up the white space of an empty frame.

When you click the button I used a Timer to delay the screen capture process to allow the button to be painted back in its original state.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.event.*;

public class ScreenTest extends JPanel
{
    JList<String> list = new JList<>();
    DefaultListModel<String> model = new DefaultListModel<>();

    public ScreenTest()
    {
        setLayout( new BorderLayout() );

        JButton create = new JButton("Test Sub Image");
        add(create, BorderLayout.PAGE_START);

        JPanel west = new JPanel( new GridLayout(50, 5) );

        for (int i = 0; i < 250; i++)
            west.add( new JButton("just for decoration") );

        add(west, BorderLayout.LINE_START);

        JLabel center = new JLabel( new ImageIcon("grass.jpg") );
        add(center);

        add(new JScrollPane(list), BorderLayout.LINE_END);

        create.addActionListener((e) -> startTest());
    }

    private void startTest()
    {
        ActionListener al = new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    for (int i = 0; i < 1920 - 500; i += 50)
                        test( i );

                    list.setModel( model );
                }
                catch (Exception e2) { System.out.println(e2); }
            }
        };

        Timer timer = new Timer(1000, al);
        timer.setRepeats( false );
        timer.start();
    }


    private void test(int xOffset) throws AWTException
    {
        try
        {
            // Construct Robot
            Robot r = new Robot();

            // Get dimensions of screen
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

            // Take screenshot
            BufferedImage screen = r.createScreenCapture(new Rectangle(0,0, (int)screenSize.getWidth(), (int)screenSize.getHeight()));

            // Take screenshot of small section of screen
            int x = xOffset;
            int y = 50;
            int w = 500;
            int h = 500;

            BufferedImage subscreen = r.createScreenCapture(new Rectangle(x,y,w,h));

            // Create a subimage of the same section of the screen from the original screenshot
            BufferedImage subimageOfScreen = screen.getSubimage(x,y,w,h);

            // Are they equal?
            model.addElement(xOffset + " : " + imgEqual(subimageOfScreen, subscreen));
        }
        catch (Exception e) { System.out.println(e); }
    }

    private boolean imgEqual(BufferedImage image1, BufferedImage image2)
    {
        int width;
        int height;
        boolean imagesEqual = true;

        if( image1.getWidth()  == ( width  = image2.getWidth() ) &&
            image1.getHeight() == ( height = image2.getHeight() ) ){

            for(int x = 0;imagesEqual == true && x < width; x++){
                for(int y = 0;imagesEqual == true && y < height; y++){
                    if( image1.getRGB(x, y) != image2.getRGB(x, y) ){
//                      System.out.println(x + " : " + y);
                        imagesEqual = false;
                    }
                }
            }
        } else {
            imagesEqual = false;
        }
        return imagesEqual;
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new ScreenTest());
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}