Skip to content

Instantly share code, notes, and snippets.

@hjanetzek
Forked from markusfisch/README.md
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hjanetzek/2975a3c5cadbea59c618 to your computer and use it in GitHub Desktop.
Save hjanetzek/2975a3c5cadbea59c618 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/*

MAX_SIZE

Maximum width/height of atlas. Default is 2048.

BORDER

Padding around sprites. Default is 0.

ATLAS

File name (and image format) of atlas image.

#!/usr/bin/env bash
# Compose images into atlas
atlas_cache_compose()
{
[ -f $CACHE/args ] && convert \
-size "`< $CACHE/width`x`< $CACHE/height`" \
xc:transparent \
`< $CACHE/args` \
-strip \
-trim \
-bordercolor none -border $BORDER \
"${ATLAS:-atlas.png}"
}
# Print JSON and build arguments for convert
atlas_cache_summarize()
{
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 "};"
}
# 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
while read INDEX TARGET
do
break
done <<< "`sort $CACHE/candidates`"
[ -f $TARGET/rect ] || return 1
local X Y W H
read 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 X Y W H < $NODE/rect
if (( W < $1 )) || (( H < $2 ))
then
return
fi
local MAX_WIDTH=`< $CACHE/width`
local 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
while read W H FILE
do
[ "$FILE" ] || continue
printf '%08d %d %d %s\n' $(( W+H )) $W $H $FILE
done | sort -r | while read 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##*/}"
case ${SRC##*.} in
svg)
COPY="${COPY%.*}.png"
# use inkscape if available
[ "$INKSCAPE" ] &&
$INKSCAPE "$SRC" \
-z -e "$COPY" &>/dev/null &&
SRC=${COPY}
;;
esac
convert \
-background none \
"$SRC" \
-strip \
-bordercolor none -border 3x3 \
-trim \
-bordercolor none -border ${BORDER} \
"$COPY"
done
identify -format '%w %h %d/%f\n' "$TMPDIR/*" | atlas_create_from_list
rm -rf $TMPDIR
}
readonly BORDER=${BORDER:-0}
if [ "$BASH_SOURCE" == "$0" ]
then
atlas_create "$@"
fi
#!/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
do
sed -e "$PATCH" < $SRC > $TMP && mv $TMP $SRC
done
done
}
if [ "$BASH_SOURCE" == "$0" ]
then
atlas_patch "$@"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment