#include "module.h" #include "lauxlib.h" #include #include "pixbuf.h" #define PIXBUF_METATABLE "pixbuf.buf" #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif pixbuf *pixbuf_from_lua_arg(lua_State *L, int arg) { return luaL_checkudata(L, arg, PIXBUF_METATABLE); } pixbuf *pixbuf_opt_from_lua_arg(lua_State *L, int arg) { return luaL_testudata(L, arg, PIXBUF_METATABLE); } static ssize_t posrelat(ssize_t pos, size_t len) { /* relative string position: negative means back from end */ if (pos < 0) pos += (ssize_t)len + 1; return MIN(MAX(pos, 1), len); } const size_t pixbuf_channels(pixbuf *p) { return p->nchan; } const size_t pixbuf_size(pixbuf *p) { return p->npix * p->nchan; } /* * Construct a pixbuf newuserdata using C arguments. * * Allocates, so may throw! Leaves new buffer at the top of the Lua stack * and returns a C pointer. */ static pixbuf *pixbuf_new(lua_State *L, size_t leds, size_t chans) { // Allocate memory // A crude hack of an overflow check, but unlikely to be reached in practice if ((leds > 8192) || (chans > 32)) { luaL_error(L, "pixbuf size limits exeeded"); return NULL; // UNREACHED } size_t size = sizeof(pixbuf) + leds * chans; pixbuf *buffer = (pixbuf*)lua_newuserdata(L, size); // Associate its metatable luaL_getmetatable(L, PIXBUF_METATABLE); lua_setmetatable(L, -2); // Save led strip size *(size_t *)&buffer->npix = leds; *(size_t *)&buffer->nchan = chans; memset(buffer->values, 0, leds * chans); return buffer; } // Handle a buffer where we can store led values int pixbuf_new_lua(lua_State *L) { const int leds = luaL_checkint(L, 1); const int chans = luaL_checkint(L, 2); luaL_argcheck(L, leds > 0, 1, "should be a positive integer"); luaL_argcheck(L, chans > 0, 2, "should be a positive integer"); pixbuf_new(L, leds, chans); return 1; } static int pixbuf_concat_lua(lua_State *L) { pixbuf *lhs = pixbuf_from_lua_arg(L, 1); pixbuf *rhs = pixbuf_from_lua_arg(L, 2); luaL_argcheck(L, lhs->nchan == rhs->nchan, 1, "can only concatenate buffers with same channel count"); size_t osize = lhs->npix + rhs->npix; if (lhs->npix > osize) { return luaL_error(L, "size sum overflow"); } pixbuf *buffer = pixbuf_new(L, osize, lhs->nchan); memcpy(buffer->values, lhs->values, pixbuf_size(lhs)); memcpy(buffer->values + pixbuf_size(lhs), rhs->values, pixbuf_size(rhs)); return 1; } static int pixbuf_channels_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); lua_pushinteger(L, buffer->nchan); return 1; } static int pixbuf_dump_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); lua_pushlstring(L, (char*)buffer->values, pixbuf_size(buffer)); return 1; } static int pixbuf_eq_lua(lua_State *L) { bool res; pixbuf *lhs = pixbuf_from_lua_arg(L, 1); pixbuf *rhs = pixbuf_from_lua_arg(L, 2); if (lhs->npix != rhs->npix) { res = false; } else if (lhs->nchan != rhs->nchan) { res = false; } else { res = true; for(size_t i = 0; i < pixbuf_size(lhs); i++) { if(lhs->values[i] != rhs->values[i]) { res = false; break; } } } lua_pushboolean(L, res); return 1; } static int pixbuf_fade_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); const int fade = luaL_checkinteger(L, 2); unsigned direction = luaL_optinteger( L, 3, PIXBUF_FADE_OUT ); luaL_argcheck(L, fade > 0, 2, "fade value should be a strictly positive int"); uint8_t *p = &buffer->values[0]; for (size_t i = 0; i < pixbuf_size(buffer); i++) { if (direction == PIXBUF_FADE_OUT) { *p++ /= fade; } else { // as fade in can result in value overflow, an int is used to perform the check afterwards int val = *p * fade; *p++ = MIN(255, val); } } return 0; } /* Fade an Ixxx-type strip by just manipulating the I bytes */ static int pixbuf_fadeI_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); const int fade = luaL_checkinteger(L, 2); unsigned direction = luaL_optinteger( L, 3, PIXBUF_FADE_OUT ); luaL_argcheck(L, fade > 0, 2, "fade value should be a strictly positive int"); uint8_t *p = &buffer->values[0]; for (size_t i = 0; i < buffer->npix; i++, p+=buffer->nchan) { if (direction == PIXBUF_FADE_OUT) { *p /= fade; } else { int val = *p * fade; *p++ = MIN(255, val); } } return 0; } static int pixbuf_fill_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); if (buffer->npix == 0) { goto out; } if (lua_gettop(L) != (1 + buffer->nchan)) { return luaL_argerror(L, 1, "need as many values as colors per pixel"); } /* Fill the first pixel from the Lua stack */ for (size_t i = 0; i < buffer->nchan; i++) { buffer->values[i] = luaL_checkinteger(L, 2+i); } /* Fill the rest of the pixels from the first */ for (size_t i = 1; i < buffer->npix; i++) { memcpy(&buffer->values[i * buffer->nchan], buffer->values, buffer->nchan); } out: lua_settop(L, 1); return 1; } static int pixbuf_get_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); const int led = luaL_checkinteger(L, 2) - 1; size_t channels = buffer->nchan; luaL_argcheck(L, led >= 0 && led < buffer->npix, 2, "index out of range"); uint8_t tmp[channels]; memcpy(tmp, &buffer->values[channels*led], channels); for (size_t i = 0; i < channels; i++) { lua_pushinteger(L, tmp[i]); } return channels; } /* :map(f, buf1, ilo, ihi, [buf2, ilo2]) */ static int pixbuf_map_lua(lua_State *L) { pixbuf *outbuf = pixbuf_from_lua_arg(L, 1); /* f at index 2 */ pixbuf *buffer1 = pixbuf_opt_from_lua_arg(L, 3); if (!buffer1) buffer1 = outbuf; const int ilo = posrelat(luaL_optinteger(L, 4, 1), buffer1->npix) - 1; const int ihi = posrelat(luaL_optinteger(L, 5, buffer1->npix), buffer1->npix) - 1; luaL_argcheck(L, ihi > ilo, 3, "Buffer limits out of order"); size_t npix = ihi - ilo + 1; luaL_argcheck(L, npix == outbuf->npix, 1, "Output buffer wrong size"); pixbuf *buffer2 = pixbuf_opt_from_lua_arg(L, 6); const int ilo2 = buffer2 ? posrelat(luaL_optinteger(L, 7, 1), buffer2->npix) - 1 : 0; if (buffer2) { luaL_argcheck(L, ilo2 + npix <= buffer2->npix, 6, "Second buffer too short"); } for (size_t p = 0; p < npix; p++) { lua_pushvalue(L, 2); for (size_t c = 0; c < buffer1->nchan; c++) { lua_pushinteger(L, buffer1->values[(ilo + p) * buffer1->nchan + c]); } if (buffer2) { for (size_t c = 0; c < buffer2->nchan; c++) { lua_pushinteger(L, buffer2->values[(ilo2 + p) * buffer2->nchan + c]); } } lua_call(L, buffer1->nchan + (buffer2 ? buffer2->nchan : 0), outbuf->nchan); for (size_t c = 0; c < outbuf->nchan; c++) { outbuf->values[(p + 1) * outbuf->nchan - c - 1] = luaL_checkinteger(L, -1); lua_pop(L, 1); } } lua_settop(L, 1); return 1; } struct mix_source { int factor; const uint8_t *values; }; static uint32_t pixbuf_mix_clamp(int32_t v) { if (v < 0) { return 0; } if (v > 255) { return 255; } return v; } /* This one can sum straightforwardly, channel by channel */ static void pixbuf_mix_raw(pixbuf *out, size_t n_src, struct mix_source* src) { size_t cells = pixbuf_size(out); for (size_t c = 0; c < cells; c++) { int32_t val = 0; for (size_t s = 0; s < n_src; s++) { val += (int32_t)src[s].values[c] * src[s].factor; } val += 128; // rounding instead of floor val /= 256; // do not use implemetation dependant right shift out->values[c] = (uint8_t)pixbuf_mix_clamp(val); } } /* Mix intensity-mediated three-color pixbufs. * * XXX This is untested in real hardware; do they actually behave like this? */ static void pixbuf_mix_i3(pixbuf *out, size_t ibits, size_t n_src, struct mix_source* src) { for(size_t p = 0; p < out->npix; p++) { int32_t sums[3] = { 0, 0, 0 }; for (size_t s = 0; s < n_src; s++) { for (size_t c = 0; c < 3; c++) { sums[c] += (int32_t)src[s].values[4*p+c+1] // color channel * src[s].values[4*p] // global intensity * src[s].factor; // user factor } } uint32_t pmaxc = 0; for (size_t c = 0; c < 3; c++) { pmaxc = sums[c] > pmaxc ? sums[c] : pmaxc; } size_t maxgi; if (pmaxc == 0) { /* Zero value */ memset(&out->values[4*p], 0, 4); return; } else if (pmaxc <= (1 << 16)) { /* Minimum global factor */ maxgi = 1; } else if (pmaxc >= ((1 << ibits) - 1) << 16) { /* Maximum global factor */ maxgi = (1 << ibits) - 1; } else { maxgi = (pmaxc >> 16) + 1; } // printf("mixi3: %x %x %x -> %x, %zx\n", sums[0], sums[1], sums[2], pmaxc, maxgi); out->values[4*p] = maxgi; for (size_t c = 0; c < 3; c++) { out->values[4*p+c+1] = pixbuf_mix_clamp((sums[c] + 256 * maxgi - 127) / (256 * maxgi)); } } } // buffer:mix(factor1, buffer1, ..) // factor is 256 for 100% // uses saturating arithmetic (one buffer at a time) static int pixbuf_mix_core(lua_State *L, size_t ibits) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); pixbuf *src_buffer; int pos = 2; size_t n_sources = (lua_gettop(L) - 1) / 2; struct mix_source sources[n_sources]; if (n_sources == 0) { lua_settop(L, 1); return 1; } for (size_t src = 0; src < n_sources; src++, pos += 2) { int factor = luaL_checkinteger(L, pos); src_buffer = pixbuf_from_lua_arg(L, pos + 1); luaL_argcheck(L, src_buffer->npix == buffer->npix && src_buffer->nchan == buffer->nchan, pos + 1, "buffer not same size or shape"); sources[src].factor = factor; sources[src].values = src_buffer->values; } if (ibits != 0) { luaL_argcheck(L, src_buffer->nchan == 4, 2, "Requires 4 channel pixbuf"); pixbuf_mix_i3(buffer, ibits, n_sources, sources); } else { pixbuf_mix_raw(buffer, n_sources, sources); } lua_settop(L, 1); return 1; } static int pixbuf_mix_lua(lua_State *L) { return pixbuf_mix_core(L, 0); } static int pixbuf_mix4I5_lua(lua_State *L) { return pixbuf_mix_core(L, 5); } // Returns the total of all channels static int pixbuf_power_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); int total = 0; size_t p = 0; for (size_t i = 0; i < buffer->npix; i++) { for (size_t j = 0; j < buffer->nchan; j++, p++) { total += buffer->values[p]; } } lua_pushinteger(L, total); return 1; } // Returns the total of all channels, intensity-style static int pixbuf_powerI_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); int total = 0; size_t p = 0; for (size_t i = 0; i < buffer->npix; i++) { int inten = buffer->values[p++]; for (size_t j = 0; j < buffer->nchan - 1; j++, p++) { total += inten * buffer->values[p]; } } lua_pushinteger(L, total); return 1; } static int pixbuf_replace_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), buffer->npix); size_t channels = buffer->nchan; uint8_t *src; size_t srcLen; if (lua_type(L, 2) == LUA_TSTRING) { size_t length; src = (uint8_t *) lua_tolstring(L, 2, &length); srcLen = length / channels; } else { pixbuf *rhs = pixbuf_from_lua_arg(L, 2); luaL_argcheck(L, rhs->nchan == buffer->nchan, 2, "buffers have different channels"); src = rhs->values; srcLen = rhs->npix; } luaL_argcheck(L, srcLen + start - 1 <= buffer->npix, 2, "does not fit into destination"); memcpy(buffer->values + (start - 1) * channels, src, srcLen * channels); return 0; } static int pixbuf_set_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); const int led = luaL_checkinteger(L, 2) - 1; const size_t channels = buffer->nchan; luaL_argcheck(L, led >= 0 && led < buffer->npix, 2, "index out of range"); int type = lua_type(L, 3); if(type == LUA_TTABLE) { for (size_t i = 0; i < channels; i++) { lua_rawgeti(L, 3, i+1); buffer->values[channels*led+i] = lua_tointeger(L, -1); lua_pop(L, 1); } } else if(type == LUA_TSTRING) { size_t len; const char *buf = lua_tolstring(L, 3, &len); // Overflow check if( channels*led + len > channels*buffer->npix ) { return luaL_error(L, "string size will exceed strip length"); } if ( len % channels != 0 ) { return luaL_error(L, "string does not contain whole LEDs"); } memcpy(&buffer->values[channels*led], buf, len); } else { luaL_argcheck(L, lua_gettop(L) <= 2 + channels, 2 + channels, "extra values given"); for (size_t i = 0; i < channels; i++) { buffer->values[channels*led+i] = luaL_checkinteger(L, 3+i); } } lua_settop(L, 1); return 1; } static void pixbuf_shift_circular(pixbuf *buffer, struct pixbuf_shift_params *sp) { /* Move a buffer of pixels per iteration; loop repeatedly if needed */ uint8_t tmpbuf[32]; uint8_t *v = buffer->values; size_t shiftRemaining = sp->shift; size_t cursor = sp->offset; do { size_t shiftNow = MIN(shiftRemaining, sizeof tmpbuf); if (sp->shiftLeft) { memcpy(tmpbuf, &v[cursor], shiftNow); memmove(&v[cursor], &v[cursor+shiftNow], sp->window - shiftNow); memcpy(&v[cursor+sp->window-shiftNow], tmpbuf, shiftNow); } else { memcpy(tmpbuf, &v[cursor+sp->window-shiftNow], shiftNow); memmove(&v[cursor+shiftNow], &v[cursor], sp->window - shiftNow); memcpy(&v[cursor], tmpbuf, shiftNow); } cursor += shiftNow; shiftRemaining -= shiftNow; } while(shiftRemaining > 0); } static void pixbuf_shift_logical(pixbuf *buffer, struct pixbuf_shift_params *sp) { /* Logical shifts don't require a temporary buffer, so we just move bytes */ uint8_t *v = buffer->values; if (sp->shiftLeft) { memmove(&v[sp->offset], &v[sp->offset+sp->shift], sp->window - sp->shift); bzero(&v[sp->offset+sp->window-sp->shift], sp->shift); } else { memmove(&v[sp->offset+sp->shift], &v[sp->offset], sp->window - sp->shift); bzero(&v[sp->offset], sp->shift); } } void pixbuf_shift(pixbuf *b, struct pixbuf_shift_params *sp) { #if 0 printf("Pixbuf %p shifting %s %s by %zd from %zd with window %zd\n", b, sp->shiftLeft ? "left" : "right", sp->type == PIXBUF_SHIFT_LOGICAL ? "logically" : "circularly", sp->shift, sp->offset, sp->window); #endif switch(sp->type) { case PIXBUF_SHIFT_LOGICAL: return pixbuf_shift_logical(b, sp); case PIXBUF_SHIFT_CIRCULAR: return pixbuf_shift_circular(b, sp); } } int pixbuf_shift_lua(lua_State *L) { struct pixbuf_shift_params sp; pixbuf *buffer = pixbuf_from_lua_arg(L, 1); const int shift_shift = luaL_checkinteger(L, 2) * buffer->nchan; const unsigned shift_type = luaL_optinteger(L, 3, PIXBUF_SHIFT_LOGICAL); const int pos_start = posrelat(luaL_optinteger(L, 4, 1), buffer->npix); const int pos_end = posrelat(luaL_optinteger(L, 5, -1), buffer->npix); if (shift_shift < 0) { sp.shiftLeft = true; sp.shift = -shift_shift; } else { sp.shiftLeft = false; sp.shift = shift_shift; } switch(shift_type) { case PIXBUF_SHIFT_LOGICAL: case PIXBUF_SHIFT_CIRCULAR: sp.type = shift_type; break; default: return luaL_argerror(L, 3, "invalid shift type"); } if (pos_start < 1) { return luaL_argerror(L, 4, "start position must be >= 1"); } if (pos_end < pos_start) { return luaL_argerror(L, 5, "end position must be >= start"); } sp.offset = (pos_start - 1) * buffer->nchan; sp.window = (pos_end - pos_start + 1) * buffer->nchan; if (sp.shift > pixbuf_size(buffer)) { return luaL_argerror(L, 2, "shifting more elements than buffer size"); } if (sp.shift > sp.window) { return luaL_argerror(L, 2, "shifting more than sliced window"); } pixbuf_shift(buffer, &sp); return 0; } /* XXX for backwards-compat with ws2812_effects; deprecated and should be removed */ void pixbuf_prepare_shift(pixbuf *buffer, struct pixbuf_shift_params *sp, int shift, enum pixbuf_shift type, int start, int end) { start = posrelat(start, buffer->npix); end = posrelat(end, buffer->npix); lua_assert((end > start) && (start > 0) && (end < buffer->npix)); sp->type = type; sp->offset = (start - 1) * buffer->nchan; sp->window = (end - start + 1) * buffer->nchan; if (shift < 0) { sp->shiftLeft = true; sp->shift = -shift * buffer->nchan; } else { sp->shiftLeft = false; sp->shift = shift * buffer->nchan; } } static int pixbuf_size_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); lua_pushinteger(L, buffer->npix); return 1; } static int pixbuf_sub_lua(lua_State *L) { pixbuf *lhs = pixbuf_from_lua_arg(L, 1); size_t l = lhs->npix; ssize_t start = posrelat(luaL_checkinteger(L, 2), l); ssize_t end = posrelat(luaL_optinteger(L, 3, -1), l); if (start <= end) { pixbuf *result = pixbuf_new(L, end - start + 1, lhs->nchan); memcpy(result->values, lhs->values + lhs->nchan * (start - 1), lhs->nchan * (end - start + 1)); return 1; } else { pixbuf_new(L, 0, lhs->nchan); return 1; } } static int pixbuf_tostring_lua(lua_State *L) { pixbuf *buffer = pixbuf_from_lua_arg(L, 1); luaL_Buffer result; luaL_buffinit(L, &result); luaL_addchar(&result, '['); int p = 0; for (size_t i = 0; i < buffer->npix; i++) { if (i > 0) { luaL_addchar(&result, ','); } luaL_addchar(&result, '('); for (size_t j = 0; j < buffer->nchan; j++, p++) { if (j > 0) { luaL_addchar(&result, ','); } char numbuf[5]; sprintf(numbuf, "%d", buffer->values[p]); luaL_addstring(&result, numbuf); } luaL_addchar(&result, ')'); } luaL_addchar(&result, ']'); luaL_pushresult(&result); return 1; } LROT_BEGIN(pixbuf_map, NULL, LROT_MASK_INDEX | LROT_MASK_EQ) LROT_TABENTRY ( __index, pixbuf_map ) LROT_FUNCENTRY( __eq, pixbuf_eq_lua ) LROT_FUNCENTRY( __concat, pixbuf_concat_lua ) LROT_FUNCENTRY( __tostring, pixbuf_tostring_lua ) LROT_FUNCENTRY( channels, pixbuf_channels_lua ) LROT_FUNCENTRY( dump, pixbuf_dump_lua ) LROT_FUNCENTRY( fade, pixbuf_fade_lua ) LROT_FUNCENTRY( fadeI, pixbuf_fadeI_lua ) LROT_FUNCENTRY( fill, pixbuf_fill_lua ) LROT_FUNCENTRY( get, pixbuf_get_lua ) LROT_FUNCENTRY( replace, pixbuf_replace_lua ) LROT_FUNCENTRY( map, pixbuf_map_lua ) LROT_FUNCENTRY( mix, pixbuf_mix_lua ) LROT_FUNCENTRY( mix4I5, pixbuf_mix4I5_lua ) LROT_FUNCENTRY( power, pixbuf_power_lua ) LROT_FUNCENTRY( powerI, pixbuf_powerI_lua ) LROT_FUNCENTRY( set, pixbuf_set_lua ) LROT_FUNCENTRY( shift, pixbuf_shift_lua ) LROT_FUNCENTRY( size, pixbuf_size_lua ) LROT_FUNCENTRY( sub, pixbuf_sub_lua ) LROT_END(pixbuf_map, NULL, LROT_MASK_INDEX | LROT_MASK_EQ) LROT_BEGIN(pixbuf, NULL, 0) LROT_NUMENTRY( FADE_IN, PIXBUF_FADE_IN ) LROT_NUMENTRY( FADE_OUT, PIXBUF_FADE_OUT ) LROT_NUMENTRY( SHIFT_CIRCULAR, PIXBUF_SHIFT_CIRCULAR ) LROT_NUMENTRY( SHIFT_LOGICAL, PIXBUF_SHIFT_LOGICAL ) LROT_FUNCENTRY( newBuffer, pixbuf_new_lua ) LROT_END(pixbuf, NULL, 0) int luaopen_pixbuf(lua_State *L) { luaL_rometatable(L, PIXBUF_METATABLE, LROT_TABLEREF(pixbuf_map)); lua_pushrotable(L, LROT_TABLEREF(pixbuf)); return 1; } NODEMCU_MODULE(PIXBUF, "pixbuf", pixbuf, luaopen_pixbuf);