This is a post by Nuffer Brothers Software Team member Cambell Christensen, a talented mobile developer available for hire.
Most app developers run into the multi-platform wall at some point in the development process. Say you’ve designed a game for the iPhone, and since it is such a hit in the Apple Store, you want to release it for the iPad, Android and that new [insert other mobile device here].
But each mobile device seems to have a different screen resolution, aspect ratio and compression support. So if your app is graphics intensive, handling all the resizing and compression can be a real pain, not to mention time consuming.

So how can you get that app out there while it’s hot on the market and not spend weeks going back and forth with the artists just to get a new version out? Write a script to do the work for you! For a recent in-house project, we wrote a script for this very problem and in this post we’ll walk you through our process. It has been a huge time saver.
CHOOSE YOUR WEAPON
We opted for a shell script, but you could use another scripting language of your choice. A shell scripting tutorial is probably outside the scope of this post, but here’s a good shell scripting beginner’s guide for anyone who might be interested: Shell Scripting Tutorial
And it is worth mentioning that there are likely many ways to handle graphic variations for multi-platform support, so this is not the only way by any means. This is just what worked for us. So we hope you will take any ideas you find useful, improve upon them and let us know about it!
Let’s start by taking a look at some important initial considerations for multi-platform graphics.
DESIGN CONSIDERATIONS
First off, it helps to think about multi-platform from the beginning so that the graphics will “work” for multiple devices. By “work”, I mean making sure images will display properly for multiple aspect ratios and screen sizes (and not cut off your hero’s head on the load screen, or whatever).
The artists at Evil Onion Productions created the artwork for our recent android game Redneck Racer as vector graphics (for scalability) and also added image padding to make different aspect ratios less problematic. However, if the graphics are not vector-based, that’s fine. As long as they are high-resolution with padding for different aspects.
On another recent 2D game port we did from PC to iOS, we had to go back to the original graphics and significantly re-design graphics to work for iPhone and iPad. Luckily, the company still had some of the Illustrator and Photoshop files. It is understandable to need to redesign interfaces for different size devices, but on this project, the full size graphics were too small for the iPad, so we had to extend backgrounds and piece other parts together. Case in point--if the game had been designed from the beginning with multiple platforms in mind, then adjusting the graphics would have been much faster.
On to the fun part: The script.
AUTOMATING THE RESIZE PROCESS
Basically, our script sifts through all the high-resolution graphics (and associated meta-data --- we’ll get there in a moment), re-sizes them accordingly, and feeds them into a sprite sheet generator. The sprite sheet generator packages the images into “atlases” with square aspects at power of two sizes (a must for sprite sheets for memory efficiency). The result is a complete graphics package for any given device in a few minutes.
The script has two main ways it can be run:
- By specifying a device (keep it simple, let the script do the rest)
- By specifying a target screen size, compression type and which directories to use
There are meta-data files (named “resize.data”) that the script relies on stored along with the source images. We’ll get into detail on the actual content and format of those files in a bit.
But for now, here’s a high-level rundown of each step the resize script takes:
- Finds all the resize.data files in any sub-folders of the source directory.
- Parses each line and scales the associated image(s) using imagemagick’s “convert” tool
- Places the scaled image(s) in an intermediary directory.
- Adds the image names to an .atl file (in the intermediary directory; See the flow chart above) which the sprite sheet generator (mkatlas.pl) relies on.
- Once all images are resized, it runs the sprite sheet generator with each .atl file.
- The generated sprite sheets are placed in the proper directory (we used “assets”) along with the newly generated .atlas files (which contain the coordinates of each graphic in a given sprite sheet).

EXTERNAL UTILITIES
As indicated in the graphic above, the script relies on a few key external tools:
- imagemagick (a powerful command-line image processing tool - see convert for details)
- mkatlas.pl (a sprite sheet “atlas” generating script from cocos2d-iphone)
- etc1tool (command-line tool for ETC1 compression - supported on most Android devices)
- texturetool (command-line tool for PVR compression - supported by iOS)
The above tools require arguments (desired image sizes, ratios, formats, etc). The trouble is, how will the script know how big you want your squirrel animation to be? You get all your graphics delivered from the artist and your squirrel road kill is as big as your pick up trucks, so you cannot rely on one ratio to resize everything. So that is why the metadata files (those resize.data files I mentioned) are so important.
THE META-DATAThe format of the “resize.data” files is arbitrary--- you might discover a better, more efficient approach or at least a cooler name for your metadata. The important thing is that you HAVE a format that your script can rely on.
In our approach, each resize.data file consists of a line for each image (or image “glob” for animations) in the folder. Each line consists of the following colon-delimited fields:
- an image name (or image glob prefix)
- the target graphic-to-screen-height ratio OR an absolute target size
- the atlas the final image belongs in (Loading.atlas, Menu.atlas, Game.atlas, however you want to break them up)
- OPTIONAL: Compression flags
- OPTIONAL: Grouping name for selecting between variations of an image for different aspect ratios. (The naming format for the images that have alternates is Letter_
GroupingName .png. A grouping is specified in the resize.data file with: “alternates= ”)
Take a look at this example “resize.data” file to clarify the above descriptions:
glob=RedCarDrivesNorth*.png:atlas=game:resize=.234563
glob=SteeringWheel.png:atl=loading:resize=.2375
glob=Stereo.png:atl=loading:resize=.3541666
glob=Audio*.png:atl=loading:resize=.3791666
glob=Controls*.png:atl=loading:resize=.5770833
glob=A_MainBackground.png:atl=loading:resize=1.00000:alternates=MainBackground
glob=B_MainBackground.png:atl=loading:resize=1.00000:alternates=MainBackground
glob=background_grassy*.png:atl=background_grassy:resize=508x1020:compression=yesWhen a new graphic is delivered by the artist, adding the image to the game is as simple as specifying an atlas it belongs in, and a resize ratio. Just drop it in the folder and running the script does the rest.
THE SCRIPT(S)
Ok, to tell the truth, the script is actually a set of scripts that call each other. We tried to decouple the resize step and the atlas generation step to allow for more control and re-usability.
Here are the scripts:
prepare_graphics.sh
This script parses parameters, adds necessary tools to the path and calls the other scripts.
#!/bin/bash
# this script depends on resize_fonts.sh, resize_assets.sh and generate_atlases.sh
# (NOTE: generate_atlases.sh depends on mkatlas.pl which depends on compression tools: etc1tool and texturetool)
# for simplicity, this script can be run with a single parameter for a target device (android or iphone).
# Or it can be run with custom size, compression and directory options. See usage for details.
set -u #referencing undefined variable causes error
set -x #print out everything it does
set -e #anything with non-zero exit status aborts script
usage()
{
echo "USAGE: Option 1: <target-device (android or iphone)>"
echo " Option 2: <target-width> <target height> <compression (etc1, pvr or none)> [<graphics-src-dir> <resized-dest-dir> <atlas-dest-dir>]"
exit
}
checkCompression()
{
if [[ ${1} != 'pvr' && ${1} != 'etc1' && ${1} != 'none' ]]; then
echo "error: unsupported compression specification: ${1}"
usage
fi
}
checkSrcDirectory()
{
if [ ! -d "${1}" ]; then
echo "specified source directory does not exist: ${1}"
exit
fi
}
#defaults
compression='none'
graphicsSrcDir='./graphics/ginormous'
resizedGraphicsDir='./graphics/wvga'
atlasesDir='./assets'
targetDevice='android'
targetW=480
targetH=800
if [ $# == 6 ]; then
targetW=${1}
targetH=${2}
compression="${3}"
graphicsSrcDir="${4}"
resizedGraphicsDir="${5}"
atlasesDir="${6}"
elif [ $# == 5 ]; then
targetW=${1}
targetH=${2}
compression="${3}"
graphicsSrcDir="${4}"
resizedGraphicsDir="${5}"
elif [ $# == 3 ]; then
targetW=${1}
targetH=${2}
compression=${3}
elif [ $# == 2 ]; then
targetW=${1}
targetH=${2}
elif [ $# == 1 ]; then
targetDevice=${1}
if [ "${targetDevice}" == 'android' ]; then
compression='etc1'
targetW=480
targetH=800
elif [ "${targetDevice}" == 'iphone' ]; then
compression='pvr'
targetW=640
targetH=960
else
echo "error: unsupported target device: ${targetDevice}"
usage
fi
else
echo "error: unable to interpret parameters"
usage
fi
checkCompression "${compression}"
checkSrcDirectory "${graphicsSrcDir}"
#add android sdk tools dir (for etc1tool) to the PATH variable if the directory exists
if [ -e local.properties ]; then
sdkpath=$(grep 'sdk\.dir' local.properties | cut -s -d= -f2)
#FIXME! check if sdkpath is assigned or not
PATH="${PATH}:${sdkpath}/tools"
fi
echo "target screen resolution: ${targetW}x${targetH}"
echo "compression: ${compression}"
echo "full-size graphics src dir: ${graphicsSrcDir}"
echo "resized graphics dest dir: ${resizedGraphicsDir}"
echo "atlas dest dir: ${atlasesDir}"
# clear out atlas dir
find ${atlasesDir} -type f | egrep -v '(sounds/|fonts/)' | xargs rm -f
./resize_assets.sh ${targetW} ${targetH} "${graphicsSrcDir}" "${resizedGraphicsDir}" || { echo "FAILED!"; exit 1; }
mv "${resizedGraphicsDir}/"*".road" "${resizedGraphicsDir}/realrect.bound" "${atlasesDir}"
./generate_atlases.sh ${compression} "${resizedGraphicsDir}" "${atlasesDir}" || { echo "FAILED!"; exit 1; }
./resize_fonts.sh ${targetH} "${graphicsSrcDir}" "${atlasesDir}/fonts" || { echo "FAILED!"; exit 1; }
echo "Finished!"resize_graphics.sh
This script searches for the resize.data files, resizes images accordingly and places them in the intermediary resize folder.
#!/bin/bash
# generates scaled graphics and .atl, .road and realrect.bound files from full size graphics and associated metadata
# USAGE: <target-Screen-Width> <target-Screen-Height> <src-dir> <dest-dir>
# depends on resize.data metadata files and .road (if any) metadata file(s) in src dir
# METADATA LINE FORMAT in resize.data file
# <glob=filename_glob>:<atl=atlas_group_name>:<resize=resize_param>[:alternates=<alternate_set_name>][:compression=yes][,compression param 01][,compression param 02],...[:realrect=filename,left bound,top bound,right bound,bottom bound, scale factor X, scale factor Y]
# ALTERNATES
# some files have alternate source graphics for different aspect ratios. Alternates should be named with a letter underscore prefix such as 'A_'
# Each alternate set has a an <alternate_set_name> to designate the set of files from which a choice will be made.
# EXAMPLE
# since the iPhone4 resolution is 960h x 640w, the script could be called using the following command:
# $./resizeAssets.sh 640 960 ./graphics/ginormous ./graphics/wvga
# FUNCTIONS
# usage() --- called by main()
# processAlternates() --- called by resizeGraphics()
# resizeGraphics() --- called by main()
# main()
set -u #referencing undefined variable causes error
set -x #print out everything it does
set -e #anything with non-zero exit status aborts script
usage()
{
echo "USAGE: <target-Screen-Width> <target-Screen-Height> <src-dir> <dest-dir>"
exit 1
}
#select from alternates stored in *.alternate.tmp files
#each alternate set will have a different .alternate.tmp file associated with it
processAlternates()
{
targetWidth=${1}
targetHeight=${2}
outDir="${3}"
find . -type f -name '*.alternates.tmp' | while read alternateFile; do
currDir=$(dirname ${alternateFile})
cat ${alternateFile} | while read alternate; do
altglob=$( echo "${alternate}" | cut -s -d: -f1 | cut -s -d= -f2)
targetAspect=$(echo "scale=12; ${targetWidth} / ${targetHeight}" | bc )
#calculate difference between target aspect ratio and current alternate possibility's aspect
imageWidth=$(convert "${currDir}/${altglob}" -format %w info:)
imageHeight=$(convert "${currDir}/${altglob}" -format %h info:)
imageAspect=$(echo "scale=12; ${imageWidth} / ${imageHeight}" | bc )
difference=$(echo "scale=12; ${imageAspect} - ${targetAspect}" | bc )
absoluteDifference=${difference#-}
#append difference field and add the new alternate possibility to the file
echo ${absoluteDifference}:${alternate} >> ${currDir}/alternates.ratios.tmp
#sort lines in file so that closest ratio to target aspect ratio is at the top
sort -n -t ':' -k 1 ${currDir}/alternates.ratios.tmp
#pull out the closest option
closest=$(head -n 1 ${currDir}/alternates.ratios.tmp)
#save only the closest option
echo ${closest} > "${currDir}/alternates.ratios.tmp"
done
#extract data of the closest match to the target aspect ratio
cat "${currDir}/alternates.ratios.tmp" | tr ':' '\n' > "${currDir}/closest.alternate.tmp"
closestGlob=$(grep 'glob=' ${currDir}/closest.alternate.tmp | cut -s -d= -f2)
closestAtl=$(grep 'atl=' ${currDir}/closest.alternate.tmp | cut -s -d= -f2)
closestResize=$(grep 'resize=' ${currDir}/closest.alternate.tmp | cut -s -d= -f2)
closestWidth=$(echo "${targetWidth} * ${closestResize}" | bc)
rm ${currDir}/alternates.ratios.tmp
rm ${currDir}/closest.alternate.tmp
#now that the appropriate alternate is selected from the choices, process that image or image sequence (if an animation)
find "${currDir}" -type f -name "${closestGlob}" | while read image; do
outputName="$(basename ${image})"
#strip off alternate's letter-underscore prefix so that resized-dir receives a predictable filename from any of the alternate options
outputName="${outputName#??}"
convert "${image}" -define png:color-type=6 -resize ${closestWidth} "${outDir}/${outputName}"
echo $(basename "${outputName}") >> "${outDir}/${closestAtl}.atl"
done
rm "${alternateFile}"
done
}
resizeGraphics()
{
targetWidth=$1
targetHeight=$2
fullSizeDir="${3}"
outDir="${4}"
#initialize realrect.bound file with a header
#realrect.bound is a file ised to overrides collision information for animations that do not fill their own frame for the duration of the animation
# i.e. - so that you don't destroy an obstacle merely by riding past it.
echo "<name> <left> <top> <right> <bottom> <x scale factor> <y scale factor>" > "${outDir}/realrect.bound"
find "${fullSizeDir}" -type f -name 'resize.data' | while read filename; do
cat ${filename} | while read line; do
resizeDir=$(dirname ${filename})
if [ x${line:+set} = xset ]; then
newLine=$(echo ${line} | tr ':' '\n')
echo "${newLine}" > ${resizeDir}/resize.tmp
glob=$(grep 'glob=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
atlas=$(grep 'atl=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
resize=$(grep 'resize=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
compression=$(grep 'compression=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
alternates=$(grep 'alternates=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
realrect=$(grep 'realrect=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
rm ${resizeDir}/resize.tmp
#compress.tmp stores names of files requiring compression before sprite sheet generation
if ! grep "${atlas}" "${outDir}/compress.tmp" > /dev/null 2>&1; then
echo "${atlas}:compression=${compression:-no}" >> "${outDir}/compress.tmp"
fi
#if a specific target size in form <width>x<height> is specified, these variables will be set
absoluteX=$(echo ${resize} | cut -s -dx -f1)
absoluteY=$(echo ${resize} | cut -s -dx -f2)
#alternates stored to be processed later - alternate sets should not span multiple resize.data files
if [ x${alternates:+set} = xset ]; then
echo "${line}" >> "${resizeDir}/${alternates}.alternates.tmp"
else
#if absolute <width>x<height> target size specified (can be aspect ratio destructive)
if [ x${absoluteY:+set} = xset ]; then
find "${resizeDir}" -type f -name "${glob}" | while read absoluteFile; do
convert "${absoluteFile}" -define png:color-type=6 -resize "${absoluteX}x${absoluteY}!" "${outDir}/$(basename ${absoluteFile})"
done
else
#else target ratio to screen-size specified
width=$(echo "scale=12; ${targetWidth} * ${resize}" | bc | xargs printf "%1.0f")
find "${resizeDir}" -type f -name "${glob}" | while read ratioFile; do
convert "${ratioFile}" -define png:color-type=6 -resize ${width} "${outDir}/$(basename ${ratioFile})"
echo "$(basename ${ratioFile})" >> "${outDir}/${atlas}.atl"
done
fi
#ENGINE SPECIFIC FILES NEEDING TO BE SCALED WITH IMAGE(S)
#road files are for determining road borders in game
#if .road files exist, they need to be scaled to match the newly scaled image files to which they correspond
#roadFilesExist=$(find ${resizeDir} -type f -name ${glob%.png}.road)
#if [ x${roadFilesExist:+set} = xset ]; then
# ./scaleRoadBounds.sh "${glob}" ${resize} ${targetWidth} ${targetHeight} "${resizeDir}" "${outDir}"
#fi
#ENGINE SPECIFIC FILES NEEDING TO BE SCALED WITH IMAGE(S)
#the realrect.bounds file is used to override collision data
#if the current image has realrect data, scale it
#if [ x${realrect:+set} = xset ]; then
# ./scaleRealRect.sh "${realrect}" ${resize} ${targetWidth} ${targetHeight} "${resizeDir}" "${outDir}"
#fi
fi
fi
done
done
#now that the main resizing process is over, alternate.tmp files will have been generated for any alternate sets
processAlternates ${targetWidth} ${targetHeight} "${outDir}"
}
main()
{
if [ $# != 4 ]
then
usage
fi
targetScreenWidth=$1
targetScreenHeight=$2
srcDir="${3}"
destDir="${4}"
if [ ! -d "${srcDir}" ]; then
echo "source directory does not exist: ${srcDir}"
exit 1
fi
#WARNING: specify destination directory carefully - this script will destroy it!
if [ -d "${destDir}" ]; then
rm -rf "${destDir}"
fi
mkdir -p "${destDir}"
resizeGraphics ${targetScreenWidth} ${targetScreenHeight} "${srcDir}" "${destDir}"
}
main ${@}
generate_atlases.sh
This script feeds the resized graphics in the intermediary directory into the sprite sheet generating script.
#!/bin/bash
# Generates sprite sheets for each atlas
# depends on .atl file(s), compress.tmp file, and mkatlas.pl script
# (which requires etc1tool and texturetool for compression)
set -u #referencing undefined variable causes error
set -x #print out everything it does
set -e #anything with non-zero exit status aborts script
usage()
{
echo "USAGE: <compression (pvr, etc1 or none)> <image-src-dir> <atlas-dest-dir>"
}
if [ $# != 3 ]; then
usage
exit 1
fi
compression="${1}"
#source dir must contain a compress.tmp metadata file
srcDir="${2}"
destDir="${3}"
etc1FileEnding='.pkm'
pvrFileEnding='.pvr'
atlasFileEnding='.atl'
compressFileEnding=0
# if 'none' is specified as the compression param,
# no extension is specified and the default output from script mkatlas.pl is .png
# otherwise...
if [ ${compression} == 'etc1' ]; then
compressFileEnding=${etc1FileEnding}
elif [ ${compression} == 'pvr' ]; then
compressFileEnding=${pvrFileEnding}
fi
# compress.tmp file generated by resize_assets.sh script indicates which atlases need compression
# each line in compress.tmp is formatted as follows:
# <atlasname>:compression=<(yes or no)>
cat "${srcDir}/compress.tmp" | while read atlasLine; do
atlasName=$(echo ${atlasLine} | cut -s -d: -f1)
atlasCompress=$(echo ${atlasLine} | cut -s -d: -f2 | cut -s -d= -f2)
# mkatlas params: -f configfile [-s size (128)] [-r imgrootdir (.)] [-p pvrspacing (0)] (cont'd)
# [-e output compressFileEnding (.png)] [-g group name (1)]
if [[ ${atlasCompress} = 'yes' && ${compression} != 'none' ]]; then
perl ./mkatlas.pl -r "${srcDir}" -f "${srcDir}/${atlasName}${atlasFileEnding}" -s 1024 -p 4 -g "${atlasName}" -e "${compressFileEnding}" || { echo "FAILED!"; exit 1; }
mv -f "${srcDir}/${atlasName}"*".atlas" "${srcDir}/${atlasName}"*"Atlas${compressFileEnding}" "${destDir}"
elif [[ ${atlasCompress} = 'no' || ${compression} = 'none' ]]; then
perl ./mkatlas.pl -r "${srcDir}" -f "${srcDir}/${atlasName}${atlasFileEnding}" -s 1024 -p 2 -g "${atlasName}" || { echo "FAILED!"; exit 1; }
mv -f "${srcDir}/${atlasName}"*".atlas" "${srcDir}/${atlasName}"*"Atlas.png" "${destDir}"
else
echo "Error: compression flag error in compress.tmp on line ${atlasLine}"
fi
doneresize_fonts.sh
You’ll notice that we deal with font.resize.data files. We wanted to use the script to generate scaled bitmap fonts which follow some different rules than other graphics, so we added another metadata file-type (called font.resize.data). We put this in a separate resize_fonts.sh script which the main prepare_graphics.sh script calls.
Example “font.resize.data” file line:
font=TheMilkmanConspiracy:outputname=TheMilkmanConspiracy:ratio=0.0225:height=129The font name and output name seem redundant, but actually allow for multiple sizes of the same font to be generated. Depending on the situation, this might be useful.
Here’s the script that resizes the bitmap fonts:
#!/bin/bash
# generates a resized font image with scaled .atlas file according to a target screen height parameter
# depends on a font.resize.data file in the following format:
# font=<sourcefontname>:outputname=<targetfontname>:ratio=<float value-letterheight-to-screenheight-ratio>:
# height=<sourceletterheight>
# the outputname parameter allows for multiple sizes of a font to be generated from a single source font image.
# the ratio parameter should be set according to how large the font should appear relative to the screen height
# depends on a <fontname>.png font with an associated <fontname>.atlas file in the following format:
# note: extra spacing is used between letters in the generated font-image in order to preserve adequate spacing
# (approx. 4 pixels) after down-scaling.
# image: fonts/TheMilkmanConspiracy.png
# size: 2048 2048
# group: TheMilkmanConspiracy
# quad: fonts/TheMilkmanConspiracy/032 50 50 40 129 1
# quad: fonts/TheMilkmanConspiracy/033 140 50 40 129 1
# quad: fonts/TheMilkmanConspiracy/034 230 50 51 129 1
# ...
# <fontname>.atlas body line format:
# "quad: dir/name/letterNum xLetterCoord yLetterCoord letterWidth letterHeight"
set -u #referencing undefined variable causes error
set -x #print out everything it does
set -e #anything with non-zero exit status aborts script
usage()
{
echo "USAGE: <target-Screen-Height> <font-image-src-dir> <font-image-dest-dir>"
exit 1
}
main()
{
if [ $# -ne 3 ]
then
usage
fi
ending='.png'
targetScreenHeight=${1}
srcDir="${2}"
destDir="${3}"
mkdir -p "${destDir}"
find "${srcDir}" -type f -name font.resize.data | while read fontResizeFile; do
resizeDir=$(dirname "${fontResizeFile}")
cat ${fontResizeFile} | while read fontResizeLine; do
fontLineWithNewLineDelim=$(echo ${fontResizeLine} | tr ':' '\n')
echo "${fontLineWithNewLineDelim}" > ${resizeDir}/resize.tmp
sourceFontName=$(grep 'font=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
targetFontName=$(grep 'outputname=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
letterToScreenHeightRatio=$(grep 'ratio=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
ginormousLetterHeight=$(grep 'height=' "${resizeDir}/resize.tmp" | cut -s -d= -f2)
fontImage=$(echo "${sourceFontName}${ending}")
ginormousFontWidth=$(convert "${resizeDir}/${fontImage}" -format %w info:)
rm "${resizeDir}/resize.tmp"
targetLetterHeight=$(echo "scale=12; ${targetScreenHeight} * ${letterToScreenHeightRatio}" | bc)
fontScale=$(echo "scale=12; ${targetLetterHeight} / ${ginormousLetterHeight}" | bc)
targetFontWidth=$(echo "scale=12; ${ginormousFontWidth} * ${fontScale}" | bc)
convert ${resizeDir}/${fontImage} -define png:color-type=6 -resize ${targetFontWidth} ${destDir}/resized${fontImage}
#font atlas needs to be square
#calculate next highest power of two
powOf2=1
while [ $(echo "scale=1; ${powOf2} < ${targetFontWidth}" | bc) -ne 0 ]; do
powOf2=$(echo "scale=0; ${powOf2} * 2" | bc)
done
#pad image on bottom and right with transparent pixels to the above calculated next highest power of 2
convert ${destDir}/resized${fontImage} -background transparent -gravity NorthWest -extent ${powOf2}x${powOf2} -define png:color-type=6 ${destDir}/${fontImage}
rm -f ${destDir}/resized${fontImage}
#scaling font atlas file
#Line 1: copy straight over - image: fonts/TheMilkmanConspiracy.png
image=$(grep 'image:' "${resizeDir}/${sourceFontName}.atlas" | cut -s -d' ' -f2)
#Line 3: copy straight over - group: TheMilkmanConspiracy
group=$(grep 'group:' "${resizeDir}/${sourceFontName}.atlas" | cut -s -d' ' -f2)
#font atlas header
echo image: ${image} > ${destDir}/${sourceFontName}.atlas
echo size: ${powOf2} ${powOf2} >> ${destDir}/${sourceFontName}.atlas
echo group: ${group} >> ${destDir}/${sourceFontName}.atlas
#font atlas body line format: quad: dir/name/letterNum xLetterBound yLetterBound letterWidth letterHeight
#example - quad: fonts/TheMilkmanConspiracy/032 4 4 40 129 1
grep 'quad:' ${resizeDir}/${sourceFontName}.atlas | while read quadLine; do
letterID=$(echo "${quadLine}" | cut -s -d' ' -f2)
xLetterBound=$(echo "${quadLine}" | cut -s -d' ' -f3)
yLetterBound=$(echo "${quadLine}" | cut -s -d' ' -f4)
letterWidth=$(echo "${quadLine}" | cut -s -d' ' -f5)
letterHeight=$(echo "${quadLine}" | cut -s -d' ' -f6)
#scale and round letter bounds to nearest whole number
newXLetterBound=$(echo "scale=12; ${xLetterBound} * ${fontScale}" | bc | xargs printf "%1.0f")
newYLetterBound=$(echo "scale=12; ${yLetterBound} * ${fontScale}" | bc | xargs printf "%1.0f")
newLetterWidth=$(echo "scale=12; ${letterWidth} * ${fontScale}" | bc | xargs printf "%1.0f")
newLetterHeight=$(echo "scale=12; ${letterHeight} * ${fontScale}" | bc | xargs printf "%1.0f")
xScale=1
yScale=1
echo quad: "${letterID} ${newXLetterBound} ${newYLetterBound} ${newLetterWidth} ${newLetterHeight} ${xScale} ${yScale}" >> ${destDir}/${sourceFontName}.atlas
done
done
done
}
main ${@}scaleRoadBounds.sh, scaleRealRect.sh
These are additional scripts called by resize_graphics.sh that scale engine specific meta-data files. We haven’t included these scaling script here because they might bore you to tears and they probably don’t apply to your engine, but you get the idea-just scale any metadata while resizing the images to make your life easier.
WELCOME TO THE FUTURE, MARTY - RETINA DISPLAY
For future versions of this script, we plan to add support for the iPhone retina display which uses the image_name@2x.png option to load high-resolution images (see apple developer resource guide for details). This would simply require producing two versions of each iPhone image, one double the dimensions of the other with “@2x” added into the name. Easy, right?
PACKING IT ALL UP
Automating the graphics processing has not only allowed us to focus more on the game-play itself, but has also provided a stream-lined way to add new graphics into our game as we add features.