exr.zig (18069B)
1 const std = @import("std"); 2 3 const Version = packed struct(u32) { 4 format_version: u8, 5 single_part_tiled: bool, 6 long_names: bool, 7 non_image: bool, 8 multipart: bool, 9 padding: u20, 10 11 pub fn imageType(this: *const Version) !ImageType { 12 if (this.single_part_tiled and (this.non_image or this.multipart)) { 13 return error.InvalidImageType; 14 } 15 if (this.single_part_tiled) { 16 return .single_part_tile; 17 } 18 if (this.non_image and this.multipart) { 19 return .multi_part_deep; 20 } 21 if (this.non_image) { 22 return .single_part_deep; 23 } 24 if (this.multipart) { 25 return .multi_part; 26 } 27 return .single_part_scan_line; 28 } 29 }; 30 31 const ImageType = enum { 32 single_part_scan_line, 33 single_part_tile, 34 multi_part, 35 single_part_deep, 36 multi_part_deep, 37 }; 38 39 const AttributeType = enum { 40 box2i, 41 box2f, 42 bytes, 43 chlist, 44 chromaticities, 45 compression, 46 double, 47 env_map, 48 float, 49 int, 50 key_code, 51 line_order, 52 m33f, 53 m44f, 54 preview, 55 rational, 56 string, 57 string_vector, 58 tile_desc, 59 time_code, 60 v2i, 61 v2f, 62 v3i, 63 v3f, 64 }; 65 66 const AttributeValue = union(AttributeType) { 67 box2i: Box2i, 68 box2f: Box2f, 69 bytes: Bytes, 70 chlist: []Channel, 71 chromaticities: Chromaticities, 72 compression: Compression, 73 double: f64, 74 env_map: EnvMap, 75 float: f32, 76 int: i32, 77 key_code: KeyCode, 78 line_order: LineOrder, 79 m33f: [9]f32, 80 m44f: [16]f32, 81 preview: Preview, 82 rational: Rational, 83 string: String, 84 string_vector: []String, 85 tile_desc: TileDesc, 86 time_code: TimeCode, 87 v2i: [2]i32, 88 v2f: [2]f32, 89 v3i: [3]i32, 90 v3f: [3]f32, 91 }; 92 93 const Box2i = packed struct { 94 x_min: i32, 95 y_min: i32, 96 x_max: i32, 97 y_max: i32, 98 }; 99 100 const Box2f = packed struct { 101 x_min: f32, 102 y_min: f32, 103 x_max: f32, 104 y_max: f32, 105 }; 106 107 const Bytes = struct { 108 }; 109 110 const PixelType = enum(u32) { 111 uint = 0, 112 half = 1, 113 float = 2, 114 }; 115 116 const Channel = struct { 117 name: String, 118 pixel_type: PixelType, 119 p_linear: u8, 120 reserved: [3]u8 = .{ 0, 0, 0 }, 121 x_sampling: u32, 122 y_sampling: u32, 123 124 pub fn parse(reader: anytype) !?Channel { 125 const name_string = try String.parseNullTerminated(reader); 126 if (name_string.len == 0) { 127 return null; 128 } 129 const pixel_type_int = try reader.readInt(u32, .little); 130 const pixel_type = @as(PixelType, @enumFromInt(pixel_type_int)); 131 const p_linear = try reader.readInt(u8, .little); 132 try reader.skipBytes(3, .{}); 133 const x_sampling = try reader.readInt(u32, .little); 134 const y_sampling = try reader.readInt(u32, .little); 135 return .{ 136 .name = name_string, 137 .pixel_type = pixel_type, 138 .p_linear = p_linear, 139 .x_sampling = x_sampling, 140 .y_sampling = y_sampling, 141 }; 142 } 143 }; 144 145 const Chromaticities = struct { 146 red_x: f32, 147 red_y: f32, 148 green_x: f32, 149 green_y: f32, 150 blue_x: f32, 151 blue_y: f32, 152 white_x: f32, 153 white_y: f32, 154 }; 155 156 const Compression = enum(u8) { 157 none = 0, 158 rle = 1, 159 zips = 2, 160 zip = 3, 161 piz = 4, 162 pxr24 = 5, 163 b44 = 6, 164 b44a = 7, 165 dwaa = 8, 166 dwab = 9, 167 htj2k = 10, 168 }; 169 170 const EnvMap = enum(u8) { 171 lat_long = 0, 172 cube = 1, 173 }; 174 175 const KeyCode = struct { 176 film_mfc_code: i32, 177 film_type: i32, 178 prefix: i32, 179 count: i32, 180 perf_offset: i32, 181 perfs_per_frame: i32, 182 perfs_per_count: i32, 183 }; 184 185 const LineOrder = enum(u8) { 186 increasing_y = 0, 187 decreasing_y = 1, 188 random_y = 2, 189 }; 190 191 const Preview = struct { 192 width: u32, 193 height: u32, 194 pixels: []const u8, 195 }; 196 197 const Rational = struct { 198 integer: i32, 199 fraction: u32, 200 }; 201 202 const String = struct { 203 len: u32, 204 buf: [255]u8, 205 206 pub fn parseNullTerminated(reader: anytype) !String { 207 var string = String{ .len = 0, .buf = undefined }; 208 var stream = std.io.fixedBufferStream(&string.buf); 209 const writer = stream.writer(); 210 try reader.streamUntilDelimiter(writer, 0, string.buf.len); 211 string.len = @intCast(stream.pos); 212 return string; 213 } 214 215 pub fn parseLength(reader: anytype, len: u32) !String { 216 var string = String{ .len = len, .buf = undefined }; 217 try reader.readNoEof(string.buf[0..string.len]); 218 return string; 219 } 220 221 pub fn slice(string: *const String) []const u8 { 222 return string.buf[0..string.len]; 223 } 224 }; 225 226 const LevelMode = enum(u4) { 227 one = 0, 228 mipmap = 1, 229 ripmap = 2, 230 }; 231 232 const RoundingMode = enum(u4) { 233 down = 0, 234 up = 1, 235 }; 236 237 const TileDesc = packed struct { 238 x_size: u32, 239 y_size: u32, 240 level_mode: LevelMode, 241 rounding_mode: RoundingMode, 242 }; 243 244 const TimeCode = struct { 245 time_and_flags: u32, 246 user_data: u32, 247 }; 248 249 const Attribute = struct { 250 name: String, 251 value: AttributeValue, 252 253 pub fn parse(reader: anytype, allocator: std.mem.Allocator) !?Attribute { 254 const name_string = try String.parseNullTerminated(reader); 255 if (name_string.len == 0) { 256 return null; 257 } 258 const type_string = try String.parseNullTerminated(reader); 259 const size = try reader.readInt(u32, .little); 260 const value = try Attribute.parseValue( 261 type_string.slice(), 262 size, 263 reader, 264 allocator, 265 ); 266 return .{ 267 .name = name_string, 268 .value = value, 269 }; 270 } 271 272 fn parseValue( 273 type_str: []const u8, 274 size: u32, 275 reader: anytype, 276 allocator: std.mem.Allocator, 277 ) !AttributeValue { 278 if (std.mem.eql(u8, type_str, "box2i")) { 279 const value = try reader.readStruct(Box2i); 280 return .{ .box2i = value }; 281 } 282 else if (std.mem.eql(u8, type_str, "box2f")) { 283 const value = try reader.readStruct(Box2f); 284 return .{ .box2f = value }; 285 } 286 else if (std.mem.eql(u8, type_str, "bytes")) { 287 return error.NotImplemented; 288 } 289 else if (std.mem.eql(u8, type_str, "chlist")) { 290 var list = std.ArrayList(Channel).init(allocator); 291 errdefer list.deinit(); 292 while (try Channel.parse(reader)) |*channel| { 293 try list.append(channel.*); 294 } 295 const channels = try list.toOwnedSlice(); 296 return .{ .chlist = channels }; 297 } 298 else if (std.mem.eql(u8, type_str, "chromaticities")) { 299 return error.NotImplemented; 300 } 301 else if (std.mem.eql(u8, type_str, "compression")) { 302 const value_as_int = try reader.readInt(u8, .little); 303 const value = @as(Compression, @enumFromInt(value_as_int)); 304 return .{ .compression = value }; 305 } 306 else if (std.mem.eql(u8, type_str, "double")) { 307 var bytes: [8]u8 = undefined; 308 try reader.readNoEof(&bytes); 309 const value = std.mem.bytesToValue(f32, &bytes); 310 return .{ .double = value }; 311 } 312 else if (std.mem.eql(u8, type_str, "envmap")) { 313 return error.NotImplemented; 314 } 315 else if (std.mem.eql(u8, type_str, "float")) { 316 var bytes: [4]u8 = undefined; 317 try reader.readNoEof(&bytes); 318 const value = std.mem.bytesToValue(f32, &bytes); 319 return .{ .float = value }; 320 } 321 else if (std.mem.eql(u8, type_str, "int")) { 322 return error.NotImplemented; 323 } 324 else if (std.mem.eql(u8, type_str, "keycode")) { 325 return error.NotImplemented; 326 } 327 else if (std.mem.eql(u8, type_str, "lineOrder")) { 328 const value_as_int = try reader.readInt(u8, .little); 329 const value = @as(LineOrder, @enumFromInt(value_as_int)); 330 return .{ .line_order = value }; 331 } 332 else if (std.mem.eql(u8, type_str, "m33f")) { 333 return error.NotImplemented; 334 } 335 else if (std.mem.eql(u8, type_str, "m44f")) { 336 return error.NotImplemented; 337 } 338 else if (std.mem.eql(u8, type_str, "preview")) { 339 return error.NotImplemented; 340 } 341 else if (std.mem.eql(u8, type_str, "rational")) { 342 return error.NotImplemented; 343 } 344 else if (std.mem.eql(u8, type_str, "string")) { 345 const string = try String.parseLength(reader, size); 346 return .{ .string = string }; 347 } 348 else if (std.mem.eql(u8, type_str, "stringvector")) { 349 return error.NotImplemented; 350 } 351 else if (std.mem.eql(u8, type_str, "tiledesc")) { 352 return error.NotImplemented; 353 } 354 else if (std.mem.eql(u8, type_str, "timecode")) { 355 return error.NotImplemented; 356 } 357 else if (std.mem.eql(u8, type_str, "v2i")) { 358 const value0 = try reader.readInt(i32, .little); 359 const value1 = try reader.readInt(i32, .little); 360 return .{ .v2i = .{ value0, value1 } }; 361 } 362 else if (std.mem.eql(u8, type_str, "v2f")) { 363 var bytes: [8]u8 = undefined; 364 try reader.readNoEof(&bytes); 365 const value0 = std.mem.bytesToValue(f32, &bytes[0..4]); 366 const value1 = std.mem.bytesToValue(f32, &bytes[4..8]); 367 return .{ .v2f = .{ value0, value1 } }; 368 } 369 else if (std.mem.eql(u8, type_str, "v3i")) { 370 const value0 = try reader.readInt(i32, .little); 371 const value1 = try reader.readInt(i32, .little); 372 const value2 = try reader.readInt(i32, .little); 373 return .{ .v3i = .{ value0, value1, value2 } }; 374 } 375 else if (std.mem.eql(u8, type_str, "v3f")) { 376 var bytes: [12]u8 = undefined; 377 try reader.readNoEof(&bytes); 378 const value0 = std.mem.bytesToValue(f32, &bytes[0..4]); 379 const value1 = std.mem.bytesToValue(f32, &bytes[4..8]); 380 const value2 = std.mem.bytesToValue(f32, &bytes[8..12]); 381 return .{ .v3f = .{ value0, value1, value2 } }; 382 } 383 else { 384 return error.InvalidAttributeType; 385 } 386 } 387 }; 388 389 const AttributeSet = packed struct(u32) { 390 channels: bool = false, 391 compression: bool = false, 392 data_window: bool = false, 393 display_window: bool = false, 394 line_order: bool = false, 395 pixel_aspect_ratio: bool = false, 396 screen_window_center: bool = false, 397 screen_window_width: bool = false, 398 _padding: u24 = 0, 399 }; 400 401 const required_attributes = AttributeSet{ 402 .channels = true, 403 .compression = true, 404 .data_window = true, 405 .display_window = true, 406 .line_order = true, 407 .pixel_aspect_ratio = true, 408 .screen_window_center = true, 409 .screen_window_width = true, 410 }; 411 412 // TODO: decide on a strategy for handling attributes for non-scanline types 413 const Header = struct { 414 channels: []Channel, 415 compression: Compression, 416 data_window: Box2i, 417 display_window: Box2i, 418 line_order: LineOrder, 419 pixel_aspect_ratio: f32, 420 screen_window_center: [2]f32, 421 screen_window_width: f32, 422 423 pub fn parse(reader: anytype, allocator: std.mem.Allocator) !Header { 424 var attrib_set = AttributeSet{}; 425 var header: Header = undefined; 426 while (try Attribute.parse(reader, allocator)) |*attrib| { 427 if (std.mem.eql(u8, attrib.name.slice(), "channels")) { 428 if (attrib_set.channels) { 429 return error.DuplicateAttribute; 430 } 431 header.channels = attrib.value.chlist; 432 attrib_set.channels = true; 433 } 434 else if (std.mem.eql(u8, attrib.name.slice(), "compression")) { 435 if (attrib_set.compression) { 436 return error.DuplicateAttribute; 437 } 438 header.compression = attrib.value.compression; 439 attrib_set.compression = true; 440 } 441 else if (std.mem.eql(u8, attrib.name.slice(), "dataWindow")) { 442 if (attrib_set.data_window) { 443 return error.DuplicateAttribute; 444 } 445 header.data_window = attrib.value.box2i; 446 attrib_set.data_window = true; 447 } 448 else if (std.mem.eql(u8, attrib.name.slice(), "displayWindow")) { 449 if (attrib_set.display_window) { 450 return error.DuplicateAttribute; 451 } 452 header.display_window = attrib.value.box2i; 453 attrib_set.display_window = true; 454 } 455 else if (std.mem.eql(u8, attrib.name.slice(), "lineOrder")) { 456 if (attrib_set.line_order) { 457 return error.DuplicateAttribute; 458 } 459 header.line_order = attrib.value.line_order; 460 attrib_set.line_order = true; 461 } 462 else if (std.mem.eql(u8, attrib.name.slice(), "pixelAspectRatio")) { 463 if (attrib_set.pixel_aspect_ratio) { 464 return error.DuplicateAttribute; 465 } 466 header.pixel_aspect_ratio = attrib.value.float; 467 attrib_set.pixel_aspect_ratio = true; 468 } 469 else if (std.mem.eql(u8, attrib.name.slice(), "screenWindowCenter")) { 470 if (attrib_set.screen_window_center) { 471 return error.DuplicateAttribute; 472 } 473 header.screen_window_center = attrib.value.v2f; 474 attrib_set.screen_window_center = true; 475 } 476 else if (std.mem.eql(u8, attrib.name.slice(), "screenWindowWidth")) { 477 if (attrib_set.screen_window_width) { 478 return error.DuplicateAttribute; 479 } 480 header.screen_window_width = attrib.value.float; 481 attrib_set.screen_window_width = true; 482 } 483 } 484 if (attrib_set != required_attributes) { 485 return error.MissingAttributes; 486 } 487 return header; 488 } 489 }; 490 491 fn scanLinesPerBlock(compression: Compression) u32 { 492 return switch (compression) { 493 .none => 1, 494 .rle => 1, 495 .zips => 1, 496 .zip => 16, 497 .piz => 32, 498 .pxr24 => 16, 499 .b44 => 32, 500 .b44a => 32, 501 .dwaa => 32, 502 .dwab => 256, 503 .htj2k => 256, 504 }; 505 } 506 507 pub fn loadFile(path: []const u8, allocator: std.mem.Allocator) !void { 508 var file = try std.fs.cwd().openFile(path, .{ .mode = .read_only }); 509 defer file.close(); 510 511 var source = std.io.StreamSource{ .file = file }; 512 var reader = std.io.bufferedReader(source.reader()); 513 var stream = reader.reader(); 514 515 const magic = try stream.readInt(u32, .little); 516 517 std.debug.print("magic = {}\n", .{magic}); 518 519 const version = try stream.readStruct(Version); 520 521 const image_type = version.imageType(); 522 523 std.debug.print("version = {}\n", .{version}); 524 std.debug.print("type = {any}\n", .{image_type}); 525 526 // TODO: multipart files have a list of headers 527 const header = try Header.parse(stream, allocator); 528 std.debug.print("header = {}\n", .{header}); 529 std.debug.print("data window = {}\n", .{header.data_window}); 530 531 const num_scan_lines = @as(u32, @intCast(header.data_window.y_max - header.data_window.y_min + 1)); 532 const num_scan_lines_per_block = scanLinesPerBlock(header.compression); 533 const num_scan_line_blocks = num_scan_lines / num_scan_lines_per_block; 534 std.debug.print("num scan lines = {}\n", .{num_scan_lines}); 535 std.debug.print("num scan lines per block = {}\n", .{num_scan_lines_per_block}); 536 std.debug.print("num scan line blocks = {}\n", .{num_scan_line_blocks}); 537 538 const num_pixels_per_scan_line = @as(u32, @intCast(header.data_window.x_max - header.data_window.x_min + 1)); 539 540 const offsets = try allocator.alloc(u64, num_scan_line_blocks); 541 defer allocator.free(offsets); 542 for (offsets) |*offset| { 543 offset.* = try stream.readInt(u64, .little); 544 std.debug.print("offset = {}\n", .{offset.*}); 545 } 546 547 const pixel_buffers = try allocator.alloc([]u8, header.channels.len); 548 for (0.., header.channels) |i, *channel| { 549 const pixel_size: u32 = switch (channel.pixel_type) { 550 .uint => 4, 551 .half => 2, 552 .float => 4, 553 }; 554 const num_samples_y = num_scan_lines / channel.y_sampling; 555 const num_samples_x = num_pixels_per_scan_line / channel.x_sampling; 556 const num_samples = num_samples_x * num_samples_y; 557 const channel_size = num_samples * pixel_size; 558 const buf = try allocator.alloc(u8, channel_size); 559 pixel_buffers[i] = buf; 560 } 561 562 // can't use buffered reader when seeking (will this be changed?) 563 var source_reader = source.reader(); 564 for (offsets) |offset| { 565 try source.seekTo(offset); 566 const y_coord = try source_reader.readInt(u32, .little); 567 const pixel_data_size = try source_reader.readInt(u32, .little); 568 569 for (0.., header.channels) |i, *channel| { 570 if (y_coord % channel.y_sampling != 0) { 571 continue; 572 } 573 const pixel_size: u32 = switch (channel.pixel_type) { 574 .uint => 4, 575 .half => 2, 576 .float => 4, 577 }; 578 const num_samples_x = num_pixels_per_scan_line / channel.x_sampling; 579 const num_bytes = num_samples_x * pixel_size; 580 const buf_offset = (y_coord / channel.y_sampling) * num_bytes; 581 const buf = pixel_buffers[i]; 582 try source_reader.readNoEof(buf[buf_offset..buf_offset + num_bytes]); 583 } 584 std.debug.print("y = {} size = {}\n", .{y_coord, pixel_data_size}); 585 } 586 std.debug.print("g = {any}\n", .{std.mem.bytesAsSlice(f16, pixel_buffers[0])}); 587 std.debug.print("z = {any}\n", .{std.mem.bytesAsSlice(f32, pixel_buffers[1])}); 588 }