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
44pub struct CameraWidgetState {
47 pub config: CameraConfig,
49 pub started: bool,
51 pub gl_texture_id: Option<u32>,
53 pub on_frame: OptionOnVideoFrame,
56}
57
58#[repr(C)]
61pub struct CameraWidget {
62 pub config: CameraConfig,
64 pub on_frame: OptionOnVideoFrame,
66}
67
68impl CameraWidget {
69 pub fn create(config: CameraConfig) -> Self {
71 Self {
72 config,
73 on_frame: OptionOnVideoFrame::None,
74 }
75 }
76
77 pub fn set_on_frame<C: Into<OnVideoFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
81 self.on_frame = Some(OnVideoFrame {
82 refany: data,
83 callback: on_frame.into(),
84 })
85 .into();
86 }
87
88 pub fn with_on_frame<C: Into<OnVideoFrameCallback>>(
90 mut self,
91 data: RefAny,
92 on_frame: C,
93 ) -> Self {
94 self.set_on_frame(data, on_frame);
95 self
96 }
97
98 pub fn dom(self) -> Dom {
101 let state = CameraWidgetState {
102 config: self.config,
103 started: false,
104 gl_texture_id: None,
105 on_frame: self.on_frame,
106 };
107 let dataset = RefAny::new(state);
108
109 let (w, h) = frame_dims(&self.config);
110 let placeholder = ImageRef::null_image(
111 w as usize,
112 h as usize,
113 RawImageFormat::BGRA8,
114 b"azul-camera-placeholder".to_vec(),
115 );
116
117 Dom::create_image(placeholder)
118 .with_dataset(OptionRefAny::Some(dataset.clone()))
119 .with_merge_callback(merge_camera_state as DatasetMergeCallbackType)
120 .with_callback(
121 EventFilter::Component(ComponentEventFilter::AfterMount),
122 dataset,
123 Callback::from(camera_on_after_mount as CallbackType),
124 )
125 }
126}
127
128fn frame_dims(config: &CameraConfig) -> (u32, u32) {
130 let w = if config.width > 0 { config.width } else { 640 };
131 let h = if config.height > 0 { config.height } else { 480 };
132 (w, h)
133}
134
135extern "C" fn camera_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
137 let dims = {
138 let mut s = match data.downcast_mut::<CameraWidgetState>() {
139 Some(s) => s,
140 None => return Update::DoNothing,
141 };
142 if s.started {
143 return Update::DoNothing;
144 }
145 s.started = true;
146 frame_dims(&s.config)
147 };
148
149 info.add_thread(
150 ThreadId::unique(),
151 Thread::create(
152 RefAny::new(CameraThreadInit {
153 width: dims.0,
154 height: dims.1,
155 }),
156 data.clone(),
157 ThreadCallback::new(camera_worker),
158 ),
159 );
160 Update::DoNothing
161}
162
163extern "C" fn camera_worker(mut init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
166 let (w, h) = init
167 .downcast_ref::<CameraThreadInit>()
168 .map(|i| (i.width, i.height))
169 .unwrap_or((640, 480));
170
171 if let Some(backend) = camera_backend() {
174 let handle = (backend.open)(0, w, h);
175 if handle != 0 {
176 let mut buf: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
177 loop {
178 let (fw, fh) = (backend.read)(handle, &mut buf);
179 if fw == 0 || fh == 0 {
180 break;
181 }
182 let frame = VideoFrame {
183 width: fw,
184 height: fh,
185 bytes: buf.clone().into(),
186 };
187 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
188 WriteBackCallback::new(camera_writeback),
189 RefAny::new(frame),
190 ))) {
191 break;
192 }
193 }
194 (backend.close)(handle);
195 return;
196 }
197 }
198
199 let px = (w as usize) * (h as usize);
200 let mut tick: u32 = 0;
201 loop {
202 let color = [
203 (tick % 256) as u8,
204 (tick.wrapping_mul(2) % 256) as u8,
205 (tick.wrapping_mul(3) % 256) as u8,
206 255u8,
207 ];
208 let mut bytes = Vec::with_capacity(px * 4);
209 for _ in 0..px {
210 bytes.extend_from_slice(&color);
211 }
212 let frame = VideoFrame {
213 width: w,
214 height: h,
215 bytes: bytes.into(),
216 };
217 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
218 WriteBackCallback::new(camera_writeback),
219 RefAny::new(frame),
220 )));
221 if !sent {
222 break;
223 }
224 std::thread::sleep(std::time::Duration::from_millis(33));
225 tick = tick.wrapping_add(8);
226 }
227}
228
229extern "C" fn camera_writeback(
232 mut writeback_data: RefAny,
233 mut frame_data: RefAny,
234 mut info: CallbackInfo,
235) -> Update {
236 let (current, hook) = match writeback_data.downcast_ref::<CameraWidgetState>() {
237 Some(s) => (s.gl_texture_id, s.on_frame.clone()),
238 None => (None, OptionOnVideoFrame::None),
239 };
240 let mut user_update = Update::DoNothing;
241 let new_id = match frame_data.downcast_ref::<VideoFrame>() {
242 Some(frame) => {
243 let id = present_frame(&mut info, writeback_data.clone(), current, &frame);
244 user_update = invoke_on_frame(&hook, &mut info, &frame);
245 id
246 }
247 None => return Update::DoNothing,
248 };
249 if let Some(mut s) = writeback_data.downcast_mut::<CameraWidgetState>() {
250 s.gl_texture_id = new_id;
251 }
252 user_update
253}
254
255extern "C" fn merge_camera_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
258 {
259 let new_guard = new_data.downcast_mut::<CameraWidgetState>();
260 let old_guard = old_data.downcast_ref::<CameraWidgetState>();
261 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
262 new_g.started = old_g.started;
263 new_g.gl_texture_id = old_g.gl_texture_id;
264 }
265 }
266 new_data
267}