Browse Source

SpritePacker cli

Chris Friesen 11 years ago
parent
commit
6f383f64df

+ 20 - 0
Docs/Reference.dox

@@ -2449,6 +2449,26 @@ RampGenerator <output file> <width> <power> [dimensions]
 
 The output is saved in PNG format. The power parameter is fed into the pow() function to determine ramp shape; higher value gives more brightness and more abrupt fade at the edge.
 
+\section Tools_SpritePacker SpritePacker
+
+Takes a series of images and packs them into a single texture and creates a sprite sheet xml file.
+
+Usage:
+\verbatim
+SpritePacker -options <input file> <input file> <output png file>
+Options:
+    -h Shows this help message.
+    -px Adds x pixels of padding per image to width.
+    -py Adds y pixels of padding per image to height.
+    -ox Adds x pixels to the horizontal position per image.
+    -oy Adds y pixels to the horizontal position per image.
+    -frameHeight Sets a fixed height for image and centers within frame.
+    -frameWidth Sets a fixed width for image and centers within frame.
+    -trim Trims excess transparent space from individual images offsets by frame size.
+    -xml 'path' Generates an SpriteSheet xml file at path.
+    -debug Draws allocation boxes on sprite.
+\endverbatim
+
 \section Tools_ScriptCompiler ScriptCompiler
 
 Compiles AngelScript file(s) to binary bytecode for faster loading. Can also dump the %Script API in Doxygen format.

+ 546 - 0
Source/ThirdParty/STB/stb_rect_pack.h

@@ -0,0 +1,546 @@
+// stb_rect_pack.h - v0.05 - public domain - rectangle packing
+// Sean Barrett 2014
+//
+// Useful for e.g. packing rectangular textures into an atlas.
+// Does not do rotation.
+//
+// Not necessarily the awesomest packing method, but better than
+// the totally naive one in stb_truetype (which is primarily what
+// this is meant to replace).
+//
+// Has only had a few tests run, may have issues.
+//
+// More docs to come.
+//
+// No memory allocations; uses qsort() and assert() from stdlib.
+//
+// This library currently uses the Skyline Bottom-Left algorithm.
+//
+// Please note: better rectangle packers are welcome! Please
+// implement them to the same API, but with a different init
+// function.
+//
+// Version history:
+//
+//     0.05:  added STBRP_ASSERT to allow replacing assert
+//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support
+//     0.01:  initial release
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//       INCLUDE SECTION
+//
+
+#ifndef STB_INCLUDE_STB_RECT_PACK_H
+#define STB_INCLUDE_STB_RECT_PACK_H
+
+#define STB_RECT_PACK_VERSION  1
+
+#ifdef STBRP_STATIC
+#define STBRP_DEF static
+#else
+#define STBRP_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct stbrp_context stbrp_context;
+typedef struct stbrp_node    stbrp_node;
+typedef struct stbrp_rect    stbrp_rect;
+
+#ifdef STBRP_LARGE_RECTS
+typedef int            stbrp_coord;
+#else
+typedef unsigned short stbrp_coord;
+#endif
+
+STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
+// Assign packed locations to rectangles. The rectangles are of type
+// 'stbrp_rect' defined below, stored in the array 'rects', and there
+// are 'num_rects' many of them.
+//
+// Rectangles which are successfully packed have the 'was_packed' flag
+// set to a non-zero value and 'x' and 'y' store the minimum location
+// on each axis (i.e. bottom-left in cartesian coordinates, top-left
+// if you imagine y increasing downwards). Rectangles which do not fit
+// have the 'was_packed' flag set to 0.
+//
+// You should not try to access the 'rects' array from another thread
+// while this function is running, as the function temporarily reorders
+// the array while it executes.
+//
+// To pack into another rectangle, you need to call stbrp_init_target
+// again. To continue packing into the same rectangle, you can call
+// this function again. Calling this multiple times with multiple rect
+// arrays will probably produce worse packing results than calling it
+// a single time with the full rectangle array, but the option is
+// available.
+
+struct stbrp_rect
+{
+   // reserved for your use:
+   int            id;
+
+   // input:
+   stbrp_coord    w, h;
+
+   // output:
+   stbrp_coord    x, y;
+   int            was_packed;  // non-zero if valid packing
+
+}; // 16 bytes, nominally
+
+
+STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
+// Initialize a rectangle packer to:
+//    pack a rectangle that is 'width' by 'height' in dimensions
+//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long
+//
+// You must call this function every time you start packing into a new target.
+//
+// There is no "shutdown" function. The 'nodes' memory must stay valid for
+// the following stbrp_pack_rects() call (or calls), but can be freed after
+// the call (or calls) finish.
+//
+// Note: to guarantee best results, either:
+//       1. make sure 'num_nodes' >= 'width'
+//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
+//
+// If you don't do either of the above things, widths will be quantized to multiples
+// of small integers to guarantee the algorithm doesn't run out of temporary storage.
+//
+// If you do #2, then the non-quantized algorithm will be used, but the algorithm
+// may run out of temporary storage and be unable to pack some rectangles.
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
+// Optionally call this function after init but before doing any packing to
+// change the handling of the out-of-temp-memory scenario, described above.
+// If you call init again, this will be reset to the default (false).
+
+
+STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
+// Optionally select which packing heuristic the library should use. Different
+// heuristics will produce better/worse results for different data sets.
+// If you call init again, this will be reset to the default.
+
+enum
+{
+   STBRP_HEURISTIC_Skyline_default=0,
+   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
+   STBRP_HEURISTIC_Skyline_BF_sortHeight,
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// the details of the following structures don't matter to you, but they must
+// be visible so you can handle the memory allocations for them
+
+struct stbrp_node
+{
+   stbrp_coord  x,y;
+   stbrp_node  *next;
+};
+
+struct stbrp_context
+{
+   int width;
+   int height;
+   int align;
+   int init_mode;
+   int heuristic;
+   int num_nodes;
+   stbrp_node *active_head;
+   stbrp_node *free_head;
+   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//     IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#include <stdlib.h>
+
+#ifndef STBRP_ASSERT
+#include <assert.h>
+#define STBRP_ASSERT assert
+#endif
+
+enum
+{
+   STBRP__INIT_skyline = 1,
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+   switch (context->init_mode) {
+      case STBRP__INIT_skyline:
+         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+         context->heuristic = heuristic;
+         break;
+      default:
+         STBRP_ASSERT(0);
+   }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+   if (allow_out_of_mem)
+      // if it's ok to run out of memory, then don't bother aligning them;
+      // this gives better packing, but may fail due to OOM (even though
+      // the rectangles easily fit). @TODO a smarter approach would be to only
+      // quantize once we've hit OOM, then we could get rid of this parameter.
+      context->align = 1;
+   else {
+      // if it's not ok to run out of memory, then quantize the widths
+      // so that num_nodes is always enough nodes.
+      //
+      // I.e. num_nodes * align >= width
+      //                  align >= width / num_nodes
+      //                  align = ceil(width/num_nodes)
+
+      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+   }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+   int i;
+#ifndef STBRP_LARGE_RECTS
+   STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
+#endif
+
+   for (i=0; i < num_nodes-1; ++i)
+      nodes[i].next = &nodes[i+1];
+   nodes[i].next = NULL;
+   context->init_mode = STBRP__INIT_skyline;
+   context->heuristic = STBRP_HEURISTIC_Skyline_default;
+   context->free_head = &nodes[0];
+   context->active_head = &context->extra[0];
+   context->width = width;
+   context->height = height;
+   context->num_nodes = num_nodes;
+   stbrp_setup_allow_out_of_mem(context, 0);
+
+   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+   context->extra[0].x = 0;
+   context->extra[0].y = 0;
+   context->extra[0].next = &context->extra[1];
+   context->extra[1].x = (stbrp_coord) width;
+#ifdef STBRP_LARGE_RECTS
+   context->extra[1].y = (1<<30);
+#else
+   context->extra[1].y = 65535;
+#endif
+   context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+   stbrp_node *node = first;
+   int x1 = x0 + width;
+   int min_y, visited_width, waste_area;
+   STBRP_ASSERT(first->x <= x0);
+
+   #if 0
+   // skip in case we're past the node
+   while (node->next->x <= x0)
+      ++node;
+   #else
+   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+   #endif
+
+   STBRP_ASSERT(node->x <= x0);
+
+   min_y = 0;
+   waste_area = 0;
+   visited_width = 0;
+   while (node->x < x1) {
+      if (node->y > min_y) {
+         // raise min_y higher.
+         // we've accounted for all waste up to min_y,
+         // but we'll now add more waste for everything we've visted
+         waste_area += visited_width * (node->y - min_y);
+         min_y = node->y;
+         // the first time through, visited_width might be reduced
+         if (node->x < x0)
+            visited_width += node->next->x - x0;
+         else
+            visited_width += node->next->x - node->x;
+      } else {
+         // add waste area
+         int under_width = node->next->x - node->x;
+         if (under_width + visited_width > width)
+            under_width = width - visited_width;
+         waste_area += under_width * (min_y - node->y);
+         visited_width += under_width;
+      }
+      node = node->next;
+   }
+
+   *pwaste = waste_area;
+   return min_y;
+}
+
+typedef struct
+{
+   int x,y;
+   stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+   int best_waste = (1<<30), best_x, best_y = (1 << 30);
+   stbrp__findresult fr;
+   stbrp_node **prev, *node, *tail, **best = NULL;
+
+   // align to multiple of c->align
+   width = (width + c->align - 1);
+   width -= width % c->align;
+   STBRP_ASSERT(width % c->align == 0);
+
+   node = c->active_head;
+   prev = &c->active_head;
+   while (node->x + width <= c->width) {
+      int y,waste;
+      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+         // bottom left
+         if (y < best_y) {
+            best_y = y;
+            best = prev;
+         }
+      } else {
+         // best-fit
+         if (y + height <= c->height) {
+            // can only use it if it first vertically
+            if (y < best_y || (y == best_y && waste < best_waste)) {
+               best_y = y;
+               best_waste = waste;
+               best = prev;
+            }
+         }
+      }
+      prev = &node->next;
+      node = node->next;
+   }
+
+   best_x = (best == NULL) ? 0 : (*best)->x;
+
+   // if doing best-fit (BF), we also have to try aligning right edge to each node position
+   //
+   // e.g, if fitting
+   //
+   //     ____________________
+   //    |____________________|
+   //
+   //            into
+   //
+   //   |                         |
+   //   |             ____________|
+   //   |____________|
+   //
+   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+   //
+   // This makes BF take about 2x the time
+
+   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+      tail = c->active_head;
+      node = c->active_head;
+      prev = &c->active_head;
+      // find first node that's admissible
+      while (tail->x < width)
+         tail = tail->next;
+      while (tail) {
+         int xpos = tail->x - width;
+         int y,waste;
+         STBRP_ASSERT(xpos >= 0);
+         // find the left position that matches this
+         while (node->next->x <= xpos) {
+            prev = &node->next;
+            node = node->next;
+         }
+         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+         if (y + height < c->height) {
+            if (y <= best_y) {
+               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                  best_x = xpos;
+                  STBRP_ASSERT(y <= best_y);
+                  best_y = y;
+                  best_waste = waste;
+                  best = prev;
+               }
+            }
+         }
+         tail = tail->next;
+      }         
+   }
+
+   fr.prev_link = best;
+   fr.x = best_x;
+   fr.y = best_y;
+   return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+   // find best position according to heuristic
+   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+   stbrp_node *node, *cur;
+
+   // bail if:
+   //    1. it failed
+   //    2. the best node doesn't fit (we don't always check this)
+   //    3. we're out of memory
+   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+      res.prev_link = NULL;
+      return res;
+   }
+
+   // on success, create new node
+   node = context->free_head;
+   node->x = (stbrp_coord) res.x;
+   node->y = (stbrp_coord) (res.y + height);
+
+   context->free_head = node->next;
+
+   // insert the new node into the right starting point, and
+   // let 'cur' point to the remaining nodes needing to be
+   // stiched back in
+
+   cur = *res.prev_link;
+   if (cur->x < res.x) {
+      // preserve the existing one, so start testing with the next one
+      stbrp_node *next = cur->next;
+      cur->next = node;
+      cur = next;
+   } else {
+      *res.prev_link = node;
+   }
+
+   // from here, traverse cur and free the nodes, until we get to one
+   // that shouldn't be freed
+   while (cur->next && cur->next->x <= res.x + width) {
+      stbrp_node *next = cur->next;
+      // move the current node to the free list
+      cur->next = context->free_head;
+      context->free_head = cur;
+      cur = next;
+   }
+
+   // stitch the list back in
+   node->next = cur;
+
+   if (cur->x < res.x + width)
+      cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+   cur = context->active_head;
+   while (cur->x < context->width) {
+      STBRP_ASSERT(cur->x < cur->next->x);
+      cur = cur->next;
+   }
+   STBRP_ASSERT(cur->next == NULL);
+
+   {
+      stbrp_node *L1 = NULL, *L2 = NULL;
+      int count=0;
+      cur = context->active_head;
+      while (cur) {
+         L1 = cur;
+         cur = cur->next;
+         ++count;
+      }
+      cur = context->free_head;
+      while (cur) {
+         L2 = cur;
+         cur = cur->next;
+         ++count;
+      }
+      STBRP_ASSERT(count == context->num_nodes+2);
+   }
+#endif
+
+   return res;
+}
+
+static int rect_height_compare(const void *a, const void *b)
+{
+   stbrp_rect *p = (stbrp_rect *) a;
+   stbrp_rect *q = (stbrp_rect *) b;
+   if (p->h > q->h)
+      return -1;
+   if (p->h < q->h)
+      return  1;
+   return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int rect_width_compare(const void *a, const void *b)
+{
+   stbrp_rect *p = (stbrp_rect *) a;
+   stbrp_rect *q = (stbrp_rect *) b;
+   if (p->w > q->w)
+      return -1;
+   if (p->w < q->w)
+      return  1;
+   return (p->h > q->h) ? -1 : (p->h < q->h);
+}
+
+static int rect_original_order(const void *a, const void *b)
+{
+   stbrp_rect *p = (stbrp_rect *) a;
+   stbrp_rect *q = (stbrp_rect *) b;
+   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+#ifdef STBRP_LARGE_RECTS
+#define STBRP__MAXVAL  0xffffffff
+#else
+#define STBRP__MAXVAL  0xffff
+#endif
+
+STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+   int i;
+
+   // we use the 'was_packed' field internally to allow sorting/unsorting
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = i;
+      #ifndef STBRP_LARGE_RECTS
+      STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff);
+      #endif
+   }
+
+   // sort according to heuristic
+   qsort(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+   for (i=0; i < num_rects; ++i) {
+      stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+      if (fr.prev_link) {
+         rects[i].x = (stbrp_coord) fr.x;
+         rects[i].y = (stbrp_coord) fr.y;
+      } else {
+         rects[i].x = rects[i].y = STBRP__MAXVAL;
+      }
+   }
+
+   // unsort
+   qsort(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+   // set was_packed flags
+   for (i=0; i < num_rects; ++i)
+      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+}
+#endif

+ 1 - 0
Source/Tools/CMakeLists.txt

@@ -39,6 +39,7 @@ if (NOT IOS AND NOT ANDROID AND URHO3D_TOOLS)
     add_subdirectory (OgreImporter)
     add_subdirectory (PackageTool)
     add_subdirectory (RampGenerator)
+    add_subdirectory (SpritePacker)
     if (URHO3D_ANGELSCRIPT)
         add_subdirectory (ScriptCompiler)
     endif ()

+ 32 - 0
Source/Tools/SpritePacker/CMakeLists.txt

@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2008-2014 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME SpritePacker)
+
+# Define source files
+define_source_files ()
+
+include_directories(../../ThirdParty/STB)
+
+# Setup target
+setup_executable ()

+ 433 - 0
Source/Tools/SpritePacker/SpritePacker.cpp

@@ -0,0 +1,433 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <limits.h>
+
+#define STBRP_LARGE_RECTS
+#define STB_RECT_PACK_IMPLEMENTATION
+#include "stb_rect_pack.h"
+
+#include <Urho3D/Urho3D.h>
+
+#include <Urho3D/Core/Context.h>
+#include <Urho3D/Container/Vector.h>
+#include <Urho3D/Core/ProcessUtils.h>
+#include <Urho3D/Core/StringUtils.h>
+#include <Urho3D/Math/MathDefs.h>
+
+#include <Urho3D/Resource/Image.h>
+#include <Urho3D/IO/FileSystem.h>
+#include <Urho3D/IO/File.h>
+#include <Urho3D/Resource/XMLElement.h>
+#include <Urho3D/Resource/XMLFile.h>
+#include <Urho3D/IO/Log.h>
+#include <Urho3D/Math/Color.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <Urho3D/DebugNew.h>
+
+using namespace Urho3D;
+
+// number of nodes allocated to each packer info.  since this packer is not suited for real time purposes we can over allocate.
+const int PACKER_NUM_NODES = 4096;
+const int MAX_TEXTURE_SIZE = 2048;
+
+int main(int argc, char** argv);
+void Run(Vector<String>& arguments);
+
+class PackerInfo : public RefCounted
+{
+public:
+	String path;
+	String name;
+	int x;
+	int y;
+	int offsetX;
+	int offsetY;
+	int width;
+	int height;
+	int frameWidth;
+	int frameHeight;
+	int frameX;
+	int frameY;
+
+	PackerInfo(String path_, String name_) :
+		path(path_),
+		name(name_),
+		x(0),
+		y(0),
+		offsetX(0),
+		offsetY(0),
+		frameX(0),
+		frameY(0)
+	{
+	}
+
+	~PackerInfo() {}
+};
+
+void Help()
+{
+	ErrorExit("Usage: SpritePacker -options <input file> <input file> <output png file>\n"
+		"\n"
+		"Options:\n"
+		"-h Shows this help message.\n"
+		"-px Adds x pixels of padding per image to width.\n"
+		"-py Adds y pixels of padding per image to height.\n"
+		"-ox Adds x pixels to the horizontal position per image.\n"
+		"-oy Adds y pixels to the horizontal position per image.\n"
+		"-frameHeight Sets a fixed height for image and centers within frame.\n"
+		"-frameWidth Sets a fixed width for image and centers within frame.\n"
+		"-trim Trims excess transparent space from individual images offsets by frame size.\n"
+		"-xml \'path\' Generates an SpriteSheet xml file at path.\n"
+		"-debug Draws allocation boxes on sprite.\n");
+}
+
+int main(int argc, char** argv)
+{
+	Vector<String> arguments;
+
+#ifdef WIN32
+	arguments = ParseArguments(GetCommandLineW());
+#else
+	arguments = ParseArguments(argc, argv);
+#endif
+
+	Run(arguments);
+	return 0;
+}
+
+void Run(Vector<String>& arguments)
+{
+	if (arguments.Size() < 2)
+		Help();
+
+	SharedPtr<Context> context(new Context());
+	context->RegisterSubsystem(new FileSystem(context));
+	context->RegisterSubsystem(new Log(context));
+	FileSystem* fileSystem = context->GetSubsystem<FileSystem>();
+
+	Vector<String> inputFiles;
+	String outputFile;
+	String spriteSheetFileName;
+	bool debug = false;
+	unsigned padX = 0;
+	unsigned padY = 0;
+	unsigned offsetX = 0;
+	unsigned offsetY = 0;
+	unsigned frameWidth = 0;
+	unsigned frameHeight = 0;
+	bool help = false;
+	bool trim = false;
+
+	while (arguments.Size() > 0)
+	{
+		String arg = arguments[0];
+		arguments.Erase(0);
+
+		if (arg.Empty())
+			continue;
+
+		if (arg.StartsWith("-"))
+		{
+			if (arg == "-px")      { padX = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-py") { padY = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-ox") { offsetX = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-oy") { offsetY = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-frameWidth") { frameWidth = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-frameHeight") { frameHeight = ToUInt(arguments[0]); arguments.Erase(0); }
+			else if (arg == "-trim") { trim = true; }
+			else if (arg == "-xml")  { spriteSheetFileName = arguments[0]; arguments.Erase(0); }
+			else if (arg == "-h")  { help = true; break; }
+			else if (arg == "-debug")  { debug = true; }
+		}
+		else
+			inputFiles.Push(arg);
+	}
+
+	if (help)
+		Help();
+
+	if (inputFiles.Size() < 2)
+		ErrorExit("An input and output file must be specified.");
+
+	if (frameWidth ^ frameHeight)
+		ErrorExit("Both frameHeight and frameWidth must be ommited or specified.");
+
+	// take last input file as output
+	if (inputFiles.Size() > 1)
+	{
+		outputFile = inputFiles[inputFiles.Size() - 1];
+		LOGINFO("Output file set to " + outputFile + ".");
+		inputFiles.Erase(inputFiles.Size() - 1);
+	}
+
+	// set spritesheet name to outputfile.xml if not specified
+	if (spriteSheetFileName.Empty())
+		spriteSheetFileName = ReplaceExtension(outputFile, ".xml");
+
+	if (GetParentPath(spriteSheetFileName) != GetParentPath(outputFile))
+		ErrorExit("Both output xml and png must be in the same folder");
+
+	// check all input files exist
+	for (unsigned i = 0; i < inputFiles.Size(); ++i)
+	{
+		LOGINFO("Checking " + inputFiles[i] + " to see if file exists.");
+		if (!fileSystem->FileExists(inputFiles[i]))
+			ErrorExit("File " + inputFiles[i] + " does not exist.");
+	}
+
+	// Set the max offset equal to padding to prevent images from going out of bounds
+	offsetX = Min((int)offsetX, (int)padX);
+	offsetY = Min((int)offsetY, (int)padY);
+
+	Vector<SharedPtr<PackerInfo > > packerInfos;
+
+	for (unsigned i = 0; i < inputFiles.Size(); ++i)
+	{
+		String path = inputFiles[i];
+		String name = ReplaceExtension(GetFileName(path), "");
+		File file(context, path);
+		Image image(context);
+
+		if (!image.Load(file))
+			ErrorExit("Could not load image " + path + ".");
+
+		if (image.IsCompressed())
+			ErrorExit(path + " is compressed. Compressed images are not allowed.");
+
+		SharedPtr<PackerInfo> packerInfo(new PackerInfo(path, name));
+		int imageWidth = image.GetWidth();
+		int imageHeight = image.GetHeight();
+		int trimOffsetX = 0;
+		int trimOffsetY = 0;
+		int adjustedWidth = imageWidth;
+		int adjustedHeight = imageHeight;
+
+		if (trim)
+		{
+			int minX = imageWidth;
+			int minY = imageHeight;
+			int maxX = 0;
+			int maxY = 0;
+
+			for (int y = 0; y < imageHeight; ++y)
+			{
+				for (int x = 0; x < imageWidth; ++x)
+				{
+					bool found = (image.GetPixelInt(x, y) & 0x000000ff) != 0;
+					if (found) {
+						minX = Min(minX, x);
+						minY = Min(minY, y);
+						maxX = Max(maxX, x);
+						maxY = Max(maxY, y);
+					}
+				}
+			}
+
+			trimOffsetX = minX;
+			trimOffsetY = minY;
+			adjustedWidth = maxX - minX + 1;
+			adjustedHeight = maxY - minY + 1;
+		}
+
+		if (trim)
+		{
+			packerInfo->frameWidth = imageWidth;
+			packerInfo->frameHeight = imageHeight;
+		}
+		else if (frameWidth || frameHeight)
+		{
+			packerInfo->frameWidth = frameWidth;
+			packerInfo->frameHeight = frameHeight;
+		}
+		packerInfo->width = adjustedWidth;
+		packerInfo->height = adjustedHeight;
+		packerInfo->offsetX -= trimOffsetX;
+		packerInfo->offsetY -= trimOffsetY;
+		packerInfos.Push(packerInfo);
+	}
+
+	int packedWidth = MAX_TEXTURE_SIZE;
+	int packedHeight = MAX_TEXTURE_SIZE;
+	{
+		// fill up an list of tries in increasing size and take the first win
+		Vector<IntVector2> tries;
+		for(unsigned x=2; x<11; ++x)
+		{
+			for(unsigned y=2; y<11; ++y)
+				tries.Push(IntVector2((1<<x), (1<<y)));
+		}
+
+		// load rectangles
+		stbrp_rect* packerRects = new stbrp_rect[packerInfos.Size()];
+		for (unsigned i = 0; i < packerInfos.Size(); ++i)
+		{
+			PackerInfo* packerInfo = packerInfos[i];
+			stbrp_rect* packerRect = &packerRects[i];
+			packerRect->id = i;
+			packerRect->h = packerInfo->height + padY;
+			packerRect->w = packerInfo->width + padX;
+		}
+
+		bool success = false;
+		while (tries.Size() > 0)
+		{
+			IntVector2 size = tries[0];
+			tries.Erase(0);
+			bool fit = true;
+			int textureHeight = size.y_;
+			int textureWidth = size.x_;
+			if (success && textureHeight * textureWidth > packedWidth * packedHeight)
+				continue;
+
+			stbrp_context packerContext;
+			stbrp_node packerMemory[PACKER_NUM_NODES];
+			stbrp_init_target(&packerContext, textureWidth, textureHeight, packerMemory, packerInfos.Size());
+			stbrp_pack_rects(&packerContext, packerRects, packerInfos.Size());
+
+			// check to see if everything fit
+			for (unsigned i = 0; i < packerInfos.Size(); ++i)
+			{
+				stbrp_rect* packerRect = &packerRects[i];
+				if (!packerRect->was_packed)
+				{
+					fit = false;
+					break;
+				}
+			}
+			if (fit)
+			{
+				success = true;
+				// distribute values to packer info
+				for (unsigned i = 0; i < packerInfos.Size(); ++i)
+				{
+					stbrp_rect* packerRect = &packerRects[i];
+					PackerInfo* packerInfo = packerInfos[packerRect->id];
+					packerInfo->x = packerRect->x;
+					packerInfo->y = packerRect->y;
+				}
+				packedWidth = size.x_;
+				packedHeight = size.y_;
+			}
+		}
+		delete packerRects;
+		if (!success)
+			ErrorExit("Could not allocate for all images.  The max sprite sheet texture size is " + String(MAX_TEXTURE_SIZE) + "x" + String(MAX_TEXTURE_SIZE) + ".");
+	}
+
+
+	// create image for spritesheet
+	Image spriteSheetImage(context);
+	spriteSheetImage.SetSize(packedWidth, packedHeight, 4);
+
+	// zero out image
+	spriteSheetImage.SetData((unsigned char*)calloc(sizeof(unsigned char), packedWidth * packedHeight * 4));
+
+	XMLFile xml(context);
+	XMLElement root = xml.CreateRoot("TextureAtlas");
+	root.SetAttribute("imagePath", GetFileNameAndExtension(outputFile));
+
+	for (unsigned i = 0; i < packerInfos.Size(); ++i)
+	{
+		SharedPtr<PackerInfo> packerInfo = packerInfos[i];
+		XMLElement subTexture = root.CreateChild("SubTexture");
+		subTexture.SetString("name", packerInfo->name);
+		subTexture.SetInt("x", packerInfo->x + offsetX);
+		subTexture.SetInt("y", packerInfo->y + offsetY);
+		subTexture.SetInt("width", packerInfo->width);
+		subTexture.SetInt("height", packerInfo->height);
+
+		if (packerInfo->frameWidth || packerInfo->frameHeight)
+		{
+			subTexture.SetInt("frameWidth", packerInfo->frameWidth);
+			subTexture.SetInt("frameHeight", packerInfo->frameHeight);
+			subTexture.SetInt("offsetX", packerInfo->offsetX);
+			subTexture.SetInt("offsetY", packerInfo->offsetY);
+		}
+
+		LOGINFO("Transfering " + packerInfo->path + " to sprite sheet.");
+
+		File file(context, packerInfo->path);
+		Image image(context);
+		if (!image.Load(file))
+			ErrorExit("Could not load image " + packerInfo->path + ".");
+
+		for (int y = 0; y < packerInfo->height; ++y)
+		{
+			for (int x = 0; x < packerInfo->width; ++x)
+			{
+				unsigned color = image.GetPixelInt(x - packerInfo->offsetX, y - packerInfo->offsetY);
+				spriteSheetImage.SetPixelInt(
+					packerInfo->x + offsetX + x,
+					packerInfo->y + offsetY + y, color);
+			}
+		}
+	}
+
+	if (debug)
+	{
+		unsigned OUTER_BOUNDS_DEBUG_COLOR = Color::BLUE.ToUInt();
+		unsigned INNER_BOUNDS_DEBUG_COLOR = Color::GREEN.ToUInt();
+
+		LOGINFO("Drawing debug information.");
+		for (unsigned i = 0; i < packerInfos.Size(); ++i)
+		{
+			SharedPtr<PackerInfo> packerInfo = packerInfos[i];
+
+			// Draw outer bounds
+			for (int x = 0; x < packerInfo->frameWidth; ++x)
+			{
+				spriteSheetImage.SetPixelInt(packerInfo->x + x, packerInfo->y, OUTER_BOUNDS_DEBUG_COLOR);
+				spriteSheetImage.SetPixelInt(packerInfo->x + x, packerInfo->y + packerInfo->frameHeight, OUTER_BOUNDS_DEBUG_COLOR);
+			}
+			for (int y = 0; y < packerInfo->frameHeight; ++y)
+			{
+				spriteSheetImage.SetPixelInt(packerInfo->x, packerInfo->y + y, OUTER_BOUNDS_DEBUG_COLOR);
+				spriteSheetImage.SetPixelInt(packerInfo->x + packerInfo->frameWidth, packerInfo->y + y, OUTER_BOUNDS_DEBUG_COLOR);
+			}
+
+			// Draw inner bounds
+			for (int x = 0; x < packerInfo->width; ++x)
+			{
+				spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + x, packerInfo->y + offsetY, INNER_BOUNDS_DEBUG_COLOR);
+				spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + x, packerInfo->y + offsetY + packerInfo->height, INNER_BOUNDS_DEBUG_COLOR);
+			}
+			for (int y = 0; y < packerInfo->height; ++y)
+			{
+				spriteSheetImage.SetPixelInt(packerInfo->x + offsetX, packerInfo->y + offsetY + y, INNER_BOUNDS_DEBUG_COLOR);
+				spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + packerInfo->width, packerInfo->y + offsetY + y, INNER_BOUNDS_DEBUG_COLOR);
+			}
+		}
+	}
+
+	LOGINFO("Saving output image.");
+	spriteSheetImage.SavePNG(outputFile);
+
+	LOGINFO("Saving SpriteSheet xml file.");
+	File spriteSheetFile(context);
+	spriteSheetFile.Open(spriteSheetFileName, FILE_WRITE);
+	xml.Save(spriteSheetFile);
+}