comp.c

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

#include "comp.h"

// https://docs.gimp.org/en/gimp-concepts-layer-modes.html
// https://love2d.org/wiki/BlendMode_Formulas
__attribute__((hot)) void lil_composite(const enum lil_BlendMode cm, lil_Colour* b, const lil_Colour* t){
	switch(cm){
		case LIL_BM(ALPHA):
			for(int i = 0; i < 3; i++)
				b->arr[i] = ((b->arr[i] * (255 - t->a)) + t->arr[i] * (t->a)) / 255;
			b->a = min(b->a + t->a, 255);
			break;
		case LIL_BM(REPLACE):
			b->c = t->c;
			break;

		case LIL_BM(ADD):
			for(int i = 0; i < 3; i++)
				b->arr[i] = min((b->arr[i] + t->arr[i]), 255);
			break;
		case LIL_BM(MULTIPLY):
			for(int i = 0; i < 4; i++)
				b->arr[i] = (b->arr[i] * t->arr[i]) / 255;
			break;
		case LIL_BM(SUBTRACT):
			for(int i = 0; i < 3; i++)
				b->arr[i] = max((b->arr[i] - t->arr[i]), 0);
			break;

		case LIL_BM(LIGHTEN):
			for(int i = 0; i < 3; i++)
				b->arr[i] = max((b->arr[i] * (255 - t->a) + t->arr[i] * t->a) / 255, b->arr[i]);
			break;
		case LIL_BM(DARKEN):
			for(int i = 0; i < 3; i++)
				b->arr[i] = min((b->arr[i] * (255 - t->a) + t->arr[i] * t->a) / 255, b->arr[i]);
			break;
		case LIL_BM(AVERAGE):
			for(int i = 0; i < 4; i++)
				b->arr[i] = (b->arr[i] + t->arr[i]) / 2;
			break;

		case LIL_BM(SCREEN):
			for(int i = 0; i < 3; i++)
				b->arr[i] = (b->arr[i] * (255 - t->arr[i]) + (t->arr[i] * t->arr[i])) / 255;
			break;
		case LIL_BM(DISSOLVE):
			if((rand() % 255) < t->a)
				b->c = t->c;
			break;
		case LIL_BM(DIFFERENCE):
			for(int i = 0; i < 3; i++)
				b->arr[i] = abs((signed)t->arr[i] - (signed)b->arr[i]);
			break;

		case LIL_BM(XOR):
			b->rgb ^= t->rgb;
			break;
		case LIL_BM(AND):
			b->rgb &= t->rgb;
			break;
		case LIL_BM(OR):
			b->rgb |= t->rgb;
			break;
	}
}

/// Not an actual table in the library, just documenting them
// @string BlendMode
//
// alpha replace
//
// add multiply subtract
//
// lighten darken
//
// screen dissolve difference
//
// xor and or
//
// @string SampleMode
//
// nearest bilinear box
//
// By default upscaling uses bilinear and downscaling uses box. It can be passed as a string or table like { upMode, downMode }
//
// @table Enums
const char* const lil_compMethodList[] = {
	"alpha", "replace",
	"add", "multiply", "subtract",
	"lighten", "darken", "average",
	"screen", "dissolve", "difference",
	"xor", "and", "or",
	NULL,
};
const char* const lil_sampleMethodList[] = {
	"bilinear", "box", "nearest",
	NULL,
};
const char* const lil_sampleMethodBoundsList[] = {
	"transparent", "black", "white",
	"mirror", "edge",
	NULL,
};

#define TRANS ((lil_Colour){0})

// https://pippin.gimp.org/image-processing/chap_resampling.html
__attribute__((hot)) lil_Colour lil_sampleImage(const enum lil_SampleMode sm, const lil_Image* img, const lil_Number x, const lil_Number y, const lil_Number sx, const lil_Number sy){
	lil_Number x1, y1, x2, y2;
	lil_Colour a, b, c, d;
	switch(sm & 0xff){
		case LIL_SMBILINEAR: (void)0;
			x1 = floor(x);
			y1 = floor(y);
			x2 = x1 + 1.;
			y2 = y1 + 1.;
			const lil_Number xd = x - x1;
			const lil_Number yd = y - y1;
			const lil_Number xdd = 1. - xd;
			const lil_Number ydd = 1. - yd;
			a = isWithinBounds(img, floor(x1), floor(y1)) ? img->d[(int)XY(img, x1, y1)] : TRANS;
			b = isWithinBounds(img, floor(x2), floor(y1)) ? img->d[(int)XY(img, x2, y1)] : TRANS;
			c = isWithinBounds(img, floor(x1), floor(y2)) ? img->d[(int)XY(img, x1, y2)] : TRANS;
			d = isWithinBounds(img, floor(x2), floor(y2)) ? img->d[(int)XY(img, x2, y2)] : TRANS;
			// TODO improve alpha handling
			for(int i = 0; i < 4; i++){
				a.arr[i] = (
					(lil_Number)a.arr[i] * xdd * ydd +
					(lil_Number)b.arr[i] * xd  * ydd +
					(lil_Number)c.arr[i] * xdd * yd  +
					(lil_Number)d.arr[i] * xd  * yd
				);
			}
			return a;

		case LIL_SMBOX: (void)0;
			const unsigned bw = ceil(sx/1.);
			const unsigned bh = ceil(sy/1.);
			x1 = x;
			y1 = y;
			lil_Number rgba[4] = { 0, 0, 0, 0 };
			for(unsigned y = 0; y < bh; y++){
				for(unsigned x = 0; x < bw; x++){
					if(!isWithinBounds(img, x + x1, y + y1))
						continue;
					size_t o = XY(img, x + x1, y + y1);
					for(unsigned i = 0; i < 4; i++)
						rgba[i] += img->d[o].arr[i];
				}
			}
			for(unsigned i = 0; i < 4; i++)
				a.arr[i] = rgba[i] / (bw * bh);
			return a;

		case LIL_SMNEAREST:
		default:
			return isWithinBounds(img, (int)x, (int)y) ? img->d[XY(img, (int)x, (int)y)] : TRANS;
	}
}
generated by LDoc 1.4.6 Last updated 2023-04-13 13:58:34