lib/gif/src/encode/write.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const lzw = @import("gif_lzw");
3 const model = @import("model.zig");
4 const plan_mod = @import("plan.zig");
5 const storage_mod = @import("storage.zig");
6
7 const AnimationView = model.AnimationView;
8 const Error = plan_mod.Error;
9 const FrameView = model.FrameView;
10 const Plan = plan_mod.Plan;
11 const Storage = storage_mod.Storage;
12
13 pub fn bytesFromAnimation(storage: *Storage, animation: AnimationView) Error![]const u8 {
14 const regions = try storage.acquire(animation);
15 errdefer storage.reset();
16 var writer = Writer{ .output = regions.output };
17 const plan = &storage.capacity.plan;
18 try writeHeader(&writer, plan, animation.loop_count);
19 for (animation.frames) |frame| {
20 try writeFrame(
21 &writer,
22 plan,
23 frame,
24 regions.compressed,
25 regions.dictionary,
26 );
27 }
28 try writer.byte(0x3b);
29 if (writer.index != storage.capacity.plan.output_bytes) return error.EncodeInputMismatch;
30 return regions.output[0..writer.index];
31 }
32
33 fn writeHeader(writer: *Writer, plan: *const Plan, loop_count: ?u16) Error!void {
34 try writer.slice("GIF89a");
35 try writer.word(@intCast(plan.width));
36 try writer.word(@intCast(plan.height));
37 const size_field: u8 = std.math.log2_int(u16, @intCast(plan.table_entries)) - 1;
38 try writer.byte(0xf0 | size_field);
39 try writer.slice(&.{ 0, 0 });
40 try writer.slice(plan.palette.table[0 .. plan.table_entries * 3]);
41 if (loop_count) |loop| {
42 try writer.slice(&.{ 0x21, 0xff, 0x0b });
43 try writer.slice("NETSCAPE2.0");
44 try writer.slice(&.{ 0x03, 0x01 });
45 try writer.word(loop);
46 try writer.byte(0);
47 }
48 }
49
50 fn writeFrame(
51 writer: *Writer,
52 plan: *const Plan,
53 frame: FrameView,
54 compressed: []u8,
55 dictionary: lzw.Dictionary,
56 ) Error!void {
57 try writer.slice(&.{ 0x21, 0xf9, 0x04 });
58 const transparency_flag: u8 = @intFromBool(plan.palette.transparent != null);
59 try writer.byte((2 << 2) | transparency_flag);
60 try writer.word(frame.delay_cs);
61 try writer.byte(plan.palette.transparent orelse 0);
62 try writer.byte(0);
63 try writer.byte(0x2c);
64 try writer.word(0);
65 try writer.word(0);
66 try writer.word(@intCast(plan.width));
67 try writer.word(@intCast(plan.height));
68 try writer.byte(0);
69
70 const min_code_size = plan.palette.minCodeSize();
71 try writer.byte(min_code_size);
72 const source = model.PixelSource{ .palette = &plan.palette, .rgba8 = frame.rgba8 };
73 const compressed_len = try lzw.encodeSource(min_code_size, source, compressed, dictionary);
74 var offset: usize = 0;
75 while (offset < compressed_len) {
76 const chunk: u8 = @intCast(@min(255, compressed_len - offset));
77 try writer.byte(chunk);
78 try writer.slice(compressed[offset .. offset + chunk]);
79 offset += chunk;
80 }
81 try writer.byte(0);
82 }
83
84 const Writer = struct {
85 output: []u8,
86 index: usize = 0,
87
88 fn byte(self: *Writer, value: u8) Error!void {
89 if (self.index >= self.output.len) return error.OutputCapacityExceeded;
90 self.output[self.index] = value;
91 self.index += 1;
92 }
93
94 fn slice(self: *Writer, values: []const u8) Error!void {
95 std.debug.assert(self.index <= self.output.len);
96 if (values.len > self.output.len - self.index) return error.OutputCapacityExceeded;
97 @memcpy(self.output[self.index..][0..values.len], values);
98 self.index += values.len;
99 }
100
101 fn word(self: *Writer, value: u16) Error!void {
102 try self.byte(@intCast(value & 0xff));
103 try self.byte(@intCast(value >> 8));
104 }
105 };