diff options
| -rw-r--r-- | src/jpg/frame.zig | 50 | ||||
| -rw-r--r-- | src/jpg/huffman-table.zig | 48 | ||||
| -rw-r--r-- | src/jpg/quantization-table.zig | 48 | ||||
| -rw-r--r-- | src/jpg/root.zig | 4 | ||||
| -rw-r--r-- | src/jpg/scan.zig | 56 | ||||
| -rw-r--r-- | src/jpg/segment.zig | 149 |
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)); + } } |