From 626a9101bd61d4c5f06bf1621002b2c66d7642b9 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 30 Jan 2024 07:14:34 -0600 Subject: [PATCH] images: move to subfolder, use union enum for implementations There will only ever be a handful of image implementations. Let's just use a union enum for handling them. Signed-off-by: Tim Culverhouse --- src/Image.zig | 84 ------------------------------------------ src/InternalScreen.zig | 2 +- src/Screen.zig | 10 ++++- src/Window.zig | 9 ++--- src/image/Kitty.zig | 62 ++++++------------------------- src/image/image.zig | 49 ++++++++++++++++++------ src/main.zig | 2 +- src/vaxis.zig | 17 +++++++-- 8 files changed, 77 insertions(+), 158 deletions(-) delete mode 100644 src/Image.zig diff --git a/src/Image.zig b/src/Image.zig deleted file mode 100644 index 0e34875..0000000 --- a/src/Image.zig +++ /dev/null @@ -1,84 +0,0 @@ -const std = @import("std"); -const math = std.math; -const testing = std.testing; -const zigimg = @import("zigimg"); - -const Window = @import("Window.zig"); -const Winsize = @import("Tty.zig").Winsize; - -const Image = @This(); - -pub const Source = union(enum) { - /// loads an image from a path. path can be relative to cwd, or absolute - path: []const u8, - /// loads an image from raw bytes - mem: []const u8, -}; - -pub const Protocol = enum { - kitty, - // TODO: sixel, full block, half block, quad block -}; - -/// the decoded image -img: zigimg.Image, - -/// unique identifier for this image -id: u32, - -/// width of the image, in cells -cell_width: usize, -/// height of the image, in cells -cell_height: usize, - -/// initialize a new image -pub fn init( - alloc: std.mem.Allocator, - winsize: Winsize, - src: Source, - id: u32, -) !Image { - const img = switch (src) { - .path => |path| try zigimg.Image.fromFilePath(alloc, path), - .mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes), - }; - // cell geometry - const pix_per_col = try math.divCeil(usize, winsize.x_pixel, winsize.cols); - const pix_per_row = try math.divCeil(usize, winsize.y_pixel, winsize.rows); - - const cell_width = math.divCeil(usize, img.width, pix_per_col) catch 0; - const cell_height = math.divCeil(usize, img.height, pix_per_row) catch 0; - - return Image{ - .img = img, - .cell_width = cell_width, - .cell_height = cell_height, - .id = id, - }; -} - -pub fn deinit(self: *Image) void { - self.img.deinit(); -} - -pub fn draw(self: *Image, win: Window, placement_id: u32) !void { - try win.writeImage(win.x_off, win.y_off, self, placement_id); -} - -test "image" { - const alloc = testing.allocator; - var img = try init( - alloc, - .{ - .rows = 1, - .cols = 1, - .x_pixel = 1, - .y_pixel = 1, - }, - .{ .path = "vaxis.png" }, - 1, - ); - defer img.deinit(); - try testing.expectEqual(200, img.cell_width); - try testing.expectEqual(197, img.cell_height); -} diff --git a/src/InternalScreen.zig b/src/InternalScreen.zig index abd3d85..6566f56 100644 --- a/src/InternalScreen.zig +++ b/src/InternalScreen.zig @@ -3,7 +3,7 @@ const assert = std.debug.assert; const Style = @import("cell.zig").Style; const Cell = @import("cell.zig").Cell; const Shape = @import("Mouse.zig").Shape; -const Image = @import("Image.zig").Placement; +const Image = @import("image/image.zig").Image; const Placement = @import("Screen.zig").Placement; const log = std.log.scoped(.internal_screen); diff --git a/src/Screen.zig b/src/Screen.zig index 4be5884..d8c2511 100644 --- a/src/Screen.zig +++ b/src/Screen.zig @@ -3,7 +3,7 @@ const assert = std.debug.assert; const Cell = @import("cell.zig").Cell; const Shape = @import("Mouse.zig").Shape; -const Image = @import("Image.zig"); +const Image = @import("image/image.zig").Image; const log = std.log.scoped(.screen); @@ -14,6 +14,14 @@ pub const Placement = struct { placement_id: u32, col: usize, row: usize, + + /// two placements are considered equal if their image id and their + /// placement id are equal + pub fn eql(self: Placement, tgt: Placement) bool { + if (self.img.getId() != tgt.img.getId()) return false; + if (self.placement_id != tgt.placement_id) return false; + return true; + } }; width: usize = 0, diff --git a/src/Window.zig b/src/Window.zig index 425ed03..b0dcaf3 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Screen = @import("Screen.zig"); const Cell = @import("cell.zig").Cell; -const Image = @import("Image.zig"); +const Image = @import("image/image.zig").Image; const gw = @import("gwidth.zig"); const log = std.log.scoped(.window); @@ -69,17 +69,14 @@ pub fn writeCell(self: Window, col: usize, row: usize, cell: Cell) void { self.screen.writeCell(col + self.x_off, row + self.y_off, cell); } -/// writes a cell to the location in the window +/// writes an image to the location in the window pub fn writeImage( self: Window, - col: usize, - row: usize, img: *Image, placement_id: u32, ) !void { if (self.height == 0 or self.width == 0) return; - if (self.height <= row or self.width <= col) return; - self.screen.writeImage(col, row, img, placement_id); + self.screen.writeImage(self.x_off, self.y_off, img, placement_id); } /// fills the window with the default cell diff --git a/src/image/Kitty.zig b/src/image/Kitty.zig index 7bf15c8..6e10a75 100644 --- a/src/image/Kitty.zig +++ b/src/image/Kitty.zig @@ -12,63 +12,25 @@ const Kitty = @This(); /// the decoded image img: zigimg.Image, -/// unique identifier for this image -id: u32, +/// unique identifier for this image. This will be managed by the screen. The ID +/// is only null for images which have not been transmitted to the screen +id: ?u32 = null, /// width of the image, in cells cell_width: usize, /// height of the image, in cells cell_height: usize, -/// initialize a new image -pub fn init( - alloc: std.mem.Allocator, - winsize: Winsize, - src: []const u8, - id: u32, -) !Kitty { - const img = switch (src) { - .path => |path| try zigimg.Image.fromFilePath(alloc, path), - .mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes), - }; - // cell geometry - const pix_per_col = try math.divCeil(usize, winsize.x_pixel, winsize.cols); - const pix_per_row = try math.divCeil(usize, winsize.y_pixel, winsize.rows); - - const cell_width = math.divCeil(usize, img.width, pix_per_col) catch 0; - const cell_height = math.divCeil(usize, img.height, pix_per_row) catch 0; - - return Image{ - .img = img, - .cell_width = cell_width, - .cell_height = cell_height, - .id = id, - }; -} - -pub fn deinit(self: *Image) void { +pub fn deinit(self: *Kitty) void { self.img.deinit(); } -pub fn draw(self: *Image, win: Window, placement_id: u32) !void { - try win.writeImage(win.x_off, win.y_off, self, placement_id); -} - -test "image" { - const alloc = testing.allocator; - var img = try init( - alloc, - .{ - .rows = 1, - .cols = 1, - .x_pixel = 1, - .y_pixel = 1, - }, - .{ .path = "vaxis.png" }, - 0, - .kitty, - ); - defer img.deinit(); - try testing.expectEqual(200, img.cell_width); - try testing.expectEqual(197, img.cell_height); +pub fn draw(self: *Kitty, win: Window) !void { + const row: u16 = @truncate(win.y_off); + const col: u16 = @truncate(win.x_off); + // the placement id has the high 16 bits as the column and the low 16 + // bits as the row. This means we can only place this image one time at + // the same location - which is completely sane + const pid: u32 = col << 16 | row; + try win.writeImage(win.x_off, win.y_off, self, pid); } diff --git a/src/image/image.zig b/src/image/image.zig index 8c5c8db..24ba4e5 100644 --- a/src/image/image.zig +++ b/src/image/image.zig @@ -8,35 +8,60 @@ const Window = @import("../Window.zig"); const Kitty = @import("Kitty.zig"); +pub const Protocol = enum { + kitty, + // TODO: sixel, full block, half block, quad block +}; + pub const Image = union(enum) { kitty: Kitty, - pub const Protocol = enum { - kitty, - // TODO: sixel, full block, half block, quad block - }; - /// initialize a new image pub fn init( alloc: std.mem.Allocator, winsize: Winsize, src: []const u8, - id: u32, protocol: Protocol, ) !Image { + const img = switch (src) { + .path => |path| try zigimg.Image.fromFilePath(alloc, path), + .mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes), + }; + // cell geometry + const pix_per_col = try math.divCeil(usize, winsize.x_pixel, winsize.cols); + const pix_per_row = try math.divCeil(usize, winsize.y_pixel, winsize.rows); + + const cell_width = math.divCeil(usize, img.width, pix_per_col) catch 0; + const cell_height = math.divCeil(usize, img.height, pix_per_row) catch 0; + switch (protocol) { .kitty => { - const img = try Kitty.init(alloc, winsize, src, id); - return .{ .kitty = img }; + return .{ + .kitty = Kitty{ + .img = img, + .cell_width = cell_width, + .cell_height = cell_height, + }, + }; }, } } - pub fn deinit(self: *Image) void { - self.img.deinit(); + pub fn deinit(self: Image) void { + switch (self) { + inline else => |case| case.deinit(), + } } - pub fn draw(self: *Image, win: Window, placement_id: u32) !void { - try win.writeImage(win.x_off, win.y_off, self, placement_id); + pub fn draw(self: Image, win: Window) !void { + switch (self) { + inline else => |case| case.draw(win), + } + } + + pub fn getId(self: Image) ?u32 { + switch (self) { + .kitty => |k| return k.id, + } } }; diff --git a/src/main.zig b/src/main.zig index 6fd2407..2367f88 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,7 +17,6 @@ pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) { test { _ = @import("GraphemeCache.zig"); - _ = @import("Image.zig"); _ = @import("Key.zig"); _ = @import("Mouse.zig"); _ = @import("Options.zig"); @@ -29,6 +28,7 @@ test { _ = @import("ctlseqs.zig"); _ = @import("event.zig"); _ = @import("gwidth.zig"); + _ = @import("image/image.zig"); _ = @import("queue.zig"); _ = @import("vaxis.zig"); } diff --git a/src/vaxis.zig b/src/vaxis.zig index 94d8128..f28383e 100644 --- a/src/vaxis.zig +++ b/src/vaxis.zig @@ -280,13 +280,24 @@ pub fn Vaxis(comptime T: type) type { var cursor: Style = .{}; var link: Hyperlink = .{}; - // delete remove images from the screen by looping through the - // current state and comparing to the next state + // remove images from the screen by looping through the last state + // and comparing to the next state for (self.screen_last.images.items) |last_img| { const keep: bool = for (self.screen.images.items) |next_img| { - if (std.meta.eql(last_img, next_img)) break true; + if (last_img.eql(next_img)) break true; } else false; if (keep) continue; + // TODO: remove image placements + } + + // add new images. Could slightly optimize by knowing which images + // we need to keep from the remove loop + for (self.screen.images.items) |img| { + const transmit: bool = for (self.screen_last.images.items) |last_img| { + if (last_img.eql(img)) break false; + } else true; + if (!transmit) continue; + // TODO: transmit the new image to the screen } var i: usize = 0;