335 lines
10 KiB
C
335 lines
10 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>
|
|
|
|
// Helper functions from jsffi.c that need to be declared
|
|
extern JSValue number2js(JSContext *js, double g);
|
|
extern double js2number(JSContext *js, JSValue v);
|
|
extern rect js2rect(JSContext *js, JSValue v);
|
|
extern irect js2irect(JSContext *js, JSValue v);
|
|
extern colorf js2color(JSContext *js, JSValue v);
|
|
extern HMM_Vec2 js2vec2(JSContext *js, JSValue v);
|
|
extern SDL_PixelFormat str2pixelformat(const char *str);
|
|
extern const char *pixelformat2str(SDL_PixelFormat fmt);
|
|
|
|
JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
|
|
{
|
|
const char *str = pixelformat2str(fmt);
|
|
return JS_NewString(js, str);
|
|
}
|
|
|
|
SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
|
|
{
|
|
if (JS_IsUndefined(v)) return SDL_PIXELFORMAT_UNKNOWN;
|
|
const char *s = JS_ToCString(js, v);
|
|
if (!s) return SDL_PIXELFORMAT_UNKNOWN;
|
|
|
|
SDL_PixelFormat fmt = str2pixelformat(s);
|
|
JS_FreeCString(js,s);
|
|
return fmt;
|
|
}
|
|
|
|
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_IsUndefined(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_IsUndefined(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_IsUndefined(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;
|
|
)
|
|
|
|
// 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_IsUndefined(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_IsUndefined(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 an ArrayBuffer");
|
|
}
|
|
|
|
// Get pitch if provided, otherwise calculate it
|
|
int pitch;
|
|
JSValue pitch_val = JS_GetPropertyStr(js, argv[0], "pitch");
|
|
if (!JS_IsUndefined(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),
|
|
};
|
|
|
|
JSValue js_sdl_surface_use(JSContext *js)
|
|
{
|
|
QJSCLASSPREP_FUNCS(SDL_Surface)
|
|
|
|
// Return a constructor function that creates SDL_Surface objects
|
|
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);
|
|
|
|
return ctor;
|
|
} |