Skip to main content

fission_shell_winit/
camera.rs

1use fission_core::{
2    CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
3    CameraFacing, CameraFlashlightRequest, CameraPermission, CameraPermissionRequest,
4    CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO, GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION,
5    SET_CAMERA_FLASHLIGHT,
6};
7use fission_shell::async_host::AsyncRegistry;
8use std::io::Cursor;
9use std::sync::{Arc, Mutex};
10
11/// Host-side camera and flashlight provider.
12pub trait CameraHost: Send + Sync + 'static {
13    /// Returns camera permission state and host-visible camera devices.
14    fn availability(&self) -> Result<CameraAvailability, CameraError>;
15    /// Requests camera permission and returns the resulting permission state.
16    fn request_permission(
17        &self,
18        request: CameraPermissionRequest,
19    ) -> Result<CameraPermission, CameraError>;
20    /// Captures a still image according to the selected camera, format, flash, and quality request.
21    fn capture_photo(&self, request: CameraCaptureRequest) -> Result<CameraCapture, CameraError>;
22    /// Enables, disables, or adjusts the selected camera flashlight where available.
23    fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError>;
24    /// Cancels an active camera capture flow.
25    fn cancel_capture(&self) -> Result<(), CameraError>;
26}
27
28#[derive(Debug, Default)]
29pub struct UnsupportedCameraHost;
30
31impl CameraHost for UnsupportedCameraHost {
32    fn availability(&self) -> Result<CameraAvailability, CameraError> {
33        Ok(CameraAvailability {
34            permission: CameraPermission::Denied,
35            devices: Vec::new(),
36        })
37    }
38
39    fn request_permission(
40        &self,
41        _request: CameraPermissionRequest,
42    ) -> Result<CameraPermission, CameraError> {
43        Err(CameraError::unsupported("request_permission"))
44    }
45
46    fn capture_photo(&self, _request: CameraCaptureRequest) -> Result<CameraCapture, CameraError> {
47        Err(CameraError::unsupported("capture_photo"))
48    }
49
50    fn set_flashlight(&self, _request: CameraFlashlightRequest) -> Result<(), CameraError> {
51        Err(CameraError::unsupported("set_flashlight"))
52    }
53
54    fn cancel_capture(&self) -> Result<(), CameraError> {
55        Err(CameraError::unsupported("cancel_capture"))
56    }
57}
58
59#[derive(Debug)]
60pub struct MemoryCameraHost {
61    availability: CameraAvailability,
62    capture: CameraCapture,
63    flashlight_calls: Arc<Mutex<Vec<CameraFlashlightRequest>>>,
64}
65
66impl MemoryCameraHost {
67    pub fn new(availability: CameraAvailability, capture: CameraCapture) -> Self {
68        Self {
69            availability,
70            capture,
71            flashlight_calls: Arc::new(Mutex::new(Vec::new())),
72        }
73    }
74
75    pub fn flashlight_calls(&self) -> Vec<CameraFlashlightRequest> {
76        self.flashlight_calls
77            .lock()
78            .map(|calls| calls.clone())
79            .unwrap_or_default()
80    }
81}
82
83impl Default for MemoryCameraHost {
84    fn default() -> Self {
85        Self::new(
86            CameraAvailability {
87                permission: CameraPermission::Granted,
88                devices: vec![CameraDevice {
89                    id: "memory-camera".into(),
90                    label: Some("Memory camera".into()),
91                    facing: CameraFacing::Back,
92                    has_flashlight: true,
93                }],
94            },
95            CameraCapture {
96                bytes: demo_capture_png(96, 72),
97                content_type: "image/png".into(),
98                width: 96,
99                height: 72,
100                camera_id: Some("memory-camera".into()),
101            },
102        )
103    }
104}
105
106fn demo_capture_png(width: u32, height: u32) -> Vec<u8> {
107    let mut image = image::RgbaImage::new(width, height);
108    for (x, y, pixel) in image.enumerate_pixels_mut() {
109        let red = 24 + ((x * 140) / width.max(1)) as u8;
110        let green = 120 + ((y * 90) / height.max(1)) as u8;
111        let blue = 180 + (((x + y) * 50) / (width + height).max(1)) as u8;
112        *pixel = image::Rgba([red, green, blue, 255]);
113    }
114    let mut bytes = Cursor::new(Vec::new());
115    image::DynamicImage::ImageRgba8(image)
116        .write_to(&mut bytes, image::ImageFormat::Png)
117        .expect("encode in-memory camera capture");
118    bytes.into_inner()
119}
120
121impl CameraHost for MemoryCameraHost {
122    fn availability(&self) -> Result<CameraAvailability, CameraError> {
123        Ok(self.availability.clone())
124    }
125
126    fn request_permission(
127        &self,
128        _request: CameraPermissionRequest,
129    ) -> Result<CameraPermission, CameraError> {
130        Ok(self.availability.permission)
131    }
132
133    fn capture_photo(&self, _request: CameraCaptureRequest) -> Result<CameraCapture, CameraError> {
134        Ok(self.capture.clone())
135    }
136
137    fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError> {
138        self.flashlight_calls.lock().unwrap().push(request);
139        Ok(())
140    }
141
142    fn cancel_capture(&self) -> Result<(), CameraError> {
143        Ok(())
144    }
145}
146
147pub(crate) fn register_camera_capabilities(
148    async_registry: &mut AsyncRegistry,
149    host: Arc<dyn CameraHost>,
150) {
151    let availability_host = host.clone();
152    async_registry.register_operation_capability(GET_CAMERA_AVAILABILITY, move |(), _| {
153        let host = availability_host.clone();
154        async move { host.availability() }
155    });
156
157    let permission_host = host.clone();
158    async_registry.register_operation_capability(REQUEST_CAMERA_PERMISSION, move |request, _| {
159        let host = permission_host.clone();
160        async move { host.request_permission(request) }
161    });
162
163    let capture_host = host.clone();
164    async_registry.register_operation_capability(CAPTURE_PHOTO, move |request, _| {
165        let host = capture_host.clone();
166        async move { host.capture_photo(request) }
167    });
168
169    let flashlight_host = host.clone();
170    async_registry.register_operation_capability(SET_CAMERA_FLASHLIGHT, move |request, _| {
171        let host = flashlight_host.clone();
172        async move { host.set_flashlight(request) }
173    });
174
175    async_registry.register_operation_capability(CANCEL_CAMERA_CAPTURE, move |(), _| {
176        let host = host.clone();
177        async move { host.cancel_capture() }
178    });
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use fission_core::CameraImageFormat;
185
186    #[test]
187    fn unsupported_host_reports_errors() {
188        let host = UnsupportedCameraHost;
189        assert!(host
190            .capture_photo(CameraCaptureRequest {
191                format: CameraImageFormat::Jpeg,
192                ..Default::default()
193            })
194            .is_err());
195        assert!(host
196            .set_flashlight(CameraFlashlightRequest {
197                enabled: true,
198                ..Default::default()
199            })
200            .is_err());
201    }
202
203    #[test]
204    fn memory_host_returns_capture_and_records_flashlight() {
205        let host = MemoryCameraHost::default();
206        let availability = host.availability().unwrap();
207        assert_eq!(availability.permission, CameraPermission::Granted);
208
209        let capture = host.capture_photo(CameraCaptureRequest::default()).unwrap();
210        assert_eq!(capture.width, 96);
211        assert_eq!(capture.height, 72);
212        assert_eq!(capture.content_type, "image/png");
213        image::load_from_memory(&capture.bytes).expect("memory camera capture should decode");
214
215        let request = CameraFlashlightRequest {
216            enabled: true,
217            intensity: Some(128),
218            ..Default::default()
219        };
220        host.set_flashlight(request.clone()).unwrap();
221        assert_eq!(host.flashlight_calls(), vec![request]);
222    }
223}