a modern tui library written in zig
1const std = @import("std");
2const fmt = std.fmt;
3const math = std.math;
4const base64 = std.base64.standard.Encoder;
5const zigimg = @import("zigimg");
6
7const Window = @import("Window.zig");
8
9const Image = @This();
10
11const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
12
13pub const Source = union(enum) {
14 path: []const u8,
15 mem: []const u8,
16};
17
18pub const TransmitFormat = enum {
19 rgb,
20 rgba,
21 png,
22};
23
24pub const Placement = struct {
25 img_id: u32,
26 options: Image.DrawOptions,
27};
28
29pub const CellSize = struct {
30 rows: usize,
31 cols: usize,
32};
33
34pub const DrawOptions = struct {
35 /// an offset into the top left cell, in pixels, with where to place the
36 /// origin of the image. These must be less than the pixel size of a single
37 /// cell
38 pixel_offset: ?struct {
39 x: usize,
40 y: usize,
41 } = null,
42 /// the vertical stacking order
43 /// < 0: Drawn beneath text
44 /// < -1_073_741_824: Drawn beneath "default" background cells
45 z_index: ?i32 = null,
46 /// A clip region of the source image to draw.
47 clip_region: ?struct {
48 x: ?usize = null,
49 y: ?usize = null,
50 width: ?usize = null,
51 height: ?usize = null,
52 } = null,
53 /// Scaling to apply to the Image
54 scale: enum {
55 /// no scaling applied. the image may extend beyond the window
56 none,
57 /// Stretch / shrink the image to fill the window
58 fill,
59 /// Scale the image to fit the window, maintaining aspect ratio
60 fit,
61 /// Scale the image to fit the window, only if needed.
62 contain,
63 } = .none,
64 /// the size to render the image. Generally you will not need to use this
65 /// field, and should prefer to use scale. `draw` will fill in this field with
66 /// the correct values if a scale method is applied.
67 size: ?struct {
68 rows: ?usize = null,
69 cols: ?usize = null,
70 } = null,
71};
72
73/// unique identifier for this image. This will be managed by the screen.
74id: u32,
75
76/// width in pixels
77width: usize,
78/// height in pixels
79height: usize,
80
81pub fn draw(self: Image, win: Window, opts: DrawOptions) !void {
82 var p_opts = opts;
83 switch (opts.scale) {
84 .none => {},
85 .fill => {
86 p_opts.size = .{
87 .rows = win.height,
88 .cols = win.width,
89 };
90 },
91 .fit,
92 .contain,
93 => contain: {
94 // cell geometry
95 const x_pix = win.screen.width_pix;
96 const y_pix = win.screen.height_pix;
97 const w = win.screen.width;
98 const h = win.screen.height;
99
100 const pix_per_col = try std.math.divCeil(usize, x_pix, w);
101 const pix_per_row = try std.math.divCeil(usize, y_pix, h);
102
103 const win_width_pix = pix_per_col * win.width;
104 const win_height_pix = pix_per_row * win.height;
105
106 const fit_x: bool = if (win_width_pix >= self.width) true else false;
107 const fit_y: bool = if (win_height_pix >= self.height) true else false;
108
109 // Does the image fit with no scaling?
110 if (opts.scale == .contain and fit_x and fit_y) break :contain;
111
112 // Does the image require vertical scaling?
113 if (fit_x and !fit_y)
114 p_opts.size = .{
115 .rows = win.height,
116 }
117
118 // Does the image require horizontal scaling?
119 else if (!fit_x and fit_y)
120 p_opts.size = .{
121 .cols = win.width,
122 }
123 else if (!fit_x and !fit_y) {
124 const diff_x = self.width - win_width_pix;
125 const diff_y = self.height - win_height_pix;
126 // The width difference is larger than the height difference.
127 // Scale by width
128 if (diff_x > diff_y)
129 p_opts.size = .{
130 .cols = win.width,
131 }
132 else
133 // The height difference is larger than the width difference.
134 // Scale by height
135 p_opts.size = .{
136 .rows = win.height,
137 };
138 } else {
139 std.debug.assert(opts.scale == .fit);
140 std.debug.assert(win_width_pix >= self.width);
141 std.debug.assert(win_height_pix >= self.height);
142
143 // Fits in both directions. Find the closer direction
144 const diff_x = win_width_pix - self.width;
145 const diff_y = win_height_pix - self.height;
146 // The width is closer in dimension. Scale by that
147 if (diff_x < diff_y)
148 p_opts.size = .{
149 .cols = win.width,
150 }
151 else
152 p_opts.size = .{
153 .rows = win.height,
154 };
155 }
156 },
157 }
158 const p = Placement{
159 .img_id = self.id,
160 .options = p_opts,
161 };
162 win.writeCell(0, 0, .{ .image = p });
163}
164
165/// the size of the image, in cells
166pub fn cellSize(self: Image, win: Window) !CellSize {
167 // cell geometry
168 const x_pix = win.screen.width_pix;
169 const y_pix = win.screen.height_pix;
170 const w = win.screen.width;
171 const h = win.screen.height;
172
173 const pix_per_col = try std.math.divCeil(usize, x_pix, w);
174 const pix_per_row = try std.math.divCeil(usize, y_pix, h);
175
176 const cell_width = std.math.divCeil(usize, self.width, pix_per_col) catch 0;
177 const cell_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0;
178 return .{
179 .rows = cell_height,
180 .cols = cell_width,
181 };
182}