images: kitty image protocol works
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
cc75fe6272
commit
f901dde2a0
11 changed files with 167 additions and 265 deletions
|
@ -43,8 +43,9 @@ pub fn main() !void {
|
|||
// _always_ be called, but is left to the application to decide when
|
||||
try vx.queryTerminal();
|
||||
|
||||
var img = try vaxis.Image.init(alloc, .{ .path = "vaxis.png" }, 1, .kitty);
|
||||
defer img.deinit();
|
||||
const img = try vx.loadImage(alloc, .{ .path = "vaxis.png" });
|
||||
|
||||
var n: usize = 0;
|
||||
|
||||
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
||||
// queue which can serve as the primary event queue for an application
|
||||
|
@ -56,6 +57,7 @@ pub fn main() !void {
|
|||
// enum has the fields for those events (ie "key_press", "winsize")
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
n += 1;
|
||||
if (key.matches('c', .{ .ctrl = true })) {
|
||||
break :outer;
|
||||
} else if (key.matches('l', .{ .ctrl = true })) {
|
||||
|
@ -79,7 +81,9 @@ pub fn main() !void {
|
|||
// the old and only updated cells will be drawn
|
||||
win.clear();
|
||||
|
||||
try img.draw(win);
|
||||
const child = win.initChild(n, n, .expand, .expand);
|
||||
|
||||
img.draw(child, false, 0);
|
||||
|
||||
// Render the screen
|
||||
try vx.render();
|
||||
|
|
61
src/Image.zig
Normal file
61
src/Image.zig
Normal file
|
@ -0,0 +1,61 @@
|
|||
const std = @import("std");
|
||||
const fmt = std.fmt;
|
||||
const math = std.math;
|
||||
const testing = std.testing;
|
||||
const base64 = std.base64.standard.Encoder;
|
||||
const zigimg = @import("zigimg");
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const Winsize = @import("Tty.zig").Winsize;
|
||||
|
||||
const log = std.log.scoped(.image);
|
||||
|
||||
const Image = @This();
|
||||
|
||||
const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
|
||||
|
||||
pub const Source = union(enum) {
|
||||
path: []const u8,
|
||||
mem: []const u8,
|
||||
};
|
||||
|
||||
pub const Placement = struct {
|
||||
img_id: u32,
|
||||
z_index: i32,
|
||||
scale: bool,
|
||||
};
|
||||
|
||||
pub const CellSize = struct {
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
};
|
||||
|
||||
/// unique identifier for this image. This will be managed by the screen.
|
||||
id: u32,
|
||||
|
||||
// width in pixels
|
||||
width: usize,
|
||||
// height in pixels
|
||||
height: usize,
|
||||
|
||||
pub fn draw(self: Image, win: Window, scale: bool, z_index: i32) void {
|
||||
const p = Placement{
|
||||
.img_id = self.id,
|
||||
.z_index = z_index,
|
||||
.scale = scale,
|
||||
};
|
||||
win.writeCell(0, 0, .{ .image = p });
|
||||
}
|
||||
|
||||
pub fn cellSize(self: Image, winsize: Winsize) !CellSize {
|
||||
// cell geometry
|
||||
const pix_per_col = try std.math.divCeil(usize, winsize.x_pixel, winsize.cols);
|
||||
const pix_per_row = try std.math.divCeil(usize, winsize.y_pixel, winsize.rows);
|
||||
|
||||
const cell_width = std.math.divCeil(usize, self.width, pix_per_col) catch 0;
|
||||
const cell_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0;
|
||||
return .{
|
||||
.rows = cell_height,
|
||||
.cols = cell_width,
|
||||
};
|
||||
}
|
|
@ -3,8 +3,6 @@ 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/image.zig").Image;
|
||||
const Placement = @import("Screen.zig").Placement;
|
||||
|
||||
const log = std.log.scoped(.internal_screen);
|
||||
|
||||
|
@ -37,13 +35,10 @@ cursor_vis: bool = false,
|
|||
|
||||
mouse_shape: Shape = .default,
|
||||
|
||||
images: std.ArrayList(Placement) = undefined,
|
||||
|
||||
/// sets each cell to the default cell
|
||||
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
||||
var screen = InternalScreen{
|
||||
.buf = try alloc.alloc(InternalCell, w * h),
|
||||
.images = std.ArrayList(Placement).init(alloc),
|
||||
};
|
||||
for (screen.buf, 0..) |_, i| {
|
||||
screen.buf[i] = .{
|
||||
|
@ -58,7 +53,6 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
|||
}
|
||||
|
||||
pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
|
||||
self.images.deinit();
|
||||
for (self.buf, 0..) |_, i| {
|
||||
self.buf[i].char.deinit();
|
||||
self.buf[i].uri.deinit();
|
||||
|
|
|
@ -3,30 +3,19 @@ const assert = std.debug.assert;
|
|||
|
||||
const Cell = @import("cell.zig").Cell;
|
||||
const Shape = @import("Mouse.zig").Shape;
|
||||
const Image = @import("image/image.zig").Image;
|
||||
const Image = @import("Image.zig");
|
||||
const Winsize = @import("Tty.zig").Winsize;
|
||||
|
||||
const log = std.log.scoped(.screen);
|
||||
|
||||
const Screen = @This();
|
||||
|
||||
pub const Placement = struct {
|
||||
img: Image,
|
||||
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,
|
||||
height: usize = 0,
|
||||
|
||||
width_pix: usize = 0,
|
||||
height_pix: usize = 0,
|
||||
|
||||
buf: []Cell = undefined,
|
||||
|
||||
cursor_row: usize = 0,
|
||||
|
@ -37,14 +26,15 @@ unicode: bool = false,
|
|||
|
||||
mouse_shape: Shape = .default,
|
||||
|
||||
images: std.ArrayList(Placement) = undefined,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
||||
pub fn init(alloc: std.mem.Allocator, winsize: Winsize) !Screen {
|
||||
const w = winsize.cols;
|
||||
const h = winsize.rows;
|
||||
var self = Screen{
|
||||
.buf = try alloc.alloc(Cell, w * h),
|
||||
.width = w,
|
||||
.height = h,
|
||||
.images = std.ArrayList(Placement).init(alloc),
|
||||
.width_pix = winsize.x_pixel,
|
||||
.height_pix = winsize.y_pixel,
|
||||
};
|
||||
for (self.buf, 0..) |_, i| {
|
||||
self.buf[i] = .{};
|
||||
|
@ -53,7 +43,6 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
|||
}
|
||||
pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.buf);
|
||||
self.images.deinit();
|
||||
}
|
||||
|
||||
/// writes a cell to a location. 0 indexed
|
||||
|
@ -70,19 +59,3 @@ pub fn writeCell(self: *Screen, col: usize, row: usize, cell: Cell) void {
|
|||
assert(i < self.buf.len);
|
||||
self.buf[i] = cell;
|
||||
}
|
||||
|
||||
pub fn writeImage(
|
||||
self: *Screen,
|
||||
col: usize,
|
||||
row: usize,
|
||||
img: Image,
|
||||
placement_id: u32,
|
||||
) !void {
|
||||
const p = Placement{
|
||||
.img = img,
|
||||
.placement_id = placement_id,
|
||||
.col = col,
|
||||
.row = row,
|
||||
};
|
||||
try self.images.append(p);
|
||||
}
|
||||
|
|
|
@ -69,27 +69,9 @@ 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 an image to the location in the window
|
||||
pub fn writeImage(
|
||||
self: Window,
|
||||
img: Image,
|
||||
placement_id: u32,
|
||||
) !void {
|
||||
if (self.height == 0 or self.width == 0) return;
|
||||
try self.screen.writeImage(self.x_off, self.y_off, img, placement_id);
|
||||
}
|
||||
|
||||
/// fills the window with the default cell
|
||||
pub fn clear(self: Window) void {
|
||||
self.fill(.{});
|
||||
// we clear any image with it's first cell within this window
|
||||
for (self.screen.images.items, 0..) |p, i| {
|
||||
if (p.col >= self.x_off and p.col < self.width and
|
||||
p.row >= self.y_off and p.row < self.height)
|
||||
{
|
||||
_ = self.screen.images.swapRemove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the width of the grapheme. This depends on the terminal capabilities
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
const Image = @import("Image.zig");
|
||||
|
||||
pub const Cell = struct {
|
||||
char: Character = .{},
|
||||
style: Style = .{},
|
||||
link: Hyperlink = .{},
|
||||
image: ?Image.Placement = null,
|
||||
};
|
||||
|
||||
pub const Character = struct {
|
||||
|
|
|
@ -89,3 +89,8 @@ pub const osc8_clear = "\x1b]8;;\x1b\\";
|
|||
pub const osc9_notify = "\x1b]9;{s}\x1b\\";
|
||||
pub const osc777_notify = "\x1b]777;notify;{s};{s}\x1b\\";
|
||||
pub const osc22_mouse_shape = "\x1b]22;{s}\x1b\\";
|
||||
|
||||
// Kitty graphics
|
||||
pub const kitty_graphics_clear = "\x1b_Ga=d\x1b\\";
|
||||
pub const kitty_graphics_place = "\x1b_Ga=p,i={d},z={d},C=1\x1b\\";
|
||||
pub const kitty_graphics_scale = "\x1b_Ga=p,i={d},z={d},c={d},r={d},C=1\x1b\\";
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
const std = @import("std");
|
||||
const fmt = std.fmt;
|
||||
const math = std.math;
|
||||
const testing = std.testing;
|
||||
const base64 = std.base64.standard.Encoder;
|
||||
const zigimg = @import("zigimg");
|
||||
|
||||
const Window = @import("../Window.zig");
|
||||
const Winsize = @import("../Tty.zig").Winsize;
|
||||
|
||||
const log = std.log.scoped(.kitty);
|
||||
|
||||
const Kitty = @This();
|
||||
|
||||
const max_chunk: usize = 4096;
|
||||
|
||||
const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
/// the decoded image
|
||||
img: zigimg.Image,
|
||||
|
||||
/// 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,
|
||||
|
||||
pub fn deinit(self: *const Kitty) void {
|
||||
var img = self.img;
|
||||
img.deinit();
|
||||
}
|
||||
|
||||
/// transmit encodes and transmits the image to the terminal
|
||||
pub fn transmit(self: Kitty, writer: anytype) !void {
|
||||
var alloc = self.alloc;
|
||||
const png_buf = try alloc.alloc(u8, self.img.imageByteSize());
|
||||
defer alloc.free(png_buf);
|
||||
const png = try self.img.writeToMemory(png_buf, .{ .png = .{} });
|
||||
const b64_buf = try alloc.alloc(u8, base64.calcSize(png.len));
|
||||
const encoded = base64.encode(b64_buf, png);
|
||||
defer alloc.free(b64_buf);
|
||||
|
||||
log.debug("transmitting kitty image: id={d}, len={d}", .{ self.id, encoded.len });
|
||||
|
||||
if (encoded.len < max_chunk) {
|
||||
try fmt.format(
|
||||
writer,
|
||||
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
||||
.{
|
||||
self.id,
|
||||
encoded,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
var n: usize = max_chunk;
|
||||
|
||||
try fmt.format(
|
||||
writer,
|
||||
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
||||
.{ self.id, encoded[0..n] },
|
||||
);
|
||||
while (n < encoded.len) : (n += max_chunk) {
|
||||
const end: usize = @min(n + max_chunk, encoded.len);
|
||||
const m: u2 = if (end == encoded.len) 0 else 1;
|
||||
try fmt.format(
|
||||
writer,
|
||||
"\x1b_Gm={d};{s}\x1b\\",
|
||||
.{
|
||||
m,
|
||||
encoded[n..end],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
const std = @import("std");
|
||||
const math = std.math;
|
||||
const testing = std.testing;
|
||||
const zigimg = @import("zigimg");
|
||||
|
||||
const Winsize = @import("../Tty.zig").Winsize;
|
||||
const Window = @import("../Window.zig");
|
||||
|
||||
const Kitty = @import("Kitty.zig");
|
||||
|
||||
const log = std.log.scoped(.image);
|
||||
|
||||
pub const Image = union(enum) {
|
||||
kitty: Kitty,
|
||||
|
||||
pub const Protocol = enum {
|
||||
kitty,
|
||||
// TODO: sixel, full block, half block, quad block
|
||||
};
|
||||
|
||||
pub const CellSize = struct {
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
};
|
||||
|
||||
pub const Source = union(enum) {
|
||||
path: []const u8,
|
||||
mem: []const u8,
|
||||
};
|
||||
|
||||
/// initialize a new image
|
||||
pub fn init(
|
||||
alloc: std.mem.Allocator,
|
||||
src: Source,
|
||||
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),
|
||||
};
|
||||
|
||||
switch (protocol) {
|
||||
.kitty => {
|
||||
return .{
|
||||
.kitty = Kitty{
|
||||
.alloc = alloc,
|
||||
.img = img,
|
||||
.id = id,
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: Image) void {
|
||||
switch (self) {
|
||||
inline else => |*case| case.deinit(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(self: Image, win: Window) !void {
|
||||
switch (self) {
|
||||
.kitty => {
|
||||
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 << 15 | row;
|
||||
try win.writeImage(self, pid);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transmit(self: Image, writer: anytype) !void {
|
||||
switch (self) {
|
||||
.kitty => |k| return k.transmit(writer),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getId(self: Image) ?u32 {
|
||||
switch (self) {
|
||||
.kitty => |k| return k.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cellSize(self: Image, winsize: Winsize) !CellSize {
|
||||
// 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, self.img.width, pix_per_col) catch 0;
|
||||
const cell_height = math.divCeil(usize, self.img.height, pix_per_row) catch 0;
|
||||
|
||||
return CellSize{
|
||||
.rows = cell_height,
|
||||
.cols = cell_width,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -10,7 +10,7 @@ pub const Winsize = @import("Tty.zig").Winsize;
|
|||
|
||||
pub const widgets = @import("widgets/main.zig");
|
||||
|
||||
pub const Image = @import("image/image.zig").Image;
|
||||
pub const Image = @import("Image.zig");
|
||||
|
||||
/// Initialize a Vaxis application.
|
||||
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
||||
|
|
104
src/vaxis.zig
104
src/vaxis.zig
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const atomic = std.atomic;
|
||||
const base64 = std.base64.standard.Encoder;
|
||||
|
||||
const Queue = @import("queue.zig").Queue;
|
||||
const ctlseqs = @import("ctlseqs.zig");
|
||||
|
@ -14,7 +15,8 @@ const Style = @import("cell.zig").Style;
|
|||
const Hyperlink = @import("cell.zig").Hyperlink;
|
||||
const gwidth = @import("gwidth.zig");
|
||||
const Shape = @import("Mouse.zig").Shape;
|
||||
const Placement = Screen.Placement;
|
||||
const Image = @import("Image.zig");
|
||||
const zigimg = @import("zigimg");
|
||||
|
||||
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
||||
/// be a tagged union which contains all of the events the application will
|
||||
|
@ -58,6 +60,8 @@ pub fn Vaxis(comptime T: type) type {
|
|||
alt_screen: bool = false,
|
||||
/// if we have entered kitty keyboard
|
||||
kitty_keyboard: bool = false,
|
||||
// TODO: should be false but we aren't querying yet
|
||||
kitty_graphics: bool = true,
|
||||
bracketed_paste: bool = false,
|
||||
mouse: bool = false,
|
||||
} = .{},
|
||||
|
@ -71,6 +75,9 @@ pub fn Vaxis(comptime T: type) type {
|
|||
/// futex times out
|
||||
query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
|
||||
|
||||
// images
|
||||
next_img_id: u32 = 1,
|
||||
|
||||
// statistics
|
||||
renders: usize = 0,
|
||||
render_dur: i128 = 0,
|
||||
|
@ -151,7 +158,7 @@ pub fn Vaxis(comptime T: type) type {
|
|||
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
||||
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
||||
self.screen.deinit(alloc);
|
||||
self.screen = try Screen.init(alloc, winsize.cols, winsize.rows);
|
||||
self.screen = try Screen.init(alloc, winsize);
|
||||
self.screen.unicode = self.caps.unicode;
|
||||
// try self.screen.int(alloc, winsize.cols, winsize.rows);
|
||||
// we only init our current screen. This has the effect of redrawing
|
||||
|
@ -243,7 +250,6 @@ pub fn Vaxis(comptime T: type) type {
|
|||
// the next render call will refresh the entire screen
|
||||
pub fn queueRefresh(self: *Self) void {
|
||||
self.refresh = true;
|
||||
self.screen_last.images.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
/// draws the screen to the terminal
|
||||
|
@ -280,26 +286,8 @@ pub fn Vaxis(comptime T: type) type {
|
|||
var cursor: Style = .{};
|
||||
var link: Hyperlink = .{};
|
||||
|
||||
// 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 (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
|
||||
try img.img.transmit(tty.buffered_writer.writer());
|
||||
}
|
||||
// Clear all images
|
||||
_ = try tty.write(ctlseqs.kitty_graphics_clear);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < self.screen.buf.len) {
|
||||
|
@ -326,7 +314,7 @@ pub fn Vaxis(comptime T: type) type {
|
|||
// If cell is the same as our last frame, we don't need to do
|
||||
// anything
|
||||
const last = self.screen_last.buf[i];
|
||||
if (!self.refresh and last.eql(cell) and !last.skipped) {
|
||||
if (!self.refresh and last.eql(cell) and !last.skipped and cell.image == null) {
|
||||
reposition = true;
|
||||
// Close any osc8 sequence we might be in before
|
||||
// repositioning
|
||||
|
@ -348,6 +336,10 @@ pub fn Vaxis(comptime T: type) type {
|
|||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
||||
}
|
||||
|
||||
if (cell.image) |img| {
|
||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.kitty_graphics_place, .{ img.img_id, img.z_index });
|
||||
}
|
||||
|
||||
// something is different, so let's loop throuugh everything and
|
||||
// find out what
|
||||
|
||||
|
@ -581,6 +573,70 @@ pub fn Vaxis(comptime T: type) type {
|
|||
try tty.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loadImage(
|
||||
self: *Self,
|
||||
alloc: std.mem.Allocator,
|
||||
src: Image.Source,
|
||||
) !Image {
|
||||
var tty = self.tty orelse return error.NoTTY;
|
||||
defer self.next_img_id += 1;
|
||||
|
||||
const writer = tty.buffered_writer.writer();
|
||||
|
||||
var img = switch (src) {
|
||||
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
||||
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
||||
};
|
||||
defer img.deinit();
|
||||
const png_buf = try alloc.alloc(u8, img.imageByteSize());
|
||||
defer alloc.free(png_buf);
|
||||
const png = try img.writeToMemory(png_buf, .{ .png = .{} });
|
||||
const b64_buf = try alloc.alloc(u8, base64.calcSize(png.len));
|
||||
const encoded = base64.encode(b64_buf, png);
|
||||
defer alloc.free(b64_buf);
|
||||
|
||||
const id = self.next_img_id;
|
||||
|
||||
log.debug("transmitting kitty image: id={d}, len={d}", .{ id, encoded.len });
|
||||
|
||||
if (encoded.len < 4096) {
|
||||
try std.fmt.format(
|
||||
writer,
|
||||
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
||||
.{
|
||||
id,
|
||||
encoded,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
var n: usize = 4096;
|
||||
|
||||
try std.fmt.format(
|
||||
writer,
|
||||
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
||||
.{ id, encoded[0..n] },
|
||||
);
|
||||
while (n < encoded.len) : (n += 4096) {
|
||||
const end: usize = @min(n + 4096, encoded.len);
|
||||
const m: u2 = if (end == encoded.len) 0 else 1;
|
||||
try std.fmt.format(
|
||||
writer,
|
||||
"\x1b_Gm={d};{s}\x1b\\",
|
||||
.{
|
||||
m,
|
||||
encoded[n..end],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
try tty.buffered_writer.flush();
|
||||
return Image{
|
||||
.id = id,
|
||||
.width = img.width,
|
||||
.height = img.height,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue