lib/png/src/encode/encoder.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const flate = std.compress.flate;
3 const model = @import("model.zig");
4 const plan_mod = @import("plan.zig");
5 const storage_mod = @import("storage.zig");
6
7 const ImageView = model.ImageView;
8 const Bounds = plan_mod.Bounds;
9 const Plan = plan_mod.Plan;
10 const Storage = storage_mod.Storage;
11 const Status = storage_mod.Status;
12 const Scratch = plan_mod.Scratch;
13 const Error = plan_mod.Error;
14 pub const signature = [_]u8{ 0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a };
15 const stored_zlib_header = plan_mod.stored_zlib_header;
16
17 pub const Encoding = struct {
18 storage: Storage,
19 bytes: []const u8,
20
21 pub const InitError = Storage.InitError || Error;
22
23 pub fn init(allocator: std.mem.Allocator, image: ImageView) InitError!Encoding {
24 return initWithBounds(allocator, image, try Bounds.exact(image));
25 }
26
27 pub fn initWithBounds(
28 allocator: std.mem.Allocator,
29 image: ImageView,
30 bounds: Bounds,
31 ) InitError!Encoding {
32 var scratch: Scratch = undefined;
33 var storage = try Storage.init(allocator, .{
34 .image = image,
35 .bounds = bounds,
36 .scratch = &scratch,
37 });
38 errdefer storage.deinit(allocator);
39 storage.activate();
40 const bytes = try bytesFromImage(&storage, image);
41 return .{ .storage = storage, .bytes = bytes };
42 }
43
44 pub fn status(self: *const Encoding) Status {
45 return self.storage.status();
46 }
47
48 pub fn deinit(self: *Encoding, allocator: std.mem.Allocator) void {
49 self.storage.reset();
50 self.storage.deinit(allocator);
51 self.* = undefined;
52 }
53 };
54
55 pub fn bytesFromImage(storage: *Storage, image: ImageView) Error![]const u8 {
56 const regions = try storage.acquire(image);
57 errdefer storage.reset();
58 const plan = &storage.capacity.plan;
59 var writer = Writer{ .output = regions.output };
60 try writer.slice(&signature);
61 try writeHeader(&writer, plan);
62 try writer.integer(@intCast(plan.zlib_bytes));
63 try writer.slice("IDAT");
64 const compression_buffer = regions.output[writer.index..][0 .. plan.zlib_bytes + 8];
65 const idat = try writer.region(plan.zlib_bytes);
66 switch (plan.options.compression) {
67 .compressed => try encodeCompressed(
68 compression_buffer,
69 plan.zlib_bytes,
70 image,
71 plan.color,
72 regions.window,
73 regions.raw,
74 ),
75 .stored => try encodeStored(idat, image, plan.color, regions.raw),
76 }
77 try writeCrc(&writer, "IDAT", idat);
78 try writeChunk(&writer, "IEND", &.{});
79 if (writer.index != plan.output_bytes) return error.EncodeInputMismatch;
80 return regions.output[0..writer.index];
81 }
82
83 fn writeHeader(writer: *Writer, plan: *const Plan) Error!void {
84 var ihdr: [13]u8 = undefined;
85 std.mem.writeInt(u32, ihdr[0..4], @intCast(plan.width), .big);
86 std.mem.writeInt(u32, ihdr[4..8], @intCast(plan.height), .big);
87 ihdr[8] = 8;
88 ihdr[9] = @backingInt(plan.color);
89 ihdr[10] = 0;
90 ihdr[11] = 0;
91 ihdr[12] = 0;
92 try writeChunk(writer, "IHDR", &ihdr);
93 }
94
95 fn encodeCompressed(
96 output: []u8,
97 encoded_bytes: usize,
98 image: ImageView,
99 color: model.Color,
100 window: []u8,
101 raw: []u8,
102 ) Error!void {
103 var writer = std.Io.Writer.fixed(output);
104 var compressor = try flate.Compress.init(
105 &writer,
106 window,
107 .zlib,
108 flate.Compress.Options.level_1,
109 );
110 try plan_mod.streamRaw(&compressor.writer, image, color, raw);
111 try compressor.finish();
112 if (writer.buffered().len != encoded_bytes) return error.EncodeInputMismatch;
113 }
114
115 fn encodeStored(
116 output: []u8,
117 image: ImageView,
118 color: model.Color,
119 raw: []u8,
120 ) Error!void {
121 var writer = std.Io.Writer.fixed(output);
122 try writer.writeAll(&stored_zlib_header);
123 var adler = std.hash.Adler32{};
124 var source = model.RawSource.init(image, color);
125 while (source.remaining() > 0) {
126 const block_bytes: usize = @min(source.remaining(), std.math.maxInt(u16));
127 try writeStoredBlockHeader(&writer, block_bytes == source.remaining(), block_bytes);
128 var remaining = block_bytes;
129 while (remaining > 0) {
130 const count = source.read(raw[0..@min(raw.len, remaining)]);
131 std.debug.assert(count > 0);
132 adler.update(raw[0..count]);
133 try writer.writeAll(raw[0..count]);
134 remaining -= count;
135 }
136 }
137 try writer.writeInt(u32, adler.adler, .big);
138 if (writer.buffered().len != output.len) return error.EncodeInputMismatch;
139 }
140
141 fn writeStoredBlockHeader(writer: *std.Io.Writer, final: bool, byte_count: usize) Error!void {
142 const len: u16 = @intCast(byte_count);
143 try writer.writeByte(if (final) 1 else 0);
144 try writer.writeInt(u16, len, .little);
145 try writer.writeInt(u16, ~len, .little);
146 }
147
148 fn writeChunk(writer: *Writer, chunk_type: *const [4]u8, data: []const u8) Error!void {
149 try writer.integer(@intCast(data.len));
150 try writer.slice(chunk_type);
151 try writer.slice(data);
152 try writeCrc(writer, chunk_type, data);
153 }
154
155 fn writeCrc(writer: *Writer, chunk_type: *const [4]u8, data: []const u8) Error!void {
156 var crc = std.hash.Crc32.init();
157 crc.update(chunk_type);
158 crc.update(data);
159 try writer.integer(crc.final());
160 }
161
162 const Writer = struct {
163 output: []u8,
164 index: usize = 0,
165
166 fn integer(self: *Writer, value: u32) Error!void {
167 const bytes = try self.region(@sizeOf(u32));
168 std.mem.writeInt(u32, bytes[0..4], value, .big);
169 }
170
171 fn slice(self: *Writer, values: []const u8) Error!void {
172 const destination = try self.region(values.len);
173 @memcpy(destination, values);
174 }
175
176 fn region(self: *Writer, byte_count: usize) Error![]u8 {
177 if (byte_count > self.output.len -| self.index) return error.OutputCapacityExceeded;
178 const result = self.output[self.index..][0..byte_count];
179 self.index += byte_count;
180 return result;
181 }
182 };