Skip to content

versioning

Add logo to cover for Open Graph¶

The logo_on_cover.py file provides a Flask blueprint that dynamically generates cover images with a logo overlaid in a specified corner as an image that can be used for an Open Graph . It exposes an HTTP endpoint that takes a cover image filename and optional query parameters (logo scale, corner, margin), composites the logo onto the cover, and returns the resulting PNG image. The file also implements caching to optimize repeated requests for the same parameters.

The main responsibilities of this file are:

  • Loading and converting an SVG logo to PNG.
  • Overlaying the logo onto a cover image at a configurable position and scale.
  • Serving the composited image via a Flask route, with input validation and caching.

🔌 API¶

logo_on_cover ¶

Functions:

Name Description
create_og_cover_blueprint

Creates a Flask blueprint for serving cover images with a logo overlay.

overlay_logo_bytes

Overlays a logo onto a cover image and returns the composited image as PNG bytes.

create_og_cover_blueprint(path_logo, logger=None) ¶

Creates a Flask blueprint for serving cover images with a logo overlay.

This function sets up routes and logic for handling requests to overlay a logo on cover images, including caching and query parameter validation.

Parameters:

Name Type Description Default
logger Logger | None

An optional Logger instance for logging.

None

Returns:

Type Description
Blueprint

A Flask Blueprint configured for cover image overlay routes.

Source code in src/routes/logo_on_cover.py
def create_og_cover_blueprint(
    path_logo: Path, logger: Logger | None = None
) -> Blueprint:
    """
    Creates a Flask blueprint for serving cover images with a logo overlay.

    This function sets up routes and logic for handling requests to overlay a logo on cover images,
    including caching and query parameter validation.

    Args:
        logger: An optional Logger instance for logging.

    Returns:
        A Flask Blueprint configured for cover image overlay routes.
    """
    og = Blueprint("og", __name__)

    logger: Logger = logger or NullLogger()

    @og.route("/cover/<path:filename>")
    def cover_with_logo(filename):
        """
        Serves a cover image with a logo overlaid, using query parameters for customization.

        This function locates the requested cover image, validates input parameters, overlays the logo,
        and returns the composited PNG image. Results are cached for performance.

        Args:
            filename: The name of the cover image file.

        Returns:
            A Flask response containing the PNG image with the logo overlaid.

        Raises:
            Aborts with a 404 error if the cover image is not found.
            Aborts with a 400 error if query parameters are invalid.
        """
        cover_path = _get_cover_path(filename)
        _validate_cover_path(cover_path)
        logo_scale, corner, margin = _get_query_params()
        _validate_query_params(logo_scale, corner, margin)
        # TODO: Add redis caching
        # Use cover file modification time for cache key
        # cover_mtime = cover_path.stat().st_mtime
        # cache_key = f"{filename}:{cover_mtime}:{logo_scale}:{corner}:{margin}"
        # if cached := current_app.cache.get(cache_key):
        #     return send_file(BytesIO(cached), mimetype="image/png")
        with open(path_logo, "rb") as f:
            file_logo = f.read()
        result = overlay_logo_bytes(
            cover_bytes=cover_path.read_bytes(),
            svg_bytes=file_logo,
            logo_scale=logo_scale,
            corner=corner,
            margin=margin,
        )
        # current_app.cache.set(cache_key, result, timeout=60 * 60 * 24)
        return send_file(BytesIO(result), mimetype="image/png")

    return og

overlay_logo_bytes(cover_bytes, svg_bytes, *, logo_scale=0.4, corner='bottom_right', margin=10) ¶

Overlays a logo onto a cover image and returns the composited image as PNG bytes.

This function places a logo, provided as SVG bytes, onto a cover image at a specified corner and scale.

Parameters:

Name Type Description Default
cover_bytes bytes

The cover image data as bytes.

required
svg_bytes bytes

The SVG logo image data as bytes.

required
logo_scale float

The scale of the logo relative to the shortest side of the cover image.

0.4
corner str

The corner of the cover image to place the logo ('bottom_right', 'bottom_left', 'top_right', 'top_left').

'bottom_right'
margin int

The margin in pixels between the logo and the edge of the cover image.

10

Returns:

Type Description
bytes

PNG image data as bytes with the logo overlaid.

Raises:

Type Description
ValueError

If an unsupported corner is specified.

Source code in src/routes/logo_on_cover.py
def overlay_logo_bytes(
    cover_bytes: bytes,
    svg_bytes: bytes,
    *,
    logo_scale: float = 0.4,
    corner: str = "bottom_right",
    margin: int = 10,
) -> bytes:
    """Overlays a logo onto a cover image and returns the composited image as PNG bytes.

    This function places a logo, provided as SVG bytes, onto a cover image at a specified corner and scale.

    Args:
        cover_bytes (bytes): The cover image data as bytes.
        svg_bytes (bytes): The SVG logo image data as bytes.
        logo_scale (float): The scale of the logo relative to the shortest side of the cover image.
        corner (str): The corner of the cover image to place the logo ('bottom_right', 'bottom_left', 'top_right', 'top_left').
        margin (int): The margin in pixels between the logo and the edge of the cover image.

    Returns:
        PNG image data as bytes with the logo overlaid.

    Raises:
        ValueError: If an unsupported corner is specified.
    """
    cover = _prepare_cover(cover_bytes)
    canvas = _create_blurred_canvas(cover)

    orig_width, orig_height = cover.size
    img_logo = _prepare_logo(svg_bytes, (orig_width, orig_height), logo_scale)
    x, y = _calculate_logo_position(canvas.size, img_logo.size, corner, margin)

    # Ensure logo has an alpha channel for use as mask
    if img_logo.mode != "RGBA":
        img_logo = img_logo.convert("RGBA")

    canvas.paste(img_logo, (x, y), img_logo)

    return _save_image_to_bytes(canvas)