Files
cell/source/qjs_sdl_surface.c
John Alanbrook 1040c61863
Some checks failed
Build and Deploy / build-macos (push) Failing after 8s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
fix blob usage errors
2025-05-28 23:56:18 -05:00

336 lines
10 KiB
C

#include "qjs_sdl_surface.h"
#include "qjs_macros.h"
#include "jsffi.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_surface.h>
#include "prosperon.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;
}