summaryrefslogtreecommitdiff
path: root/src/image.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 13:13:53 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit487434a0b55fa7cf479f39481b51da0a7eda79e1 (patch)
treece410e0c52eb030e1de76079834e6697975c9c17 /src/image.zig
parentd59397d4146ffa23adbbe6dc99c64ebb22a93b2d (diff)
downloadpardes-487434a0b55fa7cf479f39481b51da0a7eda79e1.tar.gz
pardes-487434a0b55fa7cf479f39481b51da0a7eda79e1.zip
gui: draw image surfaces as GPU quads via glsl shaders
Diffstat (limited to 'src/image.zig')
-rw-r--r--src/image.zig46
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);