use super::parser::{DecodedImage, KittyCommand, KittyError, KittyErrorCode}; use super::store::{InternalImageId, KittyImageStore}; use crate::panes::sixel::PixelRect; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; use zellij_utils::pane_size::SizeInPixels; #[derive(Debug, Clone, Copy, PartialEq)] pub struct KittyImageChunk { pub cell_x: usize, pub cell_y: usize, pub internal_image_id: InternalImageId, pub source_px_x: usize, pub source_px_y: usize, pub source_px_width: usize, pub source_px_height: usize, pub cell_offset_x: u32, pub cell_offset_y: u32, pub z_index: i32, pub dest_cells: (u16, u16), pub scaled_px: Option<(usize, usize)>, pub placement_uid: u64, } #[derive(Debug, Clone, Copy, PartialEq)] pub struct KittyVerticalAnchor { pub canonical_line: usize, pub offset_px_from_line_start: isize, } #[derive(Debug, Clone, PartialEq)] pub struct KittyPlacement { pub image_id: u32, pub placement_id: u32, pub placement_uid: u64, pub internal_id: InternalImageId, pub display_rect: PixelRect, pub source_rect: PixelRect, pub emit_x: usize, pub emit_y: usize, pub scaled_px: Option<(usize, usize)>, pub dest_cells: (u16, u16), pub cell_offset: (u32, u32), pub z_index: i32, pub vertical_anchor: KittyVerticalAnchor, } #[derive(Debug, Clone, Copy, PartialEq)] pub struct KittyReplyData { pub image_id: Option, pub image_number: Option, pub placement_id: Option, pub quiet: u8, } impl KittyReplyData { pub fn from_command(command: &KittyCommand) -> Self { KittyReplyData { image_id: command.image_id, image_number: command.image_number, placement_id: command.placement_id, quiet: command.quiet, } } } #[derive(Debug, Clone)] pub struct KittyGrid { character_cell_size: Rc>>, pub kitty_image_store: Rc>, image_ids: HashMap, image_numbers: HashMap>, placements: Vec, next_synthetic_image_id: u32, front_drops: u64, merged_rows: u64, split_rows: u64, commands_handled: u64, previous_cell_size: Option, rows_below_the_viewport: Option, } #[derive(Clone, Copy, Debug)] pub struct KittyRowsBelowTheViewport { pub first_row: usize, pub total_rows: usize, } fn einval(command: &KittyCommand, message: &str) -> KittyError { KittyError { code: KittyErrorCode::Einval, message: message.to_owned(), image_id: command.image_id, image_number: command.image_number, placement_id: command.placement_id, quiet: command.quiet, } } fn enoent(command: &KittyCommand, message: &str) -> KittyError { KittyError { code: KittyErrorCode::Enoent, message: message.to_owned(), image_id: command.image_id, image_number: command.image_number, placement_id: command.placement_id, quiet: command.quiet, } } impl KittyGrid { pub fn new( character_cell_size: Rc>>, kitty_image_store: Rc>, ) -> Self { let previous_cell_size = *character_cell_size.borrow(); KittyGrid { character_cell_size, kitty_image_store, image_ids: HashMap::new(), image_numbers: HashMap::new(), placements: Vec::new(), next_synthetic_image_id: u32::MAX, front_drops: 0, merged_rows: 0, split_rows: 0, commands_handled: 0, previous_cell_size, rows_below_the_viewport: None, } } pub fn note_command_handled(&mut self) { self.commands_handled += 1; } pub fn commands_handled(&self) -> u64 { self.commands_handled } pub fn front_drops(&self) -> u64 { self.front_drops } pub fn merged_rows(&self) -> u64 { self.merged_rows } pub fn split_rows(&self) -> u64 { self.split_rows } pub fn placements(&self) -> &[KittyPlacement] { &self.placements } pub fn placements_mut(&mut self) -> &mut [KittyPlacement] { &mut self.placements } pub fn retain_placements bool>(&mut self, mut keep: F) { let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for placement in self.placements.drain(..) { if keep(&placement) { kept.push(placement); } else { store.free(placement.internal_id); } } self.placements = kept; } pub fn placement_count(&self) -> usize { self.placements.len() } pub fn pane_image_id_map(&self) -> &HashMap { &self.image_ids } fn alloc_synthetic_image_id(&mut self) -> u32 { loop { let id = self.next_synthetic_image_id; self.next_synthetic_image_id = self.next_synthetic_image_id.wrapping_sub(1); if id != 0 && !self.image_ids.contains_key(&id) { return id; } } } fn drop_image_number_references(&mut self, pane_image_id: u32) { for ids in self.image_numbers.values_mut() { ids.retain(|id| *id != pane_image_id); } self.image_numbers.retain(|_, ids| !ids.is_empty()); } pub fn transmit( &mut self, command: &KittyCommand, image: DecodedImage, ) -> Result { let pane_image_id = if command.image_number.is_some() { self.alloc_synthetic_image_id() } else { command.image_id.unwrap_or(0) }; if pane_image_id != 0 { if let Some(old_internal) = self.image_ids.get(&pane_image_id).copied() { let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for placement in self.placements.drain(..) { if placement.image_id == pane_image_id { store.free(placement.internal_id); } else { kept.push(placement); } } self.placements = kept; if store.get(old_internal).is_some() { store.free(old_internal); } drop(store); self.image_ids.remove(&pane_image_id); self.drop_image_number_references(pane_image_id); } } let internal = self .kitty_image_store .borrow_mut() .store_image(image) .map_err(|mut error| { error.image_id = command.image_id; error.image_number = command.image_number; error.placement_id = command.placement_id; error.quiet = command.quiet; error })?; if let Some(image_number) = command.image_number { self.image_numbers .entry(image_number) .or_insert_with(Vec::new) .push(pane_image_id); } self.image_ids.insert(pane_image_id, internal); self.kitty_image_store.borrow_mut().touch(internal); Ok(pane_image_id) } pub fn resolve_display_target( &self, command: &KittyCommand, ) -> Result<(u32, InternalImageId), KittyError> { let pane_image_id = if let Some(image_id) = command.image_id { image_id } else if let Some(image_number) = command.image_number { *self .image_numbers .get(&image_number) .and_then(|ids| ids.last()) .ok_or_else(|| enoent(command, "unknown image number"))? } else { return Err(enoent(command, "no image id specified")); }; let internal = *self .image_ids .get(&pane_image_id) .ok_or_else(|| enoent(command, "unknown image id"))?; if self.kitty_image_store.borrow().get(internal).is_none() { return Err(enoent(command, "unknown image id")); } Ok((pane_image_id, internal)) } pub fn place( &mut self, pane_image_id: u32, internal: InternalImageId, command: &KittyCommand, cursor_px: (usize, usize), cell: SizeInPixels, vertical_anchor: KittyVerticalAnchor, ) -> Result<(u16, u16), KittyError> { let (image_width, image_height) = { let store = self.kitty_image_store.borrow(); let image = store .get(internal) .ok_or_else(|| enoent(command, "unknown image id"))?; (image.width as usize, image.height as usize) }; let source_x = command.source_x as usize; let source_y = command.source_y as usize; if source_x >= image_width || source_y >= image_height { return Err(einval(command, "source rectangle outside image bounds")); } let source_w = if command.source_w == 0 { image_width - source_x } else { std::cmp::min(command.source_w as usize, image_width - source_x) }; let source_h = if command.source_h == 0 { image_height - source_y } else { std::cmp::min(command.source_h as usize, image_height - source_y) }; if source_w == 0 || source_h == 0 { return Err(einval(command, "empty source rectangle")); } let off_x = std::cmp::min(command.cell_offset_x as usize, cell.width - 1); let off_y = std::cmp::min(command.cell_offset_y as usize, cell.height - 1); let ceil_div = |a: usize, b: usize| (a + b - 1) / b; let (dst_w, dst_h, cols, rows, is_scaled) = match (command.columns as usize, command.rows as usize) { (0, 0) => ( source_w, source_h, ceil_div(source_w + off_x, cell.width), ceil_div(source_h + off_y, cell.height), false, ), (c, 0) => { let dst_w = c * cell.width; let dst_h = std::cmp::max( ((dst_w as f64) * (source_h as f64) / (source_w as f64)).round() as usize, 1, ); let height_px = ((dst_w + off_x) as f64) * (source_h as f64) / (source_w as f64); let rows = std::cmp::max((height_px / cell.height as f64).ceil() as usize, 1); (dst_w, dst_h, c, rows, true) }, (0, r) => { let dst_h = r * cell.height; let dst_w = std::cmp::max( ((dst_h as f64) * (source_w as f64) / (source_h as f64)).round() as usize, 1, ); let width_px = ((dst_h + off_y) as f64) * (source_w as f64) / (source_h as f64); let cols = std::cmp::max((width_px / cell.width as f64).ceil() as usize, 1); (dst_w, dst_h, cols, r, true) }, (c, r) => (c * cell.width, r * cell.height, c, r, true), }; let dest_cells = (cols as u16, rows as u16); if is_scaled { let scaled_bytes = { let store = self.kitty_image_store.borrow(); let image = store .get(internal) .ok_or_else(|| enoent(command, "unknown image id"))?; let cropped = crop_rgba( &image.rgba, image_width, image_height, source_x, source_y, source_w, source_h, ); scale_rgba(&cropped, source_w, source_h, dst_w, dst_h) }; self.kitty_image_store.borrow_mut().add_scaled_variant( internal, dest_cells, scaled_bytes, ); } let display_rect = PixelRect { x: cursor_px.0 + off_x, y: (cursor_px.1 + off_y) as isize, width: dst_w, height: dst_h, }; let placement_id = if pane_image_id != 0 { command.placement_id.unwrap_or(0) } else { 0 }; let mut reused_uid: Option = None; if pane_image_id != 0 && placement_id != 0 { let mut store = self.kitty_image_store.borrow_mut(); self.placements.retain(|placement| { if placement.image_id == pane_image_id && placement.placement_id == placement_id { store.remove_placement_ref(placement.internal_id); reused_uid = Some(placement.placement_uid); false } else { true } }); } let placement_uid = match reused_uid { Some(uid) => uid, None => self.kitty_image_store.borrow_mut().next_placement_uid(), }; self.placements.push(KittyPlacement { image_id: pane_image_id, placement_id, placement_uid, internal_id: internal, display_rect, source_rect: PixelRect { x: source_x, y: source_y as isize, width: source_w, height: source_h, }, emit_x: 0, emit_y: 0, scaled_px: if is_scaled { Some((dst_w, dst_h)) } else { None }, dest_cells, cell_offset: (off_x as u32, off_y as u32), z_index: command.z_index, vertical_anchor, }); self.kitty_image_store .borrow_mut() .add_placement_ref(internal); Ok(dest_cells) } pub fn delete( &mut self, command: &KittyCommand, cursor_cell: (usize, usize), viewport_cells: (usize, usize), scrollback_rows: usize, ) -> Result<(), KittyError> { let specifier = command.delete_specifier.unwrap_or('a'); let uppercase = specifier.is_ascii_uppercase(); let lower = specifier.to_ascii_lowercase(); let cell = *self.character_cell_size.borrow(); let geometry_needed = matches!(lower, 'a' | 'c' | 'p' | 'q' | 'x' | 'y'); let cell = match (cell, geometry_needed) { (Some(cell), _) => cell, (None, true) => return Ok(()), (None, false) => SizeInPixels { width: 1, height: 1, }, }; let cell_w = cell.width; let cell_h = cell.height; let la_px = scrollback_rows * cell_h; let probe_rect = |x: usize, y: usize, width: usize, height: usize| PixelRect { x, y: y as isize, width, height, }; let target_image_id: Option = match lower { 'i' => Some( command .image_id .ok_or_else(|| einval(command, "missing image id for delete"))?, ), 'n' => { let image_number = command .image_number .ok_or_else(|| einval(command, "missing image number for delete"))?; match self .image_numbers .get(&image_number) .and_then(|ids| ids.last()) { Some(id) => Some(*id), None => return Ok(()), } }, _ => None, }; let column_probe_x = |source_x: u32| -> Option { if source_x == 0 { None } else { Some((source_x as usize - 1) * cell_w) } }; let row_probe_y = |source_y: u32| -> Option { if source_y == 0 { None } else { Some(la_px + (source_y as usize - 1) * cell_h) } }; let matches_placement = |placement: &KittyPlacement| -> bool { match lower { 'a' => placement .display_rect .intersecting_rect(&probe_rect( 0, la_px, viewport_cells.0 * cell_w, viewport_cells.1 * cell_h, )) .is_some(), 'i' | 'n' => { placement.image_id == target_image_id.unwrap() && command .placement_id .map(|placement_id| placement.placement_id == placement_id) .unwrap_or(true) }, 'c' => placement .display_rect .intersecting_rect(&probe_rect( cursor_cell.0 * cell_w, la_px + cursor_cell.1 * cell_h, cell_w, cell_h, )) .is_some(), 'p' => match ( column_probe_x(command.source_x), row_probe_y(command.source_y), ) { (Some(px), Some(py)) => placement .display_rect .intersecting_rect(&probe_rect(px, py, cell_w, cell_h)) .is_some(), _ => false, }, 'q' => { placement.z_index == command.z_index && match ( column_probe_x(command.source_x), row_probe_y(command.source_y), ) { (Some(px), Some(py)) => placement .display_rect .intersecting_rect(&probe_rect(px, py, cell_w, cell_h)) .is_some(), _ => false, } }, 'x' => match column_probe_x(command.source_x) { Some(px) => placement .display_rect .intersecting_rect(&probe_rect(px, 0, cell_w, usize::MAX / 2)) .is_some(), None => false, }, 'y' => match row_probe_y(command.source_y) { Some(py) => placement .display_rect .intersecting_rect(&probe_rect(0, py, usize::MAX / 2, cell_h)) .is_some(), None => false, }, 'z' => placement.z_index == command.z_index, 'r' => { placement.image_id >= command.source_x && placement.image_id <= command.source_y }, _ => false, } }; { let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for placement in self.placements.drain(..) { if matches_placement(&placement) { if uppercase { store.free(placement.internal_id); } else { store.remove_placement_ref(placement.internal_id); } } else { kept.push(placement); } } self.placements = kept; } if uppercase { match lower { 'i' | 'n' => { let target = target_image_id.unwrap(); let no_surviving = !self .placements .iter() .any(|placement| placement.image_id == target); if no_surviving { if let Some(internal) = self.image_ids.get(&target).copied() { self.kitty_image_store.borrow_mut().free(internal); } } }, 'r' => { let in_range: Vec<(u32, InternalImageId)> = self .image_ids .iter() .filter(|(id, _)| **id >= command.source_x && **id <= command.source_y) .map(|(id, internal)| (*id, *internal)) .collect(); let mut store = self.kitty_image_store.borrow_mut(); for (_, internal) in in_range { if store.refcount(internal) == Some(0) { store.free(internal); } } }, _ => {}, } let store = self.kitty_image_store.borrow(); self.image_ids .retain(|_, internal| store.get(*internal).is_some()); drop(store); let live_ids: Vec = self.image_ids.keys().copied().collect(); self.image_numbers.values_mut().for_each(|ids| { ids.retain(|id| live_ids.contains(id)); }); self.image_numbers.retain(|_, ids| !ids.is_empty()); } Ok(()) } pub fn note_rows_below_the_viewport(&mut self, frame: Option) { self.rows_below_the_viewport = frame; } pub fn note_rows_below_the_viewport_shifted(&mut self, row_delta: isize) { self.rows_below_the_viewport = self.rows_below_the_viewport .map(|frame| KittyRowsBelowTheViewport { first_row: std::cmp::max(0, frame.first_row as isize + row_delta) as usize, total_rows: std::cmp::max(0, frame.total_rows as isize + row_delta) as usize, }); } pub fn settle_placements_below_the_viewport( &mut self, frame: Option, ) -> bool { let previous = std::mem::replace(&mut self.rows_below_the_viewport, frame); let (previous, frame) = match (previous, frame) { (Some(previous), Some(frame)) => (previous, frame), _ => return false, }; let row_delta = frame.total_rows as isize - previous.total_rows as isize; if row_delta == 0 || self.placements.is_empty() { return false; } let cell = match *self.character_cell_size.borrow() { Some(cell) => cell, None => return false, }; let first_row_px = previous.first_row as isize * cell.height as isize; let delta_px = row_delta * cell.height as isize; let mut settled = false; for placement in self.placements.iter_mut() { if placement.display_rect.y >= first_row_px { placement.display_rect.y += delta_px; settled = true; } } settled } pub fn offset_grid_top(&mut self) { self.front_drops += 1; if let Some(cell) = *self.character_cell_size.borrow() { self.rows_below_the_viewport = self.rows_below_the_viewport .map(|frame| KittyRowsBelowTheViewport { first_row: std::cmp::max(0, frame.first_row as isize + -1) as usize, total_rows: std::cmp::max(0, frame.total_rows as isize + -1) as usize, }); let height_to_reduce = cell.height as isize; let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for mut placement in self.placements.drain(..) { placement.display_rect.y -= height_to_reduce; if placement.display_rect.y + placement.display_rect.height as isize <= 0 { store.free(placement.internal_id); } else { kept.push(placement); } } self.placements = kept; } } pub fn merge_rows_into_line_start(&mut self, line_start_row: usize, merged_row_count: usize) { if merged_row_count == 0 { return; } if let Some(cell) = *self.character_cell_size.borrow() { self.merged_rows += merged_row_count as u64; self.rows_below_the_viewport = self.rows_below_the_viewport .map(|frame| KittyRowsBelowTheViewport { first_row: std::cmp::max( 0, frame.first_row as isize + -(merged_row_count as isize), ) as usize, total_rows: std::cmp::max( 0, frame.total_rows as isize + -(merged_row_count as isize), ) as usize, }); let line_start_px = line_start_row as isize * cell.height as isize; let merged_px = merged_row_count as isize * cell.height as isize; for placement in self.placements.iter_mut() { if placement.display_rect.y >= line_start_px { placement.display_rect.y = std::cmp::max(line_start_px, placement.display_rect.y - merged_px); } } } } pub fn split_line_start_into_rows(&mut self, line_start_row: usize, split_row_count: usize) { if split_row_count == 0 { return; } if let Some(cell) = *self.character_cell_size.borrow() { self.split_rows += split_row_count as u64; self.rows_below_the_viewport = self.rows_below_the_viewport .map(|frame| KittyRowsBelowTheViewport { first_row: std::cmp::max( 0, frame.first_row as isize + split_row_count as isize, ) as usize, total_rows: std::cmp::max( 0, frame.total_rows as isize + split_row_count as isize, ) as usize, }); let line_start_px = line_start_row as isize * cell.height as isize; let split_px = split_row_count as isize * cell.height as isize; for placement in self.placements.iter_mut() { if placement.display_rect.y >= line_start_px + cell.height as isize { placement.display_rect.y += split_px; } } } } pub fn has_placement_anchored_to_first_canonical_line(&self) -> bool { self.placements .iter() .any(|placement| placement.vertical_anchor.canonical_line == 0) } pub fn drop_first_canonical_anchor_line(&mut self, first_canonical_row: usize) { let cell_height = match *self.character_cell_size.borrow() { Some(cell) => cell.height as isize, None => return, }; let first_line_start_px = first_canonical_row as isize * cell_height; for placement in self.placements.iter_mut() { if placement.vertical_anchor.canonical_line == 0 { placement.vertical_anchor.offset_px_from_line_start = placement.display_rect.y - first_line_start_px; } else { placement.vertical_anchor.canonical_line -= 1; } } } pub fn insert_canonical_anchor_line_at_front(&mut self) { for placement in self.placements.iter_mut() { placement.vertical_anchor.canonical_line += 1; } } pub fn apply_region_scroll( &mut self, region_top_px: isize, region_bottom_px: isize, n_px: isize, la_delta_px: isize, viewport_top_px: isize, preserve_above_region_top: bool, ) { let delta_inside = la_delta_px - n_px; let delta_outside = la_delta_px; let shifted_region_top = region_top_px + la_delta_px; let shifted_region_bottom = region_bottom_px + la_delta_px; let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for mut placement in self.placements.drain(..) { let top = placement.display_rect.y; let bottom = top + placement.display_rect.height as isize; if bottom <= viewport_top_px { kept.push(placement); continue; } let entirely_inside = top >= region_top_px && bottom <= region_bottom_px; let intersects_region = top < region_bottom_px && bottom > region_top_px; if entirely_inside { placement.display_rect.y += delta_inside; let new_top = placement.display_rect.y; let new_bottom = new_top + placement.display_rect.height as isize; if new_top >= shifted_region_bottom || (!preserve_above_region_top && new_bottom <= shifted_region_top) { store.free(placement.internal_id); continue; } if !preserve_above_region_top && new_top < shifted_region_top { let cut = (shifted_region_top - new_top) as usize; placement.emit_y += cut; placement.display_rect.y = shifted_region_top; placement.display_rect.height -= cut; } let new_bottom = placement.display_rect.y + placement.display_rect.height as isize; if new_bottom > shifted_region_bottom { placement.display_rect.height -= (new_bottom - shifted_region_bottom) as usize; } if placement.display_rect.height == 0 { store.free(placement.internal_id); continue; } kept.push(placement); } else if intersects_region { if preserve_above_region_top && top < region_top_px { placement.display_rect.y += delta_inside; kept.push(placement); continue; } placement.display_rect.y += delta_outside; let new_top = placement.display_rect.y; let new_bottom = new_top + placement.display_rect.height as isize; if new_top < shifted_region_top { placement.display_rect.height = (shifted_region_top - new_top) as usize; } else if new_bottom > shifted_region_bottom { let cut = (shifted_region_bottom - new_top) as usize; placement.emit_y += cut; placement.display_rect.y = shifted_region_bottom; placement.display_rect.height -= cut; } else { store.free(placement.internal_id); continue; } if placement.display_rect.height == 0 { store.free(placement.internal_id); continue; } kept.push(placement); } else { placement.display_rect.y += delta_outside; kept.push(placement); } } self.placements = kept; } pub fn character_cell_size_possibly_changed(&mut self) { if let (Some(previous_cell_size), Some(character_cell_size)) = (self.previous_cell_size, *self.character_cell_size.borrow()) { if previous_cell_size != character_cell_size { let mut regenerations: Vec<(InternalImageId, (u16, u16), PixelRect)> = Vec::new(); for placement in self.placements.iter_mut() { placement.display_rect.x = (placement.display_rect.x / previous_cell_size.width) * character_cell_size.width; placement.display_rect.y = (placement.display_rect.y / previous_cell_size.height as isize) * character_cell_size.height as isize; if placement.scaled_px.is_some() { let (cols, rows) = placement.dest_cells; placement.display_rect.width = cols as usize * character_cell_size.width; placement.display_rect.height = rows as usize * character_cell_size.height; placement.scaled_px = Some((placement.display_rect.width, placement.display_rect.height)); placement.emit_x = 0; placement.emit_y = 0; regenerations.push(( placement.internal_id, placement.dest_cells, placement.source_rect, )); } } for (internal, dest_cells, source_rect) in regenerations { let scaled_bytes = { let store = self.kitty_image_store.borrow(); match store.get(internal) { Some(image) => { let cropped = crop_rgba( &image.rgba, image.width as usize, image.height as usize, source_rect.x, source_rect.y as usize, source_rect.width, source_rect.height, ); Some(scale_rgba( &cropped, source_rect.width, source_rect.height, dest_cells.0 as usize * character_cell_size.width, dest_cells.1 as usize * character_cell_size.height, )) }, None => None, } }; if let Some(scaled_bytes) = scaled_bytes { self.kitty_image_store.borrow_mut().add_scaled_variant( internal, dest_cells, scaled_bytes, ); } } } } self.previous_cell_size = *self.character_cell_size.borrow(); } pub fn clear_all_placements(&mut self) { let mut store = self.kitty_image_store.borrow_mut(); for placement in self.placements.drain(..) { store.free(placement.internal_id); } } pub fn clear_visible_placements(&mut self, scrollback_size_in_lines: usize) { let cell_size = { *self.character_cell_size.borrow() }; let cell_height = match cell_size { Some(cell) => cell.height as isize, None => { self.clear_all_placements(); return; }, }; let scrollback_top = scrollback_size_in_lines as isize * cell_height; let mut store = self.kitty_image_store.borrow_mut(); let mut kept = Vec::with_capacity(self.placements.len()); for placement in self.placements.drain(..) { let screen_relative_top_row = (placement.display_rect.y - scrollback_top).div_euclid(cell_height); let dest_rows = placement.dest_cells.1 as isize; if screen_relative_top_row + dest_rows > 0 { store.free(placement.internal_id); } else { kept.push(placement); } } self.placements = kept; } pub fn image_cell_coordinates_in_viewport( &self, viewport_height: usize, scrollback_height: usize, ) -> Vec<(usize, usize, usize, usize)> { match *self.character_cell_size.borrow() { Some(character_cell_size) => self .placements .iter() .map(|placement| { let pixel_rect = &placement.display_rect; let scrollback_size_in_pixels = scrollback_height * character_cell_size.height; let y_pixel_coordinates_in_viewport = pixel_rect.y - scrollback_size_in_pixels as isize; let image_y = std::cmp::max(y_pixel_coordinates_in_viewport, 0) as usize / character_cell_size.height; let image_x = pixel_rect.x / character_cell_size.width; let image_height_in_pixels = if y_pixel_coordinates_in_viewport < 0 { pixel_rect.height as isize + y_pixel_coordinates_in_viewport } else { pixel_rect.height as isize }; let image_height = image_height_in_pixels as usize / character_cell_size.height; let image_width = pixel_rect.width / character_cell_size.width; let height_remainder = if image_height_in_pixels as usize % character_cell_size.height > 0 { 1 } else { 0 }; let width_remainder = if pixel_rect.width % character_cell_size.width > 0 { 1 } else { 0 }; let image_top_edge = image_y; let image_bottom_edge = std::cmp::min(image_y + image_height + height_remainder, viewport_height); let image_left_edge = image_x; let image_right_edge = image_x + image_width + width_remainder; ( image_top_edge, image_bottom_edge, image_left_edge, image_right_edge, ) }) .collect(), None => vec![], } } pub fn viewport_kitty_chunks( &self, viewport_height: usize, scrollback_size_in_lines: usize, viewport_width_in_cells: usize, viewport_x_offset: usize, viewport_y_offset: usize, ) -> Vec { let mut full = HashMap::new(); full.insert(0, viewport_height); self.changed_kitty_chunks_in_viewport( full, scrollback_size_in_lines, viewport_width_in_cells, viewport_x_offset, viewport_y_offset, ) } pub fn changed_kitty_chunks_in_viewport( &self, changed_rects: HashMap, scrollback_size_in_lines: usize, viewport_width_in_cells: usize, viewport_x_offset: usize, viewport_y_offset: usize, ) -> Vec { let mut changed_kitty_image_chunks = vec![]; if let Some(character_cell_size) = { *self.character_cell_size.borrow() } { for placement in &self.placements { let image_pixel_rect = &placement.display_rect; for (line_index, line_count) in &changed_rects { let changed_rect_pixel_height = line_count * character_cell_size.height; let changed_rect_top_edge = ((line_index + scrollback_size_in_lines) * character_cell_size.height) as isize; let changed_rect_bottom_edge = changed_rect_top_edge + changed_rect_pixel_height as isize; let image_top_edge = image_pixel_rect.y; let image_bottom_edge = image_pixel_rect.y + image_pixel_rect.height as isize; let cell_x_in_current_pane = image_pixel_rect.x / character_cell_size.width; let cell_x = viewport_x_offset + cell_x_in_current_pane; let image_pixel_width = if image_pixel_rect.x + image_pixel_rect.width <= (viewport_width_in_cells * character_cell_size.width) { image_pixel_rect.width } else { (viewport_width_in_cells * character_cell_size.width) .saturating_sub(image_pixel_rect.x) }; if image_pixel_width == 0 { continue; } let image_cell_distance_from_scrollback_top = std::cmp::max( image_top_edge.div_euclid(character_cell_size.height as isize), 0, ) as usize; let image_cell_distance_from_changed_rect_top = image_cell_distance_from_scrollback_top .saturating_sub(line_index + scrollback_size_in_lines); let cell_y = viewport_y_offset + line_index + image_cell_distance_from_changed_rect_top; let source_px_x = placement.emit_x; let source_px_y = placement.emit_y + std::cmp::max(changed_rect_top_edge - image_top_edge, 0) as usize; let source_px_height = std::cmp::min( (std::cmp::min(changed_rect_bottom_edge, image_bottom_edge) - std::cmp::max(changed_rect_top_edge, image_top_edge)) as usize, image_pixel_rect.height, ); if (image_top_edge >= changed_rect_top_edge && image_top_edge <= changed_rect_bottom_edge) || (image_bottom_edge >= changed_rect_top_edge && image_bottom_edge <= changed_rect_bottom_edge) || (image_bottom_edge >= changed_rect_bottom_edge && image_top_edge <= changed_rect_top_edge) { changed_kitty_image_chunks.push(KittyImageChunk { cell_x, cell_y, internal_image_id: placement.internal_id, source_px_x, source_px_y, source_px_width: image_pixel_width, source_px_height, cell_offset_x: placement.cell_offset.0, cell_offset_y: placement.cell_offset.1, z_index: placement.z_index, dest_cells: placement.dest_cells, scaled_px: placement.scaled_px, placement_uid: placement.placement_uid, }); } } } } changed_kitty_image_chunks } } pub fn crop_rgba( src: &[u8], src_w: usize, src_h: usize, x: usize, y: usize, w: usize, h: usize, ) -> Vec { let mut out = Vec::with_capacity(w * h * 4); for row in y..y + h { if row >= src_h { break; } let row_start = (row * src_w + x) * 4; let row_end = std::cmp::min(row_start + w * 4, (row * src_w + src_w) * 4); out.extend_from_slice(&src[row_start..row_end]); } out } pub fn scale_rgba(src: &[u8], src_w: usize, src_h: usize, dst_w: usize, dst_h: usize) -> Vec { let mut out = Vec::with_capacity(dst_w * dst_h * 4); if src_w == 0 || src_h == 0 || dst_w == 0 || dst_h == 0 { return out; } for dy in 0..dst_h { let fy = (dy as f64 + 0.5) * (src_h as f64) / (dst_h as f64) - 0.5; let y0 = fy.floor().max(0.0).min((src_h - 1) as f64) as usize; let y1 = std::cmp::min(y0 + 1, src_h - 1); let ty = (fy - y0 as f64).max(0.0).min(1.0); for dx in 0..dst_w { let fx = (dx as f64 + 0.5) * (src_w as f64) / (dst_w as f64) - 0.5; let x0 = fx.floor().max(0.0).min((src_w - 1) as f64) as usize; let x1 = std::cmp::min(x0 + 1, src_w - 1); let tx = (fx - x0 as f64).max(0.0).min(1.0); for channel in 0..4 { let p00 = src[(y0 * src_w + x0) * 4 + channel] as f64; let p10 = src[(y0 * src_w + x1) * 4 + channel] as f64; let p01 = src[(y1 * src_w + x0) * 4 + channel] as f64; let p11 = src[(y1 * src_w + x1) * 4 + channel] as f64; let value = (1.0 - ty) * ((1.0 - tx) * p00 + tx * p10) + ty * ((1.0 - tx) * p01 + tx * p11); out.push(value.round().max(0.0).min(255.0) as u8); } } } out } #[cfg(test)] #[path = "./unit/grid_state_tests.rs"] mod grid_state_tests;