1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
//! `show-image` is a library for quickly displaying images. //! It is intended as a debugging aid for writing image processing code. //! The library is not intended for making full-featured GUIs, //! but you can process keyboard events from the created windows. //! //! # Supported image types. //! The library aims to support as many different data types used to represent images. //! To keep the dependency graph as small as possible, //! support for third party libraries must be enabled explicitly with feature flags. //! //! Currently, the following types are supported: //! * Tuples of binary data and [`ImageInfo`]. //! * [`image::DynamicImage`] and [`image::ImageBuffer`] with the `image` feature. //! * [`tch::Tensor`](::tch::Tensor) with the `tch` feature. //! //! If you think support for a some data type is missing, //! feel free to send a PR or create an issue on GitHub. //! //! # Event handling. //! You can receive events using [`Window::events`]. //! This is a general channel on which all events for that window are sent. //! Alternatively you can use [`Window::add_event_handler`] to register an asynchronous event handler. //! This event handler will run in the context thread, so shouldn't block for too long. //! //! You can also handle keyboard events for windows using [`Window::wait_key`] or [`Window::wait_key_deadline`]. //! These functions will wait for key press events while discarding key up events. //! //! # Saving displayed images. //! If the `save` feature is enabled, windows allow the displayed image to be saved using `Ctrl+S`. //! This will open a file dialog to save the currently displayed image. //! //! Note that images are saved in a background thread. //! To ensure that no data loss occurs, call [`stop`] to gracefully stop and join the background thread. //! //! # Example 1: Showing an image. //! This example uses a tuple of `(&[u8], `[`ImageInfo`]`)` as image, //! but any type that implements [`ImageData`] will do. //! ```no_run //! # use image; //! # use std::time::Duration; //! # let pixel_data = &[0u8][..]; //! use show_image::{ImageInfo, make_window}; //! //! let image = (pixel_data, ImageInfo::rgb8(1920, 1080)); //! //! // Create a window and display the image. //! let window = make_window("image")?; //! window.set_image("image-001", image)?; //! //! # Result::<(), String>::Ok(()) //! ``` //! //! # Example 2: Handling keyboard events. //! ```no_run //! # use std::time::Duration; //! # use show_image::ImageInfo; //! use show_image::{Event, KeyCode, make_window}; //! //! // Create a window and display the image. //! # let image = (&[0u8][..], ImageInfo::rgb8(1920, 1080)); //! let window = make_window("image")?; //! window.set_image("image-001", &image)?; //! //! // Print keyboard events until Escape is pressed, then exit. //! // If the user closes the window, the channel is closed and the loop also exits. //! for event in window.events()? { //! if let Event::KeyboardEvent(event) = event { //! println!("{:#?}", event); //! if event.key == KeyCode::Escape { //! break; //! } //! } //! } //! //! // Make sure all background tasks are stopped cleanly. //! show_image::stop()?; //! # Result::<(), String>::Ok(()) //! ``` pub use keyboard_types::Code as ScanCode; pub use keyboard_types::Key as KeyCode; pub use keyboard_types::KeyState; pub use keyboard_types::KeyboardEvent; pub use keyboard_types::Location as KeyLocation; pub use keyboard_types::Modifiers as KeyModifiers; mod backend; mod background_thread; mod features; mod image_info; mod oneshot; mod event; pub use backend::*; pub use features::*; pub use image_info::*; pub use event::*; /// Error that can occur while waiting for a key press. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum WaitKeyError { /// The window is closed. /// /// No further key events will happen, /// and any loop waiting for keys should terminate. WindowClosed, } /// Allows a type to be displayed as an image. /// /// This trait is implemented for tuples of `(Data, ImageInfo)` if `Data` can be converted into a `Box<[u8]>`, /// and for `&(Data, ImageInfo)` if `Data` is `AsRef<[u8]>`. /// Amongst others, that includes `&[u8]`, `Box<[u8]>`, `Vec<u8>`. /// /// Implementations for types from third-party libraries can be enabled using feature flags. pub trait ImageData { /// Get the image data as boxed slice. /// /// This function takes self by value to prevent copying if possible. /// If the data can not be moved into a box, consider implementing the trait for references. fn data(self) -> Box<[u8]>; /// Get the [`ImageInfo`] describing the binary data. /// /// This function may fail at runtime if the data can not be described properly. fn info(&self) -> Result<ImageInfo, String>; /// Convert the image into a tuple containing the image info and data. fn into_image_tuple(self) -> Result<(Box<[u8]>, ImageInfo), String> where Self: Sized { let info = self.info()?; Ok((self.data(), info)) } } /// An image currently being shown by a window. /// /// This struct isn't meant to be created manually. /// It is available in event handlers or through a [`Window`] object. #[derive(Clone, Debug)] pub struct Image { /// The image data. pub data: std::sync::Arc<[u8]>, /// The image info, describing the raw data. pub info: ImageInfo, /// The name of the image. pub name: String, } /// A rectangle. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct Rectangle { x: i32, y: i32, width: u32, height: u32 } impl Rectangle { /// Create a rectangle from X, Y coordinates and the width and height. pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Self { Self { x, y, width, height } } /// Get the X location of the rectangle. pub fn x(&self) -> i32 { self.x } /// Get the Y location of the rectangle. pub fn y(&self) -> i32 { self.y } /// Get the width of the rectangle. pub fn width(&self) -> u32 { self.width } /// Get the height of the rectangle. pub fn height(&self) -> u32 { self.height } } /// Options for creating a window. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WindowOptions { /// The name of the window. pub name: String, /// The initial size of the window in pixel. /// /// This may be ignored by a window manager. pub size: [u32; 2], /// If true allow the window to be resized. /// /// This may be ignored by a window manager. pub resizable: bool, /// Preserve the aspect ratio pub preserve_aspect_ratio: bool, /// Borderless window pub borderless: bool, } impl std::error::Error for WaitKeyError {} impl std::fmt::Display for WaitKeyError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { WaitKeyError::WindowClosed => write!(f, "window closed"), } } } impl Default for WindowOptions { fn default() -> Self { Self { name: String::from("image"), size: [800, 600], resizable: true, preserve_aspect_ratio: true, borderless: false } } } impl WindowOptions { /// Set the name of the window. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_name(mut self, name: String) -> Self { self.name = name; self } /// Set the initial size of the window. /// /// This property may be ignored by a window manager. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_size(mut self, size: [u32; 2]) -> Self { self.size = size; self } /// Set the initial width of the window. /// /// This property may be ignored by a window manager. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_width(mut self, width: u32) -> Self { self.size[0] = width; self } /// Set the initial height of the window. /// /// This property may be ignored by a window manager. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_height(mut self, height: u32) -> Self { self.size[1] = height; self } /// Make the window resizable or not. /// /// This property may be ignored by a window manager. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_resizable(mut self, resizable: bool) -> Self { self.resizable = resizable; self } /// Preserve the aspect ratio of displayed images, or not. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_preserve_aspect_ratio(mut self, preserve_aspect_ratio: bool) -> Self { self.preserve_aspect_ratio = preserve_aspect_ratio; self } /// Make the window borderless or not. /// /// This function consumes and returns `self` to allow daisy chaining. pub fn set_borderless(mut self, borderless: bool) -> Self { self.borderless = borderless; self } } /// Save an image to the given path. #[cfg(feature = "save")] pub fn save_image(path: &std::path::Path, data: &[u8], info: ImageInfo) -> Result<(), String> { let color_type = match info.pixel_format { PixelFormat::Mono8 => image::ColorType::L8, PixelFormat::Rgb8 => image::ColorType::Rgb8, PixelFormat::Rgba8 => image::ColorType::Rgba8, PixelFormat::Bgr8 => image::ColorType::Bgr8, PixelFormat::Bgra8 => image::ColorType::Bgra8, }; let bytes_per_pixel = usize::from(info.pixel_format.bytes_per_pixel()); if info.row_stride == info.width * bytes_per_pixel { image::save_buffer(path, data, info.width as u32, info.height as u32, color_type) .map_err(|e| format!("failed to save image: {}", e)) } else { let mut packed = Vec::with_capacity(info.width * info.height * bytes_per_pixel); for row in 0..info.height { packed.extend_from_slice(&data[info.row_stride * row..][..info.width * bytes_per_pixel]); } image::save_buffer(path, &packed, info.width as u32, info.height as u32, color_type) .map_err(|e| format!("failed to save image: {}", e)) } } /// Prompt the user to save an image. /// /// The name hint is used as initial path for the prompt. #[cfg(feature = "save")] pub fn prompt_save_image(name_hint: &str, data: &[u8], info: ImageInfo) -> Result<(), String> { let path = match tinyfiledialogs::save_file_dialog("Save image", name_hint) { Some(x) => x, None => return Ok(()), }; save_image(path.as_ref(), &data, info) } impl<Container> ImageData for (Container, ImageInfo) where Box<[u8]>: From<Container>, { fn data(self) -> Box<[u8]> { Box::from(self.0) } fn info(&self) -> Result<ImageInfo, String> { Ok(self.1.clone()) } } impl<Container> ImageData for (Container, &ImageInfo) where Box<[u8]>: From<Container>, { fn data(self) -> Box<[u8]> { Box::from(self.0) } fn info(&self) -> Result<ImageInfo, String> { Ok(self.1.clone()) } } impl<Container> ImageData for &(Container, ImageInfo) where Container: AsRef<[u8]>, { fn data(self) -> Box<[u8]> { Box::from(self.0.as_ref()) } fn info(&self) -> Result<ImageInfo, String> { Ok(self.1.clone()) } } impl<Container> ImageData for &(Container, &ImageInfo) where Container: AsRef<[u8]>, { fn data(self) -> Box<[u8]> { Box::from(self.0.as_ref()) } fn info(&self) -> Result<ImageInfo, String> { Ok(self.1.clone()) } }