Skip to main content

fission_shell_winit/
camera.rs

1use fission_core::{
2    single_chunk_data_stream, CameraAvailability, CameraCapture, CameraCaptureRequest,
3    CameraDevice, CameraError, CameraFacing, CameraFlashlightRequest, CameraPermission,
4    CameraPermissionRequest, CapabilityCtx, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO,
5    GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION, 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(
22        &self,
23        request: CameraCaptureRequest,
24        ctx: &CapabilityCtx,
25    ) -> Result<CameraCapture, CameraError>;
26    /// Enables, disables, or adjusts the selected camera flashlight where available.
27    fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError>;
28    /// Cancels an active camera capture flow.
29    fn cancel_capture(&self) -> Result<(), CameraError>;
30}
31
32#[derive(Debug, Default)]
33pub struct UnsupportedCameraHost;
34
35impl CameraHost for UnsupportedCameraHost {
36    fn availability(&self) -> Result<CameraAvailability, CameraError> {
37        Ok(CameraAvailability {
38            permission: CameraPermission::Denied,
39            devices: Vec::new(),
40        })
41    }
42
43    fn request_permission(
44        &self,
45        _request: CameraPermissionRequest,
46    ) -> Result<CameraPermission, CameraError> {
47        Err(CameraError::unsupported("request_permission"))
48    }
49
50    fn capture_photo(
51        &self,
52        _request: CameraCaptureRequest,
53        _ctx: &CapabilityCtx,
54    ) -> Result<CameraCapture, CameraError> {
55        Err(CameraError::unsupported("capture_photo"))
56    }
57
58    fn set_flashlight(&self, _request: CameraFlashlightRequest) -> Result<(), CameraError> {
59        Err(CameraError::unsupported("set_flashlight"))
60    }
61
62    fn cancel_capture(&self) -> Result<(), CameraError> {
63        Err(CameraError::unsupported("cancel_capture"))
64    }
65}
66
67#[derive(Debug)]
68pub struct MemoryCameraHost {
69    availability: CameraAvailability,
70    capture_bytes: Arc<Vec<u8>>,
71    content_type: String,
72    width: u32,
73    height: u32,
74    camera_id: Option<String>,
75    flashlight_calls: Arc<Mutex<Vec<CameraFlashlightRequest>>>,
76}
77
78impl MemoryCameraHost {
79    pub fn new(
80        availability: CameraAvailability,
81        capture_bytes: Vec<u8>,
82        content_type: impl Into<String>,
83        width: u32,
84        height: u32,
85        camera_id: Option<String>,
86    ) -> Self {
87        Self {
88            availability,
89            capture_bytes: Arc::new(capture_bytes),
90            content_type: content_type.into(),
91            width,
92            height,
93            camera_id,
94            flashlight_calls: Arc::new(Mutex::new(Vec::new())),
95        }
96    }
97
98    pub fn flashlight_calls(&self) -> Vec<CameraFlashlightRequest> {
99        self.flashlight_calls
100            .lock()
101            .map(|calls| calls.clone())
102            .unwrap_or_default()
103    }
104}
105
106impl Default for MemoryCameraHost {
107    fn default() -> Self {
108        Self::new(
109            CameraAvailability {
110                permission: CameraPermission::Granted,
111                devices: vec![CameraDevice {
112                    id: "memory-camera".into(),
113                    label: Some("Memory camera".into()),
114                    facing: CameraFacing::Back,
115                    has_flashlight: true,
116                }],
117            },
118            demo_capture_png(96, 72),
119            "image/png",
120            96,
121            72,
122            Some("memory-camera".into()),
123        )
124    }
125}
126
127fn demo_capture_png(width: u32, height: u32) -> Vec<u8> {
128    let mut image = image::RgbaImage::new(width, height);
129    for (x, y, pixel) in image.enumerate_pixels_mut() {
130        let red = 24 + ((x * 140) / width.max(1)) as u8;
131        let green = 120 + ((y * 90) / height.max(1)) as u8;
132        let blue = 180 + (((x + y) * 50) / (width + height).max(1)) as u8;
133        *pixel = image::Rgba([red, green, blue, 255]);
134    }
135    let mut bytes = Cursor::new(Vec::new());
136    image::DynamicImage::ImageRgba8(image)
137        .write_to(&mut bytes, image::ImageFormat::Png)
138        .expect("encode in-memory camera capture");
139    bytes.into_inner()
140}
141
142impl CameraHost for MemoryCameraHost {
143    fn availability(&self) -> Result<CameraAvailability, CameraError> {
144        Ok(self.availability.clone())
145    }
146
147    fn request_permission(
148        &self,
149        _request: CameraPermissionRequest,
150    ) -> Result<CameraPermission, CameraError> {
151        Ok(self.availability.permission)
152    }
153
154    fn capture_photo(
155        &self,
156        _request: CameraCaptureRequest,
157        ctx: &CapabilityCtx,
158    ) -> Result<CameraCapture, CameraError> {
159        let stream =
160            ctx.register_data_stream(single_chunk_data_stream((*self.capture_bytes).clone()));
161        Ok(CameraCapture {
162            stream,
163            byte_len: Some(self.capture_bytes.len() as u64),
164            content_type: self.content_type.clone(),
165            width: self.width,
166            height: self.height,
167            camera_id: self.camera_id.clone(),
168        })
169    }
170
171    fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError> {
172        self.flashlight_calls.lock().unwrap().push(request);
173        Ok(())
174    }
175
176    fn cancel_capture(&self) -> Result<(), CameraError> {
177        Ok(())
178    }
179}
180
181pub(crate) fn register_camera_capabilities(
182    async_registry: &mut AsyncRegistry,
183    host: Arc<dyn CameraHost>,
184) {
185    let availability_host = host.clone();
186    async_registry.register_operation_capability(GET_CAMERA_AVAILABILITY, move |(), _| {
187        let host = availability_host.clone();
188        async move { host.availability() }
189    });
190
191    let permission_host = host.clone();
192    async_registry.register_operation_capability(REQUEST_CAMERA_PERMISSION, move |request, _| {
193        let host = permission_host.clone();
194        async move { host.request_permission(request) }
195    });
196
197    let capture_host = host.clone();
198    async_registry.register_operation_capability(CAPTURE_PHOTO, move |request, ctx| {
199        let host = capture_host.clone();
200        async move { host.capture_photo(request, &ctx) }
201    });
202
203    let flashlight_host = host.clone();
204    async_registry.register_operation_capability(SET_CAMERA_FLASHLIGHT, move |request, _| {
205        let host = flashlight_host.clone();
206        async move { host.set_flashlight(request) }
207    });
208
209    async_registry.register_operation_capability(CANCEL_CAMERA_CAPTURE, move |(), _| {
210        let host = host.clone();
211        async move { host.cancel_capture() }
212    });
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use fission_core::{CameraImageFormat, DataStreamRegistry};
219
220    #[test]
221    fn unsupported_host_reports_errors() {
222        let host = UnsupportedCameraHost;
223        let ctx = CapabilityCtx::new_runtime(1, DataStreamRegistry::new());
224        assert!(host
225            .capture_photo(
226                CameraCaptureRequest {
227                    format: CameraImageFormat::Jpeg,
228                    ..Default::default()
229                },
230                &ctx,
231            )
232            .is_err());
233        assert!(host
234            .set_flashlight(CameraFlashlightRequest {
235                enabled: true,
236                ..Default::default()
237            })
238            .is_err());
239    }
240
241    #[test]
242    fn memory_host_returns_capture_and_records_flashlight() {
243        let host = MemoryCameraHost::default();
244        let availability = host.availability().unwrap();
245        assert_eq!(availability.permission, CameraPermission::Granted);
246
247        let ctx = CapabilityCtx::new_runtime(1, DataStreamRegistry::new());
248        let capture = host
249            .capture_photo(CameraCaptureRequest::default(), &ctx)
250            .unwrap();
251        assert_eq!(capture.width, 96);
252        assert_eq!(capture.height, 72);
253        assert_eq!(capture.content_type, "image/png");
254        assert!(capture.stream.0 > 0);
255
256        let request = CameraFlashlightRequest {
257            enabled: true,
258            intensity: Some(128),
259            ..Default::default()
260        };
261        host.set_flashlight(request.clone()).unwrap();
262        assert_eq!(host.flashlight_calls(), vec![request]);
263    }
264}