summaryrefslogtreecommitdiff
path: root/src/image_pane.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/image_pane.zig')
-rw-r--r--src/image_pane.zig262
1 files changed, 0 insertions, 262 deletions
diff --git a/src/image_pane.zig b/src/image_pane.zig
deleted file mode 100644
index 71ef2228..00000000
--- a/src/image_pane.zig
+++ /dev/null
@@ -1,262 +0,0 @@
-//! Image panes: their owned pixels, PETSCII cache, lifecycle, and two render
-//! paths. Core layout supplies the body rectangle; this module fills it or
-//! attaches one native image to it.
-const std = @import("std");
-const pardes = @import("pardes.zig");
-const image = @import("image.zig");
-const look = @import("look.zig");
-const dump = @import("dump.zig");
-const config = @import("config.zig");
-
-const GridKey = struct {
- cols: u16 = 0,
- rows: u16 = 0,
- palette: image.PaletteMode = .commodore,
- ascii: bool = true,
-};
-
-/// The decoded, downscaled RGBA is retained so changing the pane size or one
-/// of the glyph-renderer toggles can rebuild the PETSCII projection without
-/// decoding the source again. All slices are owned by Pardes.image_gpa.
-pub const State = struct {
- path: []u8,
- petscii: bool = false,
- pmode: image.PaletteMode = .commodore,
- ascii: bool = true,
- tried: bool = false,
- rgba: []u8 = &.{},
- iw: usize = 0,
- ih: usize = 0,
- /// Dump-loaded bytes, decoded lazily by the same path as a disk image.
- raw: []u8 = &.{},
- grid: image.petscii.Grid = .{ .cells = &.{}, .gw = 0, .gh = 0 },
- grid_key: GridKey = .{},
-
- pub fn deinit(state: *State, gpa: std.mem.Allocator) void {
- gpa.free(state.path);
- if (state.rgba.len > 0) gpa.free(state.rgba);
- if (state.raw.len > 0) gpa.free(state.raw);
- if (state.grid.cells.len > 0) gpa.free(state.grid.cells);
- state.* = undefined;
- }
-};
-
-/// Construct an image pane from a path and optionally transferred dump bytes.
-/// `raw` must be image_gpa-owned and ownership transfers only on success.
-pub fn create(p: *pardes.Pardes, id: usize, path: []const u8, raw: []u8) !*pardes.Pane {
- const path_copy = try p.image_gpa.dupe(u8, path);
- errdefer p.image_gpa.free(path_copy);
- const pane = try p.newDocPane(id);
- pane.image = .{ .path = path_copy, .raw = raw };
- return pane;
-}
-
-/// Serialize the binary image record used by images and, for dump-schema
-/// compatibility, PDFs. Common pane metadata is supplied by the core.
-pub fn dumpPane(
- arena: std.mem.Allocator,
- pane: *const pardes.Pane,
- tag: []const u8,
- body: []const u8,
- scroll: usize,
- path: []const u8,
- raw: []const u8,
-) !dump.Pane {
- const bytes = if (raw.len > 0) raw else look.readFile(arena, path) catch "";
- return .{
- .kind = .image,
- .tag = tag,
- .body = body,
- .scroll = scroll,
- .cols = pane.cols,
- .rows = pane.rows,
- .vweight = pane.vweight,
- .image = .{
- .path = path,
- .bytes_b64 = if (bytes.len > 0) try dump.encodeBytes(arena, bytes) else "",
- .petscii = if (pane.image) |state| state.petscii else false,
- .palette = if (pane.image) |state| switch (state.pmode) {
- .commodore => .commodore,
- .terminal => .terminal,
- } else .commodore,
- .ascii = if (pane.image) |state| state.ascii else true,
- },
- };
-}
-
-/// Rebuild an ordinary raster image from its dump record. PDF records share
-/// the schema for compatibility and are deliberately dispatched by the core
-/// before reaching this function.
-pub fn restore(p: *pardes.Pardes, id: usize, src: dump.Pane) !*pardes.Pane {
- const saved = src.image.?;
- var raw: []u8 = if (saved.bytes_b64.len > 0)
- try dump.decodeBytes(p.image_gpa, saved.bytes_b64)
- else
- &.{};
- errdefer if (raw.len > 0) p.image_gpa.free(raw);
- const pane = try create(p, id, saved.path, raw);
- raw = &.{};
- pane.image.?.petscii = saved.petscii;
- pane.image.?.pmode = switch (saved.palette) {
- .commodore => .commodore,
- .terminal => .terminal,
- };
- pane.image.?.ascii = saved.ascii;
- pane.cols = @max(1, src.cols);
- pane.rows = @max(1, src.rows);
- return pane;
-}
-
-pub fn togglePetscii(state: *State) void {
- state.petscii = !state.petscii;
-}
-
-pub fn togglePalette(state: *State) void {
- state.pmode = if (state.pmode == .commodore) .terminal else .commodore;
-}
-
-pub fn toggleAscii(state: *State) void {
- state.ascii = !state.ascii;
-}
-
-/// Image renderer choices are pane-local, not global Config values. Keep them
-/// queryable where they apply: the live tag beside the image's path.
-pub fn tagPrefix(arena: std.mem.Allocator, state: *const State) ![]u8 {
- return std.fmt.allocPrint(
- arena,
- "{s} petscii:{s} palette:{s} ascii:{s} {s}",
- .{
- config.tag_image,
- if (state.petscii) "on" else "off",
- @tagName(state.pmode),
- if (state.ascii) "on" else "off",
- state.path,
- },
- );
-}
-
-/// Prefix written by version-1 dumps before renderer choices became visible
-/// in the live tag. Returned as a borrowed slice of saved_tag for one-time
-/// custom-tail migration.
-pub fn legacySavedPrefix(state: *const State, saved_tag: []const u8) ?[]const u8 {
- const lead = config.tag_image ++ " ";
- if (!std.mem.startsWith(u8, saved_tag, lead)) return null;
- const path_at = lead.len;
- if (!std.mem.startsWith(u8, saved_tag[path_at..], state.path)) return null;
- return saved_tag[0 .. path_at + state.path.len];
-}
-
-fn ensureDecoded(p: *pardes.Pardes, state: *State) void {
- if (state.tried) return;
- state.tried = true;
- const bytes: []const u8 = if (state.raw.len > 0)
- state.raw
- else
- look.readFile(p.scratch.allocator(), state.path) catch "";
- if (image.decode(p.image_gpa, bytes)) |decoded| {
- state.rgba = decoded.rgba;
- state.iw = decoded.w;
- state.ih = decoded.h;
- }
-}
-
-fn ensureGrid(p: *pardes.Pardes, state: *State, cols: u16, rows: u16) void {
- const wanted = GridKey{ .cols = cols, .rows = rows, .palette = state.pmode, .ascii = state.ascii };
- if (state.grid.cells.len > 0 and std.meta.eql(state.grid_key, wanted)) return;
- if (state.grid.cells.len > 0) p.image_gpa.free(state.grid.cells);
- const palette = switch (state.pmode) {
- .commodore => image.petscii.commodore,
- .terminal => image.ansiPalette(),
- };
- state.grid = image.petscii.render(
- p.image_gpa,
- state.rgba,
- state.iw,
- state.ih,
- cols,
- rows,
- palette,
- state.ascii,
- ) catch .{ .cells = &.{}, .gw = 0, .gh = 0 };
- state.grid_key = wanted;
-}
-
-/// Render the image body supplied by core layout. Native-capable hosts receive
-/// a pixel attachment; other hosts and explicit PETSCII mode receive cells.
-pub fn draw(
- p: *pardes.Pardes,
- state: *State,
- pane_id: u8,
- serial: u32,
- x: u16,
- y: u16,
- cols: u16,
- rows: u16,
-) void {
- ensureDecoded(p, state);
- if (state.rgba.len == 0 or cols == 0 or rows == 0) return;
- if (!state.petscii and p.native_images) {
- _ = p.appendImagePlace(.{
- .pane = pane_id,
- .serial = serial,
- .x = x,
- .y = y,
- .w = cols,
- .h = rows,
- .rgba = state.rgba,
- .iw = state.iw,
- .ih = state.ih,
- });
- return;
- }
-
- ensureGrid(p, state, cols, rows);
- if (state.grid.cells.len == 0) return;
- const offx = if (cols > state.grid.gw) (@as(usize, cols) - state.grid.gw) / 2 else 0;
- const offy = if (rows > state.grid.gh) (@as(usize, rows) - state.grid.gh) / 2 else 0;
- for (0..state.grid.gh) |cy| for (0..state.grid.gw) |cx| {
- const cell = &state.grid.cells[cy * state.grid.gw + cx];
- const fg: pardes.Color = switch (state.pmode) {
- .commodore => .{ .rgb = image.petscii.commodore[cell.fg] },
- .terminal => .{ .index = cell.fg },
- };
- const bg: pardes.Color = switch (state.pmode) {
- .commodore => .{ .rgb = image.petscii.commodore[cell.bg] },
- .terminal => .{ .index = cell.bg },
- };
- const sx = x + @as(u16, @intCast(offx + cx));
- const sy = y + @as(u16, @intCast(offy + cy));
- if (sx < p.surface.cols and sy < p.surface.rows)
- p.surface.set(sx, sy, cell.glyph[0..cell.glen], .{ .fg = fg, .bg = bg });
- };
-}
-
-test "pane-local renderer choices are visible in the image tag" {
- var state: State = .{ .path = @constCast("/tmp/picture.ppm") };
- togglePetscii(&state);
- togglePalette(&state);
- toggleAscii(&state);
- const tag = try tagPrefix(std.testing.allocator, &state);
- defer std.testing.allocator.free(tag);
- try std.testing.expectEqualStrings(
- config.tag_image ++ " petscii:on palette:terminal ascii:off /tmp/picture.ppm",
- tag,
- );
- try std.testing.expectEqualStrings(
- "img /tmp/picture.ppm",
- legacySavedPrefix(&state, "img /tmp/picture.ppm Keep Del").?,
- );
-}
-
-test "dump restore propagates malformed embedded image bytes" {
- const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
- defer p.deinit();
- try std.testing.expect(p.panes[1] == null);
- try std.testing.expectError(error.InvalidCharacter, restore(p, 1, .{
- .kind = .image,
- .tag = "img /tmp/bad.ppm",
- .body = "",
- .image = .{ .path = "/tmp/bad.ppm", .bytes_b64 = "A..A" },
- }));
- try std.testing.expect(p.panes[1] == null);
-}