automeme/meme_generator.py

619 lines
26 KiB
Python
Raw Normal View History

2022-08-15 00:57:07 +00:00
import math
2022-09-04 19:52:55 +00:00
from typing import Callable, Tuple, Union
2022-08-15 00:57:07 +00:00
from PIL import Image
2022-08-21 21:46:07 +00:00
from PIL import ImageDraw, ImageSequence
2022-08-15 00:57:07 +00:00
from PIL import ImageFont
from PIL import ImageOps
import requests
import io
2022-08-18 02:47:04 +00:00
from utils import get_real_filename
2022-08-15 00:57:07 +00:00
from image_utils import ImageText
from os.path import exists
2022-08-18 02:47:04 +00:00
from random import choice
2022-10-20 00:42:57 +00:00
from pympler import tracker
2022-08-15 00:57:07 +00:00
2022-10-20 00:42:57 +00:00
HIGHLIGHT_MODE = False #Adds a square around the region where text is being put in a text box. Useful for debugging.
CAPTION_FILENAME = "watermark_captions.txt" #Name of the file containing watermark captions.
2022-08-18 02:47:04 +00:00
2022-08-15 00:57:07 +00:00
class ColorScheme:
BLACK = 0
WHITE_WITH_BLACK_BORDER = 1
def create_soy_vs_chad_meme(emoji1, emoji2, caption1, caption2):
LEFT_MARGIN_COLUMN = 20
CONTENT_COLUMN = 300
MIDDLE_MARGIN_COLUMN = 80
RIGHT_MARGIN_COLUMN = LEFT_MARGIN_COLUMN
TOP_MARGIN_ROW = 80
IMAGE_ROW = 300
MIDDLE_MARGIN_ROW = 20
TEXT_ROW = 100
2022-08-18 02:47:04 +00:00
BOTTOM_MARGIN_ROW = 80
2022-08-15 00:57:07 +00:00
total_image_size_x = 2*CONTENT_COLUMN + LEFT_MARGIN_COLUMN + RIGHT_MARGIN_COLUMN + MIDDLE_MARGIN_COLUMN
total_image_size_y = TOP_MARGIN_ROW + IMAGE_ROW + MIDDLE_MARGIN_ROW + TEXT_ROW + BOTTOM_MARGIN_ROW
left_image = get_emoji_from_rdrama(emoji1)
right_image = get_emoji_from_rdrama(emoji2)
#Resize images
2022-08-21 21:46:07 +00:00
left_image = left_image.fit((CONTENT_COLUMN, IMAGE_ROW))
right_image = right_image.fit((CONTENT_COLUMN, IMAGE_ROW))
2022-08-15 00:57:07 +00:00
2022-08-21 21:46:07 +00:00
right_image = right_image.flip()
2022-08-15 00:57:07 +00:00
#Base image
2022-08-21 21:46:07 +00:00
base = AnimatedImage.new((total_image_size_x,total_image_size_y))
2022-08-15 00:57:07 +00:00
#Add images
2022-08-21 21:46:07 +00:00
base = center_and_paste(base, left_image, (LEFT_MARGIN_COLUMN,TOP_MARGIN_ROW), (CONTENT_COLUMN, IMAGE_ROW))
base = center_and_paste(base, right_image, (LEFT_MARGIN_COLUMN+CONTENT_COLUMN+MIDDLE_MARGIN_COLUMN,TOP_MARGIN_ROW), (CONTENT_COLUMN, IMAGE_ROW))
2022-08-15 00:57:07 +00:00
#Text regions
2022-08-21 21:46:07 +00:00
base = add_text_box(base,
2022-08-15 00:57:07 +00:00
caption1,
(CONTENT_COLUMN, TEXT_ROW),
(LEFT_MARGIN_COLUMN, TOP_MARGIN_ROW+IMAGE_ROW+MIDDLE_MARGIN_ROW),
init_font_size=50,
align="cht",
font="impact.ttf",
color=ColorScheme.WHITE_WITH_BLACK_BORDER)
2022-08-21 21:46:07 +00:00
base = add_text_box(base,
2022-08-15 00:57:07 +00:00
caption2,
(CONTENT_COLUMN, TEXT_ROW),
(LEFT_MARGIN_COLUMN+CONTENT_COLUMN+MIDDLE_MARGIN_COLUMN, TOP_MARGIN_ROW+IMAGE_ROW+MIDDLE_MARGIN_ROW),
init_font_size=50,
align="cht",
font="impact.ttf",
color=ColorScheme.WHITE_WITH_BLACK_BORDER)
2022-08-18 02:47:04 +00:00
return base
2022-08-15 00:57:07 +00:00
2022-08-21 21:46:07 +00:00
def create_classic_meme(image: 'AnimatedImage', top_caption : str, bottom_caption : str) -> 'AnimatedImage':
2022-08-15 00:57:07 +00:00
image_x_size, image_y_size = image.size
UNIT = 5
caption_size = int(image_y_size/UNIT)
2022-08-21 23:27:33 +00:00
image = add_text_box(image, top_caption, (image_x_size, caption_size), (0,0), "impact.ttf", caption_size, align="cvch", color=ColorScheme.WHITE_WITH_BLACK_BORDER)
image = add_text_box(image, bottom_caption, (image_x_size, caption_size), (0,int((UNIT-1)*(image_y_size/UNIT))), "impact.ttf", caption_size, align="cvch", color=ColorScheme.WHITE_WITH_BLACK_BORDER)
2022-08-15 00:57:07 +00:00
return image
2022-08-21 21:46:07 +00:00
def create_classic_meme_from_url(url : str, top_caption : str, bottom_caption : str) -> 'AnimatedImage':
2022-09-04 19:52:55 +00:00
return create_classic_meme(get_image_file_from_url(url).stretch_maintain_aspect_ratio(500), top_caption, bottom_caption)
2022-08-15 00:57:07 +00:00
def create_classic_meme_from_emoji(emoji : str, top_caption : str, bottom_caption : str):
2022-08-21 23:27:33 +00:00
EMOJI_SIZE = 600
2022-08-15 00:57:07 +00:00
BORDER_SIZE = 100
marsey = get_emoji_from_rdrama(emoji)
2022-08-21 21:46:07 +00:00
marsey = marsey.fit((EMOJI_SIZE, EMOJI_SIZE))
2022-08-15 00:57:07 +00:00
2022-08-21 21:46:07 +00:00
base = AnimatedImage.new(size=(EMOJI_SIZE+2*BORDER_SIZE, EMOJI_SIZE+2*BORDER_SIZE))
base = base.paste(marsey, (BORDER_SIZE, BORDER_SIZE))
2022-08-15 00:57:07 +00:00
return create_classic_meme(base, top_caption, bottom_caption)
2022-08-21 21:46:07 +00:00
def create_modern_meme(image : 'AnimatedImage', caption : str) -> 'AnimatedImage':
2022-08-15 00:57:07 +00:00
CAPTION_SIZE = 150
image_x_size, image_y_size = image.size
2022-08-21 21:46:07 +00:00
base = AnimatedImage.new(size=(image_x_size,image_y_size+CAPTION_SIZE))
base = base.paste(image, (0, CAPTION_SIZE))
base = add_text_box(base, caption, (image_x_size, CAPTION_SIZE), (0,0), font="intreb.ttf",align="chcv")
2022-08-15 00:57:07 +00:00
return base
def create_modern_meme_from_url(url : str, caption : str):
2022-09-04 19:52:55 +00:00
return create_modern_meme(get_image_file_from_url(url).stretch_maintain_aspect_ratio(500), caption)
2022-08-15 00:57:07 +00:00
def create_modern_meme_from_emoji(emoji: str, caption: str):
2022-08-21 23:27:33 +00:00
EMOJI_SIZE = 600
2022-08-15 00:57:07 +00:00
BORDER_SIZE = 10
marsey = get_emoji_from_rdrama(emoji)
2022-08-21 21:46:07 +00:00
marsey = marsey.fit((EMOJI_SIZE, EMOJI_SIZE))
2022-08-15 00:57:07 +00:00
2022-08-21 21:46:07 +00:00
base = AnimatedImage.new((EMOJI_SIZE+2*BORDER_SIZE, EMOJI_SIZE+2*BORDER_SIZE))
base = base.paste(marsey, (BORDER_SIZE, BORDER_SIZE))
2022-08-15 00:57:07 +00:00
return create_modern_meme(base, caption)
class WebcomicPanel():
2022-10-20 00:42:57 +00:00
'''
A panel in a webcomic.
'''
PANEL_SIZE = 400 #Size of a webcomic panel.
FONT = "impact.ttf" #The font to use.
COLOR = ColorScheme.WHITE_WITH_BLACK_BORDER #The color of the font.
2022-08-15 00:57:07 +00:00
def __init__(self):
pass
def create_image(self) -> Image:
2022-08-21 21:46:07 +00:00
return AnimatedImage.new((self.PANEL_SIZE,self.PANEL_SIZE))
2022-08-15 00:57:07 +00:00
2022-08-28 22:38:40 +00:00
def add_text_box(self, base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', align="") -> 'AnimatedImage':
2022-08-21 21:46:07 +00:00
return add_text_box(base, caption, region_size, coordinates, align=align, font=self.FONT, color=self.COLOR, init_font_size=int(self.PANEL_SIZE/10))
2022-08-18 02:47:04 +00:00
2022-08-15 00:57:07 +00:00
class OneCharacterWebcomicPanel(WebcomicPanel):
2022-10-20 00:42:57 +00:00
'''
Panel with one character talking.
'''
2022-08-15 00:57:07 +00:00
def __init__(self, emoji, caption, words_in_background):
self.emoji = emoji
self.caption = caption
self.words_in_background = words_in_background
def create_image(self) -> Image:
base = super().create_image()
panel_size_x, panel_size_y = base.size
# We put the text in the background of the panel.
text_region_x_size = int(panel_size_x)
text_region_y_size = int(panel_size_y if self.words_in_background else panel_size_y/2)
2022-08-21 21:46:07 +00:00
base = self.add_text_box(base, self.caption, (text_region_x_size, text_region_y_size), (0,0))
2022-08-15 00:57:07 +00:00
# We put marsey in the bottom left quadrant
emoji_region_x_size = int(panel_size_x)
emoji_region_y_size = int(panel_size_y/2)
emoji_placement_position_x = 0
emoji_placement_position_y = int(panel_size_y/2)
emoji = get_emoji_from_rdrama(self.emoji)
2022-08-21 21:46:07 +00:00
base = center_and_paste(base, emoji, (emoji_placement_position_x, emoji_placement_position_y), (emoji_region_x_size, emoji_region_y_size))
2022-08-15 00:57:07 +00:00
return add_border_to_image(base)
class TwoCharacterWebcomicPanel(WebcomicPanel):
2022-10-20 00:42:57 +00:00
'''
Panel with two characters talking.
'''
2022-08-15 00:57:07 +00:00
def __init__(self, left_emoji, left_caption, right_emoji, right_caption):
self.left_emoji = left_emoji
self.left_caption = left_caption
self.right_emoji = right_emoji
self.right_caption = right_caption
def create_image(self) -> Image:
base = super().create_image()
panel_size_x, panel_size_y = base.size
# We put the left marsey in the bottom left quadrant
left_emoji_region_x_size = int(panel_size_x/2)
left_emoji_region_y_size = int(panel_size_y/2)
left_emoji_placement_position_x = 0
left_emoji_placement_position_y = int(panel_size_y/2)
left_emoji = get_emoji_from_rdrama(self.left_emoji)
2022-08-21 21:46:07 +00:00
base = center_and_paste(base, left_emoji, (left_emoji_placement_position_x, left_emoji_placement_position_y), (left_emoji_region_x_size, left_emoji_region_y_size))
2022-08-15 00:57:07 +00:00
# We put the right marsey in the bottom right quadrant
right_emoji_region_x_size = int(panel_size_x/2)
right_emoji_region_y_size = int(panel_size_y/2)
right_emoji_placement_position_x = int(panel_size_x/2)
right_emoji_placement_position_y = int(panel_size_y/2)
right_emoji = get_emoji_from_rdrama(self.right_emoji)
2022-08-21 21:46:07 +00:00
base = center_and_paste(base, right_emoji, (right_emoji_placement_position_x, right_emoji_placement_position_y), (right_emoji_region_x_size, right_emoji_region_y_size))
2022-08-15 00:57:07 +00:00
#Each text caption will get 5/8 of the width, and 1/4 of the height of the panel.
#The left caption will be all the way to the left. The right caption will be 3/8 to the right.
CAPTION_UNITS = 5
CAPTION_DIVISOR = 8
# We put the text in the top half of the panel.
left_text_region_x_size = int(CAPTION_UNITS*(panel_size_x/CAPTION_DIVISOR))
left_text_region_y_size = int(panel_size_y/4)
left_text_region_x_position = 0
left_text_region_y_position = 0
2022-08-21 21:46:07 +00:00
base = self.add_text_box(base, self.left_caption, (left_text_region_x_size, left_text_region_y_size), (left_text_region_x_position,left_text_region_y_position), align="bl")
2022-08-15 00:57:07 +00:00
# We put the text in the top half of the panel.
right_text_region_x_size = int(CAPTION_UNITS*(panel_size_x/CAPTION_DIVISOR))
right_text_region_y_size = int(panel_size_y/4)
right_text_region_x_position = int((CAPTION_DIVISOR-CAPTION_UNITS)*(panel_size_x/8))
right_text_region_y_position = int(panel_size_y/4)
2022-08-21 21:46:07 +00:00
base = self.add_text_box(base, self.right_caption, (right_text_region_x_size, right_text_region_y_size), (right_text_region_x_position,right_text_region_y_position), align="br")
2022-08-15 00:57:07 +00:00
return add_border_to_image(base)
class TitleCardWebcomicPanel(WebcomicPanel):
2022-10-20 00:42:57 +00:00
'''
A caption in a webcomic.
'''
2022-08-15 00:57:07 +00:00
def __init__(self, caption):
self.caption = caption
def create_image(self) -> Image:
base = super().create_image()
2022-08-21 21:46:07 +00:00
base = self.add_text_box(base, self.caption, base.size, (0,0), align="cvch")
2022-08-15 00:57:07 +00:00
return add_border_to_image(base)
def create_webcomic(layout : 'list[WebcomicPanel]'):
assumed_panel_x_size, assumed_panel_y_size = layout[0].create_image().size
total_image_x_size = assumed_panel_x_size * 2
total_image_y_size = assumed_panel_x_size * math.ceil(len(layout)/2)
2022-08-21 21:46:07 +00:00
image = AnimatedImage.new((total_image_x_size, total_image_y_size))
2022-08-15 00:57:07 +00:00
for i in range(len(layout)):
2022-10-20 00:42:57 +00:00
print(i)
2022-08-15 00:57:07 +00:00
panel = layout[i]
x = i%2
y = math.floor(i/2)
2022-08-21 21:46:07 +00:00
image = image.paste(panel.create_image(), (x*assumed_panel_x_size, y*assumed_panel_y_size))
2022-08-18 02:47:04 +00:00
return image
2022-08-15 00:57:07 +00:00
2022-08-28 22:38:40 +00:00
def add_text_box(base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', font : str= "arial.ttf", init_font_size = 45, align :str = "", color = ColorScheme.BLACK):
2022-10-20 00:42:57 +00:00
'''
Adds a text box, where the size of the text scales to fit the box as closely as possible.
base: the image to put the text in.
caption: the text
region size: the size of the text box.
coordinates: the position of the text box.
font: name of the font file to use (defaults to arial)
init_font_size: the largest font size that will be used.
align: sets the alignment of the text.
Horizontal Alignments:
t: top
b: bottom
cv: center
Vertical Alignments:
l: left
r: right
ch: center
color: what colorscheme to use.
'''
2022-08-28 22:38:40 +00:00
font = get_real_filename(font)
2022-08-15 00:57:07 +00:00
if caption == "":
2022-08-21 21:46:07 +00:00
return base
2022-08-15 00:57:07 +00:00
if init_font_size == 0:
print("Retard moment")
2022-08-21 21:46:07 +00:00
return base
2022-08-15 00:57:07 +00:00
region_x_size, region_y_size = region_size
2022-08-20 22:18:44 +00:00
2022-08-15 00:57:07 +00:00
if color == ColorScheme.BLACK:
fill_color = (0,0,0)
stroke = None
stroke_size = 0
if color == ColorScheme.WHITE_WITH_BLACK_BORDER:
fill_color = (255,255,255)
stroke = (0,0,0)
stroke_size = 3
2022-08-28 22:38:40 +00:00
caption = caption.upper()
2022-08-20 22:18:44 +00:00
line_image = ImageText((region_x_size+stroke_size, region_y_size))
2022-08-15 00:57:07 +00:00
place = "left"
if "ch" in align:
place = "center"
2022-08-20 22:18:44 +00:00
actual_text_box_size = line_image.write_text_box((stroke_size,0), caption, region_x_size, font_size=init_font_size, font_filename=font, color=fill_color, stroke_color=stroke, stroke_size=stroke_size, place=place, calculate_only=True)
2022-08-15 00:57:07 +00:00
_, actual_text_box_y_size, input_text_block_x_size, actual_text_box_x_size = actual_text_box_size
if actual_text_box_y_size <= region_y_size:
2022-08-20 22:18:44 +00:00
line_image.write_text_box((stroke_size,0), caption, region_x_size, font_size=init_font_size, font_filename=font, color=fill_color, stroke_color=stroke, stroke_size=stroke_size, place=place)
2022-08-15 00:57:07 +00:00
actual_paste_x_coordinates, actual_paste_y_coordinates = coordinates
if align != "":
y_difference = region_y_size - actual_text_box_y_size
x_difference = region_x_size - actual_text_box_x_size
# Horizontal Align
if "t" in align:
#This is the default
pass
elif "b" in align:
actual_paste_y_coordinates+=y_difference
elif "cv" in align:
actual_paste_y_coordinates += int(y_difference/2)
if "l" in align:
#This is the default
pass
elif "r" in align:
actual_paste_x_coordinates+=x_difference
# elif "ch" in align:
# actual_paste_x_coordinates += int(x_difference/2)
if HIGHLIGHT_MODE:
image_draw = ImageDraw.Draw(base)
image_draw.rectangle((coordinates, (coordinates[0]+region_size[0], coordinates[1]+region_size[1])), fill="blue")
image_draw.rectangle(((actual_paste_x_coordinates, actual_paste_y_coordinates), (actual_paste_x_coordinates+actual_text_box_x_size, actual_text_box_y_size+actual_paste_y_coordinates)), fill="tan")
2022-08-21 21:46:07 +00:00
animated_line_image = AnimatedImage.from_image(line_image.image)
return base.paste(animated_line_image, (actual_paste_x_coordinates, actual_paste_y_coordinates))
2022-08-15 00:57:07 +00:00
else:
2022-08-21 21:46:07 +00:00
return add_text_box(base, caption, region_size, coordinates, font=font, init_font_size=init_font_size-1, align=align, color=color)
2022-08-15 00:57:07 +00:00
2022-08-28 22:38:40 +00:00
def add_text(base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', font : str= "arial.ttf"):
2022-10-20 00:42:57 +00:00
'''
Very simple interface for adding text.
'''
2022-08-28 22:38:40 +00:00
font = get_real_filename(font)
print(font)
2022-08-15 00:57:07 +00:00
if caption == "":
return
region_x_size, region_y_size = region_size
line_image = ImageText((region_x_size, region_y_size))
line_image.fill_text_box((0,0), caption, region_x_size, region_y_size, font_filename=font)
2022-08-21 21:46:07 +00:00
return base.paste(AnimatedImage.from_image(line_image.image), coordinates)
2022-08-15 00:57:07 +00:00
2022-08-18 02:47:04 +00:00
def add_watermark(image : Image, name_of_other_creator):
2022-10-20 00:42:57 +00:00
'''
Adds the rdrama watermark to the bottom of the image, crediting the other creator of the image.
'''
2022-08-18 02:47:04 +00:00
global watermark_captions
2022-08-23 01:30:52 +00:00
WATERMARK_HEIGHT = int(0.05 * image.height)
2022-08-15 00:57:07 +00:00
image_size_x, image_size_y = image.size
2022-08-21 21:46:07 +00:00
base = AnimatedImage.new(size=(image_size_x, image_size_y + WATERMARK_HEIGHT))
base = base.paste(image, (0,0))
2022-08-15 00:57:07 +00:00
marsey = get_emoji_from_rdrama("marseyjamming")
2022-08-21 21:46:07 +00:00
marsey = marsey.fit((WATERMARK_HEIGHT, WATERMARK_HEIGHT))
base = base.paste(marsey, (0, image_size_y))
2022-08-15 00:57:07 +00:00
text_line_size = int(WATERMARK_HEIGHT/2)
2022-08-18 02:47:04 +00:00
caption = choice(watermark_captions)
2022-08-21 21:46:07 +00:00
base = add_text(base, f"A meme by {name_of_other_creator} and automeme", (image_size_x, text_line_size), (WATERMARK_HEIGHT, image_size_y))
base = add_text(base, f"{caption}, go to rdrama.net", (image_size_x, text_line_size), (WATERMARK_HEIGHT, image_size_y+text_line_size))
2022-08-15 00:57:07 +00:00
return base
2022-08-28 22:38:40 +00:00
def center_and_paste(base : Image, to_paste : Image, coordinates: 'tuple[int, int]', box_size : 'tuple[int, int]') -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Centers to_paste in a box whose upper-left corder is "coordinates", and with size box_size.
'''
2022-08-21 21:46:07 +00:00
to_paste = to_paste.fit(box_size)
2022-08-15 00:57:07 +00:00
image_size_x, image_size_y = to_paste.size
box_size_x, box_size_y = box_size
extra_space_x = box_size_x - image_size_x
extra_space_y = box_size_y - image_size_y
offset_x = int(extra_space_x/2)
offset_y = int(extra_space_y/2)
box_coordinate_x, box_coordinate_y = coordinates
x, y = offset_x + box_coordinate_x, offset_y + box_coordinate_y
2022-08-21 21:46:07 +00:00
return base.paste(to_paste, (x, y))
2022-08-15 00:57:07 +00:00
def add_border_to_image(image : Image, thickness : int = 5):
2022-10-20 00:42:57 +00:00
'''
Adds a black border to an image.
'''
2022-08-15 00:57:07 +00:00
inner_image_x_size, inner_image_y_size = image.size
outer_image_x_size, outer_image_y_size = inner_image_x_size + 2*thickness, inner_image_y_size + 2*thickness
2022-08-21 21:46:07 +00:00
outside_image = AnimatedImage.new((outer_image_x_size,outer_image_y_size), color=(0,0,0))
2022-08-15 00:57:07 +00:00
2022-08-21 21:46:07 +00:00
outside_image = outside_image.paste(image, (thickness, thickness))
2022-08-15 00:57:07 +00:00
return outside_image
2022-08-21 21:46:07 +00:00
def get_emoji_from_rdrama(emoji_name) -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Gets an emoji from rdrama. If there is a "!" in the emoji name, it will be flipped.
'''
2022-08-15 00:57:07 +00:00
cleaned_emoji_name : str = emoji_name
should_flip = False
if '!' in emoji_name:
cleaned_emoji_name = cleaned_emoji_name.replace("!", "")
should_flip = True
2022-08-24 01:18:46 +00:00
if (exists(get_real_filename(f"emoji_cache/{cleaned_emoji_name}.webp"))):
2022-08-28 22:38:40 +00:00
image = AnimatedImage.from_image(Image.open(get_real_filename(f"emoji_cache/{cleaned_emoji_name}.webp")))
2022-08-21 21:46:07 +00:00
2022-08-15 00:57:07 +00:00
else:
image = get_image_file_from_url(f"https://www.rdrama.net/e/{cleaned_emoji_name}.webp")
2022-08-24 01:18:46 +00:00
image.save(get_real_filename(f"emoji_cache/{cleaned_emoji_name}.webp"))
2022-08-15 00:57:07 +00:00
if should_flip:
2022-08-21 21:46:07 +00:00
image = image.flip()
2022-08-15 00:57:07 +00:00
return image
2022-08-21 21:46:07 +00:00
def get_image_file_from_url(url) -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Gets the image at the given address.
'''
2022-08-24 01:18:46 +00:00
try:
r = requests.get(url)
image_file = io.BytesIO(r.content)
im = Image.open(image_file)
return AnimatedImage.from_image(im)
except:
print(f"ERROR GETTING FILE FROM {url}")
2022-10-20 00:42:57 +00:00
2022-08-18 02:47:04 +00:00
def parse_caption_file(filename):
2022-10-20 00:42:57 +00:00
'''
Opens a file of strings and returns them.
'''
2022-08-18 02:47:04 +00:00
if not exists(filename):
return []
to_return = []
with open(get_real_filename(filename), "r") as f:
for id in f.readlines():
to_return.append(id.strip())
return to_return
2022-10-20 00:42:57 +00:00
2022-08-18 02:47:04 +00:00
2022-08-21 21:46:07 +00:00
class AnimatedImage():
2022-10-20 00:42:57 +00:00
'''
Basically a list of images, meant to be animated.
'''
2022-08-28 22:38:40 +00:00
def __init__(self, frames : 'list[Image.Image]'):
if len(frames) > 100:
self.frames = frames[0:100]
else:
self.frames = frames
2022-08-21 21:46:07 +00:00
@property
2022-08-28 22:38:40 +00:00
def size(self) -> 'Tuple[int, int]':
2022-10-20 00:42:57 +00:00
'''
Size of the image.
'''
return self.frames[0].size
2022-08-21 21:46:07 +00:00
2022-08-23 01:30:52 +00:00
@property
def height(self) -> int:
2022-10-20 00:42:57 +00:00
'''
Height of the image.
'''
2022-08-23 01:30:52 +00:00
return self.frames[0].size[1]
@property
def width(self) -> int:
2022-10-20 00:42:57 +00:00
'''
Width of the image.
'''
2022-08-23 01:30:52 +00:00
return self.frames[0].size[0]
2022-08-21 21:46:07 +00:00
def flip(self) -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Flips image horizontally.
'''
return self.perform_transform_for_all_images(lambda image : ImageOps.mirror(image))
2022-08-21 21:46:07 +00:00
2022-10-20 00:42:57 +00:00
def save(self, filename : str) -> None:
'''
Saves the image to the filename
'''
2022-08-21 21:46:07 +00:00
self.frames[0].save(f"{filename}", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
def get_binary(self) -> bytes:
2022-10-20 00:42:57 +00:00
'''
Converts the image to binary.
'''
2022-08-21 21:46:07 +00:00
output = io.BytesIO()
self.frames[0].save(output, format="webp", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
return output.getvalue()
2022-10-20 00:42:57 +00:00
def get_binary_gif(self) -> bytes:
output = io.BytesIO()
self.frames[0].save(output, format="gif", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
return output.getvalue()
2022-08-21 21:46:07 +00:00
def from_image(image: Image.Image) -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Converts a run of the mill PIL image to an AnimatedImage.
'''
2022-08-21 21:46:07 +00:00
frames = []
for image_frame in ImageSequence.Iterator(image):
frames.append(image_frame.copy())
return AnimatedImage(frames)
def paste(self, other : 'AnimatedImage', position : 'Tuple[int, int]') -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Pastes one image onto another. Tries to do it without breaking the loop.
'''
2022-08-21 21:46:07 +00:00
num_self_frames = len(self.frames)
num_other_frames = len(other.frames)
new_frames = []
2022-10-20 00:42:57 +00:00
frames_to_create = min(get_ideal_number_of_frames(num_self_frames, num_other_frames), 100)
for i in range(frames_to_create):
2022-08-21 21:46:07 +00:00
self_frame = self.frames[i%num_self_frames]
other_frame = other.frames[i%num_other_frames]
new_frame = self_frame.copy()
try:
new_frame.paste(other_frame, position, other_frame)
except ValueError:
#something about transparently idfk lol
new_frame.paste(other_frame, position)
new_frames.append(new_frame)
return AnimatedImage(new_frames)
2022-08-28 22:38:40 +00:00
def new(size : 'Tuple[int, int]', color=(255,255,255)) -> 'AnimatedImage':
2022-10-20 00:42:57 +00:00
'''
Creates a blank AnimatedImage.
'''
2022-08-21 21:46:07 +00:00
base = Image.new(mode="RGB", size=size, color=color)
return AnimatedImage.from_image(base)
2022-08-28 22:38:40 +00:00
def fit(self, region : 'Tuple[int, int]') -> 'AnimatedImage':
2022-09-04 19:52:55 +00:00
'''
Shrinks the image while preserving it's aspect ratio, such that the new image has the same ratio but is contained in the given box.
'''
2022-10-20 00:42:57 +00:00
return self.perform_transform_for_all_images(lambda image : ImageOps.contain(image, region))
2022-09-04 19:52:55 +00:00
def stretch(self, region : 'Tuple[int, int]') -> 'AnimatedImage':
'''
Resizes the image such that the final image has the exact dimensions given by region
'''
return self.perform_transform_for_all_images(lambda image : image.resize(region, Image.ANTIALIAS))
def stretch_maintain_aspect_ratio(self, size : int) -> 'AnimatedImage':
aspect_ratio = self.height / self.width
ideal_height = int(size * aspect_ratio)
return self.stretch((size, ideal_height))
def perform_transform_for_all_images(self, transform : 'Callable[[Image.Image], Image.Image]') -> 'AnimatedImage':
new_frames = []
for frame in self.frames:
new_frames.append(transform(frame))
return AnimatedImage(new_frames)
2022-08-21 21:46:07 +00:00
def prime_decomposition(a):
primes = []
current = a
while True:
if current == 1:
return primes
for i in range(current+1):
if i > 1 and current % i == 0:
primes.append(i)
current = int(current/i)
break
def get_ideal_number_of_frames(frame_count_1, frame_count_2):
bigger = max(frame_count_1, frame_count_2)
smaller = min(frame_count_1, frame_count_2)
if (bigger % smaller == 0):
return bigger
else:
product = 1
for i in get_leftover_primes(bigger, smaller):
product *= i
return bigger * i
def get_leftover_primes(a_, b_):
a = max(a_, b_)
b = min(a_, b_)
a_primes = prime_decomposition(a)
b_primes = prime_decomposition(b)
leftover_primes = []
for i in b_primes:
if i in a_primes:
a_primes.remove(i)
else:
leftover_primes.append(i)
return leftover_primes
2022-10-20 00:42:57 +00:00
watermark_captions = parse_caption_file(get_real_filename(CAPTION_FILENAME))
2022-08-21 21:46:07 +00:00
# shooting = get_emoji_from_rdrama("!marseyshooting")
# shooting2 = get_emoji_from_rdrama("marseydeterminedgun")
# base = AnimatedImage.new((500, 500))
# base = base.paste(shooting, (0,0))
# base = base.paste(shooting2.fit((50, 50)), (250,250))
# base = add_text_box(base, "killing time", (100, 500), (0, 400))
# base.save("kingshit.webp")
#create_soy_vs_chad_meme("bigsmilesoyjak", "!marseyshooting", "I have fun new toys and games for your children", "Die").save("brand_new_meme.webp")
2022-08-15 00:57:07 +00:00
# create_webcomic([
# TwoCharacterWebcomicPanel("soyjak", "Black people deserve the rope", "marseyconfused", "Why?"),
# TwoCharacterWebcomicPanel("seethejak", "Because they are degenerate!!!", "marseysmug", "Kinda like you?"),
# OneCharacterWebcomicPanel("soycry", "No!! I am a pure aryan white boy and I am special Hitler told me so, so fuck off you nigger cunt. God I hate you so much, if I commit suicide its gonna be your fault, fuck you, its not fair, why do you always have to bully me around, you are such a bitch, i hate you so much, hitler was right and six million wasnt nearly enough, the jews are responsible for this and they will be punished on the day of the rope, youll be sorry then, plus i am armed and dangerous and i have a gun and i will shoot you if you keep making fun of me plus youll be in troulbe if I kill myself so how do you like that you stupid nigger bitch. fuck", words_in_background=True),
# TitleCardWebcomicPanel("One Hour Later..."),
# TwoCharacterWebcomicPanel("marseytombstone", "", "!marseycry", "He had so much to live for"),
# OneCharacterWebcomicPanel("marseylaugh", "Just Kidding!", False)
2022-08-21 21:46:07 +00:00
# ]).save("webcomic.webp")
2022-10-20 00:42:57 +00:00
#create_modern_meme_from_url("https://media.giphy.com/media/gYkga3bZav66I/giphy.webp", "me when i see a black person (i am extremely racist)").save("racism.webp")
2022-09-04 19:52:55 +00:00
#create_classic_meme_from_url("https://rdrama.net/images/16619117705921352.webp", "I left out 'hiking' intentionally since it's outdoor hobby 101. But even if he's trying to attract a homebody some kind of interest would be good to have. Like home brewing, art, instrument, or god forbid tabletop gaming.", "This just screams most boring, generic, codependent 'man' ever. I need a wife and I'll be happy to do whatever you want to do 24/7 because I have no personality, friends, or interests").save("classic.webp")
#create_modern_meme_from_url("https://rdrama.net/images/16617243111639555.webp", "It really does, especially with that weird lighting. ANd what's up with the subset of troons with Himmler eyes?").save("modern_image.webp")
2022-08-23 01:30:52 +00:00
#create_classic_meme_from_emoji("marseycock", "I WANT TO PUT MY DICK", "INSIDE OF MARSEY").save("classic_with_emoji.webp")
2022-08-21 21:46:07 +00:00
#create_modern_meme_from_emoji("rdramajanny", "youll be sorry when i get my mop you BITCH").save("modern_emoji.webp")