Something went wrong. Try again.
A sans-I/O Zig library that reads and writes DEC sixel graphics as z2d surfaces.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Decoding sixel data that is already in memory.
const std = @import("std");const Allocator = std.mem.Allocator;const Canvas = @import("Canvas.zig");const Image = @import("Image.zig");const Parser = @import("Parser.zig");const introducer = @import("introducer.zig");const palette = @import("palette.zig");
pub const Options = struct { limits: Canvas.Limits = .{}, /// See `Parser.Options.registers`. registers: u16 = palette.max_registers,};
pub const Error = Allocator.Error || error{ /// The data starts with ESC or `0x90` but not with a sixel introducer. InvalidIntroducer, /// The data draws nothing and gives no size, so there is no image to /// return: z2d has no surface with no pixels. EmptyImage,};
/// Decodes one sixel image from `bytes` into an image the caller owns.////// `bytes` may be a whole control string, introducer and all,////// ESC P 0;1;0 q "1;1;64;64 #0;2;0;0;0 ... ESC \////// in which case the introducer's parameters apply, or just the data that/// would come after the `q`, in which case the defaults do. Decoding stops at/// the first string terminator, abort, or escape sequence, and the/// termination of the result says which it was; data with none of those ends/// with the slice, as `end_of_data`. Anything after the end is ignored. Both/// the 7-bit and the 8-bit forms of DCS and ST are recognized here, since a/// slice handed over as sixel data is not UTF-8 text.pub fn decode(gpa: Allocator, bytes: []const u8, options: Options) Error!Image { var params: introducer.Params = .{}; var body = bytes; if (bytes.len > 0 and introducer.isStart(bytes[0], true)) { switch (introducer.match(bytes, true)) { .sixel => |m| { params = m.params; body = bytes[m.len..]; }, .not_sixel, .need_more => return error.InvalidIntroducer, } }
var parser: Parser = .init(gpa, params, .{ .limits = options.limits, .registers = options.registers, .c1 = true, }); defer parser.deinit(); const n = try parser.feed(body); const termination: Image.Termination = if (n == body.len) .end_of_data else switch (body[n]) { introducer.st_c1 => .st, introducer.esc => if (n + 1 < body.len and body[n + 1] == '\\') .st else .interrupted, 0x18, 0x1a => .aborted, else => .interrupted, }; return (try parser.finish(termination)) orelse error.EmptyImage;}
const testing = std.testing;
test "a whole control string" { var img = try decode(testing.allocator, "\x1bP0;1;0q\"1;1;2;2#1;2;100;0;0~~\x1b\\", .{}); defer img.deinit(testing.allocator); try testing.expectEqual(Image.Termination.st, img.termination); try testing.expect(img.transparent); try testing.expectEqual(@as(u32, 2), img.width()); try testing.expectEqual(@as(u32, 6), img.height());}
test "just the data, and the 8-bit forms" { var a = try decode(testing.allocator, "#1~", .{}); defer a.deinit(testing.allocator); try testing.expectEqual(Image.Termination.end_of_data, a.termination); try testing.expect(!a.transparent);
var b = try decode(testing.allocator, "\x90q~\x9c", .{}); defer b.deinit(testing.allocator); try testing.expectEqual(Image.Termination.st, b.termination);}
test "how the data ended" { var a = try decode(testing.allocator, "~\x18~~", .{}); defer a.deinit(testing.allocator); try testing.expectEqual(Image.Termination.aborted, a.termination); try testing.expectEqual(@as(u32, 1), a.width());
var b = try decode(testing.allocator, "~\x1b[0m", .{}); defer b.deinit(testing.allocator); try testing.expectEqual(Image.Termination.interrupted, b.termination);
var c = try decode(testing.allocator, "~\x1b", .{}); defer c.deinit(testing.allocator); try testing.expectEqual(Image.Termination.interrupted, c.termination);}
test "errors" { try testing.expectError(error.InvalidIntroducer, decode(testing.allocator, "\x1bP$q", .{})); try testing.expectError(error.InvalidIntroducer, decode(testing.allocator, "\x1bP0;1", .{})); try testing.expectError(error.EmptyImage, decode(testing.allocator, "\x1bPq\x1b\\", .{})); try testing.expectError(error.EmptyImage, decode(testing.allocator, "", .{}));}