diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/pkg/wuffs/src | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/pkg/wuffs/src')
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/c.zig | 18 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/error.zig | 13 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/jpeg.zig | 151 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/main.zig | 20 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/png.zig | 151 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/wuffs/src/swizzle.zig | 121 |
6 files changed, 474 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/pkg/wuffs/src/c.zig b/vendor/libghostty-vt/pkg/wuffs/src/c.zig new file mode 100644 index 00000000..d94247df --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/c.zig @@ -0,0 +1,18 @@ +pub const c = @cImport({ + for (defines) |d| @cDefine(d, "1"); + @cInclude("wuffs-v0.4.c"); +}); + +/// All the C macros defined so that the header matches the build. +pub const defines: []const []const u8 = &[_][]const u8{ + "WUFFS_CONFIG__MODULES", + "WUFFS_CONFIG__MODULE__AUX__BASE", + "WUFFS_CONFIG__MODULE__AUX__IMAGE", + "WUFFS_CONFIG__MODULE__BASE", + "WUFFS_CONFIG__MODULE__ADLER32", + "WUFFS_CONFIG__MODULE__CRC32", + "WUFFS_CONFIG__MODULE__DEFLATE", + "WUFFS_CONFIG__MODULE__JPEG", + "WUFFS_CONFIG__MODULE__PNG", + "WUFFS_CONFIG__MODULE__ZLIB", +}; diff --git a/vendor/libghostty-vt/pkg/wuffs/src/error.zig b/vendor/libghostty-vt/pkg/wuffs/src/error.zig new file mode 100644 index 00000000..0be55cf4 --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/error.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +const c = @import("c.zig").c; + +pub const Error = std.mem.Allocator.Error || error{ WuffsError, Overflow }; + +pub fn check(log: anytype, status: *const c.struct_wuffs_base__status__struct) error{WuffsError}!void { + if (!c.wuffs_base__status__is_ok(status)) { + const e = c.wuffs_base__status__message(status); + log.warn("decode err={s}", .{e}); + return error.WuffsError; + } +} diff --git a/vendor/libghostty-vt/pkg/wuffs/src/jpeg.zig b/vendor/libghostty-vt/pkg/wuffs/src/jpeg.zig new file mode 100644 index 00000000..69d91c8a --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/jpeg.zig @@ -0,0 +1,151 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const c = @import("c.zig").c; +const Error = @import("error.zig").Error; +const check = @import("error.zig").check; +const ImageData = @import("main.zig").ImageData; +const maximum_image_size = @import("main.zig").maximum_image_size; +const mul = std.math.mul; + +const log = std.log.scoped(.wuffs_jpeg); + +/// Decode a JPEG image. +pub fn decode(alloc: Allocator, data: []const u8) Error!ImageData { + // Work around some weirdness in WUFFS/Zig, there are some structs that + // are defined as "extern" by the Zig compiler which means that Zig won't + // allocate them on the stack at compile time. WUFFS has functions for + // dynamically allocating these structs but they use the C malloc/free. This + // gets around that by using the Zig allocator to allocate enough memory for + // the struct and then casts it to the appropriate pointer. + + const decoder_buf = try alloc.alloc(u8, c.sizeof__wuffs_jpeg__decoder()); + defer alloc.free(decoder_buf); + + const decoder: ?*c.wuffs_jpeg__decoder = @ptrCast(decoder_buf); + { + const status = c.wuffs_jpeg__decoder__initialize( + decoder, + c.sizeof__wuffs_jpeg__decoder(), + c.WUFFS_VERSION, + 0, + ); + try check(log, &status); + } + + var source_buffer: c.wuffs_base__io_buffer = .{ + .data = .{ .ptr = @ptrCast(@constCast(data.ptr)), .len = data.len }, + .meta = .{ + .wi = data.len, + .ri = 0, + .pos = 0, + .closed = true, + }, + }; + + var image_config: c.wuffs_base__image_config = undefined; + { + const status = c.wuffs_jpeg__decoder__decode_image_config( + decoder, + &image_config, + &source_buffer, + ); + try check(log, &status); + } + + const width = c.wuffs_base__pixel_config__width(&image_config.pixcfg); + const height = c.wuffs_base__pixel_config__height(&image_config.pixcfg); + + c.wuffs_base__pixel_config__set( + &image_config.pixcfg, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL, + c.WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, + width, + height, + ); + + const size: usize = try mul( + usize, + try mul(usize, width, height), + @sizeOf(c.wuffs_base__color_u32_argb_premul), + ); + + if (size > maximum_image_size) { + log.warn("image size {d} is larger than the maximum allowed ({d})", .{ size, maximum_image_size }); + return error.Overflow; + } + + const destination = try alloc.alloc( + u8, + size, + ); + errdefer alloc.free(destination); + + // temporary buffer for intermediate processing of image + const work_buffer = try alloc.alloc( + u8, + + // The type of this is a u64 on all systems but our allocator + // uses a usize which is a u32 on 32-bit systems. + std.math.cast( + usize, + c.wuffs_jpeg__decoder__workbuf_len(decoder).max_incl, + ) orelse return error.OutOfMemory, + ); + defer alloc.free(work_buffer); + + const work_slice = c.wuffs_base__make_slice_u8( + work_buffer.ptr, + work_buffer.len, + ); + + var pixel_buffer: c.wuffs_base__pixel_buffer = undefined; + { + const status = c.wuffs_base__pixel_buffer__set_from_slice( + &pixel_buffer, + &image_config.pixcfg, + c.wuffs_base__make_slice_u8(destination.ptr, destination.len), + ); + try check(log, &status); + } + + { + const status = c.wuffs_jpeg__decoder__decode_frame( + decoder, + &pixel_buffer, + &source_buffer, + c.WUFFS_BASE__PIXEL_BLEND__SRC, + work_slice, + null, + ); + try check(log, &status); + } + + return .{ + .width = width, + .height = height, + .data = destination, + }; +} + +test "jpeg_decode_000000" { + const data = try decode(std.testing.allocator, @embedFile("1x1#000000.jpg")); + defer std.testing.allocator.free(data.data); + + try std.testing.expectEqual(1, data.width); + try std.testing.expectEqual(1, data.height); + try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 255 }, data.data); +} + +test "jpeg_decode_FFFFFF" { + const data = try decode(std.testing.allocator, @embedFile("1x1#FFFFFF.jpg")); + defer std.testing.allocator.free(data.data); + + try std.testing.expectEqual(1, data.width); + try std.testing.expectEqual(1, data.height); + try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data); +} + +test "jpeg: too big" { + const data = decode(std.testing.allocator, @embedFile("too_big.jpg")); + try std.testing.expectError(error.Overflow, data); +} diff --git a/vendor/libghostty-vt/pkg/wuffs/src/main.zig b/vendor/libghostty-vt/pkg/wuffs/src/main.zig new file mode 100644 index 00000000..207d83f9 --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/main.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub const png = @import("png.zig"); +pub const jpeg = @import("jpeg.zig"); +pub const swizzle = @import("swizzle.zig"); +pub const Error = @import("error.zig").Error; + +/// The maximum image size, based on the 4G limit of Ghostty's +/// `image-storage-limit` config. +pub const maximum_image_size = 4 * 1024 * 1024 * 1024; + +pub const ImageData = struct { + width: u32, + height: u32, + data: []u8, +}; + +test { + std.testing.refAllDeclsRecursive(@This()); +} diff --git a/vendor/libghostty-vt/pkg/wuffs/src/png.zig b/vendor/libghostty-vt/pkg/wuffs/src/png.zig new file mode 100644 index 00000000..57a0e63b --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/png.zig @@ -0,0 +1,151 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const c = @import("c.zig").c; +const Error = @import("error.zig").Error; +const check = @import("error.zig").check; +const ImageData = @import("main.zig").ImageData; +const maximum_image_size = @import("main.zig").maximum_image_size; +const mul = std.math.mul; + +const log = std.log.scoped(.wuffs_png); + +/// Decode a PNG image. +pub fn decode(alloc: Allocator, data: []const u8) Error!ImageData { + // Work around some weirdness in WUFFS/Zig, there are some structs that + // are defined as "extern" by the Zig compiler which means that Zig won't + // allocate them on the stack at compile time. WUFFS has functions for + // dynamically allocating these structs but they use the C malloc/free. This + // gets around that by using the Zig allocator to allocate enough memory for + // the struct and then casts it to the appropriate pointer. + + const decoder_buf = try alloc.alloc(u8, c.sizeof__wuffs_png__decoder()); + defer alloc.free(decoder_buf); + + const decoder: ?*c.wuffs_png__decoder = @ptrCast(decoder_buf); + { + const status = c.wuffs_png__decoder__initialize( + decoder, + c.sizeof__wuffs_png__decoder(), + c.WUFFS_VERSION, + 0, + ); + try check(log, &status); + } + + var source_buffer: c.wuffs_base__io_buffer = .{ + .data = .{ .ptr = @ptrCast(@constCast(data.ptr)), .len = data.len }, + .meta = .{ + .wi = data.len, + .ri = 0, + .pos = 0, + .closed = true, + }, + }; + + var image_config: c.wuffs_base__image_config = undefined; + { + const status = c.wuffs_png__decoder__decode_image_config( + decoder, + &image_config, + &source_buffer, + ); + try check(log, &status); + } + + const width = c.wuffs_base__pixel_config__width(&image_config.pixcfg); + const height = c.wuffs_base__pixel_config__height(&image_config.pixcfg); + + c.wuffs_base__pixel_config__set( + &image_config.pixcfg, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL, + c.WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, + width, + height, + ); + + const size: usize = try mul( + usize, + try mul(usize, width, height), + @sizeOf(c.wuffs_base__color_u32_argb_premul), + ); + + if (size > maximum_image_size) { + log.warn("image size {d} is larger than the maximum allowed ({d})", .{ size, maximum_image_size }); + return error.Overflow; + } + + const destination = try alloc.alloc( + u8, + size, + ); + errdefer alloc.free(destination); + + // temporary buffer for intermediate processing of image + const work_buffer = try alloc.alloc( + u8, + + // The type of this is a u64 on all systems but our allocator + // uses a usize which is a u32 on 32-bit systems. + std.math.cast( + usize, + c.wuffs_png__decoder__workbuf_len(decoder).max_incl, + ) orelse return error.OutOfMemory, + ); + defer alloc.free(work_buffer); + + const work_slice = c.wuffs_base__make_slice_u8( + work_buffer.ptr, + work_buffer.len, + ); + + var pixel_buffer: c.wuffs_base__pixel_buffer = undefined; + { + const status = c.wuffs_base__pixel_buffer__set_from_slice( + &pixel_buffer, + &image_config.pixcfg, + c.wuffs_base__make_slice_u8(destination.ptr, destination.len), + ); + try check(log, &status); + } + + { + const status = c.wuffs_png__decoder__decode_frame( + decoder, + &pixel_buffer, + &source_buffer, + c.WUFFS_BASE__PIXEL_BLEND__SRC, + work_slice, + null, + ); + try check(log, &status); + } + + return .{ + .width = width, + .height = height, + .data = destination, + }; +} + +test "png_decode_000000" { + const data = try decode(std.testing.allocator, @embedFile("1x1#000000.png")); + defer std.testing.allocator.free(data.data); + + try std.testing.expectEqual(1, data.width); + try std.testing.expectEqual(1, data.height); + try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 255 }, data.data); +} + +test "png_decode_FFFFFF" { + const data = try decode(std.testing.allocator, @embedFile("1x1#FFFFFF.png")); + defer std.testing.allocator.free(data.data); + + try std.testing.expectEqual(1, data.width); + try std.testing.expectEqual(1, data.height); + try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data); +} + +test "png: too big" { + const data = decode(std.testing.allocator, @embedFile("too_big.png")); + try std.testing.expectError(error.Overflow, data); +} diff --git a/vendor/libghostty-vt/pkg/wuffs/src/swizzle.zig b/vendor/libghostty-vt/pkg/wuffs/src/swizzle.zig new file mode 100644 index 00000000..352cf2b5 --- /dev/null +++ b/vendor/libghostty-vt/pkg/wuffs/src/swizzle.zig @@ -0,0 +1,121 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const c = @import("c.zig").c; +const Error = @import("error.zig").Error; + +const log = std.log.scoped(.wuffs_swizzler); + +pub fn gToRgba(alloc: Allocator, src: []const u8) Error![]u8 { + return swizzle( + alloc, + src, + c.WUFFS_BASE__PIXEL_FORMAT__Y, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL, + ); +} + +pub fn gaToRgba(alloc: Allocator, src: []const u8) Error![]u8 { + return swizzle( + alloc, + src, + c.WUFFS_BASE__PIXEL_FORMAT__YA_PREMUL, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL, + ); +} + +pub fn rgbToRgba(alloc: Allocator, src: []const u8) Error![]u8 { + return swizzle( + alloc, + src, + c.WUFFS_BASE__PIXEL_FORMAT__RGB, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL, + ); +} + +pub fn bgrToRgba(alloc: Allocator, src: []const u8) Error![]u8 { + return swizzle( + alloc, + src, + c.WUFFS_BASE__PIXEL_FORMAT__BGR, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL, + ); +} + +pub fn bgraToRgba(alloc: Allocator, src: []const u8) Error![]u8 { + return swizzle( + alloc, + src, + c.WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL, + c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL, + ); +} + +fn swizzle( + alloc: Allocator, + src: []const u8, + comptime src_pixel_format: u32, + comptime dst_pixel_format: u32, +) Error![]u8 { + const src_slice = c.wuffs_base__make_slice_u8( + @constCast(src.ptr), + src.len, + ); + + const dst_fmt = c.wuffs_base__make_pixel_format( + dst_pixel_format, + ); + + assert(c.wuffs_base__pixel_format__is_direct(&dst_fmt)); + assert(c.wuffs_base__pixel_format__is_interleaved(&dst_fmt)); + assert(c.wuffs_base__pixel_format__bits_per_pixel(&dst_fmt) % 8 == 0); + + const dst_size = c.wuffs_base__pixel_format__bits_per_pixel(&dst_fmt) / 8; + + const src_fmt = c.wuffs_base__make_pixel_format( + src_pixel_format, + ); + + assert(c.wuffs_base__pixel_format__is_direct(&src_fmt)); + assert(c.wuffs_base__pixel_format__is_interleaved(&src_fmt)); + assert(c.wuffs_base__pixel_format__bits_per_pixel(&src_fmt) % 8 == 0); + + const src_size = c.wuffs_base__pixel_format__bits_per_pixel(&src_fmt) / 8; + + assert(src.len % src_size == 0); + + const dst = try alloc.alloc(u8, src.len * dst_size / src_size); + errdefer alloc.free(dst); + + const dst_slice = c.wuffs_base__make_slice_u8( + dst.ptr, + dst.len, + ); + + var swizzler: c.wuffs_base__pixel_swizzler = undefined; + { + const status = c.wuffs_base__pixel_swizzler__prepare( + &swizzler, + dst_fmt, + c.wuffs_base__empty_slice_u8(), + src_fmt, + c.wuffs_base__empty_slice_u8(), + c.WUFFS_BASE__PIXEL_BLEND__SRC, + ); + if (!c.wuffs_base__status__is_ok(&status)) { + const e = c.wuffs_base__status__message(&status); + log.warn("{s}", .{e}); + return error.WuffsError; + } + } + { + _ = c.wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice( + &swizzler, + dst_slice, + c.wuffs_base__empty_slice_u8(), + src_slice, + ); + } + + return dst; +} |
