Skip to content

Instantly share code, notes, and snippets.

@adamelliotfields
Last active April 5, 2024 17:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamelliotfields/d41a7daca4ab35b7d4c05821f827111e to your computer and use it in GitHub Desktop.
Save adamelliotfields/d41a7daca4ab35b7d4c05821f827111e to your computer and use it in GitHub Desktop.
Favicon and Webmanifest Script
#!/usr/bin/env bash
set -euo pipefail
# Generates favicons and a webmanifest from a single image
# https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs
#
# Usage:
# favicon.sh <file> [dir] [flags]
#
# Args:
# file: the input image file
# dir: the output directory (default: '.')
#
# Flags:
# -c,--color: the background color for the icons (default: #020617)
# -s,--sizes: comma-separated list of sizes for the icons (default: 192px)
# --no-manifest: do not generate a webmanifest
function favicon() {
local usage="usage: favicon.sh <file> [dir] [flags]"
local color="#020617" # slate-950
local sizes='192'
local out_dir="."
local in_file=""
local no_manifest=false
local has_out_dir=false
# webmanifest
# could make all these flags if we wanted
local name='Example App'
local short_name='Example'
local description='A website with a description.'
local background_color="$color"
local theme_color="$color"
local display='minimal-ui'
local start_url='/'
local scope='/'
local icons='[]'
# no args
if [[ $# -eq 0 ]] ; then
echo "$usage"
return 1
fi
# parse args
while [[ $# -gt 0 ]] ; do
case "$1" in
-c=*|--color=*)
color="${1#*=}"
shift ;;
-c|--color)
color="$2"
shift
shift ;;
-s=*|--sizes=*)
sizes="${1#*=}"
shift ;;
-s|--sizes)
sizes="$2"
shift
shift ;;
--no-manifest)
no_manifest=true
shift ;;
*)
if [[ -z $in_file ]] ; then
in_file="$1"
elif [[ $has_out_dir == false ]] ; then
out_dir="$1"
has_out_dir=true
fi
shift ;;
esac
done
# validate in_file
if [[ ! -f $in_file ]] ; then
echo "favicon: file ${in_file} does not exist"
return 1
fi
# validate out_dir
if [[ ! -d $out_dir ]] ; then
mkdir -p "$out_dir"
fi
# build webmanifest
# shellcheck disable=SC2155
local webmanifest=$(cat <<EOF
{
"name": "$name",
"short_name": "$short_name",
"description": "$description",
"background_color": "$background_color",
"theme_color": "$theme_color",
"display": "$display",
"start_url": "$start_url",
"scope": "$scope",
"icons": $icons
}
EOF
)
# generate favicon
[[ -e "${out_dir}/favicon.ico" ]] && rm "${out_dir}/favicon.ico"
magick "$in_file" -resize "32x32" "${out_dir}/favicon.ico"
# generate apple-touch-icon (with 20px padding)
[[ -e "${out_dir}/apple-touch-icon.png" ]] && rm "${out_dir}/apple-touch-icon.png"
magick "$in_file" -resize "140x140" -gravity center -background "$color" -extent "180x180" -flatten "$out_dir/apple-touch-icon.png"
# generate icons
for size in $(echo "$sizes" | tr ',' '\n') ; do
# resize to add 20px padding
local resize=$((size - 40))
[[ -e "${out_dir}/icon-${size}.png" ]] && rm "${out_dir}/icon-${size}.png"
magick "$in_file" \
-resize "${resize}x${resize}" \
-gravity center \
-background "$color" \
-extent "${size}x${size}" \
-flatten "${out_dir}/icon-${size}.png"
# append each size to the webmanifest
webmanifest=$(echo "$webmanifest" | jq -c --arg size "$size" '.icons += [{ "src": "/icon-\($size).png", "type": "image/png", "sizes": "\($size)x\($size)" }]')
done
# unless the flag was set, write the webmanifest to the out_dir
if [[ $no_manifest == false ]] ; then
[[ -e "${out_dir}/manifest.webmanifest" ]] && rm "${out_dir}/manifest.webmanifest"
echo "$webmanifest" | tee "${out_dir}/manifest.webmanifest" >/dev/null
fi
} ; favicon "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment