//-------------------------------------------------------------------------------------
// DirectXTexHDR.cpp
//  
// DirectX Texture Library - Radiance HDR (RGBE) file format reader/writer
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
//-------------------------------------------------------------------------------------

#include "DirectXTexp.h"

//
// In theory HDR (RGBE) Radiance files can have any of the following data orientations
//
//      +X width +Y height
//      +X width -Y height
//      -X width +Y height
//      -X width -Y height
//      +Y height +X width 
//      -Y height +X width 
//      +Y height -X width 
//      -Y height -X width 
//
// All HDR files we've encountered are always written as "-Y height +X width", so
// we support only that one as that's what other Radiance parsing code does as well.
//

//Uncomment to disable the use of adapative RLE encoding when writing an HDR. Used for testing only.
//#define DISABLE_COMPRESS

//Uncomment to use "old colors" standard RLE encoding when writing an HDR. Used for testing only.
//#define WRITE_OLD_COLORS

using namespace DirectX;

namespace
{
    const char g_Signature[] = "#?RADIANCE";
        // This is the official header signature for the .HDR (RGBE) file format.

    const char g_AltSignature[] = "#?RGBE";
        // This is a common variant header signature that is otherwise exactly the same format.

    const char g_Format[] = "FORMAT=";
    const char g_Exposure[] = "EXPOSURE=";

    const char g_sRGBE[] = "32-bit_rle_rgbe";
    const char g_sXYZE[] = "32-bit_rle_xyze";

    const char g_Header[] =
        "#?RADIANCE\n"\
        "FORMAT=32-bit_rle_rgbe\n"\
        "\n"\
        "-Y %u +X %u\n";

    inline size_t FindEOL(const char* str, size_t maxlen)
    {
        size_t pos = 0;

        while (pos < maxlen)
        {
            if (str[pos] == '\n')
                return pos;
            else if (str[pos] == '\0')
                return size_t(-1);
            ++pos;
        }

        return 0;
    }

    //-------------------------------------------------------------------------------------
    // Decodes HDR header
    //-------------------------------------------------------------------------------------
    HRESULT DecodeHDRHeader(
        _In_reads_bytes_(size) const void* pSource,
        size_t size,
        _Out_ TexMetadata& metadata,
        size_t& offset,
        float& exposure)
    {
        if (!pSource)
            return E_INVALIDARG;

        memset(&metadata, 0, sizeof(TexMetadata));

        exposure = 1.f;
        
        if (size < sizeof(g_Signature))
        {
            return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        }

        // Verify magic signature
        if (memcmp(pSource, g_Signature, sizeof(g_Signature) - 1) != 0
            && memcmp(pSource, g_AltSignature, sizeof(g_AltSignature) - 1) != 0)
        {
            return E_FAIL;
        }

        // Process first part of header
        bool formatFound = false;
        auto info = static_cast<const char*>(pSource);
        while (size > 0)
        {
            if (*info == '\n')
            {
                ++info;
                --size;
                break;
            }

            const size_t formatLen = sizeof(g_Format) - 1;
            const size_t exposureLen = sizeof(g_Exposure) - 1;
            if ((size > formatLen) && memcmp(info, g_Format, formatLen) == 0)
            {
                info += formatLen;
                size -= formatLen;

                // Trim whitespace
                while (*info == ' ' || *info == '\t')
                {
                    if (--size == 0)
                        return E_FAIL;
                    ++info;
                }

                static_assert(sizeof(g_sRGBE) == sizeof(g_sXYZE), "Format strings length mismatch");

                const size_t encodingLen = sizeof(g_sRGBE) - 1;

                if (size < encodingLen)
                {
                    return E_FAIL;
                }

                if (memcmp(info, g_sRGBE, encodingLen) != 0 && memcmp(info, g_sXYZE, encodingLen) != 0)
                {
                    return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
                }

                formatFound = true;

                size_t len = FindEOL(info, size);
                if (len == size_t(-1))
                {
                    return E_FAIL;
                }

                info += len + 1;
                size -= len + 1;
            }
            else if ((size > exposureLen) && memcmp(info, g_Exposure, exposureLen) == 0)
            {
                info += exposureLen;
                size -= exposureLen;

                // Trim whitespace
                while (*info == ' ' || *info == '\t')
                {
                    if (--size == 0)
                        return E_FAIL;
                    ++info;
                }

                size_t len = FindEOL(info, size);
                if (len == size_t(-1)
                    || len < 1)
                {
                    return E_FAIL;
                }

                char buff[32] = {};
                strncpy_s(buff, info, std::min<size_t>(31, len));

                auto newExposure = static_cast<float>(atof(buff));
                if ((newExposure >= 1e-12) && (newExposure <= 1e12))
                {
                    // Note that we ignore strange exposure values (like EXPOSURE=0)
                    exposure *= newExposure;
                }

                info += len + 1;
                size -= len + 1;
            }
            else
            {
                size_t len = FindEOL(info, size);
                if (len == size_t(-1))
                {
                    return E_FAIL;
                }

                info += len + 1;
                size -= len + 1;
            }
        }

        if (!formatFound)
        {
            return E_FAIL;
        }

        // Get orientation
        char orientation[256] = {};

        size_t len = FindEOL(info, std::min<size_t>(sizeof(orientation), size - 1));
        if (len == size_t(-1)
            || len <= 2)
        {
            return E_FAIL;
        }

        strncpy_s(orientation, info, len);

        if (orientation[0] != '-' && orientation[1] != 'Y')
        {
            // We only support the -Y +X orientation (see top of file)
            return HRESULT_FROM_WIN32(
                ((orientation[0] == '+' || orientation[0] == '-') && (orientation[1] == 'X' || orientation[1] == 'Y'))
                ? ERROR_NOT_SUPPORTED : ERROR_INVALID_DATA
            );
        }

        uint32_t height = 0;
        if (sscanf_s(orientation + 2, "%u", &height) != 1)
        {
            return E_FAIL;
        }

        const char* ptr = orientation + 2;
        while (*ptr != 0 && *ptr != '-' && *ptr != '+')
            ++ptr;

        if (*ptr == 0)
        {
            return E_FAIL;
        }
        else if (*ptr != '+')
        {
            // We only support the -Y +X orientation (see top of file)
            return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        }

        ++ptr;
        if (*ptr == 0 || (*ptr != 'X' && *ptr != 'Y'))
        {
            return E_FAIL;
        }
        else if (*ptr != 'X')
        {
            // We only support the -Y +X orientation (see top of file)
            return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        }

        ++ptr;
        uint32_t width;
        if (sscanf_s(ptr, "%u", &width) != 1)
        {
            return E_FAIL;
        }

        info += len + 1;
        size -= len + 1;

        if (!width || !height)
        {
            return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        }

        if (size == 0)
        {
            return E_FAIL;
        }

        offset = info - static_cast<const char*>(pSource);

        metadata.width = width;
        metadata.height = height;
        metadata.depth = metadata.arraySize = metadata.mipLevels = 1;
        metadata.format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        metadata.dimension = TEX_DIMENSION_TEXTURE2D;

        return S_OK;
    }

    //-------------------------------------------------------------------------------------
    // FloatToRGBE
    //-------------------------------------------------------------------------------------
    inline void FloatToRGBE(_Out_writes_(width*4) uint8_t* pDestination, _In_reads_(width*fpp) const float* pSource, size_t width, _In_range_(3, 4) int fpp)
    {
        auto ePtr = pSource + width * fpp;

        for (size_t j = 0; j < width; ++j)
        {
            if (pSource + 2 >= ePtr) break;
            float r = pSource[0] >= 0.f ? pSource[0] : 0.f;
            float g = pSource[1] >= 0.f ? pSource[1] : 0.f;
            float b = pSource[2] >= 0.f ? pSource[2] : 0.f;
            pSource += fpp;

            const float max_xy = (r > g) ? r : g;
            float max_xyz = (max_xy > b) ? max_xy : b;

            if (max_xyz > 1e-32)
            {
                int e;
                max_xyz = frexpf(max_xyz, &e) * 256.f / max_xyz;
                e += 128;

                uint8_t red = uint8_t(r * max_xyz);
                uint8_t green = uint8_t(g * max_xyz);
                uint8_t blue = uint8_t(b * max_xyz);

                pDestination[0] = red;
                pDestination[1] = green;
                pDestination[2] = blue;
                pDestination[3] = (red || green || blue) ? uint8_t(e & 0xff) : 0;
            }
            else
            {
                pDestination[0] = pDestination[1] = pDestination[2] = pDestination[3] = 0;
            }

            pDestination += 4;
        }
    }

    //-------------------------------------------------------------------------------------
    // Encode using Adapative RLE
    //-------------------------------------------------------------------------------------
    _Success_(return > 0)
        size_t EncodeRLE(_Out_writes_(width * 4) uint8_t* enc, _In_reads_(width * 4) const uint8_t* rgbe, size_t rowPitch, size_t width)
    {
        if (width < 8 || width > INT16_MAX)
        {
            // Don't try to compress too narrow or too wide scan-lines
            return 0;
        }

#ifdef WRITE_OLD_COLORS
        size_t encSize = 0;

        const uint8_t* scanPtr = rgbe;
        for (size_t pixelCount = 0; pixelCount < width;)
        {
            size_t spanLen = 1;
            auto spanPtr = reinterpret_cast<const uint32_t*>(scanPtr);
            while (pixelCount + spanLen < width && spanLen < INT16_MAX)
            {
                if (spanPtr[spanLen] == *spanPtr)
                {
                    ++spanLen;
                }
                else
                    break;
            }

            if (spanLen > 2)
            {
                if (scanPtr[0] == 1 && scanPtr[1] == 1 && scanPtr[2] == 1)
                {
                    return 0;
                }

                if (encSize + 8 > rowPitch)
                    return 0;

                auto rleLen = static_cast<uint8_t>(std::min<size_t>(spanLen - 1, 255));

                enc[0] = scanPtr[0];
                enc[1] = scanPtr[1];
                enc[2] = scanPtr[2];
                enc[3] = scanPtr[3];
                enc[4] = 1;
                enc[5] = 1;
                enc[6] = 1;
                enc[7] = rleLen;
                enc += 8;
                encSize += 8;

                size_t remaining = spanLen - 1 - rleLen;

                if (remaining > 0)
                {
                    rleLen = static_cast<uint8_t>(remaining >> 8);

                    if (rleLen > 0)
                    {
                        if (encSize + 4 > rowPitch)
                            return 0;

                        enc[0] = 1;
                        enc[1] = 1;
                        enc[2] = 1;
                        enc[3] = rleLen;
                        enc += 4;
                        encSize += 4;

                        remaining -= (rleLen << 8);
                    }

                    while (remaining > 0)
                    {
                        if (encSize + 4 > rowPitch)
                            return 0;

                        enc[0] = scanPtr[0];
                        enc[1] = scanPtr[1];
                        enc[2] = scanPtr[2];
                        enc[3] = scanPtr[3];
                        enc += 4;
                        encSize += 4;

                        --remaining;
                    }
                }

                scanPtr += spanLen * 4;
                pixelCount += spanLen;
            }
            else if (scanPtr[0] == 1 && scanPtr[1] == 1 && scanPtr[2] == 1)
            {
                return 0;
            }
            else
            {
                if (encSize + 4 > rowPitch)
                    return 0;

                enc[0] = scanPtr[0];
                enc[1] = scanPtr[1];
                enc[2] = scanPtr[2];
                enc[3] = scanPtr[3];
                enc += 4;
                encSize += 4;
                ++pixelCount;
                scanPtr += 4;
            }
        }

        return encSize;
#else
        enc[0] = 2;
        enc[1] = 2;
        enc[2] = uint8_t(width >> 8);
        enc[3] = uint8_t(width & 0xff);
        enc += 4;
        size_t encSize = 4;

        uint8_t scan[128] = {};

        for (int channel = 0; channel < 4; ++channel)
        {
            const uint8_t* spanPtr = rgbe + channel;
            for (size_t pixelCount = 0; pixelCount < width;)
            {
                uint8_t spanLen = 1;
                while (pixelCount + spanLen < width && spanLen < 127)
                {
                    if (spanPtr[spanLen * 4] == *spanPtr)
                    {
                        ++spanLen;
                    }
                    else
                        break;
                }

                if (spanLen > 1)
                {
                    if (encSize + 2 > rowPitch)
                        return 0;

                    enc[0] = 128 + spanLen;
                    enc[1] = *spanPtr;
                    enc += 2;
                    encSize += 2;
                    spanPtr += size_t(spanLen) * 4;
                    pixelCount += spanLen;
                }
                else
                {
                    uint8_t runLen = 1;
                    scan[0] = *spanPtr;
                    while (pixelCount + runLen < width && runLen < 127)
                    {
                        if (spanPtr[(runLen - 1) * 4] != spanPtr[runLen * 4])
                        {
                            scan[runLen] = spanPtr[runLen * 4];
                            runLen++;
                        }
                        else
                            break;
                    }

                    if (encSize + runLen + 1 > rowPitch)
                        return 0;

                    *enc++ = runLen;
                    memcpy(enc, scan, runLen);
                    enc += runLen;
                    encSize += size_t(runLen) + 1;
                    spanPtr += size_t(runLen) * 4;
                    pixelCount += runLen;
                }
            }
        }

        return encSize;
#endif
    }
}


//=====================================================================================
// Entry-points
//=====================================================================================

//-------------------------------------------------------------------------------------
// Obtain metadata from HDR file in memory/on disk
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::GetMetadataFromHDRMemory(const void* pSource, size_t size, TexMetadata& metadata)
{
    if (!pSource || size == 0)
        return E_INVALIDARG;

    size_t offset;
    float exposure;
    return DecodeHDRHeader(pSource, size, metadata, offset, exposure);
}

_Use_decl_annotations_
HRESULT DirectX::GetMetadataFromHDRFile(const wchar_t* szFile, TexMetadata& metadata)
{
    if (!szFile)
        return E_INVALIDARG;

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr)));
#else
    ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
        FILE_FLAG_SEQUENTIAL_SCAN, nullptr)));
#endif
    if (!hFile)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Get the file size
    FILE_STANDARD_INFO fileInfo;
    if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid HDR file)
    if (fileInfo.EndOfFile.HighPart > 0)
    {
        return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE);
    }

    // Need at least enough data to fill the standard header to be a valid HDR
    if (fileInfo.EndOfFile.LowPart < sizeof(g_Signature))
    {
        return E_FAIL;
    }

    // Read the first part of the file to find the header
    uint8_t header[8192] = {};
    DWORD bytesRead = 0;
    if (!ReadFile(hFile.get(), header, std::min<DWORD>(sizeof(header), fileInfo.EndOfFile.LowPart), &bytesRead, nullptr))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    size_t offset;
    float exposure;
    return DecodeHDRHeader(header, bytesRead, metadata, offset, exposure);
}


//-------------------------------------------------------------------------------------
// Load a HDR file in memory
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::LoadFromHDRMemory(const void* pSource, size_t size, TexMetadata* metadata, ScratchImage& image)
{
    if (!pSource || size == 0)
        return E_INVALIDARG;

    image.Release();

    size_t offset;
    float exposure;
    TexMetadata mdata;
    HRESULT hr = DecodeHDRHeader(pSource, size, mdata, offset, exposure);
    if (FAILED(hr))
        return hr;

    if (offset > size)
        return E_FAIL;

    size_t remaining = size - offset;
    if (remaining == 0)
        return E_FAIL;

    hr = image.Initialize2D(mdata.format, mdata.width, mdata.height, 1, 1);
    if (FAILED(hr))
        return hr;

    // Copy pixels
    auto sourcePtr = static_cast<const uint8_t*>(pSource) + offset;

    size_t pixelLen = remaining;

    const Image* img = image.GetImage(0, 0, 0);
    if (!img)
    {
        image.Release();
        return E_POINTER;
    }

    auto destPtr = img->pixels;

#ifdef _DEBUG
    memset(img->pixels, 0xFF, img->rowPitch * img->height);
#endif

    for (size_t scan = 0; scan < mdata.height; ++scan)
    {
        if (pixelLen < 4)
        {
            image.Release();
            return E_FAIL;
        }

        uint8_t inColor[4];
        memcpy(inColor, sourcePtr, 4);
        sourcePtr += 4;
        pixelLen -= 4;

        auto scanLine = reinterpret_cast<float*>(destPtr);
        
        if (inColor[0] == 2 && inColor[1] == 2 && inColor[2] < 128)
        {
            // Adaptive Run Length Encoding (RLE)
            if (size_t((size_t(inColor[2]) << 8) + inColor[3]) != mdata.width)
            {
                image.Release();
                return E_FAIL;
            }

            for (int channel = 0; channel < 4; ++channel)
            {
                auto pixelLoc = scanLine + channel;
                for(size_t pixelCount = 0; pixelCount < mdata.width;)
                {
                    if (pixelLen < 2)
                    {
                        image.Release();
                        return E_FAIL;
                    }

                    assert(sourcePtr < (reinterpret_cast<const uint8_t*>(pSource) + size));

                    uint8_t runLen = *sourcePtr;
                    if (runLen > 128)
                    {
                        runLen &= 127;
                        if (pixelCount + runLen > mdata.width)
                        {
                            image.Release();
                            return E_FAIL;
                        }

                        auto val = static_cast<float>(sourcePtr[1]);
                        for (uint8_t j = 0; j < runLen; ++j)
                        {
                            *pixelLoc = val;
                            pixelLoc += 4;
                        }
                        pixelCount += runLen;
                        sourcePtr += 2;
                        pixelLen -= 2;
                    }
                    else if ((size < size_t(runLen) + 1) || ((pixelCount + size_t(runLen)) > mdata.width))
                    {
                        image.Release();
                        return E_FAIL;
                    }
                    else
                    {
                        ++sourcePtr;
                        for (uint8_t j = 0; j < runLen; ++j)
                        {
                            auto val = static_cast<float>(*sourcePtr++);
                            *pixelLoc = val;
                            pixelLoc += 4;
                        }
                        pixelCount += runLen;
                        pixelLen -= size_t(runLen) + 1;
                    }
                }
            }
        }
        else
        {
            auto pixelLoc = scanLine;

            float prevColor[4];
            prevColor[0] = inColor[0];
            prevColor[1] = inColor[1];
            prevColor[2] = inColor[2];
            prevColor[3] = inColor[3];

            int bitShift = 0;
            for (size_t pixelCount = 0; pixelCount < mdata.width;)
            {
                if (inColor[0] == 1 && inColor[1] == 1 && inColor[2] == 1)
                {
                    if (bitShift > 24)
                    {
                        image.Release();
                        return E_FAIL;
                    }

                    // "Standard" Run Length Encoding
                    size_t spanLen = size_t(inColor[3]) << bitShift;
                    if (spanLen + pixelCount > mdata.width)
                    {
                        image.Release();
                        return E_FAIL;
                    }

                    for (size_t j = 0; j < spanLen; ++j)
                    {
                        pixelLoc[0] = prevColor[0];
                        pixelLoc[1] = prevColor[1];
                        pixelLoc[2] = prevColor[2];
                        pixelLoc[3] = prevColor[3];
                        pixelLoc += 4;
                    }
                    pixelCount += spanLen;
                    bitShift += 8;
                }
                else
                {
                    // Uncompressed
                    pixelLoc[0] = prevColor[0] = inColor[0];
                    pixelLoc[1] = prevColor[1] = inColor[1];
                    pixelLoc[2] = prevColor[2] = inColor[2];
                    pixelLoc[3] = prevColor[3] = inColor[3];
                    bitShift = 0;
                    ++pixelCount;
                    pixelLoc += 4;
                }

                if (pixelCount >= mdata.width)
                    break;

                if (pixelLen < 4)
                {
                    image.Release();
                    return E_FAIL;
                }

                memcpy(inColor, sourcePtr, 4);
                sourcePtr += 4;
                pixelLen -= 4;
            }
        }

        destPtr += img->rowPitch;
    }

    // Transform values
    {
        auto fdata = reinterpret_cast<float*>(image.GetPixels());

        for (size_t j = 0; j < image.GetPixelsSize(); j += 16)
        {
            auto exponent = static_cast<int>(fdata[3]);
            fdata[0] = 1.0f / exposure*ldexpf((fdata[0] + 0.5f), exponent - (128 + 8));
            fdata[1] = 1.0f / exposure*ldexpf((fdata[1] + 0.5f), exponent - (128 + 8));
            fdata[2] = 1.0f / exposure*ldexpf((fdata[2] + 0.5f), exponent - (128 + 8));
            fdata[3] = 1.f;

            fdata += 4;
        }
    }

    if (metadata)
        memcpy(metadata, &mdata, sizeof(TexMetadata));

    return S_OK;
}


//-------------------------------------------------------------------------------------
// Load a HDR file from disk
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::LoadFromHDRFile(const wchar_t* szFile, TexMetadata* metadata, ScratchImage& image)
{
    if (!szFile)
        return E_INVALIDARG;

    image.Release();

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr)));
#else
    ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
        FILE_FLAG_SEQUENTIAL_SCAN, nullptr)));
#endif
    if (!hFile)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Get the file size
    FILE_STANDARD_INFO fileInfo;
    if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid HDR file)
    if (fileInfo.EndOfFile.HighPart > 0)
    {
        return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE);
    }

    // Need at least enough data to fill the header to be a valid HDR
    if (fileInfo.EndOfFile.LowPart < sizeof(g_Signature))
    {
        return E_FAIL;
    }

    // Read file
    std::unique_ptr<uint8_t[]> temp(new (std::nothrow) uint8_t[fileInfo.EndOfFile.LowPart]);
    if (!temp)
    {
        return E_OUTOFMEMORY;
    }

    DWORD bytesRead = 0;
    if (!ReadFile(hFile.get(), temp.get(), fileInfo.EndOfFile.LowPart, &bytesRead, nullptr))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if (bytesRead != fileInfo.EndOfFile.LowPart)
    {
        return E_FAIL;
    }

    return LoadFromHDRMemory(temp.get(), fileInfo.EndOfFile.LowPart, metadata, image);
}


//-------------------------------------------------------------------------------------
// Save a HDR file to memory
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::SaveToHDRMemory(const Image& image, Blob& blob)
{
    if (!image.pixels)
        return E_POINTER;

    if (image.width > INT16_MAX || image.height > INT16_MAX)
    {
        // Images larger than this can't be RLE encoded. They are technically allowed as
        // uncompresssed, but we just don't support them.
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    int fpp;
    switch (image.format)
    {
    case DXGI_FORMAT_R32G32B32A32_FLOAT:
        fpp = 4;
        break;

    case DXGI_FORMAT_R32G32B32_FLOAT:
        fpp = 3;
        break;

    default:
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    blob.Release();

    char header[256] = {};
    sprintf_s(header, g_Header, image.height, image.width);

    auto headerLen = static_cast<DWORD>(strlen(header));

    size_t rowPitch = image.width * 4;
    size_t slicePitch = image.height * rowPitch;

    HRESULT hr = blob.Initialize(headerLen + slicePitch);
    if (FAILED(hr))
        return hr;

    // Copy header
    auto dPtr = static_cast<uint8_t*>(blob.GetBufferPointer());
    assert(dPtr != nullptr);
    memcpy_s(dPtr, blob.GetBufferSize(), header, headerLen);
    dPtr += headerLen;

#ifdef DISABLE_COMPRESS
    // Uncompressed write
    auto sPtr = reinterpret_cast<const uint8_t*>(image.pixels);
    for (size_t scan = 0; scan < image.height; ++scan)
    {
        FloatToRGBE(dPtr, reinterpret_cast<const float*>(sPtr), image.width, fpp);
        dPtr += rowPitch;
        sPtr += image.rowPitch;
    }
#else
    std::unique_ptr<uint8_t[]> temp(new (std::nothrow) uint8_t[rowPitch * 2]);
    if (!temp)
    {
        blob.Release();
        return E_OUTOFMEMORY;
    }

    auto rgbe = temp.get();
    auto enc = temp.get() + rowPitch;

    const uint8_t* sPtr = image.pixels;
    for (size_t scan = 0; scan < image.height; ++scan)
    {
        FloatToRGBE(rgbe, reinterpret_cast<const float*>(sPtr), image.width, fpp);
        sPtr += image.rowPitch;

        size_t encSize = EncodeRLE(enc, rgbe, rowPitch, image.width);
        if (encSize > 0)
        {
            memcpy(dPtr, enc, encSize);
            dPtr += encSize;
        }
        else
        {
            memcpy(dPtr, rgbe, rowPitch);
            dPtr += rowPitch;
        }
    }
#endif

    hr = blob.Trim(dPtr - static_cast<uint8_t*>(blob.GetBufferPointer()));
    if (FAILED(hr))
    {
        blob.Release();
        return hr;
    }

    return S_OK;
}


//-------------------------------------------------------------------------------------
// Save a HDR file to disk
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::SaveToHDRFile(const Image& image, const wchar_t* szFile)
{
    if (!szFile)
        return E_INVALIDARG;

    if (!image.pixels)
        return E_POINTER;

    if (image.width > INT16_MAX || image.height > INT16_MAX)
    {
        // Images larger than this can't be RLE encoded. They are technically allowed as
        // uncompresssed, but we just don't support them.
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    int fpp;
    switch (image.format)
    {
    case DXGI_FORMAT_R32G32B32A32_FLOAT:
        fpp = 4;
        break;

    case DXGI_FORMAT_R32G32B32_FLOAT:
        fpp = 3;
        break;

    default:
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    // Create file and write header
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr)));
#else
    ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr)));
#endif
    if (!hFile)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    auto_delete_file delonfail(hFile.get());

    uint64_t pitch = uint64_t(image.width) * 4u;
    uint64_t slicePitch = uint64_t(image.height) * pitch;

    if (pitch > UINT32_MAX)
        return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);

    size_t rowPitch = static_cast<size_t>(pitch);

    if (slicePitch < 65535)
    {
        // For small images, it is better to create an in-memory file and write it out
        Blob blob;

        HRESULT hr = SaveToHDRMemory(image, blob);
        if (FAILED(hr))
            return hr;

        // Write blob
        auto bytesToWrite = static_cast<const DWORD>(blob.GetBufferSize());
        DWORD bytesWritten;
        if (!WriteFile(hFile.get(), blob.GetBufferPointer(), bytesToWrite, &bytesWritten, nullptr))
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        if (bytesWritten != bytesToWrite)
        {
            return E_FAIL;
        }
    }
    else
    {
        // Otherwise, write the image one scanline at a time...
        std::unique_ptr<uint8_t[]> temp(new (std::nothrow) uint8_t[rowPitch * 2]);
        if (!temp)
            return E_OUTOFMEMORY;

        auto rgbe = temp.get();

        // Write header
        char header[256] = {};
        sprintf_s(header, g_Header, image.height, image.width);

        auto headerLen = static_cast<DWORD>(strlen(header));

        DWORD bytesWritten;
        if (!WriteFile(hFile.get(), header, headerLen, &bytesWritten, nullptr))
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        if (bytesWritten != headerLen)
            return E_FAIL;

#ifdef DISABLE_COMPRESS
        // Uncompressed write
        auto sPtr = reinterpret_cast<const uint8_t*>(image.pixels);
        for (size_t scan = 0; scan < image.height; ++scan)
        {
            FloatToRGBE(rgbe, reinterpret_cast<const float*>(sPtr), image.width, fpp);
            sPtr += image.rowPitch;

            if (!WriteFile(hFile.get(), rgbe, static_cast<DWORD>(rowPitch), &bytesWritten, nullptr))
            {
                return HRESULT_FROM_WIN32(GetLastError());
            }

            if (bytesWritten != rowPitch)
                return E_FAIL;
        }
#else
        auto enc = temp.get() + rowPitch;

        const uint8_t* sPtr = image.pixels;
        for (size_t scan = 0; scan < image.height; ++scan)
        {
            FloatToRGBE(rgbe, reinterpret_cast<const float*>(sPtr), image.width, fpp);
            sPtr += image.rowPitch;

            size_t encSize = EncodeRLE(enc, rgbe, rowPitch, image.width);
            if (encSize > 0)
            {
                if (encSize > UINT32_MAX)
                    return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);

                if (!WriteFile(hFile.get(), enc, static_cast<DWORD>(encSize), &bytesWritten, nullptr))
                {
                    return HRESULT_FROM_WIN32(GetLastError());
                }

                if (bytesWritten != encSize)
                    return E_FAIL;
            }
            else
            {
                if (!WriteFile(hFile.get(), rgbe, static_cast<DWORD>(rowPitch), &bytesWritten, nullptr))
                {
                    return HRESULT_FROM_WIN32(GetLastError());
                }

                if (bytesWritten != rowPitch)
                    return E_FAIL;
            }
        }
#endif
    }

    delonfail.clear();

    return S_OK;
}
