at last sprites "work"
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 6 Oct 2019 21:56:18 +0000 (00:56 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 6 Oct 2019 21:56:18 +0000 (00:56 +0300)
.gdbinit
.gitignore
Makefile
src/data.asm
src/gfx.asm
src/gfx.inc [new file with mode: 0644]
src/main.asm
tools/csprite/Makefile [new file with mode: 0644]
tools/csprite/src/image.c [new file with mode: 0644]
tools/csprite/src/image.h [new file with mode: 0644]
tools/csprite/src/main.c [new file with mode: 0644]

index 47cf5fb..63600ba 100644 (file)
--- a/.gdbinit
+++ b/.gdbinit
@@ -1,3 +1,2 @@
 target remote localhost:1234
 target remote localhost:1234
-set architecture i8086
 disp/i $pc
 disp/i $pc
index 269e431..a04e6a2 100644 (file)
@@ -7,3 +7,4 @@
 *.bin
 data/
 link.map
 *.bin
 data/
 link.map
+tools/csprite/csprite
index aadff36..1a2fcb4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ name = game
 elf = $(name).elf
 bin = $(name).bin
 
 elf = $(name).elf
 bin = $(name).bin
 
-ASFLAGS = -f elf32
+ASFLAGS = -f elf32 -i src/
 LDFLAGS = -m elf_i386 -T game.ld -print-gc-sections
 
 QEMU_FLAGS = -fda floppy.img -serial file:serial.log -d guest_errors
 LDFLAGS = -m elf_i386 -T game.ld -print-gc-sections
 
 QEMU_FLAGS = -fda floppy.img -serial file:serial.log -d guest_errors
@@ -23,21 +23,20 @@ boot.img: bootldr.bin $(bin)
 bootldr.bin: $(elf)
        objcopy -O binary -j '.boot*' $< $@
 
 bootldr.bin: $(elf)
        objcopy -O binary -j '.boot*' $< $@
 
-$(bin): $(elf) $(data)
+$(bin): $(elf)
        objcopy -O binary -R '.boot*' $< $@
 
 $(elf): $(obj)
        $(LD) -o $@ $(obj) -Map link.map $(LDFLAGS)
 
        objcopy -O binary -R '.boot*' $< $@
 
 $(elf): $(obj)
        $(LD) -o $@ $(obj) -Map link.map $(LDFLAGS)
 
+src/data.o: src/data.asm $(data)
+
 %.o: %.asm
        nasm -o $@ $(ASFLAGS) $<
 
 %.o: %.asm
        nasm -o $@ $(ASFLAGS) $<
 
-data/sprsheet.inc: data/sprsheet.png
-       img2tiles -o $@ -n -t 32x32 $<
-
 .PHONY: clean
 clean:
 .PHONY: clean
 clean:
-       rm -f $(bin) $(obj) bootldr.bin floppy.img boot.img
+       rm -f $(bin) $(obj) $(data) bootldr.bin floppy.img boot.img
 
 .PHONY: disasm
 disasm: bootldr.disasm $(name).disasm
 
 .PHONY: disasm
 disasm: bootldr.disasm $(name).disasm
@@ -61,3 +60,13 @@ debug: floppy.img
 
 .PHONY: sym
 sym: $(name).sym
 
 .PHONY: sym
 sym: $(name).sym
+
+
+tools/csprite/csprite:
+       $(MAKE) -C tools/csprite
+
+data/sprsheet.inc: data/sprsheet.png
+       tools/csprite/csprite -n sprsheet -s 32x32 $< >$@
+
+#data/sprsheet.inc: data/sprsheet.png
+#      img2tiles -o $@ -n -t 32x32 $<
index e6eab2f..c31fb5d 100644 (file)
@@ -1,7 +1,4 @@
 ; vi:filetype=nasm ts=8 sts=8 sw=8
 
        section .data
 ; vi:filetype=nasm ts=8 sts=8 sw=8
 
        section .data
-
-       global sprsheet_cmap
-       global sprsheet_tiles
 %include "data/sprsheet.inc"
 %include "data/sprsheet.inc"
index ca5cf74..06ff5e4 100644 (file)
@@ -17,8 +17,8 @@
        bits 32
        section .text
 
        bits 32
        section .text
 
-VIDMEM_ADDR    equ 0a0000h
-FRAMEBUF_ADDR  equ 090000h
+%define GFX_ASM_
+%include "gfx.inc"
 
 REG_CRTC_STATUS        equ 3dah
 CRTC_VBLANK_BIT        equ 08h
 
 REG_CRTC_STATUS        equ 3dah
 CRTC_VBLANK_BIT        equ 08h
@@ -26,9 +26,8 @@ CRTC_VBLANK_BIT       equ 08h
 REG_DAC_ADDR   equ 3c8h
 REG_DAC_DATA   equ 3c9h
 
 REG_DAC_ADDR   equ 3c8h
 REG_DAC_DATA   equ 3c9h
 
+       extern sprsheet
        extern sprsheet_cmap
        extern sprsheet_cmap
-       extern sprsheet_tiles
-
 
        global init_gfx
 init_gfx:
 
        global init_gfx
 init_gfx:
@@ -119,7 +118,7 @@ slow_sprite:
        add edi, eax            ; di <- (y - 16) * 320 + (x - 16)
        add edi, FRAMEBUF_ADDR
 
        add edi, eax            ; di <- (y - 16) * 320 + (x - 16)
        add edi, FRAMEBUF_ADDR
 
-       mov esi, sprsheet_tiles
+       mov esi, sprsheet
        ; calculate sprite id offset (each spr is 32*32=1k)
        mov eax, [ebp + 8]
        shl eax, 10
        ; calculate sprite id offset (each spr is 32*32=1k)
        mov eax, [ebp + 8]
        shl eax, 10
diff --git a/src/gfx.inc b/src/gfx.inc
new file mode 100644 (file)
index 0000000..de65a12
--- /dev/null
@@ -0,0 +1,18 @@
+; vi:filetype=nasm:
+%ifndef GFX_INC_
+%define GFX_INC_
+
+VIDMEM_ADDR    equ 0a0000h
+FRAMEBUF_ADDR  equ 090000h
+
+%ifndef GFX_ASM_
+       extern init_gfx
+       extern clear
+       extern swap_buffers
+       extern wait_vsync
+       extern slow_sprite
+
+       extern sprsheet
+%endif ; GFX_ASM_
+
+%endif ; GFX_INC_
index 243ff0d..1cf9d6a 100644 (file)
@@ -1,10 +1,6 @@
 ; vi:filetype=nasm ts=8 sts=8 sw=8:
        bits 32
 ; vi:filetype=nasm ts=8 sts=8 sw=8:
        bits 32
-       extern init_gfx
-       extern clear
-       extern slow_sprite
-       extern wait_vsync
-       extern swap_buffers
+%include "gfx.inc"
 
        ; this is placed at the beginning of our binary at 1mb (see game.ld)
        ; and it's what gets executed directly by the boot loader
 
        ; this is placed at the beginning of our binary at 1mb (see game.ld)
        ; and it's what gets executed directly by the boot loader
@@ -19,10 +15,11 @@ main:
 main_loop:
        call clear
 
 main_loop:
        call clear
 
+       push dword 0
        push dword 100
        push dword 160
        push dword 100
        push dword 160
-       push dword 0
-       call slow_sprite
+       push dword FRAMEBUF_ADDR
+       call sprsheet
        add esp, 16
 
        call wait_vsync
        add esp, 16
 
        call wait_vsync
diff --git a/tools/csprite/Makefile b/tools/csprite/Makefile
new file mode 100644 (file)
index 0000000..fa1448d
--- /dev/null
@@ -0,0 +1,13 @@
+src = $(wildcard src/*.c)
+obj = $(src:.c=.o)
+bin = csprite
+
+CFLAGS = -pedantic -Wall -g
+LDFLAGS = -lpng -lz
+
+$(bin): $(obj)
+       $(CC) -o $@ $(obj) $(LDFLAGS)
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(bin)
diff --git a/tools/csprite/src/image.c b/tools/csprite/src/image.c
new file mode 100644 (file)
index 0000000..18fae9b
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.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 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/csprite/src/image.h b/tools/csprite/src/image.h
new file mode 100644 (file)
index 0000000..75a952a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#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 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/csprite/src/main.c b/tools/csprite/src/main.c
new file mode 100644 (file)
index 0000000..dd9be02
--- /dev/null
@@ -0,0 +1,279 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+#include <alloca.h>
+#include "image.h"
+
+struct rect {
+       int x, y, w, h;
+};
+
+int csprite(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 fbpitch = 320;
+const char *name = "sprite";
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *endp;
+
+       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], "-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], "-fbpitch") == 0) {
+                               fbpitch = atoi(argv[++i]);
+                               if(fbpitch <= 0) {
+                                       fprintf(stderr, "-fbpitch must be followed by a positive number\n");
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "-name") == 0) {
+                               name = argv[++i];
+                               if(!name) {
+                                       fprintf(stderr, "%s must be followed by a name\n", argv[i - 1]);
+                                       return 1;
+                               }
+
+                       } else if(strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "-key") == 0) {
+                               ckey = strtol(argv[++i], &endp, 10);
+                               if(endp == argv[i] || ckey < 0 || ckey >= 256) {
+                                       fprintf(stderr, "%s must be followed by a valid color key\n", argv[i - 1]);
+                                       return 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(proc_sheet(argv[i]) == -1) {
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+const char *prefixfmt =
+       "\tglobal %s\n"
+       "%s:\n"
+       "\tmov eax, [esp + 12]\n"
+       "\tmov ecx, %d\n"
+       "\tmul ecx\n"
+       "\tadd eax, [esp + 8]\n"
+       "\tadd eax, [esp + 4]\n"
+       "\tmov edx, eax\n"
+       "\tmov eax, [esp + 16]\n"
+       "\tjmp [.tiletab + eax * 4]\n\n"
+       ".tiletab:\n";
+
+int proc_sheet(const char *fname)
+{
+       int i, j, num_xtiles, num_ytiles, xsz, ysz, tidx;
+       struct image img;
+
+       if(load_image(&img, fname) == -1) {
+               fprintf(stderr, "failed to load image: %s\n", fname);
+               return -1;
+       }
+       if(rect.w <= 0) {
+               rect.w = img.width;
+               rect.h = img.height;
+       }
+
+       if(tile_xsz <= 0) {
+               num_xtiles = num_ytiles = 1;
+               xsz = rect.w;
+               ysz = rect.h;
+       } else {
+               num_xtiles = rect.w / tile_xsz;
+               num_ytiles = rect.h / tile_ysz;
+               xsz = tile_xsz;
+               ysz = tile_ysz;
+       }
+
+       printf(prefixfmt, name, name, fbpitch);
+       for(i=0; i<num_ytiles*num_xtiles; i++) {
+               printf("\tdd .tile%d\n", i);
+       }
+       putchar('\n');
+
+       tidx = 0;
+       for(i=0; i<num_ytiles; i++) {
+               for(j=0; j<num_xtiles; j++) {
+                       printf(".tile%d:\n", tidx++);
+                       csprite(&img, rect.x + j * xsz, rect.y + i * ysz, xsz, ysz);
+               }
+       }
+
+       /* output colormap */
+       printf("\n\tglobal %s_cmap_size\n", name);
+       printf("\tglobal %s_cmap\n", name);
+       printf("%s_cmap_size: dd %d\n\n", name, img.cmap_ncolors);
+       printf("%s_cmap:\n", name);
+
+       for(i=0; i<img.cmap_ncolors; i++) {
+               printf("\tdb %d, %d, %d\n", img.cmap[i].r, img.cmap[i].g, img.cmap[i].b);
+       }
+
+       return 0;
+}
+
+enum {
+       CSOP_SKIP,
+       CSOP_FILL,
+       CSOP_COPY,
+       CSOP_ENDL
+};
+
+struct csop {
+       unsigned char op, val;
+       int len;
+};
+
+int csprite(struct image *img, int x, int y, int xsz, int ysz)
+{
+       int i, j, numops, mode, new_mode, start, skip_acc;
+       unsigned char *pptr = img->pixels + y * img->scansz + x;
+       struct csop *ops, *optr;
+
+       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(*pptr == ckey) {
+                               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++;
+               }
+               pptr += img->scansz - xsz;
+
+               if(mode != -1) {
+                       assert(start >= 0);
+                       optr->op = mode;
+                       optr->len = xsz - start;
+                       optr++;
+               }
+       }
+       numops = optr - ops;
+
+       pptr = img->pixels + y * img->scansz + x;
+       optr = ops;
+       skip_acc = 0;
+       /* edx points to dest */
+       for(i=0; i<numops; i++) {
+               switch(optr->op) {
+               case CSOP_SKIP:
+                       skip_acc += optr->len;
+                       pptr += optr->len;
+                       break;
+
+               case CSOP_ENDL:
+                       skip_acc += fbpitch - xsz;
+                       pptr += img->scansz - xsz;
+                       break;
+
+               case CSOP_COPY:
+                       if(skip_acc) {
+                               printf("\tadd edx, %d\n", skip_acc);
+                               skip_acc = 0;
+                       }
+
+                       for(j=0; j<optr->len / 4; j++) {
+                               printf("\tmov dword [edx + %d], 0x%x\n", j * 4, *(uint32_t*)pptr);
+                               pptr += 4;
+                       }
+                       j *= 4;
+                       switch(optr->len % 4) {
+                       case 3:
+                               printf("\tmov byte [edx + %d], 0x%x\n", j++, (unsigned int)*pptr++);
+                       case 2:
+                               printf("\tmov word [edx + %d], 0x%x\n", j, (unsigned int)*(uint16_t*)pptr);
+                               pptr += 2;
+                               j += 2;
+                               break;
+                       case 1:
+                               printf("\tmov byte [edx + %d], 0x%x\n", j++, (unsigned int)*pptr++);
+                               break;
+                       }
+
+                       skip_acc = optr->len;
+                       break;
+
+               default:
+                       printf("\t; invalid op: %d\n", optr->op);
+               }
+               optr++;
+       }
+       printf("\tret\n");
+
+       return 0;
+}
+
+void print_usage(const char *argv0)
+{
+       printf("Usage: %s [options] <spritesheet>\n", argv0);
+       printf("Options:\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(" -coffset <offs>: colormap offset [0, 255] (default: 0)\n");
+       printf(" -fbpitch <pitch>: target framebuffer pitch (scanline size in bytes)\n");
+       printf(" -k,-key <color>: color-key for transparency (default: 0)\n");
+       printf(" -h: print usage and exit\n");
+}