From 2bd7529337d75380170f58364b1328ef3b8dc845 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Sun, 8 Mar 2020 07:54:49 +0200 Subject: [PATCH] moving to RLE sprites instead of compiled sprites --- .gitignore | 1 + GNUmakefile | 15 ++- src/introscr.c | 4 +- src/sprite.c | 136 +++++++++++++++++++ src/sprite.h | 37 +++++ tools/procdata | 30 +++++ tools/rlesprite/Makefile | 13 ++ tools/rlesprite/src/image.c | 293 ++++++++++++++++++++++++++++++++++++++++ tools/rlesprite/src/image.h | 30 +++++ tools/rlesprite/src/main.c | 313 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 868 insertions(+), 4 deletions(-) create mode 100644 src/sprite.c create mode 100644 src/sprite.h create mode 100755 tools/procdata create mode 100644 tools/rlesprite/Makefile create mode 100644 tools/rlesprite/src/image.c create mode 100644 tools/rlesprite/src/image.h create mode 100644 tools/rlesprite/src/main.c diff --git a/.gitignore b/.gitignore index 04f87b7..c96a806 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ data *.COF *.dja *.DJA +tools/rlesprite/rlesprite diff --git a/GNUmakefile b/GNUmakefile index 817daff..3fbb73f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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 diff --git a/src/introscr.c b/src/introscr.c index 5690561..c9f3a03 100644 --- a/src/introscr.c +++ b/src/introscr.c @@ -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 +#include +#include +#include +#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; inum_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; inum_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; inum_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 index 0000000..85c4027 --- /dev/null +++ b/src/sprite.h @@ -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 index 0000000..2f1b3f2 --- /dev/null +++ b/tools/procdata @@ -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 index 0000000..77daec7 --- /dev/null +++ b/tools/rlesprite/Makefile @@ -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 index 0000000..84bad81 --- /dev/null +++ b/tools/rlesprite/src/image.c @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#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; iscansz); + 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; iheight; 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; iheight; i++) { + for(j=0; jwidth; 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; iheight; 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; ibpp / 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 +#include +#include +#include +#include +#include +#include +#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= 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; ibpp / 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 0) { + optr++->op = CSOP_ENDL; + } + + for(j=0; j= 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; iop) { + 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] \n", argv0); + printf("Options:\n"); + printf(" -o : output filename (default: stdout)\n"); + printf(" -s,-size : tile size (default: whole image)\n"); + printf(" -r,-rect : use rectangle of the input image (default: whole image)\n"); + printf(" -p,-pad : how many pixels to skip between tiles in source image (default: 0)\n"); + printf(" -coffset : colormap offset [0, 255] (default: 0)\n"); + printf(" -k,-key : color-key for transparency (default: 0)\n"); + printf(" -conv565: convert image to 16bpp 565 before processing\n"); + printf(" -h: print usage and exit\n"); +} -- 1.7.10.4