Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active April 29, 2024 00:06
Show Gist options
  • Save mmozeiko/1f97a51db53999093ba5759c16c577d4 to your computer and use it in GitHub Desktop.
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
#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