lib/gif/src/decode/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const model = @import("model.zig");
  3 const plan_mod = @import("plan.zig");
  4 
  5 pub const storage_alignment: usize = @alignOf(model.Frame);
  6 
  7 pub const Limits = struct {
  8     bytes: []const u8,
  9     bounds: plan_mod.Bounds,
 10 };
 11 
 12 pub const DeriveError = plan_mod.Error;
 13 
 14 pub const Capacity = struct {
 15     plan: plan_mod.Plan,
 16     frame_offset: usize,
 17     frame_bytes: usize,
 18     canvas_offset: usize,
 19     previous_canvas_offset: usize,
 20     indices_offset: usize,
 21     compressed_offset: usize,
 22     retained_rgba8_offset: usize,
 23     storage_bytes: usize,
 24 
 25     pub fn derive(limits: Limits) DeriveError!Capacity {
 26         const plan = try plan_mod.Plan.inspect(limits.bytes, limits.bounds);
 27         const frames = try placed(model.Frame, 0, plan.frames);
 28         const canvas_offset = frames.end;
 29         const previous_canvas_offset = try added(canvas_offset, plan.canvas_rgba8_bytes);
 30         const indices_offset = try added(previous_canvas_offset, plan.canvas_rgba8_bytes);
 31         const compressed_offset = try added(indices_offset, plan.frame_pixels);
 32         const retained_rgba8_offset = try added(compressed_offset, plan.compressed_bytes);
 33         const storage_bytes = try added(retained_rgba8_offset, plan.retained_rgba8_bytes);
 34 
 35         return .{
 36             .plan = plan,
 37             .frame_offset = frames.start,
 38             .frame_bytes = frames.bytes,
 39             .canvas_offset = canvas_offset,
 40             .previous_canvas_offset = previous_canvas_offset,
 41             .indices_offset = indices_offset,
 42             .compressed_offset = compressed_offset,
 43             .retained_rgba8_offset = retained_rgba8_offset,
 44             .storage_bytes = storage_bytes,
 45         };
 46     }
 47 };
 48 
 49 const Region = struct {
 50     start: usize,
 51     bytes: usize,
 52     end: usize,
 53 };
 54 
 55 fn added(left: usize, right: usize) DeriveError!usize {
 56     return std.math.add(usize, left, right) catch error.CapacityOverflow;
 57 }
 58 
 59 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
 60     const mask: usize = @alignOf(T) - 1;
 61     const start = (try added(offset, mask)) & ~mask;
 62     const bytes = std.math.mul(usize, count, @sizeOf(T)) catch return error.CapacityOverflow;
 63     return .{ .start = start, .bytes = bytes, .end = try added(start, bytes) };
 64 }
 65 
 66 fn modelCapacity(limits: Limits) DeriveError!Capacity {
 67     const plan = try plan_mod.Plan.inspect(limits.bytes, limits.bounds);
 68     const frame_offset: u128 = 0;
 69     const frame_bytes = @as(u128, plan.frames) * @sizeOf(model.Frame);
 70     const canvas_offset = frame_offset + frame_bytes;
 71     const previous_canvas_offset = canvas_offset + plan.canvas_rgba8_bytes;
 72     const indices_offset = previous_canvas_offset + plan.canvas_rgba8_bytes;
 73     const compressed_offset = indices_offset + plan.frame_pixels;
 74     const retained_rgba8_offset = compressed_offset + plan.compressed_bytes;
 75     const storage_bytes = retained_rgba8_offset + plan.retained_rgba8_bytes;
 76     const values = [_]u128{
 77         frame_bytes,
 78         canvas_offset,
 79         previous_canvas_offset,
 80         indices_offset,
 81         compressed_offset,
 82         retained_rgba8_offset,
 83         storage_bytes,
 84     };
 85     for (values) |value| {
 86         if (value > std.math.maxInt(usize)) return error.CapacityOverflow;
 87     }
 88     return .{
 89         .plan = plan,
 90         .frame_offset = @intCast(frame_offset),
 91         .frame_bytes = @intCast(frame_bytes),
 92         .canvas_offset = @intCast(canvas_offset),
 93         .previous_canvas_offset = @intCast(previous_canvas_offset),
 94         .indices_offset = @intCast(indices_offset),
 95         .compressed_offset = @intCast(compressed_offset),
 96         .retained_rgba8_offset = @intCast(retained_rgba8_offset),
 97         .storage_bytes = @intCast(storage_bytes),
 98     };
 99 }
100 
101 const witness = [_]u8{
102     0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x02, 0x00, 0x01, 0x00, 0xf0, 0x00, 0x00, 0xff, 0x00, 0x00,
103     0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44,
104     0x0a, 0x00, 0x3b,
105 };
106 
107 test "GIF decode capacity matches an independent aligned byte model" {
108     comptime {
109         @stardustClaim(
110             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "gif_decode_capacity"),
111             null,
112             null,
113             null,
114             null,
115             null,
116             null,
117         );
118     }
119 
120     const limits = Limits{
121         .bytes = &witness,
122         .bounds = .{
123             .canvas_pixels = 2,
124             .frame_pixels = 2,
125             .frames = 1,
126             .compressed_bytes = 2,
127             .retained_rgba8_bytes = 8,
128         },
129     };
130     const capacity = try Capacity.derive(limits);
131     try std.testing.expectEqual(try modelCapacity(limits), capacity);
132     try std.testing.expectEqual(capacity.storage_bytes, capacity.retained_rgba8_offset + 8);
133 }