1070 lines
34 KiB
C
1070 lines
34 KiB
C
#include "qjs_sdl_surface.h"
|
|
#include "qjs_blob.h"
|
|
#include "jsffi.h"
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3/SDL_gpu.h>
|
|
#include <SDL3/SDL_surface.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include "qjs_sdl.h"
|
|
#include "qjs_common.h"
|
|
|
|
#define STB_DXT_IMPLEMENTATION
|
|
#include "thirdparty/stb/stb_dxt.h"
|
|
|
|
JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
|
|
{
|
|
return SDL_PixelFormat2js(js, fmt);
|
|
}
|
|
|
|
SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
|
|
{
|
|
return js2SDL_PixelFormat(js, v);
|
|
}
|
|
|
|
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
|
|
|
|
static const scale_entry k_scale_table[] = {
|
|
{ "nearest", SDL_SCALEMODE_NEAREST },
|
|
{ "linear", SDL_SCALEMODE_LINEAR },
|
|
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
|
|
};
|
|
|
|
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
|
|
const scale_entry *it;
|
|
for(it = k_scale_table; it->name; ++it)
|
|
if(it->mode == mode) break;
|
|
return JS_NewString(js, it->name ? it->name : "nearest");
|
|
}
|
|
|
|
SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
|
|
if(JS_IsNull(v)) return SDL_SCALEMODE_NEAREST;
|
|
const char *s = JS_ToCString(js, v);
|
|
if(!s) return SDL_SCALEMODE_NEAREST;
|
|
const scale_entry *it;
|
|
for(it = k_scale_table; it->name; ++it)
|
|
if(!strcmp(it->name, s)) break;
|
|
JS_FreeCString(js, s);
|
|
return it->mode;
|
|
}
|
|
|
|
// SDL_Surface free function
|
|
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
|
|
if (s->flags & SDL_SURFACE_PREALLOCATED)
|
|
free(s->pixels);
|
|
SDL_DestroySurface(s);
|
|
}
|
|
|
|
// Class definition
|
|
QJSCLASS(SDL_Surface,)
|
|
|
|
// SDL_Surface methods
|
|
JSC_CCALL(surface_blit,
|
|
SDL_Surface *dst = js2SDL_Surface(js, self);
|
|
|
|
irect dr = {0}, *pdr = NULL;
|
|
if(!JS_IsNull(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
|
|
|
|
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
|
|
if (!src)
|
|
return JS_ThrowReferenceError(js, "Argument must be a surface.");
|
|
|
|
irect sr = {0}, *psr = NULL;
|
|
if(!JS_IsNull(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
|
|
|
|
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
|
|
|
|
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
|
|
|
|
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
|
|
)
|
|
|
|
JSC_CCALL(surface_scale,
|
|
SDL_Surface *src = js2SDL_Surface(js,self);
|
|
HMM_Vec2 wh = js2vec2(js,argv[0]);
|
|
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
|
|
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
|
|
ret = SDL_Surface2js(js,new);
|
|
)
|
|
|
|
static SDL_PixelFormatDetails pdetails = {
|
|
.format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
|
|
.bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits
|
|
.bytes_per_pixel = 4, // 4 bytes per pixel
|
|
.padding = {0, 0}, // Unused padding
|
|
.Rmask = 0xFF000000, // Red mask
|
|
.Gmask = 0x00FF0000, // Green mask
|
|
.Bmask = 0x0000FF00, // Blue mask
|
|
.Amask = 0x000000FF, // Alpha mask
|
|
.Rbits = 8, // 8 bits for Red
|
|
.Gbits = 8, // 8 bits for Green
|
|
.Bbits = 8, // 8 bits for Blue
|
|
.Abits = 8, // 8 bits for Alpha
|
|
.Rshift = 24, // Red shift
|
|
.Gshift = 16, // Green shift
|
|
.Bshift = 8, // Blue shift
|
|
.Ashift = 0 // Alpha shift
|
|
};
|
|
|
|
JSC_CCALL(surface_fill,
|
|
SDL_Surface *src = js2SDL_Surface(js,self);
|
|
colorf color = js2color(js,argv[0]);
|
|
rect r = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.w = src->w,
|
|
.h = src->h
|
|
};
|
|
SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
|
|
)
|
|
|
|
JSC_CCALL(surface_rect,
|
|
SDL_Surface *dst = js2SDL_Surface(js,self);
|
|
rect r = js2rect(js,argv[0]);
|
|
colorf color = js2color(js,argv[1]);
|
|
SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
|
|
)
|
|
|
|
JSC_CCALL(surface_convert,
|
|
SDL_Surface *surf = js2SDL_Surface(js,self);
|
|
SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
|
|
SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
|
|
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
|
|
|
|
return SDL_Surface2js(js, dst);
|
|
)
|
|
|
|
JSC_CCALL(surface_dup,
|
|
SDL_Surface *surf = js2SDL_Surface(js,self);
|
|
SDL_Surface *conv = SDL_DuplicateSurface(surf);
|
|
if (!conv)
|
|
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
|
|
|
|
return SDL_Surface2js(js,conv);
|
|
)
|
|
|
|
JSC_CCALL(surface_pixels,
|
|
SDL_Surface *surf = js2SDL_Surface(js, self);
|
|
|
|
int locked = 0;
|
|
if (SDL_MUSTLOCK(surf))
|
|
if (SDL_LockSurface(surf) < 0)
|
|
return JS_ThrowReferenceError(js, "Lock surface failed: %s", SDL_GetError());
|
|
|
|
size_t byte_size;
|
|
if (SDL_ISPIXELFORMAT_FOURCC(surf->format)) {
|
|
/* Planar/YUV formats: use BitsPerPixel to compute true size */
|
|
printf("FOURCC!!! Bits is %d\n", SDL_BYTESPERPIXEL(surf->format));
|
|
byte_size = (size_t)surf->pitch * surf->h * SDL_BYTESPERPIXEL(surf->format);
|
|
} else
|
|
byte_size = (size_t)surf->pitch * surf->h;
|
|
|
|
ret = js_new_blob_stoned_copy(js, surf->pixels, byte_size);
|
|
|
|
if (locked) SDL_UnlockSurface(surf);
|
|
)
|
|
|
|
JSC_CCALL(surface_get_width,
|
|
SDL_Surface *s = js2SDL_Surface(js,self);
|
|
return JS_NewFloat64(js, s->w);
|
|
)
|
|
|
|
JSC_CCALL(surface_get_height,
|
|
SDL_Surface *s = js2SDL_Surface(js,self);
|
|
return JS_NewFloat64(js, s->h);
|
|
)
|
|
|
|
JSC_CCALL(surface_get_format,
|
|
SDL_Surface *s = js2SDL_Surface(js,self);
|
|
return pixelformat2js(js, s->format);
|
|
)
|
|
|
|
JSC_CCALL(surface_get_pitch,
|
|
SDL_Surface *s = js2SDL_Surface(js,self);
|
|
return JS_NewFloat64(js, s->pitch);
|
|
)
|
|
|
|
JSC_CCALL(surface_toJSON,
|
|
SDL_Surface *surf = js2SDL_Surface(js,self);
|
|
|
|
// Create the result object
|
|
JSValue obj = JS_NewObject(js);
|
|
|
|
// Add width and height
|
|
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w));
|
|
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h));
|
|
|
|
// Add format
|
|
JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format));
|
|
|
|
// Add pitch
|
|
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch));
|
|
|
|
// Lock surface if needed
|
|
int locked = 0;
|
|
if (SDL_MUSTLOCK(surf)) {
|
|
if (SDL_LockSurface(surf) < 0) {
|
|
JS_FreeValue(js, obj);
|
|
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
|
|
}
|
|
locked = 1;
|
|
}
|
|
|
|
// Add pixels as ArrayBuffer
|
|
size_t byte_size = surf->pitch * surf->h;
|
|
JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size);
|
|
JS_SetPropertyStr(js, obj, "pixels", pixels);
|
|
|
|
// Unlock if we locked
|
|
if (locked)
|
|
SDL_UnlockSurface(surf);
|
|
|
|
return obj;
|
|
)
|
|
|
|
// Check for integer overflow in size calculations
|
|
static int check_size_overflow(size_t a, size_t b, size_t c, size_t *result)
|
|
{
|
|
if (a > SIZE_MAX / b) return 1;
|
|
size_t temp = a * b;
|
|
if (temp > SIZE_MAX / c) return 1;
|
|
*result = temp * c;
|
|
return 0;
|
|
}
|
|
|
|
// Helper function for BC1/BC3 compression
|
|
static JSValue compress_bc_common(JSContext *js, JSValueConst *argv, int argc, int alpha_mode, const char *format_name)
|
|
{
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "compress_%s requires an object argument", format_name);
|
|
|
|
// Check if width/height properties exist
|
|
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width");
|
|
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height");
|
|
|
|
if (JS_IsNull(width_val) || JS_IsNull(height_val)) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return JS_ThrowTypeError(js, "compress_%s requires width and height properties", format_name);
|
|
}
|
|
|
|
int width, height;
|
|
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return JS_ThrowTypeError(js, "width and height must be numbers");
|
|
}
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
|
|
if (width < 1 || height < 1)
|
|
return JS_ThrowRangeError(js, "width and height must be at least 1");
|
|
|
|
if (width % 4 != 0 || height % 4 != 0)
|
|
return JS_ThrowRangeError(js, "Width and height must be multiples of 4 for BC compression");
|
|
|
|
// Get pixel format
|
|
JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
|
|
SDL_PixelFormat format = js2pixelformat(js, format_val);
|
|
JS_FreeValue(js, format_val);
|
|
|
|
if (format == SDL_PIXELFORMAT_UNKNOWN)
|
|
return JS_ThrowTypeError(js, "Invalid or missing pixel format");
|
|
|
|
// Get pixels
|
|
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
|
|
size_t pixel_len;
|
|
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val);
|
|
|
|
if (!pixel_data) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer");
|
|
}
|
|
|
|
// Validate buffer size
|
|
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
|
|
size_t required_size;
|
|
if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "Image dimensions too large");
|
|
}
|
|
|
|
if (pixel_len < required_size) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)",
|
|
width, height, required_size, pixel_len);
|
|
}
|
|
|
|
// Get high quality mode (default true)
|
|
int high_quality = 1;
|
|
if (argc > 1) {
|
|
high_quality = JS_ToBool(js, argv[1]);
|
|
}
|
|
|
|
int mode = high_quality ? STB_DXT_HIGHQUAL : STB_DXT_NORMAL;
|
|
|
|
// Calculate output size with overflow check
|
|
int blocks_x = width / 4;
|
|
int blocks_y = height / 4;
|
|
int bytes_per_block = (alpha_mode == 0) ? 8 : 16; // BC1=8, BC3=16
|
|
size_t output_size;
|
|
if (check_size_overflow(blocks_x, blocks_y, bytes_per_block, &output_size)) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "Output dimensions too large");
|
|
}
|
|
|
|
// Allocate output buffer
|
|
unsigned char *output = malloc(output_size);
|
|
if (!output) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
|
|
// Allocate RGBA conversion buffer
|
|
size_t rgba_size;
|
|
if (check_size_overflow(width, height, 4, &rgba_size)) {
|
|
free(output);
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "RGBA buffer size too large");
|
|
}
|
|
|
|
unsigned char *rgba_data = malloc(rgba_size);
|
|
if (!rgba_data) {
|
|
free(output);
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
|
|
// Convert to RGBA using SDL
|
|
int convert_result = SDL_ConvertPixels(
|
|
width, height,
|
|
format, pixel_data, width * bytes_per_pixel,
|
|
SDL_PIXELFORMAT_RGBA32, rgba_data, width * 4
|
|
);
|
|
|
|
JS_FreeValue(js, pixels_val);
|
|
|
|
if (convert_result != 0) {
|
|
free(output);
|
|
free(rgba_data);
|
|
return JS_ThrowInternalError(js, "Failed to convert pixels: %s", SDL_GetError());
|
|
}
|
|
|
|
// Compress blocks
|
|
for (int by = 0; by < blocks_y; by++) {
|
|
for (int bx = 0; bx < blocks_x; bx++) {
|
|
unsigned char block[64]; // 4x4 RGBA = 64 bytes
|
|
|
|
// Extract 4x4 block
|
|
for (int y = 0; y < 4; y++) {
|
|
for (int x = 0; x < 4; x++) {
|
|
int src_x = bx * 4 + x;
|
|
int src_y = by * 4 + y;
|
|
int src_idx = (src_y * width + src_x) * 4;
|
|
int dst_idx = (y * 4 + x) * 4;
|
|
|
|
block[dst_idx + 0] = rgba_data[src_idx + 0];
|
|
block[dst_idx + 1] = rgba_data[src_idx + 1];
|
|
block[dst_idx + 2] = rgba_data[src_idx + 2];
|
|
block[dst_idx + 3] = rgba_data[src_idx + 3];
|
|
}
|
|
}
|
|
|
|
// Compress block
|
|
int output_idx = (by * blocks_x + bx) * bytes_per_block;
|
|
stb_compress_dxt_block(output + output_idx, block, alpha_mode, mode);
|
|
}
|
|
}
|
|
|
|
free(rgba_data);
|
|
|
|
// Create result object
|
|
JSValue result = JS_NewObject(js);
|
|
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width));
|
|
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height));
|
|
JS_SetPropertyStr(js, result, "format", JS_NewString(js, format_name));
|
|
JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, blocks_x * bytes_per_block));
|
|
|
|
JSValue compressed_pixels = js_new_blob_stoned_copy(js, output, output_size);
|
|
free(output); // Free the output buffer after copying to blob
|
|
JS_SetPropertyStr(js, result, "pixels", compressed_pixels);
|
|
|
|
return result;
|
|
}
|
|
|
|
// BC1/DXT1 compression
|
|
JSC_CCALL(surface_compress_bc1,
|
|
return compress_bc_common(js, argv, argc, 0, "bc1");
|
|
)
|
|
|
|
// BC3/DXT5 compression
|
|
JSC_CCALL(surface_compress_bc3,
|
|
return compress_bc_common(js, argv, argc, 1, "bc3");
|
|
)
|
|
|
|
// Generic helper for BC4/BC5 channel compression
|
|
static JSValue compress_bc_channels(JSContext *js, JSValueConst *argv, int argc,
|
|
int num_channels, const char *format_name,
|
|
void (*compress_func)(unsigned char *dest, const unsigned char *src))
|
|
{
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "compress_%s requires an object argument", format_name);
|
|
|
|
// Check if width/height properties exist
|
|
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width");
|
|
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height");
|
|
|
|
if (JS_IsNull(width_val) || JS_IsNull(height_val)) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return JS_ThrowTypeError(js, "compress_%s requires width and height properties", format_name);
|
|
}
|
|
|
|
int width, height;
|
|
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return JS_ThrowTypeError(js, "width and height must be numbers");
|
|
}
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
|
|
if (width < 1 || height < 1)
|
|
return JS_ThrowRangeError(js, "width and height must be at least 1");
|
|
|
|
if (width % 4 != 0 || height % 4 != 0)
|
|
return JS_ThrowRangeError(js, "Width and height must be multiples of 4 for BC compression");
|
|
|
|
// Get pixel format
|
|
JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
|
|
SDL_PixelFormat format = js2pixelformat(js, format_val);
|
|
JS_FreeValue(js, format_val);
|
|
|
|
if (format == SDL_PIXELFORMAT_UNKNOWN)
|
|
return JS_ThrowTypeError(js, "Invalid or missing pixel format");
|
|
|
|
// Get pixels
|
|
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
|
|
size_t pixel_len;
|
|
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val);
|
|
|
|
if (!pixel_data) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer");
|
|
}
|
|
|
|
// Validate buffer size
|
|
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
|
|
if (bytes_per_pixel < num_channels) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowTypeError(js, "%s compression requires a format with at least %d channel(s)",
|
|
format_name, num_channels);
|
|
}
|
|
|
|
size_t required_size;
|
|
if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "Image dimensions too large");
|
|
}
|
|
|
|
if (pixel_len < required_size) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)",
|
|
width, height, required_size, pixel_len);
|
|
}
|
|
|
|
// Calculate output size with overflow check
|
|
int blocks_x = width / 4;
|
|
int blocks_y = height / 4;
|
|
int bytes_per_block = (num_channels == 1) ? 8 : 16; // BC4=8, BC5=16
|
|
size_t output_size;
|
|
if (check_size_overflow(blocks_x, blocks_y, bytes_per_block, &output_size)) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "Output dimensions too large");
|
|
}
|
|
|
|
// Allocate output buffer
|
|
unsigned char *output = malloc(output_size);
|
|
if (!output) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
|
|
// Allocate channel extraction buffer
|
|
size_t channel_size;
|
|
if (check_size_overflow(width, height, num_channels, &channel_size)) {
|
|
free(output);
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowRangeError(js, "Channel buffer size too large");
|
|
}
|
|
|
|
unsigned char *channel_data = malloc(channel_size);
|
|
if (!channel_data) {
|
|
free(output);
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
|
|
// Extract channels
|
|
for (int y = 0; y < height; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
int src_idx = (y * width + x) * bytes_per_pixel;
|
|
int dst_idx = (y * width + x) * num_channels;
|
|
|
|
// Extract first 'num_channels' channels
|
|
for (int c = 0; c < num_channels; c++) {
|
|
channel_data[dst_idx + c] = ((unsigned char*)pixel_data)[src_idx + c];
|
|
}
|
|
}
|
|
}
|
|
|
|
JS_FreeValue(js, pixels_val);
|
|
|
|
// Compress blocks
|
|
for (int by = 0; by < blocks_y; by++) {
|
|
for (int bx = 0; bx < blocks_x; bx++) {
|
|
unsigned char block[32]; // Max 4x4 * 2 channels = 32 bytes
|
|
|
|
// Extract 4x4 block
|
|
for (int y = 0; y < 4; y++) {
|
|
for (int x = 0; x < 4; x++) {
|
|
int src_x = bx * 4 + x;
|
|
int src_y = by * 4 + y;
|
|
int src_idx = (src_y * width + src_x) * num_channels;
|
|
int dst_idx = (y * 4 + x) * num_channels;
|
|
|
|
for (int c = 0; c < num_channels; c++) {
|
|
block[dst_idx + c] = channel_data[src_idx + c];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compress block
|
|
int output_idx = (by * blocks_x + bx) * bytes_per_block;
|
|
compress_func(output + output_idx, block);
|
|
}
|
|
}
|
|
|
|
free(channel_data);
|
|
|
|
// Create result object
|
|
JSValue result = JS_NewObject(js);
|
|
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width));
|
|
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height));
|
|
JS_SetPropertyStr(js, result, "format", JS_NewString(js, format_name));
|
|
JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, blocks_x * bytes_per_block));
|
|
|
|
JSValue compressed_pixels = js_new_blob_stoned_copy(js, output, output_size);
|
|
free(output); // Free the output buffer after copying to blob
|
|
JS_SetPropertyStr(js, result, "pixels", compressed_pixels);
|
|
|
|
return result;
|
|
}
|
|
|
|
// BC4 compression (single channel)
|
|
JSC_CCALL(surface_compress_bc4,
|
|
return compress_bc_channels(js, argv, argc, 1, "bc4", stb_compress_bc4_block);
|
|
)
|
|
|
|
// BC5 compression (two channels)
|
|
JSC_CCALL(surface_compress_bc5,
|
|
return compress_bc_channels(js, argv, argc, 2, "bc5", stb_compress_bc5_block);
|
|
)
|
|
|
|
// Constructor for SDL_Surface
|
|
JSC_CCALL(surface_constructor,
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "Surface constructor requires an object argument");
|
|
|
|
// Get width and height
|
|
int width, height;
|
|
JS_GETATOM(js, width, argv[0], width, number)
|
|
JS_GETATOM(js, height, argv[0], height, number)
|
|
|
|
if (!width || !height)
|
|
return JS_ThrowTypeError(js, "Surface constructor requires width and height properties");
|
|
|
|
// Check for pixel format
|
|
SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA32; // default
|
|
JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
|
|
if (!JS_IsNull(format_val)) {
|
|
format = js2pixelformat(js, format_val);
|
|
}
|
|
JS_FreeValue(js, format_val);
|
|
|
|
// Check for pixel data
|
|
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
|
|
if (!JS_IsNull(pixels_val)) {
|
|
// Create surface from pixel data
|
|
size_t len;
|
|
void *raw = js_get_blob_data(js, &len, pixels_val);
|
|
|
|
if (!raw) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowTypeError(js, "pixels property must be a stoned blob");
|
|
}
|
|
|
|
// Get pitch if provided, otherwise calculate it
|
|
int pitch;
|
|
JSValue pitch_val = JS_GetPropertyStr(js, argv[0], "pitch");
|
|
if (!JS_IsNull(pitch_val)) {
|
|
pitch = js2number(js, pitch_val);
|
|
JS_FreeValue(js, pitch_val);
|
|
} else {
|
|
// Calculate pitch based on format
|
|
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
|
|
pitch = width * bytes_per_pixel;
|
|
}
|
|
|
|
// Copy the pixel data
|
|
void *pixels_copy = malloc(len);
|
|
if (!pixels_copy) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
memcpy(pixels_copy, raw, len);
|
|
|
|
SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch);
|
|
if (!surface) {
|
|
free(pixels_copy);
|
|
JS_FreeValue(js, pixels_val);
|
|
return JS_ThrowInternalError(js, "Failed to create surface from pixels: %s", SDL_GetError());
|
|
}
|
|
|
|
JS_FreeValue(js, pixels_val);
|
|
ret = SDL_Surface2js(js, surface);
|
|
} else {
|
|
// Create blank surface
|
|
SDL_Surface *surface = SDL_CreateSurface(width, height, format);
|
|
if (!surface)
|
|
return JS_ThrowInternalError(js, "Failed to create surface: %s", SDL_GetError());
|
|
|
|
ret = SDL_Surface2js(js, surface);
|
|
}
|
|
)
|
|
|
|
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
|
|
MIST_FUNC_DEF(surface, blit, 4),
|
|
MIST_FUNC_DEF(surface, scale, 1),
|
|
MIST_FUNC_DEF(surface, fill,1),
|
|
MIST_FUNC_DEF(surface, rect,2),
|
|
MIST_FUNC_DEF(surface, dup, 0),
|
|
MIST_FUNC_DEF(surface, pixels, 0),
|
|
MIST_FUNC_DEF(surface, convert, 1),
|
|
MIST_FUNC_DEF(surface, toJSON, 0),
|
|
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
|
|
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
|
|
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
|
|
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
|
|
};
|
|
|
|
// Helper function to create SDL_Surface from image object
|
|
static SDL_Surface* image_to_surface(JSContext *js, JSValue img_obj)
|
|
{
|
|
// Get width and height
|
|
JSValue width_val = JS_GetPropertyStr(js, img_obj, "width");
|
|
JSValue height_val = JS_GetPropertyStr(js, img_obj, "height");
|
|
|
|
if (JS_IsNull(width_val) || JS_IsNull(height_val)) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return NULL;
|
|
}
|
|
|
|
int width, height;
|
|
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) {
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
return NULL;
|
|
}
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
|
|
// Get format
|
|
JSValue format_val = JS_GetPropertyStr(js, img_obj, "format");
|
|
SDL_PixelFormat format = js2pixelformat(js, format_val);
|
|
JS_FreeValue(js, format_val);
|
|
|
|
if (format == SDL_PIXELFORMAT_UNKNOWN)
|
|
format = SDL_PIXELFORMAT_RGBA32; // default
|
|
|
|
// Get pixels
|
|
JSValue pixels_val = JS_GetPropertyStr(js, img_obj, "pixels");
|
|
size_t pixel_len;
|
|
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val);
|
|
|
|
if (!pixel_data) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return NULL;
|
|
}
|
|
|
|
// Get pitch (optional)
|
|
int pitch;
|
|
JSValue pitch_val = JS_GetPropertyStr(js, img_obj, "pitch");
|
|
if (!JS_IsNull(pitch_val)) {
|
|
pitch = js2number(js, pitch_val);
|
|
JS_FreeValue(js, pitch_val);
|
|
} else {
|
|
pitch = width * SDL_BYTESPERPIXEL(format);
|
|
}
|
|
|
|
// Create a copy of pixel data since SDL_Surface will own it
|
|
void *pixels_copy = malloc(pixel_len);
|
|
if (!pixels_copy) {
|
|
JS_FreeValue(js, pixels_val);
|
|
return NULL;
|
|
}
|
|
memcpy(pixels_copy, pixel_data, pixel_len);
|
|
JS_FreeValue(js, pixels_val);
|
|
|
|
SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch);
|
|
if (!surface) {
|
|
free(pixels_copy);
|
|
return NULL;
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
// Helper function to convert SDL_Surface back to image object
|
|
static JSValue surface_to_image(JSContext *js, SDL_Surface *surf)
|
|
{
|
|
JSValue obj = JS_NewObject(js);
|
|
|
|
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w));
|
|
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h));
|
|
JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format));
|
|
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch));
|
|
|
|
// Lock surface if needed
|
|
int locked = 0;
|
|
if (SDL_MUSTLOCK(surf)) {
|
|
if (SDL_LockSurface(surf) < 0) {
|
|
JS_FreeValue(js, obj);
|
|
return JS_NULL;
|
|
}
|
|
locked = 1;
|
|
}
|
|
|
|
// Add pixels as stoned blob
|
|
size_t byte_size = surf->pitch * surf->h;
|
|
JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size);
|
|
JS_SetPropertyStr(js, obj, "pixels", pixels);
|
|
|
|
// Unlock if we locked
|
|
if (locked)
|
|
SDL_UnlockSurface(surf);
|
|
|
|
// Add depth and hdr properties for completeness
|
|
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(surf->format)));
|
|
JS_SetPropertyStr(js, obj, "hdr", JS_FALSE);
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Scale function for image objects
|
|
JSC_CCALL(surface_scale_img,
|
|
if (argc < 2)
|
|
return JS_ThrowTypeError(js, "scale requires image and options objects");
|
|
|
|
SDL_Surface *src = image_to_surface(js, argv[0]);
|
|
if (!src)
|
|
return JS_ThrowTypeError(js, "First argument must be a valid image object");
|
|
|
|
// Get width and height from options
|
|
JSValue width_val = JS_GetPropertyStr(js, argv[1], "width");
|
|
JSValue height_val = JS_GetPropertyStr(js, argv[1], "height");
|
|
|
|
int new_width = src->w, new_height = src->h;
|
|
if (!JS_IsNull(width_val))
|
|
JS_ToInt32(js, &new_width, width_val);
|
|
if (!JS_IsNull(height_val))
|
|
JS_ToInt32(js, &new_height, height_val);
|
|
|
|
JS_FreeValue(js, width_val);
|
|
JS_FreeValue(js, height_val);
|
|
|
|
// Get scale mode
|
|
JSValue mode_val = JS_GetPropertyStr(js, argv[1], "mode");
|
|
SDL_ScaleMode mode = js2SDL_ScaleMode(js, mode_val);
|
|
JS_FreeValue(js, mode_val);
|
|
|
|
SDL_Surface *dst = SDL_ScaleSurface(src, new_width, new_height, mode);
|
|
SDL_DestroySurface(src);
|
|
|
|
if (!dst)
|
|
return JS_ThrowInternalError(js, "Scale failed: %s", SDL_GetError());
|
|
|
|
JSValue result = surface_to_image(js, dst);
|
|
SDL_DestroySurface(dst);
|
|
return result;
|
|
)
|
|
|
|
// Fill function for image objects
|
|
JSC_CCALL(surface_fill_img,
|
|
if (argc < 2)
|
|
return JS_ThrowTypeError(js, "fill requires image and color");
|
|
|
|
SDL_Surface *surf = image_to_surface(js, argv[0]);
|
|
if (!surf)
|
|
return JS_ThrowTypeError(js, "First argument must be a valid image object");
|
|
|
|
colorf color = js2color(js, argv[1]);
|
|
rect r = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.w = surf->w,
|
|
.h = surf->h
|
|
};
|
|
|
|
SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255));
|
|
|
|
JSValue result = surface_to_image(js, surf);
|
|
SDL_DestroySurface(surf);
|
|
return result;
|
|
)
|
|
|
|
// Rect function for image objects
|
|
JSC_CCALL(surface_rect_img,
|
|
if (argc < 3)
|
|
return JS_ThrowTypeError(js, "rect requires image, rectangle, and color");
|
|
|
|
SDL_Surface *surf = image_to_surface(js, argv[0]);
|
|
if (!surf)
|
|
return JS_ThrowTypeError(js, "First argument must be a valid image object");
|
|
|
|
rect r = js2rect(js, argv[1]);
|
|
colorf color = js2color(js, argv[2]);
|
|
|
|
SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255));
|
|
|
|
JSValue result = surface_to_image(js, surf);
|
|
SDL_DestroySurface(surf);
|
|
return result;
|
|
)
|
|
|
|
// Blit function for image objects
|
|
JSC_CCALL(surface_blit_img,
|
|
if (argc < 2)
|
|
return JS_ThrowTypeError(js, "blit requires destination and source images");
|
|
|
|
SDL_Surface *dst = image_to_surface(js, argv[0]);
|
|
if (!dst)
|
|
return JS_ThrowTypeError(js, "First argument must be a valid destination image");
|
|
|
|
SDL_Surface *src = image_to_surface(js, argv[1]);
|
|
if (!src) {
|
|
SDL_DestroySurface(dst);
|
|
return JS_ThrowTypeError(js, "Second argument must be a valid source image");
|
|
}
|
|
|
|
irect dr = {0}, *pdr = NULL;
|
|
if (argc > 2 && !JS_IsNull(argv[2])) {
|
|
dr = js2irect(js, argv[2]);
|
|
pdr = &dr;
|
|
}
|
|
|
|
irect sr = {0}, *psr = NULL;
|
|
if (argc > 3 && !JS_IsNull(argv[3])) {
|
|
sr = js2irect(js, argv[3]);
|
|
psr = &sr;
|
|
}
|
|
|
|
SDL_ScaleMode mode = SDL_SCALEMODE_LINEAR;
|
|
if (argc > 4)
|
|
mode = js2SDL_ScaleMode(js, argv[4]);
|
|
|
|
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
|
|
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
|
|
|
|
SDL_DestroySurface(src);
|
|
JSValue result = surface_to_image(js, dst);
|
|
SDL_DestroySurface(dst);
|
|
return result;
|
|
)
|
|
|
|
// Duplicate function for image objects
|
|
JSC_CCALL(surface_dup_img,
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "dup requires an image object");
|
|
|
|
SDL_Surface *surf = image_to_surface(js, argv[0]);
|
|
if (!surf)
|
|
return JS_ThrowTypeError(js, "Argument must be a valid image object");
|
|
|
|
SDL_Surface *dup = SDL_DuplicateSurface(surf);
|
|
SDL_DestroySurface(surf);
|
|
|
|
if (!dup)
|
|
return JS_ThrowInternalError(js, "Duplicate failed: %s", SDL_GetError());
|
|
|
|
JSValue result = surface_to_image(js, dup);
|
|
SDL_DestroySurface(dup);
|
|
return result;
|
|
)
|
|
|
|
// Generic convert function for pixel format/colorspace conversion
|
|
JSC_CCALL(surface_convert_generic,
|
|
if (argc < 2)
|
|
return JS_ThrowTypeError(js, "convert requires source and conversion objects");
|
|
|
|
// Parse source object
|
|
int src_width, src_height;
|
|
JS_GETATOM(js, src_width, argv[0], width, number)
|
|
JS_GETATOM(js, src_height, argv[0], height, number)
|
|
|
|
if (!src_width || !src_height)
|
|
return JS_ThrowTypeError(js, "source object requires width and height");
|
|
|
|
// Get source format
|
|
JSValue src_format_val = JS_GetPropertyStr(js, argv[0], "format");
|
|
SDL_PixelFormat src_format = js2pixelformat(js, src_format_val);
|
|
JS_FreeValue(js, src_format_val);
|
|
|
|
if (src_format == SDL_PIXELFORMAT_UNKNOWN)
|
|
return JS_ThrowTypeError(js, "source object requires valid format");
|
|
|
|
// Get source pixels
|
|
JSValue src_pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
|
|
size_t src_len;
|
|
void *src_pixels = js_get_blob_data(js, &src_len, src_pixels_val);
|
|
|
|
if (!src_pixels) {
|
|
JS_FreeValue(js, src_pixels_val);
|
|
return JS_ThrowTypeError(js, "source pixels must be an ArrayBuffer");
|
|
}
|
|
|
|
// Get source pitch (optional, calculate if not provided)
|
|
int src_pitch;
|
|
JSValue src_pitch_val = JS_GetPropertyStr(js, argv[0], "pitch");
|
|
if (!JS_IsNull(src_pitch_val)) {
|
|
src_pitch = js2number(js, src_pitch_val);
|
|
JS_FreeValue(js, src_pitch_val);
|
|
} else {
|
|
src_pitch = src_width * SDL_BYTESPERPIXEL(src_format);
|
|
}
|
|
|
|
// Get source colorspace (optional)
|
|
JSValue src_colorspace_val = JS_GetPropertyStr(js, argv[0], "colorspace");
|
|
SDL_Colorspace src_colorspace = SDL_COLORSPACE_SRGB; // default
|
|
if (!JS_IsNull(src_colorspace_val)) {
|
|
// For now, we'll use a simple numeric value for colorspace
|
|
int colorspace_num;
|
|
if (JS_ToInt32(js, &colorspace_num, src_colorspace_val) == 0) {
|
|
src_colorspace = (SDL_Colorspace)colorspace_num;
|
|
}
|
|
}
|
|
JS_FreeValue(js, src_colorspace_val);
|
|
|
|
// Parse conversion object
|
|
JSValue dst_format_val = JS_GetPropertyStr(js, argv[1], "format");
|
|
SDL_PixelFormat dst_format = js2pixelformat(js, dst_format_val);
|
|
JS_FreeValue(js, dst_format_val);
|
|
|
|
if (dst_format == SDL_PIXELFORMAT_UNKNOWN)
|
|
return JS_ThrowTypeError(js, "conversion object requires valid format");
|
|
|
|
// Get destination pitch (optional)
|
|
int dst_pitch;
|
|
JSValue dst_pitch_val = JS_GetPropertyStr(js, argv[1], "pitch");
|
|
if (!JS_IsNull(dst_pitch_val)) {
|
|
dst_pitch = js2number(js, dst_pitch_val);
|
|
JS_FreeValue(js, dst_pitch_val);
|
|
} else {
|
|
dst_pitch = src_width * SDL_BYTESPERPIXEL(dst_format);
|
|
}
|
|
|
|
// Get destination colorspace (optional)
|
|
JSValue dst_colorspace_val = JS_GetPropertyStr(js, argv[1], "colorspace");
|
|
SDL_Colorspace dst_colorspace = SDL_COLORSPACE_SRGB; // default
|
|
if (!JS_IsNull(dst_colorspace_val)) {
|
|
int colorspace_num;
|
|
if (JS_ToInt32(js, &colorspace_num, dst_colorspace_val) == 0) {
|
|
dst_colorspace = (SDL_Colorspace)colorspace_num;
|
|
}
|
|
}
|
|
JS_FreeValue(js, dst_colorspace_val);
|
|
|
|
// Calculate destination buffer size
|
|
size_t dst_size = dst_pitch * src_height;
|
|
void *dst_pixels = malloc(dst_size);
|
|
if (!dst_pixels) {
|
|
JS_FreeValue(js, src_pixels_val);
|
|
return JS_ThrowOutOfMemory(js);
|
|
}
|
|
|
|
// Check if we have colorspace info for both source and dest
|
|
bool has_src_colorspace = !JS_IsNull(JS_GetPropertyStr(js, argv[0], "colorspace"));
|
|
bool has_dst_colorspace = !JS_IsNull(JS_GetPropertyStr(js, argv[1], "colorspace"));
|
|
|
|
bool success;
|
|
if (has_src_colorspace || has_dst_colorspace) {
|
|
// Use SDL_ConvertPixelsAndColorspace
|
|
success = SDL_ConvertPixelsAndColorspace(
|
|
src_width, src_height,
|
|
src_format, src_colorspace, 0, src_pixels, src_pitch,
|
|
dst_format, dst_colorspace, 0, dst_pixels, dst_pitch
|
|
);
|
|
} else {
|
|
// Use SDL_ConvertPixels
|
|
success = SDL_ConvertPixels(
|
|
src_width, src_height,
|
|
src_format, src_pixels, src_pitch,
|
|
dst_format, dst_pixels, dst_pitch
|
|
);
|
|
}
|
|
|
|
JS_FreeValue(js, src_pixels_val);
|
|
|
|
if (!success) {
|
|
free(dst_pixels);
|
|
return JS_ThrowInternalError(js, "Pixel conversion failed: %s", SDL_GetError());
|
|
}
|
|
|
|
// Create result image object
|
|
JSValue result = JS_NewObject(js);
|
|
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, src_width));
|
|
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, src_height));
|
|
JS_SetPropertyStr(js, result, "format", pixelformat2js(js, dst_format));
|
|
JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, dst_pitch));
|
|
|
|
JSValue pixels = js_new_blob_stoned_copy(js, dst_pixels, dst_size);
|
|
free(dst_pixels);
|
|
JS_SetPropertyStr(js, result, "pixels", pixels);
|
|
|
|
// Add depth and hdr for consistency
|
|
JS_SetPropertyStr(js, result, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(dst_format)));
|
|
JS_SetPropertyStr(js, result, "hdr", JS_FALSE);
|
|
|
|
return result;
|
|
)
|
|
|
|
JSValue js_sdl_surface_use(JSContext *js)
|
|
{
|
|
QJSCLASSPREP_FUNCS(SDL_Surface)
|
|
|
|
// Add the surface constructor
|
|
JSValue ctor = JS_NewCFunction2(js, js_surface_constructor, "surface", 1, JS_CFUNC_constructor, 0);
|
|
|
|
JSValue proto = JS_GetClassProto(js, js_SDL_Surface_id);
|
|
JS_SetConstructor(js, ctor, proto);
|
|
JS_FreeValue(js, proto);
|
|
|
|
// Add the generic convert function as a property on the constructor
|
|
JS_SetPropertyStr(js, ctor, "convert", JS_NewCFunction(js, js_surface_convert_generic, "convert", 2));
|
|
|
|
// Add the compression functions as static methods on the constructor
|
|
JS_SetPropertyStr(js, ctor, "compress_bc1", JS_NewCFunction(js, js_surface_compress_bc1, "compress_bc1", 2));
|
|
JS_SetPropertyStr(js, ctor, "compress_bc3", JS_NewCFunction(js, js_surface_compress_bc3, "compress_bc3", 2));
|
|
JS_SetPropertyStr(js, ctor, "compress_bc4", JS_NewCFunction(js, js_surface_compress_bc4, "compress_bc4", 1));
|
|
JS_SetPropertyStr(js, ctor, "compress_bc5", JS_NewCFunction(js, js_surface_compress_bc5, "compress_bc5", 1));
|
|
|
|
// Add standalone image manipulation functions
|
|
JS_SetPropertyStr(js, ctor, "scale", JS_NewCFunction(js, js_surface_scale_img, "scale", 2));
|
|
JS_SetPropertyStr(js, ctor, "fill", JS_NewCFunction(js, js_surface_fill_img, "fill", 2));
|
|
JS_SetPropertyStr(js, ctor, "rect", JS_NewCFunction(js, js_surface_rect_img, "rect", 3));
|
|
JS_SetPropertyStr(js, ctor, "blit", JS_NewCFunction(js, js_surface_blit_img, "blit", 5));
|
|
JS_SetPropertyStr(js, ctor, "dup", JS_NewCFunction(js, js_surface_dup_img, "dup", 1));
|
|
|
|
return ctor;
|
|
} |