Skip to content

Instantly share code, notes, and snippets.

@emilio-rst
Forked from markusfisch/README.md
Created July 28, 2014 22:05
Show Gist options
  • Select an option

  • Save emilio-rst/baae894fa62cb08bb306 to your computer and use it in GitHub Desktop.

Select an option

Save emilio-rst/baae894fa62cb08bb306 to your computer and use it in GitHub Desktop.

mkatlas

BASH script to build a texture atlas for small/medium web sites/games. Requires ImageMagick.

Usage

Basic

Just run

$ ./mkatlas img/*

which will create atlas.png and outputs the corresponding sprite regions in JSON format:

var atlas={
first:{x:298,y:0,w:35,h:22},
second:{x:254,y:0,w:44,h:33},
...
}

Advanced

Alternatively you may use just markers in your JavaScript to insert the frame data in place. This way you wouldn't have to have a extra atlas object and wouldn't need to do any additional mapping. For example:

var obj = {
	name: "Jim",
	frame: {/*first*/x:0,y:0,w:0,h:0},
	live: 100 };

This file may be patched with patchatlas like this:

$ ./mkatlas img/* | ./patchatlas index.html

Switches

There are a few variables for fine tuning. Just put them before invocation, e.g.:

$ ATLAS=atlas.jpg ./mkatlas img/*

WIDTH

Maximum width of atlas. Default is 2048.

HEIGHT

Maximum height of atlas. Default is 2048.

BORDER

Padding around sprites. Default is 0.

ATLAS

File name (and image format) of atlas image.

PREFER_SMALLER

Prefer smaller region for lay out if set. Sometimes this gives a smaller atlas. Just try.

#!/usr/bin/env bash
# Horizontal cut
atlas_cut_horizontal()
{
echo $(( X+$1 )) $Y $W $2 > $NODE/child0/rect
echo $X $(( Y+$2 )) $NW $H > $NODE/child1/rect
}
# Vertical cut
atlas_cut_vertical()
{
echo $X $(( Y+$2 )) $1 $H > $NODE/child0/rect
echo $(( X+$1 )) $Y $W $NH > $NODE/child1/rect
}
# 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_insert()
{
if [ -d "$NODE/child0" ]
then
local N=$NODE
NODE=$N/child0
atlas_insert $1 $2 $3 && return 0
NODE=$N/child1
atlas_insert $1 $2 $3 && return 0
return 1
fi
[ -f $NODE/rect ] || return 1
local X Y NW NH
read X Y NW NH < $NODE/rect
if (( $1 > NW )) || (( $2 > NH ))
then
return 1
fi
local W=$(( NW-$1 ))
local H=$(( NH-$2 ))
mkdir $NODE/child0 $NODE/child1
if (( H > W ))
then
if (( PREFER_SMALLER ))
then
atlas_cut_vertical $1 $2
else
atlas_cut_horizontal $1 $2
fi
else
if (( PREFER_SMALLER ))
then
atlas_cut_horizontal $1 $2
else
atlas_cut_vertical $1 $2
fi
fi
echo \
$(( X+BORDER )) \
$(( Y+BORDER )) \
$(( $1-BORDER*2 )) \
$(( $2-BORDER*2 )) \
$3 > $NODE/image
return 0
}
# Create texture atlas from a listing in the format "width height filename"
atlas_from_list()
{
local CACHE
CACHE=`mktemp -d ${0##*/}.XXXXXXXXXX` || return 1
local WIDTH=${WIDTH:-2048}
local HEIGHT=${HEIGHT:-2048}
echo 0 0 $WIDTH $HEIGHT > $CACHE/rect
# sort files and insert them into the atlas
local MAX W H FILE
while read W H FILE
do
[ "$FILE" ] || continue
if (( W > H ))
then
MAX=$W
else
MAX=$H
fi
printf '%08d %d %d %s\n' $MAX $W $H $FILE
done | sort -r | while read MAX W H FILE
do
local NODE=$CACHE
atlas_insert $W $H $FILE || {
echo 'error: canvas too small' >&2
break
}
done
# print JSON and build arguments for convert
echo "var atlas={"
find $CACHE -type f -name image | while read
do
local X Y W H FILE
read X Y W H FILE < $REPLY
echo "$FILE -geometry +$X+$Y -composite" >> $CACHE/args
local NAME=${FILE##*/}
echo "${NAME%.*}:{x:$X,y:$Y,w:$W,h:$H},"
done
echo "};"
# compose images into atlas
convert -size "${WIDTH}x${HEIGHT}" xc:transparent \
`< $CACHE/args` \
-strip -trim -bordercolor none -border $BORDER \
"${ATLAS:-atlas.png}"
rm -rf $CACHE
}
# Create texture atlas
#
# @param ... - image files
atlas_from_files()
{
local TRIMMED
TRIMMED=`mktemp -d ${0##*/}.XXXXXXXXXX` || return 1
# trim input files first
local F
for F in "$@"
do
convert "$F" \
-strip \
-trim \
-bordercolor none -border $BORDER \
"$TRIMMED/${F##*/}"
done
identify -format '%w %h %d/%f\n' "$TRIMMED/*" | atlas_from_list
rm -rf $TRIMMED
}
readonly BORDER=${BORDER:-0}
[ "$0" == "$BASH_SOURCE" ] && atlas_from_files "$@"
#!/usr/bin/env bash
# Patch given JavaScript files with JSON read from stdin
#
# @param ... - JavaScript files to patch
atlas_patch()
{
(( $# < 1 )) && {
echo "usage: ${0##*/} FILE..."
return 1
}
while read
do
case $REPLY in
*:{*)
;;
*)
continue
;;
esac
local NAME=${REPLY%%:\{*}
[ "$NAME" ] || continue
local FRAME=${REPLY#*\{}
local PATCH="s^{/\*${NAME}\*/[\"xywh:0-9,]*}^{/*${NAME}*/${FRAME%,}^g"
local TMP=".${0##*/}-$$.tmp"
local SRC
for SRC in "$@"
do
sed -e "$PATCH" < $SRC > $TMP && mv $TMP $SRC
done
done
}
[ "$0" == "$BASH_SOURCE" ] && atlas_patch "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment