chore(docs/img): automate generating logo and preview images (#11)

This commit is contained in:
2025-10-18 23:50:15 +01:00
committed by GitHub
parent 587aa4f013
commit 25cadbf98e
10 changed files with 526 additions and 14 deletions

View File

@@ -1,22 +1,34 @@
RESOURCES_DIR := Resources RESOURCES_DIR := Resources
IMG_DIR := img
# Icon files and names # Icon files and names
ICON_FILES := $(shell find Icons/ -depth 1 -name '*.icon') ICON_FILES := $(sort $(shell find Icons/ -depth 1 -name '*.icon'))
ICON_NAMES := $(basename $(notdir $(ICON_FILES))) ICON_NAMES := $(sort $(basename $(notdir $(ICON_FILES))))
ICON_SOURCES := $(shell find $(ICON_FILES) -type f) ICON_SOURCES := $(sort $(shell find $(ICON_FILES) -type f))
ICON_VARIANTS := Default Dark
# *.icns files # *.icns files
ICNS_VARIANTS := \
Default \
Dark
ICNS_FILES := $(foreach \ ICNS_FILES := $(foreach \
icon,$(ICON_NAMES), \ icon,$(ICON_NAMES), \
$(foreach variant,$(ICNS_VARIANTS), \ $(foreach variant,$(ICON_VARIANTS), \
$(RESOURCES_DIR)/$(icon)-$(variant).icns \ $(RESOURCES_DIR)/$(icon)-$(variant).icns \
)) ) \
)
#
# Make all targets
#
.PHONY: all .PHONY: all
all: $(RESOURCES_DIR)/Assets.car $(ICNS_FILES) all: \
$(RESOURCES_DIR)/Assets.car \
$(ICNS_FILES) \
$(foreach icon,$(ICON_NAMES),$(IMG_DIR)/$(icon)-preview.png) \
$(IMG_DIR)/logo.png
#
# Resources/Assets.car generation
#
$(RESOURCES_DIR)/Assets.car: $(ICON_FILES) $(ICON_SOURCES) $(RESOURCES_DIR)/Assets.car: $(ICON_FILES) $(ICON_SOURCES)
mkdir -p "$(RESOURCES_DIR)" mkdir -p "$(RESOURCES_DIR)"
@@ -33,14 +45,53 @@ $(RESOURCES_DIR)/Assets.car: $(ICON_FILES) $(ICON_SOURCES)
--platform macosx \ --platform macosx \
--minimum-deployment-target 11.0 --minimum-deployment-target 11.0
# *.icns files generation rule #
# Resources/*.icns files generation rule
#
define ICNS_RULE define ICNS_RULE
$(RESOURCES_DIR)/%-$(1).icns: Icons/Exports/%-macOS-$(1)-1024x1024@2x.png $(RESOURCES_DIR)/%-$(1).icns: Icons/Exports/%-macOS-$(1)-1024x1024@2x.png
@mkdir -p $(RESOURCES_DIR) @mkdir -p $(RESOURCES_DIR)
bin/png2icns "$$<" "$$@" bin/png2icns "$$<" "$$@"
endef endef
$(foreach variant,$(ICNS_VARIANTS),$(eval $(call ICNS_RULE,$(variant)))) $(foreach variant,$(ICON_VARIANTS),$(eval $(call ICNS_RULE,$(variant))))
#
# img/*.png generation
#
img/logo.png: \
$(foreach icon,$(ICON_NAMES), \
Icons/Exports/$(icon)-iOS-Default-1024x1024@2x.png \
)
mkdir -p $(IMG_DIR)
bin/pngs2collage \
--output "$@" \
--max-width 512 \
--max-height 512 \
--gap 64 \
--border 0 \
--max-columns 99 \
$^
img/%-preview.png: \
$(foreach variant,$(ICON_VARIANTS), \
Icons/Exports/%-iOS-$(variant)-1024x1024@2x.png \
)
mkdir -p $(IMG_DIR)
bin/pngs2collage \
--output "$@" \
--max-width 512 \
--max-height 512 \
--gap 64 \
--border 0 \
--max-columns 99 \
$^
#
# Clean up
#
.PHONY: clean .PHONY: clean
clean: clean:

View File

@@ -62,21 +62,21 @@ be left alone.
EmacsLG1 simplifies and changes the outer shape of the default icon to comply EmacsLG1 simplifies and changes the outer shape of the default icon to comply
with the shape of Liquid Glass icons. with the shape of Liquid Glass icons.
![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/preview-lg1.png) ![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/EmacsLG1-preview.png)
### EmacsLG2 ### EmacsLG2
EmacsLG2 stays as close to the original round icon as possible, while giving it EmacsLG2 stays as close to the original round icon as possible, while giving it
that Liquid Glass flair. that Liquid Glass flair.
![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/preview-lg2.png) ![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/EmacsLG2-preview.png)
### EmacsLG3 ### EmacsLG3
EmacsLG3 is the biggest divergence from Emacs' default icon, and is inspired by EmacsLG3 is the biggest divergence from Emacs' default icon, and is inspired by
the changes Apple has made to some of its own icons in macOS 26. the changes Apple has made to some of its own icons in macOS 26.
![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/preview-lg3.png) ![EmacsLG1 Preview](https://raw.githubusercontent.com/jimeh/emacs-liquid-glass-icons/refs/heads/main/img/EmacsLG3preview.png)
## License ## License

461
bin/pngs2collage Executable file
View File

@@ -0,0 +1,461 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat >&2 << EOF
Usage: $(basename "$0") [OPTIONS] <input1.png> <input2.png> [input3.png ...]
Create a grid collage from multiple PNG images.
OPTIONS:
-o, --output <file> Output filename (default: collage.png)
--max-width <pixels> Maximum width for grid cells (default: 1024)
--max-height <pixels> Maximum height for grid cells (default: 1024)
--gap <pixels> Gap between images (default: 0)
--border <pixels> Border around entire collage (default: 0)
--max-columns <count> Maximum columns in grid (default: auto)
--dpi <value> DPI for output image (default: 72)
-h, --help Show this help message
ARGUMENTS:
At least 2 input PNG files are required.
EXAMPLES:
$(basename "$0") img1.png img2.png
$(basename "$0") -o output.png --gap 10 img1.png img2.png img3.png
$(basename "$0") --output result.png --max-columns 3 *.png
EOF
}
verify-png() {
local src="$1"
local fmt
fmt=$(sips -g format "$src" 2> /dev/null |
awk '/format/ {print tolower($2)}') || true
if [[ -z "$fmt" ]]; then
echo "Unable to determine image format with sips: $src" >&2
return 1
fi
if [[ "$fmt" != "png" ]]; then
echo "Input must be a PNG image: $src" >&2
return 1
fi
}
get-image-dimensions() {
local src="$1"
local w h
w=$(sips -g pixelWidth "$src" 2> /dev/null |
awk '/pixelWidth/ {print $2}') || true
h=$(sips -g pixelHeight "$src" 2> /dev/null |
awk '/pixelHeight/ {print $2}') || true
if [[ -z "$w" || -z "$h" ]]; then
echo "Failed to read image dimensions with sips: $src" >&2
return 1
fi
echo "$w $h"
}
calculate-cell-size() {
local max_w="$1"
local max_h="$2"
shift 2
local images=("$@")
local cell_w=0
local cell_h=0
local any_exceeds=0
for img in "${images[@]}"; do
local dims
if ! dims=$(get-image-dimensions "$img"); then
return 1
fi
local w h
read -r w h <<< "$dims"
if ((w > max_w || h > max_h)); then
any_exceeds=1
fi
if ((w > cell_w)); then
cell_w=$w
fi
if ((h > cell_h)); then
cell_h=$h
fi
done
# If any image exceeds max dimensions, use max dimensions as cell size
if ((any_exceeds)); then
if ((max_w > max_h)); then
echo "$max_w $max_w"
else
echo "$max_h $max_h"
fi
else
# Use largest dimension from all images to make square cells
if ((cell_w > cell_h)); then
echo "$cell_w $cell_w"
else
echo "$cell_h $cell_h"
fi
fi
}
calculate-padding() {
local row="$1"
local col="$2"
local total_rows="$3"
local total_cols="$4"
local gap="$5"
local border="$6"
local top bottom left right
local half_gap
# Calculate half gap (rounded for odd gaps)
half_gap=$(awk "BEGIN {print int($gap / 2)}")
# Top padding
if ((row == 0)); then
top=$border
else
top=$half_gap
fi
# Bottom padding
if ((row == total_rows - 1)); then
bottom=$border
else
bottom=$half_gap
fi
# Left padding
if ((col == 0)); then
left=$border
else
left=$half_gap
fi
# Right padding
if ((col == total_cols - 1)); then
right=$border
else
right=$half_gap
fi
echo "$top $bottom $left $right"
}
prepare-image() {
local src="$1"
local cell_size="$2"
local top="$3"
local bottom="$4"
local left="$5"
local right="$6"
local out="$7"
local tmp_resized tmp_padded
tmp_resized="${out}.tmp.resized.png"
tmp_padded="${out}.tmp.padded.png"
# Scale preserving aspect ratio; fit within cell size
sips -Z "$cell_size" "$src" --out "$tmp_resized" > /dev/null
# Pad to exact square dimensions centered on transparent background
sips --padToHeightWidth "$cell_size" "$cell_size" \
"$tmp_resized" --out "$tmp_padded" > /dev/null
# Apply asymmetric padding using ImageMagick
# Create a transparent canvas and composite the image at the correct position
local final_width final_height
final_width=$((cell_size + left + right))
final_height=$((cell_size + top + bottom))
# Create transparent canvas of final size, then composite the prepared
# image at position (left, top) to create the exact padding we want
if ! magick -size "${final_width}x${final_height}" xc:none \
"$tmp_padded" -geometry "+${left}+${top}" -composite \
"$out" 2> /dev/null; then
echo "Failed to apply padding to image: $src" >&2
rm -f "$tmp_resized" "$tmp_padded" || true
return 1
fi
rm -f "$tmp_resized" "$tmp_padded" || true
}
calculate-grid-dimensions() {
local count="$1"
local max_cols="$2"
local cols rows
if [[ "$max_cols" == "auto" ]]; then
# Calculate square-ish grid
cols=$(awk "BEGIN {print int(sqrt($count) + 0.999)}")
else
if ((max_cols > count)); then
cols=$count
else
cols=$max_cols
fi
fi
rows=$(awk "BEGIN {print int(($count + $cols - 1) / $cols)}")
echo "$cols $rows"
}
create-collage() {
local cols="$1"
local rows="$2"
local dpi="$3"
local out="$4"
shift 4
local images=("$@")
# Build ImageMagick montage command
# montage combines images in a grid with zero spacing
# (padding is already applied to individual images)
local tile_arg="${cols}x${rows}"
# Create collage with transparent background and zero spacing
if ! magick montage "${images[@]}" \
-tile "$tile_arg" \
-geometry "+0+0" \
-background "none" \
"$out" 2> /dev/null; then
echo "Failed to create collage with ImageMagick" >&2
return 1
fi
# Set DPI metadata
sips -s dpiHeight "$dpi" -s dpiWidth "$dpi" "$out" > /dev/null
}
cleanup() {
local path="$1"
if [[ -z "$path" || ! -d "$path" ]]; then
return 0
fi
# Remove generated PNGs inside the temp directory
find "$path" -type f -name '*.png' -delete 2> /dev/null || true
# Attempt to remove the temp directory if now empty
rmdir "$path" 2> /dev/null || true
}
parse-args() {
local max_width=1024
local max_height=1024
local gap=0
local border=0
local max_columns="auto"
local dpi=72
local output_file=""
local -a input_files=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
exit 0
;;
-o | --output)
output_file="$2"
shift 2
;;
--max-width)
max_width="$2"
shift 2
;;
--max-height)
max_height="$2"
shift 2
;;
--gap)
gap="$2"
shift 2
;;
--border)
border="$2"
shift 2
;;
--max-columns)
max_columns="$2"
shift 2
;;
--dpi)
dpi="$2"
shift 2
;;
-*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
*)
input_files+=("$1")
shift
;;
esac
done
# Export parsed values for main to access
export PARSED_MAX_WIDTH="$max_width"
export PARSED_MAX_HEIGHT="$max_height"
export PARSED_GAP="$gap"
export PARSED_BORDER="$border"
export PARSED_MAX_COLUMNS="$max_columns"
export PARSED_DPI="$dpi"
export PARSED_OUTPUT="$output_file"
export PARSED_INPUT_COUNT="${#input_files[@]}"
# Export input files as a string (will be parsed in main)
local i
for i in "${!input_files[@]}"; do
export "PARSED_INPUT_$i=${input_files[$i]}"
done
}
main() {
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
# Parse arguments
parse-args "$@"
# Reconstruct input files array from exports
local -a all_files=()
local i
for ((i = 0; i < PARSED_INPUT_COUNT; i++)); do
local varname="PARSED_INPUT_$i"
all_files+=("${!varname}")
done
# Determine output file and input files
local -a input_files=()
local output_file
if [[ -n "$PARSED_OUTPUT" ]]; then
# Explicit output was specified via --output/-o
output_file="$PARSED_OUTPUT"
input_files=("${all_files[@]}")
else
# No explicit output, use default or check last argument
output_file="collage.png"
if [[ ${#all_files[@]} -ge 2 ]]; then
local last_file="${all_files[-1]}"
# If last file doesn't exist and has .png extension, treat as output
if [[ ! -f "$last_file" && "$last_file" == *.png ]]; then
output_file="$last_file"
input_files=("${all_files[@]:0:$((${#all_files[@]} - 1))}")
else
input_files=("${all_files[@]}")
fi
else
input_files=("${all_files[@]}")
fi
fi
# Validate we have at least 2 input files
if [[ ${#input_files[@]} -lt 2 ]]; then
echo "Error: At least 2 input PNG files are required." >&2
usage
exit 1
fi
# Check for required commands
if ! command -v sips > /dev/null 2>&1; then
echo "This script requires 'sips' (macOS) but it wasn't found." >&2
exit 1
fi
if ! command -v magick > /dev/null 2>&1; then
echo "This script requires 'magick' (ImageMagick) but it wasn't found." >&2
exit 1
fi
# Verify all input files exist and are PNGs
for img in "${input_files[@]}"; do
if [[ ! -f "$img" ]]; then
echo "Input file not found: $img" >&2
exit 1
fi
if ! verify-png "$img"; then
exit 1
fi
done
# Create output directory if needed
local outdir
outdir="$(dirname "$output_file")"
mkdir -p "$outdir"
# Create temporary working directory
tmp_root="$(mktemp -d 2> /dev/null || mktemp -d -t pngs2collage)"
trap 'cleanup "$tmp_root"' EXIT
# Calculate grid dimensions first
local grid_dims
grid_dims=$(calculate-grid-dimensions "${#input_files[@]}" \
"$PARSED_MAX_COLUMNS")
local cols rows
read -r cols rows <<< "$grid_dims"
# Calculate cell size
local cell_dims
if ! cell_dims=$(calculate-cell-size "$PARSED_MAX_WIDTH" \
"$PARSED_MAX_HEIGHT" "${input_files[@]}"); then
exit 1
fi
local cell_size
read -r cell_size _ <<< "$cell_dims"
# Prepare all images with position-specific padding
local -a prepared_images=()
local idx=0
for img in "${input_files[@]}"; do
# Calculate grid position (row, col) from index
local row col
row=$((idx / cols))
col=$((idx % cols))
# Calculate padding for this position
local padding
padding=$(calculate-padding "$row" "$col" "$rows" "$cols" \
"$PARSED_GAP" "$PARSED_BORDER")
local top bottom left right
read -r top bottom left right <<< "$padding"
# Prepare image with position-specific padding
local prepared="${tmp_root}/prepared_${idx}.png"
if ! prepare-image "$img" "$cell_size" "$top" "$bottom" "$left" \
"$right" "$prepared"; then
echo "Failed to prepare image: $img" >&2
exit 1
fi
prepared_images+=("$prepared")
idx=$((idx + 1))
done
# Create the collage
if ! create-collage "$cols" "$rows" "$PARSED_DPI" "$output_file" \
"${prepared_images[@]}"; then
exit 1
fi
echo "Done. Collage written to: $output_file"
}
main "$@"

BIN
img/EmacsLG1-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

BIN
img/EmacsLG2-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

BIN
img/EmacsLG3-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB