lib/gif/src/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const gif = @import("root.zig");
  3 
  4 const DecodeBounds = gif.DecodeBounds;
  5 const DecodePlan = gif.DecodePlan;
  6 const DecodeStorage = gif.DecodeStorage;
  7 const Animation = gif.Animation;
  8 const Error = gif.Error;
  9 const animationFromBytes = gif.animationFromBytes;
 10 const AnimationView = gif.AnimationView;
 11 const FrameView = gif.FrameView;
 12 const EncodeStorage = gif.EncodeStorage;
 13 const EncodeScratch = gif.EncodeScratch;
 14 const bytesFromAnimation = gif.bytesFromAnimation;
 15 
 16 test "gif package namespace" {
 17     std.testing.refAllDecls(gif);
 18     _ = @import("decode/test.zig");
 19     _ = @import("encode/test.zig");
 20 }
 21 
 22 const witness_static = [_]u8{
 23     0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x02, 0x00, 0x02, 0x00, 0xf1, 0x03, 0x00, 0xff, 0x00, 0x00,
 24     0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00,
 25     0x00, 0x21, 0xff, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x6b, 0x0e,
 26     0x67, 0x61, 0x6d, 0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x34, 0x35, 0x00, 0x2c,
 27     0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x02, 0x03, 0x44, 0x34, 0x05, 0x00, 0x3b,
 28 };
 29 
 30 const witness_animation = [_]u8{
 31     0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x02, 0x00, 0x01, 0x00, 0xf0, 0x00, 0x00, 0xff, 0x00, 0x00,
 32     0x00, 0x00, 0x00, 0x21, 0xff, 0x0b, 0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e,
 33     0x30, 0x03, 0x01, 0x02, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x21, 0xff,
 34     0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x6b, 0x0e, 0x67, 0x61, 0x6d,
 35     0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x34, 0x35, 0x00, 0x2c, 0x00, 0x00, 0x00,
 36     0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x04, 0x0a, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x19,
 37     0x00, 0x00, 0x00, 0x21, 0xff, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63,
 38     0x6b, 0x0e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x34, 0x35,
 39     0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0xff, 0x00, 0x00,
 40     0x00, 0x02, 0x02, 0x04, 0x0a, 0x00, 0x3b,
 41 };
 42 
 43 const test_decode_bounds = DecodeBounds{
 44     .canvas_pixels = 16,
 45     .frame_pixels = 16,
 46     .frames = 4,
 47     .compressed_bytes = 64,
 48     .retained_rgba8_bytes = 256,
 49 };
 50 
 51 const TestDecode = struct {
 52     storage: DecodeStorage,
 53     animation: Animation,
 54 
 55     fn init(allocator: std.mem.Allocator, bytes: []const u8) !TestDecode {
 56         var storage = try DecodeStorage.init(allocator, .{
 57             .bytes = bytes,
 58             .bounds = test_decode_bounds,
 59         });
 60         errdefer storage.deinit(allocator);
 61         storage.activate();
 62         const animation = try animationFromBytes(&storage, bytes);
 63         return .{ .storage = storage, .animation = animation };
 64     }
 65 
 66     fn deinit(self: *TestDecode, allocator: std.mem.Allocator) void {
 67         self.storage.reset();
 68         self.storage.deinit(allocator);
 69         self.* = undefined;
 70     }
 71 };
 72 
 73 test "decodes an ImageMagick static witness" {
 74     const allocator = std.testing.allocator;
 75     var decoded = try TestDecode.init(allocator, &witness_static);
 76     defer decoded.deinit(allocator);
 77     const animation = decoded.animation;
 78 
 79     try std.testing.expectEqual(@as(u32, 2), animation.width);
 80     try std.testing.expectEqual(@as(u32, 2), animation.height);
 81     try std.testing.expectEqual(@as(usize, 1), animation.frames.len);
 82     try std.testing.expectEqual(@as(?u16, null), animation.loop_count);
 83     try std.testing.expectEqualSlices(u8, &.{
 84         255, 0, 0,   255, 0,   255, 0,   255,
 85         0,   0, 255, 255, 255, 255, 255, 255,
 86     }, animation.frames[0].rgba8);
 87 }
 88 
 89 test "decodes an ImageMagick animation witness" {
 90     const allocator = std.testing.allocator;
 91     var decoded = try TestDecode.init(allocator, &witness_animation);
 92     defer decoded.deinit(allocator);
 93     const animation = decoded.animation;
 94 
 95     try std.testing.expectEqual(@as(u32, 2), animation.width);
 96     try std.testing.expectEqual(@as(u32, 1), animation.height);
 97     try std.testing.expectEqual(@as(usize, 2), animation.frames.len);
 98     try std.testing.expectEqual(@as(?u16, 2), animation.loop_count);
 99     try std.testing.expectEqual(@as(u16, 25), animation.frames[0].delay_cs);
100     try std.testing.expectEqual(@as(u16, 25), animation.frames[1].delay_cs);
101     try std.testing.expectEqualSlices(u8, &.{ 255, 0, 0, 255, 255, 0, 0, 255 }, animation.frames[0].rgba8);
102     try std.testing.expectEqualSlices(u8, &.{ 0, 0, 255, 255, 0, 0, 255, 255 }, animation.frames[1].rgba8);
103 }
104 
105 test "rejects non-gif bytes" {
106     try std.testing.expectError(Error.InvalidSignature, DecodePlan.inspect("PNG then some", test_decode_bounds));
107     try std.testing.expectError(Error.TruncatedInput, DecodePlan.inspect(witness_static[0..20], test_decode_bounds));
108 }
109 
110 test "encoding the decoded ImageMagick witness reproduces its frames" {
111     const allocator = std.testing.allocator;
112     var original_decode = try TestDecode.init(allocator, &witness_animation);
113     defer original_decode.deinit(allocator);
114     const original = original_decode.animation;
115 
116     var views = try allocator.alloc(FrameView, original.frames.len);
117     defer allocator.free(views);
118     for (original.frames, 0..) |frame, i| {
119         views[i] = .{ .rgba8 = frame.rgba8, .delay_cs = frame.delay_cs };
120     }
121 
122     const view = AnimationView{
123         .width = original.width,
124         .height = original.height,
125         .frames = views,
126         .loop_count = original.loop_count,
127     };
128     var scratch: EncodeScratch = undefined;
129     var encode_storage = try EncodeStorage.init(allocator, .{
130         .animation = view,
131         .bounds = .{
132             .canvas_pixels = @as(usize, original.width) * @as(usize, original.height),
133             .frames = original.frames.len,
134             .palette_entries = 256,
135         },
136         .scratch = &scratch,
137     });
138     defer encode_storage.deinit(allocator);
139     encode_storage.activate();
140     const bytes = try bytesFromAnimation(&encode_storage, view);
141     defer encode_storage.reset();
142 
143     var reencoded_decode = try TestDecode.init(allocator, bytes);
144     defer reencoded_decode.deinit(allocator);
145     const reencoded = reencoded_decode.animation;
146 
147     try std.testing.expectEqual(original.width, reencoded.width);
148     try std.testing.expectEqual(original.height, reencoded.height);
149     try std.testing.expectEqual(original.loop_count, reencoded.loop_count);
150     try std.testing.expectEqual(original.frames.len, reencoded.frames.len);
151     for (original.frames, reencoded.frames) |expected, actual| {
152         try std.testing.expectEqual(expected.delay_cs, actual.delay_cs);
153         try std.testing.expectEqualSlices(u8, expected.rgba8, actual.rgba8);
154     }
155 }
156 
157 test "GIF root composes caller-selected decode bounds and storage" {
158     comptime {
159         @stardustClaim(
160             @import("alloc_phase").capacity.witness(@import("./decode/root.zig").Storage, "gif_decode_root"),
161             null,
162             null,
163             null,
164             null,
165             null,
166             null,
167         );
168     }
169 
170     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
171     const allocator = counting.allocator();
172     const plan = try DecodePlan.inspect(&witness_animation, test_decode_bounds);
173     var storage = try DecodeStorage.init(allocator, .{
174         .bytes = &witness_animation,
175         .bounds = plan.exactBounds(),
176     });
177     defer storage.deinit(allocator);
178     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
179     try std.testing.expectEqual(storage.status().storage_bytes, counting.allocated_bytes);
180     try std.testing.expectEqual(plan.canvas_pixels, storage.status().canvas_pixels);
181     try std.testing.expectEqual(plan.frames, storage.status().frames);
182     storage.activate();
183     const animation = try animationFromBytes(&storage, &witness_animation);
184     defer storage.reset();
185     try std.testing.expectEqual(plan.width, animation.width);
186     try std.testing.expectEqual(plan.height, animation.height);
187 }
188 
189 test "GIF root composes caller-selected encode bounds and storage" {
190     comptime {
191         @stardustClaim(
192             @import("alloc_phase").capacity.witness(@import("./encode/root.zig").Storage, "gif_encode_root"),
193             null,
194             null,
195             null,
196             null,
197             null,
198             null,
199         );
200     }
201 
202     const rgba = [_]u8{
203         251, 73, 52, 255,
204         0,   0,  0,  0,
205     };
206     const animation = AnimationView{
207         .width = 2,
208         .height = 1,
209         .frames = &.{.{ .rgba8 = &rgba }},
210     };
211     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
212     var scratch: EncodeScratch = undefined;
213     var storage = try EncodeStorage.init(counting.allocator(), .{
214         .animation = animation,
215         .bounds = .{ .canvas_pixels = 2, .frames = 1, .palette_entries = 2 },
216         .scratch = &scratch,
217     });
218     defer storage.deinit(counting.allocator());
219     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
220     storage.activate();
221     const bytes = try bytesFromAnimation(&storage, animation);
222     defer storage.reset();
223     try std.testing.expectEqualSlices(u8, "GIF89a", bytes[0..6]);
224     try std.testing.expectEqual(storage.status().output_bytes, bytes.len);
225 }