lib/gif/src/decode/read.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const lzw = @import("gif_lzw");
3 const model = @import("model.zig");
4 const storage_mod = @import("storage.zig");
5 const stream = @import("stream.zig");
6
7 const Animation = model.Animation;
8 pub const Error = model.DecodeError || model.Exhaustion || error{CapacityOverflow} || lzw.Error;
9 const Frame = model.Frame;
10 const Storage = storage_mod.Storage;
11
12 pub fn animationFromBytes(storage: *Storage, bytes: []const u8) Error!Animation {
13 const regions = try storage.acquire(bytes);
14 errdefer storage.reset();
15
16 var reader = stream.Reader{ .bytes = bytes };
17 const header = try stream.readHeader(&reader);
18 var control = model.Control{};
19 var loop_count: ?u16 = null;
20 var frame_index: usize = 0;
21 var trailer = false;
22
23 while (!trailer) {
24 switch (try reader.byte()) {
25 0x21 => {
26 const label = try reader.byte();
27 switch (label) {
28 0xf9 => control = try stream.readControl(&reader),
29 0xff => {
30 const size = try reader.byte();
31 const app = try reader.take(size);
32 if (size == 11 and (std.mem.eql(u8, app, "NETSCAPE2.0") or std.mem.eql(u8, app, "ANIMEXTS1.0"))) {
33 loop_count = try readLoopCount(&reader) orelse loop_count;
34 } else {
35 try stream.skipSubBlocks(&reader);
36 }
37 },
38 else => try stream.skipSubBlocks(&reader),
39 }
40 },
41 0x2c => {
42 if (frame_index >= regions.frames.len) return error.FrameCapacityExceeded;
43 const snapshot = regions.retained_rgba8[frame_index * regions.canvas.len ..][0..regions.canvas.len];
44 regions.frames[frame_index] = try decodeFrame(
45 &reader,
46 header,
47 control,
48 regions.canvas,
49 regions.previous_canvas,
50 regions.indices,
51 regions.compressed,
52 snapshot,
53 );
54 frame_index += 1;
55 control = .{};
56 },
57 0x3b => trailer = true,
58 else => return error.InvalidDescriptor,
59 }
60 }
61
62 if (frame_index == 0) return error.NoFrames;
63 if (frame_index != regions.frames.len) return error.DecodeInputMismatch;
64 return .{
65 .width = header.width,
66 .height = header.height,
67 .frames = regions.frames[0..frame_index],
68 .loop_count = loop_count,
69 };
70 }
71
72 fn readLoopCount(reader: *stream.Reader) model.DecodeError!?u16 {
73 var found: ?u16 = null;
74 while (true) {
75 const size = try reader.byte();
76 if (size == 0) return found;
77 const data = try reader.take(size);
78 if (size == 3 and data[0] == 0x01) {
79 found = std.mem.readInt(u16, data[1..3], .little);
80 }
81 }
82 }
83
84 fn decodeFrame(
85 reader: *stream.Reader,
86 header: stream.Header,
87 control: model.Control,
88 canvas: []u8,
89 previous_canvas: []u8,
90 indices_storage: []u8,
91 compressed_storage: []u8,
92 snapshot: []u8,
93 ) Error!Frame {
94 const descriptor = try stream.readDescriptor(reader, header.global_table);
95 const pixel_count = descriptor.pixelCount();
96 if (pixel_count > indices_storage.len) return error.FramePixelCapacityExceeded;
97 const compressed_len = try stream.readSubBlocks(reader, compressed_storage, compressed_storage.len);
98 const indices = indices_storage[0..pixel_count];
99 const written = try lzw.decode(
100 descriptor.min_code_size,
101 compressed_storage[0..compressed_len],
102 indices,
103 );
104 if (written < pixel_count) @memset(indices[written..], 0);
105
106 if (control.disposal == .previous) @memcpy(previous_canvas, canvas);
107 var row: u32 = 0;
108 while (row < descriptor.height) : (row += 1) {
109 const canvas_y = descriptor.top + deinterlace(row, descriptor.height, descriptor.interlaced);
110 if (canvas_y >= header.height) continue;
111 var column: u32 = 0;
112 while (column < descriptor.width) : (column += 1) {
113 const canvas_x = descriptor.left + column;
114 if (canvas_x >= header.width) continue;
115 const index = indices[@as(usize, row) * descriptor.width + column];
116 if (control.transparent_index) |transparent| {
117 if (index == transparent) continue;
118 }
119 const table_offset = @as(usize, index) * 3;
120 if (table_offset + 3 > descriptor.table.len) continue;
121 const pixel = canvas[(@as(usize, canvas_y) * header.width + canvas_x) * 4 ..][0..4];
122 pixel[0] = descriptor.table[table_offset];
123 pixel[1] = descriptor.table[table_offset + 1];
124 pixel[2] = descriptor.table[table_offset + 2];
125 pixel[3] = 255;
126 }
127 }
128
129 @memcpy(snapshot, canvas);
130 switch (control.disposal) {
131 .background => clearRegion(
132 canvas,
133 header.width,
134 header.height,
135 descriptor.left,
136 descriptor.top,
137 descriptor.width,
138 descriptor.height,
139 ),
140 .previous => @memcpy(canvas, previous_canvas),
141 else => {},
142 }
143 return .{ .rgba8 = snapshot, .delay_cs = control.delay_cs };
144 }
145
146 fn deinterlace(row: u32, frame_height: u32, interlaced: bool) u32 {
147 if (!interlaced) return row;
148 const pass_1 = (frame_height + 7) / 8;
149 const pass_2 = (frame_height + 3) / 8;
150 const pass_3 = (frame_height + 1) / 4;
151 if (row < pass_1) return row * 8;
152 if (row < pass_1 + pass_2) return (row - pass_1) * 8 + 4;
153 if (row < pass_1 + pass_2 + pass_3) return (row - pass_1 - pass_2) * 4 + 2;
154 return (row - pass_1 - pass_2 - pass_3) * 2 + 1;
155 }
156
157 fn clearRegion(
158 canvas: []u8,
159 width: u32,
160 height: u32,
161 left: u32,
162 top: u32,
163 region_width: u32,
164 region_height: u32,
165 ) void {
166 var row: u32 = 0;
167 while (row < region_height) : (row += 1) {
168 const y = top + row;
169 if (y >= height) continue;
170 var column: u32 = 0;
171 while (column < region_width) : (column += 1) {
172 const x = left + column;
173 if (x >= width) continue;
174 @memset(canvas[(@as(usize, y) * width + x) * 4 ..][0..4], 0);
175 }
176 }
177 }