#include "qjs_sdl_surface.h" #include "qjs_macros.h" #include "jsffi.h" #include #include #include #include // 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); static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt) { const char *str = pixelformat2str(fmt); return JS_NewString(js, str); } static 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); ) void *surface_pixel_dup(SDL_Surface *surf, size_t *size) { int locked = 0; if (SDL_MUSTLOCK(surf)) if (SDL_LockSurface(surf) < 0) return NULL; if (surf->format == SDL_PIXELFORMAT_NV12) { *size = surf->pitch * (surf->h*3.0/2.0); } else *size = (size_t)surf->pitch * surf->h; void *data = malloc(*size); memcpy(data, surf->pixels, *size); if (locked) SDL_UnlockSurface(surf); return data; } JSC_CCALL(surface_pixels, SDL_Surface *surf = js2SDL_Surface(js, self); size_t size; void *data = surface_pixel_dup(surf, &size); return JS_NewArrayBufferCopy(js, data, size); ) 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)); size_t size; void *pixels = surface_pixel_dup(surf, &size); JS_SetPropertyStr(js, obj, "pixels", JS_NewArrayBufferCopy(js, pixels, size)); 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_GetArrayBuffer(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; }