├── .gitignore ├── rustfmt.toml ├── blob ├── example.png └── example2.png ├── src ├── main.rs ├── channel.rs ├── app.rs └── handler.rs ├── Cargo.toml ├── README.md ├── LICENSE ├── flake.lock ├── flake.nix └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /blob/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlsxx/tuicam/HEAD/blob/example.png -------------------------------------------------------------------------------- /blob/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlsxx/tuicam/HEAD/blob/example2.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod channel; 3 | mod handler; 4 | 5 | use app::App; 6 | 7 | #[tokio::main(flavor = "multi_thread")] 8 | async fn main() -> Result<(), Box> { 9 | let mut terminal = ratatui::init(); 10 | 11 | opencv::core::set_log_level(opencv::core::LogLevel::LOG_LEVEL_SILENT)?; 12 | 13 | let app_result = App::try_new(&mut terminal).await?.run().await; 14 | 15 | ratatui::restore(); 16 | 17 | app_result 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tuicam" 3 | version = "0.0.3" 4 | edition = "2021" 5 | description = "Terminal-based camera with switchable modes" 6 | license = "MIT" 7 | repository = "https://github.com/hlsxx/tukai" 8 | 9 | [dependencies] 10 | ratatui = "0.29.0" 11 | tokio = { version = "1.41.0", features = ["full"] } 12 | crossterm = { version = "0.28.1", features = ["event-stream"] } 13 | futures = "0.3.31" 14 | opencv = { version = "0.94.2", features = ["videoio", "imgproc"] } 15 | 16 | [features] 17 | opencv_old = [] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Tuicam colorful 3 | Tuicam 4 |
5 | 6 | ## Requirements 7 | 8 | ### OpenCV 9 | Before using Tuicam, install [OpenCV](https://opencv.org/) on your computer. 10 | 11 | **Note:** If your OpenCV version is **< 4.6.0**, use the feature flag `opencv_old`: 12 | ```sh 13 | cargo install tuicam --features opencv_old 14 | ``` 15 | 16 | ## Installation 17 | 18 | ### Cargo 19 | Tuicam is available on [crates.io](https://crates.io/crates/tuicam). 20 | 21 | ``` 22 | cargo install tuicam 23 | ``` 24 | 25 | ### Nix 26 | ``` 27 | nix-shell -p tuicam 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /src/channel.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{crossterm::event::KeyEvent, text::Text}; 2 | 3 | pub enum AppEvent { 4 | // OpenCV mat (camera video frame) 5 | AsciiFrame(Text<'static>), 6 | 7 | // Crossterm KeyEvent 8 | Event(KeyEvent), 9 | 10 | // Crossterm Resize Event 11 | TerminalResize((u16, u16)), 12 | } 13 | 14 | pub struct Channel { 15 | // Receiver 16 | rx: tokio::sync::mpsc::UnboundedReceiver, 17 | 18 | // Transceiver 19 | tx: tokio::sync::mpsc::UnboundedSender, 20 | } 21 | 22 | impl Channel { 23 | /// Creates a unbounded channel 24 | pub fn new() -> Self { 25 | let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); 26 | 27 | Self { tx, rx } 28 | } 29 | 30 | /// Returns transceiver 31 | pub fn get_tx(&mut self) -> tokio::sync::mpsc::UnboundedSender { 32 | self.tx.clone() 33 | } 34 | 35 | /// Returns AppEvent 36 | pub async fn next(&mut self) -> Option { 37 | self.rx.recv().await 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 hlsxx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1737062831, 24 | "narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Tuicam - Terminal-based camera with switchable modes"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { 10 | self, 11 | nixpkgs, 12 | flake-utils, 13 | }: 14 | flake-utils.lib.eachDefaultSystem (system: let 15 | pkgs = import nixpkgs { 16 | inherit system; 17 | }; 18 | in { 19 | packages = { 20 | tuicam = pkgs.rustPlatform.buildRustPackage { 21 | pname = "tuicam"; 22 | version = "0.0.1"; 23 | src = ./.; 24 | cargoHash = "sha256-wGXRFCirIZ6tWYK73CZJD5Pzx1Nu5iooeZ+Esijfgtw="; 25 | 26 | nativeBuildInputs = with pkgs; [ 27 | pkg-config 28 | llvmPackages.llvm 29 | clang 30 | ]; 31 | 32 | buildInputs = with pkgs; [ 33 | opencv 34 | udev 35 | llvmPackages.libclang 36 | ]; 37 | 38 | LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; 39 | BINDGEN_EXTRA_CLANG_ARGS = ''-I${pkgs.llvmPackages.libclang.lib}/lib/clang/${pkgs.llvmPackages.libclang.version}/include''; 40 | RUSTFLAGS = ''--cfg=libclang_path="${pkgs.llvmPackages.libclang.lib}/lib"''; 41 | 42 | preBuildPhases = ["setEnvVars"]; 43 | setEnvVars = '' 44 | export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib" 45 | export BINDGEN_EXTRA_CLANG_ARGS="-I${pkgs.llvmPackages.libclang.lib}/lib/clang/${pkgs.llvmPackages.libclang.version}/include" 46 | ''; 47 | 48 | meta = with pkgs.lib; { 49 | description = "Terminal-based camera with switchable modes"; 50 | license = licenses.mit; 51 | maintainers = with maintainers; [hlsxx]; 52 | platforms = platforms.linux; 53 | }; 54 | }; 55 | }; 56 | 57 | apps = { 58 | default = flake-utils.lib.mkApp { 59 | drv = self.packages.${system}.tuicam; 60 | program = "tuicam"; 61 | }; 62 | }; 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crossterm::event::KeyModifiers; 3 | use tokio::sync::RwLock; 4 | 5 | use crate::{channel::AppEvent, handler::CamWindowScale}; 6 | 7 | use ratatui::{ 8 | crossterm::event::KeyCode, 9 | layout::{Alignment, Constraint, Direction, Flex, Layout}, 10 | style::{Color, Style, Stylize}, 11 | text::{Line, Span, Text}, 12 | widgets::{Block, BorderType, Clear, Paragraph}, 13 | DefaultTerminal, 14 | }; 15 | 16 | use crate::{ 17 | channel::Channel, 18 | handler::{EventHandler, FrameHandler, FrameHandlerConfig, ImageConvertType}, 19 | }; 20 | 21 | /// Camera TUI frame border color 22 | const PRIMARY_COLOR: Color = Color::Rgb(230, 143, 106); 23 | 24 | pub const ASCII_CHARS: &[char] = &['█', '▓', '▒', '░', ' ']; 25 | 26 | pub struct App<'a> { 27 | // Base terminal 28 | terminal: &'a mut DefaultTerminal, 29 | 30 | // App channel 31 | channel: Channel, 32 | 33 | // Frame buffer (video buffer) 34 | frame_buffer: Text<'static>, 35 | 36 | // Frame handler config (for a switchable image proccessing modes) 37 | frame_handler_config: Arc>, 38 | } 39 | 40 | impl<'a> App<'a> { 41 | /// Try to create new app 42 | /// 43 | /// App includes run method (render TUI) 44 | /// Try to creates a frame handler and event handler 45 | pub async fn try_new( 46 | terminal: &'a mut DefaultTerminal, 47 | ) -> Result> { 48 | let mut channel = Channel::new(); 49 | let terminal_size = terminal.size()?; 50 | 51 | let frame_handler_config = Arc::new(RwLock::new(FrameHandlerConfig::new(terminal_size))); 52 | 53 | let frame_handler = 54 | FrameHandler::try_new(frame_handler_config.clone(), channel.get_tx()).await?; 55 | 56 | frame_handler.run().await?; 57 | 58 | let _event_handler = EventHandler::new(channel.get_tx()); 59 | 60 | Ok(Self { 61 | terminal, 62 | channel, 63 | frame_buffer: Text::default(), 64 | frame_handler_config, 65 | }) 66 | } 67 | 68 | /// Runs TUI application 69 | /// 70 | /// Handles user events 71 | /// 72 | /// Renders widgets into frames 73 | pub async fn run(&mut self) -> Result<(), Box> { 74 | self.terminal.clear()?; 75 | 76 | loop { 77 | let terminal_size = self.terminal.size()?; 78 | 79 | if let Some(app_event) = self.channel.next().await { 80 | match app_event { 81 | AppEvent::AsciiFrame(ascii_frame) => self.frame_buffer = ascii_frame, 82 | AppEvent::Event(key_event) => { 83 | if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char(' ') { 84 | self.toggle_lock().await; 85 | } 86 | 87 | if !self.frame_handler_config.read().await.is_locked { 88 | match key_event.code { 89 | KeyCode::Char('m') => self.switch_mode().await, 90 | KeyCode::Char('f') => self.switch_cam_window_scale().await, 91 | KeyCode::Char('c') => self.switch_cam().await, 92 | KeyCode::Esc => break, 93 | _ => {} 94 | } 95 | } 96 | }, 97 | AppEvent::TerminalResize((width, height)) => { 98 | self.frame_handler_config.write().await.terminal_size = (width, height); 99 | } 100 | } 101 | } 102 | 103 | let cam_window_scale = self 104 | .frame_handler_config 105 | .read() 106 | .await 107 | .cam_window_scale 108 | .clone() as u16; 109 | 110 | let is_locked = self.frame_handler_config.read().await.is_locked; 111 | 112 | self.terminal.draw(|frame| { 113 | let area = frame.area(); 114 | 115 | let chunks = Layout::new( 116 | Direction::Vertical, 117 | [Constraint::Percentage(100), Constraint::Length(2)], 118 | ) 119 | .split(area); 120 | 121 | let top_chunk = chunks[0]; 122 | let bottom_chunk = chunks[1]; 123 | 124 | let block = Block::bordered() 125 | .border_style(Style::default().fg(PRIMARY_COLOR)) 126 | .title_style(Style::default()) 127 | .title_alignment(Alignment::Center) 128 | .border_type(BorderType::Rounded); 129 | 130 | let cam_paragraph = Paragraph::new(self.frame_buffer.clone()) 131 | .block(block) 132 | .alignment(Alignment::Center) 133 | .centered(); 134 | 135 | let horizontal = 136 | Layout::horizontal([Constraint::Length(terminal_size.width / cam_window_scale)]) 137 | .flex(Flex::Center); 138 | 139 | let vertical = 140 | Layout::vertical([Constraint::Length(terminal_size.height / cam_window_scale)]) 141 | .flex(Flex::Center); 142 | 143 | let [top_chunk] = vertical.areas(top_chunk); 144 | let [top_chunk] = horizontal.areas(top_chunk); 145 | 146 | let tools_text = Text::from(vec![Line::from(vec![ 147 | Span::from("ESC").bold(), 148 | Span::from(" exit | "), 149 | Span::from("m").bold(), 150 | Span::from(" switch mode | "), 151 | Span::from("c").bold(), 152 | Span::from(" switch camera | "), 153 | Span::from("f").bold(), 154 | Span::from(" toggle fullscreen | "), 155 | Span::from("ctrl-").bold(), 156 | Span::from(" toggle lock"), 157 | ]) 158 | .style(Style::default().fg(PRIMARY_COLOR))]); 159 | 160 | let tools_paragraph = Paragraph::new(tools_text) 161 | .alignment(Alignment::Center) 162 | .centered(); 163 | 164 | frame.render_widget(Clear, top_chunk); 165 | frame.render_widget(cam_paragraph, top_chunk); 166 | 167 | if !is_locked { 168 | frame.render_widget(tools_paragraph, bottom_chunk); 169 | } 170 | })?; 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | /// Switches a camera mode. 177 | /// 178 | /// Startup mode: Image -> GrayScale -> ASCII 179 | /// Switch: Image -> GrayScale -> Threshold -> ASCII 180 | pub async fn switch_mode(&mut self) { 181 | let new_image_convert_type = match self.frame_handler_config.read().await.image_convert_type { 182 | ImageConvertType::ColorfulHalfBlock => ImageConvertType::Colorful, 183 | ImageConvertType::Colorful => ImageConvertType::GrayScale, 184 | ImageConvertType::GrayScale => ImageConvertType::GrayScaleThreshold, 185 | ImageConvertType::GrayScaleThreshold => ImageConvertType::Threshold, 186 | ImageConvertType::Threshold => ImageConvertType::ColorfulHalfBlock, 187 | }; 188 | 189 | self.frame_handler_config.write().await.image_convert_type = new_image_convert_type; 190 | } 191 | 192 | /// Toggles lock mode. 193 | /// 194 | /// Hide/Show the bottom inctruction widget. 195 | /// Disables keyboard events. 196 | pub async fn toggle_lock(&mut self) { 197 | let mut config = self.frame_handler_config.write().await; 198 | config.is_locked = !config.is_locked; 199 | } 200 | 201 | /// Switches a camera window scale. 202 | /// 203 | /// Startup mode: Small 204 | /// Switch: Full 205 | pub async fn switch_cam_window_scale(&mut self) { 206 | let cam_window_scale = match self.frame_handler_config.read().await.cam_window_scale { 207 | CamWindowScale::Small => CamWindowScale::Full, 208 | CamWindowScale::Full => CamWindowScale::Small, 209 | }; 210 | 211 | self.frame_handler_config.write().await.cam_window_scale = cam_window_scale; 212 | } 213 | 214 | /// Switches a device camera 215 | pub async fn switch_cam(&mut self) { 216 | self.frame_handler_config.write().await.camera.switch(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/handler.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use crossterm::event::{Event, EventStream}; 4 | use futures::{FutureExt, StreamExt}; 5 | use ratatui::{ 6 | layout::Size, 7 | style::{Color, Style}, 8 | text::{Line, Span, Text}, 9 | }; 10 | 11 | use tokio::sync::RwLock; 12 | 13 | use opencv::{ 14 | imgproc, 15 | prelude::*, 16 | videoio::{self, VideoCapture, VideoCaptureTrait}, 17 | }; 18 | 19 | #[cfg(not(feature = "opencv_old"))] 20 | use opencv::core::AlgorithmHint; 21 | 22 | use crate::app::ASCII_CHARS; 23 | use crate::channel::AppEvent; 24 | 25 | type TerminalSize = (u16, u16); 26 | 27 | #[derive(Eq, PartialEq, Clone)] 28 | #[allow(unused)] 29 | pub enum ImageConvertType { 30 | ColorfulHalfBlock, 31 | Colorful, 32 | GrayScale, 33 | GrayScaleThreshold, 34 | Threshold, 35 | } 36 | 37 | /// Camera window frame scale 38 | #[derive(Clone)] 39 | pub enum CamWindowScale { 40 | Full = 1, 41 | Small = 2, 42 | } 43 | 44 | /// Camera contains all available device cameras 45 | pub struct Camera { 46 | pub(crate) active_index: Option, 47 | pub(crate) ids: Vec, 48 | } 49 | 50 | impl Camera { 51 | pub fn default() -> Self { 52 | let ids = (0..=10) 53 | .filter_map(|id| { 54 | if let Ok(cam) = VideoCapture::new(id, videoio::CAP_ANY) { 55 | if cam.is_opened().unwrap_or(false) { 56 | return Some(id); 57 | } else { 58 | return None; 59 | } 60 | } 61 | 62 | None 63 | }) 64 | .collect::>(); 65 | 66 | Self { 67 | active_index: Some(0), 68 | ids, 69 | } 70 | } 71 | 72 | /// Switches a current camera 73 | pub fn switch(&mut self) { 74 | if let Some(active_index) = self.active_index.as_mut() { 75 | if self.ids.len() > (*active_index + 1) as usize { 76 | *active_index += 1; 77 | } else { 78 | *active_index = 0; 79 | } 80 | } 81 | } 82 | 83 | /// Return a current active camera id 84 | pub fn get_cam_id(&self) -> Option<&i32> { 85 | if let Some(active_index) = self.active_index.as_ref() { 86 | self.ids.get(*active_index as usize) 87 | } else { 88 | None 89 | } 90 | } 91 | } 92 | 93 | /// Frame handler config 94 | pub struct FrameHandlerConfig { 95 | /// Image convert type (camera mode) 96 | pub image_convert_type: ImageConvertType, 97 | 98 | /// Terminal size (width, height) 99 | pub terminal_size: TerminalSize, 100 | 101 | /// Camera window scale 102 | pub cam_window_scale: CamWindowScale, 103 | 104 | /// Active camera id 105 | pub camera: Camera, 106 | 107 | /// Event handlers are locked 108 | pub is_locked: bool 109 | } 110 | 111 | impl FrameHandlerConfig { 112 | pub fn new(terminal_size: Size) -> Self { 113 | Self { 114 | image_convert_type: ImageConvertType::ColorfulHalfBlock, 115 | terminal_size: (terminal_size.width, terminal_size.height), 116 | cam_window_scale: CamWindowScale::Small, 117 | camera: Camera::default(), 118 | is_locked: false 119 | } 120 | } 121 | } 122 | 123 | /// Converts a frame into a grayscale. 124 | fn convert_into_grayscale(frame: &opencv::core::Mat, res_frame: &mut opencv::core::Mat) { 125 | #[cfg(not(feature = "opencv_old"))] 126 | { 127 | imgproc::cvt_color( 128 | frame, 129 | res_frame, 130 | imgproc::COLOR_BGR2GRAY, 131 | 0, 132 | AlgorithmHint::ALGO_HINT_DEFAULT, 133 | ) 134 | .unwrap(); 135 | } 136 | 137 | #[cfg(feature = "opencv_old")] 138 | { 139 | imgproc::cvt_color(frame, res_frame, imgproc::COLOR_BGR2GRAY, 0).unwrap(); 140 | } 141 | } 142 | 143 | /// Computes the distance between two colors 144 | fn color_dist(lhs: &[u8; 3], rhs: &[u8; 3]) -> u32 { 145 | let x = lhs[0].abs_diff(rhs[0]) as u32; 146 | let y = lhs[1].abs_diff(rhs[1]) as u32; 147 | let z = lhs[2].abs_diff(rhs[2]) as u32; 148 | x * x + y * y + z * z 149 | } 150 | 151 | /// Computes the distance between two colors 152 | fn color_average(colors: [[u8; 3]; N]) -> [u8; 3] { 153 | let x = colors.iter().map(|color| color[0] as u32).sum::() / (N as u32); 154 | let y = colors.iter().map(|color| color[1] as u32).sum::() / (N as u32); 155 | let z = colors.iter().map(|color| color[2] as u32).sum::() / (N as u32); 156 | [x as u8, y as u8, z as u8] 157 | } 158 | 159 | /// Converts a camera frame into ASCII frame. 160 | /// 161 | /// This method resizes the frame to a smaller size and then converts each pixel 162 | /// into an ASCII character based on its intensity. The intensity is calculated 163 | /// from the pixel's RGB values (Colorful), and the corresponding ASCII character is inserted 164 | /// based on that intensity. 165 | pub fn convert_frame_into_ascii( 166 | frame: opencv::core::Mat, 167 | image_convert_type: ImageConvertType, 168 | ) -> Text<'static> { 169 | let mut lines = Vec::new(); 170 | 171 | let (width, height) = match image_convert_type { 172 | ImageConvertType::ColorfulHalfBlock => (frame.cols() / 2, frame.rows() / 2), 173 | _ => (frame.cols(), frame.rows()), 174 | }; 175 | 176 | for y in 0..height { 177 | let mut spans = Vec::new(); 178 | for x in 0..width { 179 | let (ascii_char, fg_color, bg_color) = match image_convert_type { 180 | ImageConvertType::ColorfulHalfBlock => { 181 | // Sub-character colors are arranged as shown below. 182 | // 183 | // 0 | 1 184 | // --+-- 185 | // 2 | 3 186 | let subpixels: [_; 4] = std::array::from_fn(|i| { 187 | let i = i as i32; 188 | 189 | let pixel = frame 190 | .at_2d::(y * 2 + i / 2, x * 2 + i % 2) 191 | .cloned() 192 | .unwrap_or(opencv::core::Vec3b::from([255, 255, 255])); 193 | 194 | [pixel[0], pixel[1], pixel[2]] 195 | }); 196 | 197 | // Find the two nearest subpixels. These two will define 198 | // the foreground color. 199 | let (fg1, fg2, other1, other2) = [ 200 | (0, 1, 2, 3), 201 | (0, 2, 1, 3), 202 | (0, 3, 1, 2), 203 | (1, 2, 0, 3), 204 | (1, 3, 0, 2), 205 | (2, 3, 0, 1), 206 | ] 207 | .into_iter() 208 | .min_by_key(|(i, j, _, _)| color_dist(&subpixels[*i], &subpixels[*j])) 209 | .unwrap(); 210 | 211 | let fg_color = color_average([subpixels[fg1], subpixels[fg2]]); 212 | 213 | // Of the two remaining colors, is one of them closer to the 214 | // foreground color than they are to each other? 215 | let dist_remaining = color_dist(&subpixels[other1], &subpixels[other2]); 216 | let dist_1 = color_dist(&fg_color, &subpixels[other1]); 217 | let dist_2 = color_dist(&fg_color, &subpixels[other2]); 218 | 219 | let (fg_char, fg_color, bg_color) = if dist_remaining < dist_1 && dist_remaining < dist_2 220 | { 221 | // The two remaining colors are closer to each other 222 | // than they are to the foreground color. They will 223 | // both share the same background color. 224 | let bg_color = color_average([subpixels[other1], subpixels[other2]]); 225 | let fg_char = match (fg1, fg2) { 226 | (0, 1) => '▀', 227 | (0, 2) => '▌', 228 | (0, 3) => '▚', 229 | (1, 2) => '▞', 230 | (1, 3) => '▐', 231 | (2, 3) => '▄', 232 | _ => unreachable!(), 233 | }; 234 | (fg_char, fg_color, bg_color) 235 | } else if dist_1 < dist_2 { 236 | // The point at `other1` is close to the fg color. 237 | // Therefore, average those three into the foreground, and 238 | // use the background for the last color. 239 | let fg_color = color_average([subpixels[fg1], subpixels[fg2], subpixels[other1]]); 240 | let bg_color = subpixels[other2]; 241 | let fg_char = match other2 { 242 | 0 => '▟', 243 | 1 => '▙', 244 | 2 => '▜', 245 | 3 => '▛', 246 | _ => unreachable!(), 247 | }; 248 | 249 | (fg_char, fg_color, bg_color) 250 | } else { 251 | // The point at `other2` is close to the fg color. 252 | // Therefore, average those three into the foreground, and 253 | // use the background for the last color. 254 | let fg_color = color_average([subpixels[fg1], subpixels[fg2], subpixels[other2]]); 255 | let bg_color = subpixels[other1]; 256 | let fg_char = match other1 { 257 | 0 => '▟', 258 | 1 => '▙', 259 | 2 => '▜', 260 | 3 => '▛', 261 | _ => unreachable!(), 262 | }; 263 | (fg_char, fg_color, bg_color) 264 | }; 265 | 266 | ( 267 | fg_char, 268 | Color::Rgb(fg_color[2], fg_color[1], fg_color[0]), 269 | Color::Rgb(bg_color[2], bg_color[1], bg_color[0]), 270 | ) 271 | } 272 | ImageConvertType::Colorful => { 273 | let pixel = frame.at_2d::(y, x).unwrap(); 274 | ('█', Color::Rgb(pixel[2], pixel[1], pixel[0]), Color::Reset) 275 | } 276 | ImageConvertType::GrayScale => { 277 | let intensity = frame.at_2d::(y, x).unwrap(); 278 | 279 | ( 280 | '█', 281 | Color::Rgb(*intensity, *intensity, *intensity), 282 | Color::Reset, 283 | ) 284 | } 285 | ImageConvertType::GrayScaleThreshold => { 286 | let intensity = frame.at_2d::(y, x).unwrap(); 287 | let char_index = 288 | (*intensity as f32 * (ASCII_CHARS.len() - 1) as f32 / 255.0).round() as usize; 289 | 290 | ( 291 | ASCII_CHARS[char_index], 292 | Color::Rgb(255, 255, 255), 293 | Color::Reset, 294 | ) 295 | } 296 | ImageConvertType::Threshold => { 297 | let intensity = frame.at_2d::(y, x).unwrap(); 298 | ( 299 | if *intensity > 150 { '█' } else { ' ' }, 300 | Color::Rgb(255, 255, 255), 301 | Color::Reset, 302 | ) 303 | } 304 | }; 305 | 306 | let style = Style::default().fg(fg_color).bg(bg_color); 307 | spans.push(Span::from(ascii_char.to_string()).style(style)); 308 | } 309 | 310 | lines.push(Line::from(spans)); 311 | } 312 | 313 | Text::from(lines) 314 | } 315 | 316 | pub struct FrameHandler { 317 | config: Arc>, 318 | tx: tokio::sync::mpsc::UnboundedSender, 319 | } 320 | 321 | impl FrameHandler { 322 | pub async fn try_new( 323 | config: Arc>, 324 | tx: tokio::sync::mpsc::UnboundedSender, 325 | ) -> opencv::Result { 326 | Ok(Self { config, tx }) 327 | } 328 | 329 | pub fn get_cam(&self, cam_id: i32, cam: &mut Option) { 330 | *cam = Some(VideoCapture::new(cam_id, videoio::CAP_ANY).unwrap()); 331 | } 332 | 333 | /// Spawns a new Tokio task. 334 | /// 335 | /// This task opens a device camera, captures a frame, and resizes the image. 336 | /// If frame is a GrayScale or Threshold converts into approriate format 337 | pub async fn run(self) -> opencv::Result<()> { 338 | let _handle = tokio::spawn(async move { 339 | let (mut cam, mut active_cam_id) = (None, -1); 340 | 341 | let mut frame = opencv::core::Mat::default(); 342 | let mut interval = tokio::time::interval(Duration::from_millis(50)); 343 | 344 | loop { 345 | let mut small_frame = opencv::core::Mat::default(); 346 | 347 | let current_cam_id = self 348 | .config 349 | .read() 350 | .await 351 | .camera 352 | .get_cam_id() 353 | .unwrap() 354 | .clone(); 355 | 356 | if current_cam_id != active_cam_id { 357 | self.get_cam(current_cam_id, &mut cam); 358 | active_cam_id = current_cam_id; 359 | } 360 | 361 | cam.as_mut().unwrap().read(&mut frame).unwrap(); 362 | 363 | let cam_size = { 364 | let config = self.config.read().await; 365 | 366 | let cam_size = opencv::core::Size { 367 | width: (config.terminal_size.0 / config.cam_window_scale.clone() as u16) as i32, 368 | height: (config.terminal_size.1 / config.cam_window_scale.clone() as u16) as i32, 369 | }; 370 | 371 | match config.image_convert_type { 372 | ImageConvertType::ColorfulHalfBlock => opencv::core::Size { 373 | width: cam_size.width * 2, 374 | height: cam_size.height * 2, 375 | }, 376 | _ => cam_size, 377 | } 378 | }; 379 | 380 | // Some virtual cams crash on the resize call. 381 | // If some error occurs just switch to an another cam. 382 | if let Err(_) = opencv::imgproc::resize( 383 | &frame, 384 | &mut small_frame, 385 | cam_size, 386 | 0.0, 387 | 0.0, 388 | opencv::imgproc::INTER_LINEAR, 389 | ) { 390 | self.config.write().await.camera.switch(); 391 | continue; 392 | } 393 | 394 | let config = self.config.read().await; 395 | let res_frame = match config.image_convert_type { 396 | ImageConvertType::Colorful | ImageConvertType::ColorfulHalfBlock => small_frame.clone(), 397 | ImageConvertType::GrayScale | ImageConvertType::GrayScaleThreshold => { 398 | let mut gray_frame = opencv::core::Mat::default(); 399 | convert_into_grayscale(&small_frame, &mut gray_frame); 400 | gray_frame 401 | } 402 | ImageConvertType::Threshold => { 403 | let mut gray_frame = opencv::core::Mat::default(); 404 | let mut binary_frame = opencv::core::Mat::default(); 405 | 406 | convert_into_grayscale(&small_frame, &mut gray_frame); 407 | 408 | imgproc::threshold( 409 | &gray_frame, 410 | &mut binary_frame, 411 | 128.0, 412 | 255.0, 413 | imgproc::THRESH_BINARY, 414 | ) 415 | .unwrap(); 416 | 417 | binary_frame 418 | } 419 | }; 420 | 421 | let ascii_frame = convert_frame_into_ascii(res_frame, config.image_convert_type.clone()); 422 | 423 | if self.tx.send(AppEvent::AsciiFrame(ascii_frame)).is_err() { 424 | break; 425 | } 426 | 427 | interval.tick().await; 428 | } 429 | }); 430 | 431 | Ok(()) 432 | } 433 | } 434 | 435 | #[allow(unused)] 436 | pub struct EventHandler(pub tokio::task::JoinHandle<()>); 437 | 438 | impl EventHandler { 439 | /// Spawns a new Tokio task. 440 | /// 441 | /// This task waits on the Crossbeam event occur. 442 | /// The event consists of either a key event or a resize event. 443 | pub fn new(tx: tokio::sync::mpsc::UnboundedSender) -> Self { 444 | let handle = tokio::spawn(async move { 445 | let mut reader = EventStream::new(); 446 | 447 | loop { 448 | let crossterm_event = reader.next().fuse().await; 449 | 450 | if let Some(Ok(event)) = crossterm_event { 451 | match event { 452 | Event::Key(key_code) => tx.send(AppEvent::Event(key_code)).unwrap(), 453 | Event::Resize(width, height) => tx.send(AppEvent::TerminalResize((width, height))).unwrap(), 454 | _ => {} 455 | } 456 | } 457 | } 458 | }); 459 | 460 | Self(handle) 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "allocator-api2" 31 | version = "0.2.21" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.4.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 40 | 41 | [[package]] 42 | name = "backtrace" 43 | version = "0.3.74" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 46 | dependencies = [ 47 | "addr2line", 48 | "cfg-if", 49 | "libc", 50 | "miniz_oxide", 51 | "object", 52 | "rustc-demangle", 53 | "windows-targets 0.52.6", 54 | ] 55 | 56 | [[package]] 57 | name = "bitflags" 58 | version = "2.8.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 61 | 62 | [[package]] 63 | name = "bytes" 64 | version = "1.9.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 67 | 68 | [[package]] 69 | name = "cassowary" 70 | version = "0.3.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 73 | 74 | [[package]] 75 | name = "castaway" 76 | version = "0.2.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 79 | dependencies = [ 80 | "rustversion", 81 | ] 82 | 83 | [[package]] 84 | name = "cc" 85 | version = "1.2.10" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" 88 | dependencies = [ 89 | "jobserver", 90 | "libc", 91 | "shlex", 92 | ] 93 | 94 | [[package]] 95 | name = "cfg-if" 96 | version = "1.0.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 99 | 100 | [[package]] 101 | name = "clang" 102 | version = "2.0.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" 105 | dependencies = [ 106 | "clang-sys", 107 | "libc", 108 | ] 109 | 110 | [[package]] 111 | name = "clang-sys" 112 | version = "1.8.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 115 | dependencies = [ 116 | "glob", 117 | "libc", 118 | ] 119 | 120 | [[package]] 121 | name = "compact_str" 122 | version = "0.8.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 125 | dependencies = [ 126 | "castaway", 127 | "cfg-if", 128 | "itoa", 129 | "rustversion", 130 | "ryu", 131 | "static_assertions", 132 | ] 133 | 134 | [[package]] 135 | name = "crossterm" 136 | version = "0.28.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 139 | dependencies = [ 140 | "bitflags", 141 | "crossterm_winapi", 142 | "futures-core", 143 | "mio", 144 | "parking_lot", 145 | "rustix", 146 | "signal-hook", 147 | "signal-hook-mio", 148 | "winapi", 149 | ] 150 | 151 | [[package]] 152 | name = "crossterm_winapi" 153 | version = "0.9.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 156 | dependencies = [ 157 | "winapi", 158 | ] 159 | 160 | [[package]] 161 | name = "darling" 162 | version = "0.20.10" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 165 | dependencies = [ 166 | "darling_core", 167 | "darling_macro", 168 | ] 169 | 170 | [[package]] 171 | name = "darling_core" 172 | version = "0.20.10" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 175 | dependencies = [ 176 | "fnv", 177 | "ident_case", 178 | "proc-macro2", 179 | "quote", 180 | "strsim", 181 | "syn", 182 | ] 183 | 184 | [[package]] 185 | name = "darling_macro" 186 | version = "0.20.10" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 189 | dependencies = [ 190 | "darling_core", 191 | "quote", 192 | "syn", 193 | ] 194 | 195 | [[package]] 196 | name = "dunce" 197 | version = "1.0.5" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 200 | 201 | [[package]] 202 | name = "either" 203 | version = "1.13.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 206 | 207 | [[package]] 208 | name = "equivalent" 209 | version = "1.0.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 212 | 213 | [[package]] 214 | name = "errno" 215 | version = "0.3.10" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 218 | dependencies = [ 219 | "libc", 220 | "windows-sys 0.59.0", 221 | ] 222 | 223 | [[package]] 224 | name = "fnv" 225 | version = "1.0.7" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 228 | 229 | [[package]] 230 | name = "foldhash" 231 | version = "0.1.4" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 234 | 235 | [[package]] 236 | name = "futures" 237 | version = "0.3.31" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 240 | dependencies = [ 241 | "futures-channel", 242 | "futures-core", 243 | "futures-executor", 244 | "futures-io", 245 | "futures-sink", 246 | "futures-task", 247 | "futures-util", 248 | ] 249 | 250 | [[package]] 251 | name = "futures-channel" 252 | version = "0.3.31" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 255 | dependencies = [ 256 | "futures-core", 257 | "futures-sink", 258 | ] 259 | 260 | [[package]] 261 | name = "futures-core" 262 | version = "0.3.31" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 265 | 266 | [[package]] 267 | name = "futures-executor" 268 | version = "0.3.31" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 271 | dependencies = [ 272 | "futures-core", 273 | "futures-task", 274 | "futures-util", 275 | ] 276 | 277 | [[package]] 278 | name = "futures-io" 279 | version = "0.3.31" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 282 | 283 | [[package]] 284 | name = "futures-macro" 285 | version = "0.3.31" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "futures-sink" 296 | version = "0.3.31" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 299 | 300 | [[package]] 301 | name = "futures-task" 302 | version = "0.3.31" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 305 | 306 | [[package]] 307 | name = "futures-util" 308 | version = "0.3.31" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 311 | dependencies = [ 312 | "futures-channel", 313 | "futures-core", 314 | "futures-io", 315 | "futures-macro", 316 | "futures-sink", 317 | "futures-task", 318 | "memchr", 319 | "pin-project-lite", 320 | "pin-utils", 321 | "slab", 322 | ] 323 | 324 | [[package]] 325 | name = "gimli" 326 | version = "0.31.1" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 329 | 330 | [[package]] 331 | name = "glob" 332 | version = "0.3.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 335 | 336 | [[package]] 337 | name = "hashbrown" 338 | version = "0.15.2" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 341 | dependencies = [ 342 | "allocator-api2", 343 | "equivalent", 344 | "foldhash", 345 | ] 346 | 347 | [[package]] 348 | name = "heck" 349 | version = "0.5.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 352 | 353 | [[package]] 354 | name = "ident_case" 355 | version = "1.0.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 358 | 359 | [[package]] 360 | name = "indoc" 361 | version = "2.0.5" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 364 | 365 | [[package]] 366 | name = "instability" 367 | version = "0.3.7" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" 370 | dependencies = [ 371 | "darling", 372 | "indoc", 373 | "proc-macro2", 374 | "quote", 375 | "syn", 376 | ] 377 | 378 | [[package]] 379 | name = "itertools" 380 | version = "0.13.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 383 | dependencies = [ 384 | "either", 385 | ] 386 | 387 | [[package]] 388 | name = "itoa" 389 | version = "1.0.14" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 392 | 393 | [[package]] 394 | name = "jobserver" 395 | version = "0.1.32" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 398 | dependencies = [ 399 | "libc", 400 | ] 401 | 402 | [[package]] 403 | name = "libc" 404 | version = "0.2.169" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 407 | 408 | [[package]] 409 | name = "linux-raw-sys" 410 | version = "0.4.15" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 413 | 414 | [[package]] 415 | name = "lock_api" 416 | version = "0.4.12" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 419 | dependencies = [ 420 | "autocfg", 421 | "scopeguard", 422 | ] 423 | 424 | [[package]] 425 | name = "log" 426 | version = "0.4.25" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 429 | 430 | [[package]] 431 | name = "lru" 432 | version = "0.12.5" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 435 | dependencies = [ 436 | "hashbrown", 437 | ] 438 | 439 | [[package]] 440 | name = "memchr" 441 | version = "2.7.4" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 444 | 445 | [[package]] 446 | name = "miniz_oxide" 447 | version = "0.8.3" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" 450 | dependencies = [ 451 | "adler2", 452 | ] 453 | 454 | [[package]] 455 | name = "mio" 456 | version = "1.0.3" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 459 | dependencies = [ 460 | "libc", 461 | "log", 462 | "wasi", 463 | "windows-sys 0.52.0", 464 | ] 465 | 466 | [[package]] 467 | name = "num-traits" 468 | version = "0.2.19" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 471 | dependencies = [ 472 | "autocfg", 473 | ] 474 | 475 | [[package]] 476 | name = "object" 477 | version = "0.36.7" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 480 | dependencies = [ 481 | "memchr", 482 | ] 483 | 484 | [[package]] 485 | name = "once_cell" 486 | version = "1.20.2" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 489 | 490 | [[package]] 491 | name = "opencv" 492 | version = "0.94.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "f80fd7d018d20b1e49bdd65e72350f1f63cad6bc9c15f850c47c31a6ad8d0d20" 495 | dependencies = [ 496 | "cc", 497 | "dunce", 498 | "jobserver", 499 | "libc", 500 | "num-traits", 501 | "once_cell", 502 | "opencv-binding-generator", 503 | "pkg-config", 504 | "semver", 505 | "shlex", 506 | "vcpkg", 507 | "windows", 508 | ] 509 | 510 | [[package]] 511 | name = "opencv-binding-generator" 512 | version = "0.95.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "7283829fe440be381fea73521f850b287fd44f994acd6453e1e19b3d479ef7fc" 515 | dependencies = [ 516 | "clang", 517 | "clang-sys", 518 | "dunce", 519 | "once_cell", 520 | "percent-encoding", 521 | "regex", 522 | "shlex", 523 | ] 524 | 525 | [[package]] 526 | name = "parking_lot" 527 | version = "0.12.3" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 530 | dependencies = [ 531 | "lock_api", 532 | "parking_lot_core", 533 | ] 534 | 535 | [[package]] 536 | name = "parking_lot_core" 537 | version = "0.9.10" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 540 | dependencies = [ 541 | "cfg-if", 542 | "libc", 543 | "redox_syscall", 544 | "smallvec", 545 | "windows-targets 0.52.6", 546 | ] 547 | 548 | [[package]] 549 | name = "paste" 550 | version = "1.0.15" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 553 | 554 | [[package]] 555 | name = "percent-encoding" 556 | version = "2.3.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 559 | 560 | [[package]] 561 | name = "pin-project-lite" 562 | version = "0.2.16" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 565 | 566 | [[package]] 567 | name = "pin-utils" 568 | version = "0.1.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 571 | 572 | [[package]] 573 | name = "pkg-config" 574 | version = "0.3.31" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 577 | 578 | [[package]] 579 | name = "proc-macro2" 580 | version = "1.0.93" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 583 | dependencies = [ 584 | "unicode-ident", 585 | ] 586 | 587 | [[package]] 588 | name = "quote" 589 | version = "1.0.38" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 592 | dependencies = [ 593 | "proc-macro2", 594 | ] 595 | 596 | [[package]] 597 | name = "ratatui" 598 | version = "0.29.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 601 | dependencies = [ 602 | "bitflags", 603 | "cassowary", 604 | "compact_str", 605 | "crossterm", 606 | "indoc", 607 | "instability", 608 | "itertools", 609 | "lru", 610 | "paste", 611 | "strum", 612 | "unicode-segmentation", 613 | "unicode-truncate", 614 | "unicode-width 0.2.0", 615 | ] 616 | 617 | [[package]] 618 | name = "redox_syscall" 619 | version = "0.5.8" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 622 | dependencies = [ 623 | "bitflags", 624 | ] 625 | 626 | [[package]] 627 | name = "regex" 628 | version = "1.11.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 631 | dependencies = [ 632 | "aho-corasick", 633 | "memchr", 634 | "regex-automata", 635 | "regex-syntax", 636 | ] 637 | 638 | [[package]] 639 | name = "regex-automata" 640 | version = "0.4.9" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 643 | dependencies = [ 644 | "aho-corasick", 645 | "memchr", 646 | "regex-syntax", 647 | ] 648 | 649 | [[package]] 650 | name = "regex-syntax" 651 | version = "0.8.5" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 654 | 655 | [[package]] 656 | name = "rustc-demangle" 657 | version = "0.1.24" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 660 | 661 | [[package]] 662 | name = "rustix" 663 | version = "0.38.44" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 666 | dependencies = [ 667 | "bitflags", 668 | "errno", 669 | "libc", 670 | "linux-raw-sys", 671 | "windows-sys 0.59.0", 672 | ] 673 | 674 | [[package]] 675 | name = "rustversion" 676 | version = "1.0.19" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 679 | 680 | [[package]] 681 | name = "ryu" 682 | version = "1.0.18" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 685 | 686 | [[package]] 687 | name = "scopeguard" 688 | version = "1.2.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 691 | 692 | [[package]] 693 | name = "semver" 694 | version = "1.0.25" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" 697 | 698 | [[package]] 699 | name = "shlex" 700 | version = "1.3.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 703 | 704 | [[package]] 705 | name = "signal-hook" 706 | version = "0.3.17" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 709 | dependencies = [ 710 | "libc", 711 | "signal-hook-registry", 712 | ] 713 | 714 | [[package]] 715 | name = "signal-hook-mio" 716 | version = "0.2.4" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 719 | dependencies = [ 720 | "libc", 721 | "mio", 722 | "signal-hook", 723 | ] 724 | 725 | [[package]] 726 | name = "signal-hook-registry" 727 | version = "1.4.2" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 730 | dependencies = [ 731 | "libc", 732 | ] 733 | 734 | [[package]] 735 | name = "slab" 736 | version = "0.4.9" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 739 | dependencies = [ 740 | "autocfg", 741 | ] 742 | 743 | [[package]] 744 | name = "smallvec" 745 | version = "1.13.2" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 748 | 749 | [[package]] 750 | name = "socket2" 751 | version = "0.5.8" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 754 | dependencies = [ 755 | "libc", 756 | "windows-sys 0.52.0", 757 | ] 758 | 759 | [[package]] 760 | name = "static_assertions" 761 | version = "1.1.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 764 | 765 | [[package]] 766 | name = "strsim" 767 | version = "0.11.1" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 770 | 771 | [[package]] 772 | name = "strum" 773 | version = "0.26.3" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 776 | dependencies = [ 777 | "strum_macros", 778 | ] 779 | 780 | [[package]] 781 | name = "strum_macros" 782 | version = "0.26.4" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 785 | dependencies = [ 786 | "heck", 787 | "proc-macro2", 788 | "quote", 789 | "rustversion", 790 | "syn", 791 | ] 792 | 793 | [[package]] 794 | name = "syn" 795 | version = "2.0.96" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 798 | dependencies = [ 799 | "proc-macro2", 800 | "quote", 801 | "unicode-ident", 802 | ] 803 | 804 | [[package]] 805 | name = "tokio" 806 | version = "1.43.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 809 | dependencies = [ 810 | "backtrace", 811 | "bytes", 812 | "libc", 813 | "mio", 814 | "parking_lot", 815 | "pin-project-lite", 816 | "signal-hook-registry", 817 | "socket2", 818 | "tokio-macros", 819 | "windows-sys 0.52.0", 820 | ] 821 | 822 | [[package]] 823 | name = "tokio-macros" 824 | version = "2.5.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 827 | dependencies = [ 828 | "proc-macro2", 829 | "quote", 830 | "syn", 831 | ] 832 | 833 | [[package]] 834 | name = "tuicam" 835 | version = "0.0.3" 836 | dependencies = [ 837 | "crossterm", 838 | "futures", 839 | "opencv", 840 | "ratatui", 841 | "tokio", 842 | ] 843 | 844 | [[package]] 845 | name = "unicode-ident" 846 | version = "1.0.15" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" 849 | 850 | [[package]] 851 | name = "unicode-segmentation" 852 | version = "1.12.0" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 855 | 856 | [[package]] 857 | name = "unicode-truncate" 858 | version = "1.1.0" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 861 | dependencies = [ 862 | "itertools", 863 | "unicode-segmentation", 864 | "unicode-width 0.1.14", 865 | ] 866 | 867 | [[package]] 868 | name = "unicode-width" 869 | version = "0.1.14" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 872 | 873 | [[package]] 874 | name = "unicode-width" 875 | version = "0.2.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 878 | 879 | [[package]] 880 | name = "vcpkg" 881 | version = "0.2.15" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 884 | 885 | [[package]] 886 | name = "wasi" 887 | version = "0.11.0+wasi-snapshot-preview1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 890 | 891 | [[package]] 892 | name = "winapi" 893 | version = "0.3.9" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 896 | dependencies = [ 897 | "winapi-i686-pc-windows-gnu", 898 | "winapi-x86_64-pc-windows-gnu", 899 | ] 900 | 901 | [[package]] 902 | name = "winapi-i686-pc-windows-gnu" 903 | version = "0.4.0" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 906 | 907 | [[package]] 908 | name = "winapi-x86_64-pc-windows-gnu" 909 | version = "0.4.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 912 | 913 | [[package]] 914 | name = "windows" 915 | version = "0.59.0" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" 918 | dependencies = [ 919 | "windows-core", 920 | "windows-targets 0.53.0", 921 | ] 922 | 923 | [[package]] 924 | name = "windows-core" 925 | version = "0.59.0" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" 928 | dependencies = [ 929 | "windows-implement", 930 | "windows-interface", 931 | "windows-result", 932 | "windows-strings", 933 | "windows-targets 0.53.0", 934 | ] 935 | 936 | [[package]] 937 | name = "windows-implement" 938 | version = "0.59.0" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" 941 | dependencies = [ 942 | "proc-macro2", 943 | "quote", 944 | "syn", 945 | ] 946 | 947 | [[package]] 948 | name = "windows-interface" 949 | version = "0.59.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" 952 | dependencies = [ 953 | "proc-macro2", 954 | "quote", 955 | "syn", 956 | ] 957 | 958 | [[package]] 959 | name = "windows-result" 960 | version = "0.3.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" 963 | dependencies = [ 964 | "windows-targets 0.53.0", 965 | ] 966 | 967 | [[package]] 968 | name = "windows-strings" 969 | version = "0.3.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" 972 | dependencies = [ 973 | "windows-targets 0.53.0", 974 | ] 975 | 976 | [[package]] 977 | name = "windows-sys" 978 | version = "0.52.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 981 | dependencies = [ 982 | "windows-targets 0.52.6", 983 | ] 984 | 985 | [[package]] 986 | name = "windows-sys" 987 | version = "0.59.0" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 990 | dependencies = [ 991 | "windows-targets 0.52.6", 992 | ] 993 | 994 | [[package]] 995 | name = "windows-targets" 996 | version = "0.52.6" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 999 | dependencies = [ 1000 | "windows_aarch64_gnullvm 0.52.6", 1001 | "windows_aarch64_msvc 0.52.6", 1002 | "windows_i686_gnu 0.52.6", 1003 | "windows_i686_gnullvm 0.52.6", 1004 | "windows_i686_msvc 0.52.6", 1005 | "windows_x86_64_gnu 0.52.6", 1006 | "windows_x86_64_gnullvm 0.52.6", 1007 | "windows_x86_64_msvc 0.52.6", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "windows-targets" 1012 | version = "0.53.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 1015 | dependencies = [ 1016 | "windows_aarch64_gnullvm 0.53.0", 1017 | "windows_aarch64_msvc 0.53.0", 1018 | "windows_i686_gnu 0.53.0", 1019 | "windows_i686_gnullvm 0.53.0", 1020 | "windows_i686_msvc 0.53.0", 1021 | "windows_x86_64_gnu 0.53.0", 1022 | "windows_x86_64_gnullvm 0.53.0", 1023 | "windows_x86_64_msvc 0.53.0", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "windows_aarch64_gnullvm" 1028 | version = "0.52.6" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1031 | 1032 | [[package]] 1033 | name = "windows_aarch64_gnullvm" 1034 | version = "0.53.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1037 | 1038 | [[package]] 1039 | name = "windows_aarch64_msvc" 1040 | version = "0.52.6" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1043 | 1044 | [[package]] 1045 | name = "windows_aarch64_msvc" 1046 | version = "0.53.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1049 | 1050 | [[package]] 1051 | name = "windows_i686_gnu" 1052 | version = "0.52.6" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1055 | 1056 | [[package]] 1057 | name = "windows_i686_gnu" 1058 | version = "0.53.0" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1061 | 1062 | [[package]] 1063 | name = "windows_i686_gnullvm" 1064 | version = "0.52.6" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1067 | 1068 | [[package]] 1069 | name = "windows_i686_gnullvm" 1070 | version = "0.53.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1073 | 1074 | [[package]] 1075 | name = "windows_i686_msvc" 1076 | version = "0.52.6" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1079 | 1080 | [[package]] 1081 | name = "windows_i686_msvc" 1082 | version = "0.53.0" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1085 | 1086 | [[package]] 1087 | name = "windows_x86_64_gnu" 1088 | version = "0.52.6" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1091 | 1092 | [[package]] 1093 | name = "windows_x86_64_gnu" 1094 | version = "0.53.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1097 | 1098 | [[package]] 1099 | name = "windows_x86_64_gnullvm" 1100 | version = "0.52.6" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1103 | 1104 | [[package]] 1105 | name = "windows_x86_64_gnullvm" 1106 | version = "0.53.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1109 | 1110 | [[package]] 1111 | name = "windows_x86_64_msvc" 1112 | version = "0.52.6" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1115 | 1116 | [[package]] 1117 | name = "windows_x86_64_msvc" 1118 | version = "0.53.0" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1121 | --------------------------------------------------------------------------------