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 }