commit 68e5c7f42a759715099af431eb28a1b76cb93db0
parent 0e1cb264983a6d6e7d5dfabb6290f2d299cbd8b0
Author: Christian Ermann <christian.ermann@joescan.com>
Date: Sat, 16 Aug 2025 15:07:28 -0700
Add offset table and pixel data parsing
Diffstat:
| M | src/exr.zig | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
1 file changed, 84 insertions(+), 2 deletions(-)
diff --git a/src/exr.zig b/src/exr.zig
@@ -107,9 +107,15 @@ const Box2f = packed struct {
const Bytes = struct {
};
+const PixelType = enum(u32) {
+ uint = 0,
+ half = 1,
+ float = 2,
+};
+
const Channel = struct {
name: String,
- pixel_type: u32,
+ pixel_type: PixelType,
p_linear: u8,
reserved: [3]u8 = .{ 0, 0, 0 },
x_sampling: u32,
@@ -120,7 +126,8 @@ const Channel = struct {
if (name_string.len == 0) {
return null;
}
- const pixel_type = try reader.readInt(u32, .little);
+ const pixel_type_int = try reader.readInt(u32, .little);
+ const pixel_type = @as(PixelType, @enumFromInt(pixel_type_int));
const p_linear = try reader.readInt(u8, .little);
try reader.skipBytes(3, .{});
const x_sampling = try reader.readInt(u32, .little);
@@ -481,6 +488,22 @@ const Header = struct {
}
};
+fn scanLinesPerBlock(compression: Compression) u32 {
+ return switch (compression) {
+ .none => 1,
+ .rle => 1,
+ .zips => 1,
+ .zip => 16,
+ .piz => 32,
+ .pxr24 => 16,
+ .b44 => 32,
+ .b44a => 32,
+ .dwaa => 32,
+ .dwab => 256,
+ .htj2k => 256,
+ };
+}
+
pub fn loadFile(path: []const u8, allocator: std.mem.Allocator) !void {
var file = try std.fs.cwd().openFile(path, .{ .mode = .read_only });
defer file.close();
@@ -503,4 +526,63 @@ pub fn loadFile(path: []const u8, allocator: std.mem.Allocator) !void {
// TODO: multipart files have a list of headers
const header = try Header.parse(stream, allocator);
std.debug.print("header = {}\n", .{header});
+ std.debug.print("data window = {}\n", .{header.data_window});
+
+ const num_scan_lines = @as(u32, @intCast(header.data_window.y_max - header.data_window.y_min + 1));
+ const num_scan_lines_per_block = scanLinesPerBlock(header.compression);
+ const num_scan_line_blocks = num_scan_lines / num_scan_lines_per_block;
+ std.debug.print("num scan lines = {}\n", .{num_scan_lines});
+ std.debug.print("num scan lines per block = {}\n", .{num_scan_lines_per_block});
+ std.debug.print("num scan line blocks = {}\n", .{num_scan_line_blocks});
+
+ const num_pixels_per_scan_line = @as(u32, @intCast(header.data_window.x_max - header.data_window.x_min + 1));
+
+ const offsets = try allocator.alloc(u64, num_scan_line_blocks);
+ defer allocator.free(offsets);
+ for (offsets) |*offset| {
+ offset.* = try stream.readInt(u64, .little);
+ std.debug.print("offset = {}\n", .{offset.*});
+ }
+
+ const pixel_buffers = try allocator.alloc([]u8, header.channels.len);
+ for (0.., header.channels) |i, *channel| {
+ const pixel_size: u32 = switch (channel.pixel_type) {
+ .uint => 4,
+ .half => 2,
+ .float => 4,
+ };
+ const num_samples_y = num_scan_lines / channel.y_sampling;
+ const num_samples_x = num_pixels_per_scan_line / channel.x_sampling;
+ const num_samples = num_samples_x * num_samples_y;
+ const channel_size = num_samples * pixel_size;
+ const buf = try allocator.alloc(u8, channel_size);
+ pixel_buffers[i] = buf;
+ }
+
+ // can't use buffered reader when seeking (will this be changed?)
+ var source_reader = source.reader();
+ for (offsets) |offset| {
+ try source.seekTo(offset);
+ const y_coord = try source_reader.readInt(u32, .little);
+ const pixel_data_size = try source_reader.readInt(u32, .little);
+
+ for (0.., header.channels) |i, *channel| {
+ if (y_coord % channel.y_sampling != 0) {
+ continue;
+ }
+ const pixel_size: u32 = switch (channel.pixel_type) {
+ .uint => 4,
+ .half => 2,
+ .float => 4,
+ };
+ const num_samples_x = num_pixels_per_scan_line / channel.x_sampling;
+ const num_bytes = num_samples_x * pixel_size;
+ const buf_offset = (y_coord / channel.y_sampling) * num_bytes;
+ const buf = pixel_buffers[i];
+ try source_reader.readNoEof(buf[buf_offset..buf_offset + num_bytes]);
+ }
+ std.debug.print("y = {} size = {}\n", .{y_coord, pixel_data_size});
+ }
+ std.debug.print("g = {any}\n", .{std.mem.bytesAsSlice(f16, pixel_buffers[0])});
+ std.debug.print("z = {any}\n", .{std.mem.bytesAsSlice(f32, pixel_buffers[1])});
}