1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! POD types for the camera-capture surface
//! (SUPER_PLAN_2 §4 Priority 6 + research/01).
//!
//! Camera frames are GPU textures, not scalar samples, so the stateful side
//! is heavier than the sensors': `azul_layout::managers::camera` owns a
//! `CameraStream` per capture, each holding a shared `ImageRef` texture the
//! capture thread writes into (zero-copy - clones see new bytes via the
//! `ImageRef` `Arc`). A `CameraPreview` node renders that texture and, by
//! appearing in the DOM, declares "I need the camera" to the permission
//! layer (research/01 §"permission-as-DOM").
//!
//! Defined here in `azul-core` so the config / id / status types cross the
//! FFI without `azul-layout` (or AVFoundation / Camera2) as a dependency -
//! these are what an app passes to `start_camera` and reads back from a
//! stream. The `Nv12` zero-copy output format is a `RawImageFormat` addition
//! deferred to the backend tick; configs default to `BGRA8`.
use crate::resources::RawImageFormat;
/// Identifies one camera capture stream - assigned by `start_camera`, used
/// to read the stream back (`get_camera_frame`) and to stop / pause / flip it.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CaptureStreamId {
pub id: u64,
}
/// Which physical camera to open.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CameraFacing {
/// User-facing (selfie) camera.
Front,
/// World-facing (rear) camera.
Back,
/// An external / USB camera (desktop webcams report here).
External,
}
/// Lifecycle of a capture stream.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StreamState {
/// Opening the device / negotiating the format.
Starting,
/// Delivering frames.
Running,
/// Temporarily suspended (app backgrounded, `pause_camera`).
Paused,
/// Stopped by the app (`stop_camera`) or torn down.
Stopped,
/// Failed - see the stream's [`CaptureErrorCode`].
Error,
}
/// Rotation / mirroring the capture needs relative to the display (the
/// sensor's native orientation rarely matches the UI's).
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CaptureOrientation {
/// Upright (0°).
Up,
/// Upside down (180°).
Down,
/// Rotated 90° counter-clockwise.
Left,
/// Rotated 90° clockwise.
Right,
/// Horizontally mirrored (typical for the front camera).
Mirror,
}
/// Why a capture stream failed ([`StreamState::Error`]).
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CaptureErrorCode {
/// The user denied (or hasn't granted) camera permission.
PermissionDenied,
/// No camera matched the requested [`CameraFacing`].
DeviceUnavailable,
/// The device disappeared mid-capture (unplugged / claimed).
DeviceLost,
/// The requested format / resolution isn't supported.
Unsupported,
/// A platform error not covered above.
Internal,
}
/// Requested capture configuration - the input to `start_camera`. Zero
/// `width`/`height`/`fps` mean "let the backend pick its default".
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CameraConfig {
/// Which camera to open.
pub facing: CameraFacing,
/// Preferred frame width in px (0 = backend default).
pub width: u32,
/// Preferred frame height in px (0 = backend default).
pub height: u32,
/// Preferred frame rate (0 = backend default).
pub fps: u32,
/// Texture format the backend should deliver. `BGRA8` is the portable
/// default; `Nv12` (a later `RawImageFormat` addition) is the zero-copy
/// path on platforms that produce it natively.
pub output_format: RawImageFormat,
}
impl Default for CameraConfig {
fn default() -> Self {
Self {
facing: CameraFacing::Back,
width: 0,
height: 0,
fps: 0,
output_format: RawImageFormat::BGRA8,
}
}
}
impl CameraConfig {
/// A default config for the given `facing` (backend-chosen size/fps,
/// `BGRA8`).
#[must_use]
pub fn new(facing: CameraFacing) -> Self {
Self {
facing,
..Self::default()
}
}
}
/// Runtime stats for a capture stream - surfaced for HUD / debugging.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CaptureStats {
/// Measured delivery rate (frames/s), smoothed by the backend.
pub measured_fps: f32,
/// Frames delivered to the texture since the stream started.
pub frames_delivered: u64,
/// Frames the backend dropped (couldn't keep up / late).
pub frames_dropped: u64,
}
impl Default for CaptureStats {
fn default() -> Self {
Self {
measured_fps: 0.0,
frames_delivered: 0,
frames_dropped: 0,
}
}
}
#[cfg(test)]
#[path = "camera_test.rs"]
mod camera_test;