lib/gif/src/decode/plan.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const model = @import("model.zig");
3 const stream = @import("stream.zig");
4
5 pub const Bounds = struct {
6 canvas_pixels: usize,
7 frame_pixels: usize,
8 frames: usize,
9 compressed_bytes: usize,
10 retained_rgba8_bytes: usize,
11 };
12
13 pub const Error = model.DecodeError || model.Exhaustion || error{CapacityOverflow};
14
15 pub const Plan = struct {
16 input_bytes: usize,
17 input_hash: u64,
18 width: u32,
19 height: u32,
20 canvas_pixels: usize,
21 canvas_rgba8_bytes: usize,
22 frames: usize,
23 frame_pixels: usize,
24 compressed_bytes: usize,
25 retained_rgba8_bytes: usize,
26
27 pub fn inspect(bytes: []const u8, bounds: Bounds) Error!Plan {
28 var reader = stream.Reader{ .bytes = bytes };
29 const header = try stream.readHeader(&reader);
30 const canvas_pixels = try multiplied(header.width, header.height);
31 if (canvas_pixels > bounds.canvas_pixels) return error.CanvasPixelCapacityExceeded;
32 const canvas_rgba8_bytes = try multiplied(canvas_pixels, 4);
33
34 var frame_count: usize = 0;
35 var frame_pixels: usize = 0;
36 var compressed_bytes: usize = 0;
37 var retained_rgba8_bytes: usize = 0;
38 var trailer = false;
39
40 while (!trailer) {
41 switch (try reader.byte()) {
42 0x21 => {
43 const label = try reader.byte();
44 if (label == 0xf9) {
45 _ = try stream.readControl(&reader);
46 } else {
47 try stream.skipSubBlocks(&reader);
48 }
49 },
50 0x2c => {
51 const descriptor = try stream.readDescriptor(&reader, header.global_table);
52 const descriptor_pixels = descriptor.pixelCount();
53 if (descriptor_pixels > bounds.frame_pixels) return error.FramePixelCapacityExceeded;
54 frame_pixels = @max(frame_pixels, descriptor_pixels);
55
56 frame_count = std.math.add(usize, frame_count, 1) catch return error.CapacityOverflow;
57 if (frame_count > bounds.frames) return error.FrameCapacityExceeded;
58
59 const compressed = try stream.readSubBlocks(&reader, null, bounds.compressed_bytes);
60 compressed_bytes = @max(compressed_bytes, compressed);
61
62 retained_rgba8_bytes = std.math.add(
63 usize,
64 retained_rgba8_bytes,
65 canvas_rgba8_bytes,
66 ) catch return error.CapacityOverflow;
67 if (retained_rgba8_bytes > bounds.retained_rgba8_bytes) {
68 return error.RetainedRgba8CapacityExceeded;
69 }
70 },
71 0x3b => trailer = true,
72 else => return error.InvalidDescriptor,
73 }
74 }
75
76 if (frame_count == 0) return error.NoFrames;
77 return .{
78 .input_bytes = bytes.len,
79 .input_hash = std.hash.Wyhash.hash(0, bytes),
80 .width = header.width,
81 .height = header.height,
82 .canvas_pixels = canvas_pixels,
83 .canvas_rgba8_bytes = canvas_rgba8_bytes,
84 .frames = frame_count,
85 .frame_pixels = frame_pixels,
86 .compressed_bytes = compressed_bytes,
87 .retained_rgba8_bytes = retained_rgba8_bytes,
88 };
89 }
90
91 pub fn exactBounds(self: Plan) Bounds {
92 return .{
93 .canvas_pixels = self.canvas_pixels,
94 .frame_pixels = self.frame_pixels,
95 .frames = self.frames,
96 .compressed_bytes = self.compressed_bytes,
97 .retained_rgba8_bytes = self.retained_rgba8_bytes,
98 };
99 }
100 };
101
102 fn multiplied(left: anytype, right: anytype) error{CapacityOverflow}!usize {
103 return std.math.mul(usize, @intCast(left), @intCast(right)) catch error.CapacityOverflow;
104 }