ASO: renderers dark-panorama (iPhone + iPad) para screenshots 1.5.0
frame_panorama_codex.py / _ipad.py: panorámica premium oscura (Impact, marco real, +22.7% verde, línea continua) con copy localizado nativo en 7 idiomas. Usado para las screenshots publicadas de la 1.5.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
This commit is contained in:
@@ -92,7 +92,8 @@ def with_shadow(rgba: Image.Image, blur: int, alpha: float, off=(0, 40)) -> Imag
|
||||
canvas = Image.new("RGBA", (rgba.width + pad * 2, rgba.height + pad * 2), (0, 0, 0, 0))
|
||||
sh = Image.new("RGBA", rgba.size, (0, 0, 0, 0))
|
||||
sh.putalpha(rgba.split()[3].point(lambda p: int(p * alpha)))
|
||||
dark = Image.new("RGBA", rgba.size, (10, 20, 40, 255))
|
||||
# cool light glow (not a dark drop-shadow) so the device lifts off the dark bg
|
||||
dark = Image.new("RGBA", rgba.size, (78, 110, 180, 255))
|
||||
dark.putalpha(sh.split()[3])
|
||||
canvas.alpha_composite(dark, (pad + off[0], pad + off[1]))
|
||||
canvas = canvas.filter(ImageFilter.GaussianBlur(blur))
|
||||
@@ -107,9 +108,9 @@ def _blob(W, H, cx, cy, r, a):
|
||||
|
||||
|
||||
def make_bg(W, H, accent, accent2, n=1):
|
||||
"""Light, premium background: near-white vertical wash + large soft brand-color
|
||||
blobs (blue + green) for depth. `n` = number of panels (for even blob spread)."""
|
||||
top, bottom = (251, 252, 254), (238, 242, 248)
|
||||
"""Dark premium background: near-black vertical wash (slate-900 -> near black)
|
||||
+ large soft brand-color glow blobs for subtle depth. `n` = number of panels."""
|
||||
top, bottom = (15, 23, 42), (2, 6, 23) # #0F172A -> #020617
|
||||
# fast vertical gradient: build a 1px-wide column then stretch to full width
|
||||
col = Image.new("RGB", (1, H))
|
||||
cp = col.load()
|
||||
@@ -120,11 +121,12 @@ def make_bg(W, H, accent, accent2, n=1):
|
||||
for i in range(n):
|
||||
col = accent if i % 2 == 0 else accent2
|
||||
cx = pw * (i + 0.5)
|
||||
# low-alpha glows so the dark stays premium, not garish
|
||||
base = Image.composite(Image.new("RGBA", (W, H), col + (255,)), base,
|
||||
_blob(W, H, cx, H * 0.20, pw * 0.42, 60).point(lambda p: int(p * 0.55)))
|
||||
_blob(W, H, cx, H * 0.16, pw * 0.5, 60).point(lambda p: int(p * 0.26)))
|
||||
col2 = accent2 if i % 2 == 0 else accent
|
||||
base = Image.composite(Image.new("RGBA", (W, H), col2 + (255,)), base,
|
||||
_blob(W, H, cx, H * 0.86, pw * 0.5, 45).point(lambda p: int(p * 0.5)))
|
||||
_blob(W, H, cx, H * 0.90, pw * 0.55, 45).point(lambda p: int(p * 0.22)))
|
||||
return base
|
||||
|
||||
|
||||
@@ -146,16 +148,23 @@ def draw_headline(canvas, text, cx, top_y, panel_w, color, max_lines=2):
|
||||
`max_lines` lines within the panel margins (never clips). Returns the y
|
||||
coordinate just below the headline block."""
|
||||
d = ImageDraw.Draw(canvas)
|
||||
maxw = int(panel_w * 0.82)
|
||||
size = int(panel_w * 0.074)
|
||||
# keep the block well inside the panel so nothing spills into the neighbour
|
||||
maxw = int(panel_w * 0.78)
|
||||
size = int(panel_w * 0.078)
|
||||
fnt = font(size, text)
|
||||
lines = wrap(d, text, fnt, maxw)
|
||||
while size > int(panel_w * 0.042):
|
||||
floor = int(panel_w * 0.034)
|
||||
while size > floor:
|
||||
fnt = font(size, text)
|
||||
lines = wrap(d, text, fnt, maxw)
|
||||
if len(lines) <= max_lines and max(d.textlength(ln, font=fnt) for ln in lines) <= maxw:
|
||||
break
|
||||
size -= 3
|
||||
# hard safety: shrink until every line truly fits, even below the floor
|
||||
while size > 14 and max(d.textlength(ln, font=fnt) for ln in lines) > maxw:
|
||||
size -= 2
|
||||
fnt = font(size, text)
|
||||
lines = wrap(d, text, fnt, maxw)
|
||||
asc, desc = fnt.getmetrics()
|
||||
lh = int((asc + desc) * 1.06)
|
||||
for i, ln in enumerate(lines):
|
||||
@@ -189,8 +198,8 @@ def main():
|
||||
accent, accent2 = hexrgb(a.accent), hexrgb(a.accent2)
|
||||
canvas = make_bg(W * n, H, accent, accent2, n=n)
|
||||
|
||||
text_color = (13, 22, 38)
|
||||
sub_color = (90, 102, 120)
|
||||
text_color = (248, 250, 252) # near-white on dark
|
||||
sub_color = (148, 163, 184) # slate-400
|
||||
dev_w = int(W * a.device_scale)
|
||||
angles = [-6, 5, -5, 6, -5, 5]
|
||||
y_off = [0.0, 0.05, 0.0, 0.05, 0.0, 0.05]
|
||||
@@ -205,13 +214,15 @@ def main():
|
||||
lines = wrap(d, sub, sf, int(W * 0.78))
|
||||
asc, desc = sf.getmetrics(); lh = int((asc + desc) * 1.05)
|
||||
sy = hy + int(H * 0.012)
|
||||
# positive/stat sublines (▲ ↑ +) pop in green; the rest stay muted
|
||||
sub_fill = accent2 if sub.strip()[:1] in "▲↑+" else sub_color
|
||||
for j, ln in enumerate(lines):
|
||||
w = d.textlength(ln, font=sf)
|
||||
d.text((panel_cx - w / 2, sy + j * lh), ln, font=sf, fill=sub_color)
|
||||
d.text((panel_cx - w / 2, sy + j * lh), ln, font=sf, fill=sub_fill)
|
||||
|
||||
shot = Image.open(path).convert("RGB")
|
||||
dev = build_device(shot, dev_w)
|
||||
dev = with_shadow(dev, blur=int(W * 0.04), alpha=0.34, off=(0, 46))
|
||||
dev = with_shadow(dev, blur=int(W * 0.05), alpha=0.30, off=(0, 18))
|
||||
ang = angles[i % len(angles)]
|
||||
dev = dev.rotate(ang, expand=True, resample=Image.BICUBIC)
|
||||
# nudge horizontally so adjacent devices overlap across the panel border
|
||||
|
||||
Reference in New Issue
Block a user