diff --git a/README.md b/README.md index a5806c2..37521da 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,17 @@ Run `cast2gif --help` to get the usage instructions: Renders Asciinema .cast files as gif, svg, or animated png. USAGE: - cast2gif [FLAGS] + cast2gif [FLAGS] [OPTIONS] FLAGS: -f, --force Overwrite existing output file -h, --help Prints help information -V, --version Prints version information + OPTIONS: + -c, --crop crop the recording while rendering. Specify crop in terminal cells as + `top=[int],left=[int],width=[int],height=[int]`. + ARGS: The asciinema .cast file to render The file to render to diff --git a/src/cli.rs b/src/cli.rs index acf5784..8969bea 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -37,6 +37,7 @@ pub fn run() { .expect("Panic while handling panic"); } +#[derive(Debug)] enum OutputFormat { Gif, // TODO: Other image formats @@ -44,6 +45,14 @@ enum OutputFormat { // Svg, } +#[derive(Debug, Clone, Copy)] +pub struct CropSettings { + pub top: u16, + pub left: u16, + pub width: u16, + pub height: u16, +} + fn execute_cli() -> anyhow::Result<()> { use clap::{crate_authors, crate_version, App, AppSettings, Arg}; @@ -60,6 +69,13 @@ fn execute_cli() -> anyhow::Result<()> { .arg(Arg::with_name("out_file") .help("The file to render to") .required(true)) + .arg(Arg::with_name("crop") + .long("crop") + .short("c") + .help("crop the recording while rendering. \ + Specify crop in terminal cells as \ + `top=[int],left=[int],width=[int],height=[int]`.") + .takes_value(true)) // TODO: Implement other file formats // .arg(Arg::with_name("format") // .long("format") @@ -137,6 +153,45 @@ fn execute_cli() -> anyhow::Result<()> { // Some("png") => OutputFormat::Png, // Some(other) => panic!("Invalid option to --format: {}", other), // }; + let crop = { + let mut top = None; + let mut left = None; + let mut width = None; + let mut height = None; + + if let Some(crop_str) = args.value_of("crop") { + for pair in crop_str.split(",") { + let split: Vec<_> = pair.split("=").collect(); + let key = split.get(0); + let value = split.get(1); + + if let Some(value) = value { + let value: u16 = value.parse().context("Could not parse crop value as int")?; + + if let Some(&key) = key { + match key { + "top" => top = Some(value), + "left" => left = Some(value), + "width" => width = Some(value), + "height" => height = Some(value), + _ => continue, + } + } + } + } + }; + + if top.is_none() || left.is_none() || width.is_none() || height.is_none() { + None + } else { + Some(CropSettings { + top: top.unwrap(), + left: left.unwrap(), + width: width.unwrap(), + height: height.unwrap(), + }) + } + }; // Create the progress bars let multi = MultiProgress::new(); @@ -158,6 +213,7 @@ fn execute_cli() -> anyhow::Result<()> { cast_file, &out_file, progress_handler, + crop ) .expect("TODO"); }); diff --git a/src/frame_renderer/fontkit.rs b/src/frame_renderer/fontkit.rs index 01bcd55..22abdfa 100644 --- a/src/frame_renderer/fontkit.rs +++ b/src/frame_renderer/fontkit.rs @@ -18,7 +18,7 @@ use std::iter::FromIterator; use std::sync::Arc; use super::parse_color; -use crate::types::*; +use crate::{cli::CropSettings, types::*}; lazy_static! { static ref FONT_DATA: Arc> = Arc::new(Vec::from_iter( @@ -34,7 +34,7 @@ thread_local! { static FONT: Font = Font::from_bytes(FONT_DATA.clone(), 0).expect("Could not load font"); } -pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame { +pub(crate) fn render_frame_to_png(frame: TerminalFrame, crop: Option) -> RgbaFrame { flame!(guard "Render Frame To PNG"); flame!(start "Init Values"); @@ -43,6 +43,11 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame { // TODO: Configurable background color const DEFAULT_BG_COLOR: RGBA8 = RGBA::new(0, 0, 0, 255); + let crop_rows = crop.map(|x| x.height).unwrap_or(rows); + let crop_cols = crop.map(|x| x.width).unwrap_or(cols); + let crop_top = crop.map(|x| x.top).unwrap_or(0); + let crop_left = crop.map(|x| x.left).unwrap_or(0); + // Glyph rendering config lazy_static! { // static ref TRANS: Transform2F = Transform2F::default(); @@ -73,8 +78,8 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame { let font_transform = Transform2F::from_translation(Vector2F::new(0., -font_height_offset as f32)); - let height = (rows as i32 * font_height) as usize; - let width = (cols as i32 * font_width) as usize; + let height = (crop_rows as i32 * font_height) as usize; + let width = (crop_cols as i32 * font_width) as usize; // Image to render to let pixel_count = width * height; @@ -89,11 +94,11 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame { flame!(end "Init Values"); flame!(start "Render Cells"); - for row in 0..rows { - for col in 0..cols { + for (row_i, row) in (crop_top..(crop_top + crop_rows)).enumerate() { + for (col_i, col) in (crop_left..(crop_left + crop_cols)).enumerate() { let cell = frame.screen.cell(row, col).expect("Error indexing cell"); - let ypos = row as i32 * font_height; - let xpos = col as i32 * font_width; + let ypos = row_i as i32 * font_height; + let xpos = col_i as i32 * font_width; let mut subimg = image.sub_image_mut( xpos as usize, ypos as usize, diff --git a/src/lib.rs b/src/lib.rs index a0aad96..9043146 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use cli::CropSettings; use lazy_static::lazy_static; use thiserror::Error; @@ -76,6 +77,7 @@ fn png_raster_thread( frames: Fi, progress_sender: flume::Sender, frame_sender: flume::Sender, + crop: Option, ) where Fi: IntoIterator>, { @@ -93,7 +95,7 @@ fn png_raster_thread( let fs = frame_sender.clone(); let ps = progress_sender.clone(); rayon::spawn(move || { - let frame = frame_renderer::render_frame_to_png(frame); + let frame = frame_renderer::render_frame_to_png(frame, crop); fs.send(frame).expect("TODO"); ps.send(ProgressCmd::IncrementRasterProgress).expect("TODO"); }); @@ -125,6 +127,7 @@ pub fn convert_to_gif_with_progress( reader: R, writer: W, update_progress: C, + crop: Option, ) -> Result<(), Error> where R: Read + Send + 'static, @@ -146,7 +149,7 @@ where // Spawn the png rasterizer thread let ps = progress_sender.clone(); - rayon::spawn(move || png_raster_thread(term_frames, ps, raster_sender)); + rayon::spawn(move || png_raster_thread(term_frames, ps, raster_sender, crop)); // Create gifski gif encoder let (collector, gif_writer) = gifski::new(gifski::Settings { @@ -197,10 +200,10 @@ impl gifski::progress::ProgressReporter for GifWriterProgressHandler { fn done(&mut self, _msg: &str) {} } -pub fn convert_to_gif(reader: R, writer: W) -> Result<(), Error> +pub fn convert_to_gif(reader: R, writer: W, crop: Option) -> Result<(), Error> where R: Read + Send + 'static, W: Write + Send, { - convert_to_gif_with_progress(reader, writer, NullProgressHandler) + convert_to_gif_with_progress(reader, writer, NullProgressHandler, crop) }