exr-zig

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

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 }