//------------------------------------------------------------------------------------- // DirectXTexResize.cpp // // DirectX Texture Library - Image resizing operations // // 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" #include "filters.h" using Microsoft::WRL::ComPtr; namespace DirectX { //------------------------------------------------------------------------------------- // WIC related helper functions //------------------------------------------------------------------------------------- extern HRESULT _ResizeSeparateColorAndAlpha( _In_ IWICImagingFactory* pWIC, _In_ bool iswic2, _In_ IWICBitmap* original, _In_ size_t newWidth, _In_ size_t newHeight, _In_ DWORD filter, _Inout_ const Image* img ); //--- Do image resize using WIC --- static HRESULT _PerformResizeUsingWIC( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const WICPixelFormatGUID& pfGUID, _In_ const Image& destImage ) { if ( !srcImage.pixels || !destImage.pixels ) return E_POINTER; assert( srcImage.format == destImage.format ); bool iswic2 = false; IWICImagingFactory* pWIC = GetWICFactory(iswic2); if ( !pWIC ) return E_NOINTERFACE; ComPtr componentInfo; HRESULT hr = pWIC->CreateComponentInfo( pfGUID, componentInfo.GetAddressOf() ); if ( FAILED(hr) ) return hr; ComPtr pixelFormatInfo; hr = componentInfo.As( &pixelFormatInfo ); if ( FAILED(hr) ) return hr; BOOL supportsTransparency = FALSE; hr = pixelFormatInfo->SupportsTransparency( &supportsTransparency ); if ( FAILED(hr) ) return hr; ComPtr source; hr = pWIC->CreateBitmapFromMemory( static_cast( srcImage.width ), static_cast( srcImage.height ), pfGUID, static_cast( srcImage.rowPitch ), static_cast( srcImage.slicePitch ), srcImage.pixels, source.GetAddressOf() ); if ( FAILED(hr) ) return hr; if ( (filter & TEX_FILTER_SEPARATE_ALPHA) && supportsTransparency ) { hr = _ResizeSeparateColorAndAlpha( pWIC, iswic2, source.Get(), destImage.width, destImage.height, filter, &destImage ); if ( FAILED(hr) ) return hr; } else { ComPtr scaler; hr = pWIC->CreateBitmapScaler( scaler.GetAddressOf() ); if ( FAILED(hr) ) return hr; hr = scaler->Initialize( source.Get(), static_cast( destImage.width ), static_cast( destImage.height ), _GetWICInterp( filter ) ); if ( FAILED(hr) ) return hr; WICPixelFormatGUID pfScaler; hr = scaler->GetPixelFormat( &pfScaler ); if ( FAILED(hr) ) return hr; if ( memcmp( &pfScaler, &pfGUID, sizeof(WICPixelFormatGUID) ) == 0 ) { hr = scaler->CopyPixels( 0, static_cast( destImage.rowPitch ), static_cast( destImage.slicePitch ), destImage.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 back ComPtr FC; hr = pWIC->CreateFormatConverter( FC.GetAddressOf() ); if ( FAILED(hr) ) return hr; BOOL canConvert = FALSE; hr = FC->CanConvert( pfScaler, pfGUID, &canConvert ); if ( FAILED(hr) || !canConvert ) { return E_UNEXPECTED; } hr = FC->Initialize( scaler.Get(), pfGUID, _GetWICDither( filter ), 0, 0, 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; } //--- Do conversion, resize using WIC, conversion cycle --- static HRESULT _PerformResizeViaF32( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { if ( !srcImage.pixels || !destImage.pixels ) return E_POINTER; assert( srcImage.format != DXGI_FORMAT_R32G32B32A32_FLOAT ); assert( srcImage.format == destImage.format ); ScratchImage temp; HRESULT hr = _ConvertToR32G32B32A32( srcImage, temp ); if ( FAILED(hr) ) return hr; const Image *tsrc = temp.GetImage( 0, 0, 0 ); if ( !tsrc ) return E_POINTER; ScratchImage rtemp; hr = rtemp.Initialize2D( DXGI_FORMAT_R32G32B32A32_FLOAT, destImage.width, destImage.height, 1, 1 ); if ( FAILED(hr) ) return hr; const Image *tdest = rtemp.GetImage( 0, 0, 0 ); if ( !tdest ) return E_POINTER; hr = _PerformResizeUsingWIC( *tsrc, filter, GUID_WICPixelFormat128bppRGBAFloat, *tdest ); if ( FAILED(hr) ) return hr; temp.Release(); hr = _ConvertFromR32G32B32A32( *tdest, destImage ); if ( FAILED(hr) ) return hr; return S_OK; } //--- determine when to use WIC vs. non-WIC paths --- static bool _UseWICFiltering( _In_ DXGI_FORMAT format, _In_ DWORD filter ) { if ( filter & TEX_FILTER_FORCE_NON_WIC ) { // Explicit flag indicates use of non-WIC code paths return false; } if ( filter & TEX_FILTER_FORCE_WIC ) { // Explicit flag to use WIC code paths, skips all the case checks below return true; } if ( IsSRGB(format) || (filter & TEX_FILTER_SRGB) ) { // Use non-WIC code paths for sRGB correct filtering return false; } #if defined(_XBOX_ONE) && defined(_TITLE) if ( format == DXGI_FORMAT_R16G16B16A16_FLOAT || format == DXGI_FORMAT_R16_FLOAT ) { // Use non-WIC code paths as these conversions are not supported by Xbox One XDK return false; } #endif static_assert( TEX_FILTER_POINT == 0x100000, "TEX_FILTER_ flag values don't match TEX_FILTER_MASK" ); switch ( filter & TEX_FILTER_MASK ) { case TEX_FILTER_LINEAR: if ( filter & TEX_FILTER_WRAP ) { // WIC only supports 'clamp' semantics (MIRROR is equivalent to clamp for linear) return false; } if ( BitsPerColor(format) > 8 ) { // Avoid the WIC bitmap scaler when doing Linear filtering of XR/HDR formats return false; } break; case TEX_FILTER_CUBIC: if ( filter & ( TEX_FILTER_WRAP | TEX_FILTER_MIRROR ) ) { // WIC only supports 'clamp' semantics return false; } if ( BitsPerColor(format) > 8 ) { // Avoid the WIC bitmap scaler when doing Cubic filtering of XR/HDR formats return false; } break; case TEX_FILTER_TRIANGLE: // WIC does not implement this filter return false; } return true; } //------------------------------------------------------------------------------------- // Resize custom filters //------------------------------------------------------------------------------------- //--- Point Filter --- static HRESULT _ResizePointFilter( _In_ const Image& srcImage, _In_ const Image& destImage ) { assert( srcImage.pixels && destImage.pixels ); assert( srcImage.format == destImage.format ); // Allocate temporary space (2 scanlines) ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( ( sizeof(XMVECTOR) * (srcImage.width + destImage.width ) ), 16 ) ) ); if ( !scanline ) return E_OUTOFMEMORY; XMVECTOR* target = scanline.get(); XMVECTOR* row = target + destImage.width; #ifdef _DEBUG memset( row, 0xCD, sizeof(XMVECTOR)*srcImage.width ); #endif const uint8_t* pSrc = srcImage.pixels; uint8_t* pDest = destImage.pixels; size_t rowPitch = srcImage.rowPitch; size_t xinc = ( srcImage.width << 16 ) / destImage.width; size_t yinc = ( srcImage.height << 16 ) / destImage.height; size_t lasty = size_t(-1); size_t sy = 0; for( size_t y = 0; y < destImage.height; ++y ) { if ( (lasty ^ sy) >> 16 ) { if ( !_LoadScanline( row, srcImage.width, pSrc + ( rowPitch * (sy >> 16) ), rowPitch, srcImage.format ) ) return E_FAIL; lasty = sy; } size_t sx = 0; for( size_t x = 0; x < destImage.width; ++x ) { target[ x ] = row[ sx >> 16 ]; sx += xinc; } if ( !_StoreScanline( pDest, destImage.rowPitch, destImage.format, target, destImage.width ) ) return E_FAIL; pDest += destImage.rowPitch; sy += yinc; } return S_OK; } //--- Box Filter --- static HRESULT _ResizeBoxFilter( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { assert( srcImage.pixels && destImage.pixels ); assert( srcImage.format == destImage.format ); if ( ( (destImage.width << 1) != srcImage.width ) || ( (destImage.height << 1) != srcImage.height ) ) return E_FAIL; // Allocate temporary space (3 scanlines) ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( ( sizeof(XMVECTOR) * ( srcImage.width*2 + destImage.width ) ), 16 ) ) ); if ( !scanline ) return E_OUTOFMEMORY; XMVECTOR* target = scanline.get(); XMVECTOR* urow0 = target + destImage.width; XMVECTOR* urow1 = urow0 + srcImage.width; #ifdef _DEBUG memset( urow0, 0xCD, sizeof(XMVECTOR)*srcImage.width ); memset( urow1, 0xDD, sizeof(XMVECTOR)*srcImage.width ); #endif const XMVECTOR* urow2 = urow0 + 1; const XMVECTOR* urow3 = urow1 + 1; const uint8_t* pSrc = srcImage.pixels; uint8_t* pDest = destImage.pixels; size_t rowPitch = srcImage.rowPitch; for( size_t y = 0; y < destImage.height; ++y ) { if ( !_LoadScanlineLinear( urow0, srcImage.width, pSrc, rowPitch, srcImage.format, filter ) ) return E_FAIL; pSrc += rowPitch; if ( urow0 != urow1 ) { if ( !_LoadScanlineLinear( urow1, srcImage.width, pSrc, rowPitch, srcImage.format, filter ) ) return E_FAIL; pSrc += rowPitch; } for( size_t x = 0; x < destImage.width; ++x ) { size_t x2 = x << 1; AVERAGE4( target[ x ], urow0[ x2 ], urow1[ x2 ], urow2[ x2 ], urow3[ x2 ] ); } if ( !_StoreScanlineLinear( pDest, destImage.rowPitch, destImage.format, target, destImage.width, filter ) ) return E_FAIL; pDest += destImage.rowPitch; } return S_OK; } //--- Linear Filter --- static HRESULT _ResizeLinearFilter( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { assert( srcImage.pixels && destImage.pixels ); assert( srcImage.format == destImage.format ); // Allocate temporary space (3 scanlines, plus X and Y filters) ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( ( sizeof(XMVECTOR) * ( srcImage.width*2 + destImage.width ) ), 16 ) ) ); if ( !scanline ) return E_OUTOFMEMORY; std::unique_ptr lf( new (std::nothrow) LinearFilter[ destImage.width + destImage.height ] ); if ( !lf ) return E_OUTOFMEMORY; LinearFilter* lfX = lf.get(); LinearFilter* lfY = lf.get() + destImage.width; _CreateLinearFilter( srcImage.width, destImage.width, (filter & TEX_FILTER_WRAP_U) != 0, lfX ); _CreateLinearFilter( srcImage.height, destImage.height, (filter & TEX_FILTER_WRAP_V) != 0, lfY ); XMVECTOR* target = scanline.get(); XMVECTOR* row0 = target + destImage.width; XMVECTOR* row1 = row0 + srcImage.width; #ifdef _DEBUG memset( row0, 0xCD, sizeof(XMVECTOR)*srcImage.width ); memset( row1, 0xDD, sizeof(XMVECTOR)*srcImage.width ); #endif const uint8_t* pSrc = srcImage.pixels; uint8_t* pDest = destImage.pixels; size_t rowPitch = srcImage.rowPitch; size_t u0 = size_t(-1); size_t u1 = size_t(-1); for( size_t y = 0; y < destImage.height; ++y ) { auto& toY = lfY[ y ]; if ( toY.u0 != u0 ) { if ( toY.u0 != u1 ) { u0 = toY.u0; if ( !_LoadScanlineLinear( row0, srcImage.width, pSrc + (rowPitch * u0), rowPitch, srcImage.format, filter ) ) return E_FAIL; } else { u0 = u1; u1 = size_t(-1); std::swap( row0, row1 ); } } if ( toY.u1 != u1 ) { u1 = toY.u1; if ( !_LoadScanlineLinear( row1, srcImage.width, pSrc + (rowPitch * u1), rowPitch, srcImage.format, filter ) ) return E_FAIL; } for( size_t x = 0; x < destImage.width; ++x ) { auto& toX = lfX[ x ]; BILINEAR_INTERPOLATE( target[x], toX, toY, row0, row1 ); } if ( !_StoreScanlineLinear( pDest, destImage.rowPitch, destImage.format, target, destImage.width, filter ) ) return E_FAIL; pDest += destImage.rowPitch; } return S_OK; } //--- Cubic Filter --- static HRESULT _ResizeCubicFilter( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { assert( srcImage.pixels && destImage.pixels ); assert( srcImage.format == destImage.format ); // Allocate temporary space (5 scanlines, plus X and Y filters) ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( ( sizeof(XMVECTOR) * ( srcImage.width*4 + destImage.width ) ), 16 ) ) ); if ( !scanline ) return E_OUTOFMEMORY; std::unique_ptr cf( new (std::nothrow) CubicFilter[ destImage.width + destImage.height ] ); if ( !cf ) return E_OUTOFMEMORY; CubicFilter* cfX = cf.get(); CubicFilter* cfY = cf.get() + destImage.width; _CreateCubicFilter( srcImage.width, destImage.width, (filter & TEX_FILTER_WRAP_U) != 0, (filter & TEX_FILTER_MIRROR_U) != 0, cfX ); _CreateCubicFilter( srcImage.height, destImage.height, (filter & TEX_FILTER_WRAP_V) != 0, (filter & TEX_FILTER_MIRROR_V) != 0, cfY ); XMVECTOR* target = scanline.get(); XMVECTOR* row0 = target + destImage.width; XMVECTOR* row1 = row0 + srcImage.width; XMVECTOR* row2 = row0 + srcImage.width*2; XMVECTOR* row3 = row0 + srcImage.width*3; #ifdef _DEBUG memset( row0, 0xCD, sizeof(XMVECTOR)*srcImage.width ); memset( row1, 0xDD, sizeof(XMVECTOR)*srcImage.width ); memset( row2, 0xED, sizeof(XMVECTOR)*srcImage.width ); memset( row3, 0xFD, sizeof(XMVECTOR)*srcImage.width ); #endif const uint8_t* pSrc = srcImage.pixels; uint8_t* pDest = destImage.pixels; size_t rowPitch = srcImage.rowPitch; size_t u0 = size_t(-1); size_t u1 = size_t(-1); size_t u2 = size_t(-1); size_t u3 = size_t(-1); for( size_t y = 0; y < destImage.height; ++y ) { auto& toY = cfY[ y ]; // Scanline 1 if ( toY.u0 != u0 ) { if ( toY.u0 != u1 && toY.u0 != u2 && toY.u0 != u3 ) { u0 = toY.u0; if ( !_LoadScanlineLinear( row0, srcImage.width, pSrc + (rowPitch * u0), rowPitch, srcImage.format, filter ) ) return E_FAIL; } else if ( toY.u0 == u1 ) { u0 = u1; u1 = size_t(-1); std::swap( row0, row1 ); } else if ( toY.u0 == u2 ) { u0 = u2; u2 = size_t(-1); std::swap( row0, row2 ); } else if ( toY.u0 == u3 ) { u0 = u3; u3 = size_t(-1); std::swap( row0, row3 ); } } // Scanline 2 if ( toY.u1 != u1 ) { if ( toY.u1 != u2 && toY.u1 != u3 ) { u1 = toY.u1; if ( !_LoadScanlineLinear( row1, srcImage.width, pSrc + (rowPitch * u1), rowPitch, srcImage.format, filter ) ) return E_FAIL; } else if ( toY.u1 == u2 ) { u1 = u2; u2 = size_t(-1); std::swap( row1, row2 ); } else if ( toY.u1 == u3 ) { u1 = u3; u3 = size_t(-1); std::swap( row1, row3 ); } } // Scanline 3 if ( toY.u2 != u2 ) { if ( toY.u2 != u3 ) { u2 = toY.u2; if ( !_LoadScanlineLinear( row2, srcImage.width, pSrc + (rowPitch * u2), rowPitch, srcImage.format, filter ) ) return E_FAIL; } else { u2 = u3; u3 = size_t(-1); std::swap( row2, row3 ); } } // Scanline 4 if ( toY.u3 != u3 ) { u3 = toY.u3; if ( !_LoadScanlineLinear( row3, srcImage.width, pSrc + (rowPitch * u3), rowPitch, srcImage.format, filter ) ) return E_FAIL; } for( size_t x = 0; x < destImage.width; ++x ) { auto& toX = cfX[ x ]; XMVECTOR C0, C1, C2, C3; CUBIC_INTERPOLATE( C0, toX.x, row0[ toX.u0 ], row0[ toX.u1 ], row0[ toX.u2 ], row0[ toX.u3 ] ); CUBIC_INTERPOLATE( C1, toX.x, row1[ toX.u0 ], row1[ toX.u1 ], row1[ toX.u2 ], row1[ toX.u3 ] ); CUBIC_INTERPOLATE( C2, toX.x, row2[ toX.u0 ], row2[ toX.u1 ], row2[ toX.u2 ], row2[ toX.u3 ] ); CUBIC_INTERPOLATE( C3, toX.x, row3[ toX.u0 ], row3[ toX.u1 ], row3[ toX.u2 ], row3[ toX.u3 ] ); CUBIC_INTERPOLATE( target[x], toY.x, C0, C1, C2, C3 ); } if ( !_StoreScanlineLinear( pDest, destImage.rowPitch, destImage.format, target, destImage.width, filter ) ) return E_FAIL; pDest += destImage.rowPitch; } return S_OK; } //--- Triangle Filter --- static HRESULT _ResizeTriangleFilter( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { assert( srcImage.pixels && destImage.pixels ); assert( srcImage.format == destImage.format ); using namespace TriangleFilter; // Allocate initial temporary space (1 scanline, accumulation rows, plus X and Y filters) ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * srcImage.width, 16 ) ) ); if ( !scanline ) return E_OUTOFMEMORY; std::unique_ptr rowActive( new (std::nothrow) TriangleRow[ destImage.height ] ); if ( !rowActive ) return E_OUTOFMEMORY; TriangleRow * rowFree = nullptr; std::unique_ptr tfX; HRESULT hr = _Create( srcImage.width, destImage.width, (filter & TEX_FILTER_WRAP_U) != 0, tfX ); if ( FAILED(hr) ) return hr; std::unique_ptr tfY; hr = _Create( srcImage.height, destImage.height, (filter & TEX_FILTER_WRAP_V) != 0, tfY ); if ( FAILED(hr) ) return hr; XMVECTOR* row = scanline.get(); #ifdef _DEBUG memset( row, 0xCD, sizeof(XMVECTOR)*srcImage.width ); #endif auto xFromEnd = reinterpret_cast( reinterpret_cast( tfX.get() ) + tfX->sizeInBytes ); auto yFromEnd = reinterpret_cast( reinterpret_cast( tfY.get() ) + tfY->sizeInBytes ); // Count times rows get written for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) { for ( size_t j = 0; j < yFrom->count; ++j ) { size_t v = yFrom->to[ j ].u; assert( v < destImage.height ); ++rowActive[ v ].remaining; } yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); } // Filter image const uint8_t* pSrc = srcImage.pixels; size_t rowPitch = srcImage.rowPitch; const uint8_t* pEndSrc = pSrc + rowPitch * srcImage.height; uint8_t* pDest = destImage.pixels; for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) { // Create accumulation rows as needed for ( size_t j = 0; j < yFrom->count; ++j ) { size_t v = yFrom->to[ j ].u; assert( v < destImage.height ); TriangleRow* rowAcc = &rowActive[ v ]; if ( !rowAcc->scanline ) { if ( rowFree ) { // Steal and reuse scanline from 'free row' list assert( rowFree->scanline != 0 ); rowAcc->scanline.reset( rowFree->scanline.release() ); rowFree = rowFree->next; } else { rowAcc->scanline.reset( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * destImage.width, 16 ) ) ); if ( !rowAcc->scanline ) return E_OUTOFMEMORY; } memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * destImage.width ); } } // Load source scanline if ( (pSrc + rowPitch) > pEndSrc ) return E_FAIL; if ( !_LoadScanlineLinear( row, srcImage.width, pSrc, rowPitch, srcImage.format, filter ) ) return E_FAIL; pSrc += rowPitch; // Process row size_t x = 0; for( FilterFrom* xFrom = tfX->from; xFrom < xFromEnd; ++x ) { for ( size_t j = 0; j < yFrom->count; ++j ) { size_t v = yFrom->to[ j ].u; assert( v < destImage.height ); float yweight = yFrom->to[ j ].weight; XMVECTOR* accPtr = rowActive[ v ].scanline.get(); if ( !accPtr ) return E_POINTER; for ( size_t k = 0; k < xFrom->count; ++k ) { size_t u = xFrom->to[ k ].u; assert( u < destImage.width ); XMVECTOR weight = XMVectorReplicate( yweight * xFrom->to[ k ].weight ); assert( x < srcImage.width ); accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] ); } } xFrom = reinterpret_cast( reinterpret_cast( xFrom ) + xFrom->sizeInBytes ); } // Write completed accumulation rows for ( size_t j = 0; j < yFrom->count; ++j ) { size_t v = yFrom->to[ j ].u; assert( v < destImage.height ); TriangleRow* rowAcc = &rowActive[ v ]; assert( rowAcc->remaining > 0 ); --rowAcc->remaining; if ( !rowAcc->remaining ) { XMVECTOR* pAccSrc = rowAcc->scanline.get(); if ( !pAccSrc ) return E_POINTER; switch( destImage.format ) { case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_UINT: { // Need to slightly bias results for floating-point error accumulation which can // be visible with harshly quantized values static const XMVECTORF32 Bias = { 0.f, 0.f, 0.f, 0.1f }; XMVECTOR* ptr = pAccSrc; for( size_t i=0; i < destImage.width; ++i, ++ptr ) { *ptr = XMVectorAdd( *ptr, Bias ); } } break; } // This performs any required clamping if ( !_StoreScanlineLinear( pDest + (destImage.rowPitch * v), destImage.rowPitch, destImage.format, pAccSrc, destImage.width, filter ) ) return E_FAIL; // Put row on freelist to reuse it's allocated scanline rowAcc->next = rowFree; rowFree = rowAcc; } } yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); } return S_OK; } //--- Custom filter resize --- static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { if ( !srcImage.pixels || !destImage.pixels ) return E_POINTER; static_assert( TEX_FILTER_POINT == 0x100000, "TEX_FILTER_ flag values don't match TEX_FILTER_MASK" ); DWORD filter_select = ( filter & TEX_FILTER_MASK ); if ( !filter_select ) { // Default filter choice filter_select = ( ( (destImage.width << 1) == srcImage.width ) && ( (destImage.height << 1) == srcImage.height ) ) ? TEX_FILTER_BOX : TEX_FILTER_LINEAR; } switch( filter_select ) { case TEX_FILTER_POINT: return _ResizePointFilter( srcImage, destImage ); case TEX_FILTER_BOX: return _ResizeBoxFilter( srcImage, filter, destImage ); case TEX_FILTER_LINEAR: return _ResizeLinearFilter( srcImage, filter, destImage ); case TEX_FILTER_CUBIC: return _ResizeCubicFilter( srcImage, filter, destImage ); case TEX_FILTER_TRIANGLE: return _ResizeTriangleFilter( srcImage, filter, destImage ); default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } } //===================================================================================== // Entry-points //===================================================================================== //------------------------------------------------------------------------------------- // Resize image //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT Resize( const Image& srcImage, size_t width, size_t height, DWORD filter, ScratchImage& image ) { if ( width == 0 || height == 0 ) return E_INVALIDARG; #ifdef _M_X64 if ( (srcImage.width > 0xFFFFFFFF) || (srcImage.height > 0xFFFFFFFF) ) return E_INVALIDARG; if ( (width > 0xFFFFFFFF) || (height > 0xFFFFFFFF) ) return E_INVALIDARG; #endif if ( !srcImage.pixels ) return E_POINTER; if ( IsCompressed( srcImage.format ) ) { // We don't support resizing compressed images return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } HRESULT hr = image.Initialize2D( srcImage.format, width, height, 1, 1 ); if ( FAILED(hr) ) return hr; const Image *rimage = image.GetImage( 0, 0, 0 ); if ( !rimage ) return E_POINTER; if ( _UseWICFiltering( srcImage.format, filter ) ) { WICPixelFormatGUID pfGUID; if ( _DXGIToWIC( srcImage.format, pfGUID, true ) ) { // Case 1: Source format is supported by Windows Imaging Component hr = _PerformResizeUsingWIC( srcImage, filter, pfGUID, *rimage ); } else { // Case 2: Source format is not supported by WIC, so we have to convert, resize, and convert back hr = _PerformResizeViaF32( srcImage, filter, *rimage ); } } else { hr = _PerformResizeUsingCustomFilters( srcImage, filter, *rimage ); } if ( FAILED(hr) ) { image.Release(); return hr; } return S_OK; } //------------------------------------------------------------------------------------- // Resize image (complex) //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT Resize( const Image* srcImages, size_t nimages, const TexMetadata& metadata, size_t width, size_t height, DWORD filter, ScratchImage& result ) { if ( !srcImages || !nimages || width == 0 || height == 0 ) return E_INVALIDARG; #ifdef _M_X64 if ( (width > 0xFFFFFFFF) || (height > 0xFFFFFFFF) ) return E_INVALIDARG; #endif TexMetadata mdata2 = metadata; mdata2.width = width; mdata2.height = height; mdata2.mipLevels = 1; HRESULT hr = result.Initialize( mdata2 ); if ( FAILED(hr) ) return hr; bool usewic = _UseWICFiltering( metadata.format, filter ); WICPixelFormatGUID pfGUID = {0}; bool wicpf = ( usewic ) ? _DXGIToWIC( metadata.format, pfGUID, true ) : false; switch ( metadata.dimension ) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: assert( metadata.depth == 1 ); for( size_t item = 0; item < metadata.arraySize; ++item ) { size_t srcIndex = metadata.ComputeIndex( 0, item, 0 ); if ( srcIndex >= nimages ) { result.Release(); return E_FAIL; } const Image* srcimg = &srcImages[ srcIndex ]; const Image* destimg = result.GetImage( 0, item, 0 ); if ( !srcimg || !destimg ) { result.Release(); return E_POINTER; } if ( srcimg->format != metadata.format ) { result.Release(); return E_FAIL; } #ifdef _M_X64 if ( (srcimg->width > 0xFFFFFFFF) || (srcimg->height > 0xFFFFFFFF) ) { result.Release(); return E_FAIL; } #endif if ( usewic ) { if ( wicpf ) { // Case 1: Source format is supported by Windows Imaging Component hr = _PerformResizeUsingWIC( *srcimg, filter, pfGUID, *destimg ); } else { // Case 2: Source format is not supported by WIC, so we have to convert, resize, and convert back hr = _PerformResizeViaF32( *srcimg, filter, *destimg ); } } else { // Case 3: not using WIC resizing hr = _PerformResizeUsingCustomFilters( *srcimg, filter, *destimg ); } if ( FAILED(hr) ) { result.Release(); return hr; } } break; case TEX_DIMENSION_TEXTURE3D: assert( metadata.arraySize == 1 ); for( size_t slice = 0; slice < metadata.depth; ++slice ) { size_t srcIndex = metadata.ComputeIndex( 0, 0, slice ); if ( srcIndex >= nimages ) { result.Release(); return E_FAIL; } const Image* srcimg = &srcImages[ srcIndex ]; const Image* destimg = result.GetImage( 0, 0, slice ); if ( !srcimg || !destimg ) { result.Release(); return E_POINTER; } if ( srcimg->format != metadata.format ) { result.Release(); return E_FAIL; } #ifdef _M_X64 if ( (srcimg->width > 0xFFFFFFFF) || (srcimg->height > 0xFFFFFFFF) ) { result.Release(); return E_FAIL; } #endif if ( usewic ) { if ( wicpf ) { // Case 1: Source format is supported by Windows Imaging Component hr = _PerformResizeUsingWIC( *srcimg, filter, pfGUID, *destimg ); } else { // Case 2: Source format is not supported by WIC, so we have to convert, resize, and convert back hr = _PerformResizeViaF32( *srcimg, filter, *destimg ); } } else { // Case 3: not using WIC resizing hr = _PerformResizeUsingCustomFilters( *srcimg, filter, *destimg ); } if ( FAILED(hr) ) { result.Release(); return hr; } } break; default: result.Release(); return E_FAIL; } return S_OK; } }; // namespace