Before we add more rooms, some rudimentary image compression - decreases executable size (therefore time to upload to the pinetab) about 10x
parent
785642a2c9
commit
a04abd101a
13
Makefile
13
Makefile
|
@ -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
|
||||
|
|
|
@ -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))
|
|
@ -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;
|
||||
}
|
||||
|
7
engine.h
7
engine.h
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue