1use alloc::vec::Vec;
19
20use azul_core::callbacks::Update;
21use azul_core::camera::CameraConfig;
22use azul_core::dom::{ComponentEventFilter, DatasetMergeCallbackType, Dom, EventFilter};
23use azul_core::refany::{OptionRefAny, RefAny};
24use azul_core::resources::{ImageRef, RawImageFormat};
25use azul_core::task::{ThreadId, ThreadReceiver};
26
27use azul_core::video::VideoFrame;
28
29use super::capture_common::{
30 camera_backend, invoke_on_frame, present_frame, OnVideoFrame, OnVideoFrameCallback,
31 OptionOnVideoFrame,
32};
33use crate::callbacks::{Callback, CallbackInfo, CallbackType};
34use crate::thread::{
35 Thread, ThreadCallback, ThreadReceiveMsg, ThreadSender, ThreadWriteBackMsg, WriteBackCallback,
36};
37
38struct CameraThreadInit {
40 width: u32,
41 height: u32,
42}
43
44#[derive(Debug)]
47pub struct CameraWidgetState {
48 pub config: CameraConfig,
50 pub started: bool,
52 pub gl_texture_id: Option<u32>,
54 pub on_frame: OptionOnVideoFrame,
57}
58
59#[repr(C)]
62#[derive(Debug)]
63pub struct CameraWidget {
64 pub config: CameraConfig,
66 pub on_frame: OptionOnVideoFrame,
68}
69
70impl CameraWidget {
71 #[must_use] pub const fn create(config: CameraConfig) -> Self {
73 Self {
74 config,
75 on_frame: OptionOnVideoFrame::None,
76 }
77 }
78
79 pub fn set_on_frame<C: Into<OnVideoFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
83 self.on_frame = Some(OnVideoFrame {
84 refany: data,
85 callback: on_frame.into(),
86 })
87 .into();
88 }
89
90 #[must_use]
92 pub fn with_on_frame<C: Into<OnVideoFrameCallback>>(
93 mut self,
94 data: RefAny,
95 on_frame: C,
96 ) -> Self {
97 self.set_on_frame(data, on_frame);
98 self
99 }
100
101 #[must_use] pub fn dom(self) -> Dom {
104 let state = CameraWidgetState {
105 config: self.config,
106 started: false,
107 gl_texture_id: None,
108 on_frame: self.on_frame,
109 };
110 let dataset = RefAny::new(state);
111
112 let (w, h) = frame_dims(&self.config);
113 let placeholder = ImageRef::null_image(
114 w as usize,
115 h as usize,
116 RawImageFormat::BGRA8,
117 b"azul-camera-placeholder".to_vec(),
118 );
119
120 Dom::create_image(placeholder)
121 .with_dataset(OptionRefAny::Some(dataset.clone()))
122 .with_merge_callback(azul_core::dom::DatasetMergeCallback::from_ptr(merge_camera_state))
123 .with_callback(
124 EventFilter::Component(ComponentEventFilter::AfterMount),
125 dataset,
126 Callback::from_ptr(camera_on_after_mount),
127 )
128 }
129}
130
131const fn frame_dims(config: &CameraConfig) -> (u32, u32) {
133 let w = if config.width > 0 { config.width } else { 640 };
134 let h = if config.height > 0 { config.height } else { 480 };
135 (w, h)
136}
137
138extern "C" fn camera_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
140 let dims = {
141 let Some(mut s) = data.downcast_mut::<CameraWidgetState>() else {
142 return Update::DoNothing;
143 };
144 if s.started {
145 return Update::DoNothing;
146 }
147 s.started = true;
148 frame_dims(&s.config)
149 };
150
151 info.add_thread(
152 ThreadId::unique(),
153 Thread::create(
154 RefAny::new(CameraThreadInit {
155 width: dims.0,
156 height: dims.1,
157 }),
158 data.clone(),
159 ThreadCallback::new(camera_worker),
160 ),
161 );
162 Update::DoNothing
163}
164
165#[allow(clippy::cast_possible_truncation)] extern "C" fn camera_worker(mut init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
169 let (w, h) = init
170 .downcast_ref::<CameraThreadInit>()
171 .map_or((640, 480), |i| (i.width, i.height));
172
173 if let Some(backend) = camera_backend() {
176 let handle = (backend.open)(0, w, h);
177 if handle != 0 {
178 let mut buf: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
179 loop {
180 let (fw, fh) = (backend.read)(handle, &mut buf);
181 if fw == 0 || fh == 0 {
182 break;
183 }
184 let frame = VideoFrame {
185 width: fw,
186 height: fh,
187 bytes: buf.clone().into(),
188 };
189 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
190 WriteBackCallback::new(camera_writeback),
191 RefAny::new(frame),
192 ))) {
193 break;
194 }
195 }
196 (backend.close)(handle);
197 return;
198 }
199 }
200
201 let px = (w as usize) * (h as usize);
202 let mut tick: u32 = 0;
203 loop {
204 let color = [
205 (tick % 256) as u8,
206 (tick.wrapping_mul(2) % 256) as u8,
207 (tick.wrapping_mul(3) % 256) as u8,
208 255u8,
209 ];
210 let mut bytes = Vec::with_capacity(px * 4);
211 for _ in 0..px {
212 bytes.extend_from_slice(&color);
213 }
214 let frame = VideoFrame {
215 width: w,
216 height: h,
217 bytes: bytes.into(),
218 };
219 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
220 WriteBackCallback::new(camera_writeback),
221 RefAny::new(frame),
222 )));
223 if !sent {
224 break;
225 }
226 std::thread::sleep(std::time::Duration::from_millis(33));
227 tick = tick.wrapping_add(8);
228 }
229}
230
231extern "C" fn camera_writeback(
234 mut writeback_data: RefAny,
235 mut frame_data: RefAny,
236 mut info: CallbackInfo,
237) -> Update {
238 let (current, hook) = writeback_data.downcast_ref::<CameraWidgetState>().map_or_else(|| (None, OptionOnVideoFrame::None), |s| (s.gl_texture_id, s.on_frame.clone()));
239 let mut user_update = Update::DoNothing;
240 let new_id = match frame_data.downcast_ref::<VideoFrame>() {
241 Some(frame) => {
242 let id = present_frame(&mut info, writeback_data.clone(), current, &frame);
243 user_update = invoke_on_frame(&hook, &mut info, &frame);
244 id
245 }
246 None => return Update::DoNothing,
247 };
248 if let Some(mut s) = writeback_data.downcast_mut::<CameraWidgetState>() {
249 s.gl_texture_id = new_id;
250 }
251 user_update
252}
253
254extern "C" fn merge_camera_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
257 {
258 let new_guard = new_data.downcast_mut::<CameraWidgetState>();
259 let old_guard = old_data.downcast_ref::<CameraWidgetState>();
260 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
261 new_g.started = old_g.started;
262 new_g.gl_texture_id = old_g.gl_texture_id;
263 }
264 }
265 new_data
266}