diff options
Diffstat (limited to 'src/image.zig')
| -rw-r--r-- | src/image.zig | 46 |
1 files changed, 44 insertions, 2 deletions
diff --git a/src/image.zig b/src/image.zig index a3415019..1e7736c5 100644 --- a/src/image.zig +++ b/src/image.zig @@ -1,7 +1,7 @@ //! Image panes. Decoding is zstbi (C stb_image) to RGBA, downscaled; the kept //! pixels render as PETSCII glyph art directly into the Surface — or, when the -//! host has kitty graphics (and Petscii isn't toggled), ride the Surface as a -//! pixel attachment the shell transmits/places (tty: vaxis kitty; SDL: blit). +//! host supports native images (and PETSCII isn't toggled), ride the Surface as +//! a pixel attachment the shell transmits/places (TTY: Kitty; SDL: GPU texture). const std = @import("std"); const zstbi = @import("zstbi"); const ghostty_vt = @import("ghostty-vt"); @@ -25,6 +25,48 @@ const MAX_DIM: u32 = 1280; pub const PaletteMode = enum { commodore, terminal }; +/// Fit an image inside a pixel rectangle without changing its aspect ratio or +/// enlarging it. Native SDL uses this for the same "contain" policy the kitty +/// placement uses. Keeping the arithmetic here makes the backend geometry +/// testable without a GPU or an image-capable terminal. +pub const Fit = struct { w: u32, h: u32 }; + +pub fn contain(iw: usize, ih: usize, max_w: u32, max_h: u32) Fit { + if (iw == 0 or ih == 0 or max_w == 0 or max_h == 0) return .{ .w = 0, .h = 0 }; + + const src_w: u64 = @intCast(iw); + const src_h: u64 = @intCast(ih); + const bound_w: u64 = max_w; + const bound_h: u64 = max_h; + if (src_w <= bound_w and src_h <= bound_h) + return .{ .w = @intCast(src_w), .h = @intCast(src_h) }; + + // Compare the two scale ratios without floating point. Whichever bound is + // tighter determines one exact dimension; the other is rounded down so it + // can never leak a pixel outside the pane body. + if (bound_w * src_h <= bound_h * src_w) { + return .{ + .w = max_w, + .h = @intCast(@max(1, src_h * bound_w / src_w)), + }; + } + return .{ + .w = @intCast(@max(1, src_w * bound_h / src_h)), + .h = max_h, + }; +} + +test "native image contain keeps aspect, bounds, and small-image size" { + try std.testing.expectEqual(Fit{ .w = 40, .h = 20 }, contain(400, 200, 40, 40)); + try std.testing.expectEqual(Fit{ .w = 20, .h = 40 }, contain(200, 400, 40, 40)); + try std.testing.expectEqual(Fit{ .w = 17, .h = 9 }, contain(17, 9, 40, 40)); + try std.testing.expectEqual(Fit{ .w = 0, .h = 0 }, contain(17, 9, 0, 40)); + // Awkward ratios stay inside both bounds rather than rounding one pixel + // over them. + const odd = contain(403, 211, 101, 47); + try std.testing.expect(odd.w <= 101 and odd.h <= 47); +} + /// call once at startup / exit (stb_image's allocator shim) pub fn start(io: std.Io, gpa: std.mem.Allocator) void { zstbi.init(io, gpa); |
