1#[macro_use]
2mod macros;
3mod utils;
4
5#[cfg(feature = "2d")]
6mod d2;
7#[cfg(feature = "gl")]
8mod gl;
9
10use web_sys::{js_sys, HtmlVideoElement};
11
12#[allow(unused_macros)]
13macro_rules! impl_enum_from {
14 ($from:ty => $typ:ty:$name:tt) => {
15 impl From<$from> for $typ {
16 fn from(value: $from) -> Self {
17 Self::$name(value)
18 }
19 }
20 };
21}
22
23macro_rules! enum_method {
24 ($name:tt ($( $arg:tt: $typ:ty ),*) => $ret:ty) => {
25 fn $name(&self, $($arg: $typ),*) -> $ret {
26 match self {
27 #[cfg(feature = "html-2d")]
28 Self::Html2D(c) => c.$name($($arg),*),
29 #[cfg(feature = "offscreen-2d")]
30 Self::Offscreen2D(c) => c.$name($($arg),*),
31 #[cfg(all(feature = "html", feature = "webgl"))]
32 Self::HtmlGL(c) => c.$name($($arg),*),
33 #[cfg(all(feature = "html", feature = "webgl2"))]
34 Self::HtmlGL2(c) => c.$name($($arg),*),
35 #[cfg(all(feature = "offscreen", feature = "webgl"))]
36 Self::OffscreenGL(c) => c.$name($($arg),*),
37 #[cfg(all(feature = "offscreen", feature = "webgl2"))]
38 Self::OffscreenGL2(c) => c.$name($($arg),*),
39 #[allow(unreachable_patterns)]
40 _ => panic!("Unsupported variant. Please enable any features."),
41 }
42 }
43 };
44}
45
46#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
47#[non_exhaustive]
48pub enum CaptureMode {
49 Put(i32, i32),
52 Fill,
55 #[default]
58 Adjust,
59 Pinhole,
62}
63
64impl CaptureMode {
65 pub const fn put_top_left() -> Self {
66 Self::Put(0, 0)
67 }
68}
69
70#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
71pub enum CaptureColor {
72 #[default]
74 RGBA,
75 RGBL,
77 LLLA,
79}
80
81pub trait CaptureArea {
82 fn capture_width(&self) -> u32;
84
85 fn capture_height(&self) -> u32;
87
88 fn capture_size(&self) -> (u32, u32) {
90 (self.capture_width(), self.capture_height())
91 }
92
93 fn capture_area(&self) -> u32 {
95 self.capture_width() * self.capture_height()
96 }
97
98 fn set_capture_width(&self, width: u32);
100
101 fn set_capture_height(&self, height: u32);
103
104 fn set_capture_size(&self, width: u32, height: u32) {
106 self.set_capture_width(width);
107 self.set_capture_height(height);
108 }
109}
110
111pub trait BrowserVideoCapture: CaptureArea {
112 fn channels_count(&self) -> u32 {
114 4
115 }
116
117 #[cfg(feature = "image")]
118 fn color_type(&self) -> image::ColorType {
119 match self.channels_count() {
120 1 => image::ColorType::L8,
121 2 => image::ColorType::La8,
122 3 => image::ColorType::Rgb8,
123 4 => image::ColorType::Rgba8,
124 _ => panic!("Unsupported channels count"),
125 }
126 }
127
128 fn buffer_size(&self) -> usize {
130 (self.capture_area() * self.channels_count()) as usize
131 }
132
133 fn capture(&self, source: &HtmlVideoElement, mode: CaptureMode) -> (u32, u32);
135
136 fn retrieve(&self, buffer: &mut [u8]);
138
139 fn data(&self) -> Vec<u8> {
141 let mut buffer = vec![0; self.buffer_size()];
142 self.retrieve(&mut buffer);
143 buffer
144 }
145
146 #[cfg(feature = "image")]
147 fn image(&self) -> Option<image::DynamicImage> {
148 let (width, height) = self.capture_size();
149 Some(match self.channels_count() {
150 1 => image::DynamicImage::ImageLuma8(image::GrayImage::from_raw(
151 width,
152 height,
153 self.data(),
154 )?),
155 2 => image::DynamicImage::ImageLumaA8(image::GrayAlphaImage::from_raw(
156 width,
157 height,
158 self.data(),
159 )?),
160 3 => image::DynamicImage::ImageRgb8(image::RgbImage::from_raw(
161 width,
162 height,
163 self.data(),
164 )?),
165 4 => image::DynamicImage::ImageRgba8(image::RgbaImage::from_raw(
166 width,
167 height,
168 self.data(),
169 )?),
170 _ => panic!("Unsupported channels count"),
171 })
172 }
173
174 fn read(&self, source: &HtmlVideoElement, mode: CaptureMode) -> Vec<u8> {
176 let (width, height) = self.capture(source, mode);
177
178 let buffer_size = (width * height * self.channels_count()) as usize;
179
180 if buffer_size > 0 {
181 let mut buffer = vec![0; buffer_size];
182 self.retrieve(&mut buffer);
183 buffer
184 } else {
185 return Vec::new();
186 }
187 }
188
189 fn clear(&self);
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum SupportedCanvas {
195 #[cfg(feature = "html")]
196 Html(web_sys::HtmlCanvasElement),
197 #[cfg(feature = "offscreen")]
198 Offscreen(web_sys::OffscreenCanvas),
199}
200
201#[cfg(feature = "html")]
202impl_enum_from!(web_sys::HtmlCanvasElement => SupportedCanvas:Html);
203#[cfg(feature = "offscreen")]
204impl_enum_from!(web_sys::OffscreenCanvas => SupportedCanvas:Offscreen);
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum SupportedContext {
208 #[cfg(feature = "html-2d")]
209 Html2D(web_sys::CanvasRenderingContext2d),
210 #[cfg(feature = "offscreen-2d")]
211 Ofscreen2D(web_sys::OffscreenCanvasRenderingContext2d),
212 #[cfg(feature = "webgl")]
213 WebGL(web_sys::WebGlRenderingContext),
214 #[cfg(feature = "webgl2")]
215 WebGL2(web_sys::WebGl2RenderingContext),
216}
217
218#[cfg(feature = "html-2d")]
219impl_enum_from!(web_sys::CanvasRenderingContext2d => SupportedContext:Html2D);
220#[cfg(feature = "offscreen-2d")]
221impl_enum_from!(web_sys::OffscreenCanvasRenderingContext2d => SupportedContext:Ofscreen2D);
222#[cfg(feature = "webgl")]
223impl_enum_from!(web_sys::WebGlRenderingContext => SupportedContext:WebGL);
224#[cfg(feature = "webgl2")]
225impl_enum_from!(web_sys::WebGl2RenderingContext => SupportedContext:WebGL2);
226
227#[derive(Default, Debug, Clone, PartialEq, Eq)]
228pub struct BrowserCaptureBuilder {
229 pub context: Option<SupportedContext>,
230 pub canvas: Option<SupportedCanvas>,
231 pub color: Option<CaptureColor>,
232 pub options: Option<SupportedOptions>,
233}
234
235impl BrowserCaptureBuilder {
236 pub fn color(mut self, color: CaptureColor) -> Self {
237 self.color = Some(color);
238 self
239 }
240
241 pub fn canvas(mut self, canvas: SupportedCanvas) -> Self {
242 self.canvas = Some(canvas);
243 self
244 }
245
246 pub fn context(mut self, context: SupportedContext) -> Self {
247 self.context = Some(context);
248 self
249 }
250
251 pub fn options(mut self, options: SupportedOptions) -> Self {
252 self.options = Some(options);
253 self
254 }
255
256 #[cfg(feature = "html")]
257 pub fn html_canvas(self, canvas: web_sys::HtmlCanvasElement) -> Self {
258 self.canvas(SupportedCanvas::Html(canvas))
259 }
260
261 #[cfg(feature = "offscreen")]
262 pub fn offscreen_canvas(self, canvas: web_sys::OffscreenCanvas) -> Self {
263 self.canvas(SupportedCanvas::Offscreen(canvas))
264 }
265
266 #[cfg(feature = "html-2d")]
267 pub fn html_2d(self, context: web_sys::CanvasRenderingContext2d) -> Self {
268 self.context(SupportedContext::Html2D(context))
269 }
270
271 #[cfg(feature = "offscreen-2d")]
272 pub fn offscreen_2d(self, context: web_sys::OffscreenCanvasRenderingContext2d) -> Self {
273 self.context(SupportedContext::Ofscreen2D(context))
274 }
275
276 #[cfg(feature = "webgl")]
277 pub fn webgl(self, context: web_sys::WebGlRenderingContext) -> Self {
278 self.context(SupportedContext::WebGL(context))
279 }
280
281 #[cfg(feature = "webgl2")]
282 pub fn webgl2(self, context: web_sys::WebGl2RenderingContext) -> Self {
283 self.context(SupportedContext::WebGL2(context))
284 }
285
286 pub fn build(self) -> Option<Result<BrowserCapture, js_sys::Error>> {
287 match (self.canvas, self.context, self.options) {
288 #[cfg(feature = "html-2d")]
289 (Some(SupportedCanvas::Html(canvas)), Some(SupportedContext::Html2D(context)), _) => {
290 Some(Ok(HtmlCapture2D::new(
291 canvas,
292 context,
293 self.color.unwrap_or_default(),
294 )
295 .into()))
296 }
297 #[cfg(feature = "html-2d")]
298 (
299 Some(SupportedCanvas::Html(canvas)),
300 None,
301 Some(SupportedOptions::Html2D(options)),
302 ) => Some(
303 HtmlCapture2D::from_canvas_with_options(
304 canvas,
305 self.color.unwrap_or_default(),
306 options,
307 )
308 .transpose()?
309 .map(Into::into),
310 ),
311 #[cfg(feature = "offscreen-2d")]
312 (
313 Some(SupportedCanvas::Offscreen(canvas)),
314 Some(SupportedContext::Ofscreen2D(context)),
315 _,
316 ) => Some(Ok(OffscreenCapture2D::new(
317 canvas,
318 context,
319 self.color.unwrap_or_default(),
320 )
321 .into())),
322 #[cfg(feature = "offscreen-2d")]
323 (
324 Some(SupportedCanvas::Offscreen(canvas)),
325 None,
326 Some(SupportedOptions::Offscreen2D(options)),
327 ) => Some(
328 OffscreenCapture2D::from_canvas_with_options(
329 canvas,
330 self.color.unwrap_or_default(),
331 options,
332 )
333 .transpose()?
334 .map(Into::into),
335 ),
336 #[cfg(all(feature = "html", feature = "webgl"))]
337 (Some(SupportedCanvas::Html(canvas)), Some(SupportedContext::WebGL(context)), _) => {
338 Some(Ok(HtmlCaptureGL::new(
339 canvas,
340 context,
341 self.color.unwrap_or_default(),
342 )
343 .into()))
344 }
345 #[cfg(all(feature = "html", feature = "webgl2"))]
346 (Some(SupportedCanvas::Html(canvas)), Some(SupportedContext::WebGL2(context)), _) => {
347 Some(Ok(HtmlCaptureGL2::new(
348 canvas,
349 context,
350 self.color.unwrap_or_default(),
351 )
352 .into()))
353 }
354 #[cfg(all(feature = "html", feature = "webgl"))]
355 (
356 Some(SupportedCanvas::Html(canvas)),
357 None,
358 Some(SupportedOptions::HtmlGL(options)),
359 ) if matches!(options.version, GLVersion::WebGL) => Some(
360 HtmlCaptureGL::from_canvas_with_options(
361 canvas,
362 self.color.unwrap_or_default(),
363 options,
364 )
365 .transpose()?
366 .map(|c| c.validate().ok())
367 .transpose()?
368 .map(Into::into),
369 ),
370 #[cfg(all(feature = "html", feature = "webgl2"))]
371 (
372 Some(SupportedCanvas::Html(canvas)),
373 None,
374 Some(SupportedOptions::HtmlGL(options)),
375 ) if matches!(options.version, GLVersion::WebGL2) => Some(
376 HtmlCaptureGL2::from_canvas_with_options(
377 canvas,
378 self.color.unwrap_or_default(),
379 options,
380 )
381 .transpose()?
382 .map(|c| c.validate().ok())
383 .transpose()?
384 .map(Into::into),
385 ),
386 #[cfg(all(feature = "offscreen", feature = "webgl"))]
387 (
388 Some(SupportedCanvas::Offscreen(canvas)),
389 Some(SupportedContext::WebGL(context)),
390 _,
391 ) => Some(Ok(OffscreenCaptureGL::new(
392 canvas,
393 context,
394 self.color.unwrap_or_default(),
395 )
396 .into())),
397 #[cfg(all(feature = "offscreen", feature = "webgl2"))]
398 (
399 Some(SupportedCanvas::Offscreen(canvas)),
400 Some(SupportedContext::WebGL2(context)),
401 _,
402 ) => Some(Ok(OffscreenCaptureGL2::new(
403 canvas,
404 context,
405 self.color.unwrap_or_default(),
406 )
407 .into())),
408 #[cfg(all(feature = "offscreen", feature = "webgl"))]
409 (
410 Some(SupportedCanvas::Offscreen(canvas)),
411 None,
412 Some(SupportedOptions::OffscreenGL(options)),
413 ) if matches!(options.version, GLVersion::WebGL) => Some(
414 OffscreenCaptureGL::from_canvas_with_options(
415 canvas,
416 self.color.unwrap_or_default(),
417 options,
418 )
419 .transpose()?
420 .map(|c| c.validate().ok())
421 .transpose()?
422 .map(Into::into),
423 ),
424 #[cfg(all(feature = "offscreen", feature = "webgl"))]
425 (
426 Some(SupportedCanvas::Offscreen(canvas)),
427 None,
428 Some(SupportedOptions::OffscreenGL(options)),
429 ) if matches!(options.version, GLVersion::WebGL2) => Some(
430 OffscreenCaptureGL2::from_canvas_with_options(
431 canvas,
432 self.color.unwrap_or_default(),
433 options,
434 )
435 .transpose()?
436 .map(|c| c.validate().ok())
437 .transpose()?
438 .map(Into::into),
439 ),
440 _ => None,
441 }
442 }
443}
444
445pub use utils::video_size;
446
447#[cfg(all(feature = "html", feature = "2d"))]
448pub use d2::html::ColorSpaceType;
449#[cfg(feature = "html-2d")]
450pub use d2::html::{HtmlCapture2D, HtmlContextOptions2D};
451#[cfg(all(feature = "offscreen", feature = "2d"))]
452pub use d2::offscreen::OffscreenStorageType;
453#[cfg(feature = "offscreen-2d")]
454pub use d2::offscreen::{OffscreenCapture2D, OffscreenContextOptions2D};
455
456#[cfg(feature = "gl")]
457pub use gl::GLVersion;
458#[cfg(all(feature = "html", feature = "gl"))]
459pub use gl::html::{PowerPreference, HtmlContextOptionsGL};
460#[cfg(all(feature = "offscreen", feature = "gl"))]
461pub use gl::offscreen::OffscreenContextOptionsGL;
462
463#[cfg(all(feature = "html", feature = "webgl"))]
464pub use gl::html::HtmlCaptureGL;
465#[cfg(all(feature = "html", feature = "webgl2"))]
466pub use gl::html::HtmlCaptureGL2;
467#[cfg(all(feature = "offscreen", feature = "webgl"))]
468pub use gl::offscreen::OffscreenCaptureGL;
469#[cfg(all(feature = "offscreen", feature = "webgl2"))]
470pub use gl::offscreen::OffscreenCaptureGL2;
471
472#[derive(Debug, Clone, PartialEq, Eq)]
473pub enum BrowserCapture {
474 #[cfg(feature = "html-2d")]
475 Html2D(HtmlCapture2D),
476 #[cfg(feature = "offscreen-2d")]
477 Offscreen2D(OffscreenCapture2D),
478 #[cfg(all(feature = "html", feature = "webgl"))]
479 HtmlGL(HtmlCaptureGL),
480 #[cfg(all(feature = "html", feature = "webgl2"))]
481 HtmlGL2(HtmlCaptureGL2),
482 #[cfg(all(feature = "offscreen", feature = "webgl"))]
483 OffscreenGL(OffscreenCaptureGL),
484 #[cfg(all(feature = "offscreen", feature = "webgl2"))]
485 OffscreenGL2(OffscreenCaptureGL2),
486}
487
488#[cfg(feature = "html-2d")]
489impl_enum_from!(HtmlCapture2D => BrowserCapture:Html2D);
490#[cfg(feature = "offscreen-2d")]
491impl_enum_from!(OffscreenCapture2D => BrowserCapture:Offscreen2D);
492#[cfg(all(feature = "html", feature = "webgl"))]
493impl_enum_from!(HtmlCaptureGL => BrowserCapture:HtmlGL);
494#[cfg(all(feature = "offscreen", feature = "webgl"))]
495impl_enum_from!(OffscreenCaptureGL => BrowserCapture:OffscreenGL);
496#[cfg(all(feature = "html", feature = "webgl2"))]
497impl_enum_from!(HtmlCaptureGL2 => BrowserCapture:HtmlGL2);
498#[cfg(all(feature = "offscreen", feature = "webgl2"))]
499impl_enum_from!(OffscreenCaptureGL2 => BrowserCapture:OffscreenGL2);
500
501#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub enum SupportedOptions {
503 #[cfg(feature = "html-2d")]
504 Html2D(HtmlContextOptions2D),
505 #[cfg(feature = "offscreen-2d")]
506 Offscreen2D(OffscreenContextOptions2D),
507 #[cfg(all(feature = "html", feature = "gl"))]
508 HtmlGL(HtmlContextOptionsGL),
509 #[cfg(all(feature = "offscreen", feature = "gl"))]
510 OffscreenGL(OffscreenContextOptionsGL),
511}
512
513#[cfg(feature = "html-2d")]
514impl_enum_from!(HtmlContextOptions2D => SupportedOptions:Html2D);
515#[cfg(feature = "offscreen-2d")]
516impl_enum_from!(OffscreenContextOptions2D => SupportedOptions:Offscreen2D);
517#[cfg(all(feature = "html", feature = "gl"))]
518impl_enum_from!(HtmlContextOptionsGL => SupportedOptions:HtmlGL);
519#[cfg(all(feature = "offscreen", feature = "gl"))]
520impl_enum_from!(OffscreenContextOptionsGL => SupportedOptions:OffscreenGL);
521
522impl From<BrowserCapture> for Box<dyn BrowserVideoCapture> {
523 fn from(value: BrowserCapture) -> Self {
524 match value {
525 #[cfg(feature = "html-2d")]
526 BrowserCapture::Html2D(c) => Box::new(c),
527 #[cfg(feature = "offscreen-2d")]
528 BrowserCapture::Offscreen2D(c) => Box::new(c),
529 #[cfg(all(feature = "html", feature = "webgl"))]
530 BrowserCapture::HtmlGL(c) => Box::new(c),
531 #[cfg(all(feature = "html", feature = "webgl2"))]
532 BrowserCapture::HtmlGL2(c) => Box::new(c),
533 #[cfg(all(feature = "offscreen", feature = "webgl"))]
534 BrowserCapture::OffscreenGL(c) => Box::new(c),
535 #[cfg(all(feature = "offscreen", feature = "webgl2"))]
536 BrowserCapture::OffscreenGL2(c) => Box::new(c),
537 }
538 }
539}
540
541#[allow(unused_variables)]
542impl CaptureArea for BrowserCapture {
543 enum_method!(capture_width () => u32);
544 enum_method!(capture_height () => u32);
545 enum_method!(set_capture_width (width: u32) => ());
546 enum_method!(set_capture_height (height: u32) => ());
547}
548
549#[allow(unused_variables)]
550impl BrowserVideoCapture for BrowserCapture {
551 enum_method!(channels_count () => u32);
552 enum_method!(buffer_size () => usize);
553 enum_method!(capture (source: &HtmlVideoElement, mode: CaptureMode) => (u32, u32));
554 enum_method!(retrieve (buffer: &mut [u8]) => ());
555 enum_method!(data () => Vec<u8>);
556 #[cfg(feature = "image")]
557 enum_method!(image () => Option<image::DynamicImage>);
558 enum_method!(read (source: &HtmlVideoElement, mode: CaptureMode) => Vec<u8>);
559 enum_method!(clear () => ());
560}