From c398ac1711758be7b040e4d70dd5dc25e1d0d4d1 Mon Sep 17 00:00:00 2001 From: walbourn_cp Date: Tue, 16 Apr 2013 17:51:36 -0700 Subject: [PATCH] DirectXTex: Resolve SRGB handling problems with conversions - Convert: TEX_FILTER_FORCE_NON_WIC, TEX_FILTER_FORCE_WIC, TEX_FILTER_RGB_COPY_RED/BLUE/GREEN - ComputeMSE flags for sRGB colorspace and channel ignore options --- DirectXTex/DirectXTex.h | 31 ++- DirectXTex/DirectXTexConvert.cpp | 421 ++++++++++++++++--------------- DirectXTex/DirectXTexMisc.cpp | 90 ++++++- DirectXTex/DirectXTexP.h | 2 + DirectXTex/DirectXTexTGA.cpp | 2 +- DirectXTex/DirectXTexUtil.cpp | 62 +++-- 6 files changed, 374 insertions(+), 234 deletions(-) diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index 166d07b..a6a7331 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -177,7 +177,7 @@ namespace DirectX // Loads 565, 5551, and 4444 formats as 8888 to avoid use of optional WDDM 1.2 formats WIC_FLAGS_ALLOW_MONO = 0x8, - // Loads 1-bit monochrome (black & white) as R1_UNORM rather than 8-bit greyscale + // Loads 1-bit monochrome (black & white) as R1_UNORM rather than 8-bit grayscale WIC_FLAGS_ALL_FRAMES = 0x10, // Loads all images in a multi-frame file, converting/resizing to match the first frame as needed, defaults to 0th frame otherwise @@ -373,6 +373,12 @@ namespace DirectX TEX_FILTER_SEPARATE_ALPHA = 0x100, // Resize color and alpha channel independently + TEX_FILTER_RGB_COPY_RED = 0x1000, + TEX_FILTER_RGB_COPY_GREEN = 0x2000, + TEX_FILTER_RGB_COPY_BLUE = 0x4000, + // When converting RGB to R, defaults to using grayscale. These flags indicate copying a specific channel instead + // When converting RGB to RG, defaults to copying RED | GREEN. These flags control which channels are selected instead. + TEX_FILTER_DITHER = 0x10000, // Use ordered 4x4 dithering for any required conversions TEX_FILTER_DITHER_DIFFUSION = 0x20000, @@ -390,6 +396,12 @@ namespace DirectX // sRGB <-> RGB for use in conversion operations // if the input format type is IsSRGB(), then SRGB_IN is on by default // if the output format type is IsSRGB(), then SRGB_OUT is on by default + + TEX_FILTER_FORCE_NON_WIC = 0x10000000, + // Forces use of the non-WIC path when both are an option + + TEX_FILTER_FORCE_WIC = 0x20000000, + // Forces use of the WIC path even when logic would have picked a non-WIC path when both are an option }; HRESULT Resize( _In_ const Image& srcImage, _In_ size_t width, _In_ size_t height, _In_ DWORD filter, @@ -501,7 +513,22 @@ namespace DirectX HRESULT CopyRectangle( _In_ const Image& srcImage, _In_ const Rect& srcRect, _In_ const Image& dstImage, _In_ DWORD filter, _In_ size_t xOffset, _In_ size_t yOffset ); - HRESULT ComputeMSE( _In_ const Image& image1, _In_ const Image& image2, _Out_ float& mse, _Out_writes_opt_(4) float* mseV ); + enum CMSE_FLAGS + { + CMSE_DEFAULT = 0, + + CMSE_IMAGE1_SRGB = 0x1, + CMSE_IMAGE2_SRGB = 0x2, + // Indicates that image needs gamma correction before comparision + + CMSE_IGNORE_RED = 0x10, + CMSE_IGNORE_GREEN = 0x20, + CMSE_IGNORE_BLUE = 0x40, + CMSE_IGNORE_ALPHA = 0x80, + // Ignore the channel when computing MSE + }; + + HRESULT ComputeMSE( _In_ const Image& image1, _In_ const Image& image2, _Out_ float& mse, _Out_writes_opt_(4) float* mseV, _In_ DWORD flags = 0 ); //--------------------------------------------------------------------------------- // Direct3D 11 functions diff --git a/DirectXTex/DirectXTexConvert.cpp b/DirectXTex/DirectXTexConvert.cpp index 29a4a3a..59fba9b 100644 --- a/DirectXTex/DirectXTexConvert.cpp +++ b/DirectXTex/DirectXTexConvert.cpp @@ -27,6 +27,7 @@ namespace { inline float round_to_nearest( float x ) { + // Round to nearest (even) float i = floorf(x); x -= i; if(x < 0.5f) @@ -47,6 +48,7 @@ namespace namespace DirectX { +static const XMVECTORF32 g_Grayscale = { 0.2125f, 0.7154f, 0.0721f, 0.0f }; //------------------------------------------------------------------------------------- // Copies an image row with optional clearing of alpha value to 1.0 @@ -2028,12 +2030,6 @@ void _ConvertScanline( XMVECTOR* pBuffer, size_t count, DXGI_FORMAT outFormat, D if ( IsSRGB( outFormat ) ) flags |= TEX_FILTER_SRGB_OUT; - if ( in->flags & CONVF_SNORM ) - flags &= ~TEX_FILTER_SRGB_IN; - - if ( out->flags & CONVF_SNORM ) - flags &= ~TEX_FILTER_SRGB_OUT; - if ( (flags & (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT)) == (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT) ) { flags &= ~(TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT); @@ -2049,6 +2045,8 @@ void _ConvertScanline( XMVECTOR* pBuffer, size_t count, DXGI_FORMAT outFormat, D { // rgb = rgb^(2.2); a=a XMVECTOR v = *ptr; + // Use table instead of XMVectorPow( v, [2.2f 2.2f 2.2f 1]). + // Note table lookup will also saturate the result XMVECTOR v1 = _TableDecodeGamma22( v ); *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); } @@ -2131,15 +2129,113 @@ void _ConvertScanline( XMVECTOR* pBuffer, size_t count, DXGI_FORMAT outFormat, D *ptr++ = XMVectorSplatW( v ); } } - else if ( ((in->flags & CONVF_RGB_MASK) == CONVF_R) && ((out->flags & CONVF_RGB_MASK) == (CONVF_R|CONVF_G|CONVF_B)) ) + else if ( (in->flags & CONVF_RGB_MASK) == CONVF_R ) { - // R format -> RGB format - XMVECTOR* ptr = pBuffer; - for( size_t i=0; i < count; ++i ) + if ( (out->flags & CONVF_RGB_MASK) == (CONVF_R|CONVF_G|CONVF_B) ) { - XMVECTOR v = *ptr; - XMVECTOR v1 = XMVectorSplatX( v ); - *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); + // R format -> RGB format + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSplatX( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); + } + } + else if ( (out->flags & CONVF_RGB_MASK) == (CONVF_R|CONVF_G) ) + { + // R format -> RG format + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSplatX( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1100 ); + } + } + } + else if ( (in->flags & CONVF_RGB_MASK) == (CONVF_R|CONVF_G|CONVF_B) ) + { + if ( (out->flags & CONVF_RGB_MASK) == CONVF_R ) + { + // RGB format -> R format + switch( flags & ( TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_GREEN | TEX_FILTER_RGB_COPY_BLUE ) ) + { + case TEX_FILTER_RGB_COPY_RED: + // Leave data unchanged and the store will handle this... + break; + + case TEX_FILTER_RGB_COPY_GREEN: + { + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSplatY( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); + } + } + break; + + case TEX_FILTER_RGB_COPY_BLUE: + { + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSplatZ( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); + } + } + break; + + default: + { + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVector3Dot( v, g_Grayscale ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); + } + } + break; + } + } + else if ( (out->flags & CONVF_RGB_MASK) == (CONVF_R|CONVF_G) ) + { + // RGB format -> RG format + switch( flags & ( TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_GREEN | TEX_FILTER_RGB_COPY_BLUE ) ) + { + case TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_BLUE: + { + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSwizzle<0,2,0,2>( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1100 ); + } + } + break; + + case TEX_FILTER_RGB_COPY_GREEN | TEX_FILTER_RGB_COPY_BLUE: + { + XMVECTOR* ptr = pBuffer; + for( size_t i=0; i < count; ++i ) + { + XMVECTOR v = *ptr; + XMVECTOR v1 = XMVectorSwizzle<1,2,3,0>( v ); + *ptr++ = XMVectorSelect( v, v1, g_XMSelect1100 ); + } + } + break; + + case TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_GREEN: + default: + // Leave data unchanged and the store will handle this... + break; + } } } } @@ -2155,6 +2251,8 @@ void _ConvertScanline( XMVECTOR* pBuffer, size_t count, DXGI_FORMAT outFormat, D // rgb = rgb^(1/2.2); a=a XMVECTOR v = *ptr; XMVECTOR v1 = _TableEncodeGamma22( v ); + // Use table instead of XMVectorPow( v, [1/2.2f 1/2.2f 1/2.2f 1]). + // Note table lookup will also saturate the result *ptr++ = XMVectorSelect( v, v1, g_XMSelect1110 ); } } @@ -2162,6 +2260,100 @@ void _ConvertScanline( XMVECTOR* pBuffer, size_t count, DXGI_FORMAT outFormat, D } +//------------------------------------------------------------------------------------- +// Selection logic for using WIC vs. our own routines +//------------------------------------------------------------------------------------- +static inline bool _UseWICConversion( _In_ DWORD filter, _In_ DXGI_FORMAT sformat, _In_ DXGI_FORMAT tformat, + _Out_ WICPixelFormatGUID& pfGUID, _Out_ WICPixelFormatGUID& targetGUID ) +{ + memcpy( &pfGUID, &GUID_NULL, sizeof(GUID) ); + memcpy( &targetGUID, &GUID_NULL, sizeof(GUID) ); + + if ( filter & TEX_FILTER_FORCE_NON_WIC ) + { + // Explicit flag indicates use of non-WIC code paths + return false; + } + + if ( !_DXGIToWIC( sformat, pfGUID ) || !_DXGIToWIC( tformat, targetGUID ) ) + { + // Source or target format are not WIC supported native pixel formats + return false; + } + + if ( filter & TEX_FILTER_FORCE_WIC ) + { + // Explicit flag to use WIC code paths, skips all the case checks below + return true; + } + + // Check for special cases + switch ( sformat ) + { + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + switch( tformat ) + { + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_D32_FLOAT: + // WIC converts via UNORM formats and ends up converting colorspaces for these cases + case DXGI_FORMAT_A8_UNORM: + // Conversion logic for these kinds of textures is unintuitive for WIC code paths + return false; + } + break; + + case DXGI_FORMAT_R16_FLOAT: + switch( tformat ) + { + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_D32_FLOAT: + // WIC converts via UNORM formats and ends up converting colorspaces for these cases + case DXGI_FORMAT_A8_UNORM: + // Conversion logic for these kinds of textures is unintuitive for WIC code paths + return false; + } + break; + + case DXGI_FORMAT_A8_UNORM: + // Conversion logic for these kinds of textures is unintuitive for WIC code paths + return false; + + default: + switch( tformat ) + { + case DXGI_FORMAT_A8_UNORM: + // Conversion logic for these kinds of textures is unintuitive for WIC code paths + return false; + } + } + + // Check for implicit color space changes + if ( IsSRGB( sformat ) ) + filter |= TEX_FILTER_SRGB_IN; + + if ( IsSRGB( tformat ) ) + filter |= TEX_FILTER_SRGB_OUT; + + if ( (filter & (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT)) == (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT) ) + { + filter &= ~(TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT); + } + + DWORD wicsrgb = _CheckWICColorSpace( pfGUID, targetGUID ); + + if ( wicsrgb != (filter & (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT)) ) + { + // WIC will perform a colorspace conversion we didn't request + return false; + } + + return true; +} + + //------------------------------------------------------------------------------------- // Convert the source image using WIC //------------------------------------------------------------------------------------- @@ -2181,7 +2373,8 @@ static HRESULT _ConvertUsingWIC( _In_ const Image& srcImage, _In_ const WICPixel if ( FAILED(hr) ) return hr; -// Need to add logic to use TEX_FILTER_SRGB_IN/TEX_FILTER_SRGB_OUT + // Note that WIC conversion ignores the TEX_FILTER_SRGB_IN and TEX_FILTER_SRGB_OUT flags, + // but also always assumes UNORM <-> FLOAT conversions are changing color spaces sRGB <-> scRGB BOOL canConvert = FALSE; hr = FC->CanConvert( pfGUID, targetGUID, &canConvert ); @@ -2210,151 +2403,6 @@ static HRESULT _ConvertUsingWIC( _In_ const Image& srcImage, _In_ const WICPixel } -//------------------------------------------------------------------------------------- -// Convert the source using WIC and then convert to DXGI format from there -//------------------------------------------------------------------------------------- -static HRESULT _ConvertFromWIC( _In_ const Image& srcImage, _In_ const WICPixelFormatGUID& pfGUID, - _In_ DWORD filter, _In_ float threshold, _In_ const Image& destImage ) -{ - assert( srcImage.width == destImage.width ); - assert( srcImage.height == destImage.height ); - - IWICImagingFactory* pWIC = _GetWIC(); - if ( !pWIC ) - return E_NOINTERFACE; - - ScopedObject FC; - HRESULT hr = pWIC->CreateFormatConverter( &FC ); - if ( FAILED(hr) ) - return hr; - - BOOL canConvert = FALSE; - hr = FC->CanConvert( pfGUID, GUID_WICPixelFormat128bppRGBAFloat, &canConvert ); - if ( FAILED(hr) || !canConvert ) - { - // This case is not an issue for the subset of WIC formats that map directly to DXGI - return E_UNEXPECTED; - } - - ScratchImage temp; - hr = temp.Initialize2D( DXGI_FORMAT_R32G32B32A32_FLOAT, srcImage.width, srcImage.height, 1, 1 ); - if ( FAILED(hr) ) - return hr; - - const Image *timg = temp.GetImage( 0, 0, 0 ); - if ( !timg ) - return E_POINTER; - - ScopedObject source; - hr = pWIC->CreateBitmapFromMemory( static_cast( srcImage.width ), static_cast( srcImage.height ), pfGUID, - static_cast( srcImage.rowPitch ), static_cast( srcImage.slicePitch ), - srcImage.pixels, &source ); - if ( FAILED(hr) ) - return hr; - - hr = FC->Initialize( source.Get(), GUID_WICPixelFormat128bppRGBAFloat, _GetWICDither( filter ), 0, threshold, WICBitmapPaletteTypeCustom ); - if ( FAILED(hr) ) - return hr; - - hr = FC->CopyPixels( 0, static_cast( timg->rowPitch ), static_cast( timg->slicePitch ), timg->pixels ); - if ( FAILED(hr) ) - return hr; - - // Perform conversion on temp image which is now in R32G32B32A32_FLOAT format to final image - uint8_t *pSrc = timg->pixels; - uint8_t *pDest = destImage.pixels; - if ( !pSrc || !pDest ) - return E_POINTER; - - for( size_t h = 0; h < srcImage.height; ++h ) - { - _ConvertScanline( reinterpret_cast(pSrc), srcImage.width, destImage.format, DXGI_FORMAT_R32G32B32A32_FLOAT, filter ); - - if ( !_StoreScanline( pDest, destImage.rowPitch, destImage.format, reinterpret_cast(pSrc), srcImage.width ) ) - return E_FAIL; - - pSrc += timg->rowPitch; - pDest += destImage.rowPitch; - } - - return S_OK; -} - - -//------------------------------------------------------------------------------------- -// Convert the source from DXGI format then use WIC to convert to final format -//------------------------------------------------------------------------------------- -static HRESULT _ConvertToWIC( _In_ const Image& srcImage, - _In_ const WICPixelFormatGUID& targetGUID, _In_ DWORD filter, _In_ float threshold, _In_ const Image& destImage ) -{ - assert( srcImage.width == destImage.width ); - assert( srcImage.height == destImage.height ); - - IWICImagingFactory* pWIC = _GetWIC(); - if ( !pWIC ) - return E_NOINTERFACE; - - ScopedObject FC; - HRESULT hr = pWIC->CreateFormatConverter( &FC ); - if ( FAILED(hr) ) - return hr; - - BOOL canConvert = FALSE; - hr = FC->CanConvert( GUID_WICPixelFormat128bppRGBAFloat, targetGUID, &canConvert ); - if ( FAILED(hr) || !canConvert ) - { - // This case is not an issue for the subset of WIC formats that map directly to DXGI - return E_UNEXPECTED; - } - - ScratchImage temp; - hr = temp.Initialize2D( DXGI_FORMAT_R32G32B32A32_FLOAT, srcImage.width, srcImage.height, 1, 1 ); - if ( FAILED(hr) ) - return hr; - - const Image *timg = temp.GetImage( 0, 0, 0 ); - if ( !timg ) - return E_POINTER; - - const uint8_t *pSrc = srcImage.pixels; - if ( !pSrc ) - return E_POINTER; - - uint8_t *pDest = timg->pixels; - if ( !pDest ) - return E_POINTER; - - for( size_t h = 0; h < srcImage.height; ++h ) - { - if ( !_LoadScanline( reinterpret_cast(pDest), srcImage.width, pSrc, srcImage.rowPitch, srcImage.format ) ) - return E_FAIL; - - _ConvertScanline( reinterpret_cast(pDest), srcImage.width, DXGI_FORMAT_R32G32B32A32_FLOAT, srcImage.format, filter ); - - pSrc += srcImage.rowPitch; - pDest += timg->rowPitch; - } - - // Perform conversion on temp image which is now in R32G32B32A32_FLOAT format - ScopedObject source; - hr = pWIC->CreateBitmapFromMemory( static_cast( timg->width ), static_cast( timg->height ), GUID_WICPixelFormat128bppRGBAFloat, - static_cast( timg->rowPitch ), static_cast( timg->slicePitch ), - timg->pixels, &source ); - if ( FAILED(hr) ) - return hr; - - hr = FC->Initialize( source.Get(), targetGUID, _GetWICDither( filter ), 0, threshold, WICBitmapPaletteTypeCustom ); - if ( FAILED(hr) ) - return hr; - - hr = FC->CopyPixels( 0, static_cast( destImage.rowPitch ), static_cast( destImage.slicePitch ), destImage.pixels ); - if ( FAILED(hr) ) - return hr; - - return S_OK; -} - - //------------------------------------------------------------------------------------- // Convert the source image (not using WIC) //------------------------------------------------------------------------------------- @@ -2427,34 +2475,14 @@ HRESULT Convert( const Image& srcImage, DXGI_FORMAT format, DWORD filter, float return E_POINTER; } - WICPixelFormatGUID pfGUID; - if ( _DXGIToWIC( srcImage.format, pfGUID ) ) + WICPixelFormatGUID pfGUID, targetGUID; + if ( _UseWICConversion( filter, srcImage.format, format, pfGUID, targetGUID ) ) { - WICPixelFormatGUID targetGUID; - if ( _DXGIToWIC( format, targetGUID ) ) - { - // Case 1: Both source and target formats are WIC supported - hr = _ConvertUsingWIC( srcImage, pfGUID, targetGUID, filter, threshold, *rimage ); - } - else - { - // Case 2: Source format is supported by WIC, but not the target format - hr = _ConvertFromWIC( srcImage, pfGUID, filter, threshold, *rimage ); - } + hr = _ConvertUsingWIC( srcImage, pfGUID, targetGUID, filter, threshold, *rimage ); } else { - WICPixelFormatGUID targetGUID; - if ( _DXGIToWIC( format, targetGUID ) ) - { - // Case 3: Source format is not supported by WIC, but does support the target format - hr = _ConvertToWIC( srcImage, targetGUID, filter, threshold, *rimage ); - } - else - { - // Case 4: Both source and target format are not supported by WIC - hr = _Convert( srcImage, filter, *rimage ); - } + hr = _Convert( srcImage, filter, *rimage ); } if ( FAILED(hr) ) @@ -2507,8 +2535,7 @@ HRESULT Convert( const Image* srcImages, size_t nimages, const TexMetadata& meta } WICPixelFormatGUID pfGUID, targetGUID; - bool wicpf = _DXGIToWIC( metadata.format, pfGUID ); - bool wictargetpf = _DXGIToWIC( format, targetGUID ); + bool usewic = _UseWICConversion( filter, metadata.format, format, pfGUID, targetGUID ); for( size_t index=0; index < nimages; ++index ) { @@ -2533,31 +2560,13 @@ HRESULT Convert( const Image* srcImages, size_t nimages, const TexMetadata& meta return E_FAIL; } - if ( wicpf ) + if ( usewic ) { - if ( wictargetpf ) - { - // Case 1: Both source and target formats are WIC supported - hr = _ConvertUsingWIC( src, pfGUID, targetGUID, filter, threshold, dst ); - } - else - { - // Case 2: Source format is supported by WIC, but not the target format - hr = _ConvertFromWIC( src, pfGUID, filter, threshold, dst ); - } + hr = _ConvertUsingWIC( src, pfGUID, targetGUID, filter, threshold, dst ); } else { - if ( wictargetpf ) - { - // Case 3: Source format is not supported by WIC, but does support the target format - hr = _ConvertToWIC( src, targetGUID, filter, threshold, dst ); - } - else - { - // Case 4: Both source and target format are not supported by WIC - hr = _Convert( src, filter, dst ); - } + hr = _Convert( src, filter, dst ); } if ( FAILED(hr) ) diff --git a/DirectXTex/DirectXTexMisc.cpp b/DirectXTex/DirectXTexMisc.cpp index 118ad0e..ac29864 100644 --- a/DirectXTex/DirectXTexMisc.cpp +++ b/DirectXTex/DirectXTexMisc.cpp @@ -17,10 +17,12 @@ namespace DirectX { +static const XMVECTORF32 g_Gamma22 = { 2.2f, 2.2f, 2.2f, 1.f }; //------------------------------------------------------------------------------------- static HRESULT _ComputeMSE( _In_ const Image& image1, _In_ const Image& image2, - _Out_ float& mse, _Out_writes_opt_(4) float* mseV ) + _Out_ float& mse, _Out_writes_opt_(4) float* mseV, + _In_ DWORD flags ) { if ( !image1.pixels || !image2.pixels ) return E_POINTER; @@ -34,13 +36,54 @@ static HRESULT _ComputeMSE( _In_ const Image& image1, _In_ const Image& image2, if ( !scanline ) return E_OUTOFMEMORY; + // Flags implied from image formats + switch( image1.format ) + { + case DXGI_FORMAT_B8G8R8X8_UNORM: + flags |= CMSE_IGNORE_ALPHA; + break; + + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + flags |= CMSE_IMAGE1_SRGB | CMSE_IGNORE_ALPHA; + break; + + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_BC7_UNORM_SRGB: + flags |= CMSE_IMAGE1_SRGB; + break; + } + + switch( image2.format ) + { + case DXGI_FORMAT_B8G8R8X8_UNORM: + flags |= CMSE_IGNORE_ALPHA; + break; + + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + flags |= CMSE_IMAGE2_SRGB | CMSE_IGNORE_ALPHA; + break; + + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_BC7_UNORM_SRGB: + flags |= CMSE_IMAGE2_SRGB; + break; + } + const uint8_t *pSrc1 = image1.pixels; const size_t rowPitch1 = image1.rowPitch; const uint8_t *pSrc2 = image2.pixels; const size_t rowPitch2 = image2.rowPitch; - XMVECTOR acc = XMVectorZero(); + XMVECTOR acc = g_XMZero; for( size_t h = 0; h < image1.height; ++h ) { @@ -52,10 +95,39 @@ static HRESULT _ComputeMSE( _In_ const Image& image1, _In_ const Image& image2, if ( !_LoadScanline( ptr2, width, pSrc2, rowPitch2, image2.format ) ) return E_FAIL; - for( size_t i = 0; i < width; ++i, ++ptr1, ++ptr2 ) + for( size_t i = 0; i < width; ++i ) { + XMVECTOR v1 = *(ptr1++); + if ( flags & CMSE_IMAGE1_SRGB ) + { + v1 = XMVectorPow( v1, g_Gamma22 ); + } + + XMVECTOR v2 = *(ptr2++); + if ( flags & CMSE_IMAGE2_SRGB ) + { + v2 = XMVectorPow( v2, g_Gamma22 ); + } + // sum[ (I1 - I2)^2 ] - XMVECTOR v = XMVectorSubtract( *ptr1, *ptr2 ); + XMVECTOR v = XMVectorSubtract( v1, v2 ); + if ( flags & CMSE_IGNORE_RED ) + { + v = XMVectorSelect( v, g_XMZero, g_XMMaskX ); + } + if ( flags & CMSE_IGNORE_GREEN ) + { + v = XMVectorSelect( v, g_XMZero, g_XMMaskY ); + } + if ( flags & CMSE_IGNORE_BLUE ) + { + v = XMVectorSelect( v, g_XMZero, g_XMMaskZ ); + } + if ( flags & CMSE_IGNORE_ALPHA ) + { + v = XMVectorSelect( v, g_XMZero, g_XMMaskW ); + } + acc = XMVectorMultiplyAdd( v, v, acc ); } @@ -195,7 +267,7 @@ HRESULT CopyRectangle( const Image& srcImage, const Rect& srcRect, const Image& // Computes the Mean-Squared-Error (MSE) between two images //------------------------------------------------------------------------------------- _Use_decl_annotations_ -HRESULT ComputeMSE( const Image& image1, const Image& image2, float& mse, float* mseV ) +HRESULT ComputeMSE( const Image& image1, const Image& image2, float& mse, float* mseV, DWORD flags ) { if ( !image1.pixels || !image2.pixels ) return E_POINTER; @@ -223,7 +295,7 @@ HRESULT ComputeMSE( const Image& image1, const Image& image2, float& mse, float* if ( !img1 || !img2 ) return E_POINTER; - return _ComputeMSE( *img1, *img2, mse, mseV ); + return _ComputeMSE( *img1, *img2, mse, mseV, flags ); } else { @@ -237,7 +309,7 @@ HRESULT ComputeMSE( const Image& image1, const Image& image2, float& mse, float* if ( !img ) return E_POINTER; - return _ComputeMSE( *img, image2, mse, mseV ); + return _ComputeMSE( *img, image2, mse, mseV, flags ); } } else @@ -254,12 +326,12 @@ HRESULT ComputeMSE( const Image& image1, const Image& image2, float& mse, float* if ( !img ) return E_POINTER; - return _ComputeMSE( image1, *img, mse, mseV ); + return _ComputeMSE( image1, *img, mse, mseV, flags ); } else { // Case 4: neither image is compressed - return _ComputeMSE( image1, image2, mse, mseV ); + return _ComputeMSE( image1, image2, mse, mseV, flags ); } } } diff --git a/DirectXTex/DirectXTexP.h b/DirectXTex/DirectXTexP.h index de1a6c5..5a71e06 100644 --- a/DirectXTex/DirectXTexP.h +++ b/DirectXTex/DirectXTexP.h @@ -67,6 +67,8 @@ namespace DirectX DXGI_FORMAT _WICToDXGI( _In_ const GUID& guid ); bool _DXGIToWIC( _In_ DXGI_FORMAT format, _Out_ GUID& guid ); + DWORD _CheckWICColorSpace( _In_ const GUID& sourceGUID, _In_ const GUID& targetGUID ); + IWICImagingFactory* _GetWIC(); bool _IsWIC2(); diff --git a/DirectXTex/DirectXTexTGA.cpp b/DirectXTex/DirectXTexTGA.cpp index 4da2d20..5aeee0d 100644 --- a/DirectXTex/DirectXTexTGA.cpp +++ b/DirectXTex/DirectXTexTGA.cpp @@ -19,7 +19,7 @@ // The implementation here has the following limitations: // * Does not support files that contain color maps (these are rare in practice) // * Interleaved files are not supported (deprecated aspect of TGA format) -// * Only supports 8-bit greyscale; 16-, 24-, and 32-bit truecolor images +// * Only supports 8-bit grayscale; 16-, 24-, and 32-bit truecolor images // * Always writes uncompressed files (i.e. can read RLE compression, but does not write it) // diff --git a/DirectXTex/DirectXTexUtil.cpp b/DirectXTex/DirectXTexUtil.cpp index 3eda63a..b63e5c2 100644 --- a/DirectXTex/DirectXTexUtil.cpp +++ b/DirectXTex/DirectXTexUtil.cpp @@ -1,3 +1,5 @@ + + //------------------------------------------------------------------------------------- // DirectXTexUtil.cpp // @@ -22,33 +24,34 @@ struct WICTranslate { GUID wic; DXGI_FORMAT format; + bool srgb; }; static WICTranslate g_WICFormats[] = { - { GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT_R32G32B32A32_FLOAT }, + { GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT_R32G32B32A32_FLOAT, false }, - { GUID_WICPixelFormat64bppRGBAHalf, DXGI_FORMAT_R16G16B16A16_FLOAT }, - { GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT_R16G16B16A16_UNORM }, + { GUID_WICPixelFormat64bppRGBAHalf, DXGI_FORMAT_R16G16B16A16_FLOAT, false }, + { GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT_R16G16B16A16_UNORM, true }, - { GUID_WICPixelFormat32bppRGBA, DXGI_FORMAT_R8G8B8A8_UNORM }, - { GUID_WICPixelFormat32bppBGRA, DXGI_FORMAT_B8G8R8A8_UNORM }, // DXGI 1.1 - { GUID_WICPixelFormat32bppBGR, DXGI_FORMAT_B8G8R8X8_UNORM }, // DXGI 1.1 + { GUID_WICPixelFormat32bppRGBA, DXGI_FORMAT_R8G8B8A8_UNORM, true }, + { GUID_WICPixelFormat32bppBGRA, DXGI_FORMAT_B8G8R8A8_UNORM, true }, // DXGI 1.1 + { GUID_WICPixelFormat32bppBGR, DXGI_FORMAT_B8G8R8X8_UNORM, true }, // DXGI 1.1 - { GUID_WICPixelFormat32bppRGBA1010102XR, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM }, // DXGI 1.1 - { GUID_WICPixelFormat32bppRGBA1010102, DXGI_FORMAT_R10G10B10A2_UNORM }, + { GUID_WICPixelFormat32bppRGBA1010102XR, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, true }, // DXGI 1.1 + { GUID_WICPixelFormat32bppRGBA1010102, DXGI_FORMAT_R10G10B10A2_UNORM, true }, - { GUID_WICPixelFormat16bppBGRA5551, DXGI_FORMAT_B5G5R5A1_UNORM }, - { GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT_B5G6R5_UNORM }, + { GUID_WICPixelFormat16bppBGRA5551, DXGI_FORMAT_B5G5R5A1_UNORM, true }, + { GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT_B5G6R5_UNORM, true }, - { GUID_WICPixelFormat32bppGrayFloat, DXGI_FORMAT_R32_FLOAT }, - { GUID_WICPixelFormat16bppGrayHalf, DXGI_FORMAT_R16_FLOAT }, - { GUID_WICPixelFormat16bppGray, DXGI_FORMAT_R16_UNORM }, - { GUID_WICPixelFormat8bppGray, DXGI_FORMAT_R8_UNORM }, + { GUID_WICPixelFormat32bppGrayFloat, DXGI_FORMAT_R32_FLOAT, false }, + { GUID_WICPixelFormat16bppGrayHalf, DXGI_FORMAT_R16_FLOAT, false }, + { GUID_WICPixelFormat16bppGray, DXGI_FORMAT_R16_UNORM, true }, + { GUID_WICPixelFormat8bppGray, DXGI_FORMAT_R8_UNORM, true }, - { GUID_WICPixelFormat8bppAlpha, DXGI_FORMAT_A8_UNORM }, + { GUID_WICPixelFormat8bppAlpha, DXGI_FORMAT_A8_UNORM, false }, - { GUID_WICPixelFormatBlackWhite, DXGI_FORMAT_R1_UNORM }, + { GUID_WICPixelFormatBlackWhite, DXGI_FORMAT_R1_UNORM, false }, }; static bool g_WIC2 = false; @@ -131,6 +134,33 @@ bool _DXGIToWIC( DXGI_FORMAT format, GUID& guid ) return false; } +DWORD _CheckWICColorSpace( _In_ const GUID& sourceGUID, _In_ const GUID& targetGUID ) +{ + DWORD srgb = 0; + + for( size_t i=0; i < _countof(g_WICFormats); ++i ) + { + if ( memcmp( &g_WICFormats[i].wic, &sourceGUID, sizeof(GUID) ) == 0 ) + { + if ( g_WICFormats[i].srgb ) + srgb |= TEX_FILTER_SRGB_IN; + } + + if ( memcmp( &g_WICFormats[i].wic, &targetGUID, sizeof(GUID) ) == 0 ) + { + if ( g_WICFormats[i].srgb ) + srgb |= TEX_FILTER_SRGB_OUT; + } + } + + if ( (srgb & (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT)) == (TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT) ) + { + srgb &= ~(TEX_FILTER_SRGB_IN|TEX_FILTER_SRGB_OUT); + } + + return srgb; +} + bool _IsWIC2() { return g_WIC2;