diff --git a/piggy/api.py b/piggy/api.py new file mode 100644 index 0000000..0e2a130 --- /dev/null +++ b/piggy/api.py @@ -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/") +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) diff --git a/piggy/app.py b/piggy/app.py index 00fb8cb..b7fde98 100644 --- a/piggy/app.py +++ b/piggy/app.py @@ -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())) @@ -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 diff --git a/piggy/resources/fonts/Lato-Bold.ttf b/piggy/resources/fonts/Lato-Bold.ttf new file mode 100644 index 0000000..016068b Binary files /dev/null and b/piggy/resources/fonts/Lato-Bold.ttf differ diff --git a/piggy/resources/fonts/Lato-Light.ttf b/piggy/resources/fonts/Lato-Light.ttf new file mode 100644 index 0000000..dfa72ce Binary files /dev/null and b/piggy/resources/fonts/Lato-Light.ttf differ diff --git a/piggy/resources/fonts/Lato-Regular.ttf b/piggy/resources/fonts/Lato-Regular.ttf new file mode 100644 index 0000000..bb2e887 Binary files /dev/null and b/piggy/resources/fonts/Lato-Regular.ttf differ diff --git a/piggy/templates/objects/card-assignment.html b/piggy/templates/objects/card-assignment.html index 8d4208e..903f5c6 100644 --- a/piggy/templates/objects/card-assignment.html +++ b/piggy/templates/objects/card-assignment.html @@ -1,30 +1,32 @@ {# Card Container #} -{# Card Title #} -
- {{ data.meta.name }} -
+ {# Card Title #} +
+ {{ data.meta.name }} +
-{# Card Thumbnail #} -
- Header image for {{ item }} + {# Card Thumbnail #} +
+ Header image for {{ item }} - {# TEMP -
- {{ data.meta.description }} -
- #} -
+
+ Level {{ data.level }} - {{ data.level_name }} +
+
-{# Card Description #} -
- {{ data.meta.description }} -
-
\ No newline at end of file + {# Card Description #} +
+ {{ data.meta.description }} +
+ diff --git a/piggy/thumbnails.py b/piggy/thumbnails.py new file mode 100644 index 0000000..0ef0c26 --- /dev/null +++ b/piggy/thumbnails.py @@ -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 diff --git a/piggy/utils.py b/piggy/utils.py new file mode 100644 index 0000000..fbcf7c2 --- /dev/null +++ b/piggy/utils.py @@ -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") diff --git a/pyproject.toml b/pyproject.toml index 73782dd..82757f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "flask", "turtleconverter@git+https://github.com/sondregronas/turtleconverter@main", "gunicorn", + "pillow", ] [build-system]