diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-03-18 23:44:08 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-03-18 23:44:08 +0100 |
| commit | dd39cdcf27a4d8c2c0472f4cab2eb96fc80cf8b7 (patch) | |
| tree | c7957fb82dbd435c89b0bde44daca9fa440b7a33 | |
| parent | 1e7b589e77c7935cbaa8381f50cdcb86c85fa532 (diff) | |
jpg: add JFIF segment for APP0 marker
Automatically parse APP0 as a JFIF segment.
The following does not yet work:
* The extended JFIF segment marked by `JFXX` is not recognized.
* The thumbnail is not loaded but tossed, so it cannot be used
currently
| -rw-r--r-- | src/jpg/jfif.zig | 52 | ||||
| -rw-r--r-- | src/jpg/root.zig | 1 | ||||
| -rw-r--r-- | src/jpg/segment.zig | 34 |
3 files changed, 71 insertions, 16 deletions
diff --git a/src/jpg/jfif.zig b/src/jpg/jfif.zig new file mode 100644 index 0000000..91c95df --- /dev/null +++ b/src/jpg/jfif.zig @@ -0,0 +1,52 @@ +const std = @import("std"); + +pub const Version = packed struct { + major: u8, + minor: u8, + + pub fn format(self: @This(), writer: *std.Io.Writer) !void { + try writer.print("v{}.{}", .{self.major, self.minor}); + } +}; + +pub const Unit = enum(u8) { + none = 0, + pixels_per_inch = 1, + pixels_per_cm = 2, +}; + +const Self = @This(); + +version: Version, +unit: Unit, +density: struct { x: u16, y: u16 }, +thumbnail: struct { x: u8, y: u8 }, + +const identifier: []const u8 = "JFIF\x00"; + +pub fn read(reader: *std.Io.Reader) !Self { + const ident = try reader.peekArray(identifier.len); + + if (!std.mem.eql(u8, ident, identifier)) { + return error.WrongIdentifier; + } + reader.toss(identifier.len); + + var self: Self = undefined; + self.version.major = try reader.takeByte(); + self.version.minor = try reader.takeByte(); + + self.unit = @enumFromInt(try reader.takeByte()); + + self.density.x = try reader.takeInt(u16, .big); + self.density.y = try reader.takeInt(u16, .big); + + self.thumbnail.x = try reader.takeInt(u8, .big); + self.thumbnail.y = try reader.takeInt(u8, .big); + + // WARNING: we do not decode the thumbnail yet. + const size = 3 * self.thumbnail.x * self.thumbnail.y; + reader.toss(size); + + return self; +} diff --git a/src/jpg/root.zig b/src/jpg/root.zig index 55b37e8..6cde610 100644 --- a/src/jpg/root.zig +++ b/src/jpg/root.zig @@ -2,6 +2,7 @@ const std = @import("std"); pub const Marker = @import("marker.zig").Marker; pub const Segment = @import("segment.zig").Segment; +pub const Jfif = @import("jfif.zig"); test { _ = std.testing.refAllDecls(@This()); diff --git a/src/jpg/segment.zig b/src/jpg/segment.zig index ec1346a..9b55780 100644 --- a/src/jpg/segment.zig +++ b/src/jpg/segment.zig @@ -1,7 +1,7 @@ const std = @import("std"); const jpg = @import("root.zig"); -pub const Segment = union(jpg.Marker.Kind) { +pub const Segment = union(enum) { temporary: void, reset: void, start_of_image: void, @@ -13,6 +13,7 @@ pub const Segment = union(jpg.Marker.Kind) { define_number_of_lines: void, define_hierarchy_progression: void, extend_huffman_table: void, + jfif: jpg.Jfif, application_segment: void, comment: void, start_of_scan: void, @@ -20,6 +21,8 @@ pub const Segment = union(jpg.Marker.Kind) { const Self = @This(); pub fn read(gpa: std.mem.Allocator, reader: *std.Io.Reader) !Self { + _ = gpa; + if (try reader.peekByte() != 0xff) { return error.InvalidMarkerHeader; } @@ -34,26 +37,22 @@ pub const Segment = union(jpg.Marker.Kind) { .reset => .{ .reset = void{} }, .start_of_image => .{ .start_of_image = void{} }, .end_of_image => .{ .end_of_image = void{} }, - .start_of_frame => data: { - const buffer = try read_data_with_length(gpa, reader); - defer gpa.free(buffer); - break :data .{ .start_of_frame = void{} }; - }, - .application_segment => data: { - const buffer = try read_data_with_length(gpa, reader); - defer gpa.free(buffer); - break :data .{ .application_segment = void{} }; + .application_segment => |n| switch (n) { + 0 => jfif: { + // NOTE: we can toss the length of this segment + // since the JFIF segment has a predefined size + reader.toss(2); + break :jfif .{ + .jfif = try jpg.Jfif.read(reader) + }; + }, + else => unreachable, }, else => { std.debug.panic("unimplemented tag '{s}'\n", .{@tagName(marker)}); }, }; } - - fn read_data_with_length(gpa: std.mem.Allocator, reader: *std.Io.Reader) ![]u8 { - const length = try reader.takeInt(u16, .big); - return try reader.readAlloc(gpa, @intCast(length)); - } }; @@ -65,5 +64,8 @@ test "file" { try std.testing.expectEqual(.start_of_image, std.meta.activeTag(segment)); segment = try .read(std.testing.allocator, &reader); - try std.testing.expectEqual(.application_segment, std.meta.activeTag(segment)); + try std.testing.expectEqual(.jfif, std.meta.activeTag(segment)); + + segment = try .read(std.testing.allocator, &reader); + try std.testing.expectEqual(.jfif, std.meta.activeTag(segment)); } |