Created
June 11, 2025 12:46
-
-
Save aras-p/5c44b0b5c93c0e4a75a3c5cd98222cc6 to your computer and use it in GitHub Desktop.
HDR EXR generation 480x270
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment