imgFilter.c
#include <math.h>
#include "inc.h"
#define ENTRY(id) static LUAFUNC(imgFilter_ ## id)
ENTRY(invert){
const lil_Image* img = lil_getImage(L, 1);
lil_Colour b = { .c = 0x00ffffff };
int typ = lua_type(L, 2);
uintmax_t flags;
switch(typ){
case LUA_TNONE:
break;
case LUA_TNUMBER:
b.c = lua_tointeger(L, 2);
break;
case LUA_TSTRING:
flags = lil_getStringFlags(L, 2, "rgba", "rgb");
b.r = isByte1(flags, 0) * 0xff;
b.g = isByte1(flags, 1) * 0xff;
b.b = isByte1(flags, 2) * 0xff;
b.a = isByte1(flags, 3) * 0xff;
break;
default:
luaL_argerror(L, 2, "Expected nothing, number, or string");
break;
}
#if INTPTR_MAX == INT64_MAX
uint64_t cc = b.c | (uint64_t)b.c << 32;
#pragma omp parallel for
for(size_t o = 0; o < (img->w * img->h) / 2; o++)
((uint64_t*)img->d)[o] ^= cc;
if(img->w & img->h & 1)
img->d[(img->w * img->h) - 1].c ^= b.c;
#else
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++)
img->d[o].c ^= b.c;
#endif
lua_pushvalue(L, 1);
return 1;
}
ENTRY(grey){
const lil_Image* img = lil_getImage(L, 1);
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++)
img->d[o].r = img->d[o].g = img->d[o].b = (img->d[o].r + img->d[o].g + img->d[o].b) / 3;
lua_pushvalue(L, 1);
return 1;
}
ENTRY(bw){
const lil_Image* img = lil_getImage(L, 1);
const unsigned char threshold = clamp1(1.0 - luaL_optnumber(L, 2, 0.5)) * 0xff;
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++) img->d[o].r = img->d[o].g = img->d[o].b = (((img->d[o].r + img->d[o].g + img->d[o].b) / 3) >= threshold) * 0xff;
lua_pushvalue(L, 1);
return 1;
}
ENTRY(contrast){
const lil_Image* img = lil_getImage(L, 1);
const lil_Number c = clamp(luaL_checknumber(L, 2), -1, 1) * 255;
const lil_Number f = (259. * (c + 255.)) / (255. * (259. - c));
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++){
for(int i = 0; i < 3; i++)
img->d[o].arr[i] = clamp255(f * (img->d[o].arr[i] - 128) + 128);
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(brightness){
const lil_Image* img = lil_getImage(L, 1);
const lil_Number b = clamp(luaL_checknumber(L, 2), -1, 1) * 255;
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++){
for(int i = 0; i < 3; i ++)
img->d[o].arr[i] = clamp255(img->d[o].arr[i] + b);
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(gamma){
const lil_Image* img = lil_getImage(L, 1);
const lil_Number v = clamp(luaL_checknumber(L, 2), 0, 100);
const lil_Number g = 1. / v;
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++){
for(int i = 0; i < 3; i ++)
img->d[o].arr[i] = clamp255(pow(((lil_Number)img->d[o].arr[i] / 255.), g) * 255);
}
lua_pushvalue(L, 1);
return 1;
}
static int doMap(lua_State* L, const lil_Image* img, size_t start, size_t end){
for(int y = start; y < end; y++){
for(int x = 0; x < img->w; x++){
lil_Number fc[4];
const size_t o = XY(img, x, y);
lua_pushvalue(L, -1);
lil_Colour* col = &img->d[o];
#pragma omp simd
for(int i = 0; i < 4; i++)
fc[i] = (lil_Number)col->arr[i] / (lil_Number)0xff;
lua_pushnumber(L, fc[0]);
lua_pushnumber(L, fc[1]);
lua_pushnumber(L, fc[2]);
lua_pushnumber(L, fc[3]);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
if(lua_pcall(L, 6, 4, 0))
return 1;
fc[0] = luaL_checknumber(L, -4);
fc[1] = luaL_checknumber(L, -3);
fc[2] = luaL_checknumber(L, -2);
fc[3] = luaL_checknumber(L, -1);
#pragma omp simd
for(int i = 0; i < 4; i++)
col->arr[i] = clamp1(fc[i]) * (lil_Number)0xff;
lua_pop(L, 4);
}
}
return 0;
}
ENTRY(map){
const lil_Image* img = lil_getImage(L, 1);
switch(lua_type(L, 2)){
case LUA_TFUNCTION:
if(doMap(L, img, 0, img->h))
lua_error(L);
break;
case LUA_TSTRING: (void)0;
const char* str = lua_tostring(L, 2);
#ifdef _OPENMP
volatile bool err = false; volatile char* errStr = NULL;
#pragma omp parallel
{
size_t tnum = omp_get_thread_num();
size_t numt = omp_get_num_threads();
size_t start = (img->h / numt) * tnum;
size_t end = start + (img->h / numt);
if(tnum == numt - 1) end += img->h % numt;
lua_State* l = luaL_newstate();
luaL_openlibs(l);
if(luaL_dostring(l, str)){
#pragma omp critical
if(!err){
const char* es = lua_tostring(l, -1);
errStr = malloc(strlen(es) + 1);
strcpy((char*)errStr, es);
err = true;
}
lua_close(l);
} else {
if(doMap(l, img, start, end)){
#pragma omp critical
if(!err){
const char* es = lua_tostring(l, -1);
errStr = malloc(strlen(es) + 1);
strcpy((char*)errStr, es);
err = true;
}
}
lua_close(l);
}
}
if(err){
lua_pushstring(L, (char*)errStr);
free((char*)errStr);
lua_error(L);
}
#else
if(luaL_dostring(L, str))
lua_error(L);
if(doMap(L, img, 0, img->h))
lua_error(L);
#endif
break;
default:
luaL_argerror(L, 2, "Expected function or string");
break;
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(flip){
const lil_Image* img = lil_getImage(L, 1);
uintmax_t flags = lil_getStringFlags(L, 2, "vh", "v");
if(isByte1(flags, 1)){
const int xinv = img->w-1;
#pragma omp parallel for
for(int y = 0; y < img->h; y++){
for(int x = 0; x < img->w / 2; x++){
const size_t o = XY(img, x, y);
const size_t j = XY(img, xinv - x, y);
lil_Colour temp = img->d[o];
img->d[o] = img->d[j];
img->d[j] = temp;
}
}
}
if(isByte1(flags, 0)){
const int yinv = img->h-1;
#pragma omp parallel for
for(int y = 0; y < img->h / 2; y++){
for(int x = 0; x < img->w; x++){
const size_t o = XY(img, x, y);
const size_t j = XY(img, x, yinv - y);
lil_Colour temp = img->d[o];
img->d[o] = img->d[j];
img->d[j] = temp;
}
}
}
lua_pushvalue(L, 1);
return 1;
}
static void applyKernel(const lil_Number* kernel, const int kw, const int kh, const lil_Number bias, const bool alpha, const lil_Image* img, lil_Image* imgNew){
#pragma omp parallel for
for(int py = 0; py < img->h; py++){
for(int px = 0; px < img->w; px++){
lua_Number sum[4] = { 0, 0, 0, 0 };
for(int ky = 0; ky < kh; ky++){
const int y = clamp(py + (ky-(kh/2)), 0, img->h-1);
for(int kx = 0; kx < kw; kx++){
const lua_Number k = kernel[(ky*kw)+kx];
const int x = clamp(px + (kx-(kw/2)), 0, img->w-1);
const size_t o = XY(img, x, y);
for(int i = 0; i < 4; i++)
sum[i] += img->d[o].arr[i] * k;
}
}
const size_t o = XY(img, px, py);
for(int i = 0; i < 3; i++)
imgNew->d[o].arr[i] = clamp255(sum[i] + bias);
if(alpha)
imgNew->d[o].a = clamp255(sum[3] + bias);
else
imgNew->d[o].a = img->d[o].a;
}
}
}
ENTRY(kernel){
lil_Image** imgp = lil_getImagePointer(L, 1);
const lil_Image* img = *imgp;
lil_Mat kernel = lil_mat_get(L, 2);
lil_Number divisor = lil_getOptFloat(L, 3, "divisor", NAN);
luaL_argcheck(L, divisor != 0, 3, "Divisor can't be zero");
const lil_Number bias = lil_getOptFloat(L, 3, "bias", 0) * 255;
const bool alpha = lil_getOptBool(L, 3, "alpha", false);
if(isnan(divisor)){
divisor = 0;
for(int i = 0; i < kernel.w * kernel.h; i++)
divisor += kernel.d[i];
}
for(int i = 0; i < kernel.w * kernel.h; i++)
kernel.d[i] /= divisor;
lil_Image* imgNew = lil_newImage(L, img->w, img->h);
if(!imgNew)
return 0;
applyKernel(kernel.d, kernel.w, kernel.h, bias, alpha, img, imgNew);
*imgp = imgNew;
lil_mat_free(&kernel);
lil_freeImage(img);
lua_pushvalue(L, 1);
return 1;
}
static int deltaColour(lil_Colour a, lil_Colour b){
int s = 0;
for(int i = 0; i < 3; i++)
s += abs((a.arr[i] * a.a) - (b.arr[i] * b.a));
return s;
}
ENTRY(blur){
lil_Image** imgp = lil_getImagePointer(L, 1);
const lil_Image* img = *imgp;
int kw = luaL_checknumber(L, 2);
int kh = luaL_optnumber(L, 3, kw);
kw++;
kh++;
luaL_argcheck(L, kw >= 2, 2, "Invalid");
luaL_argcheck(L, kh >= 2, 3, "Invalid");
const lua_Number mulf = 1. / (kw * kh);
lil_Image* imgNew = lil_newImage(L, img->w, img->h);
if(!imgNew)
return 0;
const char* const methods[] = { "box", "snn", "gaussian", NULL };
const int method = lil_getOptEnum(L, 4, "method", methods, "box");
const lil_Number sigma = lil_getOptFloat(L, 4, "sigma", 1.);
lil_Number* kernel = NULL;
switch(method){
case 0:
#pragma omp parallel for
for(int py = 0; py < img->h; py++){
for(int px = 0; px < img->w; px++){
int sum[4] = { 0, 0, 0, 0 };
for(int ky = 0; ky < kh; ky++){
const int y = clamp(py + (ky-(kh/2)), 0, img->h-1);
for(int kx = 0; kx < kw; kx++){
const int x = clamp(px + (kx-(kw/2)), 0, img->w-1);
for(int i = 0; i < 4; i++)
sum[i] += img->d[XY(img, x, y)].arr[i];
}
}
for(int i = 0; i < 4; i++)
imgNew->d[XY(img, px, py)].arr[i] = sum[i] * mulf;
}
}
break;
case 1:
#pragma omp parallel for
for(int py = 0; py < img->h; py++){
for(int px = 0; px < img->w; px++){
lil_Number sum[4] = { 0, 0, 0, 0 };
const lil_Colour pix = img->d[XY(img, px, py)];
for(int ky = 0; ky < kh; ky++){
const int ya = clamp(py + (ky-(kh/2)), 0, img->h-1);
const int yb = clamp(py - (ky-(kh/2)), 0, img->h-1);
for(int kx = 0; kx < kw; kx++){
const int xa = clamp(px + (kx-(kw/2)), 0, img->w-1);
const int xb = clamp(px - (kx-(kw/2)), 0, img->w-1);
const lil_Colour pixa = img->d[XY(img, xa, ya)];
const lil_Colour pixb = img->d[XY(img, xb, yb)];
if(deltaColour(pix, pixa) < deltaColour(pix, pixb)){
for(int i = 0; i < 4; i++)
sum[i] += pixa.arr[i];
} else {
for(int i = 0; i < 4; i++)
sum[i] += pixb.arr[i];
}
}
}
for(int i = 0; i < 4; i++)
imgNew->d[XY(img, px, py)].arr[i] = sum[i] * mulf;
}
}
break;
case 2:
kernel = malloc(sizeof(lil_Number) * kw * kh);
lil_Number sum = 0;
#define mean (kw/2)
for(int y = 0; y < kh; y++){
for(int x = 0; x < kw; x++){
kernel[(y*kw)+x] = exp(-0.5 * (pow((x-mean)/sigma, 2.0) + pow((y-mean)/sigma,2.0)) ) / (2 * PI * sigma * sigma);
sum += kernel[(y*kw)+x];
}
}
for(int y = 0; y < kh; y++)
for(int x = 0; x < kw; x++)
kernel[(y*kw)+x] /= sum;
applyKernel(kernel, kw, kh, 0, true, img, imgNew);
#undef mean
break;
}
*imgp = imgNew;
lil_freeImage(img);
lua_pushvalue(L, 1);
return 1;
}
static int colourDifference_Lab(lil_Colour c1, lil_Colour c2){
lil_ColourLab Lab1 = lil_rgb2Lab(c1);
lil_ColourLab Lab2 = lil_rgb2Lab(c2);
float a1 = c1.a / 255.f;
float a2 = c2.a / 255.f;
float Ld = Lab1.L*a1 - Lab2.L*a2;
float ad = Lab1.a*a1 - Lab2.a*a2;
float bd = Lab1.b*a1 - Lab2.b*a2;
float Ad = (a1 - a2) * 100.f;
return Ld * Ld + ad * ad + bd * bd + Ad * Ad;
}
static int colourDifference_rgb(lil_Colour c1, lil_Colour c2){
int rd = (c1.r*c1.a >> 8) - (c2.r*c2.a >> 8);
int gd = (c1.g*c1.a >> 8) - (c2.g*c2.a >> 8);
int bd = (c1.b*c1.a >> 8) - (c2.b*c2.a >> 8);
int ba = c1.a - c2.a;
return rd * rd + gd * gd + bd * bd + ba * ba;
}
static lil_Colour findNearestColour(lil_Colour target, int (*colourDifference)(lil_Colour, lil_Colour), const lil_Colour* list, unsigned length){
lil_Colour closest = list[0];
int bestDifference = INT_MAX;
for(int i = 0; i < length; i++){
lil_Colour attempt = list[i];
long difference = colourDifference(attempt, target);
if(difference < bestDifference){
closest = attempt;
bestDifference = difference;
}
}
return closest;
}
static void applyError(lil_Colour* col, int r, int g, int b, int a, int fac){
col->r = clamp255(col->r + (r * fac / 16));
col->g = clamp255(col->g + (g * fac / 16));
col->b = clamp255(col->b + (b * fac / 16));
col->a = clamp255(col->a + (a * fac / 16));
}
static void img_palette(lil_Image* img, const lil_Colour* colours, int numCol){
#pragma omp parallel for
for(int y = 0; y < img->h; y++){
for(int x = 0; x < img->w; x++){
img->d[XY(img, x, y)] = findNearestColour(img->d[XY(img, x, y)], colourDifference_rgb, colours, numCol);
}
}
}
static void img_palette_fs(lil_Image* img, int (*colourDifference)(lil_Colour, lil_Colour), const lil_Colour* colours, int numCol){
for(int y = 0; y < img->h; y++){
for(int x = y%2 ? 0 : img->w-1; x < img->w && x >= 0; x += (((y%2) * 2) - 1)){
lil_Colour orig = img->d[XY(img, x, y)];
lil_Colour new = findNearestColour(orig, colourDifference, colours, numCol);
int r = (orig.r - new.r);
int g = (orig.g - new.g);
int b = (orig.b - new.b);
int a = (orig.a - new.a);
if(y%2 && x + 1 < img->w)
applyError(&(img->d[XY(img, x+1, y)]), r, g, b, a, 7);
else if(!(y%2) && x - 1 >= 0)
applyError(&(img->d[XY(img, x-1, y)]), r, g, b, a, 7);
if(y+1 < img->h-1){
applyError(&(img->d[XY(img, x, y+1)]), r, g, b, a, 5);
if(x-1 >= 0)
applyError(&(img->d[XY(img, x-1, y+1)]), r, g, b, a, 3);
if(x+1 < img->w-1)
applyError(&(img->d[XY(img, x+1, y+1)]), r, g, b, a, 1);
}
img->d[XY(img, x, y)] = new;
}
}
}
ENTRY(palette){
lil_Image* img = lil_getImage(L, 1);
lil_Colour* colours = NULL;
luaL_checktype(L, 2, LUA_TTABLE);
const int numCol = lua_objlen(L, 2);
luaL_argcheck(L, numCol > 0, 2, "Need colours");
int top = lua_gettop(L);
for(int i = 0; i < numCol; i++){ lua_pushinteger(L, i+1);
lua_gettable(L, 2);
lil_getColour(L, top + i + 1, NULL);
}
const char* const methods[] = { "none", "fs", "fslab", NULL };
const int dither = lil_getOptEnum(L, 3, "dither", methods, "none");
colours = malloc(sizeof(lil_Colour) * numCol);
for(int i = 0; i < numCol; i++)
colours[i] = lil_getColour(L, top + i + 1, NULL);
switch(dither){
case 0:
img_palette(img, colours, numCol);
break;
case 1:
img_palette_fs(img, colourDifference_rgb, colours, numCol);
break;
case 2:
img_palette_fs(img, colourDifference_Lab, colours, numCol);
break;
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(trim){
lil_Image** imgp = lil_getImagePointer(L, 1);
const lil_Image* img = *imgp;
lil_Colour defCol = { .c = 0 };
float fthres = clamp1(luaL_optnumber(L, 2, 0));
if(fthres == 1.f)
goto end;
int thres = fthres * colourDifference_rgb(defCol, (lil_Colour){ .c = 0xffffffff });
lil_Colour col = lil_getColour(L, 3, &defCol);
int minx = img->w-1, miny = -1, maxx = -1, maxy = -1;
for(int y = 0; y < img->h; y++){
for(int x = 0; x < img->w; x++){
lil_Colour p = img->d[XY(img, x, y)];
long diff = colourDifference_rgb(p, col);
if(diff > thres){
maxy = y;
if(miny < 0)
miny = y;
if(minx > x)
minx = x;
if(maxx < x)
maxx = x;
}
}
}
int w = (maxx - minx) + 1;
int h = (maxy - miny) + 1;
if(w <= 0 || h <= 0){
end:
lua_pushvalue(L, 1);
return 1;
}
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, minx, miny + 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);
lua_pushinteger(L, minx);
lua_pushinteger(L, miny);
return 3;
}
ENTRY(expand){
lil_Image** imgp = lil_getImagePointer(L, 1);
const lil_Image* img = *imgp;
int x = 0, y = 0, b, c, d, optTop = 0;
int a = luaL_checkinteger(L, 2);
luaL_argcheck(L, a >= 0, 2, "Need to expand by 0 or more");
int w = img->w, h = img->h;
if(lua_type(L, 3) == LUA_TNUMBER){
b = lua_tointeger(L, 3);
luaL_argcheck(L, b >= 0, 3, "Need to expand by 0 or more");
if(lua_type(L, 4) == LUA_TNUMBER){
if(lua_type(L, 5) == LUA_TNUMBER){
c = lua_tointeger(L, 4);
luaL_argcheck(L, c >= 0, 4, "Need to expand by 0 or more");
d = lua_tointeger(L, 5);
luaL_argcheck(L, d >= 0, 5, "Need to expand by 0 or more");
x = d;
y = a;
h += a;
w += b;
h += c;
w += d;
optTop = 6;
} else {
luaL_error(L, "Need 1, 2, or 4 numbers");
}
} else {
x = a;
y = b;
w += a * 2;
h += b * 2;
optTop = 4;
}
} else{
x = y = a;
w += a * 2;
h += a * 2;
optTop = 3;
}
lil_Colour colDef = { .c = 0 };
lil_Colour col = lil_getColour(L, optTop, &colDef);
lil_Image* imgNew = lil_newImage(L, w, h);
if(!imgNew)
return 0;
lil_fillImage(imgNew, col);
for(int py = 0; py < img->h; py++){
const size_t os = XY(img, 0, 0 + py);
const size_t od = XY(imgNew, x, y + py);
memcpy(&imgNew->d[od], &img->d[os], img->w * 4);
}
*imgp = imgNew;
lil_freeImage(img);
lua_pushvalue(L, 1);
lil_setImageSize(L, imgNew);
return 1;
return 1;
}
ENTRY(hue){
lil_Image* img = lil_getImage(L, 1);
lil_Number h = fmod(luaL_checknumber(L, 2), 360);
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++){
lil_ColourHsv hsv = lil_rgb2hsv(img->d[o]);
hsv.h = fmod(hsv.h + h, 360);
img->d[o].rgb = lil_hsv2rgb(hsv).rgb;
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(saturation){
lil_Image* img = lil_getImage(L, 1);
lil_Number s = luaL_checknumber(L, 2);
#pragma omp parallel for
for(size_t o = 0; o < img->w * img->h; o++){
lil_Colour c = img->d[o];
lil_Number p = sqrt(
(((lil_Number)c.r * (lil_Number)c.r) * (lil_Number).299) +
(((lil_Number)c.g * (lil_Number)c.g) * (lil_Number).587) +
(((lil_Number)c.b * (lil_Number)c.b) * (lil_Number).114)
);
img->d[o].r = clamp255(p + (c.r - p) * s);
img->d[o].g = clamp255(p + (c.g - p) * s);
img->d[o].b = clamp255(p + (c.b - p) * s);
}
lua_pushvalue(L, 1);
return 1;
}
ENTRY(affine){
lil_Image** imgp = lil_getImagePointer(L, 1);
const lil_Image* img = *imgp;
lil_Mat omat = lil_mat_get(L, 2);
luaL_argcheck(L, omat.w == 3 && omat.h == 2, 2, "Need a 3x2 matrix");
const enum lil_SampleMode sm = lil_getOptSampleMode(L, 3, 1, 1);
lil_Mat mat = { 3, 3 }; lil_mat_init(L, &mat);
memcpy(mat.d, omat.d, sizeof(*mat.d) * 3 * 3);
lil_Image* imgNew = lil_newImage(L, img->w, img->h);
if(!imgNew) return 0;
#pragma omp parallel
{
lil_Mat res = { 3, 3 }; lil_mat_init(L, &res);
lil_Mat cor = { 1, 3 }; lil_mat_init(L, &cor);
cor.d[2] = 1;
#pragma omp for
for(int y = 0; y < imgNew->h; y++){
for(int x = 0; x < imgNew->w; x++){
lil_Number xx = x;
lil_Number yy = y;
cor.d[0] = (xx / (lil_Number)imgNew->w * 2.) - 1.;
cor.d[1] = (yy / (lil_Number)imgNew->h * 2.) - 1.;
lil_mat_dot(&mat, &cor, &res);
lil_Number px = ((res.d[0] + 1.) * 0.5) * (lil_Number)img->w;
lil_Number py = ((res.d[3] + 1.) * 0.5) * (lil_Number)img->h;
if(px >= -1 && px < img->w && py >= -1 && py < img->h)
imgNew->d[XY(imgNew, x, y)] = lil_sampleImage(sm, img, px, py, 1, 1);
}
}
lil_mat_free(&res); lil_mat_free(&cor);
}
*imgp = imgNew;
lil_mat_free(&omat);
lil_mat_free(&mat);
lil_freeImage(img);
lua_pushvalue(L, 1);
lil_setImageSize(L, imgNew);
return 1;
}
#undef ENTRY
#define ENTRY(id) { #id, LUAFUNCD(imgFilter_ ## id) }
LUAREG(imgFilterLib) = {
ENTRY(invert),
ENTRY(grey),
ENTRY(bw),
ENTRY(contrast),
ENTRY(brightness),
ENTRY(gamma),
ENTRY(map),
ENTRY(flip),
ENTRY(kernel),
ENTRY(blur),
ENTRY(palette),
ENTRY(trim),
ENTRY(expand),
ENTRY(hue),
ENTRY(saturation),
ENTRY(affine),
{ NULL, NULL }
};
#undef ENTRY