From 6ecbf0d55695335f52d6fcf2b6a22ed45f5e4d99 Mon Sep 17 00:00:00 2001 From: dyknon Date: Mon, 24 Feb 2025 18:58:32 +0900 Subject: Remote camera capability. --- src/v4l2cairo.rs | 121 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 39 deletions(-) (limited to 'src/v4l2cairo.rs') 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); +impl V4l2Cairo{ + pub fn new(inner: T) -> Self{ V4l2Cairo(inner) } } -impl gtk::Source for V4l2Cairo{ - type Attr = (); - fn next(&mut self, fbpool: impl gtk::FbSourceOnce) - -> Result>{ +impl cgtk::Source for V4l2Cairo{ + type Attr = Overlay; + fn next(&mut self, fbpool: impl cgtk::FbSourceOnce) + -> Result>{ 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, +} +impl cgtk::Overlay for Overlay{ + type Widget = gtk::Label; + fn empty() -> Result{ + 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<()>{ + widget.set_label( + &self.timestamp.format("%Y/%m/%d %H:%M:%S%.3f").to_string()); + Ok(()) + } +} + -- cgit v1.2.3