MiniEXR

From Unify Community Wiki
Jump to: navigation, search

Original author: Aras Pranckevicius.
Converted to C# by: Ilya Suzdalnitski.


Description

Unity doesn't have support for writing changes made to EXR files. With this small class you can write a Color[] array to a 16-bit EXR file.

Usage example

	Color[] testColors = new Color [512*512];
 
	for (int x = 0; x < 512; x++) {
		for (int y = 0; y < 512; y++) {
			testColors[y * 512 + x].r = (float)x / 512f;
			testColors[y * 512 + x].g = (float)y / 512f;
			testColors[y * 512 + x].b = ( (x&y) == 1) ? 1.0f : 0.0f; // blue Sierpinski triangle
		}
	}
 
	MiniEXR.MiniEXR.MiniEXRWrite("C:/Users/Me/Desktop/Test.exr", 512, 512, testColors);

Sourcecode

using UnityEngine;
 
// MiniEXR 2013 by Aras Pranckevicius / Unity Technologies.
//
// C# conversion by Ilya Suzdalnitski.
//
// Writes OpenEXR RGB files out of half-precision RGBA or RGB data.
//
 
namespace MiniEXR {
	  //Based on source-forge project: http://sourceforge.net/projects/csharp-half/
    internal static class HalfHelper
    {
        private static uint[] mantissaTable = GenerateMantissaTable();
        private static uint[] exponentTable = GenerateExponentTable();
        private static ushort[] offsetTable = GenerateOffsetTable();
        private static ushort[] baseTable = GenerateBaseTable();
        private static sbyte[] shiftTable = GenerateShiftTable();
 
        // Transforms the subnormal representation to a normalized one. 
        private static uint ConvertMantissa(int i)
        {
            uint m = (uint)(i << 13); // Zero pad mantissa bits
            uint e = 0; // Zero exponent
 
            // While not normalized
            while ((m & 0x00800000) == 0)
            {
                e -= 0x00800000; // Decrement exponent (1<<23)
                m <<= 1; // Shift mantissa                
            }
            m &= unchecked((uint)~0x00800000); // Clear leading 1 bit
            e += 0x38800000; // Adjust bias ((127-14)<<23)
            return m | e; // Return combined number
        }
 
        private static uint[] GenerateMantissaTable()
        {
            uint[] mantissaTable = new uint[2048];
            mantissaTable[0] = 0;
            for (int i = 1; i < 1024; i++)
            {
                mantissaTable[i] = ConvertMantissa(i);
            }
            for (int i = 1024; i < 2048; i++)
            {
                mantissaTable[i] = (uint)(0x38000000 + ((i - 1024) << 13));
            }
 
            return mantissaTable;
        }
        private static uint[] GenerateExponentTable()
        {
            uint[] exponentTable = new uint[64];
            exponentTable[0] = 0;
            for (int i = 1; i < 31; i++)
            {
                exponentTable[i] = (uint)(i << 23);
            }
            exponentTable[31] = 0x47800000;
            exponentTable[32] = 0x80000000;
            for (int i = 33; i < 63; i++)
            {
                exponentTable[i] = (uint)(0x80000000 + ((i - 32) << 23));
            }
            exponentTable[63] = 0xc7800000;
 
            return exponentTable;
        }
        private static ushort[] GenerateOffsetTable()
        {
            ushort[] offsetTable = new ushort[64];
            offsetTable[0] = 0;
            for (int i = 1; i < 32; i++)
            {
                offsetTable[i] = 1024;
            }
            offsetTable[32] = 0;
            for (int i = 33; i < 64; i++)
            {
                offsetTable[i] = 1024;
            }
 
            return offsetTable;
        }
        private static ushort[] GenerateBaseTable()
        {
            ushort[] baseTable = new ushort[512];
            for (int i = 0; i < 256; ++i)
            {
                sbyte e = (sbyte)(127 - i);
                if (e > 24)
                { // Very small numbers map to zero
                    baseTable[i | 0x000] = 0x0000;
                    baseTable[i | 0x100] = 0x8000;
                }
                else if (e > 14)
                { // Small numbers map to denorms
                    baseTable[i | 0x000] = (ushort)(0x0400 >> (18 + e));
                    baseTable[i | 0x100] = (ushort)((0x0400 >> (18 + e)) | 0x8000);
                }
                else if (e >= -15)
                { // Normal numbers just lose precision
                    baseTable[i | 0x000] = (ushort)((15 - e) << 10);
                    baseTable[i | 0x100] = (ushort)(((15 - e) << 10) | 0x8000);
                }
                else if (e > -128)
                { // Large numbers map to Infinity
                    baseTable[i | 0x000] = 0x7c00;
                    baseTable[i | 0x100] = 0xfc00;
                }
                else
                { // Infinity and NaN's stay Infinity and NaN's
                    baseTable[i | 0x000] = 0x7c00;
                    baseTable[i | 0x100] = 0xfc00;
                }
            }
 
            return baseTable;
        }
        private static sbyte[] GenerateShiftTable()
        {
            sbyte[] shiftTable = new sbyte[512];
            for (int i = 0; i < 256; ++i)
            {
                sbyte e = (sbyte)(127 - i);
                if (e > 24)
                { // Very small numbers map to zero
                    shiftTable[i | 0x000] = 24;
                    shiftTable[i | 0x100] = 24;
                }
                else if (e > 14)
                { // Small numbers map to denorms
                    shiftTable[i | 0x000] = (sbyte)(e - 1);
                    shiftTable[i | 0x100] = (sbyte)(e - 1);
                }
                else if (e >= -15)
                { // Normal numbers just lose precision
                    shiftTable[i | 0x000] = 13;
                    shiftTable[i | 0x100] = 13;
                }
                else if (e > -128)
                { // Large numbers map to Infinity
                    shiftTable[i | 0x000] = 24;
                    shiftTable[i | 0x100] = 24;
                }
                else
                { // Infinity and NaN's stay Infinity and NaN's
                    shiftTable[i | 0x000] = 13;
                    shiftTable[i | 0x100] = 13;
                }
            }
 
            return shiftTable;
        }
 
        public static float HalfToSingle(ushort half)
        {
            uint result = mantissaTable[offsetTable[half >> 10] + (half & 0x3ff)] + exponentTable[half >> 10];
 
			return System.BitConverter.ToSingle( System.BitConverter.GetBytes( result ), 0 );
 
            //return *((float*)&result);
        }
        public static ushort SingleToHalf(float single)
        {
            //uint value = *((uint*)&single);
 
			uint value = System.BitConverter.ToUInt32( System.BitConverter.GetBytes( single ), 0 );
 
            ushort result = (ushort)(baseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> shiftTable[value >> 23]));
            return result;
        }
    }
 
	public static class MiniEXR {
 
		// 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.
 
		public static void MiniEXRWrite (string _filePath, uint _width, uint _height, uint _channels, float[] _rgbaArray) {
			System.IO.File.WriteAllBytes(_filePath, MiniEXRWrite(_width, _height, _channels, _rgbaArray) );
		}
 
		public static void MiniEXRWrite (string _filePath, uint _width, uint _height, Color[] _colorArray) {
			System.IO.File.WriteAllBytes(_filePath, MiniEXRWrite(_width, _height, _colorArray) );
		}
 
		public static byte[] MiniEXRWrite (uint _width, uint _height, Color[] _colorArray) {
			float[] rgbaArray = new float[ _colorArray.Length * 3 ];
 
			for (int i = 0; i < _colorArray.Length; i++) {
				rgbaArray[i * 3] = _colorArray[i].r;
				rgbaArray[i * 3 + 1] = _colorArray[i].g;
				rgbaArray[i * 3 + 2] = _colorArray[i].b;
			}
 
			return MiniEXRWrite(_width, _height, 3, rgbaArray);
		}
 
		public static byte[] MiniEXRWrite (uint _width, uint _height, uint _channels, float[] _rgbaArray)
		{
			//const void* rgba16f
			uint ww = _width-1;
			uint hh = _height-1;
			byte[] kHeader = {
				0x76, 0x2f, 0x31, 0x01, // magic
				2, 0, 0, 0, // version, scanline
				// channels
				(byte)'c',(byte)'h',(byte)'a',(byte)'n',(byte)'n',(byte)'e',(byte)'l',(byte)'s',0,
				(byte)'c',(byte)'h',(byte)'l',(byte)'i',(byte)'s',(byte)'t',0,
				55,0,0,0,
				(byte)'B',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // R, half
				(byte)'G',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // G, half
				(byte)'R',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // B, half
				0,
				// compression
				(byte)'c',(byte)'o',(byte)'m',(byte)'p',(byte)'r',(byte)'e',(byte)'s',(byte)'s',(byte)'i',(byte)'o',(byte)'n',0,
				(byte)'c',(byte)'o',(byte)'m',(byte)'p',(byte)'r',(byte)'e',(byte)'s',(byte)'s',(byte)'i',(byte)'o',(byte)'n',0,
				1,0,0,0,
				0, // no compression
				// dataWindow
				(byte)'d',(byte)'a',(byte)'t',(byte)'a',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',0,
				(byte)'b',(byte)'o',(byte)'x',(byte)'2',(byte)'i',0,
				16,0,0,0,
				0,0,0,0,0,0,0,0,
				(byte)(ww&0xFF), (byte)((ww>>8)&0xFF), (byte)((ww>>16)&0xFF), (byte)((ww>>24)&0xFF),
				(byte)(hh&0xFF), (byte)((hh>>8)&0xFF), (byte)((hh>>16)&0xFF), (byte)((hh>>24)&0xFF),
				// displayWindow
				(byte)'d',(byte)'i',(byte)'s',(byte)'p',(byte)'l',(byte)'a',(byte)'y',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',0,
				(byte)'b',(byte)'o',(byte)'x',(byte)'2',(byte)'i',0,
				16,0,0,0,
				0,0,0,0,0,0,0,0,
				(byte)(ww&0xFF), (byte)((ww>>8)&0xFF), (byte)((ww>>16)&0xFF), (byte)((ww>>24)&0xFF),
				(byte)(hh&0xFF), (byte)((hh>>8)&0xFF), (byte)((hh>>16)&0xFF), (byte)((hh>>24)&0xFF),
				// lineOrder
				(byte)'l',(byte)'i',(byte)'n',(byte)'e',(byte)'O',(byte)'r',(byte)'d',(byte)'e',(byte)'r',0,
				(byte)'l',(byte)'i',(byte)'n',(byte)'e',(byte)'O',(byte)'r',(byte)'d',(byte)'e',(byte)'r',0,
				1,0,0,0,
				0, // increasing Y
				// pixelAspectRatio
				(byte)'p',(byte)'i',(byte)'x',(byte)'e',(byte)'l',(byte)'A',(byte)'s',(byte)'p',(byte)'e',(byte)'c',(byte)'t',(byte)'R',(byte)'a',(byte)'t',(byte)'i',(byte)'o',0,
				(byte)'f',(byte)'l',(byte)'o',(byte)'a',(byte)'t',0,
				4,0,0,0,
				0,0,0x80,0x3f, // 1.0f
				// screenWindowCenter
				(byte)'s',(byte)'c',(byte)'r',(byte)'e',(byte)'e',(byte)'n',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',(byte)'C',(byte)'e',(byte)'n',(byte)'t',(byte)'e',(byte)'r',0,
				(byte)'v',(byte)'2',(byte)'f',0,
				8,0,0,0,
				0,0,0,0, 0,0,0,0,
				// screenWindowWidth
				(byte)'s',(byte)'c',(byte)'r',(byte)'e',(byte)'e',(byte)'n',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',(byte)'W',(byte)'i',(byte)'d',(byte)'t',(byte)'h',0,
				(byte)'f',(byte)'l',(byte)'o',(byte)'a',(byte)'t',0,
				4,0,0,0,
				0,0,0x80,0x3f, // 1.0f
				// end of header
				0,
			};
 
			uint kHeaderSize = (uint)kHeader.Length;
 
			uint kScanlineTableSize = 8 * _height;
			uint pixelRowSize = _width * 3 * 2;
			uint fullRowSize = pixelRowSize + 8;
 
			uint bufSize = kHeaderSize + kScanlineTableSize + _height * fullRowSize;
 
			byte[] buf = new byte[bufSize];
 
			// copy in header
 
			int bufI = 0;
 
			for (int i = 0; i < kHeaderSize; i++) {
				buf[ bufI ] = kHeader[i];
 
				bufI++;
			}
 
			// line offset table
			uint ofs = kHeaderSize + kScanlineTableSize;
			for (int y = 0; y < _height; ++y)
			{
				buf[ bufI++ ] = (byte)(ofs & 0xFF);
				buf[ bufI++ ] = (byte)((ofs >> 8) & 0xFF);
				buf[ bufI++ ] = (byte)((ofs >> 16) & 0xFF);
				buf[ bufI++ ] = (byte)((ofs >> 24) & 0xFF);
				buf[ bufI++ ] = 0;
				buf[ bufI++ ] = 0;
				buf[ bufI++ ] = 0;
				buf[ bufI++ ] = 0;
 
				ofs += fullRowSize;
			}
 
			//Convert float to half float
			ushort[] srcHalf = new ushort[_rgbaArray.Length];
 
			for (int i = 0; i < _rgbaArray.Length; i++) {
				//Gamma encode before converting
				_rgbaArray[i] = Mathf.Pow(_rgbaArray[i], 2.2f);
				srcHalf[i] = HalfHelper.SingleToHalf( _rgbaArray[i] );
			}
 
			uint srcDataI = 0;
 
			for (int y = 0; y < _height; ++y)
			{
				// coordinate
				buf[ bufI++ ] = (byte)(y & 0xFF);
				buf[ bufI++ ] = (byte)((y >> 8) & 0xFF);
				buf[ bufI++ ] = (byte)((y >> 16) & 0xFF);
				buf[ bufI++ ] = (byte)((y >> 24) & 0xFF);
				// data size
				buf[ bufI++ ] = (byte)(pixelRowSize & 0xFF);
				buf[ bufI++ ] = (byte)((pixelRowSize >> 8) & 0xFF);
				buf[ bufI++ ] = (byte)((pixelRowSize >> 16) & 0xFF);
				buf[ bufI++ ] = (byte)((pixelRowSize >> 24) & 0xFF);
				// B, G, R
				//memcpy (ptr, src, width*6);	//Copy first line - 6 bits, 2 bits per channel
 
 
				//First copy a line of B
				uint tempSrcI = srcDataI;
				for (int x = 0; x < _width; ++x)
				{	
					//Blue
					byte[] halfBytes = System.BitConverter.GetBytes( srcHalf[ tempSrcI + 2 ] );
					buf[ bufI++ ] = halfBytes[0];
					buf[ bufI++ ] = halfBytes[1];
 
					tempSrcI += _channels;
				}
 
				//Then copy a line of G
				tempSrcI = srcDataI;
				for (int x = 0; x < _width; ++x)
				{	
					//Blue
					byte[] halfBytes = System.BitConverter.GetBytes( srcHalf[ tempSrcI + 1 ] );
					buf[ bufI++ ] = halfBytes[0];
					buf[ bufI++ ] = halfBytes[1];
 
					tempSrcI += _channels;
				}
 
				//Finally copy a line of R
				tempSrcI = srcDataI;
				for (int x = 0; x < _width; ++x)
				{	
					//Blue
					byte[] halfBytes = System.BitConverter.GetBytes( srcHalf[ tempSrcI ] );
					buf[ bufI++ ] = halfBytes[0];
					buf[ bufI++ ] = halfBytes[1];
 
					tempSrcI += _channels;
				}
 
				srcDataI += _width * _channels;
			}
 
			return buf;
		}
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox