Search code examples
pythonpython-3.xpython-imaging-librarybitwise-operators

How to perform operations on images in python


I am trying to complete a challenge where i use an equation to construct a new image (d) from other images. Then i must get the flag in the image (d). The given images are a.png, b.png c.png and y.png and they can be found here: https://drive.google.com/drive/folders/1bZOm_0apr5ZmaRNf9R5UVIEmtMuYSphn?usp=sharing

The equation: d = y - 21a - 3b + 41c

My current code

from PIL import Image

imagey = Image.open('y.png')
imagea = Image.open('a.png')
imageb = Image.open('b.png')
imagec = Image.open('c.png')
size = width, height = imagey.size

new = Image.new('RGB', size)

imgy = imagey.load()
imga = imagea.load()
imgb = imageb.load()
imgc = imagec.load()
data = new.load()

for x in range(width):
    for y in range(height):
        they = imgy[x, y]
        thea = imga[x, y]
        theb = imgb[x, y]
        thec = imgc[x, y]

        new_color = ((int(they[0])) & ~(int((21 * thea[0])) ^ int((3 * theb[0])) ^ int(~(41 * thec[0]))),
                     (int(they[1])) & ~(int((21 * thea[1])) ^ int((3 * theb[1])) ^ int(~(41 * thec[1]))),
                     (int(they[2])) & ~(int((21 * thea[2])) ^ int((3 * theb[2])) ^ int(~(41 * thec[2]))))
        data[x, y] = new_color

new.save('final.png')
new.show()

Solution

  • If you would convert Pillow image to numpy array or you would use OpenCV or imageio to load image (and get directly numpy array) then you could do directly

    new = imagey - 21*imagea - 3*imageb + 41*imagec
    

    Result:

    Not ideal but much better than with your code.

    It can be problem with overflow. It may create array with 8bits values and calculations can gives 16bits or 32bits values which can be reduced to 8bits in every calculation.

    enter image description here

    Full working code:

    import imageio
    
    imagey = imageio.imread('y.png')
    imagea = imageio.imread('a.png')
    imageb = imageio.imread('b.png')
    imagec = imageio.imread('c.png')
    
    new = imagey - 21*imagea - 3*imageb + 41*imagec
    
    imageio.imwrite('final.png', new)
    
    # --- imageio doesn't have function to display it ---
    
    import matplotlib.pyplot as plt
    
    plt.imshow(new)
    plt.show()
    

    EDIT:

    If I use OpenCV then I get ideal result

    enter image description here

    Full working code:

    import cv2
    
    imagey = cv2.imread('y.png')
    imagea = cv2.imread('a.png')
    imageb = cv2.imread('b.png')
    imagec = cv2.imread('c.png')
    
    new = imagey - 21*imagea - 3*imageb + 41*imagec
    
    cv2.imwrite('final.png', new)
    
    # --- show window with image and wait for press any key ---
    
    cv2.imshow('Image', new)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    EDIT:

    By the way: version which converts PIL Image to numpy array and later it converts back to PIL Image - but it gives the same result as imageio.

    from PIL import Image
    import numpy as np
    
    imagey = Image.open('y.png')
    imagea = Image.open('a.png')
    imageb = Image.open('b.png')
    imagec = Image.open('c.png')
    
    arr_y = np.array(imagey)
    arr_a = np.array(imagea)
    arr_b = np.array(imageb)
    arr_c = np.array(imagec)
    
    arr_new = arr_y - 21*arr_a - 3*arr_b + 41*arr_c
    
    new = Image.fromarray(arr_new)
    
    new.save('final.png')
    new.show()
    

    BTW:

    If I check images on Linux using program file then it shows that b.png and c.png are JPEG, not PNG.

    $ file b.png
    
    b.png: JPEG image data, JFIF standard 1.01, resolution (DPI), 
    density 300x300, segment length 16, 
    Exif Standard: [TIFF image data, big-endian, direntries=0], baseline, 
    precision 8, 960x640, components 3
    

    I found that cv2.imread() gives little different values for c.png(which is JPG file) then other modules - and I don't mean that cv2 gives colors in BGR instead of RGB - and later this gives correct result. Probably cv2 uses different C library to read JPG.