moving to RLE sprites instead of compiled sprites
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 8 Mar 2020 05:54:49 +0000 (07:54 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 8 Mar 2020 05:54:49 +0000 (07:54 +0200)
.gitignore
GNUmakefile
src/introscr.c
src/sprite.c [new file with mode: 0644]
src/sprite.h [new file with mode: 0644]
tools/procdata [new file with mode: 0755]
tools/rlesprite/Makefile [new file with mode: 0644]
tools/rlesprite/src/image.c [new file with mode: 0644]
tools/rlesprite/src/image.h [new file with mode: 0644]
tools/rlesprite/src/main.c [new file with mode: 0644]

index 04f87b7..c96a806 100644 (file)
@@ -34,3 +34,4 @@ data
 *.COF
 *.dja
 *.DJA
+tools/rlesprite/rlesprite
index 817daff..3fbb73f 100644 (file)
@@ -1,12 +1,14 @@
 csrc = $(wildcard src/*.c) $(wildcard src/sdl/*.c) $(wildcard src/3dgfx/*.c)
+asmsrc = $(wildcard src/*.asm)
 
-obj = $(csrc:.c=.o)
+obj = $(csrc:.c=.o) $(asmsrc:.asm=.o)
 dep = $(obj:.o=.d)
 bin = game
 
 inc = -Isrc -Isrc/sdl -Isrc/3dgfx -Ilibs/imago/src
+warn = -pedantic -Wall
 
-CFLAGS = $(arch) -pedantic -Wall -g -MMD $(inc) `sdl-config --cflags`
+CFLAGS = $(arch) $(warn) -g -MMD $(inc) `sdl-config --cflags`
 LDFLAGS = $(arch) -Llibs/imago -limago $(sdl_ldflags) -lm
 
 ifneq ($(shell uname -m), i386)
@@ -16,10 +18,15 @@ else
        sdl_ldflags = `sdl-config --libs`
 endif
 
+.PHONY: all
+all: data $(bin)
 
 $(bin): $(obj) imago
        $(CC) -o $@ $(obj) $(LDFLAGS)
 
+%.o: %.asm
+       nasm -f elf -o $@ $<
+
 -include $(dep)
 
 .PHONY: imago
@@ -33,3 +40,7 @@ clean:
 .PHONY: cleandep
 cleandep:
        rm -f $(dep)
+
+.PHONY: data
+data:
+       @tools/procdata
index 5690561..c9f3a03 100644 (file)
@@ -54,7 +54,7 @@ void intro_draw(void)
                fade = 256 - (tm - 2 * FADE_DUR) * 256 / FADE_DUR;
        } else {
                fade = 0;
-               menu_start();
+               //menu_start();
        }
 
        for(i=0; i<fb_height; i++) {
@@ -66,7 +66,7 @@ void intro_draw(void)
                }
        }
 
-       cs_dputs(fb_pixels, 10, 10, "foo");
+       //cs_dputs(fb_pixels, 10, 10, "foo");
 
        blit_frame(fb_pixels, 1);
 }
diff --git a/src/sprite.c b/src/sprite.c
new file mode 100644 (file)
index 0000000..067c41c
--- /dev/null
@@ -0,0 +1,136 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "inttypes.h"
+#include "sprite.h"
+#include "util.h"
+
+#pragma pack (push, 1)
+struct file_header {
+       char magic[8];
+       uint16_t width, height, bpp;
+       uint16_t count;
+} PACKED;
+#pragma pack (pop)
+
+
+static int read_sprite(struct sprite *spr, int pixsz, FILE *fp);
+
+
+void destroy_sprites(struct sprites *ss)
+{
+       int i;
+
+       for(i=0; i<ss->num_sprites; i++) {
+               free(ss->sprites[i].ops);
+       }
+       free(ss->sprites);
+}
+
+int load_sprites(struct sprites *ss, const char *fname)
+{
+       int i;
+       FILE *fp;
+       struct file_header hdr;
+
+       ss->sprites = 0;
+
+       if(!(fp = fopen(fname, "rb"))) {
+               fprintf(stderr, "failed to load sprites from %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+       if(fread(&hdr, sizeof hdr, 1, fp) <= 0) {
+               fprintf(stderr, "unexpected EOF while reading from %s\n", fname);
+               goto err;
+       }
+       if(memcmp(hdr.magic, "RLESPRIT", sizeof hdr.magic) != 0) {
+               fprintf(stderr, "invalid magic in %s\n", fname);
+               goto err;
+       }
+
+       ss->width = hdr.width;
+       ss->height = hdr.height;
+       ss->bpp = hdr.bpp;
+       ss->num_sprites = hdr.count;
+
+       if(!(ss->sprites = malloc(hdr.count * sizeof *ss->sprites))) {
+               fprintf(stderr, "failed to allocate %d sprites for %s\n", hdr.count, fname);
+               goto err;
+       }
+
+       for(i=0; i<ss->num_sprites; i++) {
+               if(read_sprite(ss->sprites + i, (hdr.bpp + 7) / 8, fp) == -1) {
+                       goto err;
+               }
+       }
+
+       fclose(fp);
+       return 0;
+
+err:
+       free(ss->sprites);
+       fclose(fp);
+       return -1;
+}
+
+static int read_sprite(struct sprite *spr, int pixsz, FILE *fp)
+{
+       int i, idx, max_ops, newmax, len, bufsz;
+       void *tmp;
+       uint32_t op;
+
+       spr->ops = 0;
+       spr->num_ops = max_ops = 0;
+
+       do {
+               /* read the op */
+               if(fread(&op, sizeof op, 1, fp) <= 0) {
+                       free(spr->ops);
+                       return -1;
+               }
+
+               /* realloc ops array if necessary */
+               if(spr->num_ops >= max_ops) {
+                       newmax = max_ops ? max_ops << 1 : 16;
+                       if(!(tmp = realloc(spr->ops, newmax * sizeof *spr->ops))) {
+                               fprintf(stderr, "failed to add sprite op (newmax: %d)\n", newmax);
+                               goto err;
+                       }
+                       spr->ops = tmp;
+                       max_ops = newmax;
+               }
+
+               /* append */
+               idx = spr->num_ops++;
+               spr->ops[idx].op = SOP_OP(op);
+               len = SOP_LEN(op);
+               bufsz = len * pixsz;
+               spr->ops[idx].size = bufsz;
+
+               /* if the op was copy, we need to grab the pixel data */
+               if(SOP_OP(op) == SOP_COPY) {
+                       if(!(tmp = malloc(bufsz))) {
+                               fprintf(stderr, "failed to allocate sprite pixel data (%d bytes)\n", bufsz);
+                               goto err;
+                       }
+                       if(fread(tmp, 1, bufsz, fp) < bufsz) {
+                               fprintf(stderr, "unexpected EOF while trying to read sprite data (%d pixels)\n", len);
+                               goto err;
+                       }
+                       spr->ops[idx].data = tmp;
+               } else {
+                       spr->ops[idx].data = 0;
+               }
+
+       } while(SOP_OP(op) != SOP_END);
+
+       return 0;
+
+err:
+       for(i=0; i<spr->num_ops; i++) {
+               free(spr->ops[i].data);
+       }
+       free(spr->ops);
+       return -1;
+}
diff --git a/src/sprite.h b/src/sprite.h
new file mode 100644 (file)
index 0000000..85c4027
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef SPRITE_H_
+#define SPRITE_H_
+
+enum {
+       SOP_END,
+       SOP_ENDL,
+       SOP_SKIP,
+       SOP_RESVD,
+       SOP_COPY
+};
+
+#define SOP_OP(op)     ((op) & 0xff)
+#define SOP_LEN(op)    ((op) >> 16)
+
+struct sprite_op {
+       unsigned char op;
+       unsigned short size;
+       void *data;
+};
+
+struct sprite {
+       struct sprite_op *ops;
+       int num_ops;
+};
+
+struct sprites {
+       int width, height, bpp;
+
+       struct sprite *sprites;
+       int num_sprites;
+};
+
+void destroy_sprites(struct sprites *ss);
+
+int load_sprites(struct sprites *ss, const char *fname);
+
+#endif /* SPRITE_H_ */
diff --git a/tools/procdata b/tools/procdata
new file mode 100755 (executable)
index 0000000..2f1b3f2
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+[ -f ./procdata ] && cd ..
+if [ ! -f tools/procdata ]; then
+       echo 'run from the demo root directory' >&2
+       exit 1
+fi
+
+# process embedded images
+#if [ ! -f tools/img2bin/img2bin ]; then
+#      make -C tools/img2bin || exit 1
+#fi
+#alias img2bin=tools/img2bin/img2bin
+#
+#mkdir -p data
+#if [ ! -f data/loading.img -o data/loading.png -nt data/loading.img ]; then
+#      echo 'img2bin: loading'
+#      img2bin data/loading.png || exit 1
+#fi
+
+# process RLE sprites
+if [ ! -f tools/rlesprite/rlesprite ]; then
+       make -C tools/rlesprite || exit 1
+fi
+alias rlesprite=tools/rlesprite/rlesprite
+
+if [ ! -f data/dbgfont.spr -o data/legible.fnt -nt data/dbgfont.spr ]; then
+       echo 'rlesprite: dbgfont'
+       rlesprite -o data/dbgfont.spr -s 8x16 -conv565 data/legible.fnt
+fi
diff --git a/tools/rlesprite/Makefile b/tools/rlesprite/Makefile
new file mode 100644 (file)
index 0000000..77daec7
--- /dev/null
@@ -0,0 +1,13 @@
+src = $(wildcard src/*.c)
+obj = $(src:.c=.o)
+bin = rlesprite
+
+CFLAGS = -pedantic -Wall -g
+LDFLAGS = -lpng -lz
+
+$(bin): $(obj)
+       $(CC) -o $@ $(obj) $(LDFLAGS)
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(bin)
diff --git a/tools/rlesprite/src/image.c b/tools/rlesprite/src/image.c
new file mode 100644 (file)
index 0000000..84bad81
--- /dev/null
@@ -0,0 +1,293 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <png.h>
+#include "image.h"
+
+int alloc_image(struct image *img, int x, int y, int bpp)
+{
+       memset(img, 0, sizeof *img);
+       img->width = x;
+       img->height = y;
+       img->bpp = bpp;
+       img->scansz = img->pitch = x * bpp / 8;
+
+       if(!(img->pixels = malloc(y * img->scansz))) {
+               printf("failed to allocate %dx%d (%dbpp) pixel buffer\n", x, y, bpp);
+               return -1;
+       }
+
+       /* just a guess, assume the user will fill the details, but set up reasonable
+        * defaults just in case...
+        */
+       if(bpp <= 8) {
+               img->nchan = 1;
+               img->cmap_ncolors = 1 << bpp;
+       } else if(bpp <= 24) {
+               img->nchan = 3;
+       } else {
+               img->nchan = 4;
+       }
+       return 0;
+}
+
+int load_image(struct image *img, const char *fname)
+{
+       int i;
+       FILE *fp;
+       png_struct *png;
+       png_info *info;
+       int chan_bits, color_type;
+       png_uint_32 xsz, ysz;
+       png_color *palette;
+       unsigned char **scanline;
+       unsigned char *dptr;
+
+       if(!(fp = fopen(fname, "rb"))) {
+               printf("failed to open: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+
+       if(!(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+               fclose(fp);
+               return -1;
+       }
+       if(!(info = png_create_info_struct(png))) {
+               fclose(fp);
+               png_destroy_read_struct(&png, 0, 0);
+               return -1;
+       }
+       if(setjmp(png_jmpbuf(png))) {
+               fclose(fp);
+               png_destroy_read_struct(&png, &info, 0);
+               return -1;
+       }
+
+       png_init_io(png, fp);
+       png_read_png(png, info, 0, 0);
+
+       png_get_IHDR(png, info, &xsz, &ysz, &chan_bits, &color_type, 0, 0, 0);
+       img->width = xsz;
+       img->height = ysz;
+       img->nchan = png_get_channels(png, info);
+       img->bpp = img->nchan * chan_bits;
+       img->scansz = img->pitch = xsz * img->bpp / 8;
+       img->cmap_ncolors = 0;
+
+       if(color_type == PNG_COLOR_TYPE_PALETTE) {
+               png_get_PLTE(png, info, &palette, &img->cmap_ncolors);
+               memcpy(img->cmap, palette, img->cmap_ncolors * sizeof *img->cmap);
+       }
+
+       if(!(img->pixels = malloc(ysz * img->scansz))) {
+               perror("failed to allocate pixel buffer");
+               fclose(fp);
+               png_destroy_read_struct(&png, &info, 0);
+               return -1;
+       }
+       dptr = img->pixels;
+
+       scanline = (unsigned char**)png_get_rows(png, info);
+       for(i=0; i<ysz; i++) {
+               memcpy(dptr, scanline[i], img->scansz);
+               dptr += img->pitch;
+       }
+
+       fclose(fp);
+       png_destroy_read_struct(&png, &info, 0);
+       return 0;
+}
+
+int save_image(struct image *img, const char *fname)
+{
+       int i, chan_bits, coltype;
+       FILE *fp;
+       png_struct *png;
+       png_info *info;
+       png_text txt;
+       unsigned char **scanline = 0;
+       unsigned char *pptr;
+
+       if(!(fp = fopen(fname, "wb"))) {
+               printf("save_image: failed to open: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+
+       if(!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+               fclose(fp);
+               return -1;
+       }
+       if(!(info = png_create_info_struct(png))) {
+               png_destroy_write_struct(&png, 0);
+               fclose(fp);
+               return -1;
+       }
+
+       txt.compression = PNG_TEXT_COMPRESSION_NONE;
+       txt.key = "Software";
+       txt.text = "img2tiles";
+       txt.text_length = 0;
+
+       if(setjmp(png_jmpbuf(png))) {
+               png_destroy_write_struct(&png, &info);
+               free(scanline);
+               fclose(fp);
+               return -1;
+       }
+
+       switch(img->nchan) {
+       case 1:
+               if(img->cmap_ncolors > 0) {
+                       coltype = PNG_COLOR_TYPE_PALETTE;
+               } else {
+                       coltype = PNG_COLOR_TYPE_GRAY;
+               }
+               break;
+       case 2:
+               coltype = PNG_COLOR_TYPE_GRAY_ALPHA;
+               break;
+       case 3:
+               coltype = PNG_COLOR_TYPE_RGB;
+               break;
+       case 4:
+               coltype = PNG_COLOR_TYPE_RGB_ALPHA;
+               break;
+       }
+
+       chan_bits = img->bpp / img->nchan;
+       png_set_IHDR(png, info, img->width, img->height, chan_bits, coltype, PNG_INTERLACE_NONE,
+                       PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       png_set_text(png, info, &txt, 1);
+
+       if(img->cmap_ncolors > 0) {
+               png_set_PLTE(png, info, (png_color*)img->cmap, img->cmap_ncolors);
+       }
+
+       if(!(scanline = malloc(img->height * sizeof *scanline))) {
+               png_destroy_write_struct(&png, &info);
+               fclose(fp);
+               return -1;
+       }
+
+       pptr = img->pixels;
+       for(i=0; i<img->height; i++) {
+               scanline[i] = pptr;
+               pptr += img->pitch;
+       }
+       png_set_rows(png, info, scanline);
+
+       png_init_io(png, fp);
+       png_write_png(png, info, 0, 0);
+       png_destroy_write_struct(&png, &info);
+       free(scanline);
+       fclose(fp);
+       return 0;
+}
+
+int conv_image_rgb565(struct image *img16, struct image *img)
+{
+       int i, j;
+       uint16_t *dptr;
+       unsigned char *sptr;
+
+       if(!img->cmap_ncolors && img->bpp < 24) {
+               fprintf(stderr, "unsupported conversion from %dbpp to 16bpp 565\n", img->bpp);
+               return -1;
+       }
+
+       img16->width = img->width;
+       img16->height = img->height;
+       img16->nchan = img->nchan;
+       img16->bpp = 16;
+       img16->scansz = img16->pitch = img->width * 2;
+       if(!(img16->pixels = malloc(img16->height * img16->scansz))) {
+               return -1;
+       }
+
+       sptr = img->pixels;
+       dptr = (uint16_t*)img16->pixels;
+
+       for(i=0; i<img->height; i++) {
+               for(j=0; j<img->width; j++) {
+                       unsigned int r, g, b;
+                       if(img->bpp <= 8 && img->cmap_ncolors) {
+                               int idx = *sptr++ % img->cmap_ncolors;
+                               r = img->cmap[idx].r >> 3;
+                               g = img->cmap[idx].g >> 2;
+                               b = img->cmap[idx].b >> 3;
+                       } else if(img->nchan == 1) {
+                               g = (*sptr++ >> 2) & 0x3f;
+                               r = b = g >> 1;
+                       } else {
+                               r = (*sptr++ >> 3) & 0x1f;
+                               g = (*sptr++ >> 2) & 0x3f;
+                               b = (*sptr++ >> 3) & 0x1f;
+                       }
+                       *dptr++ = (r << 11) | (g << 5) | b;
+               }
+       }
+       return 0;
+}
+
+
+int cmp_image(struct image *a, struct image *b)
+{
+       int i;
+       unsigned char *aptr = a->pixels;
+       unsigned char *bptr = b->pixels;
+
+       if(a->width != b->width || a->height != b->height || a->bpp != b->bpp || a->nchan != b->nchan) {
+               return -1;
+       }
+
+       for(i=0; i<a->height; i++) {
+               if(memcmp(aptr, bptr, a->scansz) != 0) {
+                       return -1;
+               }
+               aptr += a->pitch;
+               bptr += b->pitch;
+       }
+       return 0;
+}
+
+void blit_image(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy)
+{
+       int i;
+       unsigned char *sptr, *dptr;
+
+       assert(src->bpp == dst->bpp);
+       assert(src->nchan == dst->nchan);
+
+       if(sx < 0) { w += sx; sx = 0; }
+       if(sy < 0) { h += sy; sy = 0; }
+       if(dx < 0) { w += dx; sx -= dx; dx = 0; }
+       if(dy < 0) { h += dy; sy -= dy; dy = 0; }
+       if(sx + w >= src->width) w = src->width - sx;
+       if(sy + h >= src->height) h = src->height - sy;
+       if(dx + w >= dst->width) w = dst->width - dx;
+       if(dy + h >= dst->height) h = dst->height - dy;
+
+       if(w <= 0 || h <= 0) return;
+
+       sptr = src->pixels + sy * src->pitch + sx * src->bpp / 8;
+       dptr = dst->pixels + dy * dst->pitch + dx * dst->bpp / 8;
+
+       for(i=0; i<h; i++) {
+               memcpy(dptr, sptr, w * dst->bpp / 8);
+               dptr += dst->pitch;
+               sptr += src->pitch;
+       }
+}
+
+void image_color_offset(struct image *img, int offs)
+{
+       int i, sz = img->height * img->pitch;
+       unsigned char *pptr = img->pixels;
+
+       for(i=0; i<sz; i++) {
+               *pptr++ += offs;
+       }
+}
diff --git a/tools/rlesprite/src/image.h b/tools/rlesprite/src/image.h
new file mode 100644 (file)
index 0000000..0b966a6
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef IMAGE_H_
+#define IMAGE_H_
+
+struct cmapent {
+       unsigned char r, g, b;
+};
+
+struct image {
+       int width, height;
+       int bpp;
+       int nchan;
+       int scansz, pitch;
+       int cmap_ncolors;
+       struct cmapent cmap[256];
+       unsigned char *pixels;
+};
+
+int alloc_image(struct image *img, int x, int y, int bpp);
+int load_image(struct image *img, const char *fname);
+int save_image(struct image *img, const char *fname);
+
+int conv_image_rgb565(struct image *img16, struct image *img);
+
+int cmp_image(struct image *a, struct image *b);
+
+void blit_image(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy);
+
+void image_color_offset(struct image *img, int offs);
+
+#endif /* IMAGE_H_ */
diff --git a/tools/rlesprite/src/main.c b/tools/rlesprite/src/main.c
new file mode 100644 (file)
index 0000000..1080b15
--- /dev/null
@@ -0,0 +1,313 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+#include <assert.h>
+#include <alloca.h>
+#include "image.h"
+
+#define MAGIC  "RLESPRIT"
+
+struct file_header {
+       char magic[8];
+       uint16_t width, height, bpp;
+       uint16_t count;
+} __attribute__((packed));
+
+enum {
+       CSOP_END,
+       CSOP_ENDL,
+       CSOP_SKIP,
+       CSOP_FILL,
+       CSOP_COPY
+};
+
+struct csop {
+       unsigned char op;
+       unsigned char padding;
+       uint16_t len;
+} __attribute__((packed));
+
+
+struct rect {
+       int x, y, w, h;
+};
+
+int rlesprite(struct image *img, int x, int y, int xsz, int ysz);
+int proc_sheet(const char *fname);
+void print_usage(const char *argv0);
+
+int tile_xsz, tile_ysz;
+struct rect rect;
+int cmap_offs;
+int ckey;
+int conv565;
+int padding;
+FILE *outfp;
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *endp;
+       const char *name = 0;
+
+       for(i=1; i<argc; i++) {
+               if(argv[i][0] == '-') {
+                       if(strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-size") == 0) {
+                               if(sscanf(argv[++i], "%dx%d", &tile_xsz, &tile_ysz) != 2 ||
+                                               tile_xsz <= 0 || tile_ysz <= 0) {
+                                       fprintf(stderr, "%s must be followed by WIDTHxHEIGHT\n", argv[i - 1]);
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "-rect") == 0) {
+                               rect.x = rect.y = 0;
+                               if(sscanf(argv[++i], "%dx%d+%d+%d", &rect.w, &rect.h, &rect.x, &rect.y) < 2 || rect.w <= 0 || rect.h <= 0) {
+                                       fprintf(stderr, "%s must be followed by WIDTHxHEIGHT+X+Y\n", argv[i - 1]);
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "-pad") == 0) {
+                               padding = strtol(argv[++i], &endp, 10);
+                               if(endp == argv[i] || padding < 0) {
+                                       fprintf(stderr, "%s must be followed by a positive number\n", argv[i - 1]);
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-coffset") == 0) {
+                               cmap_offs = strtol(argv[++i], &endp, 10);
+                               if(endp == argv[i] || cmap_offs < 0 || cmap_offs >= 256) {
+                                       fprintf(stderr, "-coffset must be followed by a valid colormap offset\n");
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-o") == 0) {
+                               if(!argv[++i]) {
+                                       fprintf(stderr, "%s must be followed by an output filename\n", argv[i - 1]);
+                                       return 1;
+                               }
+                               if(name && strcmp(name, argv[i]) != 0) {
+                                       if(outfp) {
+                                               fclose(outfp);
+                                               outfp = 0;
+                                       }
+                               }
+                               name = argv[i];
+
+                       } else if(strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "-key") == 0) {
+                               ckey = strtol(argv[++i], &endp, 10);
+                               if(endp == argv[i] || ckey < 0) {
+                                       fprintf(stderr, "%s must be followed by a valid color key\n", argv[i - 1]);
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-conv565") == 0) {
+                               conv565 = 1;
+
+                       } else if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) {
+                               print_usage(argv[0]);
+                               return 0;
+
+                       } else {
+                               fprintf(stderr, "invalid option: %s\n", argv[i]);
+                               print_usage(argv[0]);
+                               return 1;
+                       }
+
+               } else {
+                       if(!outfp) {
+                               if(name) {
+                                       if(!(outfp = fopen(name, "wb"))) {
+                                               fprintf(stderr, "failed to open output file: %s: %s\n", name, strerror(errno));
+                                               return 1;
+                                       }
+                               } else {
+                                       outfp = stdout;
+                               }
+                       }
+
+                       if(proc_sheet(argv[i]) == -1) {
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+int proc_sheet(const char *fname)
+{
+       int i, j, x, y, num_xtiles, num_ytiles, xsz, ysz;
+       struct image img;
+       struct file_header hdr;
+
+       if(load_image(&img, fname) == -1) {
+               fprintf(stderr, "failed to load image: %s\n", fname);
+               return -1;
+       }
+       if(conv565) {
+               struct image tmp;
+               if(conv_image_rgb565(&tmp, &img) == -1) {
+                       fprintf(stderr, "failed to convert image to 16bpp 565: %s\n", fname);
+                       free(img.pixels);
+                       return -1;
+               }
+               free(img.pixels);
+               img = tmp;
+       }
+
+       if(rect.w <= 0) {
+               rect.w = img.width;
+               rect.h = img.height;
+       }
+
+       if(tile_xsz <= 0) {
+               num_xtiles = num_ytiles = 1;
+               xsz = rect.w - padding;
+               ysz = rect.h - padding;
+       } else {
+               if(padding) {
+                       num_xtiles = num_ytiles = 0;
+                       i = 0;
+                       while(i < rect.w) {
+                               num_xtiles++;
+                               i += tile_xsz + padding;
+                       }
+                       i = 0;
+                       while(i < rect.h) {
+                               num_ytiles++;
+                               i += tile_ysz + padding;
+                       }
+               } else {
+                       num_xtiles = rect.w / tile_xsz;
+                       num_ytiles = rect.h / tile_ysz;
+               }
+               xsz = tile_xsz;
+               ysz = tile_ysz;
+       }
+
+       memcpy(hdr.magic, MAGIC, sizeof hdr.magic);
+       hdr.width = xsz;
+       hdr.height = ysz;
+       hdr.bpp = img.bpp;
+       hdr.count = num_xtiles * num_ytiles;
+       fwrite(&hdr, sizeof hdr, 1, outfp);
+
+       y = rect.y;
+       for(i=0; i<num_ytiles; i++) {
+               x = rect.x;
+               for(j=0; j<num_xtiles; j++) {
+                       rlesprite(&img, x, y, xsz, ysz);
+                       x += xsz + padding;
+               }
+               y += ysz + padding;
+       }
+
+       free(img.pixels);
+       return 0;
+}
+
+int rlesprite(struct image *img, int x, int y, int xsz, int ysz)
+{
+       int i, j, numops, mode, new_mode, start, skip_acc, pixsz = img->bpp / 8;
+       unsigned char *pptr = img->pixels + y * img->scansz + x * pixsz;
+       struct csop *ops, *optr, endop = {0};
+
+       ops = optr = alloca((xsz + 1) * ysz * sizeof *ops);
+
+       for(i=0; i<ysz; i++) {
+               mode = -1;
+               start = -1;
+
+               if(i > 0) {
+                       optr++->op = CSOP_ENDL;
+               }
+
+               for(j=0; j<xsz; j++) {
+                       if(memcmp(pptr, &ckey, pixsz) == 0) {
+                               new_mode = CSOP_SKIP;
+                       } else {
+                               new_mode = CSOP_COPY;
+                       }
+
+                       if(new_mode != mode) {
+                               if(mode != -1) {
+                                       assert(start >= 0);
+                                       optr->op = mode;
+                                       optr->len = j - start;
+                                       optr++;
+                               }
+                               mode = new_mode;
+                               start = j;
+                       }
+                       pptr += pixsz;
+               }
+               pptr += img->scansz - xsz * pixsz;
+
+               if(mode != -1) {
+                       assert(start >= 0);
+                       optr->op = mode;
+                       optr->len = xsz - start;
+                       optr++;
+               }
+       }
+       numops = optr - ops;
+
+       pptr = img->pixels + y * img->scansz + x * img->bpp / 8;
+       optr = ops;
+       skip_acc = 0;
+
+       for(i=0; i<numops; i++) {
+               switch(optr->op) {
+               case CSOP_SKIP:
+                       skip_acc += optr->len;
+                       pptr += optr->len * pixsz;
+                       break;
+
+               case CSOP_ENDL:
+                       /* maybe at some point combine multiple endl into yskips? meh */
+                       fwrite(optr, sizeof *optr, 1, outfp);
+                       skip_acc = 0;
+                       pptr += img->scansz - xsz * pixsz;
+                       break;
+
+               case CSOP_COPY:
+                       if(skip_acc) {
+                               struct csop skip = {0};
+                               skip.op = CSOP_SKIP;
+                               skip.len = skip_acc;
+                               fwrite(&skip, sizeof skip, 1, outfp);
+                               skip_acc = 0;
+                       }
+
+                       fwrite(optr, sizeof *optr, 1, outfp);
+                       fwrite(pptr, pixsz, optr->len, outfp);
+
+                       pptr += optr->len;
+                       break;
+
+               default:
+                       fprintf(stderr, "invalid op\n");
+               }
+               optr++;
+       }
+
+       endop.op = CSOP_END;
+       fwrite(&endop, sizeof endop, 1, outfp);
+       return 0;
+}
+
+void print_usage(const char *argv0)
+{
+       printf("Usage: %s [options] <spritesheet>\n", argv0);
+       printf("Options:\n");
+       printf(" -o <filename>: output filename (default: stdout)\n");
+       printf(" -s,-size <WxH>: tile size (default: whole image)\n");
+       printf(" -r,-rect <WxH+X+Y>: use rectangle of the input image (default: whole image)\n");
+       printf(" -p,-pad <N>: how many pixels to skip between tiles in source image (default: 0)\n");
+       printf(" -coffset <offs>: colormap offset [0, 255] (default: 0)\n");
+       printf(" -k,-key <color>: color-key for transparency (default: 0)\n");
+       printf(" -conv565: convert image to 16bpp 565 before processing\n");
+       printf(" -h: print usage and exit\n");
+}