Skip to content

Instantly share code, notes, and snippets.

@Sasszem
Created December 10, 2021 16:21
Show Gist options
  • Save Sasszem/d3b6973fb4a1d30ae19a0cbcdcf5df97 to your computer and use it in GitHub Desktop.
Save Sasszem/d3b6973fb4a1d30ae19a0cbcdcf5df97 to your computer and use it in GitHub Desktop.
Create "fake-preview" images (similar to this one: https://www.reddit.com/r/rickroll/comments/ls99p2/wheres_waldo/). Requires the Pillow package!
import struct
import zlib
from PIL import Image
import click
import io
GAMMA = 0.08
DESAT_RATE = 0.4
################################################################################
# FIRST PHASE - GENERATING THE MIXED IMAGE (AND OVERBRIGTENING THE HIDDEN ONE) #
################################################################################
def desaturate(col, rate):
r, g, b = col[0]/255, col[1]/255, col[2]/255
r = 0.5 + (r-0.5)*rate
g = 0.5 + (g-0.5)*rate
b = 0.5 + (b-0.5)*rate
r = int(255*r)
g = int(255*g)
b = int(255*b)
return (r, g, b, 255)
def gamma_adjust(col, gamma):
"""Brightening of the image"""
r, g, b = col[0], col[1], col[2]
invgamma = 1/gamma
r = int(255*(r/255)**invgamma)
g = int(255*(g/255)**invgamma)
b = int(255*(b/255)**invgamma)
return (r, g, b, 255)
def make_combined_image(foreground, background, gamma, desat_rate):
with Image.open("inp_1_res.png") as inp1, Image.open("inp_2_res.png") as inp2:
n = Image.new("RGBA", inp1.size, (0, 0, 0, 255))
w, h = n.size
for x in range(w):
for y in range(h):
if x % 2 == 0 and y % 2 == 0:
n.putpixel((x, y), gamma_adjust(
desaturate(inp1.getpixel((x, y)), 1), 0.5/gamma))
else:
if x < inp2.size[0] and y < inp2.size[1]:
n.putpixel((x, y), gamma_adjust(desaturate(
inp2.getpixel((x, y)), desat_rate), 1))
outfile = io.BytesIO()
n.save(outfile, "png")
outfile.seek(0)
return outfile
############################################################
# SECOND PHASE - ADDING EXTRA IMAGE INFORMATION TO THE PNG #
############################################################
def read_chunk(f):
"""Read a single PNG chunk from binary file"""
length = struct.unpack(">I", f.read(4))[0]
type = f.read(4).decode()
print(f"{length=}, {type=}")
data = f.read(length)
assert f.read(4) == zlib.crc32(
type.encode()+data).to_bytes(4, "big"), "CRC is not valid"
return type, data
def get_png_chunks(file):
chunks = []
assert list(file.read(8)) == [137, 80, 78, 71,
13, 10, 26, 10], "PNG signature is not valid"
chunktype = ""
while chunktype != "IEND":
chunktype, data = read_chunk(file)
chunks.append((chunktype, data))
return chunks
def write_png(chunks, file):
file.write(bytes([137, 80, 78, 71, 13, 10, 26, 10]))
for type, data in chunks:
file.write(struct.pack(">I", len(data))+type.encode()+data +
struct.pack(">I", zlib.crc32(type.encode()+data)))
@click.command()
@click.argument('hidden', type=click.Path(exists=True))
@click.argument('fake', type=click.Path(exists=True))
@click.argument('output', type=click.Path(exists=False))
@click.option('-g', '--gamma', type=float, default=GAMMA)
@click.option('-d', '--desat', "desat_rate", type=float, default=DESAT_RATE)
def main(hidden, fake, output, gamma, desat_rate):
combined = make_combined_image(hidden, fake, gamma, desat_rate)
chunks = get_png_chunks(combined)
gamma_chunk = ("gAMA", int(100000*gamma).to_bytes(4, "big"))
chunks.insert(1, gamma_chunk)
with open(output, "wb") as f:
write_png(chunks, f)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment