#![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 chrono::{DateTime, Local}; pub struct V4l2Cairo(T); impl V4l2Cairo{ pub fn new(inner: T) -> Self{ V4l2Cairo(inner) } } impl cgtk::Source for V4l2Cairo{ type Attr = Overlay; fn next(&mut self, fbpool: impl cgtk::FbSourceOnce) -> Result>{ 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( &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, } 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<()>{ let delay = Local::now() - &self.timestamp; widget.set_label( &format!("{} (delay={}ms)", self.timestamp.format("%H:%M:%S"), delay.num_milliseconds())); Ok(()) } }