#[SOLVED] Reading EXR image files

9 messages · Page 1 of 1 (latest)

lusty brook
#

Hi all, I'm new to odin and also come from a mostly python based programming experience 🙂

I'm trying to understand how the OpenEXRCore library works so that I can load up some images for use with raylib. To make things simple I'm starting with a non compressed scanline exr file and just trying to get the RGB pixel values (ignorning anything else). I've got this non functional code so far, which compiles but gives a memory error "(EXR_ERR_READ_IO) Unable to read requested data: (998) Invalid access to memory location."

package main

import "core:fmt"
import exr "vendor:OpenEXRCore"

main :: proc() {
    ctxt: exr.context_t
    ctxt_initializer := exr.DEFAULT_CONTEXT_INITIALIZER

    exr_file := exr.start_read(&ctxt, "X:/tmp/text.exr", &ctxt_initializer)
    defer exr.finish(&ctxt)

    width: int = 1920
    height: int = 1080

    chunk_info: exr.chunk_info_t
    for y := 0; y < height; y += 1 {
        read_head := exr.read_scanline_chunk_info(ctxt, 0, cast(i32)y, &chunk_info)

        packed_data := make([]u8, cast(int)chunk_info.packed_size)

        read_chunk := exr.read_chunk(ctxt, 0, &chunk_info, &packed_data)

        for x := 0; x < width; x += 1 {
            r := packed_data[x]
            g := packed_data[x]
            b := packed_data[x]
            fmt.printf("Pixel (%d, %d): R=%.3f, G=%.3f, B=%.3f\n", x, y, r, g, b)
        }
    }
}

I'm sure apart from missing some basics I've probably got something wrong with how pointers work. I'd really appricate if someone can point me in the right direction!

I've made something similar in python before using OpenImageIO to read the files, PyOpenColorIO to do color space conversion, numpy for reformatting and finally PyQt to display it, but as you can imagine that is painfully slow. So I'm at the beginning of trying to make something more performant!

analog turtle
#

Dunno exr but why aren't read scanline & chunk taking pointer to ctxt?

analog turtle
#

Alright scratch that. I dunno, I think your usage of pointers is current fcom what I see. Why don't you spin up a debug session, to pinpoint the offending line?

lusty brook
#

thanks! After some debugging I think I'm starting to better understand how this package works, and the exr format. It seems that after digging down into the "chunk" there is some packed data that needs to be decoded.

After initializing the decode pipeline however I'm now getting this output:

openexrcore-3_1.lib(internal_zip.obj) : error LNK2019: unresolved external symbol compress2 referenced in function internal_exr_apply_zip
openexrcore-3_1.lib(internal_zip.obj) : error LNK2019: unresolved external symbol uncompress referenced in function internal_exr_undo_zip       
openexrcore-3_1.lib(internal_pxr24.obj) : error LNK2001: unresolved external symbol uncompress
openexrcore-3_1.lib(internal_pxr24.obj) : error LNK2019: unresolved external symbol compress referenced in function apply_pxr24_impl

So it looks like (and after some googling) I need to find a way of linking this to zlib as a required dependency, I've put zlib.lib in the same location as my .odin file and found some reference in the docs about adding that as a foreign import, but I don't think I'm using it correctly

package main

...
@(extra_linker_flags="/NODEFAULTLIB:libcmt")
foreign import lib {
    "zlib.lib",
}

main :: proc() {
    ctxt: exr.context_t
... # Changed some other things around here but eventally get to a line:
# exr.decoding_initialize(ctxt, part, &chunk_info, &decode)
lusty brook
#

oh wow importing zlib turned out to be as simple as:

import zlib "vendor:zlib"

// anything just to initalise it:
zlib.Version()

I can compile now, still trying to correctly get the pixel data but feels close! I can read header information 😄

lusty brook
#

Progress! Here is the guts of a basic working example to print RGB values (channel data needs to be set to f16 or f32 correctly).

    result = openexr.start_read(&exr_ctx, filename_cstr, &ctxtinit)
    chlist_ptr: ^openexr.attr_chlist_t = new(openexr.attr_chlist_t)
    result = openexr.get_channels(exr_ctx, 0, &chlist_ptr)
    num_parts: i32
    result = openexr.get_count(exr_ctx, &num_parts)
    data_window: openexr.attr_box2i_t
    result = openexr.get_data_window(exr_ctx, 0, &data_window)
    width: i32 = data_window.max.x - data_window.min.x + 1
    height: i32 = data_window.max.y - data_window.min.y + 1
    chunk_info: openexr.chunk_info_t
    decode_pipeline: openexr.decode_pipeline_t

    for y := data_window.min.y; y <= data_window.max.y; y += chunk_info.height {
        result = openexr.read_scanline_chunk_info(exr_ctx, 0, y, &chunk_info)
        is_half_float := chlist_ptr^.entries[0].pixel_type == .HALF
        if is_half_float {
            fmt.print("Input is 16bit")
            r_channel_data := make([]f16, chunk_info.width)
            g_channel_data := make([]f16, chunk_info.width)
            b_channel_data := make([]f16, chunk_info.width)

            result = openexr.decoding_initialize(exr_ctx, 0, &chunk_info, &decode_pipeline)

            decode_pipeline.channels[0].decode_to_ptr = cast(^u8)(&r_channel_data[0])
            decode_pipeline.channels[1].decode_to_ptr = cast(^u8)(&g_channel_data[0])
            decode_pipeline.channels[2].decode_to_ptr = cast(^u8)(&b_channel_data[0])

            result = openexr.decoding_choose_default_routines(exr_ctx, 0, &decode_pipeline)

            result = openexr.decoding_run(exr_ctx, 0, &decode_pipeline)

            fmt.printf("Converted to f32 - R: %f, G: %f, B: %f\n",
            f32(r_channel_data[0]),
            f32(g_channel_data[0]),
            f32(b_channel_data[0]),)

            openexr.decoding_destroy(exr_ctx, &decode_pipeline)

#

Next step to is figure out how to load that into a texture buffer for raylib!

opal mist
lusty brook
#

thanks @opal mist that's a good idea if I run into any more format specific problems! For now the original goal is DONE 🥳 and I've posted the gist in showcase! #showcase message