Skip to content

Instantly share code, notes, and snippets.

@mohiji
Created August 11, 2012 04:56
Show Gist options
  • Save mohiji/3321199 to your computer and use it in GitHub Desktop.
Save mohiji/3321199 to your computer and use it in GitHub Desktop.
My texture atlas tool and sample code
#!/usr/bin/env python
#
# Compiles texture atlases exported from TexturePacker (in the Generic XML format)
# into a binary format for fast/easy loading in game code. At the moment this doesn't
# support trimming or rotation, 'cause I don't really need 'em.
#
# The file format (all multibyte values are little endian):
# struct AtlasHeader {
# uint32_t magic; // (1635019891, or 'atls')
# uint16_t textureWidth;
# uint16_t textureHeight;
# uint16_t numFrames;
# char textureName[64];
# };
# sizeof(struct AtlasHeader) == 74;
#
# struct AtlasFrame {
# char name[64];
# uint16_t width, height;
# float u1, v1;
# float u2, v2;
# };
# sizeof(struct AtlasFrame) == 84;
import struct
import xml.sax
from optparse import OptionParser
class Texture:
def __init__(self, xml_attrs):
self.name = xml_attrs.getValue('imagePath')
self.width = int(xml_attrs.getValue('width'))
self.height = int(xml_attrs.getValue('height'))
class Frame:
def __init__(self, xml_attrs):
self.name = xml_attrs.getValue('n')
self.x = float(xml_attrs.getValue('x'))
self.y = float(xml_attrs.getValue('y'))
self.width = float(xml_attrs.getValue('w'))
self.height = float(xml_attrs.getValue('h'))
self.u1 = 0
self.v1 = 0
self.u2 = 0
self.v2 = 0
def make_bytes(self):
"""
Returns the byte-array representation of the frame.
"""
frame_fmt = '<64s2H4f'
return struct.pack(frame_fmt, str(self.name), int(self.width), int(self.height), self.u1, self.v1, self.u2, self.v2)
class AtlasHandler(xml.sax.ContentHandler):
def __init__(self):
xml.sax.ContentHandler.__init__(self)
self.texture = None
self.frames = []
def startElement(self, name, attrs):
if name == 'TextureAtlas':
self.texture = Texture(attrs)
elif name == 'sprite':
self.frames.append(Frame(attrs))
def endElement(self, name):
if name == 'TextureAtlas':
for frame in self.frames:
frame.u1 = frame.x / self.texture.width
frame.v1 = frame.y / self.texture.height
frame.u2 = (frame.x + frame.width) / self.texture.width
frame.v2 = (frame.y + frame.height) / self.texture.height
def dumpDescription(self):
if self.texture:
print("Texture: %s (%s, %s)" % (self.texture.name, self.texture.width, self.texture.height))
else:
print("No texture!")
for frame in self.frames:
print(" Frame: %s" % frame.name)
print(" x: %s" % frame.x)
print(" y: %s" % frame.y)
print(" width: %s" % frame.width)
print(" height: %s" % frame.height)
print(" u1: %s" % frame.u1)
print(" v1: %s" % frame.v1)
print(" u2: %s" % frame.u2)
print(" v2: %s" % frame.v2)
def make_header(self):
"""
Returns a byte array representation of the binary file header.
"""
header_fmt = "<ccccHHH64s"
return struct.pack(header_fmt, 'a', 't', 'l', 's', self.texture.width, self.texture.height, len(self.frames), str(self.texture.name))
def write_file(self, f):
"""
Writes out the byte array representation of the texture atlas to the given file.
"""
f.write(self.make_header())
for frame in self.frames:
f.write(frame.make_bytes())
def parse_file(filename):
source = open(filename)
handler = AtlasHandler()
xml.sax.parse(source, handler)
return handler
USAGE="""
Usage: atlasc.py -o <output-filename> <input-filename>
"""
def handle_commandline():
"""
Handles command-line options and arguments and does the right things.
"""
parser = OptionParser(usage=USAGE)
parser.add_option('-o', '--output', dest='output', default='out.atlas')
(options, args) = parser.parse_args()
if len(args) == 0:
print(USAGE)
return -1
atlas = parse_file(args[0])
out = open(options.output, 'w')
atlas.write_file(out)
out.close()
return 0
if __name__ == '__main__':
return handle_commandline()
//
// TextureAtlas.h
//
// Created by Jonathan Fischer on 8/10/12.
// Copyright (c) 2012 Jonathan Fischer. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface AtlasFrame : NSObject
@property (nonatomic, assign) int width;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) float u1;
@property (nonatomic, assign) float v1;
@property (nonatomic, assign) float u2;
@property (nonatomic, assign) float v2;
@end
@interface TextureAtlas : NSObject
{
NSMutableDictionary *_atlasFrames;
}
- (id)initWithContentsOfFile:(NSString*)path;
- (id)initWithData:(NSData*)data;
@property (nonatomic, readonly) int textureWidth;
@property (nonatomic, readonly) int textureHeight;
@property (nonatomic, readonly) NSString *textureName;
- (AtlasFrame*)frameForName:(NSString*)name;
- (void)addFrame:(AtlasFrame*)frame withName:(NSString*)name;
@end
//
// TextureAtlas.m
//
// Created by Jonathan Fischer on 8/10/12.
// Copyright (c) 2012 Jonathan Fischer. All rights reserved.
//
#import "TextureAtlas.h"
#include "Utility.h"
static const uint32_t kMagicNumber = 1635019891; // 'atls'
static const uint32_t kHeaderOffsetWidth = 4;
static const uint32_t kHeaderOffsetHeight = 6;
static const uint32_t kHeaderOffsetNumFrames = 8;
static const uint32_t kHeaderOffsetTextureName = 10;
static const uint32_t kNameSize = 64;
static const uint32_t kOffsetFirstFrame = 74;
static const uint32_t kFrameSize = 84;
static const uint32_t kFrameOffsetWidth = 64;
static const uint32_t kFrameOffsetHeight = 66;
static const uint32_t kFrameOffsetU1 = 68;
static const uint32_t kFrameOffsetV1 = 72;
static const uint32_t kFrameOffsetU2 = 76;
static const uint32_t kFrameOffsetV2 = 80;
struct AtlasHeader {
uint32_t magic; // (1635019891, or 'atls')
uint16_t textureWidth;
uint16_t textureHeight;
uint16_t numFrames;
char textureName[64];
};
struct AtlasFrame {
char name[64];
uint16_t width, height;
float u1, v1;
float u2, v2;
};
static void readHeader(struct AtlasHeader *header, const uint8_t *bytes)
{
header->magic = ReadLittleUint32(bytes);
header->textureWidth = ReadLittleUint16(bytes + kHeaderOffsetWidth);
header->textureHeight = ReadLittleUint16(bytes + kHeaderOffsetHeight);
header->numFrames = ReadLittleUint16(bytes + kHeaderOffsetNumFrames);
memcpy(header->textureName, bytes + kHeaderOffsetTextureName, kNameSize);
}
static void readSprite(struct AtlasFrame *frame, const uint8_t *bytes)
{
memcpy(frame->name, bytes, kNameSize);
frame->width = ReadLittleUint16(bytes + kFrameOffsetWidth);
frame->height = ReadLittleUint16(bytes + kFrameOffsetHeight);
frame->u1 = ReadLittleFloat(bytes + kFrameOffsetU1);
frame->v1 = ReadLittleFloat(bytes + kFrameOffsetV1);
frame->u2 = ReadLittleFloat(bytes + kFrameOffsetU2);
frame->v2 = ReadLittleFloat(bytes + kFrameOffsetV2);
}
@implementation AtlasFrame
@synthesize width, height, u1, v1, u2, v2;
@end
@implementation TextureAtlas
@synthesize textureWidth = _textureWidth;
@synthesize textureHeight = _textureHeight;
@synthesize textureName = _textureName;
- (id)initWithContentsOfFile:(NSString *)path
{
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
self = [self initWithData:data];
return self;
}
- (id)initWithData:(NSData *)data
{
if (data == nil) {
return nil;
}
self = [super init];
if (self) {
_atlasFrames = [[NSMutableDictionary alloc] init];
const uint8_t *bytes = [data bytes];
struct AtlasHeader header;
readHeader(&header, bytes);
_textureWidth = header.textureWidth;
_textureHeight = header.textureHeight;
_textureName = [[NSString alloc] initWithCString:header.textureName encoding:NSASCIIStringEncoding];
uint32_t offset = kOffsetFirstFrame;
for (int i = 0;i < header.numFrames; i++) {
struct AtlasFrame readFrame;
readSprite(&readFrame, bytes + offset);
AtlasFrame *frame = [[AtlasFrame alloc] init];
frame.width = readFrame.width;
frame.height = readFrame.height;
frame.u1 = readFrame.u1;
frame.v1 = readFrame.v1;
frame.u2 = readFrame.u2;
frame.v2 = readFrame.v2;
NSString *name = [NSString stringWithCString:readFrame.name encoding:NSASCIIStringEncoding];
[self addFrame:frame withName:name];
offset += kFrameSize;
}
}
return self;
}
- (void)addFrame:(AtlasFrame *)frame withName:(NSString *)name
{
if (frame == nil || name == nil) {
return;
}
[_atlasFrames setObject:frame forKey:name];
}
- (AtlasFrame*)frameForName:(NSString *)name
{
return [_atlasFrames objectForKey:name];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment