Files
nur-muslim-companion/scripts/compose-mockups.py
T

172 lines
5.6 KiB
Python

"""Composite app screenshots into Apple App Store mockups with phone frames."""
from PIL import Image, ImageDraw, ImageFont
import os, math, textwrap
SCREENSHOTS_DIR = '/tmp/nur-falah-screenshots'
OUTPUT_DIR = '/tmp/nur-falah-mockups'
# Device frame dimensions (iPhone 14 Pro proportions)
FRAME_W = 720
FRAME_H = 1480
BORDER_RADIUS = 80
BEZEL_COLOR = '#1A1A1A'
SCREEN_X = 24
SCREEN_Y = 24
SCREEN_W = FRAME_W - 48
SCREEN_H = FRAME_H - 48
SCREEN_RADIUS = 60
# Canvas for 6.7" display (1290x2796 logical @ 3x = mockup at 1x smooth)
CANVAS_W = 1290
CANVAS_H = 2796
FONTS_DIRS = ['/System/Library/Fonts', '/System/Library/Fonts/Supplemental']
def find_font(name, size):
for fd in FONTS_DIRS:
for root, dirs, files in os.walk(fd):
for f in files:
if name.lower() in f.lower() and f.endswith(('.ttf', '.ttc')):
try:
return ImageFont.truetype(os.path.join(root, f), size)
except:
pass
return ImageFont.load_default()
def draw_phone_frame(draw, x, y, w, h):
"""Draw an iPhone-like bezel frame."""
# Outer bezel (rounded rect)
draw.rounded_rectangle(
[x, y, x + w, y + h],
radius=BORDER_RADIUS,
fill=BEZEL_COLOR,
)
# Screen area (darker)
draw.rounded_rectangle(
[x + SCREEN_X, y + SCREEN_Y, x + w - SCREEN_X, y + h - SCREEN_Y],
radius=SCREEN_RADIUS,
fill='#000000',
)
# Dynamic Island (pill shape at top center)
island_w, island_h = 126, 36
island_x = x + w//2 - island_w//2
island_y = y + SCREEN_Y + 14
draw.rounded_rectangle(
[island_x, island_y, island_x + island_w, island_y + island_h],
radius=18,
fill='#000000',
)
# Home indicator bar
bar_w, bar_h = 134, 5
bar_x = x + w//2 - bar_w//2
bar_y = y + h - SCREEN_Y - 12
draw.rounded_rectangle(
[bar_x, bar_y, bar_x + bar_w, bar_y + bar_h],
radius=3,
fill='#333333',
)
def make_mockup(screenshot_path, output_path, label):
"""Create a single mockup: screenshot composited into phone frame."""
# Load screenshot
screenshot = Image.open(screenshot_path).convert('RGB')
sw, sh = screenshot.size
# Target screen area inside the phone frame
inner_w = FRAME_W - 2 * SCREEN_X
inner_h = FRAME_H - 2 * SCREEN_Y
# Scale screenshot to fit while maintaining aspect ratio
scale = min(inner_w / sw, inner_h / sh)
new_w = int(sw * scale)
new_h = int(sh * scale)
screenshot_resized = screenshot.resize((new_w, new_h), Image.LANCZOS)
# Create phone frame on transparent background
frame = Image.new('RGBA', (FRAME_W, FRAME_H), (0, 0, 0, 0))
draw = ImageDraw.Draw(frame)
draw_phone_frame(draw, 0, 0, FRAME_W, FRAME_H)
# Paste screenshot centered in the screen area
paste_x = SCREEN_X + (inner_w - new_w) // 2
paste_y = SCREEN_Y + (inner_h - new_h) // 2
frame.paste(screenshot_resized, (paste_x, paste_y))
# Create final canvas with gradient background
canvas = Image.new('RGB', (CANVAS_W, CANVAS_H), '#070A0D')
# Subtle radial gradient
bg = ImageDraw.Draw(canvas)
for i in range(CANVAS_H):
# Vertical gradient: dark at edges, slightly lighter center
ratio = abs(i / CANVAS_H - 0.5) * 2
r = int(7 + (1 - ratio) * 3)
g = int(10 + (1 - ratio) * 4)
b = int(13 + (1 - ratio) * 4)
bg.line([(0, i), (CANVAS_W, i)], fill=(r, g, b))
# Position phone frame centered
phone_x = (CANVAS_W - FRAME_W) // 2
phone_y = 120
canvas.paste(frame, (phone_x, phone_y), frame)
# Add label text below the phone
draw2 = ImageDraw.Draw(canvas)
font = find_font('SFNS', 32) or find_font('Helvetica', 32) or ImageFont.load_default()
# Wrap label text
words = label.split()
lines = []
current_line = ''
for word in words:
test = current_line + ' ' + word if current_line else word
if len(test) * 20 < CANVAS_W - 200: # rough estimate
current_line = test
else:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
y_offset = phone_y + FRAME_H + 60
for line in lines:
bbox = draw2.textbbox((0, 0), line, font=font)
tw = bbox[2] - bbox[0]
draw2.text(
((CANVAS_W - tw) // 2, y_offset),
line,
fill='#C9A84C',
font=font,
)
y_offset += 48
# Save
canvas.save(output_path, 'PNG', quality=95)
print(f' ✅ {output_path} ({canvas.size})')
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
mockups = [
('prayer.png', '1-6.7-prayer-times', 'Accurate Prayer Times with\n5 Calculation Methods'),
('qibla.png', '2-6.7-qibla-finder', 'Find the Qibla from\nAnywhere in the World'),
('quran.png', '3-6.7-holy-quran', 'The Complete Holy Quran —\nAll 114 Surahs'),
('names99.png', '4-6.7-99-names', 'Study and Reflect on the\n99 Beautiful Names of Allah'),
('tasbih.png', '5-6.7-tasbih-counter', 'Digital Tasbih —\nTap to Remember Allah'),
('hijri.png', '6-6.7-hijri-calendar', 'Stay Connected to the\nIslamic Calendar'),
]
for filename, output_name, label in mockups:
src = os.path.join(SCREENSHOTS_DIR, filename)
dst = os.path.join(OUTPUT_DIR, f'{output_name}.png')
if os.path.exists(src):
make_mockup(src, dst, label)
else:
print(f' ⚠️ Missing: {src}')
print(f'\n🎉 All mockups saved to {OUTPUT_DIR}/')
if __name__ == '__main__':
main()