Skip to content

Instantly share code, notes, and snippets.

@MarkGamed7794
Created April 17, 2024 00:36
Show Gist options
  • Save MarkGamed7794/0aa8d70eee6bd59f75c289c794f33f14 to your computer and use it in GitHub Desktop.
Save MarkGamed7794/0aa8d70eee6bd59f75c289c794f33f14 to your computer and use it in GitHub Desktop.
California DMV Bad Apple generator
"""
Requires Pillow to be installed.
Place the licence plate font in the same directory as this file
with the name "licenceplate.ttf", then run it. It should create
a file named "font_atlas.png", which is used when generating
the frames.
"""
from PIL import Image, ImageFont, ImageDraw # type: ignore
import math
CHAR_SIZE = 360 // 10
CHAR_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "
CHAR_AMOUNT = len(CHAR_SET)
GRID_SIZE = math.ceil(math.sqrt(CHAR_AMOUNT))
with Image.new("RGBA", (CHAR_SIZE * GRID_SIZE, CHAR_SIZE * GRID_SIZE), (0, 0, 0, 255)) as img:
artist = ImageDraw.Draw(img)
font = ImageFont.truetype("licenseplate.ttf", size=CHAR_SIZE)
for i, char in enumerate(CHAR_SET):
x = (i % GRID_SIZE) * CHAR_SIZE
y = (i // GRID_SIZE) * CHAR_SIZE
artist.text((x, y), char, font=font, fill=(255, 255, 255, 255))
img.save("font_atlas.png")
img.show()
"""
Requires Pillow to be installed.
Run this in a directory set up like so:
* source\
- apple-0001.jpeg
- apple-0002.jpeg
- apple-0003.jpeg
... (etc. up to however many frames you have. i exported it at 20FPS and had a total of 4,383 frames)
- font_atlas.png (generated by create_atlas.py)
- template.png (the base license place image)
- licenceplate.ttf (the licence plate font)
- generate_frames.py (this file)
Each frame in the source video should be 480x320. The template should be 1280x720.
This will create a directory named "frames\" with each frame in it named "0001.jpeg", "0002.jpeg", etc.
Adjust the number of frames by changing FRAME_COUNT near the bottom.
Sorry for the code quality. It's not a very efficient program (generating the video took my laptop about two hours).
"""
from PIL import Image, ImageFont, ImageDraw # type: ignore
import math
import time
CHAR_SIZE = 360 // 10
CHAR_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "
CHAR_AMOUNT = len(CHAR_SET)
GRID_SIZE = math.ceil(math.sqrt(CHAR_AMOUNT))
CHAR_DIMENSIONS = (16, 36)
PARAGRAPH_DIMENSIONS = (30, 10)
FONT_SIZE = 32
DRAW_DIMENSIONS = (16, 36)
DRAW_OFFSET = (((1280 - DRAW_DIMENSIONS[0] * 30) // 2) + 10, 264)
font_atlas = Image.open("font_atlas.png").convert("L")
template = Image.open("template_modified.png")
def evaluate_difference(imga: Image, imgb: Image):
# grounds for improvement
a, b = list(imga.getdata()), list(imgb.getdata())
difference_sum = 0
#print(imga.mode, imgb.mode)
for n in range(len(a)):
difference_sum += abs(a[n] - b[n])
#print(f"Difference: {difference_sum}")
return difference_sum
def best_match(crop: Image):
best_char = " "
best_value = 9999999999999999
for i, char in enumerate(CHAR_SET):
x = (i % GRID_SIZE) * CHAR_SIZE
y = (i // GRID_SIZE) * CHAR_SIZE
with font_atlas.crop((x, y, x + CHAR_DIMENSIONS[0], y + CHAR_DIMENSIONS[1])) as letter:
value = evaluate_difference(crop, letter)
if(value < best_value):
best_char = char
best_value = value
#print(f"Best Fit: {char} (score {best_value})")
#raise RuntimeError
return best_char
font = ImageFont.truetype("licenseplate.ttf", size=FONT_SIZE)
start_time = time.time()
FRAME_NUM = 1
FRAME_COUNT = 4383
while FRAME_NUM <= FRAME_COUNT:
if(FRAME_NUM > 1):
fps = 1 / ((time.time() - start_time) / (FRAME_NUM - 1))
timeleft = math.floor((FRAME_COUNT - FRAME_NUM) / fps)
print("Frame %04d/%04d (%.4f FPS, ETA %02d:%02d:%02d)" % (FRAME_NUM, FRAME_COUNT, fps, (timeleft // 3600), (timeleft // 60) % 60, timeleft % 60))
with Image.open("source/apple-%04d.jpeg" % FRAME_NUM).convert("L") as frame:
#frame.show()
out_frame = template.copy()
artist = ImageDraw.Draw(out_frame)
for y in range(PARAGRAPH_DIMENSIONS[1]):
img_y = y * CHAR_DIMENSIONS[1]
draw_y = y * DRAW_DIMENSIONS[1] + DRAW_OFFSET[1]
for x in range(PARAGRAPH_DIMENSIONS[0]):
img_x = x * CHAR_DIMENSIONS[0]
with frame.crop((img_x, img_y, img_x + CHAR_DIMENSIONS[0], img_y + CHAR_DIMENSIONS[1])) as crop:
best_char = best_match(crop)
print(best_match(crop), end="")
draw_x = x * DRAW_DIMENSIONS[0] + DRAW_OFFSET[0]
artist.text((draw_x, draw_y), best_char, (31, 41, 100), font=font)
print("")
#out_frame.show()
out_frame.save("frames/%04d.jpeg" % FRAME_NUM)
out_frame.close()
FRAME_NUM += 1
font_atlas.close()
template.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment