add colorspace support; fix webcam

This commit is contained in:
2025-05-27 01:00:55 -05:00
parent 00df0899fa
commit e8fb50659d
6 changed files with 332 additions and 27 deletions

View File

@@ -298,11 +298,46 @@ SDL_PixelFormat str2pixelformat(const char *str) {
return SDL_PIXELFORMAT_UNKNOWN;
}
const char *colorspace2str(SDL_Colorspace colorspace) {
switch(colorspace) {
case SDL_COLORSPACE_UNKNOWN: return "unknown";
case SDL_COLORSPACE_SRGB: return "srgb";
case SDL_COLORSPACE_SRGB_LINEAR: return "srgb_linear";
case SDL_COLORSPACE_HDR10: return "hdr10";
case SDL_COLORSPACE_JPEG: return "jpeg";
case SDL_COLORSPACE_BT601_LIMITED: return "bt601_limited";
case SDL_COLORSPACE_BT601_FULL: return "bt601_full";
case SDL_COLORSPACE_BT709_LIMITED: return "bt709_limited";
case SDL_COLORSPACE_BT709_FULL: return "bt709_full";
case SDL_COLORSPACE_BT2020_LIMITED: return "bt2020_limited";
case SDL_COLORSPACE_BT2020_FULL: return "bt2020_full";
default: return "unknown";
}
}
SDL_Colorspace str2colorspace(const char *str) {
if (!str) return SDL_COLORSPACE_UNKNOWN;
if (!strcmp(str, "unknown")) return SDL_COLORSPACE_UNKNOWN;
if (!strcmp(str, "srgb")) return SDL_COLORSPACE_SRGB;
if (!strcmp(str, "srgb_linear")) return SDL_COLORSPACE_SRGB_LINEAR;
if (!strcmp(str, "hdr10")) return SDL_COLORSPACE_HDR10;
if (!strcmp(str, "jpeg")) return SDL_COLORSPACE_JPEG;
if (!strcmp(str, "bt601_limited")) return SDL_COLORSPACE_BT601_LIMITED;
if (!strcmp(str, "bt601_full")) return SDL_COLORSPACE_BT601_FULL;
if (!strcmp(str, "bt709_limited")) return SDL_COLORSPACE_BT709_LIMITED;
if (!strcmp(str, "bt709_full")) return SDL_COLORSPACE_BT709_FULL;
if (!strcmp(str, "bt2020_limited")) return SDL_COLORSPACE_BT2020_LIMITED;
if (!strcmp(str, "bt2020_full")) return SDL_COLORSPACE_BT2020_FULL;
return SDL_COLORSPACE_UNKNOWN;
}
static JSValue cameraspec2js(JSContext *js, const SDL_CameraSpec *spec) {
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, pixelformat2str(spec->format)));
JS_SetPropertyStr(js, obj, "colorspace", JS_NewInt32(js, spec->colorspace));
JS_SetPropertyStr(js, obj, "colorspace", JS_NewString(js, colorspace2str(spec->colorspace)));
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, spec->width));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, spec->height));
JS_SetPropertyStr(js, obj, "framerate_numerator", JS_NewInt32(js, spec->framerate_numerator));
@@ -325,7 +360,11 @@ static SDL_CameraSpec js2cameraspec(JSContext *js, JSValue obj) {
JS_FreeValue(js, v);
v = JS_GetPropertyStr(js, obj, "colorspace");
if (!JS_IsUndefined(v)) JS_ToInt32(js, &spec.colorspace, v);
if (!JS_IsUndefined(v)) {
const char *s = JS_ToCString(js, v);
spec.colorspace = str2colorspace(s);
JS_FreeCString(js, s);
}
JS_FreeValue(js, v);
v = JS_GetPropertyStr(js, obj, "width");
@@ -438,24 +477,10 @@ JSC_CCALL(camera_capture,
}
// Create a copy of the surface
SDL_Surface *newsurf = SDL_CreateSurface(surf->w, surf->h, surf->format);
SDL_Surface *newsurf = SDL_DuplicateSurface(surf);
if (!newsurf) {
SDL_ReleaseCameraFrame(cam, surf);
return JS_ThrowReferenceError(js, "Could not create surface: %s", SDL_GetError());
}
// Copy the surface data
int result = SDL_BlitSurface(surf, NULL, newsurf, NULL);
// Release the camera frame
SDL_ReleaseCameraFrame(cam, surf);
if (result != 0) {
SDL_DestroySurface(newsurf);
return JS_ThrowReferenceError(js, "Could not blit surface: %s", SDL_GetError());
}
return SDL_Surface2js(js,newsurf);
)

View File

@@ -15,6 +15,8 @@ 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);
extern SDL_Colorspace str2colorspace(const char *str);
extern const char *colorspace2str(SDL_Colorspace colorspace);
static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
{
@@ -33,6 +35,17 @@ static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
return fmt;
}
static SDL_Colorspace js2colorspace(JSContext *js, JSValue v)
{
if (JS_IsUndefined(v)) return SDL_COLORSPACE_UNKNOWN;
const char *s = JS_ToCString(js, v);
if (!s) return SDL_COLORSPACE_UNKNOWN;
SDL_Colorspace cs = str2colorspace(s);
JS_FreeCString(js,s);
return cs;
}
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
static const scale_entry k_scale_table[] = {
@@ -139,7 +152,18 @@ JSC_CCALL(surface_rect,
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);
SDL_Surface *dst;
if (argc > 1 && !JS_IsUndefined(argv[1])) {
// Colorspace provided, use SDL_ConvertSurfaceAndColorspace
SDL_Colorspace colorspace = js2colorspace(js, argv[1]);
SDL_PropertiesID props = 0; // No additional properties needed
dst = SDL_ConvertSurfaceAndColorspace(surf, fmt, NULL, colorspace, props);
} else {
// No colorspace, use regular convert
dst = SDL_ConvertSurface(surf, fmt);
}
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
return SDL_Surface2js(js, dst);
@@ -304,7 +328,7 @@ static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
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, convert, 2),
MIST_FUNC_DEF(surface, toJSON, 0),
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
JS_CGETSET_DEF("height", js_surface_get_height, NULL),

View File

@@ -0,0 +1,72 @@
// Test camera colorspace functionality
var camera = use('camera');
var json = use('json');
// Get list of cameras
var cameras = camera.list();
if (cameras.length === 0) {
console.log("No cameras found!");
$_. stop();
}
var cam_id = cameras[0];
console.log("Testing camera:", camera.name(cam_id));
// Get supported formats
var formats = camera.supported_formats(cam_id);
console.log("\nLooking for different colorspaces in supported formats...");
// Group formats by colorspace
var colorspaces = {};
for (var i = 0; i < formats.length; i++) {
var fmt = formats[i];
if (!colorspaces[fmt.colorspace]) {
colorspaces[fmt.colorspace] = [];
}
colorspaces[fmt.colorspace].push(fmt);
}
console.log("\nFound colorspaces:");
for (var cs in colorspaces) {
console.log(" " + cs + ": " + colorspaces[cs].length + " formats");
}
// Try opening camera with different colorspaces
console.log("\nTrying to open camera with different colorspaces...");
for (var cs in colorspaces) {
// Get first format for this colorspace
var format = colorspaces[cs][0];
console.log("\nTrying colorspace '" + cs + "' with format:");
console.log(" Resolution: " + format.width + "x" + format.height);
console.log(" Pixel format: " + format.format);
// You can also create a custom format with a specific colorspace
var custom_format = {
format: format.format,
colorspace: cs, // This will be converted from string
width: format.width,
height: format.height,
framerate_numerator: format.framerate_numerator,
framerate_denominator: format.framerate_denominator
};
var cam = camera.open(cam_id, custom_format);
if (cam) {
var actual = cam.get_format();
console.log(" Opened successfully!");
console.log(" Actual colorspace: " + actual.colorspace);
// Camera will be closed when object is freed
cam = null;
} else {
console.log(" Failed to open with this colorspace");
}
// Just test first 3 colorspaces
if (Object.keys(colorspaces).indexOf(cs) >= 2) break;
}
console.log("\nColorspace test complete!");
$_.stop();

View File

@@ -0,0 +1,106 @@
// Test camera capture with colorspace conversion
var camera = use('camera');
var surface = use('surface');
var json = use('json');
// Get first camera
var cameras = camera.list();
if (cameras.length === 0) {
console.log("No cameras found!");
$_.stop();
}
var cam_id = cameras[0];
console.log("Using camera:", camera.name(cam_id));
// Open camera with default settings
var cam = camera.open(cam_id);
if (!cam) {
console.log("Failed to open camera!");
$_.stop();
}
// Get the format being used
var format = cam.get_format();
console.log("\nCamera format:");
console.log(" Resolution:", format.width + "x" + format.height);
console.log(" Pixel format:", format.format);
console.log(" Colorspace:", format.colorspace);
// Handle camera approval
var approved = false;
$_.receiver(e => {
if (e.type === 'camera_device_approved') {
console.log("\nCamera approved!");
approved = true;
} else if (e.type === 'camera_device_denied') {
console.error("Camera access denied!");
$_.stop();
}
});
// Wait for approval then capture
function capture_test() {
if (!approved) {
$_.delay(capture_test, 0.1);
return;
}
console.log("\nCapturing frame...");
var surf = cam.capture();
if (!surf) {
console.log("No frame captured yet, retrying...");
$_.delay(capture_test, 0.1);
return;
}
console.log("\nCaptured surface:");
console.log(" Size:", surf.width + "x" + surf.height);
console.log(" Format:", surf.format);
// Test various colorspace conversions
console.log("\nTesting colorspace conversions:");
// Convert to sRGB if not already
if (format.colorspace !== "srgb") {
try {
var srgb_surf = surf.convert(surf.format, "srgb");
console.log(" Converted to sRGB colorspace");
} catch(e) {
console.log(" sRGB conversion failed:", e.message);
}
}
// Convert to linear sRGB for processing
try {
var linear_surf = surf.convert("rgba8888", "srgb_linear");
console.log(" Converted to linear sRGB (RGBA8888) for processing");
} catch(e) {
console.log(" Linear sRGB conversion failed:", e.message);
}
// Convert to JPEG colorspace (common for compression)
try {
var jpeg_surf = surf.convert("rgb888", "jpeg");
console.log(" Converted to JPEG colorspace (RGB888) for compression");
} catch(e) {
console.log(" JPEG colorspace conversion failed:", e.message);
}
// If YUV format, try BT.709 (HD video standard)
if (surf.format.indexOf("yuv") !== -1 || surf.format.indexOf("yuy") !== -1) {
try {
var hd_surf = surf.convert(surf.format, "bt709_limited");
console.log(" Converted to BT.709 limited (HD video standard)");
} catch(e) {
console.log(" BT.709 conversion failed:", e.message);
}
}
console.log("\nTest complete!");
$_.stop();
}
// Start capture test after a short delay
$_.delay(capture_test, 0.5);

View File

@@ -0,0 +1,63 @@
// Test surface colorspace conversion
var surface = use('surface');
var json = use('json');
// Create a test surface
var surf = surface({
width: 640,
height: 480,
format: "rgb888"
});
console.log("Created surface:");
console.log(" Size:", surf.width + "x" + surf.height);
console.log(" Format:", surf.format);
// Fill with a test color
surf.fill([1, 0.5, 0.25, 1]); // Orange color
// Test 1: Convert format only (no colorspace change)
console.log("\nTest 1: Convert to RGBA8888 format only");
var converted1 = surf.convert("rgba8888");
console.log(" New format:", converted1.format);
// Test 2: Convert format and colorspace
console.log("\nTest 2: Convert to YUY2 format with JPEG colorspace");
var converted2 = surf.convert("yuy2", "jpeg");
console.log(" New format:", converted2.format);
// Test 3: Try different colorspaces
var colorspaces = ["srgb", "srgb_linear", "jpeg", "bt601_limited", "bt709_limited"];
var test_format = "rgba8888";
console.log("\nTest 3: Converting to", test_format, "with different colorspaces:");
for (var i = 0; i < colorspaces.length; i++) {
try {
var conv = surf.convert(test_format, colorspaces[i]);
console.log(" " + colorspaces[i] + ": Success");
} catch(e) {
console.log(" " + colorspaces[i] + ": Failed -", e.message);
}
}
// Test 4: YUV formats with appropriate colorspaces
console.log("\nTest 4: YUV format conversions:");
var yuv_tests = [
{format: "yuy2", colorspace: "jpeg"},
{format: "nv12", colorspace: "bt601_limited"},
{format: "nv21", colorspace: "bt709_limited"},
{format: "yvyu", colorspace: "bt601_full"}
];
for (var i = 0; i < yuv_tests.length; i++) {
var test = yuv_tests[i];
try {
var conv = surf.convert(test.format, test.colorspace);
console.log(" " + test.format + " with " + test.colorspace + ": Success");
} catch(e) {
console.log(" " + test.format + " with " + test.colorspace + ": Failed -", e.message);
}
}
console.log("\nColorspace conversion test complete!");
$_.stop();

View File

@@ -86,12 +86,15 @@ send(video_actor, {
var formats = camera.supported_formats(cam_id);
console.log("Camera supports", formats.length, "formats");
// Look for a 640x480 format, or fall back to first format
// Look for a 640x480 format with preferred colorspace
var preferred_format = null;
for (var i = 0; i < formats.length; i++) {
if (formats[i].width === 640 && formats[i].height === 480) {
preferred_format = formats[i];
break;
// Prefer JPEG or sRGB colorspace if available
if (formats[i].colorspace === "jpeg" || formats[i].colorspace === "srgb") {
break;
}
}
}
@@ -100,13 +103,15 @@ send(video_actor, {
preferred_format = formats[0];
}
preferred_format.framerate_numerator = 30
if (preferred_format) {
console.log("Using format:", preferred_format.width + "x" + preferred_format.height,
"FPS:", preferred_format.framerate_numerator + "/" + preferred_format.framerate_denominator,
"Format:", preferred_format.format);
"Format:", preferred_format.format,
"Colorspace:", preferred_format.colorspace);
cam_obj = camera.open(cam_id, preferred_format);
} else {
console.log("Using default format");
cam_obj = camera.open(cam_id);
}
@@ -123,6 +128,7 @@ send(video_actor, {
console.log("Actual camera format:");
console.log(" Resolution:", actual_format.width + "x" + actual_format.height);
console.log(" Format:", actual_format.format);
console.log(" Colorspace:", actual_format.colorspace);
console.log(" FPS:", actual_format.framerate_numerator + "/" + actual_format.framerate_denominator);
// Start capturing after a short delay to wait for approval
@@ -130,6 +136,8 @@ send(video_actor, {
});
});
var captured = false
function start_capturing() {
if (!cam_approved) {
console.log("Waiting for camera approval...");
@@ -163,7 +171,7 @@ function start_capturing() {
draw2d.clear();
// Capture frame from camera
var surface = cam_obj.capture().convert("rgba8888");
var surface = cam_obj.capture()
if (surface) {
// Create texture from surface directly
@@ -195,12 +203,12 @@ function start_capturing() {
op: "copyTexture",
data: {
texture_id: webcam_texture,
dest: {x: 50, y: 50, width: 700, height: 500}
dest: {x: 50, y: 50, width: 640, height: 480}
}
});
} else {
// Draw placeholder text while waiting for first frame
draw2d.text("Waiting for webcam...", {x: 200, y: 250, size: 20});
// draw2d.text("Waiting for webcam...", {x: 200, y: 250, size: 20});
}
// Draw info
@@ -244,5 +252,12 @@ function start_capturing() {
capture_and_draw();
}
$_.delay(_ => {
// Capture frame from camera
var surface = cam_obj.capture().convert("rgba8888", "srgb")
console.log('capturing!')
graphics.save_png("test.png", surface.width, surface.height, surface.pixels(),surface.pitch)
}, 3)
// Stop after 12 seconds if not already stopped
$_.delay($_.stop, 12);1
$_.delay($_.stop, 12);