Skip to main content

freya_camera/
use_camera.rs

1//! [`use_camera`] hook and the [`Camera`] handle.
2
3use std::{
4    cell::RefCell,
5    rc::Rc,
6};
7
8use freya_core::{
9    elements::image::ImageHolder,
10    prelude::*,
11};
12use freya_engine::prelude::{
13    AlphaType,
14    ColorType,
15    Data,
16    ISize,
17    ImageInfo,
18    raster_from_data,
19};
20
21use crate::{
22    camera::{
23        CameraConfig,
24        CameraError,
25        StreamInfo,
26    },
27    capture::{
28        CameraFrame,
29        CaptureHandle,
30        CaptureState,
31        spawn_capture,
32    },
33};
34
35/// Handle to a running camera. Closed when its owning scope is dropped.
36#[derive(Clone, Copy, PartialEq)]
37pub struct Camera {
38    /// The latest frame produced by the camera.
39    pub frame: State<Option<ImageHolder>>,
40    /// The resolution and frame rate negotiated with the device.
41    pub info: State<Option<StreamInfo>>,
42    /// The most recent error, if any.
43    pub error: State<Option<CameraError>>,
44}
45
46impl Camera {
47    /// Open a camera and start streaming frames into reactive state.
48    pub fn create(config: CameraConfig) -> Self {
49        let mut frame: State<Option<ImageHolder>> = State::create(None);
50        let mut info: State<Option<StreamInfo>> = State::create(None);
51        let mut error: State<Option<CameraError>> = State::create(None);
52
53        let CaptureHandle { state, wake } = spawn_capture(config);
54
55        spawn(async move {
56            loop {
57                wake.notified().await;
58
59                let CaptureState {
60                    frame: latest_frame,
61                    info: latest_info,
62                    error: latest_error,
63                } = std::mem::take(&mut *state.lock().unwrap());
64
65                if let Some(stream_info) = latest_info {
66                    *info.write() = Some(stream_info);
67                }
68                if let Some(capture_error) = latest_error {
69                    tracing::warn!("freya-camera: {capture_error}");
70                    *error.write() = Some(capture_error);
71                }
72                if let Some(camera_frame) = latest_frame {
73                    match build_holder(camera_frame) {
74                        Ok(holder) => *frame.write() = Some(holder),
75                        Err(build_error) => {
76                            tracing::warn!("freya-camera: {build_error}");
77                            *error.write() = Some(build_error);
78                        }
79                    }
80                }
81            }
82        });
83
84        Self { frame, info, error }
85    }
86}
87
88/// Open a camera and return a [`Camera`] handle. `init` runs once on mount.
89///
90/// # Example
91///
92/// ```rust, no_run
93/// use freya::{
94///     camera::*,
95///     prelude::*,
96/// };
97///
98/// fn app() -> impl IntoElement {
99///     let camera = use_camera(CameraConfig::default);
100///
101///     rect().center().expanded().child(CameraViewer::new(camera))
102/// }
103/// ```
104pub fn use_camera(init: impl FnOnce() -> CameraConfig) -> Camera {
105    use_hook(|| Camera::create(init()))
106}
107
108/// Build an [`ImageHolder`] from a raw `RGBA8` camera frame.
109fn build_holder(frame: CameraFrame) -> Result<ImageHolder, CameraError> {
110    let CameraFrame {
111        width,
112        height,
113        data,
114    } = frame;
115
116    let info = ImageInfo::new(
117        ISize::new(width as i32, height as i32),
118        ColorType::RGBA8888,
119        AlphaType::Opaque,
120        None,
121    );
122    let row_bytes = (width as usize) * 4;
123    // Safety: `data` outlives the SkImage via `ImageHolder.bytes` below.
124    let sk_data = unsafe { Data::new_bytes(&data) };
125    let image = raster_from_data(&info, sk_data, row_bytes)
126        .ok_or_else(|| CameraError::GeneralError("failed to create raster image".to_string()))?;
127
128    Ok(ImageHolder {
129        image: Rc::new(RefCell::new(image)),
130        bytes: data,
131    })
132}