Skip to content

Instantly share code, notes, and snippets.

@mokhdzanifaeq
Last active July 4, 2023 03:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mokhdzanifaeq/540a07334764f6ca0d94b0e60592e378 to your computer and use it in GitHub Desktop.
Save mokhdzanifaeq/540a07334764f6ca0d94b0e60592e378 to your computer and use it in GitHub Desktop.
extract data embeded in pixel channels
from PIL import Image
import argparse
from collections import OrderedDict
def parseMask(string):
mask = []
for val in string.split(","):
if "-" in val:
min, max = val.split("-")
mask += [1 << (i - 1) for i in range(int(min), int(max) + 1)]
else:
mask += [1 << (int(val) - 1)]
mask.sort()
return mask
parser = argparse.ArgumentParser(prog='extract.py',
usage='%(prog)s [options] image',
formatter_class=argparse.RawTextHelpFormatter,
epilog="PLANE = comma separated values or a range. example: 1,2,5 or 1-5\n" +
"ORDER = any combination of r/g/b/a. example: rgba, abrg, brg")
parser.add_argument("image", help="path to image")
parser.add_argument("-r", dest="r", help="bit plane of red channel", metavar="PLANE", default=False, type=parseMask)
parser.add_argument("-g", dest="g", help="bit plane of green channel", metavar="PLANE", default=False, type=parseMask)
parser.add_argument("-b", dest="b", help="bit plane of blue channel", metavar="PLANE", default=False, type=parseMask)
parser.add_argument("-a", dest="a", help="bit plane of alpha channel", metavar="PLANE", default=False, type=parseMask)
parser.add_argument("-c", dest="channel", help="order of channel (default: rgba)", metavar="ORDER", default=False)
parser.add_argument("-l", dest="limit", help="limit output to N character", metavar="N", default=0, type=int)
parser.add_argument("-o", dest="order", help="pixel iteration order (default: row)", default="row", choices=["row", "column"], type=str.lower)
parser.add_argument("-B", dest="bit", help="bit order to process (default: lsb)", default="lsb", choices=["lsb", "msb"], type=str.lower)
args = vars( parser.parse_args() )
img = Image.open(args["image"])
if args["order"] == "row": column, row = img.size
else: row, column = img.size
stream = ""
done = False
pixel = OrderedDict()
if args["channel"] == False:
args["channel"] = "rgba" if img.mode == "RGBA" else "rgb"
for channel in list("rgba"):
pixel[channel] = 0xff
# if bit == msb, reverse the mask order
if args["bit"] == "msb" and args[channel] != False:
args[channel] = args[channel][::-1]
for y in range(row):
if done: break
for x in range(column):
if done: break
order = (x, y) if args["order"] == "row" else (y, x)
for index, value in enumerate( img.getpixel(order) ):
pixel[pixel.keys()[index]] = value
# loop based on rgba order
for channel in list(args["channel"]):
if done: break
# if bit plane(s) is defined
if args[channel] != False:
for mask in args[channel]:
if done: break
val = pixel[channel] & mask
stream += "1" if val == mask else "0"
# if limit is reached, stop processing
if len(stream) / 8.0 == int(args["limit"]):
done = True
# convert binary stream to ascii
print ''.join( chr( int(stream[i:i+8], 2) ) for i in range(0, len(stream), 8) )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment