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 <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-30 07:14:34 -06:00
parent edb1fae2ab
commit 626a9101bd
8 changed files with 77 additions and 158 deletions

View file

@ -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);
}

View file

@ -3,7 +3,7 @@ const assert = std.debug.assert;
const Style = @import("cell.zig").Style; const Style = @import("cell.zig").Style;
const Cell = @import("cell.zig").Cell; const Cell = @import("cell.zig").Cell;
const Shape = @import("Mouse.zig").Shape; 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 Placement = @import("Screen.zig").Placement;
const log = std.log.scoped(.internal_screen); const log = std.log.scoped(.internal_screen);

View file

@ -3,7 +3,7 @@ const assert = std.debug.assert;
const Cell = @import("cell.zig").Cell; const Cell = @import("cell.zig").Cell;
const Shape = @import("Mouse.zig").Shape; const Shape = @import("Mouse.zig").Shape;
const Image = @import("Image.zig"); const Image = @import("image/image.zig").Image;
const log = std.log.scoped(.screen); const log = std.log.scoped(.screen);
@ -14,6 +14,14 @@ pub const Placement = struct {
placement_id: u32, placement_id: u32,
col: usize, col: usize,
row: 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, width: usize = 0,

View file

@ -2,7 +2,7 @@ const std = @import("std");
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const Cell = @import("cell.zig").Cell; const Cell = @import("cell.zig").Cell;
const Image = @import("Image.zig"); const Image = @import("image/image.zig").Image;
const gw = @import("gwidth.zig"); const gw = @import("gwidth.zig");
const log = std.log.scoped(.window); 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); 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( pub fn writeImage(
self: Window, self: Window,
col: usize,
row: usize,
img: *Image, img: *Image,
placement_id: u32, placement_id: u32,
) !void { ) !void {
if (self.height == 0 or self.width == 0) return; if (self.height == 0 or self.width == 0) return;
if (self.height <= row or self.width <= col) return; self.screen.writeImage(self.x_off, self.y_off, img, placement_id);
self.screen.writeImage(col, row, img, placement_id);
} }
/// fills the window with the default cell /// fills the window with the default cell

View file

@ -12,63 +12,25 @@ const Kitty = @This();
/// the decoded image /// the decoded image
img: zigimg.Image, img: zigimg.Image,
/// unique identifier for this image /// unique identifier for this image. This will be managed by the screen. The ID
id: u32, /// is only null for images which have not been transmitted to the screen
id: ?u32 = null,
/// width of the image, in cells /// width of the image, in cells
cell_width: usize, cell_width: usize,
/// height of the image, in cells /// height of the image, in cells
cell_height: usize, cell_height: usize,
/// initialize a new image pub fn deinit(self: *Kitty) void {
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 {
self.img.deinit(); self.img.deinit();
} }
pub fn draw(self: *Image, win: Window, placement_id: u32) !void { pub fn draw(self: *Kitty, win: Window) !void {
try win.writeImage(win.x_off, win.y_off, self, placement_id); 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
test "image" { // bits as the row. This means we can only place this image one time at
const alloc = testing.allocator; // the same location - which is completely sane
var img = try init( const pid: u32 = col << 16 | row;
alloc, try win.writeImage(win.x_off, win.y_off, self, pid);
.{
.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);
} }

View file

@ -8,35 +8,60 @@ const Window = @import("../Window.zig");
const Kitty = @import("Kitty.zig"); const Kitty = @import("Kitty.zig");
pub const Image = union(enum) {
kitty: Kitty,
pub const Protocol = enum { pub const Protocol = enum {
kitty, kitty,
// TODO: sixel, full block, half block, quad block // TODO: sixel, full block, half block, quad block
}; };
pub const Image = union(enum) {
kitty: Kitty,
/// initialize a new image /// initialize a new image
pub fn init( pub fn init(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
winsize: Winsize, winsize: Winsize,
src: []const u8, src: []const u8,
id: u32,
protocol: Protocol, protocol: Protocol,
) !Image { ) !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) { switch (protocol) {
.kitty => { .kitty => {
const img = try Kitty.init(alloc, winsize, src, id); return .{
return .{ .kitty = img }; .kitty = Kitty{
.img = img,
.cell_width = cell_width,
.cell_height = cell_height,
},
};
}, },
} }
} }
pub fn deinit(self: *Image) void { pub fn deinit(self: Image) void {
self.img.deinit(); switch (self) {
inline else => |case| case.deinit(),
}
} }
pub fn draw(self: *Image, win: Window, placement_id: u32) !void { pub fn draw(self: Image, win: Window) !void {
try win.writeImage(win.x_off, win.y_off, self, placement_id); switch (self) {
inline else => |case| case.draw(win),
}
}
pub fn getId(self: Image) ?u32 {
switch (self) {
.kitty => |k| return k.id,
}
} }
}; };

View file

@ -17,7 +17,6 @@ pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
test { test {
_ = @import("GraphemeCache.zig"); _ = @import("GraphemeCache.zig");
_ = @import("Image.zig");
_ = @import("Key.zig"); _ = @import("Key.zig");
_ = @import("Mouse.zig"); _ = @import("Mouse.zig");
_ = @import("Options.zig"); _ = @import("Options.zig");
@ -29,6 +28,7 @@ test {
_ = @import("ctlseqs.zig"); _ = @import("ctlseqs.zig");
_ = @import("event.zig"); _ = @import("event.zig");
_ = @import("gwidth.zig"); _ = @import("gwidth.zig");
_ = @import("image/image.zig");
_ = @import("queue.zig"); _ = @import("queue.zig");
_ = @import("vaxis.zig"); _ = @import("vaxis.zig");
} }

View file

@ -280,13 +280,24 @@ pub fn Vaxis(comptime T: type) type {
var cursor: Style = .{}; var cursor: Style = .{};
var link: Hyperlink = .{}; var link: Hyperlink = .{};
// delete remove images from the screen by looping through the // remove images from the screen by looping through the last state
// current state and comparing to the next state // and comparing to the next state
for (self.screen_last.images.items) |last_img| { for (self.screen_last.images.items) |last_img| {
const keep: bool = for (self.screen.images.items) |next_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; } else false;
if (keep) continue; 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; var i: usize = 0;