Files
maskanx_cm_backend/scripts/finalize-branded-image.py
T
AFFAANhandClaude Opus 5 19e1e84fb7 Initial commit: MaskanX backend
Independent FastAPI backend for the MaskanX agentic growth platform.

Includes the agent runtime, MCP client integrations (Meta Ads, LinkedIn,
HubSpot, Tavily, Exa, xAI, Citedy, image generation), PostgreSQL storage
for chats and cron jobs, provider and secret management, and the CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:28:22 +05:30

151 lines
4.6 KiB
Python

#!/usr/bin/env python
"""Finalize a generated social image with exact brand formatting.
This script keeps the AI-generated image and the company logo separate:
generate the base image first, then overlay the real logo here so the brand mark
does not get distorted by the image model.
"""
from __future__ import annotations
import argparse
from pathlib import Path
try:
from PIL import Image, ImageDraw, ImageOps
except ModuleNotFoundError as exc: # pragma: no cover - friendly CLI error
raise SystemExit(
"Pillow is required. Install it with: "
"python -m pip install Pillow"
) from exc
def _open_rgba(path: Path) -> Image.Image:
return Image.open(path).convert("RGBA")
def _fit_cover(img: Image.Image, size: tuple[int, int]) -> Image.Image:
target_w, target_h = size
src_w, src_h = img.size
scale = max(target_w / src_w, target_h / src_h)
resized = img.resize(
(round(src_w * scale), round(src_h * scale)),
Image.Resampling.LANCZOS,
)
left = (resized.width - target_w) // 2
top = (resized.height - target_h) // 2
return resized.crop((left, top, left + target_w, top + target_h))
def _fit_contain(img: Image.Image, size: tuple[int, int]) -> Image.Image:
target_w, target_h = size
src_w, src_h = img.size
scale = min(target_w / src_w, target_h / src_h)
resized = img.resize(
(round(src_w * scale), round(src_h * scale)),
Image.Resampling.LANCZOS,
)
canvas = Image.new("RGBA", size, (255, 255, 255, 255))
x = (target_w - resized.width) // 2
y = (target_h - resized.height) // 2
canvas.alpha_composite(resized, (x, y))
return canvas
def _parse_size(value: str) -> tuple[int, int]:
try:
w_text, h_text = value.lower().split("x", 1)
width = int(w_text.strip())
height = int(h_text.strip())
except Exception as exc:
raise argparse.ArgumentTypeError(
"Size must be formatted like 1200x628"
) from exc
if width < 300 or height < 300:
raise argparse.ArgumentTypeError("Size must be at least 300x300")
return width, height
def finalize_image(
source: Path,
logo: Path,
output: Path,
size: tuple[int, int],
fit: str,
logo_width_pct: float,
margin_pct: float,
plate: bool,
) -> None:
base = _open_rgba(source)
canvas = _fit_cover(base, size) if fit == "cover" else _fit_contain(base, size)
logo_img = _open_rgba(logo)
max_logo_w = round(size[0] * logo_width_pct / 100)
max_logo_h = round(size[1] * 0.12)
logo_img.thumbnail((max_logo_w, max_logo_h), Image.Resampling.LANCZOS)
margin = round(size[0] * margin_pct / 100)
plate_pad_x = max(12, round(logo_img.width * 0.24))
plate_pad_y = max(8, round(logo_img.height * 0.22))
logo_x = size[0] - margin - logo_img.width
logo_y = size[1] - margin - logo_img.height
if plate:
draw = ImageDraw.Draw(canvas)
rect = (
logo_x - plate_pad_x,
logo_y - plate_pad_y,
logo_x + logo_img.width + plate_pad_x,
logo_y + logo_img.height + plate_pad_y,
)
radius = max(10, round(min(logo_img.size) * 0.22))
draw.rounded_rectangle(
rect,
radius=radius,
fill=(255, 255, 255, 225),
outline=(229, 231, 235, 160),
width=1,
)
canvas.alpha_composite(logo_img, (logo_x, logo_y))
output.parent.mkdir(parents=True, exist_ok=True)
rgb = ImageOps.exif_transpose(canvas).convert("RGB")
rgb.save(output, quality=95, optimize=True)
def main() -> None:
parser = argparse.ArgumentParser(
description="Resize a generated image and overlay the company logo.",
)
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--logo", required=True, type=Path)
parser.add_argument("--out", required=True, type=Path)
parser.add_argument("--size", type=_parse_size, default=(1200, 628))
parser.add_argument("--fit", choices=("cover", "contain"), default="cover")
parser.add_argument("--logo-width-pct", type=float, default=12.0)
parser.add_argument("--margin-pct", type=float, default=3.0)
parser.add_argument(
"--no-plate",
action="store_true",
help="Do not draw a subtle white plate behind the logo.",
)
args = parser.parse_args()
finalize_image(
source=args.source,
logo=args.logo,
output=args.out,
size=args.size,
fit=args.fit,
logo_width_pct=args.logo_width_pct,
margin_pct=args.margin_pct,
plate=not args.no_plate,
)
print(f"Saved branded image: {args.out}")
if __name__ == "__main__":
main()