diff --git a/Cargo.lock b/Cargo.lock index 8cd08c2d3b..b9939a4b36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8349,7 +8349,7 @@ dependencies = [ [[package]] name = "twix" -version = "0.9.2" +version = "0.9.3" dependencies = [ "aliveness", "argument_parsers", diff --git a/crates/types/src/jpeg.rs b/crates/types/src/jpeg.rs index eea08d9d06..c998451eec 100644 --- a/crates/types/src/jpeg.rs +++ b/crates/types/src/jpeg.rs @@ -1,5 +1,7 @@ +use color_eyre::eyre; use image::{codecs::jpeg::JpegEncoder, ImageBuffer, ImageError, Luma, RgbImage}; use serde::{Deserialize, Serialize}; +use std::path::Path; use crate::{grayscale_image::GrayscaleImage, ycbcr422_image::YCbCr422Image}; @@ -36,3 +38,9 @@ impl TryFrom<&YCbCr422Image> for JpegImage { Ok(Self { data: jpeg_buffer }) } } + +impl JpegImage { + pub fn save_to_jpeg_file(&self, file: impl AsRef) -> eyre::Result<()> { + Ok(std::fs::write(file, self.data.clone())?) + } +} diff --git a/tools/twix/Cargo.toml b/tools/twix/Cargo.toml index afcfb06e15..cf33adcd57 100644 --- a/tools/twix/Cargo.toml +++ b/tools/twix/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "twix" -version = "0.9.2" +version = "0.9.3" edition.workspace = true license.workspace = true homepage.workspace = true diff --git a/tools/twix/src/panels/image/mod.rs b/tools/twix/src/panels/image/mod.rs index e8d6ce7835..2b43b2243c 100644 --- a/tools/twix/src/panels/image/mod.rs +++ b/tools/twix/src/panels/image/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{env::temp_dir, fs::create_dir_all, path::PathBuf, sync::Arc}; use chrono::{DateTime, Utc}; use color_eyre::{eyre::eyre, Result}; @@ -7,6 +7,7 @@ use eframe::egui::{ColorImage, Response, SizeHint, TextureOptions, Ui, UiBuilder use geometry::rectangle::Rectangle; use image::RgbImage; use linear_algebra::{point, vector}; +use log::{info, warn}; use serde_json::{json, Value}; use types::{jpeg::JpegImage, ycbcr422_image::YCbCr422Image}; @@ -91,6 +92,24 @@ impl Panel for ImagePanel { } } +fn save_jpeg_image(buffer: &BufferHandle, path: PathBuf) -> Result<()> { + let buffer = buffer + .get_last_value()? + .ok_or_else(|| eyre!("no image available"))?; + buffer.save_to_jpeg_file(&path)?; + info!("image saved to '{}'", path.display()); + Ok(()) +} + +fn save_raw_image(buffer: &BufferHandle, path: PathBuf) -> Result<()> { + let buffer = buffer + .get_last_value()? + .ok_or_else(|| eyre!("no image available"))?; + buffer.save_to_ycbcr_444_file(&path)?; + info!("image saved to '{}'", path.display()); + Ok(()) +} + impl Widget for &mut ImagePanel { fn ui(self, ui: &mut Ui) -> Response { ui.horizontal(|ui| { @@ -112,6 +131,25 @@ impl Widget for &mut ImagePanel { let date: DateTime = timestamp.into(); ui.label(date.format("%T%.3f").to_string()); } + if ui.button("Save").clicked() { + let time_stamp = Utc::now().format("%H:%M:%S%.3f").to_string(); + let directory = temp_dir().join("twix"); + if let Err(error) = create_dir_all(&directory) { + warn!("failed to create temporary folder /tmp/twix: {error}"); + } else { + let cycler_name = format!("{:?}", self.cycler); + let path = directory.join(format!("image_{cycler_name}_{time_stamp}.png")); + let result = match &self.image_buffer { + RawOrJpeg::Raw(buffer) => save_raw_image(buffer, path), + RawOrJpeg::Jpeg(buffer) => { + save_jpeg_image(buffer, path.with_extension("jpeg")) + } + }; + if let Err(error) = result { + warn!("failed to save image: {error}"); + } + } + } }); let (response, mut painter) = TwixPainter::allocate( ui,