App Store submission assets: privacy page, screenshots, DOCX listing
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user