Skip to content

libghostty-vt: decode png hook should support a cap on bytes returned #12163

Description

@mitchellh

This lets callers over-allocate which may be more efficient in some cases.

cc @pluiedev

Activity

  1. added this to the 1.4.0 milestone on Apr 7, 2026
  2. added theissue type on Apr 7, 2026
  3. mitchellh commented on Apr 7, 2026

    @mitchellh
    ContributorAuthor

    In general, we should consider this pattern anywhere we get allocated data returned, fyi.

  4. mdm5995 commented on Apr 11, 2026

    @mdm5995

    Does the caller supplying a FixedBufferAllocator solve this problem? That way the caller has complete control over the allocation hot path, and can ensure it is preheated with enough capacity to fit their needs. The problem then is that the wuffs fn decode uses one allocator for both scratch lifetimes and result lifetimes. so instead of providing a cap config or similar, the memory lifetimes just need to be separated instead.

    The function would change simply:

    /// Decode a PNG image.
    pub fn decode(result: Allocator, scratch: 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 result.alloc(u8, c.sizeof__wuffs_png__decoder());
        defer result.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 result.alloc(
            u8,
            size,
        );
        errdefer result.free(destination);
    
        // NOTE: this comment is now redundant, as the allocator expresses the intent directly
        // temporary buffer for intermediate processing of image
        const work_buffer = try scratch.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 scratch.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,
        };
    }
  5. added a commit that references this issue on Sep 3, 2026
    ba874e9
  6. added a commit that references this issue on Sep 24, 2026
    aa2a596
  7. added a commit that references this issue on Oct 9, 2026
    ae6f155
  8. modified the milestones: 1.4.0, 1.5.0 on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    contributor friendlyA well-scoped, approachable issue for someone looking to contribute.libghostty

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions