Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if System Clipboard Images are equal

I am not sure if my problem is platform specific, but I think it is not.
Because my expirience is based on the Windows specific java.awt.Toolkit and the Windows-Clipboard.

The following example class shows the problem i am faced with.
NOTE: Before running the program, make sure you have no Image in you system clipboard.

If there is no Image in the system clipboard the Program put a new screenshot to it.

Then I get the Clipboard data two times!

All 3 Images are equal! - the original Screenshot and every Image I get from the clipboard.
which is ok.

But now running the program a second time. NOTE: There is the old screenshot in the clipboard!

The Program generates a new screenshot and get the old one from the clipboard two times.

No Images is equal to any! - The first (new screenshot) should be not equal, it is okay

But every next Image I get is not equal.

Q1: If every next Image I get is not equal, why it was equal at the first time?
Q2: The bigger Question: How can I compare a java.awt.Image to get every next Image equal.

I am looking for an easy and fast comparison of two Images or an easy way to figure out that the clipboard doesn't has changed.

public class Example {

    public static void main( String[] args ) throws Exception {

        final Toolkit   toolkit   = Toolkit.getDefaultToolkit();
        final Clipboard clipboard = toolkit.getSystemClipboard();
        final Image     origImage = new Robot().createScreenCapture( new Rectangle( toolkit.getScreenSize() ) );

        if( !clipboard.isDataFlavorAvailable( DataFlavor.imageFlavor )
            || clipboard.getData( DataFlavor.imageFlavor ) == null ) {
            clipboard.setContents( new ImageSelection( origImage ), null );
        }

        Image clipImage1 = (Image)clipboard.getData( DataFlavor.imageFlavor );
        Image clipImage2 = (Image)clipboard.getData( DataFlavor.imageFlavor );

        System.out.println(origImage.hashCode());
        System.out.println(clipImage1.hashCode());
        System.out.println(clipImage2.hashCode());
        System.out.println(clipImage1.equals( clipImage2 ));


    }

    public static class ImageSelection implements Transferable {
        private Image image;
        public ImageSelection(Image image) {
            this.image = image;
        }
        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{DataFlavor.imageFlavor};
        }
        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return DataFlavor.imageFlavor.equals(flavor);
        }
        @Override
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if (!DataFlavor.imageFlavor.equals(flavor)) {
                throw new UnsupportedFlavorException(flavor);
            }
            return image;
        }
    }    
}
like image 628
oliholz Avatar asked Aug 26 '11 12:08

oliholz


4 Answers

Q1: The object references were the same in the first case.

Q2: here is a way to check the data is equal in the screen shots:

  //createScreenCapture() returns BufferedImage which is more useful for what you are doing.

  static boolean bufferedImageEquals( BufferedImage b1, BufferedImage b2 ) {
    if ( b1 == b2 ) {return true;} // true if both are null
    if ( b1 == null || b2 == null ) { return false; }
    if ( b1.getWidth() != b2.getWidth() ) { return false; }
    if ( b1.getHeight() != b2.getHeight() ) { return false; }
    for ( int i = 0; i < b1.getWidth(); i++) {
     for ( int j = 0; j < b1.getHeight(); j++ ) {
       if ( b1.getRGB(i,j) != b2.getRGB(i,j) ) { 
           return false;
       }
      }
    }
    return true;
  }
like image 145
Clint Avatar answered Nov 18 '22 12:11

Clint


In addition to Clint his answer, you have to convert the clipboard Image to a BufferedImage, if you want to compare them.

public static BufferedImage convert(Image img)
{
    BufferedImage i = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR_PRE);
    Graphics2D g = i.createGraphics();
    g.drawImage(img, 0, 0, null);
    g.dispose();
    return i;
}

Use this method to convert the two Images to BufferedImages and compare them the way Clint posted.

like image 22
Martijn Courteaux Avatar answered Nov 18 '22 12:11

Martijn Courteaux


If you want to compare screens, there are couple of ways,

  1. To convert it to byte array and compare arrays
  2. To do a MD5 hash calculation

And you can even save that arrays in pngs/jpg to check what is wrong with the logic.

        BufferedImage clipImage1 = (BufferedImage) clipboard
            .getData(DataFlavor.imageFlavor);
    RenderedImage renderclipImage1 = createImage(clipImage1);
    File clipImage1png = new File("clipImage1.png");
    ImageIO.write(renderclipImage1, "png", clipImage1png);
    byte[] clipeImage1Bytes = bufImageToBytesConverter(clipImage1);
    MessageDigest mdInst1 = MessageDigest.getInstance("MD5");
    mdInst1.update(clipeImage1Bytes);
    byte[] md5hashClipImage1 = mdInst1.digest();
    System.out.println(returnHex(md5hashClipImage1));

    BufferedImage clipImage2 = (BufferedImage) clipboard
            .getData(DataFlavor.imageFlavor);
    RenderedImage renderclipImage2 = createImage(clipImage2);
    File clipImage2png = new File("clipImage2.png");
    ImageIO.write(renderclipImage2, "png", clipImage2png);
    byte[] clipImage2Bytes = bufImageToBytesConverter(clipImage2);
    MessageDigest msInst2 = MessageDigest.getInstance("MD5");
    msInst2.update(clipImage2Bytes);
    byte[] md5hashClipImage2 = msInst2.digest();
    System.out.println(returnHex(md5hashClipImage2));

The output comes as

nulle5c49978317c0151969cf63f212f7662
nulle5c49978317c0151969cf63f212f7662

If you would have clarified the context more , you might have got more replies. For example, Is it for remote desktop sharing etc etc ..

like image 2
Manish Singh Avatar answered Nov 18 '22 10:11

Manish Singh


Well, I took a peek at the source code behind the JDK we use in our IDE (which happens to be an IBM JDK). java.awt.Image looks like it is an abstract class and equals is not defined in it (please check your JDK to be sure). Since this is the case, either a subclass must implement equals, or we fall back on java.lang.Object.equals(java.lang.Object), which according to our JDK implements the equals method as return this == arg.

Again, please validate this with your JDK. But here is what I would surmise would happen should your JDK and ours "match" on implementation. If the Object equals is used, then I'm guessing that the first time through your program, the JVM keeps track of the objects on the Clipboard and can tell that they are the same image. But when the JVM terminates and then restarts, it can no longer know if those were the same object or not. Therefore it assigns them different memory spaces (ie, references) as different objects and thus they no longer equal anymore.

Of course not knowing what subclasses are used or if your JDK/JVM is implemented differently, I can't say that with full certainty. But it seems highly likely, especially since the Clipboard is technically outside of the JVM and is accessed through the JVM. How can the JVM tell what is and is not equal if there is something already there? Unless somebody implements a way to tell that relies on knowledge of the OS, I'm guessing it can't.

So that means you are best off implementing your own solution, of which Clint seems to have a good crack at it.

like image 2
Chris Aldrich Avatar answered Nov 18 '22 12:11

Chris Aldrich