lib/gif/src/decode/stream.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const model = @import("model.zig");
3
4 pub const Error = model.DecodeError || error{CompressedByteCapacityExceeded};
5
6 pub const Reader = struct {
7 bytes: []const u8,
8 offset: usize = 0,
9
10 pub fn take(self: *Reader, count: usize) model.DecodeError![]const u8 {
11 if (count > self.bytes.len -| self.offset) return error.TruncatedInput;
12 const slice = self.bytes[self.offset..][0..count];
13 self.offset += count;
14 return slice;
15 }
16
17 pub fn byte(self: *Reader) model.DecodeError!u8 {
18 return (try self.take(1))[0];
19 }
20
21 pub fn word(self: *Reader) model.DecodeError!u16 {
22 const slice = try self.take(2);
23 return std.mem.readInt(u16, slice[0..2], .little);
24 }
25 };
26
27 pub const Header = struct {
28 width: u32,
29 height: u32,
30 global_table: []const u8,
31 };
32
33 pub fn readHeader(reader: *Reader) model.DecodeError!Header {
34 const header = try reader.take(6);
35 if (!std.mem.eql(u8, header[0..4], &model.signature)) return error.InvalidSignature;
36 if (!std.mem.eql(u8, header[4..6], "7a") and !std.mem.eql(u8, header[4..6], "9a")) {
37 return error.InvalidSignature;
38 }
39
40 const width: u32 = try reader.word();
41 const height: u32 = try reader.word();
42 const flags = try reader.byte();
43 _ = try reader.byte();
44 _ = try reader.byte();
45 if (width == 0 or height == 0) return error.InvalidDescriptor;
46
47 var global_table: []const u8 = &.{};
48 if (flags & 0x80 != 0) {
49 const entries = @as(usize, 2) << @intCast(flags & 0x07);
50 global_table = try reader.take(entries * 3);
51 }
52 return .{ .width = width, .height = height, .global_table = global_table };
53 }
54
55 pub const Descriptor = struct {
56 left: u32,
57 top: u32,
58 width: u32,
59 height: u32,
60 interlaced: bool,
61 table: []const u8,
62 min_code_size: u8,
63
64 pub fn pixelCount(self: Descriptor) usize {
65 return @as(usize, self.width) * @as(usize, self.height);
66 }
67 };
68
69 pub fn readDescriptor(reader: *Reader, global_table: []const u8) model.DecodeError!Descriptor {
70 const left: u32 = try reader.word();
71 const top: u32 = try reader.word();
72 const width: u32 = try reader.word();
73 const height: u32 = try reader.word();
74 const flags = try reader.byte();
75 if (width == 0 or height == 0) return error.InvalidDescriptor;
76
77 var table = global_table;
78 if (flags & 0x80 != 0) {
79 const entries = @as(usize, 2) << @intCast(flags & 0x07);
80 table = try reader.take(entries * 3);
81 }
82 if (table.len == 0) return error.MissingColorTable;
83
84 return .{
85 .left = left,
86 .top = top,
87 .width = width,
88 .height = height,
89 .interlaced = flags & 0x40 != 0,
90 .table = table,
91 .min_code_size = try reader.byte(),
92 };
93 }
94
95 pub fn skipSubBlocks(reader: *Reader) model.DecodeError!void {
96 while (true) {
97 const size = try reader.byte();
98 if (size == 0) return;
99 _ = try reader.take(size);
100 }
101 }
102
103 pub fn readSubBlocks(reader: *Reader, destination: ?[]u8, limit: usize) Error!usize {
104 var used: usize = 0;
105 while (true) {
106 const size = try reader.byte();
107 if (size == 0) return used;
108 const data = try reader.take(size);
109 if (data.len > limit -| used) return error.CompressedByteCapacityExceeded;
110 if (destination) |bytes| {
111 if (data.len > bytes.len -| used) return error.CompressedByteCapacityExceeded;
112 @memcpy(bytes[used..][0..data.len], data);
113 }
114 used += data.len;
115 }
116 }
117
118 pub fn readControl(reader: *Reader) model.DecodeError!model.Control {
119 if (try reader.byte() != 4) return error.InvalidDescriptor;
120 const data = try reader.take(4);
121 if (try reader.byte() != 0) return error.InvalidDescriptor;
122 const flags = data[0];
123 return .{
124 .disposal = @fromBackingInt(@intCast(@as(u3, @intCast((flags >> 2) & 0x07)))),
125 .delay_cs = std.mem.readInt(u16, data[1..3], .little),
126 .transparent_index = if (flags & 0x01 != 0) data[3] else null,
127 };
128 }