|
#!/usr/bin/env bash |
|
|
|
# Compose images into atlas |
|
atlas_cache_compose() |
|
{ |
|
local W H |
|
W=$(< "$CACHE/width") |
|
H=$(< "$CACHE/height") |
|
|
|
# MIN_SIZE is not a misspelling |
|
# shellcheck disable=SC2153 |
|
(( W < MIN_SIZE )) && W=$MIN_SIZE |
|
# shellcheck disable=SC2153 |
|
(( H < MIN_SIZE )) && H=$MIN_SIZE |
|
|
|
# don't quote $CACHE/args because this should be word splitted |
|
# shellcheck disable=SC2046 |
|
[ -f "$CACHE/args" ] && convert \ |
|
-size "${W}x${H}" \ |
|
xc:transparent \ |
|
$(< "$CACHE/args") \ |
|
-strip \ |
|
"${ATLAS:-atlas.png}" |
|
} |
|
|
|
# Print JSON and build arguments for convert |
|
atlas_cache_summarize() |
|
{ |
|
echo "var atlas={" |
|
|
|
find "$CACHE" -type f -name image | while read -r |
|
do |
|
local X Y W H FILE |
|
read -r X Y W H FILE < "$REPLY" |
|
|
|
echo "$FILE -geometry +$X+$Y -composite" >> "$CACHE/args" |
|
|
|
if (( MARGIN > 0 )) |
|
then |
|
(( X += MARGIN )) |
|
(( Y += MARGIN )) |
|
(( W -= MARGIN*2 )) |
|
(( H -= MARGIN*2 )) |
|
fi |
|
|
|
local NAME=${FILE##*/} |
|
echo "${NAME%.*}:{x:$X,y:$Y,w:$W,h:$H}," |
|
done |
|
|
|
echo "};" |
|
} |
|
|
|
# Insert image, packing algorithm from |
|
# http://www.blackpawn.com/texts/lightmaps/default.html |
|
# |
|
# @param 1 - image width |
|
# @param 2 - image height |
|
# @param 3 - image file |
|
atlas_cache_insert() |
|
{ |
|
[ -f "$CACHE/candidates" ] || return 1 |
|
|
|
local INDEX TARGET |
|
# INDEX is unused but required to consume the first argument |
|
# shellcheck disable=SC2034 |
|
while read -r INDEX TARGET |
|
do |
|
break |
|
done <<< "$(sort "$CACHE/candidates")" |
|
|
|
[ -f "$TARGET/rect" ] || return 1 |
|
|
|
local X Y W H |
|
read -r X Y W H < "$TARGET/rect" |
|
|
|
if (( W < $1 )) || (( H < $2 )) |
|
then |
|
return 1 |
|
fi |
|
|
|
mkdir "$TARGET/child0" "$TARGET/child1" || return $? |
|
|
|
local RW=$(( W-$1 )) |
|
local RH=$(( H-$2 )) |
|
|
|
if (( RW > RH )) |
|
then |
|
# +-------+---+ |
|
# | image | | |
|
# +-------+ | |
|
# | | r | |
|
# | b | | |
|
# | | | |
|
# +-------+---+ |
|
echo $(( X+$1 )) "$Y" "$RW" "$H" > "$TARGET/child0/rect" |
|
echo "$X" $(( Y+$2 )) "$1" "$RH" > "$TARGET/child1/rect" |
|
else |
|
# +-------+---+ |
|
# | image | r | |
|
# +-------+---+ |
|
# | | |
|
# | b | |
|
# | | |
|
# +-----------+ |
|
echo $(( X+$1 )) "$Y" "$RW" "$2" > "$TARGET/child0/rect" |
|
echo "$X" $(( Y+$2 )) "$W" "$RH" > "$TARGET/child1/rect" |
|
fi |
|
|
|
local RIGHT=$(( X+$1 )) |
|
(( RIGHT > $(< "$CACHE/width") )) && echo "$RIGHT" > "$CACHE/width" |
|
|
|
local BOTTOM=$(( Y+$2 )) |
|
(( BOTTOM > $(< "$CACHE/height") )) && echo "$BOTTOM" > "$CACHE/height" |
|
|
|
echo "$X" "$Y" "$1" "$2" "$3" > "$TARGET/image" |
|
} |
|
|
|
# Find possible candidates and give them a sort index |
|
# |
|
# @param 1 - image width |
|
# @param 2 - image height |
|
# @param 3 - image file |
|
atlas_cache_find_nodes() |
|
{ |
|
if [ -d "$NODE/child0" ] |
|
then |
|
NODE="$NODE/child0" atlas_cache_find_nodes "$1" "$2" "$3" |
|
NODE="$NODE/child1" atlas_cache_find_nodes "$1" "$2" "$3" |
|
|
|
return |
|
fi |
|
|
|
[ -f "$NODE/rect" ] || return 1 |
|
|
|
local X Y W H |
|
read -r X Y W H < "$NODE/rect" |
|
|
|
if (( W < $1 )) || (( H < $2 )) |
|
then |
|
return |
|
fi |
|
|
|
local MAX_WIDTH |
|
MAX_WIDTH=$(< "$CACHE/width") |
|
local MAX_HEIGHT |
|
MAX_HEIGHT=$(< "$CACHE/height") |
|
local RIGHT=$(( X+$1 )) |
|
local BOTTOM=$(( Y+$2 )) |
|
|
|
(( RIGHT > MAX_WIDTH )) && MAX_WIDTH=$RIGHT |
|
(( BOTTOM > MAX_HEIGHT )) && MAX_HEIGHT=$BOTTOM |
|
|
|
printf '%08d %s\n' \ |
|
$(( MAX_WIDTH+MAX_HEIGHT )) \ |
|
"$NODE" >> "$CACHE/candidates" |
|
} |
|
|
|
# Sort files and insert them into the atlas |
|
atlas_cache_compile() |
|
{ |
|
local NODE=$CACHE |
|
local W H FILE |
|
|
|
# MAX is unused but required to consume the sorting index |
|
# shellcheck disable=SC2034 |
|
while read -r W H FILE |
|
do |
|
[ "$FILE" ] || continue |
|
|
|
printf '%08d %d %d %s\n' $(( W+H )) "$W" "$H" "$FILE" |
|
done | sort -r | while read -r MAX W H FILE |
|
do |
|
rm -f "$CACHE/candidates" |
|
|
|
if ! atlas_cache_find_nodes "$W" "$H" "$FILE" || |
|
! atlas_cache_insert "$W" "$H" "$FILE" |
|
then |
|
echo "error: cannot insert $FILE" >&2 |
|
return 1 |
|
fi |
|
done |
|
} |
|
|
|
# Read 'width height file' from standard input and create atlas |
|
atlas_create_from_list() |
|
{ |
|
local CACHE |
|
CACHE=$(mktemp -d "${0##*/}.XXXXXXXXXX") || return $? |
|
|
|
local MAX_SIZE=${MAX_SIZE:-2048} |
|
echo 0 0 "$MAX_SIZE" "$MAX_SIZE" > "$CACHE/rect" |
|
|
|
echo 0 > "$CACHE/width" |
|
echo 0 > "$CACHE/height" |
|
|
|
atlas_cache_compile && |
|
atlas_cache_summarize && |
|
atlas_cache_compose |
|
|
|
rm -rf "$CACHE" |
|
} |
|
|
|
# Create texture atlas from given image files |
|
# |
|
# @param ... - image files |
|
atlas_create() |
|
{ |
|
local INKSCAPE=${INKSCAPE:-$(which inkscape)} |
|
local TMPDIR |
|
TMPDIR=$(mktemp -d "${0##*/}.XXXXXXXXXX") || return $? |
|
|
|
# prepare source files |
|
local SRC |
|
for SRC |
|
do |
|
local COPY="$TMPDIR/${SRC##*/}" BORDER= |
|
|
|
case ${SRC##*.} in |
|
svg) |
|
COPY="${COPY%.*}.png" |
|
|
|
# use inkscape if available |
|
[ "$INKSCAPE" ] && |
|
$INKSCAPE "$SRC" \ |
|
-z -e "$COPY" &>/dev/null && |
|
SRC=${COPY} |
|
;; |
|
esac |
|
|
|
if (( MARGIN > 0 )) |
|
then |
|
# title_* should do glob matching |
|
# shellcheck disable=SC2053 |
|
if [[ ${SRC##*/} == ${EXPAND:-tile_*} ]] |
|
then |
|
local W |
|
W=$(identify -format '%w,%h' "$SRC") |
|
H=$(( MARGIN*2+${W#*,} )) |
|
W=$(( MARGIN*2+${W%,*} )) |
|
BORDER="-set option:distort:viewport \ |
|
${W}x${H}-${MARGIN}-${MARGIN} \ |
|
-virtual-pixel Mirror \ |
|
-filter point \ |
|
-distort SRT 0 \ |
|
+repage" |
|
else |
|
BORDER="-border $MARGIN" |
|
fi |
|
fi |
|
|
|
# BORDER cannot be quoted because it will result in an |
|
# empty argument which makes convert stall |
|
# shellcheck disable=SC2086 |
|
convert \ |
|
-background none \ |
|
"$SRC" \ |
|
-bordercolor none -border 3x3 \ |
|
-trim \ |
|
$BORDER \ |
|
"$COPY" |
|
done |
|
|
|
identify -format '%w %h %d/%f\n' "$TMPDIR/"* | atlas_create_from_list |
|
rm -rf "$TMPDIR" |
|
} |
|
|
|
readonly MARGIN=${MARGIN:-0} |
|
|
|
if [ "${BASH_SOURCE[0]}" == "$0" ] |
|
then |
|
atlas_create "$@" |
|
fi |