imageimage-processingimagemagickxor

Searching for a way to do bitwise XOR on images


I am looking for a way to get the bitwise XOR of two images on the command line (or in another way that can be implemented in a program or script).

This should result in the same final picture as using the XOR blending mode in picture editors that support it (Paint.NET, Photoshop, etc)

As an example, say I have Image A:

Image A (Seattle, from the Paint.NET documentation)

and Image B:

ImageB (an apple, from the paint.net documentation)

then the result should look like this:

Image C (XOR result of above images, from the Paint.NET documentation)

The fun part of this is of course, that when you XOR image C with image B again, you will get an exact copy of image A.

Now, I have been looking all over the internet for a way to do this programmatically, but I have found nothing. Even ImageMagick does not support doing a bitwise XOR on images.

Does somebody know a way to do this?


Solution

  • ImageMagick can do it, although it's a bit convoluted. One way is:

    convert img1 img2 -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" img_out
    

    (img1,img2,img_out are the two input and single output file names respectively).

    Explanation

    It's a bit ugly (I'm sure someone with more ImageMagick-fu than me could clean it up but it works like this:

    1. -fx "xxx" basically says "perform the operation xxx on the image". In the expression above, u and v stand for the first and second input images respectively.

    2. Now, -fx only has bitwise AND & and bitwise OR | in the way of bitwise operators. To reconstruct bitwise XOR, we need

      convert img1 img2 -fx "(u & NOT v) | (NOT u & v)" img_out
      
    3. To get the NOT (there is a logical NOT but no bitwise NOT), we remember that NOT x = 255-x if x is 8-bit. So to get NOT u we can just do 255-u, assuming image u is 8-bit. Hence, the ImageMagick command would be:

      convert img1.png img2.img -fx "((255-u)&v)|(u&(255-v))" image_xor.png
      
      • The one problem here is that when ImageMagick does fx it normalises all the pixels in u and v in the range [0,1] instead of [0,255] as we expect, and doing bitwise on non-integers screws stuff up.

      • Hence, we have to multiply all occurrences of u and v in the above expression by 255 (so the bitwise operations work), and divide by 255 at the very end to get back in the range [0,1] that ImageMagick expects.

    This gives us the original command,

    convert img1 img2 -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" img_out
    

    Voila!