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 };