Skip to content

Commit

Permalink
image: use image options struct, add more functionality
Browse files Browse the repository at this point in the history
Use an options struct for controlling image output. Add more
functionality from the core kitty image protocol
  • Loading branch information
rockorager committed May 21, 2024
1 parent fd6c380 commit 1dc90bd
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 27 deletions.
13 changes: 9 additions & 4 deletions examples/image.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub fn main() !void {

var n: usize = 0;

var clip_y: usize = 0;

while (true) {
const event = loop.nextEvent();
switch (event) {
Expand All @@ -47,7 +49,10 @@ pub fn main() !void {
return;
} else if (key.matches('l', .{ .ctrl = true })) {
vx.queueRefresh();
}
} else if (key.matches('j', .{}))
clip_y += 1
else if (key.matches('k', .{}))
clip_y -|= 1;
},
.winsize => |ws| try vx.resize(alloc, ws),
}
Expand All @@ -59,9 +64,9 @@ pub fn main() !void {
const img = imgs[n];
const dims = try img.cellSize(win);
const center = vaxis.widgets.alignment.center(win, dims.cols, dims.rows);
const scale = false;
const z_index = 0;
img.draw(center, scale, z_index);
try img.draw(center, .{ .scale = .contain, .clip_region = .{
.y = clip_y,
} });

try vx.render();
}
Expand Down
130 changes: 119 additions & 11 deletions src/Image.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,146 @@ pub const Source = union(enum) {

pub const Placement = struct {
img_id: u32,
z_index: i32,
size: ?CellSize = null,
options: Image.DrawOptions,
};

pub const CellSize = struct {
rows: usize,
cols: usize,
};

pub const DrawOptions = struct {
/// an offset into the top left cell, in pixels, with where to place the
/// origin of the image. These must be less than the pixel size of a single
/// cell
pixel_offset: ?struct {
x: usize,
y: usize,
} = null,
/// the vertical stacking order
/// < 0: Drawn beneath text
/// < -1_073_741_824: Drawn beneath "default" background cells
z_index: ?i32 = null,
/// A clip region of the source image to draw.
clip_region: ?struct {
x: ?usize = null,
y: ?usize = null,
width: ?usize = null,
height: ?usize = null,
} = null,
/// Scaling to apply to the Image
scale: enum {
/// no scaling applied. the image may extend beyond the window
none,
/// Stretch / shrink the image to fill the window
fill,
/// Scale the image to fit the window, maintaining aspect ratio
fit,
/// Scale the image to fit the window, only if needed.
contain,
} = .none,
/// the size to render the image. Generally you will not need to use this
/// field, and should prefer to use scale. `draw` will fill in this field with
/// the correct values if a scale method is applied.
size: ?struct {
rows: ?usize = null,
cols: ?usize = null,
} = null,
};

/// unique identifier for this image. This will be managed by the screen.
id: u32,

// width in pixels
/// width in pixels
width: usize,
// height in pixels
/// 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,
.size = sz: {
if (!scale) break :sz null;
break :sz CellSize{
pub fn draw(self: Image, win: Window, opts: DrawOptions) !void {
var p_opts = opts;
switch (opts.scale) {
.none => {},
.fill => {
p_opts.size = .{
.rows = win.height,
.cols = win.width,
};
},
.fit,
.contain,
=> contain: {
// cell geometry
const x_pix = win.screen.width_pix;
const y_pix = win.screen.height_pix;
const w = win.screen.width;
const h = win.screen.height;

const pix_per_col = try std.math.divCeil(usize, x_pix, w);
const pix_per_row = try std.math.divCeil(usize, y_pix, h);

const win_width_pix = pix_per_col * win.width;
const win_height_pix = pix_per_row * win.height;

const fit_x: bool = if (win_width_pix >= self.width) true else false;
const fit_y: bool = if (win_height_pix >= self.height) true else false;

// Does the image fit with no scaling?
if (opts.scale == .contain and fit_x and fit_y) break :contain;

// Does the image require vertical scaling?
if (fit_x and !fit_y)
p_opts.size = .{
.rows = win.height,
}

// Does the image require horizontal scaling?
else if (!fit_x and fit_y)
p_opts.size = .{
.cols = win.height,
}
else if (!fit_x and !fit_y) {
const diff_x = self.width - win_width_pix;
const diff_y = self.height - win_height_pix;
// The width difference is larger than the height difference.
// Scale by width
if (diff_x > diff_y)
p_opts.size = .{
.cols = win.width,
}
else
// The height difference is larger than the width difference.
// Scale by height
p_opts.size = .{
.rows = win.height,
};
} else {
std.debug.assert(opts.scale == .fit);
std.debug.assert(win_width_pix >= self.width);
std.debug.assert(win_height_pix >= self.height);

// Fits in both directions. Find the closer direction
const diff_x = win_width_pix - self.width;
const diff_y = win_height_pix - self.height;
// The width is closer in dimension. Scale by that
if (diff_x < diff_y)
p_opts.size = .{
.cols = win.width,
}
else
p_opts.size = .{
.rows = win.height,
};
}
},
}
const p = Placement{
.img_id = self.id,
.options = p_opts,
};
win.writeCell(0, 0, .{ .image = p });
}

/// the size of the image, in cells
pub fn cellSize(self: Image, win: Window) !CellSize {
// cell geometry
const x_pix = win.screen.width_pix;
Expand Down
58 changes: 48 additions & 10 deletions src/Vaxis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -366,19 +366,57 @@ pub fn render(self: *Vaxis) !void {
}

if (cell.image) |img| {
if (img.size) |size| {
try std.fmt.format(
tty.buffered_writer.writer(),
ctlseqs.kitty_graphics_scale,
.{ img.img_id, img.z_index, size.cols, size.rows },
try tty.buffered_writer.writer().print(
ctlseqs.kitty_graphics_preamble,
.{img.img_id},
);
if (img.options.pixel_offset) |offset| {
try tty.buffered_writer.writer().print(
",X={d},Y={d}",
.{ offset.x, offset.y },
);
} else {
try std.fmt.format(
tty.buffered_writer.writer(),
ctlseqs.kitty_graphics_place,
.{ img.img_id, img.z_index },
}
if (img.options.clip_region) |clip| {
if (clip.x) |x|
try tty.buffered_writer.writer().print(
",x={d}",
.{x},
);
if (clip.y) |y|
try tty.buffered_writer.writer().print(
",y={d}",
.{y},
);
if (clip.width) |width|
try tty.buffered_writer.writer().print(
",w={d}",
.{width},
);
if (clip.height) |height|
try tty.buffered_writer.writer().print(
",h={d}",
.{height},
);
}
if (img.options.size) |size| {
if (size.rows) |rows|
try tty.buffered_writer.writer().print(
",r={d}",
.{rows},
);
if (size.cols) |cols|
try tty.buffered_writer.writer().print(
",c={d}",
.{cols},
);
}
if (img.options.z_index) |z| {
try tty.buffered_writer.writer().print(
",z={d}",
.{z},
);
}
try tty.buffered_writer.writer().writeAll(ctlseqs.kitty_graphics_closing);
}

// something is different, so let's loop through everything and
Expand Down
4 changes: 2 additions & 2 deletions src/ctlseqs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,5 @@ 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\\";
pub const kitty_graphics_preamble = "\x1b_Ga=p,i={d}";
pub const kitty_graphics_closing = ",C=1\x1b\\";

0 comments on commit 1dc90bd

Please sign in to comment.