imgDraw.c

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

#include "inc.h"
#define ENTRY(id) static LUAFUNC(imgDraw_ ## id)

/// @type Img

/// @section filters

/// Fills the entire image with a single colour
// @function Img:fill
// @colour colour
ENTRY(fill){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Colour col = lil_getColour(L, 2, NULL);
	lil_fillImage(img, col);
	lua_pushvalue(L, 1);
	return 1;
}

///
// @function Img:rect
// @colour colour
// @int x
// @int y
// @int w
// @int h
// @tab[opt={}] opts
// @string opts.blend
// @int opts.corners How many pixels to round the corners
// @see Enums
ENTRY(rect){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Colour col = lil_getColour(L, 2, NULL);
	int ox = lua_tointeger(L, 3);
	int oy = lua_tointeger(L, 4);
	int w  = luaL_checkinteger(L, 5);
	int h  = luaL_checkinteger(L, 6);
	int corn = clamp(lil_getOptInt(L, 7, "corners", 0), 0, min(w, h) / 2);
	const enum lil_BlendMode comp = lil_getOptBlendMode(L, 7);
	if(corn){
		for(int i = 0; i < 3; i++){ // Three rectangles make a cross
			int mx, my, mw, mh;
			switch(i){
				case 0: mx = ox + corn; my = oy;            mw = w - corn * 2; mh = corn;         break;
				case 1: mx = ox;        my = oy + corn;     mw = w;            mh = h - corn * 2; break;
				case 2: mx = ox + corn; my = oy + h - corn; mw = w - corn * 2; mh = corn;         break;
			}
			if(lil_constrainRect(L, img, &mx, &my, &mw, &mh))
				continue;
			for(int y = my; y < mh + my; y++){
				for(int x = mx; x < mw + mx; x++){
					assert(x >= 0 && x < img->w && y >= 0 && y < img->h);
					size_t o = XY(img, x, y);
					lil_composite(comp, &img->d[o], &col);
				}
			}
		}
		for(int px = -corn; px < 0; px++){ // Fill in corners with quarter circles
			int height =  sqrt(corn * corn - px * px);
			for(int py = -height; py < 0; py++){
				for(int i = 0; i < 4; i++){
					int x, y;
					switch(i){
						case 0: x = px + ox + corn;         y = py + oy + corn;         break;
						case 1: x = ox - px + w - corn - 1; y = py + oy + corn;         break;
						case 2: x = ox + px + corn;         y = oy - py + h - corn - 1; break;
						case 3: x = ox - px + w - corn - 1; y = oy - py + h - corn - 1; break;
					}
					if(x < 0 || x >= img->w || y < 0 || y >= img->h)
						continue;
					size_t o = XY(img, x, y);
					lil_composite(comp, &img->d[o], &col);
				}
			}
		}
	} else {
		if(lil_constrainRect(L, img, &ox, &oy, &w, &h))
			return 1;
		for(int y = oy; y < h + oy; y++){
			for(int x = ox; x < w + ox; x++){
				assert(x >= 0 && x < img->w && y >= 0 && y < img->h);
				size_t o = XY(img, x, y);
				lil_composite(comp, &img->d[o], &col);
			}
		}
	}
	lua_pushvalue(L, 1);
	return 1;
}

///
// @function Img:circle
// @colour colour
// @int x
// @int y
// @int w
// @int h
// @tab[opt={}] opts
// @string opts.blend
// @see Enums
ENTRY(circle){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Colour col = lil_getColour(L, 2, NULL);
	const int ox  = luaL_optinteger(L, 3, img->w / 2);
	const int oy  = luaL_optinteger(L, 4, img->h / 2);
	const int r  = luaL_optinteger(L, 5, img->w < img->h ? img->w / 2 : img->h / 2);
	const enum lil_BlendMode bm = lil_getOptBlendMode(L, 7);
	#pragma omp parallel for
	for(int px = -r; px < r; px++){
		float fheight =  sqrt(r * r - px * px);
		int height = fheight;
		int x = px + ox;
		if(x < 0 || x >= img->w)
			continue;
		for(int py = -height; py < height; py++){
			int y = py + oy;
			if(y < 0 || y >= img->h)
				continue;
			assert(x >= 0 && x < img->w && y >= 0 && y < img->h);
			size_t o = XY(img, x, y);
			lil_composite(bm, &img->d[o], &col);
		}
	}
	lua_pushvalue(L, 1);
	return 1;
}

#ifdef LIL_USE_FREETYPE
/// Writes text to the image
// @function Img:text
// @Font font
// @string text
// @int[opt=0] x
// @int[opt=0] y
// @tab[opt={}] opts
// @string opts.blend
// @see Enums
ENTRY(text){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Font* font = lil_getFont(L, 2);
	const char* text = luaL_checkstring(L, 3);
	size_t len;
	wchar_t* wtext = lil_utf8ToWchar((const unsigned char*)text, strlen(text), &len);
	const int ox = luaL_optinteger(L, 4, 0);
	const int oy = luaL_optinteger(L, 5, 0);
	const enum lil_BlendMode bm = lil_getOptBlendMode(L, 6);
	lil_writeTextToImage(font, wtext, len, 0, 0, img, bm, ox, oy);
	lua_pushvalue(L, 1);
	free(wtext);
	return 1;
}
#endif

static int polyEdgeSorter(const void* a, const void* b){
	return *(lil_Number*)a - *(lil_Number*)b;
}
static void drawPolygon(lua_State* L, const lil_Image* img, lil_Colour col, lil_Mat points, enum lil_BlendMode bm){
	#define X 0
	#define Y 1
	#define SS 8
	int maxx = INT_MIN, maxy = INT_MIN, minx = INT_MAX, miny = INT_MAX;
	for(int p = 0; p < points.h; p++){
		lil_Number x = points.d[p*2+X], y = points.d[p*2+Y];
		if(isnan(x) || isnan(y) || isinf(x) || isinf(y))
			luaL_error(L, "Point has NAN or INF: {%f, %f}", x, y);
		maxx = max(maxx, x);
		maxy = max(maxy, y);
		minx = min(minx, x);
		miny = min(miny, y);
	}
	maxx = min(maxx + 1, img->w);
	maxy = min(maxy + 1, img->h);
	minx = max(minx, 0);
	miny = max(miny, 0);
	if(maxx < minx || maxy < miny || !(maxx | minx | maxy | miny))
		return;
	const size_t roww = maxx - minx;
	#pragma omp parallel
	{
		lil_Number* edges = malloc(sizeof(*edges) * (points.h-1));
		lil_Number* row = malloc(sizeof(*row) * roww);
		lil_Colour lcol = col;
		if(edges && row){
			#pragma omp for
			for(int py = miny; py < maxy; py++){
				#ifdef __STDC_IEC_559__
				memset(row, 0, sizeof(*row) * roww);
				#else
				for(int x = 0; x < roww; x++)
					row[x] = 0;
				#endif
				for(int sy = 0; sy < SS; sy++){
					const lil_Number y = py + (sy * (1./SS) * 0.5);
					int ne = 0;
					for(int p = 0; p < points.h-1; p++){
						lil_Number* p1 = &points.d[p*2];
						lil_Number* p2 = p1 + 2;
						if((p1[Y] >= y && p2[Y] < y) || (p1[Y] < y && p2[Y] >= y))
							edges[ne++] = p1[X] + (y - p1[Y]) / (p1[Y] - p2[Y]) * (p1[X] - p2[X]);
					}
					qsort(edges, ne, sizeof(*edges), polyEdgeSorter);
					if(ne){
						bool t = false;
						int e = 0;
						for(int x = minx; x < maxx; x++){
							lil_Number f = 1;
							while(x >= edges[e]){
								t = !t;
								f = 1 - (edges[e] - (int)edges[e]);
								e++;
								if(!t)
									row[x - minx] += (1 - f);
								if(e == ne)
									goto nextScanLine;
							}
							if(t)
								row[x - minx] += f;
						}
					}
					nextScanLine: (void)0;
				}
				for(int x = 0; x < roww; x++){
					if(row[x] > 0){
						lcol.a = col.a * (min(row[x], SS) * (1./SS));
						lil_composite(bm, &img->d[XY(img, x + minx, py)], &lcol);
					}
				}
			}
		}
		free(edges);
		free(row);
	}
	#undef SS
}
/// Polygon
// @function Img:poly
// @colour colour
// @tab points {{X,Y}...} Vertex list
// @tab[opt={}] opts
// @string opts.blend
// @see Enums
ENTRY(poly){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Colour col = lil_getColour(L, 2, NULL);
	lil_Mat points = lil_mat_get(L, 3);
	if(points.w != 2){
		lil_mat_free(&points);
		luaL_argerror(L, 3, "Invalid vertex list");
	}
	if(lil_mat_resize(&points, 2, points.h+1)){
		lil_mat_free(&points);
		return 0;
	}
	points.d[(points.h-1)*2+X] = points.d[X];
	points.d[(points.h-1)*2+Y] = points.d[Y];
	const enum lil_BlendMode bm = lil_getOptBlendMode(L, 4);
	drawPolygon(L, img, col, points, bm);
	lil_mat_free(&points);
	lua_pushvalue(L, 1);
	return 1;
}

///
// @function Img:line
// @colour colour
// @float x1
// @float y1
// @float x2
// @float y2
// @tab[opt={}] opts
// @float[opt=1] opts.width
// @string opts.blend
// @see Enums
ENTRY(line){
	const lil_Image* img = lil_getImage(L, 1);
	const lil_Colour col = lil_getColour(L, 2, NULL);
	const lil_Number x1 = luaL_checknumber(L, 3);
	const lil_Number y1 = luaL_checknumber(L, 4);
	const lil_Number x2 = luaL_checknumber(L, 5);
	const lil_Number y2 = luaL_checknumber(L, 6);
	const lil_Number w  = lil_getOptFloat(L, 7, "width", 1) / 2.;
	const enum lil_BlendMode bm = lil_getOptBlendMode(L, 7);
	lil_Mat points = { 2, 5 }; lil_mat_init(L, &points);
	const lil_Number x = (x1 + x2) / 2.;
	const lil_Number y = (y1 + y2) / 2.;
	const lil_Number l  = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 2.;
	const lil_Number a  = atan2(y1 - y2, x1 - x2);
	const lil_Number s  = sin(a);
	const lil_Number c  = cos(a);
	points.d[0*2+X] = x + l * c - w * s;
	points.d[0*2+Y] = y + w * c + l * s;
	points.d[1*2+X] = x - l * c - w * s;
	points.d[1*2+Y] = y + w * c - l * s;
	points.d[2*2+X] = x - l * c + w * s;
	points.d[2*2+Y] = y - w * c - l * s;
	points.d[3*2+X] = x + l * c + w * s;
	points.d[3*2+Y] = y - w * c + l * s;
	points.d[4*2+X] = points.d[0*2+X];
	points.d[4*2+Y] = points.d[0*2+Y];
	drawPolygon(L, img, col, points, bm);
	lil_mat_free(&points);
	lua_pushvalue(L, 1);
	return 1;
	#undef X
	#undef Y
}

#undef ENTRY
#define ENTRY(id) { #id, LUAFUNCD(imgDraw_ ## id) }

LUAREG(imgDrawLib) = {
	ENTRY(fill),
	ENTRY(rect),
	ENTRY(circle),
	ENTRY(text),
	ENTRY(poly),
	ENTRY(line),
	{ NULL, NULL }
};

#undef ENTRY
generated by LDoc 1.4.6 Last updated 2023-04-13 13:58:34