Skip to content

Instantly share code, notes, and snippets.

@aras-p
Created June 11, 2025 12:46
Show Gist options
  • Select an option

  • Save aras-p/5c44b0b5c93c0e4a75a3c5cd98222cc6 to your computer and use it in GitHub Desktop.

Select an option

Save aras-p/5c44b0b5c93c0e4a75a3c5cd98222cc6 to your computer and use it in GitHub Desktop.

Revisions

  1. aras-p created this gist Jun 11, 2025.
    268 changes: 268 additions & 0 deletions main.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,268 @@
    // miniexr.cpp - v0.2 - public domain - 2013 Aras Pranckevicius / Unity Technologies
    //
    // Writes OpenEXR RGB files out of half-precision RGBA or RGB data.
    //
    // Only tested on Windows (VS2008) and Mac (clang 3.3), little endian.
    // Testing status: "works for me".
    //
    // History:
    // 0.2 Source data can be RGB or RGBA now.
    // 0.1 Initial release.


    // Compile main() that writes out a test .exr file?
    #define COMPILE_TEST_MAIN_ENTRYPOINT 1


    #define _CRT_SECURE_NO_WARNINGS

    #include <assert.h>
    #include <string.h>
    #include <stdlib.h>

    #define ARRAY_SIZE(x) sizeof(x)/sizeof(x[0])


    // Writes EXR into a memory buffer.
    // Input:
    // - (width) x (height) image,
    // - channels=4: 8 bytes per pixel (R,G,B,A order, 16 bit float per channel; alpha ignored), or
    // - channels=3: 6 bytes per pixel (R,G,B order, 16 bit float per channel).
    // Returns memory buffer with .EXR contents and buffer size in outSize. free() the buffer when done with it.
    unsigned char* miniexr_write (unsigned width, unsigned height, unsigned channels, const void* rgba16f, size_t* outSize)
    {
    const unsigned ww = width-1;
    const unsigned hh = height-1;
    const unsigned char kHeader[] = {
    0x76, 0x2f, 0x31, 0x01, // magic
    2, 0, 0, 0, // version, scanline
    // channels
    'c','h','a','n','n','e','l','s',0,
    'c','h','l','i','s','t',0,
    55,0,0,0,
    'B',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // B, half
    'G',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // G, half
    'R',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // R, half
    0,
    // compression
    'c','o','m','p','r','e','s','s','i','o','n',0,
    'c','o','m','p','r','e','s','s','i','o','n',0,
    1,0,0,0,
    0, // no compression
    // dataWindow
    'd','a','t','a','W','i','n','d','o','w',0,
    'b','o','x','2','i',0,
    16,0,0,0,
    0,0,0,0,0,0,0,0,
    ww&0xFF, (ww>>8)&0xFF, (ww>>16)&0xFF, (ww>>24)&0xFF,
    hh&0xFF, (hh>>8)&0xFF, (hh>>16)&0xFF, (hh>>24)&0xFF,
    // displayWindow
    'd','i','s','p','l','a','y','W','i','n','d','o','w',0,
    'b','o','x','2','i',0,
    16,0,0,0,
    0,0,0,0,0,0,0,0,
    ww&0xFF, (ww>>8)&0xFF, (ww>>16)&0xFF, (ww>>24)&0xFF,
    hh&0xFF, (hh>>8)&0xFF, (hh>>16)&0xFF, (hh>>24)&0xFF,
    // lineOrder
    'l','i','n','e','O','r','d','e','r',0,
    'l','i','n','e','O','r','d','e','r',0,
    1,0,0,0,
    0, // increasing Y
    // pixelAspectRatio
    'p','i','x','e','l','A','s','p','e','c','t','R','a','t','i','o',0,
    'f','l','o','a','t',0,
    4,0,0,0,
    0,0,0x80,0x3f, // 1.0f
    // screenWindowCenter
    's','c','r','e','e','n','W','i','n','d','o','w','C','e','n','t','e','r',0,
    'v','2','f',0,
    8,0,0,0,
    0,0,0,0, 0,0,0,0,
    // screenWindowWidth
    's','c','r','e','e','n','W','i','n','d','o','w','W','i','d','t','h',0,
    'f','l','o','a','t',0,
    4,0,0,0,
    0,0,0x80,0x3f, // 1.0f
    // end of header
    0,
    };
    const size_t kHeaderSize = ARRAY_SIZE(kHeader);

    const size_t kScanlineTableSize = 8 * height;
    const size_t pixelRowSize = width * 3 * 2;
    const size_t fullRowSize = pixelRowSize + 8;

    size_t bufSize = kHeaderSize + kScanlineTableSize + height * fullRowSize;
    unsigned char* buf = (unsigned char*)malloc (bufSize);
    if (!buf)
    return NULL;

    // copy in header
    memcpy (buf, kHeader, kHeaderSize);

    // line offset table
    size_t ofs = kHeaderSize + kScanlineTableSize;
    unsigned char* ptr = buf + kHeaderSize;
    for (unsigned y = 0; y < height; ++y)
    {
    *ptr++ = ofs & 0xFF;
    *ptr++ = (ofs >> 8) & 0xFF;
    *ptr++ = (ofs >> 16) & 0xFF;
    *ptr++ = (ofs >> 24) & 0xFF;
    *ptr++ = 0;
    *ptr++ = 0;
    *ptr++ = 0;
    *ptr++ = 0;
    ofs += fullRowSize;
    }

    // scanline data
    const unsigned char* src = (const unsigned char*)rgba16f;
    const unsigned stride = channels * 2;
    for (unsigned y = 0; y < height; ++y)
    {
    // coordinate
    *ptr++ = y & 0xFF;
    *ptr++ = (y >> 8) & 0xFF;
    *ptr++ = (y >> 16) & 0xFF;
    *ptr++ = (y >> 24) & 0xFF;
    // data size
    *ptr++ = pixelRowSize & 0xFF;
    *ptr++ = (pixelRowSize >> 8) & 0xFF;
    *ptr++ = (pixelRowSize >> 16) & 0xFF;
    *ptr++ = (pixelRowSize >> 24) & 0xFF;
    // B, G, R
    const unsigned char* chsrc;
    chsrc = src + 4;
    for (unsigned x = 0; x < width; ++x)
    {
    *ptr++ = chsrc[0];
    *ptr++ = chsrc[1];
    chsrc += stride;
    }
    chsrc = src + 2;
    for (unsigned x = 0; x < width; ++x)
    {
    *ptr++ = chsrc[0];
    *ptr++ = chsrc[1];
    chsrc += stride;
    }
    chsrc = src + 0;
    for (unsigned x = 0; x < width; ++x)
    {
    *ptr++ = chsrc[0];
    *ptr++ = chsrc[1];
    chsrc += stride;
    }

    src += width * stride;
    }

    assert (ptr == buf + bufSize);

    *outSize = bufSize;
    return buf;
    }



    #if COMPILE_TEST_MAIN_ENTRYPOINT

    #include <stdio.h>
    #include <math.h>

    static unsigned short FloatToHalf (float f)
    {
    union convertf2i {
    unsigned int i;
    float f;
    };
    convertf2i f2i;
    f2i.f = f;
    unsigned int i = f2i.i;

    if (i==0)
    return 0;

    // Not robust at handling denormals, infinities, ...
    return ((i>>16)&0x8000)|((((i&0x7f800000)-0x38000000)>>13)&0x7c00)|((i>>13)&0x03ff);
    }

    int main()
    {
    const int kW = 480;
    const int kH = 270;
    unsigned short rgba[kW*kH*4];
    unsigned ofs = 0;
    const float kGamma = 2.2f;
    for (int y = 0; y < kH; ++y)
    {
    for (int x = 0; x < kW; ++x)
    {
    int tilex = x/120;
    int tiley = y/135;
    int tile = tiley * 4 + tilex;
    float fx = (x - tilex*120)/119.0f;
    float fy = (y - tiley*135)/134.0f;
    float r = 0, g = 0, b = 0;

    switch (tile) {
    case 0: // linear 0..10 gray gradient
    r = g = b = fx * 10.0f;
    break;
    case 4: // linear 0..1 gray gradient
    r = g = b = fx * 1.0f;
    break;
    case 1: // linear color gradient
    r = fx * 10.0f;
    g = (1.0f-fx) * 10.0f;
    b = fy * 10.0f;
    break;
    case 5: // linear color gradient
    r = fx * 1.0f;
    g = (1.0f-fx) * 1.0f;
    b = fy;
    break;
    case 2: // HDR yellow
    r = 6.0f;
    g = 4.0f;
    b = 1.0f;
    break;
    case 6: // LDR yellow
    r = 6.0f/6.0f;
    g = 4.0f/6.0f;
    b = 1.0f/6.0f;
    break;
    case 3: // HDR blue
    r = 0.2f;
    g = 1.0f;
    b = 2.0f;
    break;
    case 7: // LDR blue
    r = 0.2f/2.0f;
    g = 1.0f/2.0f;
    b = 2.0f/2.0f;
    break;
    }

    //rgba[ofs++] = FloatToHalf(powf(x*2.0f/kW,kGamma)); // horizontal red gradient
    //rgba[ofs++] = FloatToHalf(powf(y*2.0f/kH,kGamma)); // vertical green gradient
    //rgba[ofs++] = FloatToHalf((x&y) ? 1.0f : 0.0f); // blue Sierpinski triangle
    rgba[ofs++] = FloatToHalf(r);
    rgba[ofs++] = FloatToHalf(g);
    rgba[ofs++] = FloatToHalf(b);
    rgba[ofs++] = 0;
    }
    }

    size_t exrSize;
    unsigned char* exr = miniexr_write (kW, kH, 4, rgba, &exrSize);
    FILE* f = fopen ("test.exr", "wb");
    fwrite (exr, 1, exrSize, f);
    fclose (f);
    free (exr);

    return 0;
    }

    #endif