summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/jpg/frame.zig50
-rw-r--r--src/jpg/huffman-table.zig48
-rw-r--r--src/jpg/quantization-table.zig48
-rw-r--r--src/jpg/root.zig4
-rw-r--r--src/jpg/scan.zig56
-rw-r--r--src/jpg/segment.zig149
6 files changed, 326 insertions, 29 deletions
diff --git a/src/jpg/frame.zig b/src/jpg/frame.zig
new file mode 100644
index 0000000..35a6e8e
--- /dev/null
+++ b/src/jpg/frame.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+
+const Self = @This();
+
+pub const Component = packed struct(u24) {
+ identifier: u8,
+ horizontal_sampling_factor: u4,
+ vertical_sampling_factor: u4,
+ quantization_table_destination_selector: u8,
+};
+
+index: usize,
+sample_precision: u8,
+number_of_lines: u16,
+samples_per_line: u16,
+components: []Component,
+
+
+pub fn read(
+ gpa: std.mem.Allocator,
+ index: usize,
+ reader: *std.Io.Reader,
+) !Self {
+ var self: Self = undefined;
+
+ self.index = index;
+ self.sample_precision = try reader.takeByte();
+ self.number_of_lines = try reader.takeInt(u16, .big);
+ self.samples_per_line = try reader.takeInt(u16, .big);
+
+ const number_of_components = try reader.takeByte();
+
+ self.components = try gpa.alloc(Component, number_of_components);
+ errdefer gpa.free(self.components);
+
+ for (self.components) |*component| {
+ component.identifier = try reader.takeByte();
+ const sampling = try reader.takeByte();
+ component.horizontal_sampling_factor = @truncate(sampling >> 4);
+ component.vertical_sampling_factor = @truncate(sampling);
+ component.quantization_table_destination_selector = try reader.takeByte();
+ }
+
+ return self;
+}
+
+pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
+ gpa.free(self.components);
+ self.* = undefined;
+}
diff --git a/src/jpg/huffman-table.zig b/src/jpg/huffman-table.zig
new file mode 100644
index 0000000..8d653ab
--- /dev/null
+++ b/src/jpg/huffman-table.zig
@@ -0,0 +1,48 @@
+const std = @import("std");
+
+const Self = @This();
+
+class: u4,
+destination_identifier: u4,
+codes_count: [16]u8,
+values: [16][]u8,
+
+pub fn read(gpa: std.mem.Allocator, reader: *std.Io.Reader) !Self {
+ const kind = try reader.takeByte();
+ var self: Self = undefined;
+
+ self.class = @truncate(kind >> 4);
+ self.destination_identifier = @truncate(kind);
+
+ for (self.codes_count[0..]) |*count| {
+ count.* = try reader.takeByte();
+ }
+
+ for (self.values[0..], self.codes_count) |*values, count| {
+ values.* = try gpa.alloc(u8, count);
+
+ for (values.*) |*value| {
+ value.* = try reader.takeByte();
+ }
+ }
+
+ return self;
+}
+
+pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
+ for (self.values) |value| {
+ gpa.free(value);
+ }
+
+ self.* = undefined;
+}
+
+pub fn size_in_file(self: Self) usize {
+ var size: usize = 17;
+
+ for (self.codes_count) |count| {
+ size += count;
+ }
+
+ return size;
+}
diff --git a/src/jpg/quantization-table.zig b/src/jpg/quantization-table.zig
new file mode 100644
index 0000000..c4c22d5
--- /dev/null
+++ b/src/jpg/quantization-table.zig
@@ -0,0 +1,48 @@
+const std = @import("std");
+
+const Self = @This();
+
+pub const Precision = enum(u1) {
+ b8 = 0,
+ b16 = 1,
+};
+
+const Header = packed struct(u8) {
+ identifier: u4,
+ precision: u4,
+};
+
+precision: Precision,
+identifier: u2,
+elements: union(Precision) {
+ b8: [64]u8,
+ b16: [64]u16,
+},
+
+pub fn read(reader: *std.Io.Reader) !Self {
+ const header: Header = @bitCast(try reader.takeByte());
+ var self: Self = undefined;
+ self.identifier = @truncate(header.identifier);
+ self.precision = @enumFromInt(@as(u1, @truncate(header.precision)));
+
+ switch (self.precision) {
+ .b8 => {
+ self.elements = .{ .b8 = undefined };
+ for (&self.elements.b8) |*element| {
+ element.* = try reader.takeByte();
+ }
+ },
+ .b16 => {
+ self.elements = .{ .b16 = undefined };
+ for (&self.elements.b16) |*element| {
+ element.* = try reader.takeInt(u16, .big);
+ }
+ },
+ }
+
+ return self;
+}
+
+pub fn size_in_file(self: Self) usize {
+ return 65 + 64 * @as(usize, @intFromEnum(self.precision));
+}
diff --git a/src/jpg/root.zig b/src/jpg/root.zig
index 6cde610..8983d35 100644
--- a/src/jpg/root.zig
+++ b/src/jpg/root.zig
@@ -3,6 +3,10 @@ const std = @import("std");
pub const Marker = @import("marker.zig").Marker;
pub const Segment = @import("segment.zig").Segment;
pub const Jfif = @import("jfif.zig");
+pub const QuantizationTable = @import("quantization-table.zig");
+pub const Frame = @import("frame.zig");
+pub const HuffmanTable = @import("huffman-table.zig");
+pub const Scan = @import("scan.zig");
test {
_ = std.testing.refAllDecls(@This());
diff --git a/src/jpg/scan.zig b/src/jpg/scan.zig
new file mode 100644
index 0000000..d47b453
--- /dev/null
+++ b/src/jpg/scan.zig
@@ -0,0 +1,56 @@
+const std = @import("std");
+
+const Self = @This();
+
+pub const Component = struct {
+ selector: u8,
+ dc_selector: u4,
+ ac_selector: u4,
+};
+
+components: []Component,
+selection_start: u8,
+selection_end: u8,
+params: packed union {
+ approximation_bit_position: u8,
+ point_transform: u4,
+},
+data: []u8,
+
+pub fn read(gpa: std.mem.Allocator, reader: *std.Io.Reader) !Self {
+ const count = try reader.takeByte();
+ var self: Self = undefined;
+
+ self.components = try gpa.alloc(Component, count);
+ errdefer gpa.free(self.components);
+
+ for (self.components) |*component| {
+ component.selector = try reader.takeByte();
+ const entropy_selector = try reader.takeByte();
+ component.dc_selector = @truncate(entropy_selector >> 4);
+ component.ac_selector = @truncate(entropy_selector);
+ }
+
+ self.selection_start = try reader.takeByte();
+ self.selection_end = try reader.takeByte();
+ self.params = @bitCast(try reader.takeByte());
+
+ var aw: std.Io.Writer.Allocating = .init(gpa);
+
+ var size = try reader.streamDelimiter(&aw.writer, 0xff);
+ while ((try reader.peek(2))[1] == 0) {
+ try aw.writer.writeByte(0xff);
+ reader.toss(2);
+ size = try reader.streamDelimiter(&aw.writer, 0xff);
+ }
+
+ self.data = try aw.toOwnedSlice();
+
+ return self;
+}
+
+pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
+ gpa.free(self.components);
+ gpa.free(self.data);
+ self.* = undefined;
+}
diff --git a/src/jpg/segment.zig b/src/jpg/segment.zig
index 9b55780..273f63d 100644
--- a/src/jpg/segment.zig
+++ b/src/jpg/segment.zig
@@ -6,23 +6,15 @@ pub const Segment = union(enum) {
reset: void,
start_of_image: void,
end_of_image: void,
- start_of_frame: void,
- define_quantization_table: void,
- define_huffman_table: void,
- define_restart_interval: void,
- 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,
+ frame: jpg.Frame,
+ quantization_tables: [4]?jpg.QuantizationTable,
+ huffman_tables: []jpg.HuffmanTable,
+ scan: jpg.Scan,
const Self = @This();
pub fn read(gpa: std.mem.Allocator, reader: *std.Io.Reader) !Self {
- _ = gpa;
-
if (try reader.peekByte() != 0xff) {
return error.InvalidMarkerHeader;
}
@@ -37,35 +29,134 @@ pub const Segment = union(enum) {
.reset => .{ .reset = void{} },
.start_of_image => .{ .start_of_image = void{} },
.end_of_image => .{ .end_of_image = 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,
+ .application_segment => |n| read_application_segment(reader, n),
+ .define_quantization_table => dqt: {
+ var size = try reader.takeInt(u16, .big) - 2;
+ var tables: [4]?jpg.QuantizationTable = .{ null, null, null, null };
+
+ while (size > 0) {
+ const table = try jpg.QuantizationTable.read(reader);
+ tables[table.identifier] = table;
+ size -= @truncate(table.size_in_file());
+ }
+
+ break :dqt .{ .quantization_tables = tables };
+ },
+ .start_of_frame => |n| sof: {
+ reader.toss(2);
+ break :sof .{ .frame = try .read(gpa, n, reader) };
+ },
+ .define_huffman_table => dhf: {
+ var size = try reader.takeInt(u16, .big) - 2;
+ var tables: std.ArrayList(jpg.HuffmanTable) = .empty;
+ errdefer tables.deinit(gpa);
+
+ while (size > 0) {
+ const table = try tables.addOne(gpa);
+ table.* = try .read(gpa, reader);
+
+ size -= @truncate(table.size_in_file());
+ }
+
+ break :dhf .{ .huffman_tables = try tables.toOwnedSlice(gpa) };
+ },
+ .start_of_scan => sos: {
+ reader.toss(2);
+ break :sos .{ .scan = try .read(gpa, reader) };
},
else => {
std.debug.panic("unimplemented tag '{s}'\n", .{@tagName(marker)});
},
};
}
+
+ fn read_application_segment(reader: *std.Io.Reader, n: usize) !Self {
+ return switch (n) {
+ 0 => jfif: {
+ reader.toss(2);
+ break :jfif .{
+ .jfif = try .read(reader)
+ };
+ },
+ else => std.debug.panic("unimplemented app{}\n", .{n}),
+ };
+ }
+
+ pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
+ switch (self.*) {
+ .frame => |*frame| frame.deinit(gpa),
+ .huffman_tables => |tables| {
+ for (tables) |*table| table.deinit(gpa);
+ gpa.free(tables);
+ },
+ .scan => |*s| s.deinit(gpa),
+ else => {},
+ }
+ }
};
-test "file" {
+test "rgb" {
const buffer = @embedFile("../testing/rgb.jpg");
var reader: std.Io.Reader = .fixed(buffer);
- var segment: Segment = try .read(std.testing.allocator, &reader);
- try std.testing.expectEqual(.start_of_image, std.meta.activeTag(segment));
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(.start_of_image, std.meta.activeTag(segment));
+ }
+
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(.jfif, std.meta.activeTag(segment));
+ }
+
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ const table = segment.quantization_tables[0].?;
+ try std.testing.expectEqual(.b8, table.precision);
+ try std.testing.expectEqual(0, table.identifier);
+ }
+
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ const table = segment.quantization_tables[1].?;
+ try std.testing.expectEqual(.b8, table.precision);
+ try std.testing.expectEqual(1, table.identifier);
+ }
+
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(0, segment.frame.index);
+ try std.testing.expectEqual(8, segment.frame.sample_precision);
+ try std.testing.expectEqual(450, segment.frame.number_of_lines);
+ try std.testing.expectEqual(600, segment.frame.samples_per_line);
+ try std.testing.expectEqual(3, segment.frame.components.len);
+
+ for (segment.frame.components, 0..) |component, index| {
+ try std.testing.expectEqual(index + 1, component.identifier);
+ }
+ }
+
+ for (0..4) |_| {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(.huffman_tables, std.meta.activeTag(segment));
+ }
- segment = try .read(std.testing.allocator, &reader);
- try std.testing.expectEqual(.jfif, std.meta.activeTag(segment));
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(.scan, std.meta.activeTag(segment));
+ }
- segment = try .read(std.testing.allocator, &reader);
- try std.testing.expectEqual(.jfif, std.meta.activeTag(segment));
+ {
+ var segment: Segment = try .read(std.testing.allocator, &reader);
+ defer segment.deinit(std.testing.allocator);
+ try std.testing.expectEqual(.end_of_image, std.meta.activeTag(segment));
+ }
}