Search code examples
pythonpython-imaging-library

Image from PIL module not allowing reading and writing


The goal of this code is to encode a 64 by 64 image in binary and store it in a txt file, and then decode and load it with another script

from PIL import Image

img = Image.open('image.png')
map = img.load()
width,height = img.size
pixellist = []
index = 0

def binaryToDecimal(n):
    return int(n,2)

with open('encimg.txt','r') as f:
    content = f.read()
repeatnum = len(content)/24
for i in range(int(repeatnum)):
    red = content[0:8:1]
    content = content[8:]
    green = content[0:8:1]
    content = content[8:]
    blue = content[0:8:1]
    content = content[8:]
    pixel = [binaryToDecimal(red),binaryToDecimal(green),binaryToDecimal(blue)]
    pixellist.append(pixel)
pixellist = [i for i in pixellist if i != ['','','',]]
for column in range(width):
    for row in range(height):
        map[column,row] =tuple(pixellist[column*row])
        index += 1

img.save('newimage',format='png')
from PIL import Image

input_image = Image.open("images.png")
pixel_map = input_image.load()
width, height = input_image.size
binarylist = []
index = 0

def decimalToBinary(n):
    return bin(n).replace("0b", "")

for i in range(width):
    for j in range(height):
        r, g, b = input_image.getpixel((i, j))
        binarylist.extend([decimalToBinary(r),decimalToBinary(g),decimalToBinary(b)])

with open('encimg.txt','w') as f:
    for item in binarylist:
        f.write(item)
        index += 1
        print(index, end = '\r')

I tried to try the same code but with a 16x16 image instead, which worked, but wouldn't work when I scaled it back up to 64x64.


Solution

  • Your "reader" code assumes 8 binary digits per colour, but your writer/encoder doesn't produce 8 binary digits per colour.

    You use:

    return bin(n).replace("0b","")
    

    which will produce 0 when X=0, rather than the 00000000 you were probably expecting.

    Try the following instead

    return f'{n:08b}'
    

    Just FYI, if you haven't seen such things before they are call "f-strings" and are described here.

    Also, your loops need interchanging in the encoder, so they read:

    for i in range(width):
        for j in range(height):
    

    Your whole concept is grossly inefficient, but your decoder is especially bad and will take minutes for large images because you are constantly shuffling the entire variable content to the left by 8 characters with:

    content = content[8:]
    

    You would be better to use indexing into the variable, rather than recreating it for every pixel, i.e.:

    offset = 0
    for i in range(int(repeatnum)):
        red   = content[offset:offset+8:1]
        green = content[offset+8:offset+16:1]
        blue  = content[offset+16:offset+24:1]
        offset += 24
        pixel = (binaryToDecimal(red),binaryToDecimal(green),binaryToDecimal(blue))
    

    Also, you need to make a tuple to put in map.

    Also, your population of map miscalculates the offset into your pixel list, it needs to be:

    for column in range(width):
    for row in range(height):
        this = pixellist[column+(width*row)]
        map[column,row] = this
    

    To be sure of being able to save palette images and images with transparency in your RGB format, you should change this line:

    input_image = Image.open("images.png")
    

    to this:

    input_image = Image.open("images.png").convert('RGB')
    

    The whole code (as hacked to work, rather than being what I would recommend is as follows for the encoder:

    #!/usr/bin/env python3
    
    from PIL import Image
    
    input_image = Image.open("a.png").convert('RGB')
    pixel_map = input_image.load()
    width, height = input_image.size
    binarylist = []
    index = 0
    
    def decimalToBinary(n):
        return f'{n:08b}'
    
    for j in range(height):
        for i in range(width):
            r, g, b = input_image.getpixel((i, j))
            print(f'{r=} {g=} {b=}')
            binarylist.extend([decimalToBinary(r),decimalToBinary(g),decimalToBinary(b)])
    
    with open('encimg.txt','w') as f:
        for item in binarylist:
            f.write(item)
            print(f'{item=}')
            
    

    and as follows for the decoder:

    #!/usr/bin/env python3
    
    from PIL import Image
    
    img = Image.open('a.png').convert('RGB')
    map = img.load()
    width,height = img.size
    print(f'DEBUG: {width=} {height=}')
    pixellist = []
    index = 0
    
    def binaryToDecimal(n):
        return int(n,2)
    
    with open('encimg.txt','r') as f:
        content = f.read()
    repeatnum = len(content)/24
    
    offset = 0
    for i in range(int(repeatnum)):
        red   = content[offset:offset+8:1]
        green = content[offset+8:offset+16:1]
        blue  = content[offset+16:offset+24:1]
        offset += 24
        pixel = (binaryToDecimal(red),binaryToDecimal(green),binaryToDecimal(blue))
        pixellist.append(pixel)
    
    #pixellist = [i for i in pixellist if i != ['','','',]]
    for column in range(width):
        for row in range(height):
            this = pixellist[column+(width*row)]
            map[column,row] = this
            print(f'{column=},{row=} {this=}')
    
    img.save('result.png')
    

    If I had to code this using lists and loops and stuff, I would probably go with something like this for the encoder:

    #!/usr/bin/env python3
    
    from PIL import Image
    
    # Open image and ensure in RGB mode - not palette, not alpha
    input_image = Image.open("a.png").convert('RGB')
    
    with open('encimg.txt','w') as f:
        # Get pixels via PIL generator
        for pixel in input_image.getdata():
            str = f'{pixel[0]:08b}{pixel[1]:08b}{pixel[2]:08b}'
            f.write(str)
    

    and something like this for the decoder:

    #!/usr/bin/env python3
    
    from PIL import Image
    
    width, height = 640, 480
    print(f'DEBUG: {width=} {height=}')
    
    pixels = []
    with open('encimg.txt','r') as f:
        while this := f.read(24):
           R = this[0:8:1]
           G = this[8:16:1]
           B = this[16:24:1]
           pixels.append((int(R,2), int(G,2), int(B,2)))
    
    # Create new, empty output image
    result = Image.new('RGB', (width,height))
    # Stuff decoded data into it
    result.putdata(pixels)
    result.save('result.png')