Skip to content

Commit a25b4d1

Browse files
committed
Add Crop Option
1 parent 4f76eeb commit a25b4d1

File tree

4 files changed

+81
-13
lines changed

4 files changed

+81
-13
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@ Run `cast2gif --help` to get the usage instructions:
4747
Renders Asciinema .cast files as gif, svg, or animated png.
4848

4949
USAGE:
50-
cast2gif [FLAGS] <cast_file> <out_file>
50+
cast2gif [FLAGS] [OPTIONS] <cast_file> <out_file>
5151

5252
FLAGS:
5353
-f, --force Overwrite existing output file
5454
-h, --help Prints help information
5555
-V, --version Prints version information
5656

57+
OPTIONS:
58+
-c, --crop <crop> crop the recording while rendering. Specify crop in terminal cells as
59+
`top=[int],left=[int],width=[int],height=[int]`.
60+
5761
ARGS:
5862
<cast_file> The asciinema .cast file to render
5963
<out_file> The file to render to

src/cli.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,22 @@ pub fn run() {
3737
.expect("Panic while handling panic");
3838
}
3939

40+
#[derive(Debug)]
4041
enum OutputFormat {
4142
Gif,
4243
// TODO: Other image formats
4344
// Png,
4445
// Svg,
4546
}
4647

48+
#[derive(Debug, Clone, Copy)]
49+
pub struct CropSettings {
50+
pub top: u16,
51+
pub left: u16,
52+
pub width: u16,
53+
pub height: u16,
54+
}
55+
4756
fn execute_cli() -> anyhow::Result<()> {
4857
use clap::{crate_authors, crate_version, App, AppSettings, Arg};
4958

@@ -60,6 +69,13 @@ fn execute_cli() -> anyhow::Result<()> {
6069
.arg(Arg::with_name("out_file")
6170
.help("The file to render to")
6271
.required(true))
72+
.arg(Arg::with_name("crop")
73+
.long("crop")
74+
.short("c")
75+
.help("crop the recording while rendering. \
76+
Specify crop in terminal cells as \
77+
`top=[int],left=[int],width=[int],height=[int]`.")
78+
.takes_value(true))
6379
// TODO: Implement other file formats
6480
// .arg(Arg::with_name("format")
6581
// .long("format")
@@ -137,6 +153,45 @@ fn execute_cli() -> anyhow::Result<()> {
137153
// Some("png") => OutputFormat::Png,
138154
// Some(other) => panic!("Invalid option to --format: {}", other),
139155
// };
156+
let crop = {
157+
let mut top = None;
158+
let mut left = None;
159+
let mut width = None;
160+
let mut height = None;
161+
162+
if let Some(crop_str) = args.value_of("crop") {
163+
for pair in crop_str.split(",") {
164+
let split: Vec<_> = pair.split("=").collect();
165+
let key = split.get(0);
166+
let value = split.get(1);
167+
168+
if let Some(value) = value {
169+
let value: u16 = value.parse().context("Could not parse crop value as int")?;
170+
171+
if let Some(&key) = key {
172+
match key {
173+
"top" => top = Some(value),
174+
"left" => left = Some(value),
175+
"width" => width = Some(value),
176+
"height" => height = Some(value),
177+
_ => continue,
178+
}
179+
}
180+
}
181+
}
182+
};
183+
184+
if top.is_none() || left.is_none() || width.is_none() || height.is_none() {
185+
None
186+
} else {
187+
Some(CropSettings {
188+
top: top.unwrap(),
189+
left: left.unwrap(),
190+
width: width.unwrap(),
191+
height: height.unwrap(),
192+
})
193+
}
194+
};
140195

141196
// Create the progress bars
142197
let multi = MultiProgress::new();
@@ -158,6 +213,7 @@ fn execute_cli() -> anyhow::Result<()> {
158213
cast_file,
159214
&out_file,
160215
progress_handler,
216+
crop
161217
)
162218
.expect("TODO");
163219
});

src/frame_renderer/fontkit.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::iter::FromIterator;
1818
use std::sync::Arc;
1919

2020
use super::parse_color;
21-
use crate::types::*;
21+
use crate::{cli::CropSettings, types::*};
2222

2323
lazy_static! {
2424
static ref FONT_DATA: Arc<Vec<u8>> = Arc::new(Vec::from_iter(
@@ -34,7 +34,7 @@ thread_local! {
3434
static FONT: Font = Font::from_bytes(FONT_DATA.clone(), 0).expect("Could not load font");
3535
}
3636

37-
pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame {
37+
pub(crate) fn render_frame_to_png(frame: TerminalFrame, crop: Option<CropSettings>) -> RgbaFrame {
3838
flame!(guard "Render Frame To PNG");
3939

4040
flame!(start "Init Values");
@@ -43,6 +43,11 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame {
4343
// TODO: Configurable background color
4444
const DEFAULT_BG_COLOR: RGBA8 = RGBA::new(0, 0, 0, 255);
4545

46+
let crop_rows = crop.map(|x| x.height).unwrap_or(rows);
47+
let crop_cols = crop.map(|x| x.width).unwrap_or(cols);
48+
let crop_top = crop.map(|x| x.top).unwrap_or(0);
49+
let crop_left = crop.map(|x| x.left).unwrap_or(0);
50+
4651
// Glyph rendering config
4752
lazy_static! {
4853
// static ref TRANS: Transform2F = Transform2F::default();
@@ -73,8 +78,8 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame {
7378
let font_transform =
7479
Transform2F::from_translation(Vector2F::new(0., -font_height_offset as f32));
7580

76-
let height = (rows as i32 * font_height) as usize;
77-
let width = (cols as i32 * font_width) as usize;
81+
let height = (crop_rows as i32 * font_height) as usize;
82+
let width = (crop_cols as i32 * font_width) as usize;
7883

7984
// Image to render to
8085
let pixel_count = width * height;
@@ -89,11 +94,11 @@ pub(crate) fn render_frame_to_png(frame: TerminalFrame) -> RgbaFrame {
8994
flame!(end "Init Values");
9095

9196
flame!(start "Render Cells");
92-
for row in 0..rows {
93-
for col in 0..cols {
97+
for (row_i, row) in (crop_top..(crop_top + crop_rows)).enumerate() {
98+
for (col_i, col) in (crop_left..(crop_left + crop_cols)).enumerate() {
9499
let cell = frame.screen.cell(row, col).expect("Error indexing cell");
95-
let ypos = row as i32 * font_height;
96-
let xpos = col as i32 * font_width;
100+
let ypos = row_i as i32 * font_height;
101+
let xpos = col_i as i32 * font_width;
97102
let mut subimg = image.sub_image_mut(
98103
xpos as usize,
99104
ypos as usize,

src/lib.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use cli::CropSettings;
12
use lazy_static::lazy_static;
23
use thiserror::Error;
34

@@ -76,6 +77,7 @@ fn png_raster_thread<Fi>(
7677
frames: Fi,
7778
progress_sender: flume::Sender<ProgressCmd>,
7879
frame_sender: flume::Sender<RgbaFrame>,
80+
crop: Option<CropSettings>,
7981
) where
8082
Fi: IntoIterator<Item = Result<TerminalFrame, AsciinemaError>>,
8183
{
@@ -93,7 +95,7 @@ fn png_raster_thread<Fi>(
9395
let fs = frame_sender.clone();
9496
let ps = progress_sender.clone();
9597
rayon::spawn(move || {
96-
let frame = frame_renderer::render_frame_to_png(frame);
98+
let frame = frame_renderer::render_frame_to_png(frame, crop);
9799
fs.send(frame).expect("TODO");
98100
ps.send(ProgressCmd::IncrementRasterProgress).expect("TODO");
99101
});
@@ -125,6 +127,7 @@ pub fn convert_to_gif_with_progress<R, W, C>(
125127
reader: R,
126128
writer: W,
127129
update_progress: C,
130+
crop: Option<CropSettings>,
128131
) -> Result<(), Error>
129132
where
130133
R: Read + Send + 'static,
@@ -146,7 +149,7 @@ where
146149

147150
// Spawn the png rasterizer thread
148151
let ps = progress_sender.clone();
149-
rayon::spawn(move || png_raster_thread(term_frames, ps, raster_sender));
152+
rayon::spawn(move || png_raster_thread(term_frames, ps, raster_sender, crop));
150153

151154
// Create gifski gif encoder
152155
let (collector, gif_writer) = gifski::new(gifski::Settings {
@@ -197,10 +200,10 @@ impl gifski::progress::ProgressReporter for GifWriterProgressHandler {
197200
fn done(&mut self, _msg: &str) {}
198201
}
199202

200-
pub fn convert_to_gif<R, W>(reader: R, writer: W) -> Result<(), Error>
203+
pub fn convert_to_gif<R, W>(reader: R, writer: W, crop: Option<CropSettings>) -> Result<(), Error>
201204
where
202205
R: Read + Send + 'static,
203206
W: Write + Send,
204207
{
205-
convert_to_gif_with_progress(reader, writer, NullProgressHandler)
208+
convert_to_gif_with_progress(reader, writer, NullProgressHandler, crop)
206209
}

0 commit comments

Comments
 (0)