show_image/lib.rs
1//! `show-image` is a library for quickly displaying images.
2//! It is intended as a debugging aid for writing image processing code.
3//! The library is not intended for making full-featured GUIs,
4//! but you can process keyboard events from the created windows.
5//!
6//! # Supported image types.
7//! The library aims to support as many different data types used to represent images.
8//! To keep the dependency graph as small as possible,
9//! support for third party libraries must be enabled explicitly with feature flags.
10//!
11//! Currently, the following types are supported:
12//! * The [`Image`] and [`ImageView`] types from this crate.
13//! * [`image::DynamicImage`][::image::DynamicImage] and [`image::ImageBuffer`][::image::ImageBuffer] (requires the `"image"` feature).
14//! * [`tch::Tensor`][::tch::Tensor] (requires the `"tch"` feature).
15//! * [`raqote::DrawTarget`][::raqote::DrawTarget] and [`raqote::Image`][::raqote::Image] (requires the `"raqote"` feature).
16//!
17//! If you think support for a some data type is missing,
18//! feel free to send a PR or create an issue on GitHub.
19//!
20//! # Global context and threading
21//! The library uses a global context that runs an event loop.
22//! This context must be initialized before any `show-image` functions can be used.
23//! Additionally, some platforms require the event loop to be run in the main thread.
24//! To ensure portability, the same restriction is enforced on all platforms.
25//!
26//! The easiest way to initialize the global context and run the event loop in the main thread
27//! is to use the [`main`] attribute macro on your main function.
28//! If you want to run some code in the main thread before the global context takes over,
29//! you can use the [`run_context()`] function or one of it's variations instead.
30//! Note that you must still call those functions from the main thread,
31//! and they do not return control back to the caller.
32//!
33//! # Event handling.
34//! You can register an event handler to run in the global context thread using [`WindowProxy::add_event_handler()`] or some of the similar functions.
35//! You can also register an event handler directly with the context to handle global events (including all window events).
36//! Since these event handlers run in the event loop, they should not block for any significant time.
37//!
38//! You can also receive events using [`WindowProxy::event_channel()`] or [`ContextProxy::event_channel()`].
39//! These functions create a new channel for receiving window events or global events, respectively.
40//! As long as you're receiving the events in your own thread, you can block as long as you like.
41//!
42//! # Saving displayed images.
43//! If the `save` feature is enabled, windows allow the displayed image to be saved using `Ctrl+S` or `Ctrl+Shift+S`.
44//! The first shortcut will open a file dialog to save the currently displayed image.
45//! The second shortcut will directly save the image in the current working directory using the name of the image.
46//!
47//! The image is saved without any overlays.
48//! To save an image including overlays, add `Alt` to the shortcut: `Ctrl+Alt+S` and `Ctrl+Alt+Shift+S`.
49//!
50//! Note that images are saved in a background thread.
51//! To ensure that no data loss occurs, call [`exit()`] to terminate the process rather than [`std::process::exit()`].
52//! That will ensure that the background threads are joined before the process is terminated.
53//!
54//! # Example 1: Showing an image.
55//! ```no_run
56//! # use image;
57//! use show_image::{ImageView, ImageInfo, create_window};
58//!
59//! #[show_image::main]
60//! fn main() -> Result<(), Box<dyn std::error::Error>> {
61//!
62//! # let pixel_data = &[0u8][..];
63//! let image = ImageView::new(ImageInfo::rgb8(1920, 1080), pixel_data);
64//!
65//! // Create a window with default options and display the image.
66//! let window = create_window("image", Default::default())?;
67//! window.set_image("image-001", image)?;
68//!
69//! Ok(())
70//! }
71//! ```
72//!
73//! # Example 2: Handling keyboard events using an event channel.
74//! ```no_run
75//! # use show_image::{ImageInfo, ImageView};
76//! use show_image::{event, create_window};
77//!
78//! // Create a window and display the image.
79//! # let image = ImageView::new(ImageInfo::rgb8(1920, 1080), &[0u8][..]);
80//! let window = create_window("image", Default::default())?;
81//! window.set_image("image-001", &image)?;
82//!
83//! // Print keyboard events until Escape is pressed, then exit.
84//! // If the user closes the window, the channel is closed and the loop also exits.
85//! for event in window.event_channel()? {
86//! if let event::WindowEvent::KeyboardInput(event) = event {
87//! println!("{:#?}", event);
88//! if event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() {
89//! break;
90//! }
91//! }
92//! }
93//!
94//! # Result::<(), Box<dyn std::error::Error>>::Ok(())
95//! ```
96//!
97//! # Back-end and GPU selection
98//!
99//! This crate uses [`wgpu`] for rendering.
100//! You can force the selection of a specfic WGPU backend by setting the `WGPU_BACKEND` environment variable to one of the supported values:
101//!
102//! * `primary`: Use the primary backend for the platform (the default).
103//! * `vulkan`: Use the vulkan back-end.
104//! * `metal`: Use the metal back-end.
105//! * `dx12`: Use the DirectX 12 back-end.
106//! * `dx11`: Use the DirectX 11 back-end.
107//! * `gl`: Use the OpenGL back-end.
108//! * `webgpu`: Use the browser WebGPU back-end.
109//!
110//! You can also influence the GPU selection by setting the `WGPU_POWER_PREF` environment variable:
111//!
112//! * `low`: Prefer a low power GPU (the default).
113//! * `high`: Prefer a high performance GPU.
114
115#![cfg_attr(feature = "nightly", feature(doc_cfg))]
116#![cfg_attr(feature = "nightly", feature(termination_trait_lib))]
117#![warn(missing_docs)]
118
119mod backend;
120mod background_thread;
121pub mod error;
122pub mod event;
123mod features;
124mod image_info;
125mod image_types;
126mod oneshot;
127mod rectangle;
128
129pub use self::backend::*;
130#[allow(unused_imports)]
131pub use self::features::*;
132pub use self::image_info::*;
133pub use self::image_types::*;
134pub use self::rectangle::Rectangle;
135
136pub use winit;
137pub use winit::window::WindowId;
138
139pub use glam;
140
141/// An RGBA color.
142#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
143pub struct Color {
144 /// The red component in the range 0 to 1.
145 pub red: f64,
146
147 /// The green component in the range 0 to 1.
148 pub green: f64,
149
150 /// The blue component in the range 0 to 1.
151 pub blue: f64,
152
153 /// The alpha component in the range 0 to 1.
154 pub alpha: f64,
155}
156
157impl Color {
158 /// Create a new fully opaque color from the RGB components.
159 pub const fn rgb(red: f64, green: f64, blue: f64) -> Self {
160 Self::rgba(red, green, blue, 1.0)
161 }
162
163 /// Create a new color from the RGBA components.
164 pub const fn rgba(red: f64, green: f64, blue: f64, alpha: f64) -> Self {
165 Self { red, green, blue, alpha }
166 }
167
168 /// Get a color representing fully opaque black.
169 pub const fn black() -> Self {
170 Self::rgb(0.0, 0.0, 0.0)
171 }
172
173 /// Get a color representing fully opaque white.
174 pub const fn white() -> Self {
175 Self::rgb(1.0, 1.0, 1.0)
176 }
177}
178
179pub mod termination;
180
181#[cfg(feature = "macros")]
182pub use show_image_macros::main;
183
184/// Save an image to the given path.
185#[cfg(feature = "save")]
186#[cfg_attr(feature = "nightly", doc(cfg(feature = "save")))]
187fn save_rgba8_image(
188 path: impl AsRef<std::path::Path>,
189 data: &[u8],
190 size: glam::UVec2,
191 row_stride: u32,
192) -> Result<(), error::SaveImageError> {
193 let path = path.as_ref();
194
195 let file = std::fs::File::create(path)?;
196
197 let mut encoder = png::Encoder::new(file, size.x, size.y);
198 encoder.set_color(png::ColorType::Rgba);
199 encoder.set_depth(png::BitDepth::Eight);
200
201 let mut writer = encoder.write_header()?;
202
203 if row_stride == size.x * 4 {
204 Ok(writer.write_image_data(data)?)
205 } else {
206 use std::io::Write;
207
208 let mut writer = writer.into_stream_writer()?;
209 for row in data.chunks(row_stride as usize) {
210 let row = &row[..size.x as usize * 4];
211 writer.write_all(row)?;
212 }
213 writer.finish()?;
214 Ok(())
215 }
216}