automated screenshot generation
This commit is contained in:
276
frame_screens.sh
Executable file
276
frame_screens.sh
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
FONT_COLOR="#3C3C3C" # color for light text
|
||||
FONT_BOLD_COLOR="#B51700" # color for bold texto pipefail
|
||||
|
||||
# Inputs
|
||||
SRC_ROOT="${1:-Shots/Screenshots}" # root folder with lang subfolders (de/, fr/, en/…)
|
||||
BG_IMAGE="${2:-Shots/frame-bg.png}" # background image (portrait)
|
||||
OUT_ROOT="${3:-Shots/Framed}" # output folder
|
||||
FONT="./Shots/Fonts/Oswald-Light.ttf" # font for title text
|
||||
FONT_BOLD="./Shots/Fonts/Oswald-SemiBold.ttf" # font for *emphasized* text
|
||||
|
||||
# Tweakables
|
||||
CORNER_RADIUS="auto" # corner radius; "auto" picks a good value based on width
|
||||
INSET=2 # inset (px) to shave off simulator’s black edge pixels
|
||||
SHADOW_OPACITY=60 # 0–100
|
||||
SHADOW_BLUR=20 # blur radius
|
||||
SHADOW_OFFSET_X=0 # px
|
||||
SHADOW_OFFSET_Y=40 # px
|
||||
CANVAS_MARGIN=190 # margin around the device on the background, px
|
||||
TITLE_MARGIN=120 # margin above the device for title text, px
|
||||
|
||||
mkdir -p "$OUT_ROOT"
|
||||
|
||||
# Function to render mixed-font text (light + semi-bold for *text*)
|
||||
render_mixed_font_title() {
|
||||
local canvas="$1"
|
||||
local title_text="$2"
|
||||
local title_y="$3"
|
||||
local output="$4"
|
||||
|
||||
if [[ "$title_text" == *"*"* ]]; then
|
||||
# Get canvas dimensions
|
||||
read -r canvas_w canvas_h <<<"$(identify -format "%w %h" "$canvas")"
|
||||
|
||||
# Create a temporary image to measure and render text parts
|
||||
local temp_img
|
||||
temp_img="$(mktemp /tmp/text_temp.XXXXXX_$$.png)"
|
||||
cp "$canvas" "$temp_img"
|
||||
|
||||
# Parse text into segments with their font types
|
||||
declare -a text_segments=()
|
||||
declare -a font_types=()
|
||||
|
||||
local current_text=""
|
||||
local in_bold=false
|
||||
local i=0
|
||||
|
||||
while [ $i -lt ${#title_text} ]; do
|
||||
local char="${title_text:$i:1}"
|
||||
|
||||
if [[ "$char" == "*" ]]; then
|
||||
# Save current segment (even if empty, to handle cases like "**")
|
||||
text_segments+=("$current_text")
|
||||
if [[ "$in_bold" == true ]]; then
|
||||
font_types+=("bold")
|
||||
else
|
||||
font_types+=("light")
|
||||
fi
|
||||
current_text=""
|
||||
# Toggle bold state
|
||||
if [[ "$in_bold" == true ]]; then
|
||||
in_bold=false
|
||||
else
|
||||
in_bold=true
|
||||
fi
|
||||
else
|
||||
current_text+="$char"
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# Handle remaining text
|
||||
if [[ -n "$current_text" ]]; then
|
||||
text_segments+=("$current_text")
|
||||
if [[ "$in_bold" == true ]]; then
|
||||
font_types+=("bold")
|
||||
else
|
||||
font_types+=("light")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Debug: print segments (remove this later)
|
||||
echo "DEBUG: Text segments:"
|
||||
local debug_i=0
|
||||
while [ $debug_i -lt ${#text_segments[@]} ]; do
|
||||
echo " [$debug_i]: '${text_segments[$debug_i]}' (${font_types[$debug_i]})"
|
||||
debug_i=$((debug_i + 1))
|
||||
done
|
||||
|
||||
# Calculate total width
|
||||
local total_width=0
|
||||
local j=0
|
||||
while [ $j -lt ${#text_segments[@]} ]; do
|
||||
local segment="${text_segments[$j]}"
|
||||
local font_type="${font_types[$j]}"
|
||||
|
||||
# Skip empty segments for width calculation
|
||||
if [[ -n "$segment" ]]; then
|
||||
local font_for_measurement="$FONT"
|
||||
if [[ "$font_type" == "bold" ]]; then
|
||||
font_for_measurement="$FONT_BOLD"
|
||||
fi
|
||||
|
||||
# Replace leading/trailing spaces with non-breaking spaces for measurement
|
||||
local segment_for_measurement="$segment"
|
||||
segment_for_measurement="${segment_for_measurement/#/ }" # leading space
|
||||
segment_for_measurement="${segment_for_measurement/%/ }" # trailing space
|
||||
|
||||
local part_width=$(magick -font "$font_for_measurement" -pointsize 148 -size x label:"$segment_for_measurement" -format "%w" info:)
|
||||
total_width=$((total_width + part_width))
|
||||
fi
|
||||
j=$((j + 1))
|
||||
done
|
||||
|
||||
# Calculate starting X position to center the entire text
|
||||
local start_x=$(( (canvas_w - total_width) / 2 ))
|
||||
|
||||
# Render each segment
|
||||
local x_offset=0
|
||||
j=0
|
||||
while [ $j -lt ${#text_segments[@]} ]; do
|
||||
local segment="${text_segments[$j]}"
|
||||
local font_type="${font_types[$j]}"
|
||||
|
||||
# Skip empty segments for rendering
|
||||
if [[ -n "$segment" ]]; then
|
||||
local font_to_use="$FONT"
|
||||
local color_to_use="$FONT_COLOR"
|
||||
if [[ "$font_type" == "bold" ]]; then
|
||||
font_to_use="$FONT_BOLD"
|
||||
color_to_use="$FONT_BOLD_COLOR"
|
||||
fi
|
||||
|
||||
# Replace leading/trailing spaces with non-breaking spaces for rendering
|
||||
local segment_for_rendering="$segment"
|
||||
segment_for_rendering="${segment_for_rendering/#/ }" # leading space
|
||||
segment_for_rendering="${segment_for_rendering/%/ }" # trailing space
|
||||
|
||||
magick "$temp_img" \
|
||||
-font "$font_to_use" -pointsize 148 -fill "$color_to_use" \
|
||||
-gravity northwest -annotate "+$((start_x + x_offset))+${title_y}" "$segment_for_rendering" \
|
||||
"$temp_img"
|
||||
|
||||
# Calculate width of rendered text for next position (use same processed segment)
|
||||
local text_width=$(magick -font "$font_to_use" -pointsize 148 -size x label:"$segment_for_rendering" -format "%w" info:)
|
||||
x_offset=$((x_offset + text_width))
|
||||
fi
|
||||
j=$((j + 1))
|
||||
done
|
||||
|
||||
cp "$temp_img" "$output"
|
||||
rm -f "$temp_img"
|
||||
else
|
||||
# No asterisks, simple rendering
|
||||
magick "$canvas" \
|
||||
-font "$FONT" -pointsize 148 -fill "$FONT_COLOR" \
|
||||
-gravity north -annotate "+0+${title_y}" "$title_text" \
|
||||
"$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get title from config file
|
||||
get_title() {
|
||||
local lang="$1"
|
||||
local screenshot_name="$2"
|
||||
local config_file="./Shots/Titles/${lang}.conf"
|
||||
|
||||
# Extract view name from filename format: 03-LoadEditorView_0_5EE662BD-84C7-41AC-806E-EB8C7340A037.png
|
||||
# Remove .png extension, then extract the part after the first dash and before the first underscore
|
||||
local base_name=$(basename "$screenshot_name" .png)
|
||||
# Remove leading number and dash (e.g., "03-")
|
||||
base_name=${base_name#*-}
|
||||
# Remove everything from the first underscore onwards (e.g., "_0_5EE662BD...")
|
||||
base_name=${base_name%%_*}
|
||||
|
||||
# Try to find title in config file
|
||||
if [[ -f "$config_file" ]]; then
|
||||
local title=$(grep "^${base_name}=" "$config_file" 2>/dev/null | cut -d'=' -f2-)
|
||||
if [[ -n "$title" ]]; then
|
||||
echo "$title"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback to default title
|
||||
echo "***NOT SET***"
|
||||
}
|
||||
|
||||
# Function to frame one screenshot
|
||||
frame_one () {
|
||||
local in="$1" # input screenshot (e.g., 1320x2868)
|
||||
local out="$2" # output image
|
||||
local bg="$3"
|
||||
local lang="$4" # language code (e.g., "de", "en")
|
||||
local screenshot_name="$5" # screenshot filename
|
||||
|
||||
# Read sizes
|
||||
read -r W H <<<"$(identify -format "%w %h" "$in")"
|
||||
|
||||
# Determine corner radius
|
||||
local R
|
||||
if [[ "$CORNER_RADIUS" == "auto" ]]; then
|
||||
# Heuristic: ~1/12 of width works well for iPhone 6.9" (≈110px for 1320px width)
|
||||
R=$(( W / 12 ))
|
||||
else
|
||||
R=$CORNER_RADIUS
|
||||
fi
|
||||
|
||||
# Create rounded-corner mask the same size as the screenshot
|
||||
local mask
|
||||
mask="$(mktemp /tmp/mask.XXXXXX_$$.png)"
|
||||
magick -size "${W}x${H}" xc:black \
|
||||
-fill white -draw "roundrectangle ${INSET},${INSET},$((W-1-INSET)),$((H-1-INSET)),$R,$R" \
|
||||
"$mask"
|
||||
|
||||
# Apply rounded corners + make a soft drop shadow
|
||||
# 1) Rounded PNG
|
||||
local rounded
|
||||
rounded="$(mktemp /tmp/rounded.XXXXXX_$$.png)"
|
||||
magick "$in" -alpha set "$mask" -compose copyopacity -composite "$rounded"
|
||||
|
||||
# 2) Shadow from rounded image
|
||||
local shadow
|
||||
shadow="$(mktemp /tmp/shadow.XXXXXX_$$.png)"
|
||||
magick "$rounded" \
|
||||
\( +clone -background black -shadow ${SHADOW_OPACITY}x${SHADOW_BLUR}+${SHADOW_OFFSET_X}+${SHADOW_OFFSET_Y} \) \
|
||||
+swap -background none -layers merge +repage "$shadow"
|
||||
|
||||
# Compose on the background, centered
|
||||
# First, scale background to be at least screenshot+margin in both dimensions
|
||||
read -r BW BH <<<"$(identify -format "%w %h" "$bg")"
|
||||
local minW=$((W + 2*CANVAS_MARGIN))
|
||||
local minH=$((H + 2*CANVAS_MARGIN + TITLE_MARGIN))
|
||||
local canvas
|
||||
canvas="$(mktemp /tmp/canvas.XXXXXX_$$.png)"
|
||||
magick "$bg" -resize "${minW}x${minH}^" -gravity center -extent "${minW}x${minH}" "$canvas"
|
||||
|
||||
# Add title text above the screenshot
|
||||
local title_text=$(get_title "$lang" "$screenshot_name")
|
||||
local with_title
|
||||
with_title="$(mktemp /tmp/with_title.XXXXXX_$$.png)"
|
||||
|
||||
# Calculate title position (center horizontally, positioned above the screenshot)
|
||||
local title_y=$((TITLE_MARGIN - 10)) # 10px from top of title margin
|
||||
|
||||
# Render title with mixed fonts
|
||||
render_mixed_font_title "$canvas" "$title_text" "$title_y" "$with_title"
|
||||
|
||||
# Now place shadow (which already includes the rounded image) positioned below the title
|
||||
# Calculate the vertical offset to center the screenshot in the remaining space below the title
|
||||
local screenshot_offset=$((TITLE_MARGIN*2))
|
||||
local temp_result
|
||||
temp_result="$(mktemp /tmp/temp_result.XXXXXX_$$.png)"
|
||||
magick "$with_title" "$shadow" -gravity center -geometry "+0+${screenshot_offset}" -compose over -composite "$temp_result"
|
||||
|
||||
# Final step: scale to exact dimensions 1320 × 2868px
|
||||
magick "$temp_result" -resize "1320x2868^" -gravity center -extent "1320x2868" "$out"
|
||||
|
||||
rm -f "$mask" "$rounded" "$shadow" "$canvas" "$with_title" "$temp_result"
|
||||
}
|
||||
|
||||
# Process all screenshots in SRC_ROOT/*/*.png
|
||||
shopt -s nullglob
|
||||
for langdir in "$SRC_ROOT"/*; do
|
||||
[[ -d "$langdir" ]] || continue
|
||||
rel="$(basename "$langdir")"
|
||||
mkdir -p "$OUT_ROOT/$rel"
|
||||
for shot in "$langdir"/*.png; do
|
||||
base="$(basename "$shot")"
|
||||
frame_one "$shot" "$OUT_ROOT/$rel/$base" "$BG_IMAGE" "$rel" "$base"
|
||||
echo "Framed: $rel/$base"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Done. Framed images in: $OUT_ROOT/"
|
||||
Reference in New Issue
Block a user