1
1
mirror of https://github.com/wez/wezterm.git synced 2024-07-07 11:06:30 +03:00

fix kitty image protocol display parameters

This commit is contained in:
jonboh 2024-01-24 19:57:01 +01:00 committed by Wez Furlong
parent e7111a2bfa
commit 500f84617e
5 changed files with 125 additions and 73 deletions

View File

@ -22,17 +22,19 @@ pub struct ImageAttachParams {
pub image_height: u32,
/// Dimensions of the area of the image to be displayed, in pixels
pub source_width: u32,
pub source_height: u32,
pub source_width: Option<u32>,
pub source_height: Option<u32>,
/// Origin of the source data region, top left corner in pixels
pub source_origin_x: u32,
pub source_origin_y: u32,
/// When rendering in the cell, use this offset from the top left
/// of the cell
pub padding_left: u16,
pub padding_top: u16,
/// of the cell. This is only used in the Kitty image protocol.
/// This should be smaller than the size of the cell. Larger values will
/// be truncated.
pub cell_padding_left: u16,
pub cell_padding_top: u16,
/// Plane on which to display the image
pub z_index: i32,
@ -68,45 +70,59 @@ impl TerminalState {
let physical_rows = self.screen().physical_rows;
let cell_pixel_width = self.pixel_width / physical_cols;
let cell_pixel_height = self.pixel_height / physical_rows;
let avail_width = params.image_width.saturating_sub(params.source_origin_x);
let avail_height = params.image_height.saturating_sub(params.source_origin_y);
let source_width = params.source_width.min(params.image_width).min(avail_width);
let source_height = params
let cell_padding_left = params
.cell_padding_left
.min(cell_pixel_width.saturating_sub(1) as u16);
let cell_padding_top = params
.cell_padding_top
.min(cell_pixel_height.saturating_sub(1) as u16);
//NOTE: review conflicting origin vs drawing going over image
let image_max_width = params.image_width.saturating_sub(params.source_origin_x);
let image_max_height = params.image_height.saturating_sub(params.source_origin_y);
let draw_width = params
.source_width
.unwrap_or(image_max_width)
.min(image_max_width);
let draw_height = params
.source_height
.min(params.image_height)
.min(avail_height);
.unwrap_or(image_max_height)
.min(image_max_height);
let aspect = source_width as f32 / source_height as f32;
let width_in_cells = params
let (fullcells_width, remainder_width_cell, x_delta_divisor) = params
.columns
.unwrap_or_else(|| (source_width as f32 / cell_pixel_width as f32).ceil() as usize);
let height_in_cells = params
.map(|cols| {
(
cols,
0,
(cols * cell_pixel_width) as u32 * params.image_width / draw_width,
)
})
.unwrap_or_else(|| {
(
draw_width as usize / cell_pixel_width,
draw_width as usize % cell_pixel_width,
params.image_width,
)
});
let (fullcells_height, remainder_height_cell, y_delta_divisor) = params
.rows
.unwrap_or_else(|| (source_height as f32 / cell_pixel_height as f32).ceil() as usize);
// Figure out the desired pixel dimensions, respecting the original
// aspect of the picture if they specific rows/columns as the max size.
let target_pixel_width = if params.columns.is_some() {
if source_width > source_height {
width_in_cells * cell_pixel_width
} else {
((height_in_cells * cell_pixel_height) as f32 * aspect).ceil() as usize
}
} else {
source_width as usize
};
let target_pixel_height = if params.rows.is_some() {
if source_height > source_width {
height_in_cells * cell_pixel_height
} else {
((width_in_cells * cell_pixel_width) as f32 / aspect).ceil() as usize
}
} else {
source_height as usize
};
.map(|rows| {
(
rows,
0,
(rows * cell_pixel_height) as u32 * params.image_height / draw_height,
)
})
.unwrap_or_else(|| {
(
draw_height as usize / cell_pixel_height,
draw_height as usize % cell_pixel_height,
params.image_height,
)
});
let target_pixel_width = fullcells_width * cell_pixel_width + remainder_width_cell;
let target_pixel_height = fullcells_height * cell_pixel_height + remainder_height_cell;
let first_row = self.screen().visible_row_to_stable_row(self.cursor.y);
let mut ypos = NotNan::new(params.source_origin_y as f32 / params.image_height as f32)
@ -115,6 +131,15 @@ impl TerminalState {
.context("computing xpos")?;
let cursor_x = self.cursor.x;
let width_in_cells = fullcells_width + (remainder_width_cell > 0) as usize;
let height_in_cells = fullcells_height + (remainder_height_cell > 0) as usize;
let height_in_cells = if params.do_not_move_cursor {
height_in_cells.min(self.screen().physical_rows - self.cursor.y as usize)
} else {
height_in_cells
};
log::debug!(
"image is {}x{} cells (cell is {}x{}), target pixel dims {}x{}, {:?}, (term is {}x{}@{}x{})",
width_in_cells,
@ -130,16 +155,10 @@ impl TerminalState {
self.pixel_height
);
let height_in_cells = if params.do_not_move_cursor {
height_in_cells.min(self.screen().physical_rows - self.cursor.y as usize)
} else {
height_in_cells
};
let mut remain_y = target_pixel_height as usize;
let mut remain_y = target_pixel_height;
for y in 0..height_in_cells {
let padding_bottom = cell_pixel_height.saturating_sub(remain_y) as u16;
let y_delta = (remain_y.min(cell_pixel_height) as f32) / (target_pixel_height as f32);
let y_delta = (remain_y.min(cell_pixel_height) as f32) / y_delta_divisor as f32;
remain_y = remain_y.saturating_sub(cell_pixel_height);
let mut xpos = start_xpos;
@ -152,22 +171,22 @@ impl TerminalState {
"setting cells for y={} x=[{}..{}]",
cursor_y,
cursor_x,
cursor_x + width_in_cells
cursor_x + fullcells_width
);
let mut remain_x = target_pixel_width as usize;
let mut remain_x = target_pixel_width;
for x in 0..width_in_cells {
let padding_right = cell_pixel_width.saturating_sub(remain_x) as u16;
let x_delta = (remain_x.min(cell_pixel_width) as f32) / (target_pixel_width as f32);
let x_delta = (remain_x.min(cell_pixel_width) as f32) / x_delta_divisor as f32;
remain_x = remain_x.saturating_sub(cell_pixel_width);
log::debug!(
"x_delta {} ({} px), y_delta {} ({} px), padding_right={}, padding_bottom={}",
x_delta,
x_delta * source_width as f32,
x_delta * x_delta_divisor as f32,
y_delta,
y_delta * source_width as f32,
y_delta * y_delta_divisor as f32,
padding_right,
padding_bottom
);
remain_x = remain_x.saturating_sub(cell_pixel_width);
let mut cell = self
.screen_mut()
.get_cell(cursor_x + x, cursor_y)
@ -178,8 +197,8 @@ impl TerminalState {
TextureCoordinate::new(xpos + x_delta, ypos + y_delta),
params.data.clone(),
params.z_index,
params.padding_left,
params.padding_top,
cell_padding_left,
cell_padding_top,
padding_right,
padding_bottom,
params.image_id,
@ -202,6 +221,11 @@ impl TerminalState {
}
}
// adjust cursor position if the drawn cells move beyond current cell
let x_padding_shift = (draw_width as usize + cell_padding_left as usize
> cell_pixel_width * width_in_cells) as i64;
let y_padding_shift = (draw_height as usize + cell_padding_top as usize
> cell_pixel_height * height_in_cells) as i64;
if !params.do_not_move_cursor {
// Sixel places the cursor under the left corner of the image,
// unless sixel_scrolls_right is enabled.
@ -213,8 +237,8 @@ impl TerminalState {
if bottom_right {
self.set_cursor_pos(
&Position::Relative(width_in_cells as i64),
&Position::Relative(0),
&Position::Relative(width_in_cells as i64 + x_padding_shift),
&Position::Relative(y_padding_shift),
);
}
}

View File

@ -132,12 +132,12 @@ impl TerminalState {
if let Err(err) = self.assign_image_to_cells(ImageAttachParams {
image_width: width as u32,
image_height: height as u32,
source_width: width as u32,
source_height: height as u32,
source_width: None,
source_height: None,
source_origin_x: 0,
source_origin_y: 0,
padding_left: 0,
padding_top: 0,
cell_padding_left: 0,
cell_padding_top: 0,
z_index: 0,
columns: None,
rows: None,

View File

@ -35,7 +35,9 @@ impl KittyImageState {
}
fn record_id_to_data(&mut self, image_id: u32, data: Arc<ImageData>) {
self.remove_data_for_id(image_id);
if image_id != 0 {
self.remove_data_for_id(image_id);
}
self.prune_unreferenced();
self.used_memory += data.len();
self.id_to_data.insert(image_id, data);
@ -98,7 +100,9 @@ impl TerminalState {
placement,
verbosity
);
self.kitty_remove_placement(image_id, placement.placement_id);
if image_id != 0 {
self.kitty_remove_placement(image_id, placement.placement_id);
}
let img = Arc::clone(self.kitty_img.id_to_data.get(&image_id).ok_or_else(|| {
anyhow::anyhow!(
"no matching image id {} in id_to_data for image_number {:?}",
@ -112,12 +116,12 @@ impl TerminalState {
let info = self.assign_image_to_cells(ImageAttachParams {
image_width,
image_height,
source_width: placement.w.unwrap_or(image_width),
source_height: placement.h.unwrap_or(image_height),
source_width: placement.w,
source_height: placement.h,
source_origin_x: placement.x.unwrap_or(0),
source_origin_y: placement.y.unwrap_or(0),
padding_left: placement.x_offset.unwrap_or(0) as u16,
padding_top: placement.y_offset.unwrap_or(0) as u16,
cell_padding_left: placement.x_offset.unwrap_or(0) as u16,
cell_padding_top: placement.y_offset.unwrap_or(0) as u16,
data: img,
style: ImageAttachStyle::Kitty,
z_index: placement.z_index.unwrap_or(0),

View File

@ -132,14 +132,14 @@ impl TerminalState {
if let Err(err) = self.assign_image_to_cells(ImageAttachParams {
image_width: width,
image_height: height,
source_width: width,
source_height: height,
source_width: None,
source_height: None,
rows: None,
columns: None,
source_origin_x: 0,
source_origin_y: 0,
padding_left: 0,
padding_top: 0,
cell_padding_left: 0,
cell_padding_top: 0,
data: image_data,
style: ImageAttachStyle::Sixel,
z_index: 0,

View File

@ -24,5 +24,29 @@ def write_chunked(**cmd):
sys.stdout.flush()
cmd.clear()
with open(sys.argv[-1], 'rb') as f:
write_chunked(a='T', f=100, data=f.read())
def just_print(img):
write_chunked(a='T', f=100, data=img)
def test_x_y_w_h_c_r(img):
write_chunked(a='T', f=100, y=150, h=105, C=1, data=img)
write_chunked(a='T', f=100, y=200, w=1, data=img)
write_chunked(a='T', f=100, y=200, h=1, data=img)
write_chunked(a='T', f=100, x=300, y=100, h=10, w=10, data=img)
write_chunked(a='T', f=100, x=300, y=100, h=10, w=10, r=15, data=img)
write_chunked(a='T', f=100, x=300, y=100, h=10, w=10, c=1, data=img)
write_chunked(a='T', f=100, x=300, y=100, h=10, w=10, r=1, data=img)
write_chunked(a='T', f=100, x=300, y=100, h=10, w=10, r=15, c=20, data=img)
def test_cell_offsets(img):
write_chunked(a='T', f=100, h=10, w=10, X=2, Y=2, data=img)
write_chunked(a='T', f=100, h=20, w=10, X=2, Y=2, data=img)
write_chunked(a='T', f=100, h=2, Y=20, data=img)
write_chunked(a='T', f=100, h=38, w=2, X=19, data=img)
if __name__ == "__main__":
with open(sys.argv[-1], 'rb') as f:
img = f.read()
just_print(img)
# test_x_y_w_h_c_r(img)
# test_cell_offsets(img)