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