TexturePacker loader

Monkey Forums/Monkey Code/TexturePacker loader

luggage(Posted 2011) [#1]
Hi There

I threw this together tonight, it kinda works so maybe someone will find it useful. TexturePacker is a tool at http://www.texturepacker.com/ for creating well packed texture pages.

To use it the first thing you should do is create your texture page. Ensure that the format is "Sparrow" and that images are not Trimmed. Once made you should have a png and an xml file. Let's call them page.png and page.xml.

Open page.xml in a text editor and delete the top line. It should start out looking like...

<?xml version="1.0" encoding="UTF-8"?>
<TextureAtlas imagePath="page.png">
...stuff...


and finish like...
<TextureAtlas imagePath="page.png">
...stuff...


The reason is I use the config code suppled in bananas\skn3\config and it doesn't appear to like that first line.

Don't forget to make sure the config.monkey file is in your project somewhere. The main texturepage class is below...

Import mojo
Import config      ' remember to copy this from bananas\skn3\config

Class TexturePageData

	Field x:Int
	Field y:Int
	Field width:Int
	Field height:Int
	
	Method New( newX:Int, newY:Int, newWidth:Int, newHeight:Int )
	
		x = newX
		y = newY
		width = newWidth
		height = newHeight	
	
	End

End

Class TexturePage 

	' the list of all textures stored on this texture page
	Global imageList:StringMap<TexturePageData> = New StringMap<TexturePageData>

	' a flag to say whether the texture image was loaded or not
	Field isLoaded:Bool
	
	' the texture page's image
	Field textureImage:Image

	' give the filename to the xml file to load
	Method New( fileName:String )

		Local atlasNode:ConfigNode
		Local configData:Config
		Local nodes:List<ConfigNode>	
		Local texturePageFileName:String 
	
		isLoaded = False
		
		' load the xml file in as a string
		Local xmlStr:String = LoadString( fileName )
		
		' make sure that we've actually loaded some data
		If (xmlStr.Length > 0 ) 
				
			' parse the xml file
			configData = LoadConfig( xmlStr )
		
			' find the node that describes the filename of the texture atlas
			atlasNode 	= configData.FindNodeByPath("TextureAtlas")
			
			' find all the nodes that are SubTextures
			nodes 		= configData.FindNodesByPath("TextureAtlas/SubTexture")

			' get the file name of the texture to page to load
			texturePageFileName = atlasNode.GetAttribute("imagePath")
			
			' load the texture page
			textureImage = LoadImage( texturePageFileName )
							
			' make sure we actually loaded the image
			If (textureImage <> null)
					
				' now run through all of SubTextures...	
				For Local node := Eachin nodes
			
					' convert the attributes to ints
					Local x:Int = Int(node.GetAttribute("x").Trim())
					Local y:Int = Int(node.GetAttribute("y").Trim())
					Local width:Int = Int(node.GetAttribute("width").Trim())
					Local height:Int = Int(node.GetAttribute("height").Trim())
				
					' add our subtexture data to the map
					imageList.Set(node.GetAttribute("name"), New TexturePageData(x, y, width, height) )

					' set the flag so we can check this texturepage later on to ensure it's vald
					isLoaded = True
						
				Next
				
			EndIf	
				
		EndIf		
	
	End
	
	' a debug dunction that dumps all of the subtextures
	Method DebugDump:Void()
				
		For Local it:= Eachin imageList.Keys()
			Print it
		Next
	
	End

	' returns True if data was loaded correctly
	Method IsLoaded:Bool(  )
		Return isLoaded
	End
	
	' returns a new image given the name of the image.  The name must not include the extension.
	' ie.  If "tree.png" is on your texture page then you must pass through "tree"
	Method FindImage:Image( fileName:String )

		' make sure we were loaded correctly first...
		If (IsLoaded() = True)
			
			' get the texture page data for the image
			Local tp:TexturePageData = imageList.Get( fileName )		

			If (tp <> null)
			
				' grab the image and return it
				Return textureImage.GrabImage( tp.x, tp.y, tp.width, tp.height )			
				
			EndIf
			
		EndIf
			
		Return null
	End

End


And finally, how to use it.

' create an image variable in your class somewhere
Field myImage:Image

' Where you load your assets load in your texture page.
Local page:TexturePage = New TexturePage("page.xml")

' and grab your image from it texture page
myImage = page.FindImage("tree")

' in your render, draw it
DrawImage( myImage, 50, 50 )



luggage(Posted 2011) [#2]
I've not given it a thorough test but hopefully there's nothing too wrong there. Feel free to let me know if there is, it's pretty much the first bit of Monkey coding I've done there.

I'll try and get around to adding support for Trimmed sprites at some point. It's a bit trickier as I'm trying to make sure the Image drawing stuff isn't relying on yet another sprite class or something.

I'll also look into having a Global FindImage function that will look in all the loaded texture pages for an image. That way if your data spills over into multiple texture pages you don't have to be concerned about which graphic is where.


matt(Posted 2011) [#3]
Thanks for this.


anawiki(Posted 2011) [#4]
Isn't it a waste of ram if you create big texture and them split it into individual images? There is a modul somewhere here that lets you draw from atlas texture without grabbing images.


luggage(Posted 2011) [#5]
Ooh. Good point. I assumed that was what GrabImage did. Just thought it gave back an image structure but internally it was drawing from the texture atlas.


DGuy(Posted 2011) [#6]
GrabImage DOES NOT create a new image (texture), but rather creates an image which referes to a section of the larger image.

I was concerned about this myself, so I perused the mojo code to be sure (and think I understood most of it ... :P) ...


NoOdle(Posted 2011) [#7]
I thought grab image did, it returns (new image) where as DrawImageRect seems to use the original... I only had a brief look through the source though.


luggage(Posted 2011) [#8]
Will have to check it out. I thought GrabImage would just allocate a new Image structure but still reference the original graphic.


MikeHart(Posted 2011) [#9]
To my knowledge, GrabImage creates a new image structure with a reference to the old image. So the overhead is VERY small.