img.c

///
// @module Lil
// @license MPL-2.0
// @author eiko
#include <math.h>

#include "inc.h"

/// **Props:** int w, int h, lil_Image** ud, [string path]
// @type Img

lil_Image** lil_getImagePointer(lua_State* L, const int i){
	lua_getfield(L, i, "ud");
	lil_Image** imgp = ((lil_Image**)luaL_checkudata(L, -1, LIL_IMG_UD_MT));
	lua_pop(L, 1);
	return imgp;
}
lil_Image* lil_getImage(lua_State* L, const int i){
	return *lil_getImagePointer(L, i);
}
static lil_Image* lil_newImage_(lua_State* L, const int w, const int h){
	assert(w && h);
	#ifdef LIL_USE_LIMITS
	if(((size_t)w * (size_t)h) > LIL_MAX_IMAGE)
		return NULL;
	#endif
	lil_Image* img = malloc(sizeof(lil_Image));
	img->w = w;
	img->h = h;
	return img;
}
lil_Image* lil_newImageCalloc(lua_State* L, const int w, const int h){
	lil_Image* img = lil_newImage_(L, w, h);
	if(!img) return NULL;
	img->d = calloc(w * h, sizeof(lil_Colour));
	if(!img->d){
		free(img);
		return NULL;
	}
	return img;
}
lil_Image* lil_newImage(lua_State* L, const int w, const int h){
	lil_Image* img = lil_newImage_(L, w, h);
	if(!img) return NULL;
	img->d = malloc(w * h * sizeof(lil_Colour));
	if(!img->d){
		free(img);
		return NULL;
	}
	return img;
}
lil_Image* lil_cloneImage(lua_State* L, const lil_Image* img){
	lil_Image* newImg = lil_newImage(L, img->w, img->h);
	if(!newImg) return NULL;
	memcpy(newImg->dptr, img->dptr, img->w * img->h * sizeof(img->d[0]));
	return newImg;
}
void lil_freeImage(const lil_Image* img){
	free(img->d);
	free((void*)img);
}
void lil_pushImage(lua_State* L, const lil_Image* img){
	lua_newtable(L);

	*((lil_Image**)lua_newuserdata(L, sizeof(lil_Image*))) = (lil_Image*)img;
	luaL_getmetatable(L, LIL_IMG_UD_MT);
	lua_setmetatable(L, -2);
	lua_setfield(L, -2, "ud");

	lua_pushinteger(L, img->w);
	lua_setfield(L, -2, "w");
	lua_pushinteger(L, img->h);
	lua_setfield(L, -2, "h");

	luaL_getmetatable(L, LIL_IMG_MT);
	lua_setmetatable(L, -2);
}
void lil_setImageSize(lua_State* L, lil_Image* img){
	lua_pushinteger(L, img->w);
	lua_setfield(L, -2, "w");
	lua_pushinteger(L, img->h);
	lua_setfield(L, -2, "h");
}
void lil_fillImage(const lil_Image* img, const lil_Colour col){
	if((col.r == col.g && col.g == col.b && col.b == col.a) || !col.a){
		memset(img->dptr, col.a, img->w * img->h * sizeof(lil_Colour));
	} else {
		for(int o = 0; o < img->w; o++)
			img->d[o] = col;
		for(int o = 1; o < img->h; o++)
			memcpy(&img->d[XY(img, 0, o)], &img->d[0], img->w * sizeof(lil_Colour));
	}
}


///
// @function Img:resize
// @int w
// @int[opt=w] h
// @tab[opt={}] opts
// @string[opt="bilinear"|"box"] opts.sample
// @see Enums
static LUAFUNC(img_resize){
	lil_Image** imgp = lil_getImagePointer(L, 1);
	const lil_Image* img = *imgp;
	const int w = luaL_checkinteger(L, 2);
	const int h = luaL_optinteger(L, 3, w);
	luaL_argcheck(L, w > 0, 2, "Invalid width");
	luaL_argcheck(L, h > 0, 3, "Invalid height");
	if(w == img->w && h == img->h){
		lua_pushvalue(L, 1);
		return 1;
	}
	lil_Image* imgNew = lil_newImage(L, w, h);
	if(!imgNew)
		return 0;
	lil_Number scaleX = (lil_Number)img->w / (lil_Number)w;
	lil_Number scaleY = (lil_Number)img->h / (lil_Number)h;
	enum lil_SampleMode sm = lil_getOptSampleMode(L, 4, scaleX, scaleY);
	#pragma omp parallel for
	for(int y = 0; y < h; y++)
		for(int x = 0; x < w; x++)
			imgNew->d[(y * w) + x] = lil_sampleImage(sm, img, (lil_Number)x * scaleX, (lil_Number)y * scaleY, scaleX, scaleY);
	*imgp = imgNew;
	lil_freeImage(img);
	lua_pushvalue(L, 1);
	lil_setImageSize(L, imgNew);
	return 1;
}

///
// @function Img:crop
// @int x
// @int y
// @int w
// @int h
static LUAFUNC(img_crop){
	// TODO  modes
	lil_Image** imgp = lil_getImagePointer(L, 1);
	const lil_Image* img = *imgp;
	const int x = luaL_checkinteger(L, 2);
	const int y = luaL_checkinteger(L, 3);
	const int w = luaL_checkinteger(L, 4);
	const int h = luaL_checkinteger(L, 5);
	luaL_argcheck(L, x >= 0,                    2, "");
	luaL_argcheck(L, y >= 0,                    3, "");
	luaL_argcheck(L, w >  0 && x + w <= img->w, 4, "");
	luaL_argcheck(L, h >  0 && y + h <= img->h, 5, "");
	lil_Image* imgNew = lil_newImage(L, w, h);
	if(!imgNew)
		return 0;
	for(int py = 0; py < h; py++){
		const size_t os = XY(img, x, y + py);
		const size_t od = XY(imgNew, 0, py);
		memcpy(&imgNew->d[od], &img->d[os], w * 4);
	}
	*imgp = imgNew;
	lil_freeImage(img);
	lua_pushvalue(L, 1);
	lil_setImageSize(L, imgNew);
	return 1;
}

/// Returns the pixel at x,y or nil if its out of range
// @function Img:getPixel
// @int x
// @int y
// @return r, g, b, a
// @return nil
static LUAFUNC(img_getPixel){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Number x = luaL_checknumber(L, 2);
	const lil_Number y = luaL_checknumber(L, 3);
	if(x >= img->w || y >= img->h || x < 0 || y < 0)
		return 0;
	const enum lil_SampleMode sm = lil_getOptSampleMode(L, 6, 1, 1);
	const lil_Colour col = lil_sampleImage(sm, img, x, y, 1, 1);
	lua_pushnumber(L, (lil_Number)col.r / (lil_Number)0xff);
	lua_pushnumber(L, (lil_Number)col.g / (lil_Number)0xff);
	lua_pushnumber(L, (lil_Number)col.b / (lil_Number)0xff);
	lua_pushnumber(L, (lil_Number)col.a / (lil_Number)0xff);
	return 4;
}

/// Sets a pixel at x,y.
// Does not return the Img since it's a bit more speed optimised and niche, the sort of thing you use in a loop instead of chaining
// @function Img:setPixel
// @float r
// @float g
// @float b
// @float a
// @int x
// @int y
static LUAFUNC(img_setPixel){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Number r = luaL_checknumber(L, 2);
	const lil_Number g = luaL_checknumber(L, 3);
	const lil_Number b = luaL_checknumber(L, 4);
	const lil_Number a = luaL_checknumber(L, 5);
	const int x = luaL_checkinteger(L, 6);
	const int y = luaL_checkinteger(L, 7);
	if(x >= img->w || y >= img->h || x < 0 || y < 0)
		return 0;
	const lil_Colour col = {{ r * 0xff, g * 0xff, b * 0xff, a * 0xff }};
	img->d[XY(img, x, y)] = col;
	return 4;
}

///
// @function Img:clone
// @return Img
static LUAFUNC(img_clone){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Image* imgNew = lil_cloneImage(L, img);
	if(!imgNew)
		return 0;
	lil_pushImage(L, imgNew);
	return 1;
}

/// Composites the given image on top
// @function Img:comp
// @Img top
// @int[opt=0] x Bottom X, Where on the bottom image the top gets placed
// @int[opt=0] y Bottom Y
// @tab[opt={}] opts
// @tab opts.crop { x, y, w, h }
// @string opts.blend
// @see Enums
static LUAFUNC(img_comp){
	const lil_Image* bot = lil_getImage(L, 1);
	const lil_Image* top = lil_getImage(L, 2);
	int bx = luaL_optnumber(L, 3, 0);
	int by = luaL_optnumber(L, 4, 0);
	int tx = 0;
	int ty = 0;
	int w = top->w;
	int h = top->h;
	lil_getOptRect(L, 5, "crop", &tx, &ty, &w, &h);
	lua_pushvalue(L, 1);
	if(lil_constrainRectImg(L, bot, &bx, &by, top, &tx, &ty, &w, &h))
		return 1;
	const enum lil_BlendMode comp = lil_getOptBlendMode(L, 5);
	#pragma omp parallel for
	for(int y = 0; y < h; y++){
		for(int x = 0; x < w; x++){
			assert(x >= 0 && x + bx < bot->w && y >= 0 && y + by < bot->h);
			assert(x >= 0 && x + tx < top->w && y >= 0 && y + ty < top->h);
			const size_t b = XY(bot, x + bx, y + by);
			const size_t t = XY(top, x + tx, y + ty);
			lil_composite(comp, &bot->d[b], &top->d[t]);
		}
	}
	return 1;
}

///
// @function Img:rotate
// @float degrees
static LUAFUNC(img_rotate){
	lil_Image** imgp = lil_getImagePointer(L, 1);
	const lil_Image* img = *imgp;
	const lil_Number rad = degToRad(luaL_checknumber(L, 2));
	const lil_Number s = sin(rad);
	const lil_Number c = cos(rad);
	const lil_Number px = ((lil_Number)img->w)/2.;
	const lil_Number py = ((lil_Number)img->h)/2.;
	lil_Image* newImg = lil_newImage(L, img->w, img->h);
	if(!newImg)
		return 0;
	#pragma omp parallel for
	for(int y = 0; y < img->h; y++){
		for(int x = 0; x < img->w; x++){
			float srcX = ((x-px) * c + (y-py) * s) + px;
			float srcY = ((y-py) * c - (x-px) * s) + py;
			if(srcX >= img->w || srcX < 0 || srcY >= img->h || srcY < 0){
				newImg->d[XY(img, x, y)].c = 0;
				continue;
			}
			newImg->d[XY(img, x, y)] = lil_sampleImage(LIL_SMBILINEAR, img, srcX, srcY, 1, 1);
		}
	}
	*imgp = newImg;
	lil_freeImage(img);
	lua_pushvalue(L, 1);
	return 1;
}

/// Example: lil.Img (707x1000 [test/beer.jpg] 0x0151c880)
// @function Img:__tostring
// @return string
static LUAFUNC(img_tostring){
	const lil_Image* img = lil_getImage(L, 1);
	lua_getfield(L, 1, "path");
	if(lua_type(L, -1) == LUA_TSTRING){
		const char* path = lua_tostring(L, -1);
		lua_pushfstring(L, "lil.Img (%dx%d [%s] %p)", img->w, img->h, path, img);
	} else {
		lua_pushfstring(L, "lil.Img (%dx%d %p)", img->w, img->h, img);
	}
	return 1;
}

/// == equality check, alpha aware
// @function Img:__eq
// @return bool
static LUAFUNC(img_eq){
	const lil_Image* img1 = lil_getImage(L, 1);
	const lil_Image* img2 = lil_getImage(L, 2);
	if(img1->w != img2->w || img1->h != img2->h){
		lua_pushboolean(L, false);
		return 1;
	}
	volatile bool flag = true;
	#pragma omp parallel for
	for(size_t o = 0; o < img1->w * img1->h; o++){
		const lil_Colour c1 = img1->d[o];
		const lil_Colour c2 = img2->d[o];
		if(c1.rgb * c1.a != c2.rgb * c2.a)
			flag = false;
	}
	lua_pushboolean(L, flag);
	return 1;
}

///
// @function Img:isOpaque
// @return bool
static LUAFUNC(img_isOpaque){
	const lil_Image* img = lil_getImage(L, 1);
	volatile bool is = true;
	#pragma omp parallel for
	#if INTPTR_MAX == INT64_MAX
	for(size_t o = 0; o < (img->w * img->h) / 2; o++)
		if((((uint64_t*)img->d)[o] & 0xff000000ff000000) != 0xff000000ff000000)
			is = false;
	if(img->w & img->h & 1)
		if((img->d[(img->w * img->h) - 1].c & 0xff000000) != 0xff000000)
			is = false;
	#else
	for(size_t o = 0; o < img->w * img->h; o++)
		if((img->d[o].c & 0xff000000) != 0xff000000)
			is = false;
	#endif
	lua_pushboolean(L, is);
	return 1;
}

LUAREG(imgLib) = {
	{ "setPixel", LUAFUNCD(img_setPixel) },
	{ "getPixel", LUAFUNCD(img_getPixel) },
	{ "resize", LUAFUNCD(img_resize) },
	{ "crop", LUAFUNCD(img_crop) },
	{ "clone", LUAFUNCD(img_clone) },
	{ "comp", LUAFUNCD(img_comp) },
	{ "rotate", LUAFUNCD(img_rotate) },
	{ "__tostring", LUAFUNCD(img_tostring) },
	{ "__eq", LUAFUNCD(img_eq) },
	{ "isOpaque", LUAFUNCD(img_isOpaque) },
	{ NULL, NULL }
};

static LUAFUNC(img_ud_gc){
	lil_freeImage(*((lil_Image**)luaL_checkudata(L, 1, LIL_IMG_UD_MT)));
	return 0;
}
LUAREG(imgUdLib) = {
	{ "__gc", LUAFUNCD(img_ud_gc) },
	{ NULL, NULL }
};
generated by LDoc 1.4.6 Last updated 2023-04-13 13:58:34