Before we add more rooms, some rudimentary image compression - decreases executable size (therefore time to upload to the pinetab) about 10x

master
immibis 2025-02-19 04:49:32 +01:00
parent 785642a2c9
commit a04abd101a
6 changed files with 137 additions and 13 deletions

View File

@ -1,6 +1,6 @@
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
OBJS = pinetab2_framework.o game.o textbox.o inventory.o
OBJS = pinetab2_framework.o game.o textbox.o inventory.o decompress_sprite.o
SPRITES = $(patsubst $(PROJECT_ROOT)sprites/%.xcf,%,$(wildcard $(PROJECT_ROOT)sprites/*.xcf))
NAVMESHES = $(patsubst $(PROJECT_ROOT)navmesh/%.tmx,%,$(wildcard $(PROJECT_ROOT)navmesh/*.tmx))
@ -44,15 +44,8 @@ pinetab2_framework: $(OBJS)
clean:
rm -fr pinetab2_framework $(OBJS) $(EXTRA_CLEAN)
sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
;(gimp-image-scale image 1280 800) ;\
(gimp-image-rotate image ROTATE-90) ;\
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
(plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer 0 0 0 1 0 1 0 1 0 0) ; swap red and blue channels \
(file-raw-save 1 image layer "$(patsubst %.o,%.raw,$@)" "$(patsubst %.o,%.raw,$@)") ;\
) ;\
)' -b '(gimp-quit 0)'
sprite_%.o: %.png $(PROJECT_ROOT)compress_sprite.py
python3 "$(PROJECT_ROOT)compress_sprite.py" "$<" $(patsubst %.o,%.raw,$@)
$(OBJCOPY) -I binary --rename-section .data=.rodata,alloc,load,readonly,data,contents $(patsubst %.o,%.raw,$@) $@
rm $(patsubst %.o,%.raw,$@)
@# symbols are _binary_sprite_%_raw_start / _end / _size

59
compress_sprite.py Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python3
import sys
import struct
import PIL.Image
_, inpath, outpath = sys.argv
image = PIL.Image.open(inpath)
assert image.mode == "RGBA", "image mode is "+image.mode
# We measure width and height prior to the rotation
width, height = image.width, image.height
image = image.transpose(PIL.Image.Transpose.ROTATE_270)
with open(outpath, "wb") as out:
out.write(struct.pack("=HH", width, height))
for channel in "BGRA":
pixelbytes = image.getchannel(channel).tobytes()
def rlegroups():
curpixel=None
count=0
litrun = b""
for pixel in pixelbytes:
if curpixel is None:
curpixel = pixel
count = 1
elif curpixel != pixel:
assert count > 0
if count <= 2:
if len(litrun)+count > 127:
assert len(litrun) > 0
yield litrun
litrun = b""
litrun += bytes([curpixel]) * count
else:
if litrun != b"":
assert len(litrun) > 0
yield litrun
litrun = b""
yield (curpixel, count)
curpixel = pixel
count = 1
else:
count += 1
if litrun != b"":
assert len(litrun) > 0
yield litrun
if count > 0:
yield (curpixel, count)
for group in rlegroups():
if type(group) == bytes:
out.write(struct.pack("=B", len(group)+128) + group)
else:
pixel, count = group
assert count > 0
if count >= 128:
out.write(struct.pack("=BIB", 0, count, pixel))
else:
out.write(struct.pack("=BB", count, pixel))

62
decompress_sprite.cpp Normal file
View File

@ -0,0 +1,62 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include "engine.h"
#include <inttypes.h>
// TODO: even better compression using zlib or something - or vector graphics
#define MAX_SPRITES 5000
static struct sprite sprites[MAX_SPRITES];
static int numsprites;
struct sprite *get_decompressed_sprite(const unsigned char *spritedata) {
for(int i = 0; i < numsprites; i++) {
if(sprites[i].original_data_source == spritedata)
return &sprites[i];
}
struct sprite *s = &sprites[numsprites++];
s->width = *(uint16_t*)(spritedata + 0);
s->height = *(uint16_t*)(spritedata + 2);
s->original_data_source = spritedata;
spritedata += 4;
s->pixels = (uint32_t*)malloc(s->width*s->height*4);
if(!s->pixels) error(1, 0, "get_decompressed_sprite: memory allocation failed");
for(int channel = 0; channel < 4; channel++) {
unsigned char *pixels = (unsigned char*)s->pixels;
pixels += channel;
unsigned int pixelsleft = s->width * s->height;
while(pixelsleft > 0) {
unsigned char control = *spritedata++;
if(control & 0x80) {
control &= 0x7f;
if(control > pixelsleft) error(1, 0, "corrupted compressed sprite A %u > %u", (unsigned int)control, (unsigned int)pixelsleft);
for(int i = 0; i < control; i++) {
*pixels = *spritedata++;
pixels += 4;
}
pixelsleft -= control;
} else {
uint32_t count;
if(control == 0) {
count = *(uint32_t*)spritedata;
spritedata += 4;
} else {
count = control;
}
unsigned char value = *spritedata++;
if(count > pixelsleft) error(1, 0, "corrupted compressed sprite B %u > %u", (unsigned int)count, (unsigned int)pixelsleft);
for(uint32_t i = 0; i < count; i++) {
*pixels = value;
pixels += 4;
}
pixelsleft -= count;
}
}
}
return s;
}

View File

@ -83,6 +83,13 @@ void push_scene_textbox(const char *text);
void create_standard_inventory(scene *s);
void standard_inventory_onclick(struct object *obj);
struct sprite {
int width, height;
uint32_t *pixels;
const unsigned char *original_data_source;
};
struct sprite *get_decompressed_sprite(const unsigned char *spritedata);
// used during rendering
// pixel is 0xRRGGBB
extern uint32_t *curfb;

View File

@ -221,7 +221,7 @@ struct object *scene_add_object(struct scene *sc, int id, int x, int y, int widt
objects[i].y = y;
objects[i].width = width;
objects[i].height = height;
objects[i].pixels = pixels;
objects[i].pixels = pixels ? (const char*)get_decompressed_sprite((const unsigned char*)pixels)->pixels : nullptr;
return &objects[i];
}
}

View File

@ -7,7 +7,7 @@ static const char *textbox_text;
static int tb_width_chars, tb_height_chars;
static int visible_chars = 0;
extern const char _binary_sprite_font_raw_start[];
extern const unsigned char _binary_sprite_font_raw_start[];
@ -16,6 +16,9 @@ extern const char _binary_sprite_font_raw_start[];
#define LINESPACE 8
static void textbox_render_fn(struct scene *s) {
struct sprite *fontsprite = get_decompressed_sprite(_binary_sprite_font_raw_start);
int boxheight = tb_height_chars*(CHARSIZE + LINESPACE) - LINESPACE;
int boxwidth = tb_width_chars*(CHARSIZE + HSPACE) - HSPACE;
int x = (1280 - tb_width_chars*CHARSIZE)/2;
@ -31,7 +34,7 @@ static void textbox_render_fn(struct scene *s) {
x = xleft;
y += CHARSIZE + LINESPACE;
} else {
blit(x, y, CHARSIZE, CHARSIZE, (*str - 32)*(CHARSIZE*CHARSIZE) + (uint32_t*)_binary_sprite_font_raw_start);
blit(x, y, CHARSIZE, CHARSIZE, (*str - 32)*(CHARSIZE*CHARSIZE) + (uint32_t*)fontsprite->pixels);
x += CHARSIZE + HSPACE;
}
}