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
|
#![cfg(feature = "gui")]
use anyhow::{anyhow, Result};
use gtk4 as gtk;
use gtk::prelude::*;
use crate::gtk as cgtk;
use crate::v4l2abst;
use crate::color::yuv2rgb;
use zune_jpeg::JpegDecoder as JpegDec;
use zune_jpeg::zune_core::options::DecoderOptions as JpegOptions;
use zune_jpeg::zune_core::colorspace::ColorSpace as JpegColorSpace;
use zune_jpeg::zune_core::bytestream::ZCursor as JpegCursor;
use chrono::{DateTime, Local};
pub struct V4l2Cairo<T: v4l2abst::CaptStream>(T);
impl<T: v4l2abst::CaptStream> V4l2Cairo<T>{
pub fn new(inner: T) -> Self{
V4l2Cairo(inner)
}
}
impl<T: v4l2abst::CaptStream + Send + 'static> cgtk::Source for V4l2Cairo<T>{
type Attr = Overlay;
fn next(&mut self, fbpool: impl cgtk::FbSourceOnce)
-> Result<cgtk::Packet<Overlay>>{
let mut fbpool = Some(fbpool);
loop{
let img = self.0.next(|frame|{
let v4l2abst::Frame{
format, width, height, stride: sstride, buf, timestamp
} = frame;
if &format == "YUYV"{
assert!(width % 2 == 0);
assert!(width * 2 <= sstride);
assert!(buf.len() >= sstride * height);
let mut img = fbpool.take().unwrap().get(width, height)?;
let stride: usize = img.stride().try_into()?;
let mut imgslice = img.data()?;
for (x, y) in (0..height).map(
|y| (0..width).map(move |x|(x, y))).flatten(){
let p = sstride*y + x*2;
let (r, g, b) = yuv2rgb(
buf[p], buf[p/4*4 + 1], buf[p/4*4 + 3]);
imgslice[stride*y + x*4 + 0] = b;
imgslice[stride*y + x*4 + 1] = g;
imgslice[stride*y + x*4 + 2] = r;
imgslice[stride*y + x*4 + 3] = 0;
}
drop(imgslice);
Ok((img, timestamp))
}else if &format == "MJPG" || &format == "JPEG"{
// Jpeg is not placed in start of slice in some situation.
// It is even possible that there are no Jpeg data.
let jindex = (0..buf.len()-1)
.filter(|i| buf[*i] == 0xff && buf[i+1] == 0xd8)
.next()
.ok_or(anyhow!("jpeg not found"))?;
let mut jpeg = JpegDec::new_with_options(
JpegCursor::new(&buf[jindex..]),
JpegOptions::new_fast()
.jpeg_set_out_colorspace(JpegColorSpace::BGRA));
let b = jpeg.decode()?;
let info = jpeg.info().unwrap();
if info.width as usize != width
|| info.height as usize != height
{
return Err(anyhow!("invalid size of jpeg"));
}
let mut img = fbpool.take().unwrap().get(width, height)?;
let stride: usize = img.stride().try_into()?;
let mut imgslice = img.data()?;
for y in 0..height{
imgslice[stride*y..stride*y+width*4]
.copy_from_slice(&b[y*width*4..((y+1)*width)*4]);
}
drop(imgslice);
Ok((img, timestamp))
}else{
unimplemented!()
}
})?;
if let Ok((img, timestamp)) = img{
return Ok(cgtk::Packet{
image: img.take_data()?,
attr: Overlay{ timestamp },
});
}
}
}
}
pub struct Overlay{
timestamp: DateTime<Local>,
}
impl cgtk::Overlay for Overlay{
type Widget = gtk::Label;
fn empty() -> Result<gtk::Label>{
Ok(gtk::Label::builder()
.label("...")
.valign(gtk::Align::End)
.halign(gtk::Align::Start)
.build())
}
fn activate(widget: >k::Label) -> Result<()>{
let disp = widget.display();
let style = gtk::CssProvider::new();
style.load_from_string(r#"
.v4l2cairo_label{
color: black;
text-shadow:
white 1px 1px,
white 1px 0,
white 1px -1px,
white 0 1px,
white 0 -1px,
white -1px 1px,
white -1px 0,
white -1px -1px;
}
"#);
gtk::style_context_add_provider_for_display(
&disp, &style, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
widget.add_css_class("v4l2cairo_label");
Ok(())
}
fn update(&self, widget: >k::Label) -> Result<()>{
let delay = Local::now() - &self.timestamp;
widget.set_label(
&format!("{} (delay={}ms)",
self.timestamp.format("%H:%M:%S"),
delay.num_milliseconds()));
Ok(())
}
}
|