Last active
January 31, 2024 11:06
-
-
Save laanwj/51f276c44ba9882bb4b27cc6f3a499a4 to your computer and use it in GitHub Desktop.
tools to write (block) data to png files and vice versa
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 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.
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?
@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
Here's some additional help for anyone trying to use these scripts:
$ pip3 install Pillow
$ python3 imgtoblock.py DjNNVO1XcAMzSAx.png output.txt
size 512×571
metaheader:
size : 1168239
CRC32 : 5e8dabe1
So far, so good. Now let's go the other direction and encode this block back to an image:
$ python3 blocktoimg.py output.txt output.png
exporting 512×571 to output.png
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