//------------------------------------------------------------------------------------- // DirectXTexWIC.cpp // // DirectX Texture Library - WIC-based file reader/writer // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "directxtexp.h" using Microsoft::WRL::ComPtr; //------------------------------------------------------------------------------------- // IStream support for WIC Memory routines //------------------------------------------------------------------------------------- #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) #include #pragma comment(lib,"shcore.lib") #ifdef __cplusplus_winrt static inline HRESULT CreateMemoryStream( _Outptr_ IStream** stream ) { auto randomAccessStream = ref new ::Windows::Storage::Streams::InMemoryRandomAccessStream(); return CreateStreamOverRandomAccessStream( randomAccessStream, IID_PPV_ARGS( stream ) ); } #else #include #include #include static inline HRESULT CreateMemoryStream( _Outptr_ IStream** stream ) { Microsoft::WRL::ComPtr abiStream; HRESULT hr = Windows::Foundation::ActivateInstance( Microsoft::WRL::Wrappers::HStringReference( RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream ).Get(), abiStream.GetAddressOf() ); if (SUCCEEDED(hr)) { hr = CreateStreamOverRandomAccessStream( abiStream.Get(), IID_PPV_ARGS( stream ) ); } return hr; } #endif // __cplusplus_winrt #else #pragma prefast(suppress:28196, "a simple wrapper around an existing annotated function" ); static inline HRESULT CreateMemoryStream( _Outptr_ IStream** stream ) { return CreateStreamOnHGlobal( 0, TRUE, stream ); } #endif //------------------------------------------------------------------------------------- // WIC Pixel Format nearest conversion table //------------------------------------------------------------------------------------- struct WICConvert { GUID source; GUID target; }; static WICConvert g_WICConvert[] = { // Directly support the formats listed in XnaTexUtil::g_WICFormats, so no conversion required // Note target GUID in this conversion table must be one of those directly supported formats. { GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM { GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM { GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT { GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT { GUID_WICPixelFormat16bppBGR555, GUID_WICPixelFormat16bppBGRA5551 }, // DXGI_FORMAT_B5G5R5A1_UNORM { GUID_WICPixelFormat32bppBGR101010, GUID_WICPixelFormat32bppRGBA1010102 }, // DXGI_FORMAT_R10G10B10A2_UNORM { GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat48bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat48bppBGR, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat64bppBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat64bppPRGBA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat64bppPBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT { GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT { GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT { GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT { GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT { GUID_WICPixelFormat32bppRGBE, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT { GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat64bppCMYK, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat40bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat80bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE) { GUID_WICPixelFormat32bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat64bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT #endif // We don't support n-channel formats }; namespace DirectX { //------------------------------------------------------------------------------------- // Returns the DXGI format and optionally the WIC pixel GUID to convert to //------------------------------------------------------------------------------------- static DXGI_FORMAT _DetermineFormat( _In_ const WICPixelFormatGUID& pixelFormat, _In_ DWORD flags, _In_ bool iswic2, _Out_opt_ WICPixelFormatGUID* pConvert ) { if ( pConvert ) memset( pConvert, 0, sizeof(WICPixelFormatGUID) ); DXGI_FORMAT format = _WICToDXGI( pixelFormat ); if ( format == DXGI_FORMAT_UNKNOWN ) { if ( memcmp( &GUID_WICPixelFormat96bppRGBFixedPoint, &pixelFormat, sizeof(WICPixelFormatGUID) ) == 0 ) { #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE) if ( iswic2 ) { if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat96bppRGBFloat, sizeof(WICPixelFormatGUID) ); format = DXGI_FORMAT_R32G32B32_FLOAT; } else #else UNREFERENCED_PARAMETER(iswic2); #endif { if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat128bppRGBAFloat, sizeof(WICPixelFormatGUID) ); format = DXGI_FORMAT_R32G32B32A32_FLOAT; } } else { for( size_t i=0; i < _countof(g_WICConvert); ++i ) { if ( memcmp( &g_WICConvert[i].source, &pixelFormat, sizeof(WICPixelFormatGUID) ) == 0 ) { if ( pConvert ) memcpy( pConvert, &g_WICConvert[i].target, sizeof(WICPixelFormatGUID) ); format = _WICToDXGI( g_WICConvert[i].target ); assert( format != DXGI_FORMAT_UNKNOWN ); break; } } } } // Handle special cases based on flags switch (format) { case DXGI_FORMAT_B8G8R8A8_UNORM: // BGRA case DXGI_FORMAT_B8G8R8X8_UNORM: // BGRX if ( flags & WIC_FLAGS_FORCE_RGB ) { format = DXGI_FORMAT_R8G8B8A8_UNORM; if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat32bppRGBA, sizeof(WICPixelFormatGUID) ); } break; case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: if ( flags & WIC_FLAGS_NO_X2_BIAS ) { format = DXGI_FORMAT_R10G10B10A2_UNORM; if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat32bppRGBA1010102, sizeof(WICPixelFormatGUID) ); } break; case DXGI_FORMAT_B5G5R5A1_UNORM: case DXGI_FORMAT_B5G6R5_UNORM: if ( flags & WIC_FLAGS_NO_16BPP ) { format = DXGI_FORMAT_R8G8B8A8_UNORM; if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat32bppRGBA, sizeof(WICPixelFormatGUID) ); } break; case DXGI_FORMAT_R1_UNORM: if ( !(flags & WIC_FLAGS_ALLOW_MONO ) ) { // By default we want to promote a black & white to gresycale since R1 is not a generally supported D3D format format = DXGI_FORMAT_R8_UNORM; if ( pConvert ) memcpy( pConvert, &GUID_WICPixelFormat8bppGray, sizeof(WICPixelFormatGUID) ); } } return format; } //------------------------------------------------------------------------------------- // Determines metadata for image //------------------------------------------------------------------------------------- static HRESULT _DecodeMetadata( _In_ DWORD flags, _In_ bool iswic2, _In_ IWICBitmapDecoder *decoder, _In_ IWICBitmapFrameDecode *frame, _Out_ TexMetadata& metadata, _Out_opt_ WICPixelFormatGUID* pConvert ) { if ( !decoder || !frame ) return E_POINTER; memset( &metadata, 0, sizeof(TexMetadata) ); metadata.depth = 1; metadata.mipLevels = 1; metadata.dimension = TEX_DIMENSION_TEXTURE2D; UINT w, h; HRESULT hr = frame->GetSize( &w, &h ); if ( FAILED(hr) ) return hr; metadata.width = w; metadata.height = h; if ( flags & WIC_FLAGS_ALL_FRAMES ) { UINT fcount; hr = decoder->GetFrameCount( &fcount ); if ( FAILED(hr) ) return hr; metadata.arraySize = fcount; } else metadata.arraySize = 1; WICPixelFormatGUID pixelFormat; hr = frame->GetPixelFormat( &pixelFormat ); if ( FAILED(hr) ) return hr; metadata.format = _DetermineFormat( pixelFormat, flags, iswic2, pConvert ); if ( metadata.format == DXGI_FORMAT_UNKNOWN ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); if ( !( flags & WIC_FLAGS_IGNORE_SRGB ) ) { GUID containerFormat; hr = decoder->GetContainerFormat( &containerFormat ); if ( FAILED(hr) ) return hr; ComPtr metareader; hr = frame->GetMetadataQueryReader( metareader.GetAddressOf() ); if ( SUCCEEDED(hr) ) { // Check for sRGB colorspace metadata bool sRGB = false; PROPVARIANT value; PropVariantInit( &value ); if ( memcmp( &containerFormat, &GUID_ContainerFormatPng, sizeof(GUID) ) == 0 ) { // Check for sRGB chunk if ( SUCCEEDED( metareader->GetMetadataByName( L"/sRGB/RenderingIntent", &value ) ) && value.vt == VT_UI1 ) { sRGB = true; } } #if defined(_XBOX_ONE) && defined(_TITLE) else if ( memcmp( &containerFormat, &GUID_ContainerFormatJpeg, sizeof(GUID) ) == 0 ) { if ( SUCCEEDED( metareader->GetMetadataByName( L"/app1/ifd/exif/{ushort=40961}", &value ) ) && value.vt == VT_UI2 && value.uiVal == 1 ) { sRGB = true; } } else if ( memcmp( &containerFormat, &GUID_ContainerFormatTiff, sizeof(GUID) ) == 0 ) { if ( SUCCEEDED( metareader->GetMetadataByName( L"/ifd/exif/{ushort=40961}", &value ) ) && value.vt == VT_UI2 && value.uiVal == 1 ) { sRGB = true; } } #else else if ( SUCCEEDED( metareader->GetMetadataByName( L"System.Image.ColorSpace", &value ) ) && value.vt == VT_UI2 && value.uiVal == 1 ) { sRGB = true; } #endif PropVariantClear( &value ); if ( sRGB ) metadata.format = MakeSRGB( metadata.format ); } else if ( hr == WINCODEC_ERR_UNSUPPORTEDOPERATION ) { // Some formats just don't support metadata (BMP, ICO, etc.), so ignore this failure hr = S_OK; } } return hr; } //------------------------------------------------------------------------------------- // Decodes a single frame //------------------------------------------------------------------------------------- static HRESULT _DecodeSingleFrame( _In_ DWORD flags, _In_ const TexMetadata& metadata, _In_ const WICPixelFormatGUID& convertGUID, _In_ IWICBitmapFrameDecode *frame, _Inout_ ScratchImage& image ) { if ( !frame ) return E_POINTER; HRESULT hr = image.Initialize2D( metadata.format, metadata.width, metadata.height, 1, 1 ); if ( FAILED(hr) ) return hr; const Image *img = image.GetImage( 0, 0, 0 ); if ( !img ) return E_POINTER; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; if ( memcmp( &convertGUID, &GUID_NULL, sizeof(GUID) ) == 0 ) { hr = frame->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } else { ComPtr FC; hr = pWIC->CreateFormatConverter( FC.GetAddressOf() ); if ( FAILED(hr) ) return hr; WICPixelFormatGUID pixelFormat; hr = frame->GetPixelFormat( &pixelFormat ); if ( FAILED(hr) ) return hr; BOOL canConvert = FALSE; hr = FC->CanConvert( pixelFormat, convertGUID, &canConvert ); if ( FAILED(hr) || !canConvert ) { return E_UNEXPECTED; } hr = FC->Initialize( frame, convertGUID, _GetWICDither( flags ), 0, 0, WICBitmapPaletteTypeCustom ); if ( FAILED(hr) ) return hr; hr = FC->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } return S_OK; } //------------------------------------------------------------------------------------- // Decodes an image array, resizing/format converting as needed //------------------------------------------------------------------------------------- static HRESULT _DecodeMultiframe( _In_ DWORD flags, _In_ const TexMetadata& metadata, _In_ IWICBitmapDecoder *decoder, _Inout_ ScratchImage& image ) { if ( !decoder ) return E_POINTER; HRESULT hr = image.Initialize2D( metadata.format, metadata.width, metadata.height, metadata.arraySize, 1 ); if ( FAILED(hr) ) return hr; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; WICPixelFormatGUID sourceGUID; if ( !_DXGIToWIC( metadata.format, sourceGUID ) ) return E_FAIL; for( size_t index = 0; index < metadata.arraySize; ++index ) { const Image* img = image.GetImage( 0, index, 0 ); if ( !img ) return E_POINTER; ComPtr frame; hr = decoder->GetFrame( static_cast( index ), frame.GetAddressOf() ); if ( FAILED(hr) ) return hr; WICPixelFormatGUID pfGuid; hr = frame->GetPixelFormat( &pfGuid ); if ( FAILED(hr) ) return hr; UINT w, h; hr = frame->GetSize( &w, &h ); if ( FAILED(hr) ) return hr; if ( w == metadata.width && h == metadata.height ) { // This frame does not need resized if ( memcmp( &pfGuid, &sourceGUID, sizeof(WICPixelFormatGUID) ) == 0 ) { hr = frame->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } else { ComPtr FC; hr = pWIC->CreateFormatConverter( FC.GetAddressOf() ); if ( FAILED(hr) ) return hr; BOOL canConvert = FALSE; hr = FC->CanConvert( pfGuid, sourceGUID, &canConvert ); if ( FAILED(hr) || !canConvert ) { return E_UNEXPECTED; } hr = FC->Initialize( frame.Get(), sourceGUID, _GetWICDither( flags ), 0, 0, WICBitmapPaletteTypeCustom ); if ( FAILED(hr) ) return hr; hr = FC->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } } else { // This frame needs resizing ComPtr scaler; hr = pWIC->CreateBitmapScaler( scaler.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = scaler->Initialize( frame.Get(), static_cast( metadata.width ), static_cast( metadata.height ), _GetWICInterp( flags ) ); if ( FAILED(hr) ) return hr; WICPixelFormatGUID pfScaler; hr = scaler->GetPixelFormat( &pfScaler ); if ( FAILED(hr) ) return hr; if ( memcmp( &pfScaler, &sourceGUID, sizeof(WICPixelFormatGUID) ) == 0 ) { hr = scaler->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } else { // The WIC bitmap scaler is free to return a different pixel format than the source image, so here we // convert it to our desired format ComPtr FC; hr = pWIC->CreateFormatConverter( FC.GetAddressOf() ); if ( FAILED(hr) ) return hr; BOOL canConvert = FALSE; hr = FC->CanConvert( pfScaler, sourceGUID, &canConvert ); if ( FAILED(hr) || !canConvert ) { return E_UNEXPECTED; } hr = FC->Initialize( scaler.Get(), sourceGUID, _GetWICDither( flags ), 0, 0, WICBitmapPaletteTypeCustom ); if ( FAILED(hr) ) return hr; hr = FC->CopyPixels( 0, static_cast( img->rowPitch ), static_cast( img->slicePitch ), img->pixels ); if ( FAILED(hr) ) return hr; } } } return S_OK; } //------------------------------------------------------------------------------------- // Encodes image metadata //------------------------------------------------------------------------------------- static HRESULT _EncodeMetadata( _In_ IWICBitmapFrameEncode* frame, _In_ const GUID& containerFormat, _In_ DXGI_FORMAT format ) { if ( !frame ) return E_POINTER; ComPtr metawriter; HRESULT hr = frame->GetMetadataQueryWriter( metawriter.GetAddressOf() ); if ( SUCCEEDED( hr ) ) { PROPVARIANT value; PropVariantInit( &value ); bool sRGB = IsSRGB( format ); value.vt = VT_LPSTR; value.pszVal = "DirectXTex"; if ( memcmp( &containerFormat, &GUID_ContainerFormatPng, sizeof(GUID) ) == 0 ) { // Set Software name (void)metawriter->SetMetadataByName( L"/tEXt/{str=Software}", &value ); // Set sRGB chunk if ( sRGB ) { value.vt = VT_UI1; value.bVal = 0; (void)metawriter->SetMetadataByName( L"/sRGB/RenderingIntent", &value ); } } #if defined(_XBOX_ONE) && defined(_TITLE) else if ( memcmp( &containerFormat, &GUID_ContainerFormatJpeg, sizeof(GUID) ) == 0 ) { // Set Software name (void)metawriter->SetMetadataByName( L"/app1/ifd/{ushort=305}", &value ); if ( sRGB ) { // Set EXIF Colorspace of sRGB value.vt = VT_UI2; value.uiVal = 1; (void)metawriter->SetMetadataByName( L"/app1/ifd/exif/{ushort=40961}", &value ); } } else if ( memcmp( &containerFormat, &GUID_ContainerFormatTiff, sizeof(GUID) ) == 0 ) { // Set Software name (void)metawriter->SetMetadataByName( L"/ifd/{ushort=305}", &value ); if ( sRGB ) { // Set EXIF Colorspace of sRGB value.vt = VT_UI2; value.uiVal = 1; (void)metawriter->SetMetadataByName( L"/ifd/exif/{ushort=40961}", &value ); } } #else else { // Set Software name (void)metawriter->SetMetadataByName( L"System.ApplicationName", &value ); if ( sRGB ) { // Set EXIF Colorspace of sRGB value.vt = VT_UI2; value.uiVal = 1; (void)metawriter->SetMetadataByName( L"System.Image.ColorSpace", &value ); } } #endif } else if ( hr == WINCODEC_ERR_UNSUPPORTEDOPERATION ) { // Some formats just don't support metadata (BMP, ICO, etc.), so ignore this failure hr = S_OK; } return hr; } //------------------------------------------------------------------------------------- // Encodes a single frame //------------------------------------------------------------------------------------- static HRESULT _EncodeImage( _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID containerFormat, _In_ IWICBitmapFrameEncode* frame, _In_opt_ IPropertyBag2* props, _In_opt_ const GUID* targetFormat ) { if ( !frame ) return E_INVALIDARG; if ( !image.pixels ) return E_POINTER; WICPixelFormatGUID pfGuid; if ( !_DXGIToWIC( image.format, pfGuid ) ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); HRESULT hr = frame->Initialize( props ); if ( FAILED(hr) ) return hr; #ifdef _M_X64 if ( (image.width > 0xFFFFFFFF) || (image.height > 0xFFFFFFFF) ) return E_INVALIDARG; #endif hr = frame->SetSize( static_cast( image.width ), static_cast( image.height ) ); if ( FAILED(hr) ) return hr; hr = frame->SetResolution( 72, 72 ); if ( FAILED(hr) ) return hr; WICPixelFormatGUID targetGuid = (targetFormat) ? (*targetFormat) : pfGuid; hr = frame->SetPixelFormat( &targetGuid ); if ( FAILED(hr) ) return hr; if ( targetFormat && memcmp( targetFormat, &targetGuid, sizeof(WICPixelFormatGUID) ) != 0 ) { // Requested output pixel format is not supported by the WIC codec return E_FAIL; } hr = _EncodeMetadata( frame, containerFormat, image.format ); if ( FAILED(hr) ) return hr; if ( memcmp( &targetGuid, &pfGuid, sizeof(WICPixelFormatGUID) ) != 0 ) { // Conversion required to write bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr source; hr = pWIC->CreateBitmapFromMemory( static_cast( image.width ), static_cast( image.height ), pfGuid, static_cast( image.rowPitch ), static_cast( image.slicePitch ), image.pixels, source.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr FC; hr = pWIC->CreateFormatConverter( FC.GetAddressOf() ); if ( FAILED(hr) ) return hr; BOOL canConvert = FALSE; hr = FC->CanConvert( pfGuid, targetGuid, &canConvert ); if ( FAILED(hr) || !canConvert ) { return E_UNEXPECTED; } hr = FC->Initialize( source.Get(), targetGuid, _GetWICDither( flags ), 0, 0, WICBitmapPaletteTypeCustom ); if ( FAILED(hr) ) return hr; WICRect rect = { 0, 0, static_cast( image.width ), static_cast( image.height ) }; hr = frame->WriteSource( FC.Get(), &rect ); if ( FAILED(hr) ) return hr; } else { // No conversion required hr = frame->WritePixels( static_cast( image.height ), static_cast( image.rowPitch ), static_cast( image.slicePitch ), reinterpret_cast( image.pixels ) ); if ( FAILED(hr) ) return hr; } hr = frame->Commit(); if ( FAILED(hr) ) return hr; return S_OK; } static HRESULT _EncodeSingleFrame( _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID containerFormat, _Inout_ IStream* stream, _In_opt_ const GUID* targetFormat, _In_opt_ std::function setCustomProps ) { if ( !stream ) return E_INVALIDARG; // Initialize WIC bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr encoder; HRESULT hr = pWIC->CreateEncoder( containerFormat, 0, encoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = encoder->Initialize( stream, WICBitmapEncoderNoCache ); if ( FAILED(hr) ) return hr; ComPtr frame; ComPtr props; hr = encoder->CreateNewFrame( frame.GetAddressOf(), props.GetAddressOf() ); if ( FAILED(hr) ) return hr; if ( memcmp( &containerFormat, &GUID_ContainerFormatBmp, sizeof(WICPixelFormatGUID) ) == 0 && iswic2 ) { // Opt-in to the WIC2 support for writing 32-bit Windows BMP files with an alpha channel PROPBAG2 option = { 0 }; option.pstrName = L"EnableV5Header32bppBGRA"; VARIANT varValue; varValue.vt = VT_BOOL; varValue.boolVal = VARIANT_TRUE; (void)props->Write( 1, &option, &varValue ); } if ( setCustomProps ) { setCustomProps( props.Get() ); } hr = _EncodeImage( image, flags, containerFormat, frame.Get(), props.Get(), targetFormat ); if ( FAILED(hr) ) return hr; hr = encoder->Commit(); if ( FAILED(hr) ) return hr; return S_OK; } //------------------------------------------------------------------------------------- // Encodes an image array //------------------------------------------------------------------------------------- static HRESULT _EncodeMultiframe( _In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ DWORD flags, _In_ REFGUID containerFormat, _Inout_ IStream* stream, _In_opt_ const GUID* targetFormat, _In_opt_ std::function setCustomProps ) { if ( !stream || nimages < 2 ) return E_INVALIDARG; if ( !images ) return E_POINTER; // Initialize WIC bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr encoder; HRESULT hr = pWIC->CreateEncoder( containerFormat, 0, encoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr einfo; hr = encoder->GetEncoderInfo( einfo.GetAddressOf() ); if ( FAILED(hr) ) return hr; BOOL mframe = FALSE; hr = einfo->DoesSupportMultiframe( &mframe ); if ( FAILED(hr) ) return hr; if ( !mframe ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); hr = encoder->Initialize( stream, WICBitmapEncoderNoCache ); if ( FAILED(hr) ) return hr; for( size_t index=0; index < nimages; ++index ) { ComPtr frame; ComPtr props; hr = encoder->CreateNewFrame( frame.GetAddressOf(), props.GetAddressOf() ); if ( FAILED(hr) ) return hr; if ( setCustomProps ) { setCustomProps( props.Get() ); } hr = _EncodeImage( images[index], flags, containerFormat, frame.Get(), props.Get(), targetFormat ); if ( FAILED(hr) ) return hr; } hr = encoder->Commit(); if ( FAILED(hr) ) return hr; return S_OK; } //===================================================================================== // Entry-points //===================================================================================== //------------------------------------------------------------------------------------- // Obtain metadata from WIC-supported file in memory //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT GetMetadataFromWICMemory( LPCVOID pSource, size_t size, DWORD flags, TexMetadata& metadata ) { if ( !pSource || size == 0 ) return E_INVALIDARG; #ifdef _M_X64 if ( size > 0xFFFFFFFF ) return HRESULT_FROM_WIN32( ERROR_FILE_TOO_LARGE ); #endif bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; // Create input stream for memory ComPtr stream; HRESULT hr = pWIC->CreateStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = stream->InitializeFromMemory( reinterpret_cast( const_cast( pSource ) ), static_cast( size ) ); if ( FAILED(hr) ) return hr; // Initialize WIC ComPtr decoder; hr = pWIC->CreateDecoderFromStream( stream.Get(), 0, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr frame; hr = decoder->GetFrame( 0, frame.GetAddressOf() ); if ( FAILED(hr) ) return hr; // Get metadata hr = _DecodeMetadata( flags, iswic2, decoder.Get(), frame.Get(), metadata, 0 ); if ( FAILED(hr) ) return hr; return S_OK; } //------------------------------------------------------------------------------------- // Obtain metadata from WIC-supported file on disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT GetMetadataFromWICFile( LPCWSTR szFile, DWORD flags, TexMetadata& metadata ) { if ( !szFile ) return E_INVALIDARG; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; // Initialize WIC ComPtr decoder; HRESULT hr = pWIC->CreateDecoderFromFilename( szFile, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr frame; hr = decoder->GetFrame( 0, frame.GetAddressOf() ); if ( FAILED(hr) ) return hr; // Get metadata hr = _DecodeMetadata( flags, iswic2, decoder.Get(), frame.Get(), metadata, 0 ); if ( FAILED(hr) ) return hr; return S_OK; } //------------------------------------------------------------------------------------- // Load a WIC-supported file in memory //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT LoadFromWICMemory( LPCVOID pSource, size_t size, DWORD flags, TexMetadata* metadata, ScratchImage& image ) { if ( !pSource || size == 0 ) return E_INVALIDARG; #ifdef _M_X64 if ( size > 0xFFFFFFFF ) return HRESULT_FROM_WIN32( ERROR_FILE_TOO_LARGE ); #endif bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; image.Release(); // Create input stream for memory ComPtr stream; HRESULT hr = pWIC->CreateStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = stream->InitializeFromMemory( reinterpret_cast( const_cast( pSource ) ), static_cast( size ) ); if ( FAILED(hr) ) return hr; // Initialize WIC ComPtr decoder; hr = pWIC->CreateDecoderFromStream( stream.Get(), 0, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr frame; hr = decoder->GetFrame( 0, frame.GetAddressOf() ); if ( FAILED(hr) ) return hr; // Get metadata TexMetadata mdata; WICPixelFormatGUID convertGUID = {0}; hr = _DecodeMetadata( flags, iswic2, decoder.Get(), frame.Get(), mdata, &convertGUID ); if ( FAILED(hr) ) return hr; if ( (mdata.arraySize > 1) && (flags & WIC_FLAGS_ALL_FRAMES) ) { hr = _DecodeMultiframe( flags, mdata, decoder.Get(), image ); } else { hr = _DecodeSingleFrame( flags, mdata, convertGUID, frame.Get(), image ); } if ( FAILED(hr) ) { image.Release(); return hr; } if ( metadata ) memcpy( metadata, &mdata, sizeof(TexMetadata) ); return S_OK; } //------------------------------------------------------------------------------------- // Load a WIC-supported file from disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT LoadFromWICFile( LPCWSTR szFile, DWORD flags, TexMetadata* metadata, ScratchImage& image ) { if ( !szFile ) return E_INVALIDARG; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; image.Release(); // Initialize WIC ComPtr decoder; HRESULT hr = pWIC->CreateDecoderFromFilename( szFile, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr frame; hr = decoder->GetFrame( 0, frame.GetAddressOf() ); if ( FAILED(hr) ) return hr; // Get metadata TexMetadata mdata; WICPixelFormatGUID convertGUID = {0}; hr = _DecodeMetadata( flags, iswic2, decoder.Get(), frame.Get(), mdata, &convertGUID ); if ( FAILED(hr) ) return hr; if ( (mdata.arraySize > 1) && (flags & WIC_FLAGS_ALL_FRAMES) ) { hr = _DecodeMultiframe( flags, mdata, decoder.Get(), image ); } else { hr = _DecodeSingleFrame( flags, mdata, convertGUID, frame.Get(), image ); } if ( FAILED(hr) ) { image.Release(); return hr; } if ( metadata ) memcpy( metadata, &mdata, sizeof(TexMetadata) ); return S_OK; } //------------------------------------------------------------------------------------- // Save a WIC-supported file to memory //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT SaveToWICMemory( const Image& image, DWORD flags, REFGUID containerFormat, Blob& blob, const GUID* targetFormat, std::function setCustomProps ) { if ( !image.pixels ) return E_POINTER; blob.Release(); ComPtr stream; HRESULT hr = CreateMemoryStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = _EncodeSingleFrame( image, flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); if ( FAILED(hr) ) return hr; // Copy stream data into blob STATSTG stat; hr = stream->Stat( &stat, STATFLAG_NONAME ); if ( FAILED(hr) ) return hr; if ( stat.cbSize.HighPart > 0 ) return HRESULT_FROM_WIN32( ERROR_FILE_TOO_LARGE ); hr = blob.Initialize( stat.cbSize.LowPart ); if ( FAILED(hr) ) return hr; LARGE_INTEGER li = { 0 }; hr = stream->Seek( li, STREAM_SEEK_SET, 0 ); if ( FAILED(hr) ) return hr; DWORD bytesRead; hr = stream->Read( blob.GetBufferPointer(), static_cast( blob.GetBufferSize() ), &bytesRead ); if ( FAILED(hr) ) return hr; if ( bytesRead != blob.GetBufferSize() ) return E_FAIL; return S_OK; } _Use_decl_annotations_ HRESULT SaveToWICMemory( const Image* images, size_t nimages, DWORD flags, REFGUID containerFormat, Blob& blob, const GUID* targetFormat, std::function setCustomProps ) { if ( !images || nimages == 0 ) return E_INVALIDARG; blob.Release(); ComPtr stream; HRESULT hr = CreateMemoryStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; if ( nimages > 1 ) hr = _EncodeMultiframe( images, nimages, flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); else hr = _EncodeSingleFrame( images[0], flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); if ( FAILED(hr) ) return hr; // Copy stream data into blob STATSTG stat; hr = stream->Stat( &stat, STATFLAG_NONAME ); if ( FAILED(hr) ) return hr; if ( stat.cbSize.HighPart > 0 ) return HRESULT_FROM_WIN32( ERROR_FILE_TOO_LARGE ); hr = blob.Initialize( stat.cbSize.LowPart ); if ( FAILED(hr) ) return hr; LARGE_INTEGER li = { 0 }; hr = stream->Seek( li, STREAM_SEEK_SET, 0 ); if ( FAILED(hr) ) return hr; DWORD bytesRead; hr = stream->Read( blob.GetBufferPointer(), static_cast( blob.GetBufferSize() ), &bytesRead ); if ( FAILED(hr) ) return hr; if ( bytesRead != blob.GetBufferSize() ) return E_FAIL; return S_OK; } //------------------------------------------------------------------------------------- // Save a WIC-supported file to disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT SaveToWICFile( const Image& image, DWORD flags, REFGUID containerFormat, LPCWSTR szFile, const GUID* targetFormat, std::function setCustomProps ) { if ( !szFile ) return E_INVALIDARG; if ( !image.pixels ) return E_POINTER; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr stream; HRESULT hr = pWIC->CreateStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = stream->InitializeFromFilename( szFile, GENERIC_WRITE ); if ( FAILED(hr) ) return hr; hr = _EncodeSingleFrame( image, flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); if ( FAILED(hr) ) return hr; return S_OK; } _Use_decl_annotations_ HRESULT SaveToWICFile( const Image* images, size_t nimages, DWORD flags, REFGUID containerFormat, LPCWSTR szFile, const GUID* targetFormat, std::function setCustomProps ) { if ( !szFile || !images || nimages == 0 ) return E_INVALIDARG; bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr stream; HRESULT hr = pWIC->CreateStream( stream.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = stream->InitializeFromFilename( szFile, GENERIC_WRITE ); if ( FAILED(hr) ) return hr; if ( nimages > 1 ) hr = _EncodeMultiframe( images, nimages, flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); else hr = _EncodeSingleFrame( images[0], flags, containerFormat, stream.Get(), targetFormat, setCustomProps ); if ( FAILED(hr) ) return hr; return S_OK; } }; // namespace