Saturday, October 15, 2011

Handling 2D Graphics for Multiple Devices

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:

  1. Finds all the resize.data files in any sub-folders of the source directory.
  2. Parses each line and scales the associated image(s) using imagemagick’s “convert” tool
  3. Places the scaled image(s) in an intermediary directory.
  4. 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.
  5. Once all images are resized, it runs the sprite sheet generator with each .atl file.
  6. 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:

  1. imagemagick (a powerful command-line image processing tool - see convert for details)
  2. mkatlas.pl (a sprite sheet “atlas” generating script from cocos2d-iphone)
  3. etc1tool (command-line tool for ETC1 compression - supported on most Android devices)
  4. 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-DATA

The 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:

  1. an image name (or image glob prefix)
  2. the target graphic-to-screen-height ratio OR an absolute target size
  3. the atlas the final image belongs in (Loading.atlas, Menu.atlas, Game.atlas, however you want to break them up)
  4. OPTIONAL: Compression flags
  5. 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=yes

When 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


done

resize_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=129

The 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.

No comments:

Post a Comment