added stb, more binaryout changes"
[henge/apc.git] / stb / stb_herringbone_wang_tile.h
diff --git a/stb/stb_herringbone_wang_tile.h b/stb/stb_herringbone_wang_tile.h
new file mode 100644 (file)
index 0000000..ba2cf60
--- /dev/null
@@ -0,0 +1,1220 @@
+/* stbhw - v0.6 -  http://nothings.org/gamedev/herringbone
+   Herringbone Wang Tile Generator - Sean Barrett 2014 - public domain
+
+== LICENSE ==============================
+
+This software is dual-licensed to the public domain and under the following
+license: you are granted a perpetual, irrevocable license to copy, modify,
+publish, and distribute this file as you see fit.
+
+== WHAT IT IS ===========================
+
+ This library is an SDK for Herringbone Wang Tile generation:
+
+      http://nothings.org/gamedev/herringbone
+
+ The core design is that you use this library offline to generate a
+ "template" of the tiles you'll create. You then edit those tiles, then
+ load the created tile image file back into this library and use it at
+ runtime to generate "maps".
+ You cannot load arbitrary tile image files with this library; it is
+ only designed to load image files made from the template it created.
+ It stores a binary description of the tile sizes & constraints in a
+ few pixels, and uses those to recover the rules, rather than trying
+ to parse the tiles themselves.
+ You *can* use this library to generate from arbitrary tile sets, but
+ only by loading the tile set and specifying the constraints explicitly
+ yourself.
+
+== COMPILING ============================
+
+ 1. #define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION before including this
+    header file in *one* source file to create the implementation
+    in that source file.
+
+ 2. optionally #define STB_HBWANG_RAND() to be a random number
+    generator. if you don't define it, it will use rand(),
+    and you need to seed srand() yourself.
+
+ 3. optionally #define STB_HBWANG_ASSERT(x), otherwise
+    it will use assert()
+
+ 4. optionally #define STB_HBWANG_STATIC to force all symbols to be
+    static instead of public, so they are only accesible
+    in the source file that creates the implementation
+
+ 5. optionally #define STB_HBWANG_NO_REPITITION_REDUCTION to disable
+    the code that tries to reduce having the same tile appear
+    adjacent to itself in wang-corner-tile mode (e.g. imagine
+    if you were doing something where 90% of things should be
+    the same grass tile, you need to disable this system)
+
+ 6. optionally define STB_HBWANG_MAX_X and STB_HBWANG_MAX_Y
+    to be the max dimensions of the generated map in multiples
+    of the wang tile's short side's length (e.g. if you
+    have 20x10 wang tiles, so short_side_len=10, and you
+    have MAX_X is 17, then the largest map you can generate
+    is 170 pixels wide). The defaults are 100x100. This
+    is used to define static arrays which affect memory
+    usage.
+
+== USING ================================
+
+  To use the map generator, you need a tileset. You can download
+  some sample tilesets from http://nothings.org/gamedev/herringbone
+
+  Then see the "sample application" below.
+
+  You can also use this file to generate templates for
+  tilesets which you then hand-edit to create the data.
+
+
+== MEMORY MANAGEMENT ====================
+
+  The tileset loader allocates memory with malloc(). The map
+  generator does no memory allocation, so e.g. you can load
+  tilesets at startup and never free them and never do any
+  further allocation.
+
+
+== SAMPLE APPLICATION ===================
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"        // http://nothings.org/stb_image.c
+
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"  // http://nothings.org/stb/stb_image_write.h
+
+#define STB_HBWANG_IMPLEMENTATION
+#include "stb_hbwang.h"
+
+int main(int argc, char **argv)
+{
+   unsigned char *data;
+   int xs,ys, w,h;
+   stbhw_tileset ts;
+
+   if (argc != 4) {
+      fprintf(stderr, "Usage: mapgen {tile-file} {xsize} {ysize}\n"
+                      "generates file named 'test_map.png'\n");
+      exit(1);
+   }
+   data = stbi_load(argv[1], &w, &h, NULL, 3);
+   xs = atoi(argv[2]);
+   ys = atoi(argv[3]);
+   if (data == NULL) {
+      fprintf(stderr, "Error opening or parsing '%s' as an image file\n", argv[1]);
+      exit(1);
+   }
+   if (xs < 1 || xs > 1000) {
+      fprintf(stderr, "xsize invalid or out of range\n");
+      exit(1);
+   }
+   if (ys < 1 || ys > 1000) {
+      fprintf(stderr, "ysize invalid or out of range\n");
+      exit(1);
+   }
+
+   stbhw_build_tileset_from_image(&ts, data, w*3, w, h);
+   free(data);
+
+   // allocate a buffer to create the final image to
+   data = malloc(3 * xs * ys);
+
+   srand(time(NULL));
+   stbhw_generate_image(&ts, NULL, data, xs*3, xs, ys);
+
+   stbi_write_png("test_map.png", xs, ys, 3, data, xs*3);
+
+   stbhw_free_tileset(&ts);
+   free(data);
+
+   return 0;
+}
+
+== VERSION HISTORY ===================
+
+       0.6   2014-08-17   - fix broken map-maker
+       0.5   2014-07-07   - initial release 
+
+*/
+
+//////////////////////////////////////////////////////////////////////////////
+//                                                                          //
+//                         HEADER FILE SECTION                              //
+//                                                                          //
+
+#ifndef INCLUDE_STB_HWANG_H
+#define INCLUDE_STB_HWANG_H
+
+#ifdef STB_HBWANG_STATIC
+#define STBHW_EXTERN static
+#else
+#ifdef __cplusplus
+#define STBHW_EXTERN extern "C"
+#else
+#define STBHW_EXTERN extern
+#endif
+#endif
+
+typedef struct stbhw_tileset stbhw_tileset;
+
+// returns description of last error produced by any function (not thread-safe)
+STBHW_EXTERN char *stbhw_get_last_error(void);
+
+// build a tileset from an image that conforms to a template created by this
+// library. (you allocate storage for stbhw_tileset and function fills it out;
+// memory for individual tiles are malloc()ed).
+// returns non-zero on success, 0 on error
+STBHW_EXTERN int stbhw_build_tileset_from_image(stbhw_tileset *ts,
+                     unsigned char *pixels, int stride_in_bytes, int w, int h);
+
+// free a tileset built by stbhw_build_tileset_from_image
+STBHW_EXTERN void stbhw_free_tileset(stbhw_tileset *ts);
+
+// generate a map that is w * h pixels (3-bytes each)
+// returns non-zero on success, 0 on error
+// not thread-safe (uses a global data structure to avoid memory management)
+// weighting should be NULL, as non-NULL weighting is currently untested
+STBHW_EXTERN int stbhw_generate_image(stbhw_tileset *ts, int **weighting,
+                     unsigned char *pixels, int stride_in_bytes, int w, int h);
+
+//////////////////////////////////////
+//
+// TILESET DATA STRUCTURE
+//
+// if you use the image-to-tileset system from this file, you
+// don't need to worry about these data structures. but if you
+// want to build/load a tileset yourself, you'll need to fill
+// these out.
+
+typedef struct
+{
+   // the edge or vertex constraints, according to diagram below
+   signed char a,b,c,d,e,f;
+
+   // The herringbone wang tile data; it is a bitmap which is either
+   // w=2*short_sidelen,h=short_sidelen, or w=short_sidelen,h=2*short_sidelen.
+   // it is always RGB, stored row-major, with no padding between rows.
+   // (allocate stbhw_tile structure to be large enough for the pixel data)
+   unsigned char pixels[1];
+} stbhw_tile;
+
+struct stbhw_tileset
+{
+   int is_corner;
+   int num_color[6];  // number of colors for each of 6 edge types or 4 corner types
+   int short_side_len;
+   stbhw_tile **h_tiles;
+   stbhw_tile **v_tiles;
+   int num_h_tiles, max_h_tiles;
+   int num_v_tiles, max_v_tiles;
+};
+
+///////////////  TEMPLATE GENERATOR  //////////////////////////
+
+// when requesting a template, you fill out this data
+typedef struct
+{
+   int is_corner;      // using corner colors or edge colors?
+   int short_side_len; // rectangles is 2n x n, n = short_side_len
+   int num_color[6];   // see below diagram for meaning of the index to this;
+                       // 6 values if edge (!is_corner), 4 values if is_corner
+                       // legal numbers: 1..8 if edge, 1..4 if is_corner
+   int num_vary_x;     // additional number of variations along x axis in the template
+   int num_vary_y;     // additional number of variations along y axis in the template
+   int corner_type_color_template[4][4];
+      // if corner_type_color_template[s][t] is non-zero, then any
+      // corner of type s generated as color t will get a little
+      // corner sample markup in the template image data
+
+} stbhw_config;
+
+// computes the size needed for the template image
+STBHW_EXTERN void stbhw_get_template_size(stbhw_config *c, int *w, int *h);
+
+// generates a template image, assuming data is 3*w*h bytes long, RGB format
+STBHW_EXTERN int stbhw_make_template(stbhw_config *c, unsigned char *data, int w, int h, int stride_in_bytes);
+
+#endif//INCLUDE_STB_HWANG_H
+
+
+// TILE CONSTRAINT TYPES
+//
+// there are 4 "types" of corners and 6 types of edges.
+// you can configure the tileset to have different numbers
+// of colors for each type of color or edge.
+// 
+// corner types:
+//
+//                     0---*---1---*---2---*---3
+//                     |       |               |
+//                     *       *               *
+//                     |       |               |
+//     1---*---2---*---3       0---*---1---*---2
+//     |               |       |
+//     *               *       *
+//     |               |       |
+//     0---*---1---*---2---*---3
+//
+//
+//  edge types:
+//
+//     *---2---*---3---*      *---0---*
+//     |               |      |       |
+//     1               4      5       1
+//     |               |      |       |
+//     *---0---*---2---*      *       *
+//                            |       |
+//                            4       5
+//                            |       |
+//                            *---3---*
+//
+// TILE CONSTRAINTS
+//
+// each corner/edge has a color; this shows the name
+// of the variable containing the color
+//
+// corner constraints:
+//
+//                        a---*---d
+//                        |       |
+//                        *       *
+//                        |       |
+//     a---*---b---*---c  b       e
+//     |               |  |       |
+//     *               *  *       *
+//     |               |  |       |
+//     d---*---e---*---f  c---*---f
+//
+//
+//  edge constraints:
+//
+//     *---a---*---b---*      *---a---*
+//     |               |      |       |
+//     c               d      b       c
+//     |               |      |       |
+//     *---e---*---f---*      *       *
+//                            |       |
+//                            d       e
+//                            |       |
+//                            *---f---*
+//
+
+
+//////////////////////////////////////////////////////////////////////////////
+//                                                                          //
+//                       IMPLEMENTATION SECTION                             //
+//                                                                          //
+
+#ifdef STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION
+
+
+#include <string.h> // memcpy
+#include <stdlib.h> // malloc
+
+#ifndef STB_HBWANG_RAND
+#include <stdlib.h>
+#define STB_HBWANG_RAND()  (rand() >> 4)
+#endif
+
+#ifndef STB_HBWANG_ASSERT
+#include <assert.h>
+#define STB_HBWANG_ASSERT(x)  assert(x)
+#endif
+
+// map size
+#ifndef STB_HBWANG_MAX_X
+#define STB_HBWANG_MAX_X  100
+#endif
+
+#ifndef STB_HBWANG_MAX_Y
+#define STB_HBWANG_MAX_Y  100
+#endif
+
+// global variables for color assignments
+// @MEMORY change these to just store last two/three rows
+//         and keep them on the stack
+static signed char c_color[STB_HBWANG_MAX_Y+6][STB_HBWANG_MAX_X+6];
+static signed char v_color[STB_HBWANG_MAX_Y+6][STB_HBWANG_MAX_X+5];
+static signed char h_color[STB_HBWANG_MAX_Y+5][STB_HBWANG_MAX_X+6];
+
+static char *stbhw_error;
+STBHW_EXTERN char *stbhw_get_last_error(void)
+{
+   char *temp = stbhw_error;
+   stbhw_error = 0;
+   return temp;
+}
+
+
+
+
+/////////////////////////////////////////////////////////////
+//
+//  SHARED TEMPLATE-DESCRIPTION CODE
+//
+//  Used by both template generator and tileset parser; by
+//  using the same code, they are locked in sync and we don't
+//  need to try to do more sophisticated parsing of edge color
+//  markup or something.
+
+typedef void stbhw__process_rect(struct stbhw__process *p, int xpos, int ypos,
+                                 int a, int b, int c, int d, int e, int f);
+
+typedef struct stbhw__process
+{
+   stbhw_tileset *ts;
+   stbhw_config *c;
+   stbhw__process_rect *process_h_rect;
+   stbhw__process_rect *process_v_rect;
+   unsigned char *data;
+   int stride,w,h;
+} stbhw__process;
+
+static void stbhw__process_h_row(stbhw__process *p,
+                           int xpos, int ypos,
+                           int a0, int a1,
+                           int b0, int b1,
+                           int c0, int c1,
+                           int d0, int d1,
+                           int e0, int e1,
+                           int f0, int f1,
+                           int variants)
+{
+   int a,b,c,d,e,f,v;
+
+   for (v=0; v < variants; ++v)
+      for (f=f0; f <= f1; ++f)
+         for (e=e0; e <= e1; ++e)
+            for (d=d0; d <= d1; ++d)
+               for (c=c0; c <= c1; ++c)
+                  for (b=b0; b <= b1; ++b)
+                     for (a=a0; a <= a1; ++a) {
+                        p->process_h_rect(p, xpos, ypos, a,b,c,d,e,f);
+                        xpos += 2*p->c->short_side_len + 3;
+                     }
+}
+
+static void stbhw__process_v_row(stbhw__process *p,
+                           int xpos, int ypos,
+                           int a0, int a1,
+                           int b0, int b1,
+                           int c0, int c1,
+                           int d0, int d1,
+                           int e0, int e1,
+                           int f0, int f1,
+                           int variants)
+{
+   int a,b,c,d,e,f,v;
+
+   for (v=0; v < variants; ++v)
+      for (f=f0; f <= f1; ++f)
+         for (e=e0; e <= e1; ++e)
+            for (d=d0; d <= d1; ++d)
+               for (c=c0; c <= c1; ++c)
+                  for (b=b0; b <= b1; ++b)
+                     for (a=a0; a <= a1; ++a) {
+                        p->process_v_rect(p, xpos, ypos, a,b,c,d,e,f);
+                        xpos += p->c->short_side_len+3;
+                     }
+}
+
+static void stbhw__get_template_info(stbhw_config *c, int *w, int *h, int *h_count, int *v_count)
+{
+   int size_x,size_y;
+   int horz_count,vert_count;
+
+   if (c->is_corner) {
+      int horz_w = c->num_color[1] * c->num_color[2] * c->num_color[3] * c->num_vary_x;
+      int horz_h = c->num_color[0] * c->num_color[1] * c->num_color[2] * c->num_vary_y;
+
+      int vert_w = c->num_color[0] * c->num_color[3] * c->num_color[2] * c->num_vary_y;
+      int vert_h = c->num_color[1] * c->num_color[0] * c->num_color[3] * c->num_vary_x;
+
+      int horz_x = horz_w * (2*c->short_side_len + 3);
+      int horz_y = horz_h * (  c->short_side_len + 3);
+
+      int vert_x = vert_w * (  c->short_side_len + 3);
+      int vert_y = vert_h * (2*c->short_side_len + 3);
+
+      horz_count = horz_w * horz_h;
+      vert_count = vert_w * vert_h;
+
+      size_x = horz_x > vert_x ? horz_x : vert_x;
+      size_y = 2 + horz_y + 2 + vert_y;
+   } else {
+      int horz_w = c->num_color[0] * c->num_color[1] * c->num_color[2] * c->num_vary_x;
+      int horz_h = c->num_color[3] * c->num_color[4] * c->num_color[2] * c->num_vary_y;
+
+      int vert_w = c->num_color[0] * c->num_color[5] * c->num_color[1] * c->num_vary_y;
+      int vert_h = c->num_color[3] * c->num_color[4] * c->num_color[5] * c->num_vary_x;
+
+      int horz_x = horz_w * (2*c->short_side_len + 3);
+      int horz_y = horz_h * (  c->short_side_len + 3);
+
+      int vert_x = vert_w * (  c->short_side_len + 3);
+      int vert_y = vert_h * (2*c->short_side_len + 3);
+
+      horz_count = horz_w * horz_h;
+      vert_count = vert_w * vert_h;
+
+      size_x = horz_x > vert_x ? horz_x : vert_x;
+      size_y = 2 + horz_y + 2 + vert_y;
+   }
+   if (w) *w = size_x;
+   if (h) *h = size_y;
+   if (h_count) *h_count = horz_count;
+   if (v_count) *v_count = vert_count;
+}
+
+STBHW_EXTERN void stbhw_get_template_size(stbhw_config *c, int *w, int *h)
+{
+   stbhw__get_template_info(c, w, h, NULL, NULL);
+}
+
+static int stbhw__process_template(stbhw__process *p)
+{
+   int i,j,k,q, ypos;
+   int size_x, size_y;
+   stbhw_config *c = p->c;
+
+   stbhw__get_template_info(c, &size_x, &size_y, NULL, NULL);
+
+   if (p->w < size_x || p->h < size_y) {
+      stbhw_error = "image too small for configuration";
+      return 0;
+   }
+
+   if (c->is_corner) {
+      ypos = 2;
+      for (k=0; k < c->num_color[2]; ++k) {
+         for (j=0; j < c->num_color[1]; ++j) {
+            for (i=0; i < c->num_color[0]; ++i) {
+               for (q=0; q < c->num_vary_y; ++q) {
+                  stbhw__process_h_row(p, 0,ypos,
+                     0,c->num_color[1]-1, 0,c->num_color[2]-1, 0,c->num_color[3]-1,
+                     i,i, j,j, k,k,
+                     c->num_vary_x);
+                  ypos += c->short_side_len + 3;
+               }
+            }
+         }
+      }
+      ypos += 2;
+      for (k=0; k < c->num_color[3]; ++k) {
+         for (j=0; j < c->num_color[0]; ++j) {
+            for (i=0; i < c->num_color[1]; ++i) {
+               for (q=0; q < c->num_vary_x; ++q) {
+                  stbhw__process_v_row(p, 0,ypos, 
+                     0,c->num_color[0]-1, 0,c->num_color[3]-1, 0,c->num_color[2]-1,
+                     i,i, j,j, k,k,
+                     c->num_vary_y);
+                  ypos += (c->short_side_len*2) + 3;
+               }
+            }
+         }
+      }
+      assert(ypos == size_y);
+   } else {
+      ypos = 2;
+      for (k=0; k < c->num_color[3]; ++k) {
+         for (j=0; j < c->num_color[4]; ++j) {
+            for (i=0; i < c->num_color[2]; ++i) {
+               for (q=0; q < c->num_vary_y; ++q) {
+                  stbhw__process_h_row(p, 0,ypos,
+                     0,c->num_color[2]-1, k,k,
+                     0,c->num_color[1]-1, j,j,
+                     0,c->num_color[0]-1, i,i,
+                     c->num_vary_x);
+                  ypos += c->short_side_len + 3;
+               }
+            }
+         }
+      }
+      ypos += 2;
+      for (k=0; k < c->num_color[3]; ++k) {
+         for (j=0; j < c->num_color[4]; ++j) {
+            for (i=0; i < c->num_color[5]; ++i) {
+               for (q=0; q < c->num_vary_x; ++q) {
+                  stbhw__process_v_row(p, 0,ypos,
+                     0,c->num_color[0]-1, i,i,
+                     0,c->num_color[1]-1, j,j,
+                     0,c->num_color[5]-1, k,k,
+                     c->num_vary_y);
+                  ypos += (c->short_side_len*2) + 3;
+               }
+            }
+         }
+      }
+      assert(ypos == size_y);
+   }
+   return 1;
+}
+
+
+/////////////////////////////////////////////////////////////
+//
+//  MAP GENERATOR
+//
+
+static void stbhw__draw_pixel(unsigned char *output, int stride, int x, int y, unsigned char c[3])
+{
+   memcpy(output + y*stride + x*3, c, 3);
+}
+
+static void stbhw__draw_h_tile(unsigned char *output, int stride, int xmax, int ymax, int x, int y, stbhw_tile *h, int sz)
+{
+   int i,j;
+   for (j=0; j < sz; ++j)
+      if (y+j >= 0 && y+j < ymax)
+         for (i=0; i < sz*2; ++i)
+            if (x+i >= 0 && x+i < xmax)
+               stbhw__draw_pixel(output,stride, x+i,y+j, &h->pixels[(j*sz*2 + i)*3]);
+}
+
+static void stbhw__draw_v_tile(unsigned char *output, int stride, int xmax, int ymax, int x, int y, stbhw_tile *h, int sz)
+{
+   int i,j;
+   for (j=0; j < sz*2; ++j)
+      if (y+j >= 0 && y+j < ymax)
+         for (i=0; i < sz; ++i)
+            if (x+i >= 0 && x+i < xmax)
+               stbhw__draw_pixel(output,stride, x+i,y+j, &h->pixels[(j*sz + i)*3]);
+}
+
+
+// randomly choose a tile that fits constraints for a given spot, and update the constraints
+static stbhw_tile * stbhw__choose_tile(stbhw_tile **list, int numlist,
+                                      signed char *a, signed char *b, signed char *c,
+                                      signed char *d, signed char *e, signed char *f,
+                                      int **weighting)
+{
+   int i,n,m = 1<<30,pass;
+   for (pass=0; pass < 2; ++pass) {
+      n=0;
+      // pass #1:
+      //   count number of variants that match this partial set of constraints
+      // pass #2:
+      //   stop on randomly selected match
+      for (i=0; i < numlist; ++i) {
+         stbhw_tile *h = list[i];
+         if ((*a < 0 || *a == h->a) &&
+             (*b < 0 || *b == h->b) &&
+             (*c < 0 || *c == h->c) &&
+             (*d < 0 || *d == h->d) &&
+             (*e < 0 || *e == h->e) &&
+             (*f < 0 || *f == h->f)) {
+            if (weighting)
+               n += weighting[0][i];
+            else
+               n += 1;
+            if (n > m) {
+               // use list[i]
+               // update constraints to reflect what we placed
+               *a = h->a;
+               *b = h->b;
+               *c = h->c;
+               *d = h->d;
+               *e = h->e;
+               *f = h->f;
+               return h;
+            }
+         }
+      }
+      if (n == 0) {
+         stbhw_error = "couldn't find tile matching constraints";
+         return NULL;
+      }
+      m = STB_HBWANG_RAND() % n;
+   }
+   STB_HBWANG_ASSERT(0);
+   return NULL;
+}
+
+static int stbhw__match(int x, int y)
+{
+   return c_color[y][x] == c_color[y+1][x+1];
+}
+
+static int stbhw__weighted(int num_options, int *weights)
+{
+   int k, total, choice;
+   total = 0;
+   for (k=0; k < num_options; ++k)
+      total += weights[k];
+   choice = STB_HBWANG_RAND() % total;
+   total = 0;
+   for (k=0; k < num_options; ++k) {
+      total += weights[k];
+      if (choice < total)
+         break;
+   }
+   STB_HBWANG_ASSERT(k < num_options);
+   return k;
+}
+
+static int stbhw__change_color(int old_color, int num_options, int *weights)
+{
+   if (weights) {
+      int k, total, choice;
+      total = 0;
+      for (k=0; k < num_options; ++k)
+         if (k != old_color)
+            total += weights[k];
+      choice = STB_HBWANG_RAND() % total;
+      total = 0;
+      for (k=0; k < num_options; ++k) {
+         if (k != old_color) {
+            total += weights[k];
+            if (choice < total)
+               break;
+         }
+      }
+      STB_HBWANG_ASSERT(k < num_options);
+      return k;
+   } else {
+      int offset = 1+STB_HBWANG_RAND() % (num_options-1);
+      return (old_color+offset) % num_options;
+   }
+}
+
+
+
+// generate a map that is w * h pixels (3-bytes each)
+// returns 1 on success, 0 on error 
+STBHW_EXTERN int stbhw_generate_image(stbhw_tileset *ts, int **weighting, unsigned char *output, int stride, int w, int h)
+{
+   int sidelen = ts->short_side_len;
+   int xmax = (w / sidelen) + 6;
+   int ymax = (h / sidelen) + 6;
+   if (xmax > STB_HBWANG_MAX_X+6 || ymax > STB_HBWANG_MAX_Y+6) {
+      stbhw_error = "increase STB_HBWANG_MAX_X/Y";
+      return 0;
+   }
+
+   if (ts->is_corner) {
+      int i,j, ypos;
+      int *cc = ts->num_color;
+
+      for (j=0; j < ymax; ++j) {
+         for (i=0; i < xmax; ++i) {
+            int p = (i-j+1)&3; // corner type
+            if (weighting==NULL || weighting[p]==0 || cc[p] == 1)
+               c_color[j][i] = STB_HBWANG_RAND() % cc[p];
+            else
+               c_color[j][i] = stbhw__weighted(cc[p], weighting[p]);
+         }
+      }
+      #ifndef STB_HBWANG_NO_REPITITION_REDUCTION
+      // now go back through and make sure we don't have adjancent 3x2 vertices that are identical,
+      // to avoid really obvious repetition (which happens easily with extreme weights)
+      for (j=0; j < ymax-3; ++j) {
+         for (i=0; i < xmax-3; ++i) {
+            int p = (i-j+1) & 3; // corner type
+            STB_HBWANG_ASSERT(i+3 < STB_HBWANG_MAX_X+6);
+            STB_HBWANG_ASSERT(j+3 < STB_HBWANG_MAX_Y+6);
+            if (stbhw__match(i,j) && stbhw__match(i,j+1) && stbhw__match(i,j+2)
+                && stbhw__match(i+1,j) && stbhw__match(i+1,j+1) && stbhw__match(i+1,j+2)) {
+               int p = ((i+1)-(j+1)+1) & 3;
+               if (cc[p] > 1)
+                  c_color[j+1][i+1] = stbhw__change_color(c_color[j+1][i+1], cc[p], weighting ? weighting[p] : NULL);
+            }
+            if (stbhw__match(i,j) && stbhw__match(i+1,j) && stbhw__match(i+2,j)
+                && stbhw__match(i,j+1) && stbhw__match(i+1,j+1) && stbhw__match(i+2,j+1)) {
+               int p = ((i+2)-(j+1)+1) & 3;
+               if (cc[p] > 1)
+                  c_color[j+1][i+2] = stbhw__change_color(c_color[j+1][i+2], cc[p], weighting ? weighting[p] : NULL);
+            }
+         }
+      }
+      #endif
+
+      ypos = -1 * sidelen;
+      for (j = -1; ypos < h; ++j) {
+         // a general herringbone row consists of:
+         //    horizontal left block, the bottom of a previous vertical, the top of a new vertical
+         int phase = (j & 3);
+         // displace horizontally according to pattern
+         if (phase == 0) {
+            i = 0;
+         } else {
+            i = phase-4;
+         }
+         for (i;; i += 4) {
+            int xpos = i * sidelen;
+            if (xpos >= w)
+               break;
+            // horizontal left-block
+            if (xpos + sidelen*2 >= 0 && ypos >= 0) {
+               stbhw_tile *t = stbhw__choose_tile(
+                  ts->h_tiles, ts->num_h_tiles,
+                  &c_color[j+2][i+2], &c_color[j+2][i+3], &c_color[j+2][i+4],
+                  &c_color[j+3][i+2], &c_color[j+3][i+3], &c_color[j+3][i+4],
+                  weighting
+               );
+               if (t == NULL)
+                  return 0;
+               stbhw__draw_h_tile(output,stride,w,h, xpos, ypos, t, sidelen);
+            }
+            xpos += sidelen * 2;
+            // now we're at the end of a previous vertical one
+            xpos += sidelen;
+            // now we're at the start of a new vertical one
+            if (xpos < w) {
+               stbhw_tile *t = stbhw__choose_tile(
+                  ts->v_tiles, ts->num_v_tiles,
+                  &c_color[j+2][i+5], &c_color[j+3][i+5], &c_color[j+4][i+5],
+                  &c_color[j+2][i+6], &c_color[j+3][i+6], &c_color[j+4][i+6],
+                  weighting
+               );
+               if (t == NULL)
+                  return 0;
+               stbhw__draw_v_tile(output,stride,w,h, xpos, ypos,  t, sidelen);
+            }
+         }
+         ypos += sidelen;
+      }
+   } else {
+      // @TODO edge-color repetition reduction
+      int i,j, ypos;
+      memset(v_color, -1, sizeof(v_color));
+      memset(h_color, -1, sizeof(h_color));
+
+      ypos = -1 * sidelen;
+      for (j = -1; ypos<h; ++j) {
+         // a general herringbone row consists of:
+         //    horizontal left block, the bottom of a previous vertical, the top of a new vertical
+         int phase = (j & 3);
+         // displace horizontally according to pattern
+         if (phase == 0) {
+            i = 0;
+         } else {
+            i = phase-4;
+         }
+         for (i;; i += 4) {
+            int xpos = i * sidelen;
+            if (xpos >= w)
+               break;
+            // horizontal left-block
+            if (xpos + sidelen*2 >= 0 && ypos >= 0) {
+               stbhw_tile *t = stbhw__choose_tile(
+                  ts->h_tiles, ts->num_h_tiles,
+                  &h_color[j+2][i+2], &h_color[j+2][i+3],
+                  &v_color[j+2][i+2], &v_color[j+2][i+4],
+                  &h_color[j+3][i+2], &h_color[j+3][i+3],
+                  weighting
+               );
+               if (t == NULL) return 0;
+               stbhw__draw_h_tile(output,stride,w,h, xpos, ypos, t, sidelen);
+            }
+            xpos += sidelen * 2;
+            // now we're at the end of a previous vertical one
+            xpos += sidelen;
+            // now we're at the start of a new vertical one
+            if (xpos < w) {
+               stbhw_tile *t = stbhw__choose_tile(
+                  ts->v_tiles, ts->num_v_tiles,
+                  &h_color[j+2][i+5],
+                  &v_color[j+2][i+5], &v_color[j+2][i+6],
+                  &v_color[j+3][i+5], &v_color[j+3][i+6],
+                  &h_color[j+4][i+5],
+                  weighting
+               );
+               if (t == NULL) return 0;
+               stbhw__draw_v_tile(output,stride,w,h, xpos, ypos,  t, sidelen);
+            }
+         }
+         ypos += sidelen;
+      }
+   }
+   return 1;
+}
+
+static void stbhw__parse_h_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+   stbhw_tile *h = (stbhw_tile *) malloc(sizeof(*h)-1 + 3 * (len*2) * len);
+   int i,j;
+   ++xpos;
+   ++ypos;
+   h->a = a, h->b = b, h->c = c, h->d = d, h->e = e, h->f = f;
+   for (j=0; j < len; ++j)
+      for (i=0; i < len*2; ++i)
+         memcpy(h->pixels + j*(3*len*2) + i*3, p->data+(ypos+j)*p->stride+(xpos+i)*3, 3);
+   STB_HBWANG_ASSERT(p->ts->num_h_tiles < p->ts->max_h_tiles);
+   p->ts->h_tiles[p->ts->num_h_tiles++] = h;
+}
+
+static void stbhw__parse_v_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+   stbhw_tile *h = (stbhw_tile *) malloc(sizeof(*h)-1 + 3 * (len*2) * len);
+   int i,j;
+   ++xpos;
+   ++ypos;
+   h->a = a, h->b = b, h->c = c, h->d = d, h->e = e, h->f = f;
+   for (j=0; j < len*2; ++j)
+      for (i=0; i < len; ++i)
+         memcpy(h->pixels + j*(3*len) + i*3, p->data+(ypos+j)*p->stride+(xpos+i)*3, 3);
+   STB_HBWANG_ASSERT(p->ts->num_v_tiles < p->ts->max_v_tiles);
+   p->ts->v_tiles[p->ts->num_v_tiles++] = h;
+}
+
+STBHW_EXTERN int stbhw_build_tileset_from_image(stbhw_tileset *ts, unsigned char *data, int stride, int w, int h)
+{
+   int i, h_count, v_count;
+   unsigned char header[9];
+   stbhw_config c = { 0 };
+   stbhw__process p = { 0 };
+
+   // extract binary header
+
+   // remove encoding that makes it more visually obvious it encodes actual data
+   for (i=0; i < 9; ++i)
+      header[i] = data[w*3 - 1 - i] ^ (i*55);
+
+   // extract header info
+   if (header[7] == 0xc0) {
+      // corner-type
+      c.is_corner = 1;
+      for (i=0; i < 4; ++i)
+         c.num_color[i] = header[i];
+      c.num_vary_x = header[4];
+      c.num_vary_y = header[5];
+      c.short_side_len = header[6];
+   } else {
+      c.is_corner = 0;
+      // edge-type
+      for (i=0; i < 6; ++i)
+         c.num_color[i] = header[i];
+      c.num_vary_x = header[6];
+      c.num_vary_y = header[7];
+      c.short_side_len = header[8];
+   }
+
+   if (c.num_vary_x < 0 || c.num_vary_x > 64 || c.num_vary_y < 0 || c.num_vary_y > 64)
+      return 0;
+   if (c.short_side_len == 0)
+      return 0;
+   if (c.num_color[0] > 32 || c.num_color[1] > 32 || c.num_color[2] > 32 || c.num_color[3] > 32)
+      return 0;
+
+   stbhw__get_template_info(&c, NULL, NULL, &h_count, &v_count);
+
+   ts->is_corner = c.is_corner;
+   ts->short_side_len = c.short_side_len;
+   memcpy(ts->num_color, c.num_color, sizeof(ts->num_color));
+
+   ts->max_h_tiles = h_count;
+   ts->max_v_tiles = v_count;
+
+   ts->num_h_tiles = ts->num_v_tiles = 0;
+
+   ts->h_tiles = (stbhw_tile **) malloc(sizeof(*ts->h_tiles) * h_count);
+   ts->v_tiles = (stbhw_tile **) malloc(sizeof(*ts->v_tiles) * v_count);
+
+   p.ts = ts;
+   p.data = data;
+   p.stride = stride;
+   p.process_h_rect = stbhw__parse_h_rect;
+   p.process_v_rect = stbhw__parse_v_rect;
+   p.w = w;
+   p.h = h;
+   p.c = &c;
+
+   // load all the tiles out of the image
+   return stbhw__process_template(&p);
+}
+
+STBHW_EXTERN void stbhw_free_tileset(stbhw_tileset *ts)
+{
+   int i;
+   for (i=0; i < ts->num_h_tiles; ++i)
+      free(ts->h_tiles[i]);
+   for (i=0; i < ts->num_v_tiles; ++i)
+      free(ts->v_tiles[i]);
+   free(ts->h_tiles);
+   free(ts->v_tiles);
+   ts->h_tiles = NULL;
+   ts->v_tiles = NULL;
+   ts->num_h_tiles = ts->max_h_tiles = 0;
+   ts->num_v_tiles = ts->max_v_tiles = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//               GENERATOR
+//
+//
+
+
+// shared code
+
+static void stbhw__set_pixel(unsigned char *data, int stride, int xpos, int ypos, unsigned char color[3])
+{
+   memcpy(data + ypos*stride + xpos*3, color, 3);
+}
+
+static void stbhw__stbhw__set_pixel_whiten(unsigned char *data, int stride, int xpos, int ypos, unsigned char color[3])
+{
+   unsigned char c2[3];
+   int i;
+   for (i=0; i < 3; ++i)
+      c2[i] = (color[i]*2 + 255)/3;
+   memcpy(data + ypos*stride + xpos*3, c2, 3);
+}
+
+
+static unsigned char stbhw__black[3] = { 0,0,0 };
+
+// each edge set gets its own unique color variants
+// used http://phrogz.net/css/distinct-colors.html to generate this set,
+// but it's not very good and needs to be revised
+
+static unsigned char stbhw__color[7][8][3] =
+{
+   { {255,51,51}  , {143,143,29}, {0,199,199}, {159,119,199},     {0,149,199}  , {143, 0,143}, {255,128,0}, {64,255,0},  },
+   { {235,255,30 }, {255,0,255},  {199,139,119},  {29,143, 57},    {143,0,71}   , { 0,143,143}, {0,99,199}, {143,71,0},  },
+   { {0,149,199}  , {143, 0,143}, {255,128,0}, {64,255,0},        {255,191,0}  , {51,255,153}, {0,0,143}, {199,119,159},},
+   { {143,0,71}   , { 0,143,143}, {0,99,199}, {143,71,0},         {255,190,153}, { 0,255,255}, {128,0,255}, {255,51,102},},
+   { {255,191,0}  , {51,255,153}, {0,0,143}, {199,119,159},       {255,51,51}  , {143,143,29}, {0,199,199}, {159,119,199},},
+   { {255,190,153}, { 0,255,255}, {128,0,255}, {255,51,102},      {235,255,30 }, {255,0,255}, {199,139,119},  {29,143, 57}, },
+
+   { {40,40,40 },  { 90,90,90 }, { 150,150,150 }, { 200,200,200 },
+     { 255,90,90 }, { 160,160,80}, { 50,150,150 }, { 200,50,200 } },
+};
+
+static void stbhw__draw_hline(unsigned char *data, int stride, int xpos, int ypos, int color, int len, int slot)
+{
+   int i;
+   int j = len * 6 / 16;
+   int k = len * 10 / 16;
+   for (i=0; i < len; ++i)
+      stbhw__set_pixel(data, stride, xpos+i, ypos, stbhw__black);
+   if (k-j < 2) {
+      j = len/2 - 1;
+      k = j+2;
+      if (len & 1)
+         ++k;
+   }
+   for (i=j; i < k; ++i)
+      stbhw__stbhw__set_pixel_whiten(data, stride, xpos+i, ypos, stbhw__color[slot][color]);
+}
+
+static void stbhw__draw_vline(unsigned char *data, int stride, int xpos, int ypos, int color, int len, int slot)
+{
+   int i;
+   int j = len * 6 / 16;
+   int k = len * 10 / 16;
+   for (i=0; i < len; ++i)
+      stbhw__set_pixel(data, stride, xpos, ypos+i, stbhw__black);
+   if (k-j < 2) {
+      j = len/2 - 1;
+      k = j+2;
+      if (len & 1)
+         ++k;
+   }
+   for (i=j; i < k; ++i)
+      stbhw__stbhw__set_pixel_whiten(data, stride, xpos, ypos+i, stbhw__color[slot][color]);
+}
+
+//                 0--*--1--*--2--*--3
+//                 |     |           |
+//                 *     *           *
+//                 |     |           |
+//     1--*--2--*--3     0--*--1--*--2
+//     |           |     |
+//     *           *     *
+//     |           |     |
+//     0--*--1--*--2--*--3
+//
+// variables while enumerating (no correspondence between corners
+// of the types is implied by these variables)
+//
+//     a-----b-----c      a-----d
+//     |           |      |     |
+//     |           |      |     |
+//     |           |      |     |
+//     d-----e-----f      b     e
+//                        |     |
+//                        |     |
+//                        |     |
+//                        c-----f
+//
+
+unsigned char stbhw__corner_colors[4][4][3] =
+{
+   { { 255,0,0 }, { 200,200,200 }, { 100,100,200 }, { 255,200,150 }, },
+   { { 0,0,255 }, { 255,255,0 },   { 100,200,100 }, { 150,255,200 }, },
+   { { 255,0,255 }, { 80,80,80 },  { 200,100,100 }, { 200,150,255 }, },
+   { { 0,255,255 }, { 0,255,0 },   { 200,120,200 }, { 255,200,200 }, },
+};
+
+int stbhw__corner_colors_to_edge_color[4][4] =
+{
+   // 0   1   2   3
+   {  0,  1,  4,  9, }, // 0
+   {  2,  3,  5, 10, }, // 1
+   {  6,  7,  8, 11, }, // 2
+   { 12, 13, 14, 15, }, // 3
+};
+
+#define stbhw__c2e stbhw__corner_colors_to_edge_color
+
+static void stbhw__draw_clipped_corner(unsigned char *data, int stride, int xpos, int ypos, int w, int h, int x, int y)
+{
+   static unsigned char template_color[3] = { 167,204,204 };
+   int i,j;
+   for (j = -2; j <= 1; ++j) {
+      for (i = -2; i <= 1; ++i) {
+         if ((i == -2 || i == 1) && (j == -2 || j == 1))
+            continue;
+         else {
+            if (x+i < 1 || x+i > w) continue;
+            if (y+j < 1 || y+j > h) continue;
+            stbhw__set_pixel(data, stride, xpos+x+i, ypos+y+j, template_color);
+
+         }
+      }
+   }
+}
+
+static void stbhw__edge_process_h_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+   stbhw__draw_hline(p->data, p->stride, xpos+1        , ypos        , a, len, 2);
+   stbhw__draw_hline(p->data, p->stride, xpos+  len+1  , ypos        , b, len, 3);
+   stbhw__draw_vline(p->data, p->stride, xpos          , ypos+1      , c, len, 1);
+   stbhw__draw_vline(p->data, p->stride, xpos+2*len+1  , ypos+1      , d, len, 4);
+   stbhw__draw_hline(p->data, p->stride, xpos+1        , ypos + len+1, e, len, 0);
+   stbhw__draw_hline(p->data, p->stride, xpos + len+1  , ypos + len+1, f, len, 2);
+}
+
+static void stbhw__edge_process_v_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+   stbhw__draw_hline(p->data, p->stride, xpos+1      , ypos          , a, len, 0);
+   stbhw__draw_vline(p->data, p->stride, xpos        , ypos+1        , b, len, 5);
+   stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos+1        , c, len, 1);
+   stbhw__draw_vline(p->data, p->stride, xpos        , ypos +   len+1, d, len, 4);
+   stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos +   len+1, e, len, 5);
+   stbhw__draw_hline(p->data, p->stride, xpos+1      , ypos + 2*len+1, f, len, 3);
+}
+
+static void stbhw__corner_process_h_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+
+   stbhw__draw_hline(p->data, p->stride, xpos+1        , ypos        , stbhw__c2e[a][b], len, 2);
+   stbhw__draw_hline(p->data, p->stride, xpos+  len+1  , ypos        , stbhw__c2e[b][c], len, 3);
+   stbhw__draw_vline(p->data, p->stride, xpos          , ypos+1      , stbhw__c2e[a][d], len, 1);
+   stbhw__draw_vline(p->data, p->stride, xpos+2*len+1  , ypos+1      , stbhw__c2e[c][f], len, 4);
+   stbhw__draw_hline(p->data, p->stride, xpos+1        , ypos + len+1, stbhw__c2e[d][e], len, 0);
+   stbhw__draw_hline(p->data, p->stride, xpos + len+1  , ypos + len+1, stbhw__c2e[e][f], len, 2);
+
+   if (p->c->corner_type_color_template[1][a]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, 1,1);
+   if (p->c->corner_type_color_template[2][b]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len+1,1);
+   if (p->c->corner_type_color_template[3][c]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len*2+1,1);
+
+   if (p->c->corner_type_color_template[0][d]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, 1,len+1);
+   if (p->c->corner_type_color_template[1][e]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len+1,len+1);
+   if (p->c->corner_type_color_template[2][f]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len*2+1,len+1);
+
+   stbhw__set_pixel(p->data, p->stride, xpos        , ypos, stbhw__corner_colors[1][a]);
+   stbhw__set_pixel(p->data, p->stride, xpos+len    , ypos, stbhw__corner_colors[2][b]);
+   stbhw__set_pixel(p->data, p->stride, xpos+2*len+1, ypos, stbhw__corner_colors[3][c]);
+   stbhw__set_pixel(p->data, p->stride, xpos        , ypos+len+1, stbhw__corner_colors[0][d]);
+   stbhw__set_pixel(p->data, p->stride, xpos+len    , ypos+len+1, stbhw__corner_colors[1][e]);
+   stbhw__set_pixel(p->data, p->stride, xpos+2*len+1, ypos+len+1, stbhw__corner_colors[2][f]);
+}
+
+static void stbhw__corner_process_v_rect(stbhw__process *p, int xpos, int ypos,
+                            int a, int b, int c, int d, int e, int f)
+{
+   int len = p->c->short_side_len;
+
+   stbhw__draw_hline(p->data, p->stride, xpos+1      , ypos          , stbhw__c2e[a][d], len, 0);
+   stbhw__draw_vline(p->data, p->stride, xpos        , ypos+1        , stbhw__c2e[a][b], len, 5);
+   stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos+1        , stbhw__c2e[d][e], len, 1);
+   stbhw__draw_vline(p->data, p->stride, xpos        , ypos +   len+1, stbhw__c2e[b][c], len, 4);
+   stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos +   len+1, stbhw__c2e[e][f], len, 5);
+   stbhw__draw_hline(p->data, p->stride, xpos+1      , ypos + 2*len+1, stbhw__c2e[c][f], len, 3);
+
+   if (p->c->corner_type_color_template[0][a]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,1);
+   if (p->c->corner_type_color_template[3][b]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,len+1);
+   if (p->c->corner_type_color_template[2][c]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,len*2+1);
+
+   if (p->c->corner_type_color_template[1][d]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,1);
+   if (p->c->corner_type_color_template[0][e]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,len+1);
+   if (p->c->corner_type_color_template[3][f]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,len*2+1);
+
+   stbhw__set_pixel(p->data, p->stride, xpos      , ypos        , stbhw__corner_colors[0][a]);
+   stbhw__set_pixel(p->data, p->stride, xpos      , ypos+len    , stbhw__corner_colors[3][b]);
+   stbhw__set_pixel(p->data, p->stride, xpos      , ypos+2*len+1, stbhw__corner_colors[2][c]);
+   stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos        , stbhw__corner_colors[1][d]);
+   stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos+len    , stbhw__corner_colors[0][e]);
+   stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos+2*len+1, stbhw__corner_colors[3][f]);
+}
+
+// generates a template image, assuming data is 3*w*h bytes long, RGB format
+STBHW_EXTERN int stbhw_make_template(stbhw_config *c, unsigned char *data, int w, int h, int stride_in_bytes)
+{
+   stbhw__process p;
+   int i;
+
+   p.data = data;
+   p.w = w;
+   p.h = h;
+   p.stride = stride_in_bytes;
+   p.ts = 0;
+   p.c = c;
+
+   if (c->is_corner) {
+      p.process_h_rect = stbhw__corner_process_h_rect;
+      p.process_v_rect = stbhw__corner_process_v_rect;
+   } else {
+      p.process_h_rect = stbhw__edge_process_h_rect;
+      p.process_v_rect = stbhw__edge_process_v_rect;
+   }
+
+   for (i=0; i < p.h; ++i)
+      memset(p.data + i*p.stride, 255, 3*p.w);
+
+   if (!stbhw__process_template(&p))
+      return 0;
+
+   if (c->is_corner) {
+      // write out binary information in first line of image
+      for (i=0; i < 4; ++i)
+         data[w*3-1-i] = c->num_color[i];
+      data[w*3-1-i] = c->num_vary_x;
+      data[w*3-2-i] = c->num_vary_y;
+      data[w*3-3-i] = c->short_side_len;
+      data[w*3-4-i] = 0xc0;
+   } else {
+      for (i=0; i < 6; ++i)
+         data[w*3-1-i] = c->num_color[i];
+      data[w*3-1-i] = c->num_vary_x;
+      data[w*3-2-i] = c->num_vary_y;
+      data[w*3-3-i] = c->short_side_len;
+   }
+
+   // make it more obvious it encodes actual data
+   for (i=0; i < 9; ++i)
+      p.data[p.w*3 - 1 - i] ^= i*55;
+
+   return 1;
+}
+#endif // STB_HBWANG_IMPLEMENTATION