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