luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

decoder.c (42334B)


      1 #include "decoder.h"
      2 #include "graphics.h"
      3 #include "vesa.h"
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <lauxlib.h>
      7 
      8 /* External graphics state */
      9 extern vesa_state_t vesa_state;
     10 extern uint8_t* vga_memory;
     11 
     12 /* Forward declaration for render target */
     13 typedef struct window_buffer_s window_buffer_t;
     14 
     15 /* Create a new image */
     16 image_t* image_create(uint32_t width, uint32_t height, uint8_t bpp) {
     17     image_t* img = (image_t*)malloc(sizeof(image_t));
     18     if (!img) return NULL;
     19 
     20     img->width = width;
     21     img->height = height;
     22     img->bpp = bpp;
     23     img->data_size = width * height * (bpp / 8);
     24     img->has_alpha = (bpp == 32);
     25 
     26     img->data = (uint8_t*)malloc(img->data_size);
     27     if (!img->data) {
     28         free(img);
     29         return NULL;
     30     }
     31 
     32     memset(img->data, 0, img->data_size);
     33     return img;
     34 }
     35 
     36 /* Destroy an image */
     37 void image_destroy(image_t* img) {
     38     if (!img) return;
     39     if (img->data) free(img->data);
     40     free(img);
     41 }
     42 
     43 /* Get pixel from image */
     44 void image_get_pixel(image_t* img, uint32_t x, uint32_t y, uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) {
     45     if (!img || x >= img->width || y >= img->height) {
     46         *r = *g = *b = *a = 0;
     47         return;
     48     }
     49 
     50     int bytes_per_pixel = img->bpp / 8;
     51     uint8_t* pixel = img->data + (y * img->width + x) * bytes_per_pixel;
     52 
     53     if (img->bpp == 24) {
     54         *r = pixel[0];
     55         *g = pixel[1];
     56         *b = pixel[2];
     57         *a = 255;
     58     } else if (img->bpp == 32) {
     59         *r = pixel[0];
     60         *g = pixel[1];
     61         *b = pixel[2];
     62         *a = pixel[3];
     63     } else if (img->bpp == 8) {
     64         /* Grayscale */
     65         *r = *g = *b = pixel[0];
     66         *a = 255;
     67     }
     68 }
     69 
     70 /* Set pixel in image */
     71 void image_set_pixel(image_t* img, uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
     72     if (!img || x >= img->width || y >= img->height) return;
     73 
     74     int bytes_per_pixel = img->bpp / 8;
     75     uint8_t* pixel = img->data + (y * img->width + x) * bytes_per_pixel;
     76 
     77     if (img->bpp == 24) {
     78         pixel[0] = r;
     79         pixel[1] = g;
     80         pixel[2] = b;
     81     } else if (img->bpp == 32) {
     82         pixel[0] = r;
     83         pixel[1] = g;
     84         pixel[2] = b;
     85         pixel[3] = a;
     86     } else if (img->bpp == 8) {
     87         /* Convert to grayscale */
     88         pixel[0] = (r * 30 + g * 59 + b * 11) / 100;
     89     }
     90 }
     91 
     92 /* Nearest neighbor scaling */
     93 image_t* scale_nearest_neighbor(image_t* src, uint32_t new_width, uint32_t new_height) {
     94     image_t* dst = image_create(new_width, new_height, src->bpp);
     95     if (!dst) return NULL;
     96 
     97     float x_ratio = (float)src->width / new_width;
     98     float y_ratio = (float)src->height / new_height;
     99 
    100     for (uint32_t y = 0; y < new_height; y++) {
    101         for (uint32_t x = 0; x < new_width; x++) {
    102             uint32_t src_x = (uint32_t)(x * x_ratio);
    103             uint32_t src_y = (uint32_t)(y * y_ratio);
    104 
    105             uint8_t r = 0, g = 0, b = 0, a = 0;
    106             image_get_pixel(src, src_x, src_y, &r, &g, &b, &a);
    107             image_set_pixel(dst, x, y, r, g, b, a);
    108         }
    109     }
    110 
    111     return dst;
    112 }
    113 
    114 /* Bilinear interpolation helper */
    115 static inline uint8_t bilinear_interpolate(uint8_t c00, uint8_t c10, uint8_t c01, uint8_t c11,
    116                                            float x_weight, float y_weight) {
    117     float val = c00 * (1 - x_weight) * (1 - y_weight) +
    118                 c10 * x_weight * (1 - y_weight) +
    119                 c01 * (1 - x_weight) * y_weight +
    120                 c11 * x_weight * y_weight;
    121     return (uint8_t)val;
    122 }
    123 
    124 /* Bilinear scaling */
    125 image_t* scale_bilinear(image_t* src, uint32_t new_width, uint32_t new_height) {
    126     image_t* dst = image_create(new_width, new_height, src->bpp);
    127     if (!dst) return NULL;
    128 
    129     float x_ratio = (float)(src->width - 1) / new_width;
    130     float y_ratio = (float)(src->height - 1) / new_height;
    131 
    132     for (uint32_t y = 0; y < new_height; y++) {
    133         for (uint32_t x = 0; x < new_width; x++) {
    134             float src_x = x * x_ratio;
    135             float src_y = y * y_ratio;
    136 
    137             uint32_t x0 = (uint32_t)src_x;
    138             uint32_t y0 = (uint32_t)src_y;
    139             uint32_t x1 = (x0 + 1 < src->width) ? x0 + 1 : x0;
    140             uint32_t y1 = (y0 + 1 < src->height) ? y0 + 1 : y0;
    141 
    142             float x_weight = src_x - x0;
    143             float y_weight = src_y - y0;
    144 
    145             uint8_t r00, g00, b00, a00;
    146             uint8_t r10, g10, b10, a10;
    147             uint8_t r01, g01, b01, a01;
    148             uint8_t r11, g11, b11, a11;
    149 
    150             image_get_pixel(src, x0, y0, &r00, &g00, &b00, &a00);
    151             image_get_pixel(src, x1, y0, &r10, &g10, &b10, &a10);
    152             image_get_pixel(src, x0, y1, &r01, &g01, &b01, &a01);
    153             image_get_pixel(src, x1, y1, &r11, &g11, &b11, &a11);
    154 
    155             uint8_t r = bilinear_interpolate(r00, r10, r01, r11, x_weight, y_weight);
    156             uint8_t g = bilinear_interpolate(g00, g10, g01, g11, x_weight, y_weight);
    157             uint8_t b = bilinear_interpolate(b00, b10, b01, b11, x_weight, y_weight);
    158             uint8_t a = bilinear_interpolate(a00, a10, a01, a11, x_weight, y_weight);
    159 
    160             image_set_pixel(dst, x, y, r, g, b, a);
    161         }
    162     }
    163 
    164     return dst;
    165 }
    166 
    167 /* Calculate fit dimensions (maintain aspect ratio, fit inside) */
    168 void calculate_fit_dimensions(uint32_t src_w, uint32_t src_h,
    169                               uint32_t dst_w, uint32_t dst_h,
    170                               uint32_t* out_w, uint32_t* out_h) {
    171     float src_aspect = (float)src_w / src_h;
    172     float dst_aspect = (float)dst_w / dst_h;
    173 
    174     if (src_aspect > dst_aspect) {
    175         /* Width is limiting factor */
    176         *out_w = dst_w;
    177         *out_h = (uint32_t)(dst_w / src_aspect);
    178     } else {
    179         /* Height is limiting factor */
    180         *out_h = dst_h;
    181         *out_w = (uint32_t)(dst_h * src_aspect);
    182     }
    183 }
    184 
    185 /* Calculate cover dimensions (maintain aspect ratio, cover entire area) */
    186 void calculate_cover_dimensions(uint32_t src_w, uint32_t src_h,
    187                                 uint32_t dst_w, uint32_t dst_h,
    188                                 uint32_t* out_w, uint32_t* out_h) {
    189     float src_aspect = (float)src_w / src_h;
    190     float dst_aspect = (float)dst_w / dst_h;
    191 
    192     if (src_aspect < dst_aspect) {
    193         /* Width is limiting factor */
    194         *out_w = dst_w;
    195         *out_h = (uint32_t)(dst_w / src_aspect);
    196     } else {
    197         /* Height is limiting factor */
    198         *out_h = dst_h;
    199         *out_w = (uint32_t)(dst_h * src_aspect);
    200     }
    201 }
    202 
    203 /* Scale image with mode */
    204 image_t* image_scale(image_t* src, uint32_t new_width, uint32_t new_height, scale_mode_t mode) {
    205     if (!src) return NULL;
    206 
    207     /* Calculate actual dimensions based on mode */
    208     uint32_t actual_width = new_width;
    209     uint32_t actual_height = new_height;
    210 
    211     if (mode == SCALE_FIT) {
    212         calculate_fit_dimensions(src->width, src->height, new_width, new_height,
    213                                 &actual_width, &actual_height);
    214     } else if (mode == SCALE_COVER) {
    215         calculate_cover_dimensions(src->width, src->height, new_width, new_height,
    216                                    &actual_width, &actual_height);
    217     }
    218 
    219     /* Apply scaling algorithm */
    220     if (mode == SCALE_BILINEAR) {
    221         return scale_bilinear(src, actual_width, actual_height);
    222     } else {
    223         return scale_nearest_neighbor(src, actual_width, actual_height);
    224     }
    225 }
    226 
    227 /* Flip image */
    228 image_t* image_flip(image_t* src, int horizontal, int vertical) {
    229     if (!src) return NULL;
    230 
    231     image_t* dst = image_create(src->width, src->height, src->bpp);
    232     if (!dst) return NULL;
    233 
    234     for (uint32_t y = 0; y < src->height; y++) {
    235         for (uint32_t x = 0; x < src->width; x++) {
    236             uint32_t dst_x = horizontal ? (src->width - 1 - x) : x;
    237             uint32_t dst_y = vertical ? (src->height - 1 - y) : y;
    238 
    239             uint8_t r = 0, g = 0, b = 0, a = 0;
    240             image_get_pixel(src, x, y, &r, &g, &b, &a);
    241             image_set_pixel(dst, dst_x, dst_y, r, g, b, a);
    242         }
    243     }
    244 
    245     return dst;
    246 }
    247 
    248 /* Draw image at position */
    249 void image_draw(image_t* img, int x, int y) {
    250     if (!img) return;
    251 
    252     for (uint32_t img_y = 0; img_y < img->height; img_y++) {
    253         for (uint32_t img_x = 0; img_x < img->width; img_x++) {
    254             uint8_t r = 0, g = 0, b = 0, a = 0;
    255             image_get_pixel(img, img_x, img_y, &r, &g, &b, &a);
    256 
    257             int screen_x = x + img_x;
    258             int screen_y = y + img_y;
    259 
    260             /* Draw to appropriate graphics mode */
    261             if (vesa_state.active) {
    262                 /* VESA mode - use RGB */
    263                 if (a > 0) {  /* Simple alpha test */
    264                     vesa_draw_pixel(screen_x, screen_y, vesa_rgb_to_color(r, g, b));
    265                 }
    266             } else {
    267                 /* VGA mode - convert RGB to closest palette color */
    268                 /* Simple mapping: use basic 256-color palette */
    269                 if (a > 0 && screen_x >= 0 && screen_x < 320 && screen_y >= 0 && screen_y < 200) {
    270                     /* Map RGB to 256 color palette (6-8-5 levels) */
    271                     uint8_t color = ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6);
    272                     vga_memory[screen_y * 320 + screen_x] = color;
    273                 }
    274             }
    275         }
    276     }
    277 }
    278 
    279 /* Draw image with scaling */
    280 void image_draw_scaled(image_t* img, int x, int y, int width, int height, scale_mode_t mode) {
    281     if (!img) return;
    282 
    283     /* If no scaling needed, draw directly */
    284     if ((uint32_t)width == img->width && (uint32_t)height == img->height) {
    285         image_draw(img, x, y);
    286         return;
    287     }
    288 
    289     /* Scale the image */
    290     image_t* scaled = image_scale(img, width, height, mode);
    291     if (scaled) {
    292         image_draw(scaled, x, y);
    293         image_destroy(scaled);
    294     }
    295 }
    296 
    297 /* Draw image with full options */
    298 void image_draw_with_options(image_t* img, draw_options_t* options) {
    299     if (!img || !options) return;
    300 
    301     image_t* processed = img;
    302     int needs_free = 0;
    303 
    304     /* Apply flipping if needed */
    305     if (options->flip_horizontal || options->flip_vertical) {
    306         processed = image_flip(img, options->flip_horizontal, options->flip_vertical);
    307         if (!processed) return;
    308         needs_free = 1;
    309     }
    310 
    311     /* Apply scaling if needed */
    312     uint32_t target_width = (options->width > 0) ? (uint32_t)options->width : img->width;
    313     uint32_t target_height = (options->height > 0) ? (uint32_t)options->height : img->height;
    314 
    315     if (target_width != processed->width || target_height != processed->height) {
    316         image_t* scaled = image_scale(processed, target_width, target_height, options->scale_mode);
    317         if (scaled) {
    318             if (needs_free) image_destroy(processed);
    319             processed = scaled;
    320             needs_free = 1;
    321         }
    322     }
    323 
    324     /* Draw the final image */
    325     image_draw(processed, options->x, options->y);
    326 
    327     /* Clean up if we created temporary images */
    328     if (needs_free) {
    329         image_destroy(processed);
    330     }
    331 }
    332 
    333 /* Lua binding: Draw image */
    334 int lua_image_draw(lua_State* L) {
    335     image_t* img = (image_t*)lua_touserdata(L, 1);
    336     int x = luaL_checkinteger(L, 2);
    337     int y = luaL_checkinteger(L, 3);
    338 
    339     if (img) {
    340         image_draw(img, x, y);
    341     }
    342 
    343     return 0;
    344 }
    345 
    346 /* Lua binding: Draw image with scaling */
    347 int lua_image_draw_scaled(lua_State* L) {
    348     image_t* img = (image_t*)lua_touserdata(L, 1);
    349     int x = luaL_checkinteger(L, 2);
    350     int y = luaL_checkinteger(L, 3);
    351     int width = luaL_checkinteger(L, 4);
    352     int height = luaL_checkinteger(L, 5);
    353     scale_mode_t mode = luaL_optinteger(L, 6, SCALE_NEAREST);
    354 
    355     if (img) {
    356         image_draw_scaled(img, x, y, width, height, mode);
    357     }
    358 
    359     return 0;
    360 }
    361 
    362 /* Lua binding: Get image info */
    363 int lua_image_get_info(lua_State* L) {
    364     image_t* img = (image_t*)lua_touserdata(L, 1);
    365 
    366     if (!img) {
    367         lua_pushnil(L);
    368         return 1;
    369     }
    370 
    371     lua_newtable(L);
    372 
    373     lua_pushinteger(L, img->width);
    374     lua_setfield(L, -2, "width");
    375 
    376     lua_pushinteger(L, img->height);
    377     lua_setfield(L, -2, "height");
    378 
    379     lua_pushinteger(L, img->bpp);
    380     lua_setfield(L, -2, "bpp");
    381 
    382     lua_pushboolean(L, img->has_alpha);
    383     lua_setfield(L, -2, "hasAlpha");
    384 
    385     return 1;
    386 }
    387 
    388 /* Lua binding: Destroy image */
    389 int lua_image_destroy(lua_State* L) {
    390     image_t* img = (image_t*)lua_touserdata(L, 1);
    391     if (img) {
    392         image_destroy(img);
    393     }
    394     return 0;
    395 }
    396 
    397 /* Lua binding: Get image width */
    398 int lua_image_get_width(lua_State* L) {
    399     image_t* img = (image_t*)lua_touserdata(L, 1);
    400     if (!img) {
    401         lua_pushnil(L);
    402         return 1;
    403     }
    404     lua_pushinteger(L, img->width);
    405     return 1;
    406 }
    407 
    408 /* Lua binding: Get image height */
    409 int lua_image_get_height(lua_State* L) {
    410     image_t* img = (image_t*)lua_touserdata(L, 1);
    411     if (!img) {
    412         lua_pushnil(L);
    413         return 1;
    414     }
    415     lua_pushinteger(L, img->height);
    416     return 1;
    417 }
    418 
    419 /* Lua binding: Get pixel color */
    420 int lua_image_get_pixel(lua_State* L) {
    421     image_t* img = (image_t*)lua_touserdata(L, 1);
    422     uint32_t x = (uint32_t)luaL_checkinteger(L, 2);
    423     uint32_t y = (uint32_t)luaL_checkinteger(L, 3);
    424 
    425     if (!img || x >= img->width || y >= img->height) {
    426         lua_pushnil(L);
    427         return 1;
    428     }
    429 
    430     uint8_t r, g, b, a;
    431     image_get_pixel(img, x, y, &r, &g, &b, &a);
    432 
    433     lua_pushinteger(L, r);
    434     lua_pushinteger(L, g);
    435     lua_pushinteger(L, b);
    436     lua_pushinteger(L, a);
    437     return 4;
    438 }
    439 
    440 /* Lua binding: Create new image */
    441 int lua_image_create(lua_State* L) {
    442     uint32_t width = (uint32_t)luaL_checkinteger(L, 1);
    443     uint32_t height = (uint32_t)luaL_checkinteger(L, 2);
    444     int has_alpha = lua_toboolean(L, 3);
    445 
    446     if (width == 0 || height == 0 || width > 4096 || height > 4096) {
    447         lua_pushnil(L);
    448         return 1;
    449     }
    450 
    451     uint8_t bpp = has_alpha ? 32 : 24;
    452     image_t* img = image_create(width, height, bpp);
    453 
    454     if (!img) {
    455         lua_pushnil(L);
    456         return 1;
    457     }
    458 
    459     lua_pushlightuserdata(L, img);
    460     return 1;
    461 }
    462 
    463 /* Lua binding: Set pixel color */
    464 int lua_image_set_pixel(lua_State* L) {
    465     image_t* img = (image_t*)lua_touserdata(L, 1);
    466     uint32_t x = (uint32_t)luaL_checkinteger(L, 2);
    467     uint32_t y = (uint32_t)luaL_checkinteger(L, 3);
    468     uint8_t r = (uint8_t)luaL_checkinteger(L, 4);
    469     uint8_t g = (uint8_t)luaL_checkinteger(L, 5);
    470     uint8_t b = (uint8_t)luaL_checkinteger(L, 6);
    471     uint8_t a = (uint8_t)luaL_optinteger(L, 7, 255);
    472 
    473     if (!img) {
    474         lua_pushboolean(L, 0);
    475         return 1;
    476     }
    477 
    478     if (x >= img->width || y >= img->height) {
    479         lua_pushboolean(L, 0);
    480         return 1;
    481     }
    482 
    483     image_set_pixel(img, x, y, r, g, b, a);
    484     lua_pushboolean(L, 1);
    485     return 1;
    486 }
    487 
    488 /* Rotate image by 90, 180, or 270 degrees */
    489 image_t* image_rotate(image_t* src, rotation_angle_t angle) {
    490     if (!src) return NULL;
    491 
    492     /* Normalize angle to 0, 90, 180, 270 */
    493     int normalized_angle = angle;
    494     while (normalized_angle < 0) normalized_angle += 360;
    495     normalized_angle = normalized_angle % 360;
    496 
    497     /* No rotation needed */
    498     if (normalized_angle == 0) {
    499         image_t* copy = image_create(src->width, src->height, src->bpp);
    500         if (!copy) return NULL;
    501         memcpy(copy->data, src->data, src->data_size);
    502         copy->has_alpha = src->has_alpha;
    503         return copy;
    504     }
    505 
    506     uint32_t new_width, new_height;
    507 
    508     /* For 90 and 270 degrees, dimensions swap */
    509     if (normalized_angle == 90 || normalized_angle == 270) {
    510         new_width = src->height;
    511         new_height = src->width;
    512     } else {
    513         new_width = src->width;
    514         new_height = src->height;
    515     }
    516 
    517     /* Create rotated image */
    518     image_t* dst = image_create(new_width, new_height, src->bpp);
    519     if (!dst) return NULL;
    520     dst->has_alpha = src->has_alpha;
    521 
    522     uint32_t bytes_per_pixel = (src->bpp == 32) ? 4 : 3;
    523 
    524     /* Rotate pixels */
    525     for (uint32_t y = 0; y < src->height; y++) {
    526         for (uint32_t x = 0; x < src->width; x++) {
    527             uint8_t r = 0, g = 0, b = 0, a = 0;
    528             image_get_pixel(src, x, y, &r, &g, &b, &a);
    529 
    530             uint32_t dst_x, dst_y;
    531 
    532             switch (normalized_angle) {
    533                 case 90:
    534                     /* 90 degrees clockwise: (x,y) -> (height-1-y, x) */
    535                     dst_x = src->height - 1 - y;
    536                     dst_y = x;
    537                     break;
    538 
    539                 case 180:
    540                     /* 180 degrees: (x,y) -> (width-1-x, height-1-y) */
    541                     dst_x = src->width - 1 - x;
    542                     dst_y = src->height - 1 - y;
    543                     break;
    544 
    545                 case 270:
    546                     /* 270 degrees clockwise (90 CCW): (x,y) -> (y, width-1-x) */
    547                     dst_x = y;
    548                     dst_y = src->width - 1 - x;
    549                     break;
    550 
    551                 default:
    552                     dst_x = x;
    553                     dst_y = y;
    554                     break;
    555             }
    556 
    557             image_set_pixel(dst, dst_x, dst_y, r, g, b, a);
    558         }
    559     }
    560 
    561     return dst;
    562 }
    563 
    564 /* Lua binding: Rotate image */
    565 int lua_image_rotate(lua_State* L) {
    566     image_t* img = (image_t*)lua_touserdata(L, 1);
    567     int angle = luaL_checkinteger(L, 2);
    568 
    569     if (!img) {
    570         lua_pushnil(L);
    571         lua_pushstring(L, "Invalid image");
    572         return 2;
    573     }
    574 
    575     /* Validate angle */
    576     if (angle != 0 && angle != 90 && angle != 180 && angle != 270 && angle != -90) {
    577         lua_pushnil(L);
    578         lua_pushstring(L, "Angle must be 0, 90, 180, 270, or -90");
    579         return 2;
    580     }
    581 
    582     image_t* rotated = image_rotate(img, (rotation_angle_t)angle);
    583 
    584     if (!rotated) {
    585         lua_pushnil(L);
    586         lua_pushstring(L, "Rotation failed");
    587         return 2;
    588     }
    589 
    590     lua_pushlightuserdata(L, rotated);
    591     return 1;
    592 }
    593 
    594 /* Lua binding: Get image buffer as BGRA string (for Lua Image library) */
    595 int lua_image_get_buffer_bgra(lua_State* L) {
    596     image_t* img = (image_t*)lua_touserdata(L, 1);
    597 
    598     if (!img || !img->data) {
    599         lua_pushnil(L);
    600         lua_pushstring(L, "Invalid image");
    601         return 2;
    602     }
    603 
    604     /* Allocate buffer for BGRA data (always 4 bytes per pixel) */
    605     size_t buf_size = img->width * img->height * 4;
    606     uint8_t* bgra_buf = (uint8_t*)malloc(buf_size);
    607     if (!bgra_buf) {
    608         lua_pushnil(L);
    609         lua_pushstring(L, "Out of memory");
    610         return 2;
    611     }
    612 
    613     /* Convert image data to BGRA format */
    614     int bytes_per_pixel = img->bpp / 8;
    615     for (uint32_t y = 0; y < img->height; y++) {
    616         for (uint32_t x = 0; x < img->width; x++) {
    617             uint8_t* src_pixel = img->data + (y * img->width + x) * bytes_per_pixel;
    618             uint8_t* dst_pixel = bgra_buf + (y * img->width + x) * 4;
    619 
    620             /* Source is RGB or RGBA */
    621             uint8_t r = src_pixel[0];
    622             uint8_t g = src_pixel[1];
    623             uint8_t b = src_pixel[2];
    624             uint8_t a = (bytes_per_pixel == 4) ? src_pixel[3] : 255;
    625 
    626             /* Destination is BGRA */
    627             dst_pixel[0] = b;
    628             dst_pixel[1] = g;
    629             dst_pixel[2] = r;
    630             dst_pixel[3] = a;
    631         }
    632     }
    633 
    634     /* Push as Lua string */
    635     lua_pushlstring(L, (const char*)bgra_buf, buf_size);
    636     free(bgra_buf);
    637 
    638     return 1;
    639 }
    640 
    641 /* ============================================================================
    642  * Lua ImageBuffer - Mutable BGRA pixel buffer as userdata
    643  * This allows true in-place modification without string copies
    644  * ============================================================================ */
    645 
    646 typedef struct {
    647     int width;
    648     int height;
    649     uint8_t* data;  /* BGRA format, 4 bytes per pixel */
    650 } ImageBuffer;
    651 
    652 #define IMAGEBUFFER_MT "ImageBuffer"
    653 
    654 /* Helper: Set a single pixel in a BGRA buffer (modifies in place) */
    655 static inline void buffer_set_pixel(uint8_t* buf, int width, int height, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
    656     if (x < 0 || x >= width || y < 0 || y >= height) return;
    657     size_t offset = ((size_t)y * width + x) * 4;
    658     buf[offset] = b;
    659     buf[offset + 1] = g;
    660     buf[offset + 2] = r;
    661     buf[offset + 3] = a;
    662 }
    663 
    664 /* Create a new ImageBuffer userdata
    665  * ImageBufferNew(width, height) -> ImageBuffer userdata */
    666 int lua_imagebuffer_new(lua_State* L) {
    667     int width = luaL_checkinteger(L, 1);
    668     int height = luaL_checkinteger(L, 2);
    669 
    670     if (width <= 0 || height <= 0 || width > 8192 || height > 8192) {
    671         return luaL_error(L, "Invalid buffer dimensions");
    672     }
    673 
    674     /* Create userdata */
    675     ImageBuffer* ib = (ImageBuffer*)lua_newuserdata(L, sizeof(ImageBuffer));
    676     ib->width = width;
    677     ib->height = height;
    678     ib->data = (uint8_t*)malloc((size_t)width * height * 4);
    679 
    680     if (!ib->data) {
    681         return luaL_error(L, "Out of memory");
    682     }
    683 
    684     /* Initialize to white (opaque) */
    685     size_t size = (size_t)width * height * 4;
    686     for (size_t i = 0; i < size; i += 4) {
    687         ib->data[i] = 255;     /* B */
    688         ib->data[i + 1] = 255; /* G */
    689         ib->data[i + 2] = 255; /* R */
    690         ib->data[i + 3] = 255; /* A */
    691     }
    692 
    693     /* Set metatable */
    694     luaL_getmetatable(L, IMAGEBUFFER_MT);
    695     lua_setmetatable(L, -2);
    696 
    697     return 1;
    698 }
    699 
    700 /* Get ImageBuffer from stack (with type check) */
    701 static ImageBuffer* check_imagebuffer(lua_State* L, int idx) {
    702     void* ud = lua_touserdata(L, idx);
    703     if (ud == NULL) {
    704         luaL_error(L, "ImageBuffer expected, got nil");
    705         return NULL;
    706     }
    707     return (ImageBuffer*)ud;
    708 }
    709 
    710 /* ImageBuffer:fillCircle(cx, cy, radius, color) - true in-place modification */
    711 int lua_imagebuffer_fill_circle(lua_State* L) {
    712     ImageBuffer* ib = check_imagebuffer(L, 1);
    713     if (!ib || !ib->data) return 0;
    714 
    715     int cx = luaL_checkinteger(L, 2);
    716     int cy = luaL_checkinteger(L, 3);
    717     int radius = luaL_checkinteger(L, 4);
    718     uint32_t color = luaL_checkinteger(L, 5);
    719 
    720     /* Extract RGB from color (0xRRGGBB format) */
    721     uint8_t r = (color >> 16) & 0xFF;
    722     uint8_t g = (color >> 8) & 0xFF;
    723     uint8_t b = color & 0xFF;
    724     uint8_t a = 255;
    725 
    726     int r2 = radius * radius;
    727     int width = ib->width;
    728     int height = ib->height;
    729     uint8_t* data = ib->data;
    730 
    731     /* Optimized: only iterate within bounding box */
    732     int minY = cy - radius;
    733     int maxY = cy + radius;
    734     int minX = cx - radius;
    735     int maxX = cx + radius;
    736 
    737     /* Clip to buffer bounds */
    738     if (minY < 0) minY = 0;
    739     if (maxY >= height) maxY = height - 1;
    740     if (minX < 0) minX = 0;
    741     if (maxX >= width) maxX = width - 1;
    742 
    743     for (int y = minY; y <= maxY; y++) {
    744         int dy = y - cy;
    745         int dy2 = dy * dy;
    746         for (int x = minX; x <= maxX; x++) {
    747             int dx = x - cx;
    748             if (dx*dx + dy2 <= r2) {
    749                 size_t offset = ((size_t)y * width + x) * 4;
    750                 data[offset] = b;
    751                 data[offset + 1] = g;
    752                 data[offset + 2] = r;
    753                 data[offset + 3] = a;
    754             }
    755         }
    756     }
    757 
    758     return 0;
    759 }
    760 
    761 /* ImageBuffer:drawLine(x1, y1, x2, y2, thickness, color) - true in-place modification */
    762 int lua_imagebuffer_draw_line(lua_State* L) {
    763     ImageBuffer* ib = check_imagebuffer(L, 1);
    764     if (!ib || !ib->data) return 0;
    765 
    766     int x1 = luaL_checkinteger(L, 2);
    767     int y1 = luaL_checkinteger(L, 3);
    768     int x2 = luaL_checkinteger(L, 4);
    769     int y2 = luaL_checkinteger(L, 5);
    770     int thickness = luaL_checkinteger(L, 6);
    771     uint32_t color = luaL_checkinteger(L, 7);
    772 
    773     uint8_t r = (color >> 16) & 0xFF;
    774     uint8_t g = (color >> 8) & 0xFF;
    775     uint8_t b = color & 0xFF;
    776     uint8_t a = 255;
    777 
    778     int width = ib->width;
    779     int height = ib->height;
    780     uint8_t* data = ib->data;
    781 
    782     int radius = thickness / 2;
    783     int r2 = radius * radius;
    784 
    785     /* Bresenham's line algorithm */
    786     int dx = abs(x2 - x1);
    787     int dy = abs(y2 - y1);
    788     int sx = x1 < x2 ? 1 : -1;
    789     int sy = y1 < y2 ? 1 : -1;
    790     int err = dx - dy;
    791 
    792     while (1) {
    793         /* Draw circle at current point */
    794         if (radius <= 0) {
    795             /* Single pixel */
    796             if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
    797                 size_t offset = ((size_t)y1 * width + x1) * 4;
    798                 data[offset] = b;
    799                 data[offset + 1] = g;
    800                 data[offset + 2] = r;
    801                 data[offset + 3] = a;
    802             }
    803         } else {
    804             /* Draw filled circle for thickness */
    805             int minY = y1 - radius;
    806             int maxY = y1 + radius;
    807             int minX = x1 - radius;
    808             int maxX = x1 + radius;
    809 
    810             if (minY < 0) minY = 0;
    811             if (maxY >= height) maxY = height - 1;
    812             if (minX < 0) minX = 0;
    813             if (maxX >= width) maxX = width - 1;
    814 
    815             for (int py = minY; py <= maxY; py++) {
    816                 int tdy = py - y1;
    817                 int tdy2 = tdy * tdy;
    818                 for (int px = minX; px <= maxX; px++) {
    819                     int tdx = px - x1;
    820                     if (tdx*tdx + tdy2 <= r2) {
    821                         size_t offset = ((size_t)py * width + px) * 4;
    822                         data[offset] = b;
    823                         data[offset + 1] = g;
    824                         data[offset + 2] = r;
    825                         data[offset + 3] = a;
    826                     }
    827                 }
    828             }
    829         }
    830 
    831         if (x1 == x2 && y1 == y2) break;
    832 
    833         int e2 = 2 * err;
    834         if (e2 > -dy) {
    835             err -= dy;
    836             x1 += sx;
    837         }
    838         if (e2 < dx) {
    839             err += dx;
    840             y1 += sy;
    841         }
    842     }
    843 
    844     return 0;
    845 }
    846 
    847 /* ImageBuffer:fill(color) - fill entire buffer */
    848 int lua_imagebuffer_fill(lua_State* L) {
    849     ImageBuffer* ib = check_imagebuffer(L, 1);
    850     if (!ib || !ib->data) return 0;
    851 
    852     uint32_t color = luaL_checkinteger(L, 2);
    853 
    854     uint8_t r = (color >> 16) & 0xFF;
    855     uint8_t g = (color >> 8) & 0xFF;
    856     uint8_t b = color & 0xFF;
    857     uint8_t a = 255;
    858 
    859     size_t size = (size_t)ib->width * ib->height * 4;
    860     uint8_t* data = ib->data;
    861 
    862     for (size_t i = 0; i < size; i += 4) {
    863         data[i] = b;
    864         data[i + 1] = g;
    865         data[i + 2] = r;
    866         data[i + 3] = a;
    867     }
    868 
    869     return 0;
    870 }
    871 
    872 /* ImageBuffer:getSize() -> width, height */
    873 int lua_imagebuffer_get_size(lua_State* L) {
    874     ImageBuffer* ib = check_imagebuffer(L, 1);
    875     if (!ib) {
    876         lua_pushinteger(L, 0);
    877         lua_pushinteger(L, 0);
    878         return 2;
    879     }
    880     lua_pushinteger(L, ib->width);
    881     lua_pushinteger(L, ib->height);
    882     return 2;
    883 }
    884 
    885 /* ImageBuffer:getData() -> string (for blitting to screen) */
    886 int lua_imagebuffer_get_data(lua_State* L) {
    887     ImageBuffer* ib = check_imagebuffer(L, 1);
    888     if (!ib || !ib->data) {
    889         lua_pushstring(L, "");
    890         return 1;
    891     }
    892     lua_pushlstring(L, (const char*)ib->data, (size_t)ib->width * ib->height * 4);
    893     return 1;
    894 }
    895 
    896 /* ImageBuffer:getPixel(x, y) -> r, g, b, a */
    897 int lua_imagebuffer_get_pixel(lua_State* L) {
    898     ImageBuffer* ib = check_imagebuffer(L, 1);
    899     if (!ib || !ib->data) return 0;
    900 
    901     int x = luaL_checkinteger(L, 2);
    902     int y = luaL_checkinteger(L, 3);
    903 
    904     if (x < 0 || x >= ib->width || y < 0 || y >= ib->height) {
    905         return 0;
    906     }
    907 
    908     size_t offset = ((size_t)y * ib->width + x) * 4;
    909     lua_pushinteger(L, ib->data[offset + 2]); /* R */
    910     lua_pushinteger(L, ib->data[offset + 1]); /* G */
    911     lua_pushinteger(L, ib->data[offset]);     /* B */
    912     lua_pushinteger(L, ib->data[offset + 3]); /* A */
    913     return 4;
    914 }
    915 
    916 /* ImageBuffer:setPixel(x, y, r, g, b, a) */
    917 int lua_imagebuffer_set_pixel(lua_State* L) {
    918     ImageBuffer* ib = check_imagebuffer(L, 1);
    919     if (!ib || !ib->data) return 0;
    920 
    921     int x = luaL_checkinteger(L, 2);
    922     int y = luaL_checkinteger(L, 3);
    923     int r = luaL_checkinteger(L, 4);
    924     int g = luaL_checkinteger(L, 5);
    925     int b = luaL_checkinteger(L, 6);
    926     int a = luaL_optinteger(L, 7, 255);
    927 
    928     if (x < 0 || x >= ib->width || y < 0 || y >= ib->height) {
    929         return 0;
    930     }
    931 
    932     size_t offset = ((size_t)y * ib->width + x) * 4;
    933     ib->data[offset] = b;
    934     ib->data[offset + 1] = g;
    935     ib->data[offset + 2] = r;
    936     ib->data[offset + 3] = a;
    937     return 0;
    938 }
    939 
    940 /* ImageBuffer garbage collection */
    941 int lua_imagebuffer_gc(lua_State* L) {
    942     ImageBuffer* ib = check_imagebuffer(L, 1);
    943     if (ib->data) {
    944         free(ib->data);
    945         ib->data = NULL;
    946     }
    947     return 0;
    948 }
    949 
    950 /* Register ImageBuffer metatable */
    951 void lua_imagebuffer_register(lua_State* L) {
    952     luaL_newmetatable(L, IMAGEBUFFER_MT);
    953 
    954     /* __index = methods table */
    955     lua_newtable(L);
    956 
    957     lua_pushcfunction(L, lua_imagebuffer_fill_circle);
    958     lua_setfield(L, -2, "fillCircle");
    959 
    960     lua_pushcfunction(L, lua_imagebuffer_draw_line);
    961     lua_setfield(L, -2, "drawLine");
    962 
    963     lua_pushcfunction(L, lua_imagebuffer_fill);
    964     lua_setfield(L, -2, "fill");
    965 
    966     lua_pushcfunction(L, lua_imagebuffer_get_size);
    967     lua_setfield(L, -2, "getSize");
    968 
    969     lua_pushcfunction(L, lua_imagebuffer_get_data);
    970     lua_setfield(L, -2, "getData");
    971 
    972     lua_pushcfunction(L, lua_imagebuffer_get_pixel);
    973     lua_setfield(L, -2, "getPixel");
    974 
    975     lua_pushcfunction(L, lua_imagebuffer_set_pixel);
    976     lua_setfield(L, -2, "setPixel");
    977 
    978     lua_setfield(L, -2, "__index");
    979 
    980     /* __gc for cleanup */
    981     lua_pushcfunction(L, lua_imagebuffer_gc);
    982     lua_setfield(L, -2, "__gc");
    983 
    984     lua_pop(L, 1);  /* Pop metatable */
    985 }
    986 
    987 /* ============================================================================
    988  * Legacy Lua Image Buffer Drawing Functions (string-based, copies on write)
    989  * ============================================================================ */
    990 
    991 /* lua_buffer_set_pixel(buffer, width, height, x, y, r, g, b, a) -> new_buffer
    992  * Sets a single pixel and returns the modified buffer */
    993 int lua_buffer_set_pixel(lua_State* L) {
    994     size_t buf_len;
    995     const char* buf = luaL_checklstring(L, 1, &buf_len);
    996     int width = luaL_checkinteger(L, 2);
    997     int height = luaL_checkinteger(L, 3);
    998     int x = luaL_checkinteger(L, 4);
    999     int y = luaL_checkinteger(L, 5);
   1000     int r = luaL_checkinteger(L, 6);
   1001     int g = luaL_checkinteger(L, 7);
   1002     int b = luaL_checkinteger(L, 8);
   1003     int a = luaL_optinteger(L, 9, 255);
   1004 
   1005     size_t expected = (size_t)width * height * 4;
   1006     if (buf_len < expected) {
   1007         return luaL_error(L, "Buffer too small");
   1008     }
   1009 
   1010     /* Create a copy of the buffer to modify */
   1011     char* new_buf = (char*)malloc(buf_len);
   1012     if (!new_buf) {
   1013         return luaL_error(L, "Out of memory");
   1014     }
   1015     memcpy(new_buf, buf, buf_len);
   1016 
   1017     buffer_set_pixel((uint8_t*)new_buf, width, height, x, y, r, g, b, a);
   1018 
   1019     lua_pushlstring(L, new_buf, buf_len);
   1020     free(new_buf);
   1021     return 1;
   1022 }
   1023 
   1024 /* lua_buffer_fill_circle(buffer, width, height, cx, cy, radius, r, g, b, a) -> new_buffer
   1025  * Fills a circle and returns the modified buffer */
   1026 int lua_buffer_fill_circle(lua_State* L) {
   1027     size_t buf_len;
   1028     const char* buf = luaL_checklstring(L, 1, &buf_len);
   1029     int width = luaL_checkinteger(L, 2);
   1030     int height = luaL_checkinteger(L, 3);
   1031     int cx = luaL_checkinteger(L, 4);
   1032     int cy = luaL_checkinteger(L, 5);
   1033     int radius = luaL_checkinteger(L, 6);
   1034     int r = luaL_checkinteger(L, 7);
   1035     int g = luaL_checkinteger(L, 8);
   1036     int b = luaL_checkinteger(L, 9);
   1037     int a = luaL_optinteger(L, 10, 255);
   1038 
   1039     size_t expected = (size_t)width * height * 4;
   1040     if (buf_len < expected) {
   1041         return luaL_error(L, "Buffer too small");
   1042     }
   1043 
   1044     /* Create a copy of the buffer to modify */
   1045     char* new_buf = (char*)malloc(buf_len);
   1046     if (!new_buf) {
   1047         return luaL_error(L, "Out of memory");
   1048     }
   1049     memcpy(new_buf, buf, buf_len);
   1050 
   1051     /* Fill circle using squared distance */
   1052     int r2 = radius * radius;
   1053     for (int dy = -radius; dy <= radius; dy++) {
   1054         for (int dx = -radius; dx <= radius; dx++) {
   1055             if (dx*dx + dy*dy <= r2) {
   1056                 buffer_set_pixel((uint8_t*)new_buf, width, height, cx + dx, cy + dy, r, g, b, a);
   1057             }
   1058         }
   1059     }
   1060 
   1061     lua_pushlstring(L, new_buf, buf_len);
   1062     free(new_buf);
   1063     return 1;
   1064 }
   1065 
   1066 /* lua_buffer_fill_circle_inplace(buffer, width, height, cx, cy, radius, r, g, b, a)
   1067  * Fills a circle by modifying buffer in-place (no copy, no return) */
   1068 int lua_buffer_fill_circle_inplace(lua_State* L) {
   1069     size_t buf_len;
   1070     char* buf = (char*)luaL_checklstring(L, 1, &buf_len);
   1071     int width = luaL_checkinteger(L, 2);
   1072     int height = luaL_checkinteger(L, 3);
   1073     int cx = luaL_checkinteger(L, 4);
   1074     int cy = luaL_checkinteger(L, 5);
   1075     int radius = luaL_checkinteger(L, 6);
   1076     int r = luaL_checkinteger(L, 7);
   1077     int g = luaL_checkinteger(L, 8);
   1078     int b = luaL_checkinteger(L, 9);
   1079     int a = luaL_optinteger(L, 10, 255);
   1080 
   1081     size_t expected = (size_t)width * height * 4;
   1082     if (buf_len < expected) {
   1083         return 0;
   1084     }
   1085 
   1086     /* Modify buffer in-place */
   1087     int r2 = radius * radius;
   1088     for (int dy = -radius; dy <= radius; dy++) {
   1089         for (int dx = -radius; dx <= radius; dx++) {
   1090             if (dx*dx + dy*dy <= r2) {
   1091                 buffer_set_pixel((uint8_t*)buf, width, height, cx + dx, cy + dy, r, g, b, a);
   1092             }
   1093         }
   1094     }
   1095 
   1096     return 0;
   1097 }
   1098 
   1099 /* lua_buffer_draw_line_inplace(buffer, width, height, x1, y1, x2, y2, thickness, r, g, b, a)
   1100  * Draws a line with variable thickness by modifying buffer in-place (no copy, no return) */
   1101 int lua_buffer_draw_line_inplace(lua_State* L) {
   1102     size_t buf_len;
   1103     char* buf = (char*)luaL_checklstring(L, 1, &buf_len);
   1104     int width = luaL_checkinteger(L, 2);
   1105     int height = luaL_checkinteger(L, 3);
   1106     int x1 = luaL_checkinteger(L, 4);
   1107     int y1 = luaL_checkinteger(L, 5);
   1108     int x2 = luaL_checkinteger(L, 6);
   1109     int y2 = luaL_checkinteger(L, 7);
   1110     int thickness = luaL_checkinteger(L, 8);
   1111     int r = luaL_checkinteger(L, 9);
   1112     int g = luaL_checkinteger(L, 10);
   1113     int b = luaL_checkinteger(L, 11);
   1114     int a = luaL_optinteger(L, 12, 255);
   1115 
   1116     size_t expected = (size_t)width * height * 4;
   1117     if (buf_len < expected) {
   1118         return 0;
   1119     }
   1120 
   1121     /* Bresenham's line algorithm with thickness */
   1122     int dx = abs(x2 - x1);
   1123     int dy = abs(y2 - y1);
   1124     int sx = x1 < x2 ? 1 : -1;
   1125     int sy = y1 < y2 ? 1 : -1;
   1126     int err = dx - dy;
   1127 
   1128     int radius = thickness / 2;
   1129     int r2 = radius * radius;
   1130 
   1131     while (1) {
   1132         /* Draw a filled circle at each point for thickness */
   1133         if (radius <= 0) {
   1134             buffer_set_pixel((uint8_t*)buf, width, height, x1, y1, r, g, b, a);
   1135         } else {
   1136             for (int tdy = -radius; tdy <= radius; tdy++) {
   1137                 for (int tdx = -radius; tdx <= radius; tdx++) {
   1138                     if (tdx*tdx + tdy*tdy <= r2) {
   1139                         buffer_set_pixel((uint8_t*)buf, width, height, x1 + tdx, y1 + tdy, r, g, b, a);
   1140                     }
   1141                 }
   1142             }
   1143         }
   1144 
   1145         if (x1 == x2 && y1 == y2) break;
   1146 
   1147         int e2 = 2 * err;
   1148         if (e2 > -dy) {
   1149             err -= dy;
   1150             x1 += sx;
   1151         }
   1152         if (e2 < dx) {
   1153             err += dx;
   1154             y1 += sy;
   1155         }
   1156     }
   1157 
   1158     return 0;
   1159 }
   1160 
   1161 /* lua_buffer_draw_line(buffer, width, height, x1, y1, x2, y2, thickness, r, g, b, a) -> new_buffer
   1162  * Draws a line with variable thickness using Bresenham's algorithm */
   1163 int lua_buffer_draw_line(lua_State* L) {
   1164     size_t buf_len;
   1165     const char* buf = luaL_checklstring(L, 1, &buf_len);
   1166     int width = luaL_checkinteger(L, 2);
   1167     int height = luaL_checkinteger(L, 3);
   1168     int x1 = luaL_checkinteger(L, 4);
   1169     int y1 = luaL_checkinteger(L, 5);
   1170     int x2 = luaL_checkinteger(L, 6);
   1171     int y2 = luaL_checkinteger(L, 7);
   1172     int thickness = luaL_checkinteger(L, 8);
   1173     int r = luaL_checkinteger(L, 9);
   1174     int g = luaL_checkinteger(L, 10);
   1175     int b = luaL_checkinteger(L, 11);
   1176     int a = luaL_optinteger(L, 12, 255);
   1177 
   1178     size_t expected = (size_t)width * height * 4;
   1179     if (buf_len < expected) {
   1180         return luaL_error(L, "Buffer too small");
   1181     }
   1182 
   1183     /* Create a copy of the buffer to modify */
   1184     char* new_buf = (char*)malloc(buf_len);
   1185     if (!new_buf) {
   1186         return luaL_error(L, "Out of memory");
   1187     }
   1188     memcpy(new_buf, buf, buf_len);
   1189 
   1190     /* Bresenham's line algorithm with thickness */
   1191     int dx = abs(x2 - x1);
   1192     int dy = abs(y2 - y1);
   1193     int sx = x1 < x2 ? 1 : -1;
   1194     int sy = y1 < y2 ? 1 : -1;
   1195     int err = dx - dy;
   1196 
   1197     int radius = thickness / 2;
   1198     int r2 = radius * radius;
   1199 
   1200     while (1) {
   1201         /* Draw a filled circle at each point for thickness */
   1202         if (radius <= 0) {
   1203             buffer_set_pixel((uint8_t*)new_buf, width, height, x1, y1, r, g, b, a);
   1204         } else {
   1205             for (int tdy = -radius; tdy <= radius; tdy++) {
   1206                 for (int tdx = -radius; tdx <= radius; tdx++) {
   1207                     if (tdx*tdx + tdy*tdy <= r2) {
   1208                         buffer_set_pixel((uint8_t*)new_buf, width, height, x1 + tdx, y1 + tdy, r, g, b, a);
   1209                     }
   1210                 }
   1211             }
   1212         }
   1213 
   1214         if (x1 == x2 && y1 == y2) break;
   1215 
   1216         int e2 = 2 * err;
   1217         if (e2 > -dy) {
   1218             err -= dy;
   1219             x1 += sx;
   1220         }
   1221         if (e2 < dx) {
   1222             err += dx;
   1223             y1 += sy;
   1224         }
   1225     }
   1226 
   1227     lua_pushlstring(L, new_buf, buf_len);
   1228     free(new_buf);
   1229     return 1;
   1230 }
   1231 
   1232 /* lua_buffer_fill_rect(buffer, width, height, x, y, w, h, r, g, b, a) -> new_buffer
   1233  * Fills a rectangle and returns the modified buffer */
   1234 int lua_buffer_fill_rect(lua_State* L) {
   1235     size_t buf_len;
   1236     const char* buf = luaL_checklstring(L, 1, &buf_len);
   1237     int buf_width = luaL_checkinteger(L, 2);
   1238     int buf_height = luaL_checkinteger(L, 3);
   1239     int x = luaL_checkinteger(L, 4);
   1240     int y = luaL_checkinteger(L, 5);
   1241     int w = luaL_checkinteger(L, 6);
   1242     int h = luaL_checkinteger(L, 7);
   1243     int r = luaL_checkinteger(L, 8);
   1244     int g = luaL_checkinteger(L, 9);
   1245     int b = luaL_checkinteger(L, 10);
   1246     int a = luaL_optinteger(L, 11, 255);
   1247 
   1248     size_t expected = (size_t)buf_width * buf_height * 4;
   1249     if (buf_len < expected) {
   1250         return luaL_error(L, "Buffer too small");
   1251     }
   1252 
   1253     /* Create a copy of the buffer to modify */
   1254     char* new_buf = (char*)malloc(buf_len);
   1255     if (!new_buf) {
   1256         return luaL_error(L, "Out of memory");
   1257     }
   1258     memcpy(new_buf, buf, buf_len);
   1259 
   1260     /* Clip rectangle to buffer bounds */
   1261     int x1 = x < 0 ? 0 : x;
   1262     int y1 = y < 0 ? 0 : y;
   1263     int x2 = (x + w) > buf_width ? buf_width : (x + w);
   1264     int y2 = (y + h) > buf_height ? buf_height : (y + h);
   1265 
   1266     /* Fill rectangle row by row (cache-friendly) */
   1267     for (int py = y1; py < y2; py++) {
   1268         for (int px = x1; px < x2; px++) {
   1269             size_t offset = ((size_t)py * buf_width + px) * 4;
   1270             new_buf[offset] = b;
   1271             new_buf[offset + 1] = g;
   1272             new_buf[offset + 2] = r;
   1273             new_buf[offset + 3] = a;
   1274         }
   1275     }
   1276 
   1277     lua_pushlstring(L, new_buf, buf_len);
   1278     free(new_buf);
   1279     return 1;
   1280 }
   1281 
   1282 /* lua_buffer_fill(buffer, width, height, r, g, b, a) -> new_buffer
   1283  * Fills the entire buffer with a color */
   1284 int lua_buffer_fill(lua_State* L) {
   1285     size_t buf_len;
   1286     const char* buf = luaL_checklstring(L, 1, &buf_len);
   1287     int width = luaL_checkinteger(L, 2);
   1288     int height = luaL_checkinteger(L, 3);
   1289     int r = luaL_checkinteger(L, 4);
   1290     int g = luaL_checkinteger(L, 5);
   1291     int b = luaL_checkinteger(L, 6);
   1292     int a = luaL_optinteger(L, 7, 255);
   1293 
   1294     size_t expected = (size_t)width * height * 4;
   1295     if (buf_len < expected) {
   1296         return luaL_error(L, "Buffer too small");
   1297     }
   1298 
   1299     /* Create new buffer and fill it */
   1300     char* new_buf = (char*)malloc(buf_len);
   1301     if (!new_buf) {
   1302         return luaL_error(L, "Out of memory");
   1303     }
   1304 
   1305     /* Fill all pixels */
   1306     size_t num_pixels = (size_t)width * height;
   1307     for (size_t i = 0; i < num_pixels; i++) {
   1308         size_t offset = i * 4;
   1309         new_buf[offset] = b;
   1310         new_buf[offset + 1] = g;
   1311         new_buf[offset + 2] = r;
   1312         new_buf[offset + 3] = a;
   1313     }
   1314 
   1315     lua_pushlstring(L, new_buf, buf_len);
   1316     free(new_buf);
   1317     return 1;
   1318 }
   1319 
   1320 /* lua_buffer_blit_row(buffer, width, height, y, row_data, start_x) -> new_buffer
   1321  * Blits a row of BGRA pixel data at the specified y position */
   1322 int lua_buffer_blit_row(lua_State* L) {
   1323     size_t buf_len;
   1324     const char* buf = luaL_checklstring(L, 1, &buf_len);
   1325     int width = luaL_checkinteger(L, 2);
   1326     int height = luaL_checkinteger(L, 3);
   1327     int y = luaL_checkinteger(L, 4);
   1328     size_t row_len;
   1329     const char* row_data = luaL_checklstring(L, 5, &row_len);
   1330     int start_x = luaL_optinteger(L, 6, 0);
   1331 
   1332     size_t expected = (size_t)width * height * 4;
   1333     if (buf_len < expected) {
   1334         return luaL_error(L, "Buffer too small");
   1335     }
   1336 
   1337     if (y < 0 || y >= height) {
   1338         /* Row out of bounds, return original buffer */
   1339         lua_pushvalue(L, 1);
   1340         return 1;
   1341     }
   1342 
   1343     /* Create a copy of the buffer to modify */
   1344     char* new_buf = (char*)malloc(buf_len);
   1345     if (!new_buf) {
   1346         return luaL_error(L, "Out of memory");
   1347     }
   1348     memcpy(new_buf, buf, buf_len);
   1349 
   1350     /* Calculate how many pixels to copy */
   1351     int pixels_in_row = row_len / 4;
   1352     int dest_offset = (y * width + start_x) * 4;
   1353     int pixels_to_copy = pixels_in_row;
   1354 
   1355     /* Clip to buffer bounds */
   1356     if (start_x < 0) {
   1357         int skip = -start_x;
   1358         row_data += skip * 4;
   1359         pixels_to_copy -= skip;
   1360         start_x = 0;
   1361         dest_offset = y * width * 4;
   1362     }
   1363     if (start_x + pixels_to_copy > width) {
   1364         pixels_to_copy = width - start_x;
   1365     }
   1366 
   1367     if (pixels_to_copy > 0) {
   1368         memcpy(new_buf + dest_offset, row_data, pixels_to_copy * 4);
   1369     }
   1370 
   1371     lua_pushlstring(L, new_buf, buf_len);
   1372     free(new_buf);
   1373     return 1;
   1374 }
   1375 
   1376 /* lua_buffer_create(width, height, r, g, b, a) -> buffer
   1377  * Creates a new BGRA buffer filled with the specified color */
   1378 int lua_buffer_create(lua_State* L) {
   1379     int width = luaL_checkinteger(L, 1);
   1380     int height = luaL_checkinteger(L, 2);
   1381     int r = luaL_optinteger(L, 3, 255);
   1382     int g = luaL_optinteger(L, 4, 255);
   1383     int b = luaL_optinteger(L, 5, 255);
   1384     int a = luaL_optinteger(L, 6, 255);
   1385 
   1386     if (width <= 0 || height <= 0 || width > 8192 || height > 8192) {
   1387         return luaL_error(L, "Invalid buffer dimensions");
   1388     }
   1389 
   1390     size_t buf_len = (size_t)width * height * 4;
   1391     char* buf = (char*)malloc(buf_len);
   1392     if (!buf) {
   1393         return luaL_error(L, "Out of memory");
   1394     }
   1395 
   1396     /* Fill all pixels */
   1397     size_t num_pixels = (size_t)width * height;
   1398     for (size_t i = 0; i < num_pixels; i++) {
   1399         size_t offset = i * 4;
   1400         buf[offset] = b;
   1401         buf[offset + 1] = g;
   1402         buf[offset + 2] = r;
   1403         buf[offset + 3] = a;
   1404     }
   1405 
   1406     lua_pushlstring(L, buf, buf_len);
   1407     free(buf);
   1408     return 1;
   1409 }