SDL: Allow specifying only some SDL_CameraSpec fields when opening a camera

From d4dc61355956f49a76f8d245270cd6ac720aa892 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 2 May 2024 12:25:39 -0700
Subject: [PATCH] Allow specifying only some SDL_CameraSpec fields when opening
 a camera

This allows setting the size without format, or FPS without size, etc.
---
 src/camera/SDL_camera.c | 77 ++++++++++++++++++++++-------------------
 1 file changed, 41 insertions(+), 36 deletions(-)

diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c
index 97034bb2b44a6..a9d19fc62030e 100644
--- a/src/camera/SDL_camera.c
+++ b/src/camera/SDL_camera.c
@@ -948,34 +948,38 @@ static void ChooseBestCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec
         int wantw = spec->width;
         int wanth = spec->height;
 
-        // Find the sizes with the closest aspect ratio and then find the best fit of those.
-        const float wantaspect = ((float)wantw) / ((float) wanth);
-        const float epsilon = 1e-6f;
-        float closestaspect = -9999999.0f;
-        float closestdiff = 999999.0f;
-        int closestdiffw = 9999999;
-
-        for (int i = 0; i < num_specs; i++) {
-            const SDL_CameraSpec *thisspec = &device->all_specs[i];
-            const int thisw = thisspec->width;
-            const int thish = thisspec->height;
-            const float thisaspect = ((float)thisw) / ((float) thish);
-            const float aspectdiff = SDL_fabs(wantaspect - thisaspect);
-            const float diff = SDL_fabs(closestaspect - thisaspect);
-            const int diffw = SDL_abs(thisw - wantw);
-            if (diff < epsilon) {  // matches current closestaspect? See if resolution is closer in size.
-                if (diffw < closestdiffw) {
+        if (wantw > 0 && wanth > 0) {
+            // Find the sizes with the closest aspect ratio and then find the best fit of those.
+            const float wantaspect = ((float)wantw) / ((float)wanth);
+            const float epsilon = 1e-6f;
+            float closestaspect = -9999999.0f;
+            float closestdiff = 999999.0f;
+            int closestdiffw = 9999999;
+
+            for (int i = 0; i < num_specs; i++) {
+                const SDL_CameraSpec *thisspec = &device->all_specs[i];
+                const int thisw = thisspec->width;
+                const int thish = thisspec->height;
+                const float thisaspect = ((float)thisw) / ((float)thish);
+                const float aspectdiff = SDL_fabs(wantaspect - thisaspect);
+                const float diff = SDL_fabs(closestaspect - thisaspect);
+                const int diffw = SDL_abs(thisw - wantw);
+                if (diff < epsilon) { // matches current closestaspect? See if resolution is closer in size.
+                    if (diffw < closestdiffw) {
+                        closestdiffw = diffw;
+                        closest->width = thisw;
+                        closest->height = thish;
+                    }
+                } else if (aspectdiff < closestdiff) { // this is a closer aspect ratio? Take it, reset resolution checks.
+                    closestdiff = aspectdiff;
+                    closestaspect = thisaspect;
                     closestdiffw = diffw;
                     closest->width = thisw;
                     closest->height = thish;
                 }
-            } else if (aspectdiff < closestdiff) {  // this is a closer aspect ratio? Take it, reset resolution checks.
-                closestdiff = aspectdiff;
-                closestaspect = thisaspect;
-                closestdiffw = diffw;
-                closest->width = thisw;
-                closest->height = thish;
             }
+        } else {
+            SDL_copyp(closest, &device->all_specs[0]);
         }
 
         SDL_assert(closest->width > 0);
@@ -1030,16 +1034,6 @@ static void ChooseBestCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec
 
 SDL_Camera *SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec)
 {
-    if (spec) {
-        if ((spec->width <= 0) || (spec->height <= 0)) {
-            SDL_SetError("Requested spec frame size is invalid");
-            return NULL;
-        } else if (spec->format == SDL_PIXELFORMAT_UNKNOWN) {
-            SDL_SetError("Requested spec format is invalid");
-            return NULL;
-        }
-    }
-
     SDL_CameraDevice *device = ObtainPhysicalCameraDevice(instance_id);
     if (!device) {
         return NULL;
@@ -1073,10 +1067,21 @@ SDL_Camera *SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_Camer
         return NULL;
     }
 
-    if (!spec) {
-        SDL_copyp(&device->spec, &closest);
-    } else {
+    if (spec) {
         SDL_copyp(&device->spec, spec);
+        if (spec->width <= 0 || spec->height <= 0) {
+            device->spec.width = closest.width;
+            device->spec.height = closest.height;
+        }
+        if (spec->format == SDL_PIXELFORMAT_UNKNOWN) {
+            device->spec.format = closest.format;
+        }
+        if (spec->interval_denominator == 0) {
+            device->spec.interval_numerator = closest.interval_numerator;
+            device->spec.interval_denominator = closest.interval_denominator;
+        }
+    } else {
+        SDL_copyp(&device->spec, &closest);
     }
 
     SDL_copyp(&device->actual_spec, &closest);