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 }