From 359458b96a208683b64806710dd34d82e1ab7408 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 10:52:10 -0700 Subject: [PATCH] terminal/kitty: switch to new placement math --- src/renderer/Metal.zig | 223 ++---------------------- src/terminal/kitty/graphics_render.zig | 28 +++ src/terminal/kitty/graphics_unicode.zig | 11 +- 3 files changed, 54 insertions(+), 208 deletions(-) create mode 100644 src/terminal/kitty/graphics_render.zig diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e22faaf4c..06d5346b0 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1684,226 +1684,37 @@ fn prepKittyVirtualPlacement( return; }; - if (true) { - const rp = p.renderPlacement( - &t.screen.kitty_images, - &image, - self.grid_metrics.cell_width, - self.grid_metrics.cell_height, - ) catch |err| { - log.warn("error rendering virtual placement err={}", .{err}); - return; - }; - - // Send our image to the GPU - try self.prepKittyImage(&image); - - const viewport: terminal.point.Point = t.screen.pages.pointFromPin( - .viewport, - rp.top_left, - ) orelse @panic("TODO: unreachable?"); - - try self.image_placements.append(self.alloc, .{ - .image_id = image.id, - .x = @intCast(rp.top_left.x), - .y = @intCast(viewport.viewport.y), - .z = -1, - .width = rp.dest_width, - .height = rp.dest_height, - .cell_offset_x = rp.offset_x, - .cell_offset_y = rp.offset_y, - .source_x = rp.source_x, - .source_y = rp.source_y, - .source_width = rp.source_width, - .source_height = rp.source_height, - }); - - return; - } - - // Get the placement. If an ID is specified we look for the exact one. - // If no ID, then we find the first virtual placement for this image. - const placement = if (p.placement_id > 0) storage.placements.get(.{ - .image_id = p.image_id, - .placement_id = .{ .tag = .external, .id = p.placement_id }, - }) orelse { - log.warn( - "missing placement for virtual placement, ignoring image_id={} placement_id={}", - .{ p.image_id, p.placement_id }, - ); - return; - } else placement: { - var it = storage.placements.iterator(); - while (it.next()) |entry| { - if (entry.key_ptr.image_id == p.image_id and - entry.value_ptr.location == .virtual) - { - break :placement entry.value_ptr.*; - } - } - - log.warn( - "missing placement for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); + const rp = p.renderPlacement( + &t.screen.kitty_images, + &image, + self.grid_metrics.cell_width, + self.grid_metrics.cell_height, + ) catch |err| { + log.warn("error rendering virtual placement err={}", .{err}); return; }; - // Calculate the grid size for the placement. For virtual placements, - // we use the requested row/cols. If either isn't specified, we choose - // the best size based on the image size to fit the entire image in its - // original size. - // - // This part of the code does NOT do preserve any aspect ratios. Its - // dumbly fitting the image into the grid size -- possibly user specified. - const img_grid: renderer.GridSize = grid: { - // Use requested rows/columns if specified - var rows = placement.rows; - var columns = placement.columns; - - // For unspecified rows/columns, calculate based on the image size. - if (rows == 0) { - const cell_height = self.grid_metrics.cell_height; - rows = (image.height + cell_height - 1) / cell_height; - } - if (columns == 0) { - const cell_width = self.grid_metrics.cell_width; - columns = (image.width + cell_width - 1) / cell_width; - } - - break :grid .{ - .rows = std.math.cast(terminal.size.CellCountInt, rows) orelse { - log.warn( - "placement rows too large for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); - return; - }, - .columns = std.math.cast(terminal.size.CellCountInt, columns) orelse { - log.warn( - "placement columns too large for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); - return; - }, - }; - }; - - // Next we have to fit the source image into the grid size while preserving - // aspect ratio. We will center the image horizontally/vertically if - // necessary. - - // The offsets are the pixel offsets from the top-left of the top-left - // grid cell in order to center the image as best as possible. - var x_offset: f64 = 0; - var y_offset: f64 = 0; - - // The scale factors are the scaling factors applied to the original - // image size in order to fit it into our placement grid size. - var x_scale: f64 = 0; - var y_scale: f64 = 0; - const rows_px: f64 = @floatFromInt(img_grid.rows * self.grid_metrics.cell_height); - const cols_px: f64 = @floatFromInt(img_grid.columns * self.grid_metrics.cell_width); - const img_width_f64: f64 = @floatFromInt(image.width); - const img_height_f64: f64 = @floatFromInt(image.height); - if (img_width_f64 * rows_px > img_height_f64 * cols_px) { - // Image is wider than the grid, fit width and center height - x_scale = cols_px / @max(img_width_f64, 1); - y_scale = x_scale; - y_offset = (rows_px - img_height_f64 * y_scale) / 2; - } else { - // Image is taller than the grid, fit height and center width - y_scale = rows_px / @max(img_height_f64, 1); - x_scale = y_scale; - x_offset = (cols_px - img_width_f64 * x_scale) / 2; - } - log.warn("x_offset={}, y_offset={}, x_scale={}, y_scale={}", .{ - x_offset, y_offset, x_scale, y_scale, - }); - - // At this point, we have the following information: - // - image.width/height - The original image width and height. - // - img_grid.rows/columns - The requested grid size for the placement. - // - offset/scale - The offset and scale to fit the image into the - // placement grid. - // - // For our run requested coordinates and size we now need to map - // the original image down into our grid cells honoring the offsets - // calculated for the best fit. - - const img_x_offset: f64 = x_offset / x_scale; - const img_y_offset: f64 = y_offset / y_scale; - const img_width_padded: f64 = img_width_f64 + (img_x_offset * 2); - const img_height_padded: f64 = img_height_f64 + (img_y_offset * 2); - log.warn("padded_width={}, padded_height={} original_width={}, original_height={}", .{ - img_width_padded, img_height_padded, img_width_f64, img_height_f64, - }); - - const source_width_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.width)) / @as(f64, @floatFromInt(img_grid.columns))); - var source_height_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.height)) / @as(f64, @floatFromInt(img_grid.rows))); - const source_x_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.col)) / @as(f64, @floatFromInt(img_grid.columns))); - var source_y_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.row)) / @as(f64, @floatFromInt(img_grid.rows))); - - const p_x_offset_f64: f64 = 0; - var p_y_offset_f64: f64 = 0; - const dst_width_f64: f64 = @floatFromInt(p.width * self.grid_metrics.cell_width); - var dst_height_f64: f64 = @floatFromInt(p.height * self.grid_metrics.cell_height); - - // If our y is in our top offset area, we need to adjust the source to - // be shorter, and offset it into the cell. - if (source_y_f64 < img_y_offset) { - const offset: f64 = img_y_offset - source_y_f64; - source_height_f64 -= offset; - p_y_offset_f64 = offset; - dst_height_f64 -= offset * y_scale; - source_y_f64 = 0; - } - - // if our y is in our bottom offset area, we need to shorten the - // source to fit in the cell. - if (source_y_f64 + source_height_f64 > img_height_padded - img_y_offset) { - source_y_f64 -= img_y_offset; - source_height_f64 = img_height_padded - img_y_offset - source_y_f64; - source_height_f64 -= img_y_offset; - dst_height_f64 = source_height_f64 * y_scale; - } - - const source_width: u32 = @intFromFloat(@round(source_width_f64)); - const source_height: u32 = @intFromFloat(@round(source_height_f64)); - const source_x: u32 = @intFromFloat(@round(source_x_f64)); - const source_y: u32 = @intFromFloat(@round(source_y_f64)); - const p_x_offset: u32 = @intFromFloat(@round(p_x_offset_f64 * x_scale)); - const p_y_offset: u32 = @intFromFloat(@round(p_y_offset_f64 * y_scale)); - const dest_width: u32 = @intFromFloat(@round(dst_width_f64)); - const dest_height: u32 = @intFromFloat(@round(dst_height_f64)); - - log.warn("source_x={}, source_y={}, source_width={}, source_height={}", .{ - source_x, source_y, source_width, source_height, - }); - log.warn("p_x_offset={}, p_y_offset={}", .{ p_x_offset, p_y_offset }); - log.warn("dest_width={}, dest_height={}", .{ dest_width, dest_height }); - // Send our image to the GPU try self.prepKittyImage(&image); const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, - p.pin, + rp.top_left, ) orelse @panic("TODO: unreachable?"); try self.image_placements.append(self.alloc, .{ .image_id = image.id, - .x = @intCast(p.pin.x), + .x = @intCast(rp.top_left.x), .y = @intCast(viewport.viewport.y), .z = -1, - .width = dest_width, - .height = dest_height, - .cell_offset_x = p_x_offset, - .cell_offset_y = p_y_offset, - .source_x = source_x, - .source_y = source_y, - .source_width = source_width, - .source_height = source_height, + .width = rp.dest_width, + .height = rp.dest_height, + .cell_offset_x = rp.offset_x, + .cell_offset_y = rp.offset_y, + .source_x = rp.source_x, + .source_y = rp.source_y, + .source_width = rp.source_width, + .source_height = rp.source_height, }); } diff --git a/src/terminal/kitty/graphics_render.zig b/src/terminal/kitty/graphics_render.zig new file mode 100644 index 000000000..af888582f --- /dev/null +++ b/src/terminal/kitty/graphics_render.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; +const terminal = @import("../main.zig"); + +/// A render placement is a way to position a Kitty graphics image onto +/// the screen. It is broken down into the fields that make it easier to +/// position the image using a renderer. +pub const Placement = struct { + /// The top-left corner of the image in grid coordinates. + top_left: terminal.Pin, + + /// The offset in pixels from the top-left corner of the grid cell. + offset_x: u32 = 0, + offset_y: u32 = 0, + + /// The source rectangle of the image to render. This doesn't have to + /// match the size the destination size and the renderer is expected + /// to scale the image to fit the destination size. + source_x: u32 = 0, + source_y: u32 = 0, + source_width: u32 = 0, + source_height: u32 = 0, + + /// The final width/height of the image in pixels. + dest_width: u32 = 0, + dest_height: u32 = 0, +}; diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 23371d180..b6ed5b55a 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -274,12 +274,19 @@ pub const Placement = struct { } break :dest .{ - .x_offset = x_offset, - .y_offset = y_offset, + .x_offset = x_offset * p_scale.x_scale, + .y_offset = y_offset * p_scale.y_scale, .width = width, .height = height, }; }; + // log.warn("p_grid={} p_scale={} img_scaled={} img_scale_source={} p_dest={}", .{ + // p_grid, + // p_scale, + // img_scaled, + // img_scale_source, + // p_dest, + // }); return .{ .top_left = self.pin,