use std::io; use std::path::PathBuf; use chrono::Utc; use opencv::{core, imgcodecs, imgproc, highgui, prelude::*}; // Simplified watermark overlay module for demo mod overlay { use anyhow::Result; use chrono::{DateTime, Utc}; use opencv::{core, imgproc, prelude::*}; use serde::{Deserialize, Serialize}; use freetype::{Library, Face}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum WatermarkPosition { TopLeft, TopRight, BottomLeft, BottomRight, Custom(u32, u32), } impl Default for WatermarkPosition { fn default() -> Self { Self::BottomLeft } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WatermarkOptions { pub enabled: bool, pub position: WatermarkPosition, pub font_scale: f64, pub thickness: i32, pub color: (u8, u8, u8, u8), pub background: bool, pub background_color: (u8, u8, u8, u8), pub padding: i32, } impl Default for WatermarkOptions { fn default() -> Self { Self { enabled: true, position: WatermarkPosition::BottomLeft, font_scale: 0.6, thickness: 1, color: (255, 255, 255, 255), // White background: true, background_color: (0, 0, 0, 128), // Semi-transparent black padding: 8, } } } pub struct WatermarkDemo { options: WatermarkOptions, gps_position: (f64, f64, f64), // lat, lon, alt temperature: f32, humidity: f32, freetype_lib: Library, font_face: Face, // Use a static lifetime for simplicity in demo } impl WatermarkDemo { pub fn new(options: WatermarkOptions) -> Result { let freetype_lib = Library::init()?; // Dynamically set font_path based on OS using conditional compilation let font_path = if cfg!(target_os = "windows") { "C:\\Windows\\Fonts\\arial.ttf" // Common Windows font } else if cfg!(target_os = "linux") { "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" // Common Linux font } else if cfg!(target_os = "macos") { "/System/Library/Fonts/Supplemental/Arial.ttf" // Common macOS font } else { // Fallback or error for other OS eprintln!("Warning: Could not determine a suitable font path for the current OS. Using a default that might not exist."); "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" // Default fallback }; let font_face = freetype_lib.new_face(font_path, 0)?; Ok(Self { options, gps_position: (34.0522, -118.2437, 85.0), // Default to Los Angeles temperature: 25.0, humidity: 60.0, freetype_lib, font_face, }) } pub fn set_gps(&mut self, lat: f64, lon: f64, alt: f64) { self.gps_position = (lat, lon, alt); } pub fn set_weather(&mut self, temp: f32, humidity: f32) { self.temperature = temp; self.humidity = humidity; } pub fn apply(&mut self, frame: &mut core::Mat, timestamp: DateTime) -> Result<()> { if !self.options.enabled { return Ok(()); } // Build text lines let mut lines = Vec::new(); // Timestamp lines.push(timestamp.format("%Y-%m-%d %H:%M:%S%.3f").to_string()); // GPS coordinates let (lat, lon, alt) = self.gps_position; lines.push(format!("Lat: {:.6}° Lon: {:.6}° Alt: {:.1}m", lat, lon, alt)); // Environment data lines.push(format!( "Temp: {:.1}°C Humidity: {:.1}%", self.temperature, self.humidity )); // Skip if no content if lines.is_empty() { return Ok(()); } // Get frame dimensions let width = frame.cols(); let height = frame.rows(); // Set font size based on options.font_scale (adjust as needed) let font_size = (self.options.font_scale * 30.0) as isize; // Example scaling self.font_face.set_pixel_sizes(0, font_size as u32)?; let padding = self.options.padding; let line_spacing = 4; // Space between lines // Calculate text size and total block size using FreeType let mut line_heights = Vec::with_capacity(lines.len()); let mut max_width = 0; let mut total_height = 0; for line in &lines { let mut line_width = 0; let mut line_height = 0; for char in line.chars() { self.font_face.load_char(char as usize, freetype::face::LoadFlag::RENDER)?; let glyph = self.font_face.glyph(); let metrics = glyph.metrics(); line_width += (metrics.horiAdvance / 64) as i32; line_height = line_height.max((metrics.height / 64) as i32); } line_heights.push(line_height); max_width = max_width.max(line_width); total_height += line_height; } let text_block_height = total_height + line_spacing * (lines.len() as i32 - 1) + padding * 2; let text_block_width = max_width + padding * 2; // Calculate watermark position let (x, y) = match self.options.position { WatermarkPosition::TopLeft => (padding, padding), WatermarkPosition::TopRight => (width - text_block_width - padding, padding), WatermarkPosition::BottomLeft => (padding, height - text_block_height - padding), WatermarkPosition::BottomRight => ( width - text_block_width - padding, height - text_block_height - padding ), WatermarkPosition::Custom(x, y) => (x as i32, y as i32), }; // Draw background rectangle if enabled if self.options.background { let bg_color = core::Scalar::new( self.options.background_color.0 as f64, self.options.background_color.1 as f64, self.options.background_color.2 as f64, self.options.background_color.3 as f64, ); let rect = core::Rect::new( x, y, text_block_width, text_block_height ); imgproc::rectangle( frame, rect, bg_color, -1, // Fill imgproc::LINE_8, 0, )?; } // Draw text lines using FreeType let text_color = core::Scalar::new( self.options.color.0 as f64, self.options.color.1 as f64, self.options.color.2 as f64, self.options.color.3 as f64, ); let mut current_y = y + padding; for (i, line) in lines.iter().enumerate() { let mut current_x = x + padding; for char in line.chars() { self.font_face.load_char(char as usize, freetype::face::LoadFlag::RENDER)?; let glyph = self.font_face.glyph(); let bitmap = glyph.bitmap(); let metrics = glyph.metrics(); let bitmap_width = bitmap.width() as usize; let bitmap_rows = bitmap.rows() as usize; let bitmap_buffer = bitmap.buffer(); // Calculate text position for this character let char_x = current_x + (metrics.horiBearingX / 64) as i32; let char_y = current_y + (line_heights[i] as f64 * 0.8) as i32 - (metrics.horiBearingY / 64) as i32; // Adjust baseline // Draw the glyph bitmap onto the frame for row in 0..bitmap_rows { for col in 0..bitmap_width { let bitmap_pixel = bitmap_buffer[row * bitmap.pitch() as usize + col]; if bitmap_pixel > 0 { let frame_x = char_x + col as i32; let frame_y = char_y + row as i32; // Ensure pixel is within frame bounds if frame_x >= 0 && frame_x < width && frame_y >= 0 && frame_y < height { let mut pixel = frame.at_2d_mut::(frame_y, frame_x)?; pixel[0] = text_color[0] as u8; pixel[1] = text_color[1] as u8; pixel[2] = text_color[2] as u8; } } } } current_x += (metrics.horiAdvance / 64) as i32; } current_y += line_heights[i] + line_spacing; } Ok(()) } } } // Simplified camera module for demo mod camera { use opencv::{core, prelude::*, videoio}; use chrono::{DateTime, Utc}; pub struct Frame { pub image: core::Mat, pub timestamp: DateTime, pub index: u64, } impl Frame { pub fn new(image: core::Mat, timestamp: DateTime, index: u64) -> Self { Self { image, timestamp, index } } } pub struct CameraDemo { capture: videoio::VideoCapture, frame_count: u64, } impl CameraDemo { pub fn new(device_id: i32) -> Result> { let capture = videoio::VideoCapture::new(device_id, videoio::CAP_ANY)?; if !capture.is_opened()? { return Err(format!("Failed to open camera device {}", device_id).into()); } Ok(Self { capture, frame_count: 0, }) } pub fn from_file(path: &str) -> Result> { let capture = videoio::VideoCapture::from_file(path, videoio::CAP_ANY)?; if !capture.is_opened()? { return Err(format!("Failed to open video file {}", path).into()); } Ok(Self { capture, frame_count: 0, }) } pub fn capture_frame(&mut self) -> Result> { let mut frame = core::Mat::default(); if self.capture.read(&mut frame)? { if frame.empty() { return Err("Captured frame is empty".into()); } let timestamp = Utc::now(); let index = self.frame_count; self.frame_count += 1; Ok(Frame::new(frame, timestamp, index)) } else { Err("Failed to capture frame".into()) } } } } fn main() -> Result<(), Box> { println!("*** Watermark Overlay Demo ***"); println!("This demo adds a watermark overlay to frames from a camera or video file"); println!("Press 'q' to quit"); println!("Press 'p' to change watermark position"); println!("Press 'c' to change GPS coordinates"); println!("Press 't' to change temperature and humidity"); println!(); println!("Choose input source:"); println!("1. Camera device"); println!("2. Video file"); let mut choice = String::new(); io::stdin().read_line(&mut choice)?; let mut camera_demo = match choice.trim() { "1" => { println!("Enter camera device ID (default is 0):"); let mut device_id = String::new(); io::stdin().read_line(&mut device_id)?; let device_id = device_id.trim().parse::().unwrap_or(0); println!("Opening camera device {}", device_id); camera::CameraDemo::new(device_id)? }, "2" => { println!("Enter video file path:"); let mut file_path = String::new(); io::stdin().read_line(&mut file_path)?; let file_path = file_path.trim(); println!("Opening video file: {}", file_path); camera::CameraDemo::from_file(file_path)? }, _ => { println!("Invalid choice, using default camera (device 0)"); camera::CameraDemo::new(0)? } }; // Create watermark demo let mut watermark_options = overlay::WatermarkOptions::default(); let mut watermark_demo = overlay::WatermarkDemo::new(watermark_options.clone())?; highgui::named_window("Watermark Overlay Demo", highgui::WINDOW_NORMAL)?; loop { match camera_demo.capture_frame() { Ok(frame) => { // Apply watermark to the frame let mut display_frame = frame.image.clone(); if let Err(e) = watermark_demo.apply(&mut display_frame, frame.timestamp) { println!("Error applying watermark: {}", e); } // Display the frame highgui::imshow("Watermark Overlay Demo", &display_frame)?; // Handle key presses let key = highgui::wait_key(30)?; match key as u8 as char { 'q' => break, // Quit 'p' => { // Cycle through positions watermark_options.position = match watermark_options.position { overlay::WatermarkPosition::TopLeft => overlay::WatermarkPosition::TopRight, overlay::WatermarkPosition::TopRight => overlay::WatermarkPosition::BottomRight, overlay::WatermarkPosition::BottomRight => overlay::WatermarkPosition::BottomLeft, overlay::WatermarkPosition::BottomLeft => overlay::WatermarkPosition::TopLeft, _ => overlay::WatermarkPosition::TopLeft, }; watermark_demo = overlay::WatermarkDemo::new(watermark_options.clone())?; println!("Changed watermark position: {:?}", watermark_options.position); }, 'c' => { // Change to some predefined GPS locations static LOCATIONS: [(f64, f64, f64, &str); 4] = [ (34.0522, -118.2437, 85.0, "Los Angeles"), (40.7128, -74.0060, 10.0, "New York"), (51.5074, -0.1278, 20.0, "London"), (35.6762, 139.6503, 40.0, "Tokyo"), ]; static mut LOCATION_INDEX: usize = 0; unsafe { LOCATION_INDEX = (LOCATION_INDEX + 1) % LOCATIONS.len(); let (lat, lon, alt, name) = LOCATIONS[LOCATION_INDEX]; watermark_demo.set_gps(lat, lon, alt); println!("Changed location to {}: Lat={}, Lon={}, Alt={}m", name, lat, lon, alt); } }, 't' => { // Cycle through different weather conditions static CONDITIONS: [(f32, f32, &str); 4] = [ (25.0, 60.0, "Warm and Humid"), (32.0, 80.0, "Hot and Very Humid"), (15.0, 40.0, "Cool and Dry"), (5.0, 30.0, "Cold and Dry"), ]; static mut CONDITION_INDEX: usize = 0; unsafe { CONDITION_INDEX = (CONDITION_INDEX + 1) % CONDITIONS.len(); let (temp, humidity, desc) = CONDITIONS[CONDITION_INDEX]; watermark_demo.set_weather(temp, humidity); println!("Changed weather to {}: Temp={} deg C, Humidity={}%", desc, temp, humidity); } }, _ => {} } }, Err(e) => { println!("Error capturing frame: {}", e); break; } } } highgui::destroy_all_windows()?; Ok(()) }