summaryrefslogtreecommitdiff
path: root/src/v4l2cairo.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/v4l2cairo.rs')
-rw-r--r--src/v4l2cairo.rs121
1 files changed, 82 insertions, 39 deletions
diff --git a/src/v4l2cairo.rs b/src/v4l2cairo.rs
index 322a14b..83be3b1 100644
--- a/src/v4l2cairo.rs
+++ b/src/v4l2cairo.rs
@@ -1,88 +1,131 @@
use anyhow::{anyhow, Result};
-use crate::gtk;
-use crate::v4l2;
+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 chrono::{DateTime, Local};
-pub struct V4l2Cairo(v4l2::CaptStream);
-impl V4l2Cairo{
- pub fn new(inner: v4l2::CaptStream) -> Self{
+pub struct V4l2Cairo<T: v4l2abst::CaptStream>(T);
+impl<T: v4l2abst::CaptStream> V4l2Cairo<T>{
+ pub fn new(inner: T) -> Self{
V4l2Cairo(inner)
}
}
-impl gtk::Source for V4l2Cairo{
- type Attr = ();
- fn next(&mut self, fbpool: impl gtk::FbSourceOnce)
- -> Result<gtk::Packet<()>>{
+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);
- let (w, h) = (self.0.width(), self.0.height());
- let s = self.0.bytesperline();
- let pixelformat = self.0.pixelformat();
loop{
- let img = self.0.next(|frame, _|{
- if &pixelformat == "YUYV"{
- if w % 2 != 0{
- return Err(anyhow!("invalid width of YUYV"));
- }
- if frame.len() < w*h*2{
- return Err(anyhow!("invalid size of YUYV"));
- }
- let mut img = fbpool.take().unwrap().get(w, h)?;
+ 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..h).map(
- |y| (0..w).map(move |x|(x, y))).flatten(){
- let p = s*y + x*2;
+ 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(
- frame[p], frame[p/4*4 + 1], frame[p/4*4 + 3]);
+ 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)
- }else if &pixelformat == "MJPG" || &pixelformat == "JPEG"{
+ 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..frame.len()-1)
- .filter(|i| frame[*i] == 0xff && frame[i+1] == 0xd8)
+ 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(
- &frame[jindex..],
+ &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 != w || info.height as usize != h{
+ 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(w, h)?;
+ 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..h{
- imgslice[stride*y..stride*y+w*4]
- .copy_from_slice(&b[y*w*4..((y+1)*w)*4]);
+ 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)
+ Ok((img, timestamp))
}else{
unimplemented!()
}
})?;
- if let Ok(img) = img{
- return Ok(gtk::Packet{
+ if let Ok((img, timestamp)) = img{
+ return Ok(cgtk::Packet{
image: img.take_data()?,
- attr: (),
+ 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: &gtk::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: &gtk::Label) -> Result<()>{
+ widget.set_label(
+ &self.timestamp.format("%Y/%m/%d %H:%M:%S%.3f").to_string());
+ Ok(())
+ }
+}
+