DesignGym / core /describe.py
AdithyaSK's picture
AdithyaSK HF Staff
Serve Crello corpus aafdb0e86816 from the mounted bucket
df84e45 verified
Raw History Blame Contribute Delete
4.59 kB
"""Task 2's prompt: the design described in words instead of shown as an image.
The description is generated from the ground-truth document, so it is exactly
as informative as intended and never hallucinates: every asset and every word
of copy is named, colours and fonts are given, and placement is coarse, in the
vocabulary a designer would use in a brief ("top left", "centred", "large").
The model has to turn that into coordinates, which is the skill the task tests.
"""
from __future__ import annotations
from .colors import color_name
from .doc import DesignDoc, Element
def _where(el: Element, doc: DesignDoc) -> str:
x0, y0 = max(0.0, el.x), max(0.0, el.y)
x1, y1 = min(doc.width, el.x + el.width), min(doc.height, el.y + el.height)
if x1 <= x0 or y1 <= y0:
return "mostly off the canvas"
cover_w = (x1 - x0) / doc.width
cover_h = (y1 - y0) / doc.height
if cover_w > 0.9 and cover_h > 0.9:
return "covering the whole canvas"
cx = (x0 + x1) / 2 / doc.width
cy = (y0 + y1) / 2 / doc.height
col = "left" if cx < 0.36 else "right" if cx > 0.64 else "center"
row = "top" if cy < 0.36 else "bottom" if cy > 0.64 else "middle"
if cover_w > 0.9:
return {"top": "across the top", "middle": "across the middle",
"bottom": "across the bottom"}[row]
if cover_h > 0.9:
return {"left": "down the left side", "center": "down the middle",
"right": "down the right side"}[col]
if row == "middle" and col == "center":
return "in the center"
if col == "center":
return f"at the {row} center"
if row == "middle":
return f"at the middle {col}"
return f"at the {row} {col}"
def _size(el: Element, doc: DesignDoc) -> str:
frac = (el.width * el.height) / (doc.width * doc.height)
if frac > 0.6:
return "full-bleed"
if frac > 0.25:
return "large"
if frac > 0.07:
return "medium"
if frac > 0.015:
return "small"
return "tiny"
def _text_size(el: Element, doc: DesignDoc) -> str:
rel = el.font_size / doc.height
if rel > 0.12:
return "huge"
if rel > 0.06:
return "large"
if rel > 0.03:
return "medium"
return "small"
def describe(doc: DesignDoc, meta: dict, asset_meta: dict[str, dict]) -> str:
fmt = meta.get("format") or "design"
title = meta.get("title") or ""
head = f"A {fmt} design, {doc.width}x{doc.height}px"
if title:
head += f', titled "{title}"'
lines = [head + "."]
lines.append(
f"Background colour: {color_name(doc.background)} ({doc.background})."
)
lines.append("Layers from back to front:")
for n, el in enumerate(doc.elements, 1):
where = _where(el, doc)
if el.kind == "image":
kind = asset_meta.get(el.asset_id, {}).get("kind", "image")
noun = {"photo": "photo", "graphic": "graphic", "background": "background texture"}.get(
kind, "image"
)
desc = f"{n}. A {_size(el, doc)} {noun} (asset {el.asset_id}) {where}"
elif el.kind == "shape":
desc = f"{n}. A {el.shape} in {color_name(el.fill or '#000000')} {where}"
else:
text = el.display_text().replace("\n", " / ")
style = []
if el.bold:
style.append("bold")
if el.italic:
style.append("italic")
color = el.color or "#000000"
desc = (
f'{n}. Text "{text}" in {" ".join(style + [el.font_family or ""]).strip()}, '
f"{_text_size(el, doc)} ({el.font_size:.0f}px), {color_name(color)} ({color}), "
f"{el.align}-aligned, {where}"
)
if el.letter_spacing and abs(el.letter_spacing) >= 0.5:
desc += f", letter-spacing {el.letter_spacing:.0f}px"
if len(el.lines) > 1:
desc += f", {len(el.lines)} lines"
if el.runs:
colors = sorted({color_name(r.color) for r in el.styled_runs() if r.text.strip()})
if len(colors) > 1:
desc += f" (mixed colours: {', '.join(colors)})"
if el.angle:
desc += f", rotated {el.angle:.0f} degrees"
if el.opacity < 0.95:
desc += f", {round(el.opacity * 100)}% opacity"
lines.append(desc + ".")
keywords = [k for k in (meta.get("keywords") or [])][:8]
if keywords:
lines.append(f"Mood / keywords: {', '.join(keywords)}.")
return "\n".join(lines)