Skip to content

Commit

Permalink
feat: add auto-gen thumbnails
Browse files Browse the repository at this point in the history
Resolves #5
  • Loading branch information
sondregronas committed Aug 29, 2024
1 parent de58a98 commit 821af43
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 24 deletions.
57 changes: 57 additions & 0 deletions piggy/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from flask import Blueprint, request
from piggy.thumbnails import create_thumbnail
from piggy.utils import serve_pil_image

api_routes = Blueprint("api", __name__, url_prefix="/api")


@api_routes.route("/generate_thumbnail/<string:text>")
def generate_thumbnail(text: str):
"""
Generate a thumbnail from the given heading.
:param heading: The heading to generate a thumbnail for
:return: A thumbnail image
"""
# get query parameters from the request
bg_color = request.args.get("bg_color", "")
text_color = request.args.get("text_color", "")
width = request.args.get("width", 500)
height = request.args.get("height", 200)

# c is a hash of the text to get a color combination
gibberish = request.args.get("c", "")

# Text, Background combinations
color_palettes = [
("ffffff", "85144b"),
("aaaaaa", "000000"),
("001f3f", "39cccc"),
("ff851b", "001f3f"),
("2ecc40", "001f3f"),
("39cccc", "001f3f"),
("7fdbff", "85144b"),
("ff851b", "001f3f"),
]

if gibberish and not bg_color and not text_color:
# use a simple hashing function to get a color combination from the gibberish
text_color, bg_color = color_palettes[hash(gibberish) % len(color_palettes)]

if not bg_color:
bg_color = "111111"
if not text_color:
text_color = "fefefe"

# sanitize the query parameters, text is max 50ch
_text = text[:50]
if len(_text) < len(text):
_text += "..."
bg_color = bg_color.lstrip("#")
text_color = text_color.lstrip("#")
width = min(int(width), 1000)
height = min(int(height), 1000)

# create the thumbnail
img = create_thumbnail(_text, bg_color, text_color, (width, height)).convert("RGB")
return serve_pil_image(img)
2 changes: 2 additions & 0 deletions piggy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from piggy.caching import lru_cache_wrapper, _render_assignment, cache_directory, _render_assignment_wildcard
from piggy.piggybank import PIGGYMAP
from piggy.exceptions import PiggyHTTPException
from piggy.api import api_routes

# Ensure the working directory is the root of the project
os.chdir(os.path.dirname(Path(__file__).parent.absolute()))
Expand Down Expand Up @@ -92,5 +93,6 @@ def get_assignment_media_wildcard(wildcard, filename):

app.register_blueprint(assignment_routes)
app.register_blueprint(media_routes)
app.register_blueprint(api_routes)

return app
Binary file added piggy/resources/fonts/Lato-Bold.ttf
Binary file not shown.
Binary file added piggy/resources/fonts/Lato-Light.ttf
Binary file not shown.
Binary file added piggy/resources/fonts/Lato-Regular.ttf
Binary file not shown.
50 changes: 26 additions & 24 deletions piggy/templates/objects/card-assignment.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
{# Card Container #}
<a
href="{{ abspath }}/{{ item }}"
class="card-container block p-3 rounded-xl border border-slate-500"
href="{{ abspath }}/{{ item }}"
class="card-container block p-3 rounded-xl border border-slate-500"
>
{# Card Title #}
<div class="text-2xl font-extrabold text-center pb-2">
{{ data.meta.name }}
</div>
{# Card Title #}
<div class="text-2xl font-extrabold text-center pb-2">
{{ data.meta.name }}
</div>

{# Card Thumbnail #}
<div class="thumbnail-container">
<img
class="w-full h-36 object-cover rounded-xl shadow-lg"
src="{{ media_abspath }}/{{ item }}/media/header.{{ img_fmt }}"
alt="Header image for {{ item }}"
/>
{# Card Thumbnail #}
<div class="thumbnail-container">
<img
class="w-full h-36 object-cover rounded-xl shadow-lg"
{% if data.meta.thumbnail %}
src="{{ abspath }}/{{ item }}/{{ data.meta.thumbnail }}"
{% else %}
src="{{ url_for('api.generate_thumbnail', text=data.level_name) }}?c={{ data.assignment_name }}"
{% endif %}
alt="Header image for {{ item }}"
/>

{# TEMP
<div class="thumbnail-text-overlay">
{{ data.meta.description }}
</div>
#}
</div>
<div class="thumbnail-text-overlay">
Level {{ data.level }} - {{ data.level_name }}
</div>
</div>

{# Card Description #}
<div class="mt-4 description-container font-semibold">
{{ data.meta.description }}
</div>
</a>
{# Card Description #}
<div class="mt-4 description-container font-semibold">
{{ data.meta.description }}
</div>
</a>
45 changes: 45 additions & 0 deletions piggy/thumbnails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from pathlib import Path

import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import textwrap


# TODO: this is a mess.


def create_thumbnail(
title: str, bg_color: str = "111111", text_color: str = "fefefe", size: tuple[int, int] = (700, 300)
) -> PIL.Image:
"""Returns the image data for a thumbnail of the given image."""
im = PIL.Image.new("RGB", size, "#" + bg_color)
box = (0, 0, size[0], size[1])
draw = PIL.ImageDraw.Draw(im)

margin = 40

# Plays better with the text overlay
y_offset = 10

text = textwrap.fill(title, width=30)
font_size = 180
size = None

# Get the font path relative to this file
font_path = Path(__file__).parent / "resources/fonts/Lato-Bold.ttf"
font = PIL.ImageFont.truetype(font_path.as_posix(), font_size)
recursions = 0
while (
(size is None or size[2] > box[2] - margin or size[3] > box[3] - margin) and font_size > 0 and recursions < 50
):
font = font.font_variant(size=font_size)
size = draw.multiline_textbbox((0, 0), text, font=font, align="center")
font_size -= 5
recursions += 1

box = (box[2] / 2, box[3] / 2 - y_offset)

draw.multiline_text(box, text, font=font, fill="#" + text_color, align="center", anchor="mm")

return im
9 changes: 9 additions & 0 deletions piggy/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask import send_file
from io import BytesIO


def serve_pil_image(pil_img):
img_io = BytesIO()
pil_img.save(img_io, "webp", quality=100)
img_io.seek(0)
return send_file(img_io, mimetype="image/webp")
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies = [
"flask",
"turtleconverter@git+https://github.com/sondregronas/turtleconverter@main",
"gunicorn",
"pillow",
]

[build-system]
Expand Down

0 comments on commit 821af43

Please sign in to comment.