Skip to content

Instantly share code, notes, and snippets.

@Kaldaien
Created November 21, 2024 19:57
Show Gist options
  • Select an option

  • Save Kaldaien/bfb86257a4ca5cb7a3632f0100e4f841 to your computer and use it in GitHub Desktop.

Select an option

Save Kaldaien/bfb86257a4ca5cb7a3632f0100e4f841 to your computer and use it in GitHub Desktop.
HDR10 PNG encoding
#pragma once
#ifdef SK_HDR_PNG_RESHADE
#include "reshade_api.hpp"
#include <com_ptr.hpp>
using display = reshade::api::display*;
#else
#include <atlbase.h>
using com_ptr = CComPtr;
using display = void*;
#endif
#include <cmath>
#include <cstdio>
#include <cstdint>
#include <xstring>
#include <algorithm>
#include <memory>
#include <io.h>
#include <shellapi.h>
#include <ShlObj.h>
#include <wincodec.h>
#pragma comment (lib, "windowscodecs.lib")
// Use AVX for SIMD fp16 to fp32 conversion
#pragma push_macro("_XM_F16C_INTRINSICS_")
#if (defined _M_IX86) || (defined _M_X64)
#undef _XM_F16C_INTRINSICS_
#define _XM_F16C_INTRINSICS_
#endif
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXPackedVector.inl>
namespace sk_hdr_png
{
#ifndef SK_HDR_PNG_RESHADE
enum class format : uint32_t
{
unknown = 0,
r10g10b10a2_unorm = 24,
r16g16b16a16_float = 10
};
#else
using format = reshade::api::format;
#endif
#define DeclareUint32(x,y) uint32_t x = SetUint32((x),(y))
#if (defined _M_IX86) || (defined _M_X64)
uint32_t GetUint32 (uint32_t x) noexcept { return _byteswap_ulong (x); };
uint32_t SetUint32 (uint32_t& x, uint32_t y) noexcept { x = _byteswap_ulong (y); return x; };
#else
uint32_t GetUint32 (uint32_t x) noexcept { return x; };
uint32_t SetUint32 (uint32_t& x, uint32_t y) noexcept { x = y; return x; };
#endif
struct cHRM_Payload
{
DeclareUint32 (white_x, 31270);
DeclareUint32 (white_y, 32900);
DeclareUint32 (red_x, 70800);
DeclareUint32 (red_y, 29200);
DeclareUint32 (green_x, 17000);
DeclareUint32 (green_y, 79700);
DeclareUint32 (blue_x, 13100);
DeclareUint32 (blue_y, 4600);
};
struct sBIT_Payload
{
uint8_t red_bits = 10; // May be up to 16, for scRGB data
uint8_t green_bits = 10; // May be up to 16, for scRGB data
uint8_t blue_bits = 10; // May be up to 16, for scRGB data
};
struct mDCv_Payload
{
struct {
DeclareUint32 (red_x, 35400); // 0.708 / 0.00002
DeclareUint32 (red_y, 14600); // 0.292 / 0.00002
DeclareUint32 (green_x, 8500); // 0.17 / 0.00002
DeclareUint32 (green_y, 39850); // 0.797 / 0.00002
DeclareUint32 (blue_x, 6550); // 0.131 / 0.00002
DeclareUint32 (blue_y, 2300); // 0.046 / 0.00002
} primaries;
struct {
DeclareUint32 (x, 15635); // 0.3127 / 0.00002
DeclareUint32 (y, 16450); // 0.3290 / 0.00002
} white_point;
// The only real data we need to fill-in
struct {
DeclareUint32 (maximum, 10000000); // 1000.0 cd/m^2
DeclareUint32 (minimum, 1); // 0.0001 cd/m^2
} luminance;
};
struct cLLi_Payload
{
DeclareUint32 (max_cll, 10000000); // 1000 / 0.0001
DeclareUint32 (max_fall, 2500000); // 250 / 0.0001
};
//
// ICC Profile for tonemapping comes courtesy of ledoge
//
// https://github.com/ledoge/jxr_to_png
//
struct iCCP_Payload
{
char profile_name [20] = "RGB_D65_202_Rel_PeQ";
uint8_t compression_type = 0;// (PNG_COMPRESSION_TYPE_DEFAULT)
unsigned char profile_data [2178] =
{
0x78, 0x9C, 0xED, 0x97, 0x79, 0x58, 0x13, 0x67, 0x1E, 0xC7, 0x47, 0x50,
0x59, 0x95, 0x2A, 0xAC, 0xED, 0xB6, 0x8B, 0xA8, 0x54, 0x20, 0x20, 0x42,
0xE5, 0xF4, 0x00, 0x51, 0x40, 0x05, 0xAF, 0x6A, 0x04, 0x51, 0x6E, 0x84,
0x70, 0xAF, 0x20, 0x24, 0xDC, 0x87, 0x0C, 0xA8, 0x88, 0x20, 0x09, 0x90,
0x04, 0x12, 0x24, 0x24, 0x90, 0x03, 0x82, 0xA0, 0x41, 0x08, 0x24, 0x41,
0x2E, 0x21, 0x01, 0x12, 0x83, 0x4A, 0x10, 0xA9, 0x56, 0xB7, 0x8A, 0xE0,
0xAD, 0x21, 0xE0, 0xB1, 0x6B, 0x31, 0x3B, 0x49, 0x74, 0x09, 0x6D, 0xD7,
0x3E, 0xCF, 0x3E, 0xFD, 0xAF, 0x4E, 0x3E, 0xF3, 0xBC, 0xBF, 0x79, 0xBF,
0xEF, 0xBC, 0x33, 0x9F, 0xC9, 0xFC, 0x31, 0x2F, 0x00, 0xE8, 0xBC, 0x8D,
0x4A, 0x3E, 0x62, 0x30, 0xD7, 0x09, 0x00, 0xA2, 0x63, 0xE2, 0x91, 0xEE,
0x6E, 0x2E, 0x06, 0x7B, 0x82, 0x82, 0x0D, 0xB4, 0x46, 0x01, 0x6D, 0x60,
0x0E, 0xA0, 0xDC, 0x82, 0x10, 0xA8, 0x58, 0x67, 0x38, 0x7C, 0x8F, 0xEA,
0xE8, 0x57, 0x1B, 0x34, 0xEA, 0xF5, 0xB0, 0x6A, 0xAC, 0xC4, 0x42, 0x31,
0xD7, 0xF2, 0x84, 0x9D, 0x68, 0xBD, 0xA9, 0xD6, 0x43, 0xEB, 0x16, 0xE5,
0xBD, 0xFC, 0xC6, 0xD2, 0xFC, 0xF8, 0xFF, 0x38, 0xEF, 0xE3, 0xB6, 0x30,
0x24, 0x14, 0x85, 0x80, 0xDA, 0x9F, 0xA1, 0x7D, 0x1B, 0x22, 0x16, 0x19,
0x0F, 0x4D, 0xE9, 0x04, 0xD5, 0x46, 0x49, 0xF1, 0xB1, 0x8A, 0x3A, 0x04,
0xAA, 0xBF, 0x44, 0x44, 0x04, 0x41, 0xED, 0x9C, 0x64, 0xA8, 0x36, 0x47,
0x44, 0x22, 0x62, 0xA1, 0x9A, 0x06, 0xD5, 0xDA, 0x48, 0x2F, 0x6F, 0x1F,
0xA8, 0x66, 0x29, 0xC6, 0x84, 0xAB, 0xEA, 0x1E, 0x45, 0x1D, 0xAC, 0xAA,
0x47, 0x14, 0xB5, 0xB3, 0xB5, 0x8B, 0x25, 0x54, 0x3F, 0x03, 0x80, 0xC5,
0x97, 0x5C, 0xAC, 0x9D, 0xA1, 0x5A, 0xA7, 0x06, 0xEA, 0x87, 0x47, 0x1F,
0x49, 0x50, 0x5C, 0xF7, 0x83, 0x03, 0xA0, 0x1D, 0x1A, 0xE3, 0xE9, 0x01,
0xB5, 0x30, 0x68, 0xD7, 0x07, 0xDC, 0x01, 0x37, 0xC0, 0x05, 0x08, 0x04,
0xB6, 0x01, 0xEB, 0x00, 0x3B, 0xA8, 0xB5, 0x06, 0x2C, 0xA1, 0x3D, 0x10,
0xEA, 0x0F, 0x05, 0x8E, 0x40, 0x2D, 0x1C, 0x6A, 0xF7, 0x43, 0xCF, 0xEC,
0xB7, 0xE7, 0x98, 0xAF, 0x9C, 0x63, 0x2B, 0xF4, 0x83, 0xAE, 0x06, 0xDD,
0x8A, 0x81, 0x6A, 0xC8, 0xCC, 0x73, 0x42, 0x85, 0xD9, 0x58, 0xAB, 0xCE,
0xD2, 0x86, 0x5C, 0xE7, 0xDD, 0x91, 0xCB, 0x27, 0xCD, 0x00, 0x40, 0xAB,
0x18, 0x00, 0xA6, 0x0B, 0xE5, 0xF2, 0x77, 0x54, 0xB9, 0x7C, 0x9A, 0x0A,
0x00, 0x9A, 0xB7, 0x01, 0xA0, 0x33, 0x4B, 0xE5, 0x0B, 0x00, 0x0B, 0x74,
0x80, 0x39, 0x33, 0x73, 0xD5, 0x45, 0x00, 0x80, 0xDB, 0x51, 0xB9, 0x5C,
0x9E, 0x3D, 0xD3, 0x67, 0x16, 0x09, 0xF5, 0x8F, 0x42, 0xF3, 0xD4, 0xCF,
0xF4, 0x19, 0x68, 0x01, 0xC0, 0xA2, 0xF3, 0x00, 0x70, 0x65, 0x69, 0x74,
0x58, 0xBC, 0x95, 0xA2, 0x47, 0x53, 0x73, 0x81, 0xEA, 0x6E, 0x7F, 0xF1,
0x2F, 0xFE, 0xEA, 0x78, 0x8E, 0x86, 0xE6, 0xDC, 0x79, 0xF3, 0xB5, 0xFE,
0xB2, 0x60, 0xE1, 0x22, 0xED, 0x2F, 0x16, 0x2F, 0xD1, 0xD1, 0xFD, 0xEB,
0xD2, 0x2F, 0xBF, 0xFA, 0xDB, 0xD7, 0xDF, 0xFC, 0x5D, 0x6F, 0x99, 0xFE,
0xF2, 0x15, 0x2B, 0x0D, 0xBE, 0x5D, 0x65, 0x68, 0x64, 0x0C, 0x33, 0x31,
0x5D, 0x6D, 0xB6, 0xC6, 0xDC, 0xE2, 0xBB, 0xB5, 0x96, 0x56, 0xD6, 0x36,
0xB6, 0x76, 0xEB, 0xD6, 0x6F, 0xD8, 0x68, 0xEF, 0xB0, 0xC9, 0x71, 0xF3,
0x16, 0x27, 0x67, 0x97, 0xAD, 0xDB, 0xB6, 0xBB, 0xBA, 0xED, 0xD8, 0xB9,
0x6B, 0xF7, 0x9E, 0xEF, 0xF7, 0xEE, 0x83, 0xEF, 0x77, 0xF7, 0x38, 0xE0,
0x79, 0xF0, 0x10, 0x74, 0x6F, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x87, 0x83,
0x82, 0x11, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xFF, 0x38, 0x12,
0x1D, 0x73, 0x34, 0x36, 0x0E, 0x89, 0x8A, 0x4F, 0x48, 0x4C, 0x4A, 0x4E,
0x49, 0x4D, 0x4B, 0xCF, 0x38, 0x96, 0x09, 0x66, 0x65, 0x1F, 0x3F, 0x71,
0x32, 0xE7, 0x54, 0xEE, 0xE9, 0xBC, 0xFC, 0x33, 0x05, 0x68, 0x4C, 0x61,
0x51, 0x31, 0x16, 0x87, 0x2F, 0x29, 0x25, 0x10, 0xCB, 0xCE, 0x96, 0x93,
0x2A, 0xC8, 0x94, 0xCA, 0x2A, 0x2A, 0x8D, 0xCE, 0xA8, 0xAE, 0x61, 0xD6,
0x9E, 0xAB, 0xAB, 0x3F, 0x7F, 0x81, 0xD5, 0x70, 0xB1, 0xB1, 0x89, 0xDD,
0xDC, 0xC2, 0xE1, 0xF2, 0x5A, 0x2F, 0xB5, 0xB5, 0x77, 0x74, 0x76, 0x5D,
0xEE, 0xEE, 0xE1, 0x0B, 0x7A, 0xFB, 0xFA, 0x85, 0xA2, 0x2B, 0xE2, 0x81,
0xAB, 0xD7, 0xAE, 0x0F, 0x4A, 0x86, 0x6E, 0x0C, 0xDF, 0x1C, 0xF9, 0xE1,
0xD6, 0xED, 0x1F, 0xEF, 0xDC, 0xFD, 0xE7, 0x4F, 0xF7, 0xEE, 0x8F, 0x3E,
0x18, 0x1B, 0x7F, 0xF8, 0xE8, 0xF1, 0x93, 0xA7, 0xCF, 0x9E, 0xBF, 0x78,
0x29, 0x9D, 0x90, 0x4D, 0x4E, 0xBD, 0x7A, 0xFD, 0xE6, 0xED, 0xBF, 0xFE,
0xFD, 0xEE, 0xE7, 0xE9, 0xF7, 0xF2, 0xCF, 0xFE, 0x7F, 0x72, 0x7F, 0x10,
0x04, 0xB2, 0x32, 0x34, 0x4E, 0x85, 0x2F, 0xAC, 0x70, 0x33, 0x64, 0x1B,
0xEF, 0xE8, 0x99, 0x1F, 0x7A, 0x41, 0x2F, 0xAE, 0x66, 0x55, 0x22, 0x1D,
0x36, 0x37, 0x2D, 0x7B, 0x6E, 0x7A, 0xE6, 0xFC, 0xEC, 0xC8, 0xC5, 0xC4,
0x5D, 0xC6, 0x17, 0x4D, 0x76, 0x76, 0x6B, 0x41, 0x11, 0x52, 0x19, 0xAD,
0xF0, 0xC1, 0xAE, 0xF4, 0x2D, 0x34, 0x08, 0x4E, 0x35, 0x4E, 0xF3, 0xB6,
0x25, 0x59, 0xC1, 0xB9, 0xDA, 0x11, 0x75, 0xFA, 0xC8, 0x6A, 0xC3, 0x24,
0x3A, 0x6C, 0xDF, 0x3A, 0xD6, 0xBE, 0xF5, 0x75, 0x70, 0x07, 0x82, 0xFB,
0x9E, 0x44, 0xAF, 0xA3, 0x3B, 0x23, 0xCE, 0xEA, 0xC7, 0x51, 0x57, 0x25,
0xD2, 0x8C, 0x93, 0x69, 0x26, 0xE8, 0x25, 0x62, 0xF4, 0x12, 0x21, 0x5A,
0xF7, 0x12, 0x46, 0x8F, 0x5C, 0x64, 0x15, 0x87, 0x0F, 0xB1, 0xCF, 0x43,
0xDB, 0x80, 0xE5, 0xE6, 0x69, 0x95, 0xAB, 0xF9, 0xC0, 0x03, 0x3E, 0x30,
0xCA, 0x07, 0x7E, 0xE4, 0x03, 0x7D, 0x02, 0x80, 0xD6, 0xAB, 0x17, 0xCD,
0x8E, 0xD9, 0x57, 0x96, 0xE7, 0x98, 0x43, 0xB4, 0x1C, 0x33, 0x6C, 0x52,
0xD2, 0x38, 0x66, 0xD8, 0x30, 0x66, 0x54, 0x3B, 0x6E, 0x4A, 0x78, 0x68,
0x1D, 0xDB, 0x83, 0xF2, 0x26, 0xE7, 0x39, 0x49, 0xF7, 0x13, 0x3F, 0x42,
0x90, 0xEE, 0x2F, 0x95, 0xBA, 0xE3, 0xA5, 0x07, 0xCE, 0xC8, 0x7C, 0x93,
0xFA, 0xE2, 0xFD, 0x64, 0xDE, 0xB8, 0x59, 0xF8, 0x60, 0x65, 0x3E, 0x45,
0x93, 0x7E, 0x79, 0xAF, 0x10, 0x29, 0x1A, 0x27, 0xB2, 0x34, 0x4E, 0x1E,
0x9B, 0x9B, 0x1F, 0xA1, 0x5D, 0xB9, 0xC3, 0xA8, 0x19, 0xB6, 0x83, 0xAF,
0xF0, 0x52, 0x29, 0xCF, 0xCB, 0x3C, 0x3E, 0x0F, 0x04, 0xB5, 0x72, 0xA2,
0x74, 0xCA, 0x77, 0xC3, 0x2E, 0x9A, 0xAA, 0x2B, 0xAF, 0x0C, 0xC4, 0x19,
0x1C, 0x2E, 0xFA, 0x36, 0x3C, 0x0D, 0x96, 0xE1, 0x63, 0x57, 0x61, 0x05,
0xE7, 0xA9, 0x29, 0x6F, 0x64, 0xED, 0xB3, 0xAF, 0x87, 0x6F, 0x26, 0xB8,
0xEF, 0x4D, 0xF2, 0x8E, 0x9D, 0xAD, 0xAC, 0x23, 0x46, 0xEB, 0x0A, 0xD1,
0x4B, 0xDB, 0x30, 0xCB, 0xC8, 0x45, 0xD6, 0xC8, 0x12, 0xA5, 0x72, 0x56,
0xB9, 0x79, 0xFA, 0x27, 0x94, 0xCB, 0xFE, 0x78, 0xE5, 0x2F, 0x2A, 0x4E,
0x2F, 0x26, 0xE7, 0x2C, 0xA9, 0x8A, 0xFD, 0xBA, 0x16, 0xBE, 0x86, 0xBB,
0x66, 0xB7, 0x60, 0x41, 0x18, 0x6B, 0x19, 0xB2, 0xC6, 0x10, 0xF2, 0xD2,
0x25, 0xE6, 0xEB, 0x96, 0xE5, 0x2E, 0x2D, 0x47, 0x2E, 0xA3, 0xBB, 0x5B,
0x34, 0x9B, 0xEF, 0xE1, 0x2F, 0x08, 0xBB, 0xF0, 0x21, 0x32, 0x49, 0x27,
0x9A, 0xA4, 0x97, 0x98, 0x82, 0xA0, 0xC5, 0x99, 0x80, 0x8D, 0x34, 0x5B,
0x8F, 0xD6, 0xC5, 0x91, 0xF5, 0xFA, 0x28, 0xA5, 0xB2, 0xFB, 0xF7, 0x17,
0xDD, 0xF7, 0x5E, 0xF0, 0xD8, 0x5F, 0xE6, 0xE9, 0x97, 0xE2, 0x9B, 0xB4,
0x3B, 0xAA, 0x62, 0x39, 0x92, 0x66, 0x98, 0x48, 0x83, 0x41, 0xCA, 0x18,
0xBD, 0x01, 0xCC, 0x32, 0x11, 0x46, 0xBF, 0xAD, 0xD0, 0x88, 0x52, 0xBC,
0x01, 0x59, 0x12, 0xEE, 0x90, 0x8F, 0x81, 0x94, 0x2D, 0xD4, 0x94, 0xEF,
0xF0, 0x81, 0x7E, 0xC1, 0x1C, 0x5A, 0xEF, 0xF2, 0x98, 0xE6, 0xA3, 0xF0,
0xDF, 0x50, 0x36, 0x3E, 0xF7, 0x69, 0xE5, 0x09, 0xCF, 0xDF, 0x57, 0xB6,
0x6C, 0xAE, 0xB4, 0x6A, 0xAE, 0xB0, 0x6A, 0x39, 0x65, 0xC7, 0x0B, 0x71,
0xEA, 0xDE, 0x78, 0x48, 0xA4, 0x1B, 0xD5, 0xB0, 0x1C, 0x55, 0x63, 0x94,
0xC4, 0x80, 0x59, 0x37, 0x56, 0x59, 0x37, 0x91, 0x6D, 0x9A, 0x72, 0xD7,
0x73, 0x42, 0x9D, 0x2F, 0xDB, 0x7B, 0x09, 0xA1, 0x68, 0x85, 0x2A, 0x72,
0xA4, 0x32, 0x37, 0x53, 0xE9, 0x9B, 0xE9, 0x18, 0x67, 0xE6, 0x91, 0x5D,
0x6C, 0x27, 0xFF, 0xCB, 0x5F, 0x45, 0x9F, 0x5F, 0x19, 0x0F, 0x45, 0x74,
0x58, 0x40, 0x0A, 0x2F, 0x20, 0xA5, 0x25, 0x20, 0xAD, 0xEA, 0x30, 0x08,
0x86, 0x62, 0xDC, 0x15, 0x2F, 0x0C, 0xC3, 0x18, 0xEA, 0x87, 0x94, 0xB1,
0x0E, 0xD7, 0xB1, 0x0E, 0x03, 0xD8, 0x4D, 0x5D, 0x38, 0x67, 0x6A, 0x09,
0x3C, 0xB1, 0x0C, 0xE5, 0x58, 0x50, 0x64, 0x97, 0x4D, 0xB2, 0x48, 0xAF,
0x5A, 0x2D, 0xD0, 0x18, 0x13, 0x68, 0x3C, 0x10, 0x68, 0xDE, 0xED, 0x9D,
0x2F, 0xEC, 0x5D, 0x42, 0xEF, 0x33, 0x3D, 0xDA, 0x12, 0x07, 0x2F, 0xCB,
0xDF, 0x7C, 0x0A, 0x52, 0x36, 0x66, 0x8F, 0x19, 0x37, 0x29, 0xB9, 0x38,
0x0E, 0x3B, 0x37, 0x6E, 0x46, 0x7C, 0x64, 0xAB, 0x52, 0x76, 0x9E, 0xA5,
0xEC, 0xFE, 0x51, 0xD9, 0x2F, 0xA9, 0x2F, 0x61, 0xB6, 0xB2, 0xCF, 0x2C,
0xE5, 0xE0, 0xA1, 0xEE, 0xE0, 0xA1, 0xCE, 0xE0, 0x21, 0x66, 0xC8, 0xF0,
0x89, 0xC8, 0x5B, 0x9E, 0xF1, 0x23, 0x46, 0x49, 0x6C, 0x58, 0x72, 0xAD,
0x49, 0x32, 0xC3, 0x04, 0x21, 0xE9, 0x41, 0x48, 0x3A, 0x11, 0x12, 0x66,
0xE8, 0x8D, 0x93, 0x51, 0x3F, 0x78, 0x26, 0x8C, 0x18, 0xFF, 0x37, 0x8A,
0x10, 0x09, 0x22, 0x44, 0xDD, 0x11, 0x57, 0xEA, 0xA2, 0xC4, 0xB9, 0x31,
0x83, 0x5E, 0xC9, 0x83, 0x26, 0x29, 0x8D, 0x26, 0x29, 0x4C, 0x93, 0x14,
0x86, 0x49, 0x1A, 0xEB, 0x6A, 0x1A, 0x4B, 0x94, 0xD6, 0xD0, 0x9C, 0xDE,
0x88, 0xCD, 0xE4, 0x86, 0xE4, 0x74, 0x5A, 0x82, 0x75, 0xE6, 0xE9, 0x8C,
0xD5, 0xA9, 0x74, 0x53, 0x72, 0xE2, 0x6D, 0x72, 0xE2, 0x08, 0x39, 0x51,
0x48, 0x49, 0xA9, 0xAF, 0x04, 0x41, 0x3A, 0x76, 0x3B, 0x8E, 0x68, 0x9F,
0x53, 0x61, 0x99, 0x51, 0x65, 0x26, 0x34, 0x7F, 0x2C, 0x34, 0x7F, 0x24,
0x34, 0xBF, 0x27, 0xFC, 0x4E, 0x2C, 0xB4, 0x65, 0x8A, 0x5C, 0x51, 0xBC,
0x64, 0x0F, 0x52, 0xC1, 0x96, 0xDC, 0x32, 0xAB, 0x87, 0x6B, 0x5A, 0x3E,
0xC2, 0x7E, 0x68, 0x7E, 0xFE, 0xE1, 0xDA, 0xB3, 0x8F, 0x37, 0xC4, 0xF1,
0x13, 0x7C, 0x28, 0xF9, 0xCE, 0x52, 0x0F, 0xA2, 0x1A, 0x04, 0xE9, 0x01,
0xFC, 0xC4, 0xC1, 0x82, 0x49, 0xFF, 0x64, 0x85, 0xB2, 0x42, 0x53, 0x1D,
0xAC, 0xCC, 0xB7, 0x68, 0xD2, 0x5F, 0xA1, 0x4C, 0x92, 0x8A, 0x49, 0x52,
0x11, 0x49, 0x7A, 0x99, 0x24, 0xAD, 0x25, 0x4F, 0x66, 0xD1, 0xDE, 0x6D,
0xC7, 0x76, 0x6C, 0x3C, 0x59, 0xBF, 0x36, 0xA3, 0xDA, 0x8C, 0xF4, 0x52,
0x4C, 0x7A, 0x79, 0x85, 0xF4, 0xF2, 0x72, 0x85, 0x32, 0xA2, 0xAB, 0x45,
0xE4, 0x67, 0x57, 0xC9, 0xCF, 0xC4, 0xE4, 0xE7, 0x3D, 0xE4, 0xE7, 0x75,
0x95, 0xD2, 0x6C, 0xC6, 0x5B, 0x57, 0x5C, 0xBB, 0xFD, 0xC9, 0x7A, 0x4B,
0x28, 0x62, 0xDC, 0xBB, 0xC5, 0xB8, 0x37, 0xC2, 0xB8, 0x2F, 0xAE, 0x1E,
0x6D, 0xAC, 0x19, 0xCF, 0xAD, 0x7B, 0xB1, 0x9B, 0xC8, 0x71, 0xCC, 0xAD,
0xB5, 0x3A, 0xC6, 0x58, 0xC3, 0x69, 0x93, 0x72, 0xDA, 0x5E, 0x70, 0xDA,
0x7E, 0xE2, 0xB4, 0x77, 0x73, 0x3B, 0x09, 0xAD, 0x7D, 0xFE, 0xD5, 0x4C,
0xD7, 0x42, 0xEA, 0xFA, 0x6C, 0xAA, 0x85, 0x04, 0x25, 0x93, 0xA0, 0x26,
0x24, 0xA8, 0x27, 0x92, 0xF8, 0x9B, 0x92, 0xA4, 0xA6, 0x21, 0x10, 0xEC,
0xC9, 0xF7, 0xA6, 0x61, 0xB7, 0xE5, 0x97, 0xDB, 0x3E, 0x71, 0xE8, 0x51,
0xD2, 0xFD, 0x64, 0x53, 0xD7, 0x53, 0x47, 0xCE, 0xD3, 0x2D, 0xD4, 0x67,
0xAE, 0xA8, 0xFE, 0x34, 0xBF, 0xAA, 0x82, 0xAD, 0x13, 0x07, 0x89, 0x33,
0x1C, 0x22, 0x4C, 0x1C, 0xC2, 0xCB, 0x7C, 0x0A, 0xA6, 0x0E, 0x27, 0xF7,
0x27, 0xF9, 0xCB, 0x7C, 0x71, 0xEA, 0x4C, 0xFA, 0x62, 0x27, 0xFD, 0x8A,
0x26, 0x03, 0xF2, 0x5E, 0x85, 0xA4, 0x70, 0x06, 0x28, 0x9C, 0x01, 0xB2,
0x92, 0x72, 0xEE, 0x00, 0xAE, 0xF5, 0x6A, 0x66, 0x97, 0xC4, 0xAB, 0x92,
0xED, 0x92, 0x57, 0x6B, 0xF3, 0xA9, 0x48, 0x4C, 0x51, 0x42, 0xE6, 0x8A,
0xCB, 0xB9, 0x62, 0x28, 0x02, 0xBB, 0x06, 0xBD, 0x2A, 0x9B, 0xB6, 0x42,
0x51, 0x6B, 0x3F, 0x45, 0x09, 0xB9, 0x55, 0x48, 0xBA, 0x24, 0xC4, 0xB7,
0x8B, 0xB2, 0xBA, 0xAF, 0x7A, 0x53, 0x1B, 0xB7, 0xE5, 0x33, 0x6D, 0xBA,
0x3B, 0xAA, 0xBA, 0x3B, 0x2A, 0x95, 0x90, 0x7B, 0x3A, 0x4B, 0xF9, 0x5D,
0x27, 0x84, 0x82, 0x80, 0x9A, 0xF3, 0x6E, 0x05, 0xD5, 0x76, 0xC3, 0x8C,
0xEA, 0x0F, 0x54, 0xD3, 0x87, 0xAB, 0x2B, 0x6E, 0x32, 0x0B, 0x6E, 0xB1,
0x22, 0x9A, 0x29, 0x70, 0x3C, 0xC5, 0x5E, 0x86, 0x94, 0x2B, 0x99, 0x96,
0x21, 0xA7, 0x64, 0xA8, 0xBB, 0xB2, 0x44, 0xAE, 0x0C, 0x04, 0x07, 0x73,
0x83, 0x99, 0xC5, 0x6E, 0x53, 0x41, 0x65, 0x6A, 0x10, 0x5F, 0x05, 0x97,
0xBE, 0x42, 0x60, 0xDE, 0x44, 0xA6, 0x8A, 0xD3, 0x03, 0x27, 0x03, 0x70,
0xEA, 0x4C, 0x05, 0x60, 0xA7, 0x02, 0x8B, 0xA6, 0x82, 0xF2, 0x5F, 0x87,
0xA5, 0xF2, 0x3B, 0x4A, 0x95, 0x94, 0x28, 0xC1, 0x09, 0x3A, 0xD0, 0x7D,
0x1D, 0x99, 0x03, 0x5D, 0x87, 0xE9, 0x0D, 0xDB, 0xF9, 0xED, 0xA5, 0x0A,
0x3E, 0x11, 0xB5, 0x97, 0x28, 0x99, 0x89, 0x18, 0x0D, 0xDB, 0x05, 0x6D,
0xA5, 0x4A, 0x4A, 0x94, 0xE0, 0x7A, 0xDB, 0xD0, 0xFD, 0xED, 0xE0, 0x40,
0x67, 0x10, 0x83, 0xE5, 0xDA, 0xC7, 0x2B, 0xFD, 0x48, 0x49, 0x3F, 0x0F,
0xD7, 0xCF, 0xC3, 0x88, 0x5A, 0xB3, 0xAE, 0xB7, 0x05, 0x43, 0xD6, 0xD7,
0x58, 0xA5, 0x6A, 0xE0, 0xAF, 0xB3, 0x0A, 0x07, 0x1B, 0x8E, 0xDF, 0x6C,
0x0A, 0xAB, 0xAF, 0xDD, 0x75, 0xFF, 0x2C, 0x41, 0x8D, 0xD2, 0xFB, 0x67,
0xB1, 0xA3, 0xA4, 0xD3, 0xE3, 0x94, 0x58, 0x1E, 0xC9, 0x63, 0x3A, 0xB9,
0x56, 0x0D, 0xE6, 0x74, 0x4A, 0xF5, 0x74, 0x2A, 0x65, 0x1A, 0x04, 0x6F,
0x9C, 0x0A, 0x7D, 0x1D, 0x8E, 0x57, 0x03, 0xA7, 0x20, 0xA2, 0xF8, 0x4D,
0xE4, 0x99, 0xB7, 0x31, 0x69, 0xFD, 0x5C, 0x9C, 0x1A, 0x58, 0x21, 0xB7,
0x58, 0xC8, 0x2D, 0xB8, 0xC2, 0x03, 0x07, 0x5B, 0x11, 0xFF, 0x5F, 0x24,
0xE4, 0xE2, 0xD4, 0x50, 0x44, 0x22, 0x28, 0xE2, 0x82, 0x83, 0x3C, 0x84,
0x90, 0x83, 0x53, 0x03, 0x2B, 0xE2, 0x14, 0x8B, 0x38, 0x05, 0x62, 0x0E,
0x28, 0xE1, 0x85, 0x88, 0x9B, 0x70, 0x6A, 0x60, 0xC5, 0xEC, 0xE2, 0x01,
0x76, 0xC1, 0x35, 0x76, 0xD6, 0x8D, 0x96, 0xD0, 0xA1, 0x3A, 0xDC, 0x6C,
0x8A, 0x6F, 0xD4, 0xA1, 0x87, 0xEB, 0x8F, 0xDF, 0xBE, 0x10, 0x39, 0x46,
0xC0, 0xAB, 0x81, 0x1B, 0x23, 0x60, 0xC7, 0x88, 0x85, 0xE3, 0x65, 0xB9,
0x8F, 0x49, 0xC8, 0xF7, 0xE9, 0x25, 0x6A, 0xE0, 0x95, 0x60, 0xDF, 0x67,
0xA0, 0xE5, 0xD0, 0x77, 0xC8, 0xE7, 0x4F, 0xD1, 0xCF, 0xFE, 0x7F, 0x66,
0xFF, 0x68, 0x17, 0x67, 0xE5, 0x7A, 0x56, 0x53, 0x53, 0xB5, 0xA8, 0xFD,
0xC5, 0x6A, 0x15, 0x88, 0x0D, 0x42, 0x06, 0xA9, 0xAF, 0x5D, 0x7F, 0xEF,
0xF8, 0x3F, 0x0B, 0x10, 0x3B, 0xD9
};
};
bool write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard, display display = nullptr);
bool write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance_array, int quantization_bits, display display = nullptr);
cLLi_Payload calculate_content_light_info (const float* luminance, unsigned int width, unsigned int height);
bool copy_to_clipboard (const wchar_t* image_path);
bool remove_chunk (const char* chunk_name, void* data, size_t& size);
uint32_t crc32 (const void* typeless_data, size_t offset, size_t len, uint32_t crc);
struct ParamsPQ
{
DirectX::XMVECTOR N, M;
DirectX::XMVECTOR C1, C2, C3;
DirectX::XMVECTOR MaxPQ;
DirectX::XMVECTOR RcpN, RcpM;
};
static const ParamsPQ PQ =
{
DirectX::XMVectorReplicate (2610.0 / 4096.0 / 4.0), // N
DirectX::XMVectorReplicate (2523.0 / 4096.0 * 128.0), // M
DirectX::XMVectorReplicate (3424.0 / 4096.0), // C1
DirectX::XMVectorReplicate (2413.0 / 4096.0 * 32.0), // C2
DirectX::XMVectorReplicate (2392.0 / 4096.0 * 32.0), // C3
DirectX::XMVectorReplicate (125.0f), // MaxPQ
DirectX::XMVectorReciprocal (DirectX::XMVectorReplicate (2610.0 / 4096.0 / 4.0)),
DirectX::XMVectorReciprocal (DirectX::XMVectorReplicate (2523.0 / 4096.0 * 128.0)),
};
constexpr DirectX::XMMATRIX c_from709to2020 =
{
{ 0.627403914928436279296875f, 0.069097287952899932861328125f, 0.01639143936336040496826171875f, 0.0f },
{ 0.3292830288410186767578125f, 0.9195404052734375f, 0.08801330626010894775390625f, 0.0f },
{ 0.0433130674064159393310546875f, 0.011362315155565738677978515625f, 0.895595252513885498046875f, 0.0f },
{ 0.0f, 0.0f, 0.0f, 1.0f }
};
constexpr DirectX::XMMATRIX c_from709toXYZ =
{
{ 0.4123907983303070068359375f, 0.2126390039920806884765625f, 0.0193308182060718536376953125f, 0.0f },
{ 0.3575843274593353271484375f, 0.715168654918670654296875f, 0.119194783270359039306640625f, 0.0f },
{ 0.18048079311847686767578125f, 0.072192318737506866455078125f, 0.950532138347625732421875f, 0.0f },
{ 0.0f, 0.0f, 0.0f, 1.0f }
};
constexpr DirectX::XMMATRIX c_from2020toXYZ =
{
{ 0.636958062648773193359375f, 0.26270020008087158203125f, 0.0f, 0.0f },
{ 0.144616901874542236328125f, 0.677998065948486328125f, 0.028072692453861236572265625f, 0.0f },
{ 0.1688809692859649658203125f, 0.0593017153441905975341796875f, 1.060985088348388671875f, 0.0f },
{ 0.0f, 0.0f, 0.0f, 1.0f }
};
static auto LinearToPQ = [](DirectX::XMVECTOR& N)
{
using namespace DirectX;
XMVECTOR ret =
XMVectorPow (XMVectorDivide (XMVectorMax (N, g_XMZero), PQ.MaxPQ), PQ.N);
XMVECTOR nd =
XMVectorDivide (
XMVectorMultiplyAdd (PQ.C2, ret, PQ.C1),
XMVectorMultiplyAdd (PQ.C3, ret, g_XMOne)
);
return
XMVectorPow (nd, PQ.M);
};
static auto PQToLinear = [](DirectX::XMVECTOR& N)
{
using namespace DirectX;
XMVECTOR ret =
XMVectorPow (XMVectorMax (N, g_XMZero), PQ.RcpM);
XMVECTOR nd =
XMVectorDivide (
XMVectorMax (XMVectorSubtract (ret, PQ.C1), g_XMZero),
XMVectorSubtract ( PQ.C2,
XMVectorMultiply (PQ.C3, ret)));
ret =
XMVectorMultiply (
XMVectorPow (nd, PQ.RcpN), PQ.MaxPQ
);
return ret;
};
}
static
sk_hdr_png::cLLi_Payload
sk_hdr_png::calculate_content_light_info (const float* luminance, unsigned int width, unsigned int height)
{
using namespace DirectX;
using namespace DirectX::PackedVector;
cLLi_Payload clli = { };
if (luminance == nullptr || width == 0 || height == 0)
return clli;
float N = 0.0f;
float fLumAccum = 0.0f;
float fMaxLum = 0.0f;
float fMinLum = 5240320.0f;
float fScanlineLum = 0.0f;
const float* pixel_luminance = luminance;
for (size_t y = 0; y < height; y++)
{
fScanlineLum = 0.0f;
for (size_t x = 0; x < width ; x++)
{
fMaxLum = std::max (fMaxLum, *pixel_luminance);
fMinLum = std::min (fMinLum, *pixel_luminance);
fScanlineLum += *pixel_luminance++;
}
fLumAccum +=
(fScanlineLum / static_cast <float> (width));
++N;
}
if (N > 0.0)
{
pixel_luminance = luminance;
// 0 nits - 10k nits (appropriate for screencap, but not HDR photography)
fMinLum = std::clamp (fMinLum, 0.0f, 125.0f);
fMaxLum = std::clamp (fMaxLum, fMinLum, 125.0f);
const float fLumRange =
(fMaxLum - fMinLum);
auto luminance_freq = std::make_unique <uint32_t []> (65536);
ZeroMemory (luminance_freq.get (), sizeof (uint32_t) * 65536);
for (size_t y = 0; y < height * width; y++)
{
luminance_freq [
std::clamp ( (int)
std::roundf (
(*pixel_luminance++ - fMinLum) /
(fLumRange / 65536.0f) ),
0, 65535 ) ]++;
}
double percent = 100.0;
const double img_size = (double)width *
(double)height;
// Now that we have the frequency distribution, let's claim our prize...
//
// * Calculate the 99.5th percentile luminance and use it as MaxCLL
//
for (auto i = 65535; i >= 0; --i)
{
percent -=
100.0 * ((double)luminance_freq [i] / img_size);
if (percent <= 99.5)
{
fMaxLum = fMinLum + (fLumRange * ((float)i / 65536.0f));
break;
}
}
SetUint32 (clli.max_cll,
static_cast <uint32_t> ((80.0f * fMaxLum ) / 0.0001f));
SetUint32 (clli.max_fall,
static_cast <uint32_t> ((80.0f * (fLumAccum / N)) / 0.0001f));
}
return clli;
}
static
uint32_t
sk_hdr_png::crc32 (const void* typeless_data, size_t offset, size_t len, uint32_t crc)
{
auto data = reinterpret_cast<const BYTE *>(typeless_data);
if (data == nullptr || len == 0)
{
return static_cast<uint32_t>(-1);
}
uint32_t c;
static uint32_t
png_crc_table[256] = { };
if (png_crc_table[ 0 ] == 0)
{
for (auto i = 0 ; i < 256 ; ++i)
{
c = i;
for (auto j = 0 ; j < 8 ; ++j)
{
if ((c & 1) == 1) c = (0xEDB88320 ^ ((c >> 1) & 0x7FFFFFFF));
else c = ( (c >> 1) & 0x7FFFFFFF);
}
png_crc_table [i] = c;
}
}
c = (crc ^ 0xffffffff);
for (auto k = offset ; k < (offset + len) ; ++k)
{
c = png_crc_table [(c ^ data [k]) & 255] ^
((c >> 8) & 0xFFFFFF);
}
return (c ^ 0xffffffff);
}
//
// To convert an image passed to an encoder that does not understand HDR,
// but that we actually fed HDR pixels to... perform the following:
//
// 1. Remove gAMA chunk (Prevents SKIV from recognizing as HDR)
// 2. Remove sRGB chunk (Prevents Discord from rendering in HDR)
//
// 3. Add cICP (The primary way of defining HDR10)
// 4. Add iCCP (Required for Discord to render in HDR)
//
// (5) Add cLLi [Unnecessary, but probably a good idea]
// (6) Add cHRM [Unnecessary, but probably a good idea]
//
static
bool
sk_hdr_png::remove_chunk (const char* chunk_name, void* data, size_t& size)
{
if (chunk_name == nullptr || data == nullptr || size < 12 || strlen(chunk_name) < 4)
{
return false;
}
size_t erase_pos = 0;
uint8_t* erase_ptr = nullptr;
// Effectively a string search, but ignoring nul-bytes in both
// the character array being searched and the pattern...
std::string_view data_view((const char*)data, size);
if (erase_pos = data_view.find(chunk_name, 0, 4);
erase_pos == data_view.npos)
{
return false;
}
erase_pos -= 4; // Rollback to the chunk's length field
erase_ptr = ((uint8_t*)data + erase_pos);
uint32_t chunk_size = *(uint32_t*)erase_ptr;
// Length is Big Endian, Intel/AMD CPUs are Little Endian
#if (defined _M_IX86) || (defined _M_X64)
chunk_size = _byteswap_ulong(chunk_size);
#endif
size_t size_to_erase = (size_t)12 + chunk_size;
memmove(erase_ptr,
erase_ptr + size_to_erase,
size - erase_pos - size_to_erase);
size -= size_to_erase;
return true;
}
static
bool
sk_hdr_png::write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance, int quantization_bits, display display)
{
if (image_path == nullptr || width == 0 || height == 0 || quantization_bits < 6)
{
return false;
}
// 16-byte alignment is mandatory for SIMD processing
if ((reinterpret_cast<uintptr_t>(luminance) & 0xF) != 0)
{
return false;
}
FILE*
fPNG = _wfopen(image_path, L"r+b");
if (fPNG != nullptr)
{
fseek(fPNG, 0, SEEK_END);
size_t size = ftell(fPNG);
rewind(fPNG);
auto data = std::make_unique<uint8_t[]>(size);
if (! data)
{
fclose(fPNG);
return false;
}
fread(data.get(), size, 1, fPNG);
rewind( fPNG);
remove_chunk("sRGB", data.get(), size);
remove_chunk("gAMA", data.get(), size);
fwrite(data.get(), size, 1, fPNG);
// Truncate the file
_chsize(_fileno(fPNG), static_cast<long>(size));
size_t insert_pos = 0;
const uint8_t* insert_ptr = nullptr;
// Effectively a string search, but ignoring nul-bytes in both
// the character array being searched and the pattern...
std::string_view data_view((const char *)data.get(), size);
if (insert_pos = data_view.find("IDAT", 0, 4);
insert_pos == data_view.npos)
{
fclose(fPNG);
return false;
}
insert_pos -= 4; // Rollback to the chunk's length field
insert_ptr = (data.get() + insert_pos);
fseek(fPNG, static_cast<long>(insert_pos), SEEK_SET);
struct Chunk {
uint32_t len;
unsigned char name [4];
void* data;
uint32_t crc;
uint32_t _native_len;
void write (FILE* fStream)
{
// Length is Big Endian, Intel/AMD CPUs are Little Endian
if (_native_len == 0)
{
_native_len = len;
#if (defined _M_IX86) || (defined _M_X64)
len = _byteswap_ulong(_native_len);
#endif
}
crc = crc32(data, 0, _native_len, crc32(name, 0, 4, 0x0));
#if (defined _M_IX86) || (defined _M_X64)
crc = _byteswap_ulong(crc);
#endif
fwrite(&len, 8, 1, fStream);
fwrite(data, _native_len, 1, fStream);
fwrite(&crc, 4, 1, fStream);
};
};
uint8_t cicp_data[] = {
9, // BT.2020 Color Primaries
16, // ST.2084 EOTF (PQ)
0, // Identity Coefficients
1, // Full Range
};
// Embedded ICC Profile so that Discord will render in HDR
iCCP_Payload iccp_data;
cHRM_Payload chrm_data; // Rec 2020 chromaticity
sBIT_Payload sbit_data; // Bits in original source (max=16)
mDCv_Payload mdcv_data; // Display capabilities
cLLi_Payload clli_data; // Content light info
clli_data = calculate_content_light_info(luminance, width, height);
unsigned char sBIT_quantized = static_cast<unsigned char>(quantization_bits);
sbit_data = { sBIT_quantized, sBIT_quantized, sBIT_quantized };
Chunk iccp_chunk = {sizeof(iCCP_Payload), {'i','C','C','P'}, &iccp_data};
Chunk cicp_chunk = {sizeof(cicp_data), {'c','I','C','P'}, &cicp_data};
Chunk clli_chunk = {sizeof(clli_data), {'c','L','L','i'}, &clli_data};
Chunk sbit_chunk = {sizeof(sbit_data), {'s','B','I','T'}, &sbit_data};
Chunk chrm_chunk = {sizeof(chrm_data), {'c','H','R','M'}, &chrm_data};
iccp_chunk.write(fPNG);
cicp_chunk.write(fPNG);
clli_chunk.write(fPNG);
sbit_chunk.write(fPNG);
chrm_chunk.write(fPNG);
#ifdef SK_HDR_PNG_RESHADE
///
/// Mastering metadata can be added, provided you are able to read this info
/// from the user's EDID.
///
auto colorimetry = display->get_colorimetry();
auto luminance_caps = display->get_luminance_caps();
sk_hdr_png::SetUint32 (mdcv_data.luminance.minimum,
static_cast <uint32_t> (round (luminance_caps.min_nits / 0.0001f)));
sk_hdr_png::SetUint32 (mdcv_data.luminance.maximum,
static_cast <uint32_t> (round (luminance_caps.max_nits / 0.0001f)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.red_x,
static_cast <uint32_t> (round (colorimetry.red [0] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.red_y,
static_cast <uint32_t> (round (colorimetry.red [1] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.green_x,
static_cast <uint32_t> (round (colorimetry.green [0] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.green_y,
static_cast <uint32_t> (round (colorimetry.green [1] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.blue_x,
static_cast <uint32_t> (round (colorimetry.blue [0] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.primaries.blue_y,
static_cast <uint32_t> (round (colorimetry.blue [1] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.white_point.x,
static_cast <uint32_t> (round (colorimetry.white [0] / 0.00002)));
sk_hdr_png::SetUint32 (mdcv_data.white_point.y,
static_cast <uint32_t> (round (colorimetry.white [1] / 0.00002)));
Chunk mdcv_chunk = {sizeof(mdcv_data), {'m','D','C','v'}, &mdcv_data};
mdcv_chunk.write(fPNG);
#endif
// Write the remainder of the original file
fwrite(insert_ptr, size - insert_pos, 1, fPNG);
fflush(fPNG);
fclose(fPNG);
return true;
}
return false;
}
static
bool
sk_hdr_png::copy_to_clipboard (const wchar_t* image_path)
{
std::error_code ec;
if (image_path == nullptr || !std::filesystem::exists (image_path, ec))
{
return false;
}
int clpSize = sizeof (DROPFILES);
clpSize += sizeof(wchar_t) * static_cast<int>(wcslen(image_path) + 1); // + 1 => '\0'
clpSize += sizeof(wchar_t); // two \0 needed at the end
HDROP hdrop = static_cast <HDROP>(GlobalAlloc(GHND, clpSize));
DROPFILES* df = static_cast <DROPFILES*>(GlobalLock(hdrop));
if (df != nullptr)
{
df->pFiles = sizeof(DROPFILES);
df->fWide = TRUE;
wcscpy((wchar_t*)&df[1], image_path);
bool clipboard_open = false;
for (auto attempts = 0; attempts < 8; ++attempts)
{
if (attempts > 0)
{
Sleep(1 << (attempts-1));
}
if (OpenClipboard(GetForegroundWindow()))
{
clipboard_open = true;
break;
}
}
if (clipboard_open)
{
EmptyClipboard( );
SetClipboardData(CF_HDROP, hdrop);
CloseClipboard( );
GlobalUnlock( hdrop);
return true;
}
GlobalUnlock(hdrop);
}
return false;
}
static
bool
sk_hdr_png::write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard, display display)
{
using namespace DirectX;
using namespace DirectX::PackedVector;
// PNG only supports 8-bpc and 16-bpc pixels; the bpc refers to the size of the pixel during encode/decode.
//
// * We have a 3-channel RGB image, thus 48-bpp when decoded.
//
// Space savings are possible by quantizing to alternate bit depths before encoding, 10-bpc is a sane minimum for HDR.
WICPixelFormatGUID wic_format = GUID_WICPixelFormat48bppRGB;
if (image_path == nullptr || width == 0 || height == 0 || (fmt != format::r16g16b16a16_float && fmt != format::r10g10b10a2_unorm))
{
return false;
}
// 16-byte alignment is mandatory for SIMD processing
if (pixels == nullptr || (reinterpret_cast<uintptr_t>(pixels) & 0xF) != 0)
{
return false;
}
if (quantization_bits < 6 || quantization_bits > 16)
{
return false;
}
com_ptr<IWICImagingFactory> factory;
com_ptr<IWICBitmapEncoder> encoder;
com_ptr<IWICBitmapFrameEncode> bitmap_frame;
com_ptr<IPropertyBag2> property_bag;
com_ptr<IWICStream> stream;
HRESULT hr =
CoCreateInstance(CLSID_WICImagingFactory,NULL,CLSCTX_INPROC_SERVER,IID_IWICImagingFactory,(LPVOID*)&factory);
UINT row_stride = (width * 48 + 7)/8;
UINT buffer_size = height * row_stride;
BYTE* png_buffer = (BYTE *)_aligned_malloc(sizeof ( BYTE) * buffer_size, 16);
XMFLOAT4* rgba32_scanline = (XMFLOAT4 *)_aligned_malloc(sizeof (XMFLOAT4) * width, 16);
float* luminance = (float *)_aligned_malloc(sizeof ( float) * width * height, 16);
if (png_buffer != nullptr && rgba32_scanline != nullptr && luminance != nullptr)
{
if (SUCCEEDED(hr)) hr = factory->CreateStream(&stream);
if (SUCCEEDED(hr)) hr = stream->InitializeFromFilename(image_path, GENERIC_WRITE);
if (SUCCEEDED(hr)) hr = factory->CreateEncoder(GUID_ContainerFormatPng, NULL, &encoder);
if (SUCCEEDED(hr)) hr = encoder->Initialize(stream.get(), WICBitmapEncoderNoCache);
if (SUCCEEDED(hr)) hr = encoder->CreateNewFrame(&bitmap_frame, &property_bag);
if (SUCCEEDED(hr)) hr = bitmap_frame->Initialize(property_bag.get());
if (SUCCEEDED(hr)) hr = bitmap_frame->SetSize(width, height);
if (SUCCEEDED(hr)) hr = bitmap_frame->SetPixelFormat(&wic_format);
if (SUCCEEDED(hr)) hr = IsEqualGUID(wic_format, GUID_WICPixelFormat48bppRGB) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
auto QUANTIZE_FP32_TO_UNORM16 = [](XMVECTOR& rgb32,int bit_reduce,uint16_t*& output)
{
const int quant_postscale = 1UL << bit_reduce;
const float quant_prescale = static_cast<float>(quant_postscale);
*(output++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(std::roundf ((XMVectorGetX (rgb32) * quant_prescale)) * 65536.0f) / quant_postscale));
*(output++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(std::roundf ((XMVectorGetY (rgb32) * quant_prescale)) * 65536.0f) / quant_postscale));
*(output++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(std::roundf ((XMVectorGetZ (rgb32) * quant_prescale)) * 65536.0f) / quant_postscale));
};
if (fmt == format::r10g10b10a2_unorm)
{
uint16_t* png_pixels = (uint16_t *)png_buffer;
uint32_t* src_pixels = (uint32_t *)pixels;
auto pixel_luminance = luminance;
for (size_t i = 0; i < width * height; i++)
{
const uint32_t rgba = *reinterpret_cast<const uint32_t *>(src_pixels++);
// Multiply by 64 and +/- 1 to get 10-bit range (0-1023) into 16-bit range (0-65535)
const uint16_t r = (((( rgba & 0x000003FFU) + 1U) * 64U) & 0xFFFFU) - 1U;
const uint16_t g = (((((rgba & 0x000FFC00U) >> 10U) + 1U) * 64U) & 0xFFFFU) - 1U;
const uint16_t b = (((((rgba & 0x3FF00000U) >> 20U) + 1U) * 64U) & 0xFFFFU) - 1U;
XMVECTOR rgb =
XMVectorSet (static_cast<float>(r) / 65535.0f,
static_cast<float>(g) / 65535.0f,
static_cast<float>(b) / 65535.0f, 1.0f);
if (quantization_bits < 10) {
QUANTIZE_FP32_TO_UNORM16 (rgb, quantization_bits, png_pixels);
} else {
*(png_pixels++) = r;
*(png_pixels++) = g;
*(png_pixels++) = b;
}
*pixel_luminance++ =
XMVectorGetY (
XMVector3Transform (
PQToLinear (XMVectorSaturate (rgb)), c_from2020toXYZ
)
);
}
hr = bitmap_frame->WritePixels(height, row_stride, buffer_size, png_buffer);
}
else if (fmt == format::r16g16b16a16_float)
{
uint16_t* png_pixels = (uint16_t *)png_buffer;
uint16_t* src_pixels = (uint16_t *)pixels;
auto pixel_luminance = luminance;
for (size_t y = 0; y < height; y++)
{
XMFLOAT4* rgba32_pixels = rgba32_scanline;
XMConvertHalfToFloatStream (
(float *)rgba32_pixels, sizeof (float),
src_pixels, sizeof (HALF), 4 * width
);
for (size_t x = 0; x < width ; x++)
{
XMVECTOR rgb =
XMLoadFloat4 (rgba32_pixels++);
*pixel_luminance++ =
XMVectorGetY (
XMVector3Transform (rgb, c_from709toXYZ)
);
rgb =
LinearToPQ (
XMVectorMax (
XMVector3Transform (rgb, c_from709to2020),
g_XMZero )
);
if (quantization_bits < 16)
QUANTIZE_FP32_TO_UNORM16 (rgb, quantization_bits, png_pixels);
else
{
*(png_pixels++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(XMVectorGetX (rgb) * 65536.0f)));
*(png_pixels++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(XMVectorGetY (rgb) * 65536.0f)));
*(png_pixels++) = static_cast<uint16_t>(std::min (65535, static_cast<int>(XMVectorGetZ (rgb) * 65536.0f)));
}
src_pixels += 4;
}
}
hr = bitmap_frame->WritePixels(height, row_stride, buffer_size, png_buffer);
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr)) hr = bitmap_frame->Commit();
if (SUCCEEDED(hr)) hr = encoder->Commit();
if (SUCCEEDED(hr))
{
hr = write_hdr_chunks(image_path, width, height, luminance, quantization_bits, display) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
if (use_clipboard)
hr = copy_to_clipboard(image_path) ? S_OK : E_FAIL;
}
}
if (png_buffer != nullptr) _aligned_free(png_buffer);
if (rgba32_scanline != nullptr) _aligned_free(rgba32_scanline);
if (luminance != nullptr) _aligned_free(luminance);
return SUCCEEDED(hr);
}
#pragma pop_macro("_XM_F16C_INTRINSICS_")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment