#!/usr/bin/env bash
# Regenerates the tvOS Brand Assets from assets/icon*.svg — PNGs *and* every
# Contents.json, so the whole TVBrandAssets.brandassets is reproducible from one run:
# ./scripts/gen-tvos-icons.sh
# App icons are 2-layer parallax stacks (BACK = brand gradient, FRONT = rings motif).
# The home-screen icon ships @1x + @2x; the App Store icon is @1x; Top Shelf images @1x + @2x.
# Requires rsvg-convert (brew install librsvg).
#
# NB: the PNGs are the canonical, byte-reproducible artifacts. The emitted Contents.json
# get re-formatted by biome on commit (lefthook), so re-running this produces whitespace-
# only diffs on the JSON — that is expected and cosmetic (semantics + PNGs are identical).
set -euo pipefail
cd "$(dirname "$0")/.."
ICON="assets/icon.svg"
BGSVG="assets/icon-tv-bg.svg"
MOTIF="assets/icon-tv-motif.svg"
BG="#050608" # icon's outer gradient stop → seamless top-shelf margins
BA="apple/PhysarumApp/Resources/Assets.xcassets/TVBrandAssets.brandassets"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
command -v rsvg-convert >/dev/null || { echo "missing rsvg-convert (brew install librsvg)" >&2; exit 1; }
for f in "$ICON" "$BGSVG" "$MOTIF"; do [ -f "$f" ] || { echo "missing $f" >&2; exit 1; }; done
xc() { printf '%s\n' '{ "info" : { "author" : "xcode", "version" : 1 } }' > "$1"; } # bare Contents.json
# motif (square, transparent) centered on a transparent W×H canvas → app-icon FRONT layer
front_png() { # <W> <H> <out>
local W="$1" H="$2" out="$3" x=$(( ($1 - $2) / 2 ))
rsvg-convert -w "$H" -h "$H" "$MOTIF" -o "$TMP/motif-$H.png"
cat > "$TMP/front-$H.svg" <<EOF
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="$W" height="$H">
<image x="$x" y="0" width="$H" height="$H" xlink:href="motif-$H.png"/>
</svg>
EOF
rsvg-convert -w "$W" -h "$H" "$TMP/front-$H.svg" -o "$out"
}
rm -rf "$BA"; mkdir -p "$BA"
cat > "$BA/Contents.json" <<'EOF'
{
"assets" : [
{ "filename" : "App Icon - App Store.imagestack", "idiom" : "tv", "role" : "primary-app-icon", "size" : "1280x768" },
{ "filename" : "App Icon.imagestack", "idiom" : "tv", "role" : "primary-app-icon", "size" : "400x240" },
{ "filename" : "Top Shelf Image Wide.imageset", "idiom" : "tv", "role" : "top-shelf-image-wide", "size" : "2320x720" },
{ "filename" : "Top Shelf Image.imageset", "idiom" : "tv", "role" : "top-shelf-image", "size" : "1920x720" }
],
"info" : { "author" : "xcode", "version" : 1 }
}
EOF
# a 2-layer app-icon stack. <dir> <W> <H> <2x:0|1>
stack() {
local dir="$1" W="$2" H="$3" twoX="${4:-0}"
local back="$dir/Back.imagestacklayer" front="$dir/Front.imagestacklayer"
mkdir -p "$back/Content.imageset" "$front/Content.imageset"
cat > "$dir/Contents.json" <<'EOF'
{
"info" : { "author" : "xcode", "version" : 1 },
"layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ]
}
EOF
xc "$back/Contents.json"; xc "$front/Contents.json"
rsvg-convert -w "$W" -h "$H" "$BGSVG" -o "$back/Content.imageset/back.png"
front_png "$W" "$H" "$front/Content.imageset/front.png"
if [ "$twoX" = "1" ]; then
rsvg-convert -w "$((W*2))" -h "$((H*2))" "$BGSVG" -o "$back/Content.imageset/back@2x.png"
front_png "$((W*2))" "$((H*2))" "$front/Content.imageset/front@2x.png"
printf '%s\n' '{ "images" : [ { "idiom" : "tv", "filename" : "back.png", "scale" : "1x" }, { "idiom" : "tv", "filename" : "back@2x.png", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } }' > "$back/Content.imageset/Contents.json"
printf '%s\n' '{ "images" : [ { "idiom" : "tv", "filename" : "front.png", "scale" : "1x" }, { "idiom" : "tv", "filename" : "front@2x.png", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } }' > "$front/Content.imageset/Contents.json"
else
printf '%s\n' '{ "images" : [ { "idiom" : "tv", "filename" : "back.png", "scale" : "1x" } ], "info" : { "author" : "xcode", "version" : 1 } }' > "$back/Content.imageset/Contents.json"
printf '%s\n' '{ "images" : [ { "idiom" : "tv", "filename" : "front.png", "scale" : "1x" } ], "info" : { "author" : "xcode", "version" : 1 } }' > "$front/Content.imageset/Contents.json"
fi
}
# a flat Top Shelf imageset (@1x + @2x), full icon centered on the brand bg
topshelf() { # <setdir> <basename> <W1> <H1>
local dir="$1" base="$2" W="$3" H="$4"
mkdir -p "$dir"
printf '%s\n' "{ \"images\" : [ { \"idiom\" : \"tv\", \"filename\" : \"$base-${W}x${H}.png\", \"scale\" : \"1x\" }, { \"idiom\" : \"tv\", \"filename\" : \"$base-$((W*2))x$((H*2)).png\", \"scale\" : \"2x\" } ], \"info\" : { \"author\" : \"xcode\", \"version\" : 1 } }" > "$dir/Contents.json"
for s in 1 2; do
local w=$((W*s)) h=$((H*s)) x; x=$(( (W*s - H*s) / 2 ))
rsvg-convert -w "$h" -h "$h" "$ICON" -o "$TMP/full-$h.png"
cat > "$TMP/ts.svg" <<EOF
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="$w" height="$h">
<rect width="$w" height="$h" fill="$BG"/>
<image x="$x" y="0" width="$h" height="$h" xlink:href="full-$h.png"/>
</svg>
EOF
rsvg-convert -w "$w" -h "$h" "$TMP/ts.svg" -o "$dir/$base-${w}x${h}.png"
done
}
stack "$BA/App Icon.imagestack" 400 240 1 # home-screen icon: @1x + @2x
stack "$BA/App Icon - App Store.imagestack" 1280 768 0 # App Store icon: @1x
topshelf "$BA/Top Shelf Image.imageset" topshelf 1920 720
topshelf "$BA/Top Shelf Image Wide.imageset" topshelfwide 2320 720
echo "tvOS brand assets rebuilt:"
find "$BA" -name "*.png" | sort