From f091a69790b720a3e4b7d42388367bec63d4a2d8 Mon Sep 17 00:00:00 2001 From: Bryan Lee <38807139+liby@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:06:06 +0800 Subject: [PATCH 1/2] Fix aspect ratio when rendering images with kitty protocol --- src/renderer/Metal.zig | 13 +++++++----- src/renderer/OpenGL.zig | 13 +++++++----- src/terminal/kitty/graphics_storage.zig | 27 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index d2477725c..60fcb52c6 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -2032,13 +2032,16 @@ fn prepKittyPlacement( break :offset_y @intCast(offset_pixels); } else 0; + // Get the grid size that respects aspect ratio + const grid_size = p.gridSize(image.*, t); + // If we specify `rows` then our offset above is in viewport space // and not in the coordinate space of the source image. Without `rows` // that's one and the same. - const source_offset_y: u32 = if (p.rows > 0) source_offset_y: { + const source_offset_y: u32 = if (grid_size.rows > 0) source_offset_y: { // Determine the scale factor to apply for this row height. const image_height: f64 = @floatFromInt(image.height); - const viewport_height: f64 = @floatFromInt(p.rows * self.grid_metrics.cell_height); + const viewport_height: f64 = @floatFromInt(grid_size.rows * self.grid_metrics.cell_height); const scale: f64 = image_height / viewport_height; // Apply the scale to the offset @@ -2071,11 +2074,11 @@ fn prepKittyPlacement( image.height -| source_y; // Calculate the width/height of our image. - const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; - const dest_height = if (p.rows > 0) rows: { + const dest_width = grid_size.cols * self.grid_metrics.cell_width; + const dest_height = if (grid_size.rows > 0) rows: { // Clip to the viewport to handle scrolling. offset_y is already in // viewport scale so we can subtract it directly. - break :rows (p.rows * self.grid_metrics.cell_height) - offset_y; + break :rows (grid_size.rows * self.grid_metrics.cell_height) - offset_y; } else source_height; // Accumulate the placement diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 20a0a82b3..3acd62711 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1073,13 +1073,16 @@ fn prepKittyPlacement( break :offset_y @intCast(offset_pixels); } else 0; + // Get the grid size that respects aspect ratio + const grid_size = p.gridSize(image.*, t); + // If we specify `rows` then our offset above is in viewport space // and not in the coordinate space of the source image. Without `rows` // that's one and the same. - const source_offset_y: u32 = if (p.rows > 0) source_offset_y: { + const source_offset_y: u32 = if (grid_size.rows > 0) source_offset_y: { // Determine the scale factor to apply for this row height. const image_height: f64 = @floatFromInt(image.height); - const viewport_height: f64 = @floatFromInt(p.rows * self.grid_metrics.cell_height); + const viewport_height: f64 = @floatFromInt(grid_size.rows * self.grid_metrics.cell_height); const scale: f64 = image_height / viewport_height; // Apply the scale to the offset @@ -1112,11 +1115,11 @@ fn prepKittyPlacement( image.height -| source_y; // Calculate the width/height of our image. - const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; - const dest_height = if (p.rows > 0) rows: { + const dest_width = grid_size.cols * self.grid_metrics.cell_width; + const dest_height = if (grid_size.rows > 0) rows: { // Clip to the viewport to handle scrolling. offset_y is already in // viewport scale so we can subtract it directly. - break :rows (p.rows * self.grid_metrics.cell_height) - offset_y; + break :rows (grid_size.rows * self.grid_metrics.cell_height) - offset_y; } else source_height; // Accumulate the placement diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index e55dc45c3..a04a864c4 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -687,6 +687,33 @@ pub const ImageStorage = struct { // Calculate our image size in grid cells const width_f64: f64 = @floatFromInt(width_px); const height_f64: f64 = @floatFromInt(height_px); + + // If only columns is specified, calculate rows based on aspect ratio + if (self.columns > 0 and self.rows == 0) { + const cols_f64: f64 = @floatFromInt(self.columns); + const cols_px = cols_f64 * cell_width_f64; + const aspect_ratio = height_f64 / width_f64; + const rows_px = cols_px * aspect_ratio; + const rows_cells = rows_px / cell_height_f64; + return .{ + .cols = self.columns, + .rows = @intFromFloat(@ceil(rows_cells)), + }; + } + + // If only rows is specified, calculate columns based on aspect ratio + if (self.rows > 0 and self.columns == 0) { + const rows_f64: f64 = @floatFromInt(self.rows); + const rows_px = rows_f64 * cell_height_f64; + const aspect_ratio = width_f64 / height_f64; + const cols_px = rows_px * aspect_ratio; + const cols_cells = cols_px / cell_width_f64; + return .{ + .cols = @intFromFloat(@ceil(cols_cells)), + .rows = self.rows, + }; + } + const width_cells: u32 = @intFromFloat(@ceil(width_f64 / cell_width_f64)); const height_cells: u32 = @intFromFloat(@ceil(height_f64 / cell_height_f64)); From 2f814b37e84b3618792c4674805a2bf716e08cdf Mon Sep 17 00:00:00 2001 From: Bryan Lee <38807139+liby@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:44:17 +0800 Subject: [PATCH 2/2] Add unit tests for kitty image aspect ratio calculation --- src/terminal/kitty/graphics_storage.zig | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index a04a864c4..06769dc3c 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -1262,3 +1262,43 @@ test "storage: delete images by range 4" { try testing.expectEqual(@as(usize, 0), s.placements.count()); try testing.expectEqual(tracked, t.screen.pages.countTrackedPins()); } + +test "storage: aspect ratio calculation when only columns or rows specified" { + const testing = std.testing; + const alloc = testing.allocator; + + var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); + defer t.deinit(alloc); + t.width_px = 100; + t.height_px = 100; + + // Case 1: Only columns specified + { + const image = Image{ .id = 1, .width = 4, .height = 2 }; + var placement = ImageStorage.Placement{ + .location = .{ .virtual = {} }, + .columns = 6, + .rows = 0, + }; + + const grid_size = placement.gridSize(image, &t); + // 6 columns * (2/4) = 3 rows + try testing.expectEqual(@as(u32, 6), grid_size.cols); + try testing.expectEqual(@as(u32, 3), grid_size.rows); + } + + // Case 2: Only rows specified + { + const image = Image{ .id = 2, .width = 2, .height = 4 }; + var placement = ImageStorage.Placement{ + .location = .{ .virtual = {} }, + .columns = 0, + .rows = 6, + }; + + const grid_size = placement.gridSize(image, &t); + // 6 rows * (2/4) = 3 columns + try testing.expectEqual(@as(u32, 3), grid_size.cols); + try testing.expectEqual(@as(u32, 6), grid_size.rows); + } +}