thumbnails: Take crop/scaling info from a json file next to the image file

pull/313/head
Mike Lang 2 years ago committed by Mike Lang
parent e5b1a57f37
commit a3e16a2686

@ -1,27 +1,52 @@
import json
import os import os
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
# The region of the frame image to place on the template. """
# Format is (top_left_x, top_left_y, bottom_right_x, bottom_right_y). A template is two files:
# The frame is scaled to the size of the template before this is done. NAME.png
FRAME_CROP = None # no crop NAME.json
The image is the template image itself.
The JSON file contains the following:
{
crop: BOX,
location: BOX,
}
where BOX is a 4-tuple [left x, top y, right x, bottom y] describing a rectangle in image coordinates.
# The location in the template to place the frame image after cropping. To create a thumbnail, the input frame is first cropped to the bounds of the "crop" box,
# Format is (x, y) of top-left corner. then resized to the size of the "location" box, then pasted underneath the template image at that
FRAME_LOCATION = (0, 90) location within the template image.
For example, a JSON file of:
{
"crop": [50, 100, 1870, 980],
"location": [320, 180, 1600, 900]
}
would crop the input frame from (50, 100) to (1870, 980), resize it to 720x1280,
and place it at (320, 180).
If the original frame and the template differ in size, the frame is first resized to the template.
This allows you to work with a consistent coordinate system regardless of the input frame size.
"""
def compose_thumbnail_template(base_dir, template_name, frame_data): def compose_thumbnail_template(base_dir, template_name, frame_data):
template_path = os.path.join(base_dir, "thumbnail_templates", f"{template_name}.png") template_path = os.path.join(base_dir, "thumbnail_templates", f"{template_name}.png")
info_path = os.path.join(base_dir, "thumbnail_templates", f"{template_name}.json")
template = Image.open(template_path) template = Image.open(template_path)
# PIL can't load an image from a byte string directly, we have to pretend to be a file # PIL can't load an image from a byte string directly, we have to pretend to be a file
frame = Image.open(BytesIO(frame_data)) frame = Image.open(BytesIO(frame_data))
# The parameters of how we overlay the template are hard-coded for now. with open(info_path) as f:
# We can make this configurable later if needed. info = json.load(f)
crop = info['crop']
loc_left, loc_top, loc_right, loc_bottom = info['location']
location = loc_left, loc_top
location_size = loc_right - loc_left, loc_bottom - loc_top
# Create a new blank image of the same size as the template # Create a new blank image of the same size as the template
result = Image.new('RGBA', template.size) result = Image.new('RGBA', template.size)
@ -30,10 +55,11 @@ def compose_thumbnail_template(base_dir, template_name, frame_data):
# and we don't really care about performance. # and we don't really care about performance.
if frame.size != template.size: if frame.size != template.size:
frame = frame.resize(template.size, Image.LANCZOS) frame = frame.resize(template.size, Image.LANCZOS)
# Insert the frame at the desired location and cropping # Insert the frame at the desired location, cropping and scaling.
if FRAME_CROP is not None: # Technically we might end up resizing twice here which is bad for quality,
frame = frame.crop(FRAME_CROP) # but the case of frame size != template size should be rare enough that it doesn't matter.
result.paste(frame, FRAME_LOCATION) frame = frame.crop(crop).resize(location_size, Image.LANCZOS)
result.paste(frame, location)
# Place the template "on top", letting the frame be seen only where the template's alpha # Place the template "on top", letting the frame be seen only where the template's alpha
# lets it through. # lets it through.
result.alpha_composite(template) result.alpha_composite(template)

Loading…
Cancel
Save