//! 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). const std = @import("std"); const zstbi = @import("zstbi"); const ghostty_vt = @import("ghostty-vt"); pub const petscii = @import("petscii.zig"); /// the terminal's own 16 ANSI colors — what the `terminal` palette mode /// scores against; cells then paint as indexed colors so the real terminal /// resolves the RGB pub fn ansiPalette() [16][3]u8 { var p: [16][3]u8 = undefined; for (0..16) |i| { const c = ghostty_vt.color.default[i]; p[i] = .{ c.r, c.g, c.b }; } return p; } /// cap the longest side before keeping/transmitting: a pane is at most a /// screenful of cells, multi-thousand-pixel photos waste decode/transmit time const MAX_DIM: u32 = 1280; pub const PaletteMode = enum { commodore, terminal }; /// 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); } pub fn stop() void { zstbi.deinit(); } /// decode + downscale to RGBA, gpa-owned. Returns null on any failure — the /// pane then simply shows a blank body. pub fn decode(gpa: std.mem.Allocator, bytes: []const u8) ?struct { rgba: []u8, w: usize, h: usize } { if (bytes.len == 0) return null; var img = zstbi.Image.loadFromMemory(bytes, 4) catch return null; // 4 = RGBA defer img.deinit(); var scaled: ?zstbi.Image = null; defer if (scaled) |*s| s.deinit(); const longest = @max(img.width, img.height); const src: *const zstbi.Image = if (longest > MAX_DIM) blk: { const nw = @max(1, img.width * MAX_DIM / longest); const nh = @max(1, img.height * MAX_DIM / longest); scaled = img.resize(nw, nh); break :blk &scaled.?; } else &img; const rgba = gpa.dupe(u8, src.data) catch return null; return .{ .rgba = rgba, .w = src.width, .h = src.height }; }