Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
tools to write (block) data to png files and vice versa
#!/usr/bin/env python3
# Distributed under the MIT software license
import binascii, struct, sys, io, argparse
from PIL import Image
IMG_WIDTH = 512 # could be made adaptive...
MIN_HEIGHT = 4 # minimum height of image; twitter won't let us upload anything smaller
BYTES_PER_PIXEL = 4 # RGBA, 8 bit
def div_roundup(x,y):
return (x+y-1)//y
def block_to_png(blockdata):
'''
Embed data in PNG image.
Pass in raw block data, returns raw PNG image.
'''
metaheader = struct.pack('>II', len(blockdata), binascii.crc32(blockdata))
data = metaheader + blockdata
# determine size;
# add an extra pixel to make sure that there's always padding, twitter will
# convert to JPG in the unlikely case that the entire alpha channel is 255
pixels = div_roundup(len(data), BYTES_PER_PIXEL) + 1
width = IMG_WIDTH
height = max(div_roundup(pixels, width), MIN_HEIGHT)
# add zero-padding
padding_len = width*height*BYTES_PER_PIXEL - len(data)
data += b'\x00' * padding_len
# compress as PNG
img = Image.frombytes("RGBA", (width, height), data)
outf = io.BytesIO()
img.save(outf, "PNG", optimize=True) # optimize sets compress_level to 9(max) as well
return outf.getvalue()
def parse_arguments():
parser = argparse.ArgumentParser(description='Convert bitcoin block data to PNG image')
parser.add_argument('infilename', metavar='INFILE.hex', type=str, nargs='?',
help='Block data (hex output from RPC "getblock <hash> false"). If not specified, read from standard input.')
parser.add_argument('outfilename', metavar='OUTFILE.png', type=str, nargs='?',
help='PNG image name. If not specified, write to standard output.')
return parser.parse_args()
def main():
args = parse_arguments()
if args.infilename is not None:
with open(args.infilename, 'r') as f:
blockdata = binascii.a2b_hex(f.read().strip())
else:
blockdata = binascii.a2b_hex(sys.stdin.read().strip())
imgdata = block_to_png(blockdata)
if args.outfilename is not None:
with open(args.outfilename, 'wb') as f:
f.write(imgdata)
else:
sys.stdout.buffer.write(imgdata)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
# Distributed under the MIT software license
import binascii, struct, sys, io, argparse
from PIL import Image
def png_to_block(blockdata):
'''
Extract embedded data in PNG image.
Pass in raw PNG image, return raw block data.
'''
inf = io.BytesIO(blockdata)
img = Image.open(inf)
if img.format != 'PNG' or img.mode != 'RGBA':
raise ValueError('Image has invalid format-mode {}-{}'.format(img.format, img.mode))
imgdata = img.tobytes()
metaheader = imgdata[0:8]
(blocksize,crc) = struct.unpack('>II', metaheader)
print('size {:d}×{:d}'.format(img.width, img.height))
print('metaheader:')
print(' size : {:d}'.format(blocksize))
print(' CRC32 : {:x}'.format(crc))
if 8+blocksize > len(imgdata):
raise ValueError('Block size does not fit in image, image was cropped or header corrupted')
blockdata = imgdata[8:8+blocksize]
if binascii.crc32(blockdata) != crc:
raise ValueError('Block CRC mismatch')
return blockdata
def parse_arguments():
parser = argparse.ArgumentParser(description='Convert PNG image back to bitcoin block data')
parser.add_argument('infilename', metavar='INFILE.png', type=str, nargs='?',
help='PNG image name. If not specified, read from standard input.')
parser.add_argument('outfilename', metavar='OUTFILE.txt', type=str, nargs='?',
help='Block data (hex output like RPC "getblock <hash> false"). If not specified, write to standard output.')
return parser.parse_args()
def main():
args = parse_arguments()
if args.infilename is not None:
with open(args.infilename, 'rb') as f:
imgdata = f.read()
else:
imgdata = sys.stdin.buffer.read()
blockdata = png_to_block(imgdata)
if args.outfilename is not None:
with open(args.outfilename, 'w') as f:
f.write(binascii.b2a_hex(blockdata).decode() + '\n')
else:
sys.stdout.write(binascii.b2a_hex(blockdata).decode() + '\n')
if __name__ == '__main__':
main()
@christianboyle

This comment has been minimized.

Copy link

christianboyle commented Jul 28, 2018

Here's some additional help for anyone trying to use these scripts:

  1. Get the file: https://pbs.twimg.com/media/DjNNVO1XcAMzSAx.png:orig
  2. $ pip3 install Pillow
  3. Run the example in the tweet:
    $ python3 imgtoblock.py DjNNVO1XcAMzSAx.png output.txt
  4. This process will create a file called "output.txt" in addition to printing the size (8 byte length) and CRC32 (header) in the CLI:
    size 512×571
    metaheader:
    size : 1168239
    CRC32 : 5e8dabe1
  5. Open output.txt and notice that the HEX matches what's shown here: https://blockchain.info/block/0000000000000000000972454a8f0553eaae550dccfc0abd7c9be41e5b0f3078?format=hex

So far, so good. Now let's go the other direction and encode this block back to an image:

  1. Using the file we just created, output.txt (the HEX representation of block 534106) we run blocktoimg.py:
    $ python3 blocktoimg.py output.txt output.png
  2. This process will create a file called "output.png" in addition to printing the dimensions of the exported image:
    exporting 512×571 to output.png
  3. This image is identical to our original reference image in the tweet.
    You can verify this by running the images through a diff viewer.
    The comparison results in this output, showing no difference between the two images (no red coloring): https://i.imgur.com/4cHAldg.jpg
@laanwj

This comment has been minimized.

Copy link
Owner Author

laanwj commented Jul 29, 2018

@christianboyle Thank you for the write-up. My snippet is really under-documented, hadn't expected it to catch on so much!

Edit: improved the code a bit, so that it can be used as module and in shell pipe constructs, and arguments are documented.

@radWorx

This comment has been minimized.

Copy link

radWorx commented Jul 29, 2018

hello, Trying to runt the script but I get "SyntaxError: Non-ASCII character '\xc3' in file(imgtoblock.py) on line 19, but no encoding declared;"
What encoding should I declare in the file?

@laanwj

This comment has been minimized.

Copy link
Owner Author

laanwj commented Aug 1, 2018

@radWorx are you trying to run it with python 2? This is python 3, and for python 3 all source files are UTF-8 and there is no need to specify it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.