Blob Blame Raw

#include <SDL_image.h>

#include "private.h"
#include "world.h"
#include "sprite.h"


static HeliArray cache;



static int fixsize(int x) {
	int p = (int)ceil(log2(x > 1 ? x : 1) - 0.25);
	if (p > 30) p = 30;
	return 1 << p;
}


static float colorclamp(float x)
	{ return x > 0.f ? (x < 1.f ? x : 1.f) : 0.f; }


static void mipx(float *buffer, int *w, int *h) {
	*w /= 2;
	if (*w <= 0) return;
	for(float *d = buffer, *s = buffer, *end = d + (*w)*(*h); d < end; d += 4, s += 8)
		for(int i = 0; i < 4; ++i)
			d[i] = (s[i] + s[i + 4])*0.5f;
}


static void mipy(float *buffer, int *w, int *h) {
	*h /= 2;
	if (*h <= 0) return;
	float *d = buffer, *s0 = buffer, *s1 = s0 + (*w)*4;
	for(int r = 0; r < *h; ++r, s0 = s1, s1 += (*w)*4)
		for(float *end = d + (*w); d < end; ++d, ++s0, ++s1)
			*d = (*s0 + *s1)*0.5f;
}


static void unloadFrame(void *x) {
	unsigned int texid = (unsigned int)(size_t)x;
	glDeleteTextures(1, &texid);
}


static unsigned int loadFrame(const char *path) {
	if (!heliStringEndsWithLowcase(path, ".png"))
		return 0;
	
	// load image
	SDL_Surface *surface = IMG_Load(path);
	if (!surface) {
		fprintf(stderr, "helianthus: cannot load image: %s\n", path);
		return 0;
	}
	
	// convert to RGBA
	if (surface->format->format != SDL_PIXELFORMAT_RGBA8888) {
		SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
		SDL_Surface *converted = SDL_ConvertSurface(surface, format, 0);
		SDL_FreeFormat(format);
		SDL_FreeSurface(surface);
		surface = converted;
	}
	
	// convert to float and premult alpha
	SDL_LockSurface(surface);
	int w = surface->w;
	int h = surface->h;
	size_t count = (size_t)w*h*4;
	float *buffer = (float*)malloc(count*sizeof(float));
	const unsigned char *pp = (const unsigned char *)surface->pixels;
	for(float *p = buffer, *end = p + count; p < end; p += 4, pp += 4) {
		if (pp[3]) {
			float a = pp[3]/255.f;
			p[0] = pp[0]/255.f*a;
			p[1] = pp[1]/255.f*a;
			p[2] = pp[2]/255.f*a;
			p[3] = a;
		} else {
			p[0] = pp[0]/255.f;
			p[1] = pp[1]/255.f;
			p[2] = pp[2]/255.f;
			p[3] = pp[3]/255.f;
		}
	}
	SDL_UnlockSurface(surface);
	SDL_FreeSurface(surface);
	
	// resample to power of two
	int fw = fixsize(w);
	int fh = fixsize(h);
	if (fw != w || fh != h) {
		float *fbuffer = (float*)malloc((size_t)fw*fh*4*sizeof(float));
		double kr = (double)w/fw;
		double kc = (double)h/fh;
		for(int r = 0; r < fh; ++r) {
			for(int c = 0; c < fw; ++c) {
				double pr1 = r*kr;
				int r0 = (int)floor(pr1 + HELI_PRECISION);
				pr1 -= r0;
				int r1 = r0 + 1;
				if (r1 >= w) r1 = w - 1;
				if (r0 > r1) r0 = r1;
				double pr0 = 1 - pr1;
				
				double pc1 = c*kc;
				int c0 = (int)floor(pc1 + HELI_PRECISION);
				pc1 -= c0;
				int c1 = c0 + 1;
				if (c1 >= h) c1 = h - 1;
				if (c0 > c1) c0 = c1;
				double pc0 = 1 - pc1;
				
				float *p = fbuffer + (r*fw + c)*4;
				const float *p00 = buffer + (r0*w + c0)*4;
				const float *p01 = buffer + (r0*w + c1)*4;
				const float *p10 = buffer + (r1*w + c0)*4;
				const float *p11 = buffer + (r1*w + c1)*4;
				for(int i = 0; i < 4; ++i)
					p[i] = p00[i]*pr0*pc0
					     + p01[i]*pr0*pc1
					     + p10[i]*pr1*pc0
					     + p11[i]*pr1*pc1;
			}
		}
		free(buffer);
		buffer = fbuffer;
		w = fw;
		h = fh;
	}
	
	// fix max texture size
	int maxSize = 0;
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
	if (maxSize < 64) maxSize = 64;
	while(w > maxSize) mipx(buffer, &w, &h);
	while(h > maxSize) mipy(buffer, &w, &h);
	
	// create OpenGL texture
	unsigned int texid = 0;
	glGenTextures(1, &texid);
	glBindTexture(GL_TEXTURE_2D, texid);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	// build mip levels
	unsigned char *ibuffer = malloc((size_t)w*h*4);
	while(1) {
		float *pp = buffer;
		for(unsigned char *p = ibuffer, *end = p + (size_t)w*h*4; p < end; p += 4, pp += 4) {
			if (pp[3] > 1e-5) {
				float k = 1/pp[3];
				p[0] = (unsigned char)floor(colorclamp(pp[0]*k)*255.99);
				p[1] = (unsigned char)floor(colorclamp(pp[1]*k)*255.99);
				p[2] = (unsigned char)floor(colorclamp(pp[2]*k)*255.99);
				p[3] = (unsigned char)floor(colorclamp(pp[3])*255.99);
			} else {
				p[0] = (unsigned char)floor(colorclamp(pp[0])*255.99);
				p[1] = (unsigned char)floor(colorclamp(pp[1])*255.99);
				p[2] = (unsigned char)floor(colorclamp(pp[2])*255.99);
				p[3] = 0;
			}
		}
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ibuffer);
		
		if (w <= 1 && h <= 1) break;
		if (w > 1) mipx(buffer, &w, &h);
		if (h > 1) mipy(buffer, &w, &h);
	}
	
	// done
	free(ibuffer);
	free(buffer);
	glBindTexture(GL_TEXTURE_2D, 0);

	return texid;
}


static HeliAnimation* load(const char *path) {
	HeliAnimation *a = calloc(1, sizeof(*a));
	a->path = heliStringCopy(path);
	
	Directory d = openDirectory(path);
	if (d) {
		int count = directoryGetCount(d);
		for(int i = 0; i < count; ++i) {
			char *p = heliStringConcat3(path, "/", directoryGet(d, i));
			size_t texid = loadFrame(p);
			free(p);
			if (texid) heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame);
		}
		closeDirectory(d);
	} else {
		size_t texid = loadFrame(path);
		if (texid) {
			heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame);
		} else {
			fprintf(stderr, "helianthus: cannot load animation by path: %s\n", path);
		}
	}
	
	return a;
}


static void unload(HeliAnimation *a) {
	assert(!a->refcount);
	free(a->path);
	heliArrayDestroy(&a->frames);
	free(a);
}


HeliAnimation* heliAnimationLoad(const char *path) {
	HeliPair *item = heliStringmapGet(&cache, path);
	if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload);
	HeliAnimation *a = (HeliAnimation*)item->value;
	++a->refcount;
	return a;
}


void heliAnimationUnref(HeliAnimation *a) {
	if (--a->refcount <= 0)
		heliStringmapRemove(&cache, a->path);
}


void heliAnimationFinish()
	{ heliArrayDestroy(&cache); }