exr-zig

Use EXR images
git clone git://git.electrosoup.com/exr-zig
Log | Files | Refs

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:
Msrc/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])}); }