160 lines
12 KiB
C
160 lines
12 KiB
C
#ifndef SPRITEFORGE_SFA_H
|
|
#define SPRITEFORGE_SFA_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define SFA_VERSION_MAJOR 1u
|
|
#define SFA_VERSION_MINOR 0u
|
|
#define SFA_FLAG_NORMALS 1u
|
|
#define SFA_FLAG_DEPTH 2u
|
|
#define SFA_FLAG_PALETTE_ALPHA 0x80000000u
|
|
|
|
typedef enum sfa_result { SFA_OK=0, SFA_BAD_ARGUMENT, SFA_TRUNCATED, SFA_BAD_MAGIC,
|
|
SFA_UNSUPPORTED_VERSION, SFA_UNSUPPORTED_FLAGS, SFA_CORRUPT, SFA_NO_MEMORY, SFA_IO_ERROR } sfa_result;
|
|
typedef enum sfa_blend_mode { SFA_BLEND_OPAQUE=0, SFA_BLEND_ALPHA25, SFA_BLEND_ALPHA50,
|
|
SFA_BLEND_ALPHA75, SFA_BLEND_ADDITIVE, SFA_BLEND_SUBTRACTIVE } sfa_blend_mode;
|
|
typedef struct sfa_view { const uint8_t* data; size_t size; uint32_t flags, frame_count;
|
|
uint16_t animation_count, direction_count, layer_count; uint32_t strings_offset,animations_offset,directions_offset,frames_offset,layer_order_offset; } sfa_view;
|
|
typedef struct sfa_animation { uint32_t name_hash,first_frame;uint16_t frames_per_direction,direction_count;const char* name; } sfa_animation;
|
|
typedef struct sfa_direction { int16_t centidegrees;uint32_t first_frame; } sfa_direction;
|
|
typedef struct sfa_frame { uint16_t width,height; int16_t pivot_x,pivot_y; uint16_t duration_ms,flags;
|
|
float depth_min,depth_max; uint32_t offset[3],size[3]; } sfa_frame;
|
|
typedef struct sfa_owned { uint8_t* data; size_t size; sfa_view view; } sfa_owned;
|
|
typedef struct sfa_surface { uint32_t* pixels; int width,height,stride; } sfa_surface;
|
|
typedef struct sfa_light { float x,y,z; uint8_t ambient; } sfa_light;
|
|
|
|
static uint16_t sfa_u16(const uint8_t* p) { return (uint16_t)(p[0]|((uint16_t)p[1]<<8)); }
|
|
static int16_t sfa_i16(const uint8_t* p) { return (int16_t)sfa_u16(p); }
|
|
static uint32_t sfa_u32(const uint8_t* p) { return (uint32_t)p[0]|((uint32_t)p[1]<<8)|((uint32_t)p[2]<<16)|((uint32_t)p[3]<<24); }
|
|
static float sfa_f32(const uint8_t* p) { uint32_t u=sfa_u32(p); float f; memcpy(&f,&u,4); return f; }
|
|
static int sfa_range(size_t n, uint32_t off, size_t amount) { return off<=n && amount<=n-off; }
|
|
|
|
static sfa_result sfa_open(const void* bytes, size_t size, sfa_view* out) {
|
|
const uint8_t* p=(const uint8_t*)bytes; uint32_t file_size, known=3u;
|
|
if(!p||!out) return SFA_BAD_ARGUMENT; if(size<64) return SFA_TRUNCATED;
|
|
if(memcmp(p,"SFA\0",4)) return SFA_BAD_MAGIC;
|
|
if(sfa_u16(p+4)!=1 || sfa_u16(p+6)>0) return SFA_UNSUPPORTED_VERSION;
|
|
out->flags=sfa_u32(p+8); if((out->flags & ~known)!=0) return SFA_UNSUPPORTED_FLAGS;
|
|
file_size=sfa_u32(p+12); if(file_size!=size) return SFA_CORRUPT;
|
|
out->animation_count=sfa_u16(p+16); out->direction_count=sfa_u16(p+18);
|
|
out->frame_count=sfa_u32(p+20); out->layer_count=sfa_u16(p+24);
|
|
out->strings_offset=sfa_u32(p+28);out->animations_offset=sfa_u32(p+32);out->directions_offset=sfa_u32(p+36);
|
|
out->frames_offset=sfa_u32(p+40); out->layer_order_offset=sfa_u32(p+44);
|
|
if(!sfa_range(size,out->animations_offset,(size_t)out->animation_count*16u))return SFA_CORRUPT;
|
|
if(!sfa_range(size,out->directions_offset,(size_t)out->direction_count*8u))return SFA_CORRUPT;
|
|
if(!sfa_range(size,out->frames_offset,(size_t)out->frame_count*48u)) return SFA_CORRUPT;
|
|
if(!sfa_range(size,out->layer_order_offset,(size_t)out->frame_count*out->layer_count*2u)) return SFA_CORRUPT;
|
|
out->data=p; out->size=size; return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfa_get_animation(const sfa_view* v,uint16_t index,sfa_animation* out){
|
|
const uint8_t* p;uint32_t name_offset,absolute;const uint8_t* end;
|
|
if(!v||!out||index>=v->animation_count)return SFA_BAD_ARGUMENT;p=v->data+v->animations_offset+(size_t)index*16u;
|
|
out->name_hash=sfa_u32(p);name_offset=sfa_u32(p+4);out->first_frame=sfa_u32(p+8);out->frames_per_direction=sfa_u16(p+12);out->direction_count=sfa_u16(p+14);
|
|
absolute=v->strings_offset+name_offset;if(absolute<v->strings_offset||absolute>=v->animations_offset)return SFA_CORRUPT;
|
|
end=(const uint8_t*)memchr(v->data+absolute,0,v->animations_offset-absolute);if(!end)return SFA_CORRUPT;out->name=(const char*)(v->data+absolute);return SFA_OK;
|
|
}
|
|
static sfa_result sfa_get_direction(const sfa_view* v,uint16_t index,sfa_direction* out){
|
|
const uint8_t* p;if(!v||!out||index>=v->direction_count)return SFA_BAD_ARGUMENT;p=v->data+v->directions_offset+(size_t)index*8u;
|
|
out->centidegrees=sfa_i16(p);if(sfa_u16(p+2)!=0)return SFA_CORRUPT;out->first_frame=sfa_u32(p+4);return SFA_OK;
|
|
}
|
|
static sfa_result sfa_get_layer_order(const sfa_view* v,uint32_t frame_index,uint16_t position,uint16_t* layer){
|
|
size_t index;if(!v||!layer||frame_index>=v->frame_count||position>=v->layer_count)return SFA_BAD_ARGUMENT;
|
|
index=(size_t)frame_index*v->layer_count+position;*layer=sfa_u16(v->data+v->layer_order_offset+index*2u);if(*layer>=v->layer_count)return SFA_CORRUPT;return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfa_get_frame(const sfa_view* v, uint32_t index, sfa_frame* f) {
|
|
const uint8_t* p; int i; if(!v||!f||index>=v->frame_count) return SFA_BAD_ARGUMENT;
|
|
p=v->data+v->frames_offset+(size_t)index*48u;
|
|
f->width=sfa_u16(p); f->height=sfa_u16(p+2); f->pivot_x=sfa_i16(p+4); f->pivot_y=sfa_i16(p+6);
|
|
f->duration_ms=sfa_u16(p+8); f->flags=sfa_u16(p+10); f->depth_min=sfa_f32(p+12); f->depth_max=sfa_f32(p+16);
|
|
for(i=0;i<3;i++){ f->offset[i]=sfa_u32(p+20+i*8); f->size[i]=sfa_u32(p+24+i*8);
|
|
if(f->offset[i] && !sfa_range(v->size,f->offset[i],f->size[i])) return SFA_CORRUPT; }
|
|
return SFA_OK;
|
|
}
|
|
|
|
static uint32_t sfa_mix(uint32_t d,uint32_t s,sfa_blend_mode m){
|
|
unsigned dr=d&255,dg=(d>>8)&255,db=(d>>16)&255,sr=s&255,sg=(s>>8)&255,sb=(s>>16)&255,a;
|
|
if(m==SFA_BLEND_OPAQUE)return s|0xff000000u;
|
|
if(m==SFA_BLEND_ADDITIVE){sr=dr+sr>255?255:dr+sr;sg=dg+sg>255?255:dg+sg;sb=db+sb>255?255:db+sb;}
|
|
else if(m==SFA_BLEND_SUBTRACTIVE){sr=dr>sr?dr-sr:0;sg=dg>sg?dg-sg:0;sb=db>sb?db-sb:0;}
|
|
else {a=m==SFA_BLEND_ALPHA25?64u:m==SFA_BLEND_ALPHA50?128u:192u;sr=(dr*(256-a)+sr*a)>>8;sg=(dg*(256-a)+sg*a)>>8;sb=(db*(256-a)+sb*a)>>8;}
|
|
return 0xff000000u|sr|(sg<<8)|(sb<<16);
|
|
}
|
|
|
|
static sfa_result sfa_blit(const sfa_view* v,const sfa_frame* f,const uint8_t rgb[768],const uint8_t* map,
|
|
sfa_surface dst,int anchor_x,int anchor_y,sfa_blend_mode mode){
|
|
int sy,dy,x0=anchor_x-f->pivot_x,y0=anchor_y-f->pivot_y; const uint8_t* base; uint32_t table;
|
|
if(!v||!f||!rgb||!dst.pixels||dst.stride<dst.width||!f->offset[0])return SFA_BAD_ARGUMENT;
|
|
base=v->data+f->offset[0]; table=4u*((uint32_t)f->height+1u); if(f->size[0]<table)return SFA_CORRUPT;
|
|
for(sy=0;sy<(int)f->height;sy++){const uint8_t *p,*end;uint16_t n,i;dy=y0+sy;if(dy<0||dy>=dst.height)continue;
|
|
{uint32_t a=sfa_u32(base+sy*4),b=sfa_u32(base+(sy+1)*4);if(a>b||b>f->size[0]||b-a<2)return SFA_CORRUPT;p=base+a;end=base+b;}
|
|
n=sfa_u16(p);p+=2;for(i=0;i<n;i++){uint16_t sx,len,j;if((size_t)(end-p)<4)return SFA_CORRUPT;sx=sfa_u16(p);len=sfa_u16(p+2);p+=4;if((size_t)(end-p)<len)return SFA_CORRUPT;
|
|
for(j=0;j<len;j++){int dx=x0+sx+j;if(dx>=0&&dx<dst.width){uint8_t k=map?map[p[j]]:p[j];uint32_t c=rgb[k*3]|((uint32_t)rgb[k*3+1]<<8)|((uint32_t)rgb[k*3+2]<<16);dst.pixels[(size_t)dy*dst.stride+dx]=sfa_mix(dst.pixels[(size_t)dy*dst.stride+dx],c,mode);}}p+=len;}
|
|
}return SFA_OK;
|
|
}
|
|
|
|
typedef struct sfp_view { const uint8_t* data; size_t size; uint32_t palette_count, colormap_count, records_offset, colormap_records_offset; } sfp_view;
|
|
|
|
static sfa_result sfp_open(const void* bytes,size_t size,sfp_view* out){
|
|
const uint8_t* p=(const uint8_t*)bytes;uint32_t file_size;
|
|
if(!p||!out)return SFA_BAD_ARGUMENT;if(size<40)return SFA_TRUNCATED;
|
|
if(memcmp(p,"SFP\0",4))return SFA_BAD_MAGIC;
|
|
if(sfa_u16(p+4)!=1||sfa_u16(p+6)>0)return SFA_UNSUPPORTED_VERSION;
|
|
if(sfa_u32(p+8)!=0)return SFA_UNSUPPORTED_FLAGS;
|
|
file_size=sfa_u32(p+12);if(file_size!=size)return SFA_CORRUPT;
|
|
out->palette_count=sfa_u32(p+16);out->colormap_count=sfa_u32(p+20);out->records_offset=sfa_u32(p+28);out->colormap_records_offset=sfa_u32(p+32);
|
|
if(!sfa_range(size,out->records_offset,(size_t)out->palette_count*20u)||!sfa_range(size,out->colormap_records_offset,(size_t)out->colormap_count*20u))return SFA_CORRUPT;
|
|
out->data=p;out->size=size;return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfp_palette(const sfp_view* v,uint32_t index,const uint8_t** rgb){
|
|
const uint8_t* r;uint32_t off;if(!v||!rgb||index>=v->palette_count)return SFA_BAD_ARGUMENT;
|
|
r=v->data+v->records_offset+(size_t)index*20u;off=sfa_u32(r+8);
|
|
if(!sfa_range(v->size,off,768))return SFA_CORRUPT;*rgb=v->data+off;return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfp_colormap(const sfp_view* v,uint32_t index,const uint8_t** map){
|
|
const uint8_t* r;uint32_t off;if(!v||!map||index>=v->colormap_count)return SFA_BAD_ARGUMENT;
|
|
r=v->data+v->colormap_records_offset+(size_t)index*20u;off=sfa_u32(r+8);
|
|
if(!sfa_range(v->size,off,256))return SFA_CORRUPT;*map=v->data+off;return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfa_blit_lit(const sfa_view* v,const sfa_frame* f,const uint8_t rgb[768],const uint8_t* map,
|
|
sfa_surface dst,int ax,int ay,sfa_blend_mode mode,sfa_light light){
|
|
int sy,dy,x0=ax-f->pivot_x,y0=ay-f->pivot_y;const uint8_t *cb,*nb;float ll;
|
|
if(!v||!f||!rgb||!dst.pixels||dst.stride<dst.width||!f->offset[0]||!f->offset[1])return SFA_BAD_ARGUMENT;
|
|
cb=v->data+f->offset[0];nb=v->data+f->offset[1];
|
|
if(f->size[0]<4u*((uint32_t)f->height+1u)||f->size[1]<4u*((uint32_t)f->height+1u))return SFA_CORRUPT;
|
|
ll=sqrtf(light.x*light.x+light.y*light.y+light.z*light.z);if(ll>0){light.x/=ll;light.y/=ll;light.z/=ll;}
|
|
for(sy=0;sy<(int)f->height;sy++){const uint8_t *cp,*ce,*np,*ne;uint32_t ca,cz,na,nz;uint16_t count,ncount,i;
|
|
dy=y0+sy;if(dy<0||dy>=dst.height)continue;
|
|
ca=sfa_u32(cb+sy*4);cz=sfa_u32(cb+(sy+1)*4);na=sfa_u32(nb+sy*4);nz=sfa_u32(nb+(sy+1)*4);
|
|
if(ca>cz||cz>f->size[0]||na>nz||nz>f->size[1]||cz-ca<2||nz-na<2)return SFA_CORRUPT;
|
|
cp=cb+ca;ce=cb+cz;np=nb+na;ne=nb+nz;count=sfa_u16(cp);ncount=sfa_u16(np);cp+=2;np+=2;if(count!=ncount)return SFA_CORRUPT;
|
|
for(i=0;i<count;i++){uint16_t x,n,nx,nn,j;if((size_t)(ce-cp)<4||(size_t)(ne-np)<4)return SFA_CORRUPT;
|
|
x=sfa_u16(cp);n=sfa_u16(cp+2);nx=sfa_u16(np);nn=sfa_u16(np+2);cp+=4;np+=4;
|
|
if(x!=nx||n!=nn||(size_t)(ce-cp)<n||(size_t)(ne-np)<(size_t)n*2)return SFA_CORRUPT;
|
|
for(j=0;j<n;j++){int dx=x0+x+j;if(dx>=0&&dx<dst.width){uint8_t k=map?map[cp[j]]:cp[j];float vx=(float)(int8_t)np[j*2]/127.0f,vy=(float)(int8_t)np[j*2+1]/127.0f;float vz=sqrtf(fmaxf(0.0f,1.0f-vx*vx-vy*vy));float d=vx*light.x+vy*light.y+vz*light.z;unsigned q=(unsigned)light.ambient+(unsigned)(d>0?d*(255-light.ambient):0);unsigned r=rgb[k*3]*q/255,g=rgb[k*3+1]*q/255,b=rgb[k*3+2]*q/255;uint32_t c=r|(g<<8)|(b<<16);uint32_t* target=dst.pixels+(size_t)dy*dst.stride+dx;*target=sfa_mix(*target,c,mode);}}
|
|
cp+=n;np+=(size_t)n*2;}
|
|
}return SFA_OK;
|
|
}
|
|
|
|
static sfa_result sfa_load_file(const char* path,sfa_owned* o){FILE* f;long n;size_t got;sfa_result r;if(!path||!o)return SFA_BAD_ARGUMENT;memset(o,0,sizeof(*o));f=fopen(path,"rb");if(!f)return SFA_IO_ERROR;
|
|
if(fseek(f,0,SEEK_END)|| (n=ftell(f))<0 || fseek(f,0,SEEK_SET)){fclose(f);return SFA_IO_ERROR;}o->data=(uint8_t*)malloc((size_t)n);if(!o->data){fclose(f);return SFA_NO_MEMORY;}got=fread(o->data,1,(size_t)n,f);fclose(f);if(got!=(size_t)n){free(o->data);memset(o,0,sizeof(*o));return SFA_IO_ERROR;}o->size=(size_t)n;r=sfa_open(o->data,o->size,&o->view);if(r!=SFA_OK){free(o->data);memset(o,0,sizeof(*o));}return r;}
|
|
static void sfa_free_file(sfa_owned* o){if(o){free(o->data);memset(o,0,sizeof(*o));}}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif
|