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 }