OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / IFSEngine / Utility / OpenEXR.cs

186 lines · csharp ref:
using System.IO;
using System.Text;

namespace IFSEngine.Utility;

/// <summary>
/// Helper class for the EXR hdr image file format.
/// </summary>
/// <remarks>
/// Implementation based on OpenEXR documentation:
/// <a href="https://www.openexr.com/documentation/TechnicalIntroduction.pdf">Technical docs</a>,
/// <a href="https://www.openexr.com/documentation/openexrfilelayout.pdf">File layout</a>
/// </remarks>
public static class OpenEXR
{
    /// <summary>
    /// Writes the given 4-channel image data to the specified stream in EXR format.
    /// Only a small subset of the specification is implemented:
    /// <list type="bullet">
    /// <item> 4 channel (RGBA), 32 bit floating-point pixel data </item>
    /// <item> No compression </item>
    /// <item> Regular single-part, scan line format</item>
    /// </list>
    /// </summary>
    /// <param name="stream">An empty stream where the exr file is written to.</param>
    /// <param name="data">Image data must be passed in this specific format: [y][x][rgba].</param>
    public static void WriteStream(Stream stream, float[][][] data)
    {
        int histogramHeight = data.Length;
        int histogramWidth = data[0].Length;
        const int chnum = 4;

        using var bw = new BinaryWriter(stream);

        bw.Write(20000630);//openexr "magic number"
        //version field
        bw.Write((byte)2);//file format version
        bw.Write((byte)0);//regular single-part scan line file
        bw.Write((byte)0);//unused
        bw.Write((byte)0);//unused
        //header (sequence of attributes)
        //attribute:
        //name (string0)
        //type (string0)
        //size in bytes (int)
        //value
        {
            bw.WriteString0("channels");
            bw.WriteString0("chlist");
            const int chlist_sizeinbytes = (2 + 4 + 1 + 3 + 4 + 4) * chnum + 1;
            bw.Write(chlist_sizeinbytes);//size in bytes
            {
                //A channel
                bw.WriteString0("A");//channel name
                bw.Write(2);//type: 32-bit float
                bw.Write((byte)0);//pLinear
                bw.Write((byte)0);//reserved
                bw.Write((byte)0);
                bw.Write((byte)0);
                bw.Write(1);//xSampling (1: channel contains data for every pixel)
                bw.Write(1);//ySampling (2: channel contains data for every pixel)
            }
            {
                //B channel
                bw.WriteString0("B");//channel name
                bw.Write(2);//type: 32-bit float
                bw.Write((byte)0);//pLinear
                bw.Write((byte)0);//reserved
                bw.Write((byte)0);
                bw.Write((byte)0);
                bw.Write(1);//xSampling (1: channel contains data for every pixel)
                bw.Write(1);//ySampling (2: channel contains data for every pixel)
            }
            {
                //G channel
                bw.WriteString0("G");//channel name
                bw.Write(2);//type: 32-bit float
                bw.Write((byte)0);//pLinear
                bw.Write((byte)0);//reserved
                bw.Write((byte)0);
                bw.Write((byte)0);
                bw.Write(1);//xSampling (1: channel contains data for every pixel)
                bw.Write(1);//ySampling (2: channel contains data for every pixel)
            }
            {
                //R channel
                bw.WriteString0("R");//channel name
                bw.Write(2);//type: 32-bit float
                bw.Write((byte)0);//pLinear
                bw.Write((byte)0);//reserved
                bw.Write((byte)0);
                bw.Write((byte)0);
                bw.Write(1);//xSampling (1: channel contains data for every pixel)
                bw.Write(1);//ySampling (2: channel contains data for every pixel)
            }
            bw.Write((byte)0);//null terminator for chlist attribute value
        }
        {
            bw.WriteString0("compression");//attribute name
            bw.WriteString0("compression");//attribute type
            bw.Write(1);//(int) attribute size in bytes
            bw.Write((byte)0);//no compression
        }
        {
            bw.WriteString0("dataWindow");//attribute name
            bw.WriteString0("box2i");//attribute type
            bw.Write(4 * 4);//4 ints
            bw.Write(0);//xmin
            bw.Write(0);//ymin
            bw.Write(histogramWidth - 1);//xmax
            bw.Write(histogramHeight - 1);//ymax
        }
        {
            bw.WriteString0("displayWindow");
            bw.WriteString0("box2i");
            bw.Write(4 * 4);//4 ints
            bw.Write(0);//xmin
            bw.Write(0);//ymin
            bw.Write(histogramWidth - 1);//xmax
            bw.Write(histogramHeight - 1);//ymax
        }
        {
            bw.WriteString0("lineOrder");
            bw.WriteString0("lineOrder");
            bw.Write(1);//(int) attribute size in bytes
            bw.Write((byte)0);//INCREASING_Y
        }
        {
            bw.WriteString0("pixelAspectRatio");//attribute name
            bw.WriteString0("float");//attribute type
            bw.Write(4);//(int) attribute size in bytes
            bw.Write(1.0f);//value
        }
        {
            bw.WriteString0("screenWindowCenter");//attribute name
            bw.WriteString0("v2f");//attribute type
            bw.Write(2 * 4);//(int) attribute size in bytes
            bw.Write(0.0f);//value
            bw.Write(0.0f);//value
        }
        {
            bw.WriteString0("screenWindowWidth");//attribute name
            bw.WriteString0("float");//attribute type
            bw.Write(4);//(int) attribute size in bytes
            bw.Write(1.0f);//value
        }
        bw.Write((byte)0);//supposed to be omitted??
        //Offset tables
        ulong headerLength = (ulong)stream.Length;//number of written bytes until this point
        int scanlineDataLength = chnum * histogramWidth * 4;
        int scanlineBlockLength = 2 * 4 + scanlineDataLength;
        int offsetTableLength = histogramHeight * 8;
        //sequence of offsets 
        //offset (ulong): distance in bytes from the start of the file
        //1 offset for each scan line
        for (int scanline = 0; scanline < histogramHeight; scanline++)
            bw.Write(headerLength + (ulong)offsetTableLength + (ulong)scanline * (ulong)scanlineBlockLength);
        //Pixel data
        for (int scanline = 0; scanline < histogramHeight; scanline++)
        {
            bw.Write(scanline);//(int) y coordinate of the scanline block
            bw.Write(scanlineDataLength);//(int) data size in bytes
            //channels must be written in alphabetical order
            //channel A
            for (int x = 0; x < histogramWidth; x++)
                bw.Write(data[scanline][x][3]);
            //channel B
            for (int x = 0; x < histogramWidth; x++)
                bw.Write(data[scanline][x][2]);
            //channel G
            for (int x = 0; x < histogramWidth; x++)
                bw.Write(data[scanline][x][1]);
            //channel R
            for (int x = 0; x < histogramWidth; x++)
                bw.Write(data[scanline][x][0]);
        }
    }

    private static void WriteString0(this BinaryWriter bw, string str)
    {
        bw.Write(Encoding.ASCII.GetBytes(str));
        bw.Write((byte)0);//null terminator
    }

}