Last active
April 29, 2024 00:06
-
-
Save mmozeiko/1f97a51db53999093ba5759c16c577d4 to your computer and use it in GitHub Desktop.
simple wrapper to load or save D3D11 texture from/to image file with Windows Imaging Component
This file contains 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
#pragma once | |
#define COBJMACROS | |
#include <windows.h> | |
#include <d3d11.h> | |
// | |
// interface | |
// | |
#define WIC_VFLIP (1<<0) // flip image vertically on load or save | |
#define WIC_LOAD_PMA (1<<1) // premultiply alpha on load | |
#define WIC_SAVE_NO_ALPHA (1<<2) // ignore alpha on save | |
#define WIC_SAVE_JPEG_444 (1<<3) // use 4:4:4 chroma subsampling for jpeg save | |
static void WicInit(void); | |
static void WicDone(void); | |
static ID3D11Texture2D* WicLoadTexture(LPCWSTR FileName, ID3D11Device* Device, DWORD LoadFlags); | |
// Quality is only for jpeg - from 0 to 1 (best) | |
static void WicSaveTexture(LPCWSTR FileName, ID3D11DeviceContext* Context, ID3D11Texture2D* Texture, UINT SubresourceIndex, float Quality, DWORD SaveFlags); | |
// | |
// implementation | |
// | |
#include <wincodec.h> | |
#include <shlwapi.h> | |
// replace this with your favorite ASSERT() implementation | |
#if !defined(ASSERT) | |
# include <intrin.h> | |
# define ASSERT(cond) do { if (!(cond)) __debugbreak(); } while (0) | |
#endif | |
#define HR(...) do { hr = __VA_ARGS__; ASSERT(SUCCEEDED(hr)); } while (0) | |
#pragma comment (lib, "ole32") | |
#pragma comment (lib, "shlwapi") | |
#pragma comment (lib, "windowscodecs") | |
static IWICImagingFactory* WicFactory; | |
void WicInit(void) | |
{ | |
HRESULT hr; | |
HR(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)); | |
HR(CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, (LPVOID*)&WicFactory)); | |
} | |
void WicDone(void) | |
{ | |
IWICImagingFactory_Release(WicFactory); | |
CoUninitialize(); | |
} | |
ID3D11Texture2D* WicLoadTexture(LPCWSTR FileName, ID3D11Device* Device, DWORD LoadFlags) | |
{ | |
HRESULT hr; | |
IWICBitmapDecoder* Decoder; | |
ID3D11Texture2D* Texture; | |
hr = IWICImagingFactory_CreateDecoderFromFilename(WicFactory, FileName, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &Decoder); | |
if (SUCCEEDED(hr)) | |
{ | |
IWICBitmapFrameDecode* Frame; | |
HR(IWICBitmapDecoder_GetFrame(Decoder, 0, &Frame)); | |
WICPixelFormatGUID NativeFormat; | |
HR(IWICBitmapFrameDecode_GetPixelFormat(Frame, &NativeFormat)); | |
DXGI_FORMAT DxgiFormat; | |
WICPixelFormatGUID ConvertFormat; | |
BOOL PremultipliedAlpha = !!(LoadFlags & WIC_LOAD_PMA); | |
if (IsEqualGUID(&NativeFormat, &GUID_WICPixelFormatBlackWhite) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat2bppGray) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat4bppGray) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat8bppGray)) | |
{ | |
DxgiFormat = DXGI_FORMAT_R8_UNORM; | |
ConvertFormat = GUID_WICPixelFormat8bppGray; | |
} | |
else if (IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat24bppRGB) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat24bppBGR) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat32bppRGB) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat32bppBGR)) | |
{ | |
DxgiFormat = DXGI_FORMAT_B8G8R8X8_UNORM; | |
ConvertFormat = GUID_WICPixelFormat32bppBGR; | |
} | |
else if (IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat32bppBGRA) | |
|| IsEqualGUID(&NativeFormat, &GUID_WICPixelFormat32bppPBGRA)) | |
{ | |
DxgiFormat = DXGI_FORMAT_B8G8R8A8_UNORM; | |
ConvertFormat = PremultipliedAlpha ? GUID_WICPixelFormat32bppPBGRA : GUID_WICPixelFormat32bppBGRA; | |
} | |
else // other formats, including GUID_WICPixelFormat32bppRGBA and GUID_WICPixelFormat32bppPRGBA | |
{ | |
DxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; | |
ConvertFormat = PremultipliedAlpha ? GUID_WICPixelFormat32bppPRGBA : GUID_WICPixelFormat32bppRGBA; | |
} | |
IWICBitmapSource* Source; | |
if (IsEqualGUID(&NativeFormat, &ConvertFormat)) | |
{ | |
HR(IWICBitmapFrameDecode_QueryInterface(Frame, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
} | |
else | |
{ | |
IWICFormatConverter* Converter; | |
HR(IWICImagingFactory_CreateFormatConverter(WicFactory, &Converter)); | |
HR(IWICFormatConverter_Initialize(Converter, (IWICBitmapSource*)Frame, &ConvertFormat, WICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeCustom)); | |
HR(IWICFormatConverter_QueryInterface(Converter, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICFormatConverter_Release(Converter); | |
} | |
if (LoadFlags & WIC_VFLIP) | |
{ | |
IWICBitmapFlipRotator* Flip; | |
HR(IWICImagingFactory_CreateBitmapFlipRotator(WicFactory, &Flip)); | |
HR(IWICBitmapFlipRotator_Initialize(Flip, Source, WICBitmapTransformFlipVertical)); | |
IWICBitmapSource_Release(Source); | |
HR(IWICBitmapFlipRotator_QueryInterface(Flip, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICBitmapFlipRotator_Release(Flip); | |
} | |
IWICBitmap* Bitmap; | |
HR(IWICImagingFactory_CreateBitmapFromSource(WicFactory, Source, WICBitmapCacheOnDemand, &Bitmap)); | |
UINT Width, Height; | |
HR(IWICBitmapSource_GetSize(Source, &Width, &Height)); | |
IWICBitmapLock* Lock; | |
HR(IWICBitmap_Lock(Bitmap, NULL, WICBitmapLockRead, &Lock)); | |
UINT DataSize; | |
BYTE* DataPtr; | |
HR(IWICBitmapLock_GetDataPointer(Lock, &DataSize, &DataPtr)); | |
UINT DataStride; | |
HR(IWICBitmapLock_GetStride(Lock, &DataStride)); | |
D3D11_TEXTURE2D_DESC Desc = | |
{ | |
.Width = Width, | |
.Height = Height, | |
.MipLevels = 1, | |
.ArraySize = 1, | |
.Format = DxgiFormat, | |
.SampleDesc = { 1, 0 }, | |
.Usage = D3D11_USAGE_IMMUTABLE, | |
.BindFlags = D3D11_BIND_SHADER_RESOURCE, | |
}; | |
D3D11_SUBRESOURCE_DATA TextureData = | |
{ | |
.pSysMem = DataPtr, | |
.SysMemPitch = DataStride, | |
}; | |
HR(ID3D11Device_CreateTexture2D(Device, &Desc, &TextureData, &Texture)); | |
IWICBitmapLock_Release(Lock); | |
IWICBitmap_Release(Bitmap); | |
IWICBitmapSource_Release(Source); | |
IWICBitmapFrameDecode_Release(Frame); | |
IWICBitmapDecoder_Release(Decoder); | |
} | |
else | |
{ | |
Texture = NULL; | |
} | |
return Texture; | |
} | |
void WicSaveTexture(LPCWSTR FileName, ID3D11DeviceContext* Context, ID3D11Texture2D* Texture, UINT SubresourceIndex, float Quality, DWORD SaveFlags) | |
{ | |
HRESULT hr; | |
D3D11_TEXTURE2D_DESC Desc; | |
ID3D11Texture2D_GetDesc(Texture, &Desc); | |
ID3D11Texture2D* CpuTexture; | |
if (Desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) | |
{ | |
CpuTexture = Texture; | |
ID3D11Texture2D_AddRef(Texture); | |
} | |
else | |
{ | |
ID3D11Device* Device; | |
ID3D11DeviceContext_GetDevice(Context, &Device); | |
D3D11_TEXTURE2D_DESC CpuDesc = | |
{ | |
.Width = Desc.Width, | |
.Height = Desc.Height, | |
.MipLevels = 1, | |
.ArraySize = 1, | |
.Format = Desc.Format, | |
.SampleDesc = { 1, 0 }, | |
.Usage = D3D11_USAGE_STAGING, | |
.CPUAccessFlags = D3D11_CPU_ACCESS_READ, | |
}; | |
HR(ID3D11Device_CreateTexture2D(Device, &CpuDesc, NULL, &CpuTexture)); | |
ID3D11DeviceContext_CopySubresourceRegion(Context, (ID3D11Resource*)CpuTexture, 0, 0, 0, 0, (ID3D11Resource*)Texture, SubresourceIndex, NULL); | |
SubresourceIndex = 0; | |
ID3D11Device_Release(Device); | |
} | |
D3D11_MAPPED_SUBRESOURCE Mapped; | |
HR(ID3D11DeviceContext_Map(Context, (ID3D11Resource*)CpuTexture, SubresourceIndex, D3D11_MAP_READ, 0, &Mapped)); | |
IWICStream* Stream; | |
HR(IWICImagingFactory_CreateStream(WicFactory, &Stream)); | |
HR(IWICStream_InitializeFromFilename(Stream, FileName, GENERIC_WRITE)); | |
LPWSTR FileExtension = PathFindExtensionW(FileName); | |
BOOL IsBmp = StrCmpIW(FileExtension, L".bmp") == 0; | |
BOOL IsPng = StrCmpIW(FileExtension, L".png") == 0; | |
BOOL IsJpeg = StrCmpIW(FileExtension, L".jpg") == 0 || StrCmpIW(FileExtension, L".jpeg") == 0; | |
BOOL IsHeif = StrCmpIW(FileExtension, L".heif") == 0 || StrCmpIW(FileExtension, L".heic") == 0; | |
ASSERT(IsBmp || IsPng || IsJpeg || IsHeif); | |
GUID Container = IsBmp ? GUID_ContainerFormatBmp : IsPng ? GUID_ContainerFormatPng : IsJpeg ? GUID_ContainerFormatJpeg : GUID_ContainerFormatHeif; | |
IWICBitmapEncoder* Encoder; | |
HR(IWICImagingFactory_CreateEncoder(WicFactory, &Container, NULL, &Encoder)); | |
HR(IWICBitmapEncoder_Initialize(Encoder, (IStream*)Stream, WICBitmapEncoderNoCache)); | |
IWICBitmapFrameEncode* Frame; | |
IPropertyBag2* Options = NULL; | |
HR(IWICBitmapEncoder_CreateNewFrame(Encoder, &Frame, &Options)); | |
BOOL IgnoreAlpha = !!(SaveFlags & WIC_SAVE_NO_ALPHA); | |
WICPixelFormatGUID NativeFormat; | |
if (Desc.Format == DXGI_FORMAT_R8_UNORM) | |
{ | |
NativeFormat = GUID_WICPixelFormat8bppGray; | |
} | |
else if (Desc.Format == DXGI_FORMAT_B8G8R8X8_UNORM) | |
{ | |
NativeFormat = GUID_WICPixelFormat32bppBGR; | |
} | |
else if (Desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) | |
{ | |
NativeFormat = IgnoreAlpha ? GUID_WICPixelFormat32bppRGB : GUID_WICPixelFormat32bppRGBA; | |
} | |
else if (Desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) | |
{ | |
NativeFormat = IgnoreAlpha ? GUID_WICPixelFormat32bppBGR : GUID_WICPixelFormat32bppBGRA; | |
} | |
else | |
{ | |
ASSERT(!"unsupported texture format"); | |
NativeFormat = GUID_NULL; | |
} | |
if (IsBmp && (Desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || Desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM)) | |
{ | |
PROPBAG2 Key = { .pstrName = L"EnableV5Header32bppBGRA" }; | |
VARIANT Value = | |
{ | |
.vt = VT_BOOL, | |
.boolVal = VARIANT_TRUE, | |
}; | |
HR(IPropertyBag2_Write(Options, 1, &Key, &Value)); | |
} | |
else if (IsPng) | |
{ | |
// only use "up" filter for faster performance, with minor compression hit | |
PROPBAG2 Key = { .pstrName = L"FilterOption" }; | |
VARIANT Value = | |
{ | |
.vt = VT_UI1, | |
.bVal = WICPngFilterUp, | |
}; | |
HR(IPropertyBag2_Write(Options, 1, &Key, &Value)); | |
} | |
else if (IsJpeg && Quality != 0) | |
{ | |
PROPBAG2 Key = { .pstrName = L"ImageQuality" }; | |
VARIANT Value = | |
{ | |
.vt = VT_R4, | |
.fltVal = Quality, | |
}; | |
HR(IPropertyBag2_Write(Options, 1, &Key, &Value)); | |
if (SaveFlags & WIC_SAVE_JPEG_444) | |
{ | |
Key.pstrName = L"JpegYCrCbSubsampling"; | |
Value.vt = VT_UI1; | |
Value.bVal = WICJpegYCrCbSubsampling444; | |
HR(IPropertyBag2_Write(Options, 1, &Key, &Value)); | |
} | |
} | |
HR(IWICBitmapFrameEncode_Initialize(Frame, Options)); | |
HR(IWICBitmapFrameEncode_SetSize(Frame, Desc.Width, Desc.Height)); | |
if (IsBmp && Desc.Format == DXGI_FORMAT_R8_UNORM) | |
{ | |
NativeFormat = GUID_WICPixelFormat8bppIndexed; | |
IWICPalette* Palette; | |
HR(IWICImagingFactory_CreatePalette(WicFactory, &Palette)); | |
HR(IWICPalette_InitializePredefined(Palette, WICBitmapPaletteTypeFixedGray256, FALSE)); | |
HR(IWICBitmapEncoder_SetPalette(Frame, Palette)); | |
IWICPalette_Release(Palette); | |
} | |
GUID ConvertFormat = NativeFormat; | |
HR(IWICBitmapFrameEncode_SetPixelFormat(Frame, &ConvertFormat)); | |
IWICBitmapSource* Source = NULL; | |
if (!IsEqualGUID(&NativeFormat, &ConvertFormat)) | |
{ | |
if (!Source) | |
{ | |
IWICBitmap* Bitmap; | |
HR(IWICImagingFactory_CreateBitmapFromMemory(WicFactory, Desc.Width, Desc.Height, &NativeFormat, Mapped.RowPitch, Mapped.RowPitch * Desc.Height, Mapped.pData, &Bitmap)); | |
HR(IWICBitmap_QueryInterface(Bitmap, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICBitmap_Release(Bitmap); | |
} | |
IWICFormatConverter* Converter; | |
HR(IWICImagingFactory_CreateFormatConverter(WicFactory, &Converter)); | |
HR(IWICFormatConverter_Initialize(Converter, Source, &ConvertFormat, WICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeCustom)); | |
IWICBitmapSource_Release(Source); | |
HR(IWICFormatConverter_QueryInterface(Converter, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICFormatConverter_Release(Converter); | |
} | |
if (SaveFlags & WIC_VFLIP) | |
{ | |
if (!Source) | |
{ | |
IWICBitmap* Bitmap; | |
HR(IWICImagingFactory_CreateBitmapFromMemory(WicFactory, Desc.Width, Desc.Height, &ConvertFormat, Mapped.RowPitch, Mapped.RowPitch * Desc.Height, Mapped.pData, &Bitmap)); | |
HR(IWICBitmap_QueryInterface(Bitmap, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICBitmap_Release(Bitmap); | |
} | |
IWICBitmapFlipRotator* Flip; | |
HR(IWICImagingFactory_CreateBitmapFlipRotator(WicFactory, &Flip)); | |
HR(IWICBitmapFlipRotator_Initialize(Flip, Source, WICBitmapTransformFlipVertical)); | |
IWICBitmapSource_Release(Source); | |
HR(IWICBitmapFlipRotator_QueryInterface(Flip, &IID_IWICBitmapSource, (LPVOID*)&Source)); | |
IWICBitmapFlipRotator_Release(Flip); | |
} | |
if (Source) | |
{ | |
HR(IWICBitmapFrameEncode_WriteSource(Frame, Source, NULL)); | |
IWICBitmapSource_Release(Source); | |
} | |
else | |
{ | |
HR(IWICBitmapFrameEncode_WritePixels(Frame, Desc.Height, Mapped.RowPitch, Mapped.RowPitch * Desc.Height, Mapped.pData)); | |
} | |
ID3D11DeviceContext_Unmap(Context, (ID3D11Resource*)CpuTexture, SubresourceIndex); | |
ID3D11Texture2D_Release(CpuTexture); | |
HR(IWICBitmapFrameEncode_Commit(Frame)); | |
HR(IWICBitmapEncoder_Commit(Encoder)); | |
HR(IWICStream_Commit(Stream, STGC_DEFAULT)); | |
IPropertyBag2_Release(Options); | |
IWICBitmapFrameEncode_Release(Frame); | |
IWICBitmapEncoder_Release(Encoder); | |
IWICStream_Release(Stream); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment