move surface to its own module

This commit is contained in:
2025-05-26 15:59:28 -05:00
parent af21e10e97
commit a63e5c5b55
11 changed files with 642 additions and 421 deletions

View File

@@ -195,7 +195,7 @@ sources = []
src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','prosperon.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c'
]
# quirc src

View File

@@ -1,6 +1,8 @@
// SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations
var surface = use('surface')
// Default window configuration - documents all available window options
var default_window = {
// Basic properties
@@ -431,11 +433,11 @@ function handle_renderer(msg) {
}
// Load from raw surface object (for graphics module)
else if (msg.data.surface) {
tex = ren.load_texture(msg.data);
tex = ren.load_texture(new surface(msg.data));
}
// Direct surface data
else if (msg.data.width && msg.data.height) {
tex = ren.load_texture(msg.data);
tex = ren.load_texture(new surface(msg.data));
}
else {
return {error: "Must provide surface_id or surface data"};
@@ -446,25 +448,8 @@ function handle_renderer(msg) {
resources.texture[tex_id] = tex;
return {
id: tex_id,
width: tex.width,
height: tex.height
};
case 'createTexture':
if (!msg.data || !msg.data.width || !msg.data.height) {
return {error: "Missing width or height"};
}
var tex = ren.createTexture(
msg.data.format || 'rgba8888',
msg.data.access || 'static',
msg.data.width,
msg.data.height
);
if (!tex) return {error: "Failed to create texture"};
var tex_id = allocate_id();
resources.texture[tex_id] = tex;
return {id: tex_id, data: {size: tex.size}};
case 'flush':
ren.flush();
return {success: true};
@@ -539,6 +524,7 @@ function handle_texture(msg) {
});
}
else {
console.log(json.encode(msg.data))
return {error: "Must provide either surface_id or width/height"};
}

View File

@@ -34,6 +34,8 @@ var wota = hidden.wota
var console_mod = hidden.console
var use_embed = hidden.use_embed
var use_dyn = hidden.use_dyn
var enet = hidden.enet
var nota = hidden.nota
// Strip hidden from prosperon so nothing else can access it
delete prosperon.hidden
@@ -150,37 +152,21 @@ console.error = function error(e) {
if (e instanceof Error)
pprint(`${e.name} : ${e.message}
${e.stack}`, 4)
else
pprint(e,4)
else {
var stack = new Error()
pprint(`${e}
${stack.stack}`,4)
}
}
console.panic = function panic(e) {
pprint(e, 5)
os.quit()
}
console.assert = function assert(op, str = `assertion failed [value '${op}']`) {
if (!op) console.panic(str)
}
//os.on('uncaught_exception', function(e) { console.error(e); })
console[prosperon.DOC] = {
doc: "The console object provides various logging, debugging, and output methods.",
spam: "Output a spam-level message for very verbose logging.",
debug: "Output a debug-level message.",
info: "Output info level message.",
warn: "Output warn level message.",
error: "Output error level message, and print stacktrace.",
panic: "Output a panic-level message and exit the program.",
assert: "If the condition is false, print an error and panic.",
critical: "Output critical level message, and exit game immediately.",
write: "Write raw text to console.",
say: "Write raw text to console, plus a newline.",
log: "Output directly to in game console.",
level: "Set level to output logging to console.",
stack: "Output a stacktrace to console.",
clear: "Clear console."
}
var BASEPATH = 'scripts/core/base.js'
var script = io.slurp(BASEPATH)
var fnname = "base"
@@ -316,11 +302,9 @@ script = `(function ${fnname}() { ${script}; })`
}
*/
var enet = use('enet')
var util = use('util')
var math = use('math')
var crypto = use('crypto')
var nota = use('nota')
var dying = false

View File

@@ -505,10 +505,10 @@ draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], s
// Calculate source rectangle from image.rect (UV coords)
var src_rect = {
x: image.rect.x * texture.width,
y: image.rect.y * texture.height,
width: image.rect.width * texture.width,
height: image.rect.height * texture.height
x: image.rect.x * image.width,
y: image.rect.y * image.height,
width: image.rect.width * image.width,
height: image.rect.height * image.height
}
// Handle flipping

View File

@@ -21,9 +21,20 @@ var LOADING = Symbol()
var cache = new Map()
// When creating an image, do an Object.create(graphics.Image)
graphics.Image = {
get gpu() {
// Image constructor function
graphics.Image = function(surfaceData) {
// Initialize private properties
this[CPU] = surfaceData || undefined;
this[GPU] = undefined;
this[LOADING] = false;
this[LASTUSE] = os.now();
this.rect = {x:0, y:0, width:1, height:1};
}
// Define getters and methods on the prototype
Object.defineProperties(graphics.Image.prototype, {
gpu: {
get: function() {
this[LASTUSE] = os.now();
if (!this[GPU] && !this[LOADING]) {
this[LOADING] = true;
@@ -37,7 +48,7 @@ graphics.Image = {
data: this[CPU]
}, function(response) {
if (response.error) {
console.error("Failed to load texture:", response.error);
console.error("Failed to load texture:", response.error)
self[LOADING] = false;
} else {
self[GPU] = response;
@@ -48,38 +59,46 @@ graphics.Image = {
}
return this[GPU]
}
},
get texture() { return this.gpu },
texture: {
get: function() { return this.gpu }
},
get cpu() {
cpu: {
get: function() {
this[LASTUSE] = os.now();
// Note: Reading texture back from GPU requires async operation
// For now, return the CPU data if available
return this[CPU]
}
},
get surface() { return this.cpu },
get width() {
if (this[GPU]) return this[GPU].width
if (this[CPU]) return this[CPU].width
return 0
surface: {
get: function() { return this.cpu }
},
get height() {
if (this[GPU]) return this[GPU].height
if (this[CPU]) return this[CPU].height
return 0
width: {
get: function() {
return this[CPU].width
}
},
unload_gpu() {
height: {
get: function() {
return this[CPU].height
}
}
});
// Add methods to prototype
graphics.Image.prototype.unload_gpu = function() {
this[GPU] = undefined
},
}
unload_cpu() {
graphics.Image.prototype.unload_cpu = function() {
this[CPU] = undefined
},
}
function calc_image_size(img) {
@@ -105,13 +124,7 @@ function decorate_rect_px(img) {
function make_handle(obj)
{
return Object.assign(Object.create(graphics.Image), {
rect:{x:0,y:0,width:1,height:1},
[CPU]:obj,
[GPU]:undefined,
[LOADING]:false,
[LASTUSE]:os.now()
})
return new graphics.Image(obj);
}
function wrapSurface(surf, maybeRect){
@@ -210,10 +223,9 @@ graphics.texture_from_data = function(data)
{
if (!(data instanceof ArrayBuffer)) return undefined
var surface = graphics.make_texture(data);
var img = make_handle(surface);
var image = graphics.make_texture(data);
var img = make_handle(image)
// Trigger GPU load (async)
img.gpu;
return img;
@@ -234,7 +246,7 @@ graphics.from = function(id, data)
}
graphics.texture = function texture(path) {
if (path.__proto__ === graphics.Image) return path
if (path instanceof graphics.Image) return path
if (typeof path !== 'string')
throw new Error('need a string for graphics.texture')
@@ -243,7 +255,8 @@ graphics.texture = function texture(path) {
if (cache.has(id)) return cache.get(id)
var ipath = res.find_image(id)
if (!ipath) throw new Error(`unknown image ${id}`)
if (!ipath)
throw new Error(`unknown image ${id}`)
var image = create_image(ipath)
cache.set(id, image)
@@ -340,8 +353,6 @@ graphics.get_font = function get_font(path, size) {
console.error("Failed to load font texture:", response.error);
} else {
font.texture = response;
console.log('loaded font texture');
console.log(json.encode(font.texture));
}
});
@@ -418,12 +429,6 @@ graphics.cull_sprites[prosperon.DOC] = `
Filter an array of sprites to only those visible in the provided cameras view.
`
graphics.make_surface[prosperon.DOC] = `
:param dimensions: The size object {width, height}, or an array [w,h].
:return: A blank RGBA surface with the given dimensions, typically for software rendering or icons.
Create a blank surface in RAM.
`
graphics.make_cursor[prosperon.DOC] = `
:param opts: An object with {surface, hotx, hoty} or similar.
:return: An SDL_Cursor object referencing the given surface for a custom mouse cursor.

View File

@@ -48,6 +48,7 @@
#include "qjs_spline.h"
#include "qjs_js.h"
#include "qjs_debug.h"
#include "qjs_sdl_surface.h"
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
@@ -375,11 +376,6 @@ char *js2strdup(JSContext *js, JSValue v) {
#include "qjs_macros.h"
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
if (s->flags & SDL_SURFACE_PREALLOCATED)
free(s->pixels);
SDL_DestroySurface(s);
}
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
@@ -389,11 +385,6 @@ void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
QJSCLASS(font,)
QJSCLASS(datastream,)
QJSCLASS(SDL_Surface,
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
)
JSValue angle2js(JSContext *js,double g) {
return number2js(js,g*HMM_RadToTurn);
}
@@ -994,178 +985,6 @@ static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
return it->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;
}
/* -------------------------------------------------------------------------
* surface.blit(dstrect, src, srcrect, scalemode)
* -------------------------------------------------------------------------*/
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);
printf("src %p, dst %p, src rect %g,%g,%g,%g [%p], dst rect %g,%g,%g,%g [%p]\n", src, dst, sr.x, sr.y, sr.w, sr.h, psr, dr.x, dr.y, dr.w, dr.h, pdr);
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);
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
*/
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = surf->pitch*surf->h;
ret = JS_NewArrayBufferCopy(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);
)
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),
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),
};
JSC_CCALL(os_guid,
SDL_GUID guid;
randombytes(guid.data, 16);
@@ -1240,44 +1059,20 @@ JSC_CCALL(os_make_texture,
}
int pitch = width*4;
int fmt = SDL_PIXELFORMAT_RGBA32;
size_t pixels_size = pitch * height;
// Create JS object with surface data
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
JS_SetPropertyStr(js, obj, "pixels", JS_NewArrayBufferCopy(js, data, pixels_size));
SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,fmt, data, pitch);
if (!surf) {
free(data);
return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError());
}
ret = SDL_Surface2js(js,surf);
ret = obj;
)
JSC_CCALL(graphics_surface_from_pixels,
int w, h, pitch;
SDL_PixelFormat fmt;
JS_GETATOM(js, w, argv[0], width, number)
JS_GETATOM(js, h, argv[0], height, number)
JS_GETATOM(js, fmt, argv[0], format, pixelformat)
JS_GETATOM(js, pitch, argv[0], pitch, number)
JSValue js_px = JS_GetPropertyStr(js, argv[0], "buffer");
size_t len;
void *raw = JS_GetArrayBuffer(js, &len, js_px);
JS_FreeValue(js, js_px);
if (!raw || !w || !h || !fmt)
return JS_ThrowReferenceError(js, "Invalid source.");
void *newraw = malloc(len);
memcpy(newraw, raw, len);
SDL_Surface *s = SDL_CreateSurfaceFrom(w, h, fmt, newraw, pitch);
if (!s)
return JS_ThrowInternalError(js, "Unable to create surface: %s", SDL_GetError());
return SDL_Surface2js(js, s);
)
// input: (gif image data)
JSC_CCALL(os_make_gif,
@@ -1296,8 +1091,14 @@ JSC_CCALL(os_make_gif,
ret = gif;
if (frames == 1) {
// still image, so return just that
JS_SetPropertyStr(js, gif, "surface", SDL_Surface2js(js,SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32, pixels, width*4)));
// still image, so return surface data object
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, pixels, width*height*4));
JS_SetPropertyStr(js, gif, "surface", surfData);
return gif;
}
@@ -1306,19 +1107,18 @@ JSC_CCALL(os_make_gif,
for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)delays[i]/1000.0));
void *frame_pixels = malloc(width*height*4);
if (!frame_pixels) {
JS_FreeValue(js,gif);
ret = JS_ThrowOutOfMemory(js);
goto CLEANUP;
}
memcpy(frame_pixels, (unsigned char*)pixels+(width*height*4*i), width*height*4);
SDL_Surface *framesurf = SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32,frame_pixels, width*4);
if (!framesurf) {
ret = JS_ThrowReferenceError(js, "failed to create SDL_Surface: %s", SDL_GetError());
goto CLEANUP;
}
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,framesurf));
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
void *frame_pixels = (unsigned char*)pixels+(width*height*4*i);
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, frame_pixels, width*height*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyUint32(js, delay_arr, i, frame);
}
@@ -1332,10 +1132,16 @@ CLEANUP:
JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
{
JSValue frame = JS_NewObject(js);
void *frame_pixels = malloc(aframe.ase->w*aframe.ase->h*4);
memcpy(frame_pixels, aframe.pixels, aframe.ase->w*aframe.ase->h*4);
SDL_Surface *surf = SDL_CreateSurfaceFrom(aframe.ase->w, aframe.ase->h, SDL_PIXELFORMAT_RGBA32, frame_pixels, aframe.ase->w*4);
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,surf));
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, aframe.ase->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, aframe.ase->h));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, aframe.ase->w*4));
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, aframe.pixels, aframe.ase->w*aframe.ase->h*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
return frame;
}
@@ -1393,11 +1199,6 @@ JSC_CCALL(os_make_aseprite,
cute_aseprite_free(ase);
)
JSC_CCALL(os_make_surface,
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_Surface *surface = SDL_CreateSurface(wh.x, wh.y, SDL_PIXELFORMAT_RGBA32);
ret = SDL_Surface2js(js, surface);
)
// TODO: Implement this correctly
JSC_CCALL(os_make_cursor,
@@ -1418,7 +1219,31 @@ JSC_CCALL(os_make_font,
font *f = MakeFont(data, len, js2number(js,argv[1]));
if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js,f);
JS_SetPropertyStr(js, ret, "surface", SDL_Surface2js(js,f->surface));
// Create surface data object for the font's atlas
if (f->surface) {
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, f->surface->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, f->surface->h));
JS_SetPropertyStr(js, surfData, "format", pixelformat2js(js, f->surface->format));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, f->surface->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(f->surface)) {
if (SDL_LockSurface(f->surface) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = f->surface->pitch * f->surface->h;
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, f->surface->pixels, byte_size));
if (locked)
SDL_UnlockSurface(f->surface);
JS_SetPropertyStr(js, ret, "surface", surfData);
}
)
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
@@ -1478,9 +1303,17 @@ static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
uint8_t *rgb = malloc(frame->height*frame->width*4);
memset(rgb,255,frame->height*frame->width*4);
plm_frame_to_rgba(frame, rgb, frame->width*4);
SDL_Surface *surf = SDL_CreateSurfaceFrom(frame->width,frame->height, SDL_PIXELFORMAT_RGBA32, rgb, frame->width*4);
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(ds->js);
JS_SetPropertyStr(ds->js, surfData, "width", JS_NewInt32(ds->js, frame->width));
JS_SetPropertyStr(ds->js, surfData, "height", JS_NewInt32(ds->js, frame->height));
JS_SetPropertyStr(ds->js, surfData, "format", JS_NewString(ds->js, "rgba32"));
JS_SetPropertyStr(ds->js, surfData, "pitch", JS_NewInt32(ds->js, frame->width*4));
JS_SetPropertyStr(ds->js, surfData, "pixels", JS_NewArrayBufferCopy(ds->js, rgb, frame->height*frame->width*4));
JSValue s[1];
s[0] = SDL_Surface2js(ds->js,surf);
s[0] = surfData;
JSValue cb = JS_DupValue(ds->js,ds->callback);
JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s);
JS_FreeValue(ds->js,cb);
@@ -1668,14 +1501,12 @@ static const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, cull_sprites, 2),
MIST_FUNC_DEF(os, make_surface, 1),
MIST_FUNC_DEF(os, make_cursor, 1),
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
MIST_FUNC_DEF(graphics, save_png, 4),
MIST_FUNC_DEF(graphics, save_jpg, 4),
MIST_FUNC_DEF(graphics, surface_from_pixels, 1),
};
static const JSCFunctionListEntry js_video_funcs[] = {
@@ -1764,35 +1595,40 @@ void ffi_load(JSContext *js)
m_seedRand(&rt->mrand, time(NULL));
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
// actor module moved to hidden_fn
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
// cell modules
arrput(rt->module_registry, MISTLINE(time));
arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use}));
arrput(rt->module_registry, MISTLINE(blob));
// extra
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(miniz));
// power user
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(util));
// prosperon
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use}));
arrput(rt->module_registry, ((ModuleEntry){"surface", js_sdl_surface_use}));
arrput(rt->module_registry, MISTLINE(spline));
arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use}));
arrput(rt->module_registry, MISTLINE(graphics));
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(util));
arrput(rt->module_registry, MISTLINE(video));
arrput(rt->module_registry, MISTLINE(soloud));
arrput(rt->module_registry, MISTLINE(layout));
arrput(rt->module_registry, MISTLINE(miniz));
// arrput(rt->module_registry, MISTLINE(imgui));
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(nota));
arrput(rt->module_registry, MISTLINE(enet));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(blob));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use}));
// console module moved to hidden_fn
arrput(rt->module_registry, MISTLINE(rtree));
arrput(rt->module_registry, MISTLINE(sprite));
arrput(rt->module_registry, MISTLINE(transform));
@@ -1812,7 +1648,6 @@ void ffi_load(JSContext *js)
JSValue c_types = JS_NewObject(js);
JS_SetPropertyStr(js,prosp, "c_types", c_types);
QJSCLASSPREP_FUNCS(SDL_Surface)
QJSCLASSPREP_FUNCS(font);
QJSCLASSPREP_FUNCS(datastream);
@@ -1849,6 +1684,8 @@ void ffi_load(JSContext *js)
JS_SetPropertyStr(js, hidden_fn, "actor", js_actor_use(js));
JS_SetPropertyStr(js, hidden_fn, "wota", js_wota_use(js));
JS_SetPropertyStr(js, hidden_fn, "console", js_console_use(js));
JS_SetPropertyStr(js, hidden_fn, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_fn, "enet", js_enet_use(js));
// Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));

364
source/qjs_sdl_surface.c Normal file
View File

@@ -0,0 +1,364 @@
#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 <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);
// Type conversion functions
typedef struct { const char *name; SDL_PixelFormat fmt; } fmt_entry;
static const fmt_entry k_fmt_table[] = {
{ "rgba32", SDL_PIXELFORMAT_RGBA32 },
{ "argb32", SDL_PIXELFORMAT_ARGB32 },
{ "bgra32", SDL_PIXELFORMAT_BGRA32 },
{ "abgr32", SDL_PIXELFORMAT_ABGR32 },
{ "rgb565", SDL_PIXELFORMAT_RGB565 },
{ "bgr565", SDL_PIXELFORMAT_BGR565 },
{ "rgb24", SDL_PIXELFORMAT_RGB24 },
{ "bgr24", SDL_PIXELFORMAT_BGR24 },
{ "rgb332", SDL_PIXELFORMAT_RGB332 },
{ "rgba64", SDL_PIXELFORMAT_RGBA64 },
{ "rgb48", SDL_PIXELFORMAT_RGB48 },
{ NULL, SDL_PIXELFORMAT_UNKNOWN }
};
static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
{
fmt_entry *it;
for (it = (fmt_entry*)k_fmt_table; it->name; it++)
if (it->fmt == fmt)
break;
if (it->name)
return JS_NewString(js, it->name);
return JS_UNDEFINED;
}
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;
fmt_entry *it;
for (it = (fmt_entry*)k_fmt_table; it->name; it++)
if (!strcmp(it->name, s))
break;
JS_FreeCString(js,s);
return it->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);
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
*/
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = surf->pitch*surf->h;
ret = JS_NewArrayBufferCopy(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_NewArrayBufferCopy(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_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;
}

16
source/qjs_sdl_surface.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef QJS_SDL_SURFACE_H
#define QJS_SDL_SURFACE_H
#include <quickjs.h>
#include <SDL3/SDL_surface.h>
JSValue js_sdl_surface_use(JSContext *js);
// Functions generated by QJSCLASS macro
JSValue SDL_Surface2js(JSContext *js, SDL_Surface *s);
SDL_Surface *js2SDL_Surface(JSContext *js, JSValue val);
// Free function for SDL_Surface
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s);
#endif

View File

@@ -1,6 +1,7 @@
#include "qjs_sdl_video.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "qjs_sdl_surface.h"
#include "prosperon.h"
#include "sprite.h"
#include "transform.h"
@@ -52,8 +53,6 @@ extern double js2number(JSContext *js, JSValue v);
extern JSValue number2js(JSContext *js, double n);
extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v);
extern JSValue SDL_Texture2js(JSContext *js, SDL_Texture *t);
extern SDL_Surface *js2SDL_Surface(JSContext *js, JSValue v);
extern JSValue SDL_Surface2js(JSContext *js, SDL_Surface *s);
extern SDL_Window *js2SDL_Window(JSContext *js, JSValue v);
extern JSValue SDL_Window2js(JSContext *js, SDL_Window *w);
extern SDL_Renderer *js2SDL_Renderer(JSContext *js, JSValue v);
@@ -1802,8 +1801,6 @@ JSValue js_sdl_video_use(JSContext *js) {
char id[64];
snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks());
printf("id is %s\n", id);
// Prepare argv for create_actor
// We need to create the actor on the main thread
const char *argv[] = {

View File

@@ -3,8 +3,6 @@ var draw2d
var graphics
var os = use('os');
use('tracy').level = 1
// Create SDL video actor
var video = use('sdl_video');
var video_actor = {__ACTORDATA__:{id:video}};
@@ -61,9 +59,9 @@ function start_drawing() {
var bunny_image = null;
try {
bunny_image = graphics.texture('tests/bunny.png');
console.log("Loaded bunny image");
} catch (e) {
console.error("Failed to load bunny image:", e);
console.log("Failed to load bunny image:", e);
console.log(e)
}
function draw_frame() {
@@ -186,15 +184,13 @@ function start_drawing() {
);
}
// Draw the bunny image if loaded
if (bunny_image) {
// Static bunny
draw2d.image(bunny_image, {x: 500, y: 450, width: 64, height: 64});
var img = "tests/bunny.png"
draw2d.image(img, {x: 500, y: 450, width: 64, height: 64});
// Rotating bunny
var rotation = t * 0.5;
draw2d.image(
bunny_image,
img,
{x: 600, y: 450, width: 64, height: 64},
rotation,
[0.5, 0.5] // Center anchor
@@ -203,14 +199,13 @@ function start_drawing() {
// Bouncing bunny with tint
var bounce_y = 500 + Math.sin(t * 3) * 20;
draw2d.image(
bunny_image,
img,
{x: 700, y: bounce_y, width: 48, height: 48},
0,
[0.5, 1], // Bottom center anchor
[0, 0], // No shear
{color: [1, 0.5, 0.5, 1]} // Red tint
);
}
// Flush all commands to renderer
draw2d.flush();

37
tests/surface.js Normal file
View File

@@ -0,0 +1,37 @@
// Test SDL_Surface module
var Surface = use('surface');
// Test creating a surface
var surf = new Surface({width: 100, height: 100});
console.log("Created surface:", surf.width, "x", surf.height);
console.log(json.encode(surf))
// Test fill
surf.fill([1, 0, 0, 1]); // Red
// Test dup
var surf2 = surf.dup();
console.log("Duplicated surface:", surf2.width, "x", surf2.height);
// Test scale
var surf3 = surf.scale([50, 50], "linear");
console.log("Scaled surface:", surf3.width, "x", surf3.height);
// Test format
console.log("Surface format:", surf.format);
// Test pixels
var pixels = surf.pixels();
console.log("Got pixels array buffer, length:", pixels.byteLength);
// Test creating surface with custom format
var surf4 = new Surface({width: 64, height: 64, format: "rgb24"});
console.log("Created RGB24 surface:", surf4.width, "x", surf4.height, "format:", surf4.format);
// Test creating surface from pixels
var pixelData = new ArrayBuffer(32 * 32 * 4); // 32x32 RGBA
var surf5 = new Surface({width: 32, height: 32, pixels: pixelData});
console.log("Created surface from pixels:", surf5.width, "x", surf5.height);
console.log("Surface module test passed!");