add colorspace support; fix webcam
This commit is contained in:
@@ -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);
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
72
tests/camera_colorspace.js
Normal file
72
tests/camera_colorspace.js
Normal 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();
|
||||
106
tests/camera_colorspace_convert.js
Normal file
106
tests/camera_colorspace_convert.js
Normal 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);
|
||||
63
tests/surface_colorspace.js
Normal file
63
tests/surface_colorspace.js
Normal 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();
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user