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:
parent
edb1fae2ab
commit
626a9101bd
8 changed files with 77 additions and 158 deletions
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,35 +8,60 @@ const Window = @import("../Window.zig");
|
||||||
|
|
||||||
const Kitty = @import("Kitty.zig");
|
const Kitty = @import("Kitty.zig");
|
||||||
|
|
||||||
|
pub const Protocol = enum {
|
||||||
|
kitty,
|
||||||
|
// TODO: sixel, full block, half block, quad block
|
||||||
|
};
|
||||||
|
|
||||||
pub const Image = union(enum) {
|
pub const Image = union(enum) {
|
||||||
kitty: Kitty,
|
kitty: Kitty,
|
||||||
|
|
||||||
pub const Protocol = enum {
|
|
||||||
kitty,
|
|
||||||
// TODO: sixel, full block, half block, quad block
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue