1
0
mirror of https://github.com/microsoft/DirectXTex synced 2024-11-08 14:00:05 +00:00

DirectXTex: added finite low-pass triangle custom filter

- TEX_FILTER_TRIANGLE
- texconv updated -if image filter options
This commit is contained in:
walbourn_cp 2013-06-14 16:17:16 -07:00
parent 83372f9b7f
commit 2fdf1f6d0a
5 changed files with 839 additions and 2 deletions

View File

@ -27,7 +27,7 @@
#include <dxgiformat.h> #include <dxgiformat.h>
#include <d3d11.h> #include <d3d11.h>
#define DIRECTX_TEX_VERSION 102 #define DIRECTX_TEX_VERSION 110
#if defined(_MSC_VER) && (_MSC_VER<1610) && !defined(_In_reads_) #if defined(_MSC_VER) && (_MSC_VER<1610) && !defined(_In_reads_)
#define _Analysis_assume_(exp) #define _Analysis_assume_(exp)
@ -403,6 +403,7 @@ namespace DirectX
TEX_FILTER_CUBIC = 0x300000, TEX_FILTER_CUBIC = 0x300000,
TEX_FILTER_BOX = 0x400000, TEX_FILTER_BOX = 0x400000,
TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation
TEX_FILTER_TRIANGLE = 0x500000,
// Filtering mode to use for any required image resizing // Filtering mode to use for any required image resizing
TEX_FILTER_SRGB_IN = 0x1000000, TEX_FILTER_SRGB_IN = 0x1000000,

View File

@ -417,6 +417,10 @@ static bool _UseWICFiltering( _In_ DXGI_FORMAT format, _In_ DWORD filter )
return false; return false;
} }
break; break;
case TEX_FILTER_TRIANGLE:
// WIC does not implement this filter
return false;
} }
return true; return true;
@ -1077,6 +1081,193 @@ static HRESULT _Generate2DMipsCubicFilter( _In_ size_t levels, _In_ DWORD filter
} }
//--- 2D Triangle Filter ---
static HRESULT _Generate2DMipsTriangleFilter( _In_ size_t levels, _In_ DWORD filter, _In_ const ScratchImage& mipChain, _In_ size_t item )
{
if ( !mipChain.GetImages() )
return E_INVALIDARG;
using namespace TriangleFilter;
// This assumes that the base image is already placed into the mipChain at the top level... (see _Setup2DMips)
assert( levels > 1 );
size_t width = mipChain.GetMetadata().width;
size_t height = mipChain.GetMetadata().height;
// Allocate initial temporary space (1 scanline, accumulation rows, plus X and Y filters)
ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast<XMVECTOR*>( _aligned_malloc( sizeof(XMVECTOR) * width, 16 ) ) );
if ( !scanline )
return E_OUTOFMEMORY;
std::unique_ptr<TriangleRow[]> rowActive( new (std::nothrow) TriangleRow[ height ] );
if ( !rowActive )
return E_OUTOFMEMORY;
TriangleRow * rowFree = nullptr;
std::unique_ptr<Filter> tfX, tfY;
XMVECTOR* row = scanline.get();
// Resize base image to each target mip level
for( size_t level=1; level < levels; ++level )
{
// 2D triangle filter
const Image* src = mipChain.GetImage( level-1, item, 0 );
const Image* dest = mipChain.GetImage( level, item, 0 );
if ( !src || !dest )
return E_POINTER;
const uint8_t* pSrc = src->pixels;
size_t rowPitch = src->rowPitch;
const uint8_t* pEndSrc = pSrc + rowPitch * height;
uint8_t* pDest = dest->pixels;
size_t nwidth = (width > 1) ? (width >> 1) : 1;
HRESULT hr = _Create( width, nwidth, (filter & TEX_FILTER_WRAP_U) != 0, tfX );
if ( FAILED(hr) )
return hr;
size_t nheight = (height > 1) ? (height >> 1) : 1;
hr = _Create( height, nheight, (filter & TEX_FILTER_WRAP_V) != 0, tfY );
if ( FAILED(hr) )
return hr;
#ifdef _DEBUG
memset( row, 0xCD, sizeof(XMVECTOR)*width );
#endif
auto xFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfX.get() ) + tfX->sizeInBytes );
auto yFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfY.get() ) + tfY->sizeInBytes );
// Count times rows get written (and clear out any leftover accumulation rows from last miplevel)
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 < nheight );
TriangleRow* rowAcc = &rowActive.get()[ v ];
++rowAcc->remaining;
if ( rowAcc->scanline )
{
memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth );
}
}
yFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( yFrom ) + yFrom->sizeInBytes );
}
// Filter image
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 < nheight );
TriangleRow* rowAcc = &rowActive.get()[ v ];
if ( !rowAcc->scanline )
{
if ( rowFree )
{
// Steal and reuse scanline from 'free row' list
// (it will always be at least as wide as nwidth due to loop decending order)
assert( rowFree->scanline != 0 );
rowAcc->scanline.reset( rowFree->scanline.release() );
rowFree = rowFree->next;
}
else
{
rowAcc->scanline.reset( reinterpret_cast<XMVECTOR*>( _aligned_malloc( sizeof(XMVECTOR) * nwidth, 16 ) ) );
if ( !rowAcc->scanline )
return E_OUTOFMEMORY;
}
memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth );
}
}
// Load source scanline
if ( (pSrc + rowPitch) > pEndSrc )
return E_FAIL;
if ( !_LoadScanlineLinear( row, width, pSrc, rowPitch, src->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 < nheight );
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 < nwidth );
XMVECTOR weight = XMVectorReplicate( yweight * xFrom->to[ k ].weight );
assert( x < width );
accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] );
}
}
xFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( 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 < nheight );
TriangleRow* rowAcc = &rowActive.get()[ v ];
assert( rowAcc->remaining > 0 );
--rowAcc->remaining;
if ( !rowAcc->remaining )
{
if ( !_StoreScanlineLinear( pDest + (dest->rowPitch * v), dest->rowPitch, dest->format, rowAcc->scanline.get(), dest->width, filter ) )
return E_FAIL;
// Put row on freelist to reuse it's allocated scanline
rowAcc->next = rowFree;
rowFree = rowAcc;
}
}
yFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( yFrom ) + yFrom->sizeInBytes );
}
if ( height > 1 )
height >>= 1;
if ( width > 1 )
width >>= 1;
}
return S_OK;
}
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// Generate volume mip-map helpers // Generate volume mip-map helpers
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
@ -2021,6 +2212,226 @@ static HRESULT _Generate3DMipsCubicFilter( _In_ size_t depth, _In_ size_t levels
} }
//--- 3D Triangle Filter ---
static HRESULT _Generate3DMipsTriangleFilter( _In_ size_t depth, _In_ size_t levels, _In_ DWORD filter, _In_ const ScratchImage& mipChain )
{
if ( !depth || !mipChain.GetImages() )
return E_INVALIDARG;
using namespace TriangleFilter;
// This assumes that the base images are already placed into the mipChain at the top level... (see _Setup3DMips)
assert( levels > 1 );
size_t width = mipChain.GetMetadata().width;
size_t height = mipChain.GetMetadata().height;
// Allocate initial temporary space (1 scanline, accumulation rows, plus X/Y/Z filters)
ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast<XMVECTOR*>( _aligned_malloc( sizeof(XMVECTOR) * width, 16 ) ) );
if ( !scanline )
return E_OUTOFMEMORY;
std::unique_ptr<TriangleRow[]> sliceActive( new (std::nothrow) TriangleRow[ depth ] );
if ( !sliceActive )
return E_OUTOFMEMORY;
TriangleRow * sliceFree = nullptr;
std::unique_ptr<Filter> tfX, tfY, tfZ;
XMVECTOR* row = scanline.get();
// Resize base image to each target mip level
for( size_t level=1; level < levels; ++level )
{
size_t nwidth = (width > 1) ? (width >> 1) : 1;
HRESULT hr = _Create( width, nwidth, (filter & TEX_FILTER_WRAP_U) != 0, tfX );
if ( FAILED(hr) )
return hr;
size_t nheight = (height > 1) ? (height >> 1) : 1;
hr = _Create( height, nheight, (filter & TEX_FILTER_WRAP_V) != 0, tfY );
if ( FAILED(hr) )
return hr;
size_t ndepth = (depth > 1 ) ? (depth >> 1) : 1;
hr = _Create( depth, ndepth, (filter & TEX_FILTER_WRAP_W) != 0, tfZ );
if ( FAILED(hr) )
return hr;
#ifdef _DEBUG
memset( row, 0xCD, sizeof(XMVECTOR)*width );
#endif
auto xFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfX.get() ) + tfX->sizeInBytes );
auto yFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfY.get() ) + tfY->sizeInBytes );
auto zFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfZ.get() ) + tfZ->sizeInBytes );
// Count times slices get written (and clear out any leftover accumulation slices from last miplevel)
for( FilterFrom* zFrom = tfZ->from; zFrom < zFromEnd; )
{
for ( size_t j = 0; j < zFrom->count; ++j )
{
size_t w = zFrom->to[ j ].u;
assert( w < ndepth );
TriangleRow* sliceAcc = &sliceActive.get()[ w ];
++sliceAcc->remaining;
if ( sliceAcc->scanline )
{
memset( sliceAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth * nheight );
}
}
zFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( zFrom ) + zFrom->sizeInBytes );
}
// Filter image
size_t z = 0;
for( FilterFrom* zFrom = tfZ->from; zFrom < zFromEnd; ++z )
{
// Create accumulation slices as needed
for ( size_t j = 0; j < zFrom->count; ++j )
{
size_t w = zFrom->to[ j ].u;
assert( w < ndepth );
TriangleRow* sliceAcc = &sliceActive.get()[ w ];
if ( !sliceAcc->scanline )
{
if ( sliceFree )
{
// Steal and reuse scanline from 'free slice' list
// (it will always be at least as large as nwidth*nheight due to loop decending order)
assert( sliceFree->scanline != 0 );
sliceAcc->scanline.reset( sliceFree->scanline.release() );
sliceFree = sliceFree->next;
}
else
{
sliceAcc->scanline.reset( reinterpret_cast<XMVECTOR*>( _aligned_malloc( sizeof(XMVECTOR) * nwidth * nheight, 16 ) ) );
if ( !sliceAcc->scanline )
return E_OUTOFMEMORY;
}
memset( sliceAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth * nheight );
}
}
assert( z < depth );
const Image* src = mipChain.GetImage( level-1, 0, z );
if ( !src )
return E_POINTER;
const uint8_t* pSrc = src->pixels;
size_t rowPitch = src->rowPitch;
const uint8_t* pEndSrc = pSrc + rowPitch * height;
for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; )
{
// Load source scanline
if ( (pSrc + rowPitch) > pEndSrc )
return E_FAIL;
if ( !_LoadScanlineLinear( row, width, pSrc, rowPitch, src->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 < zFrom->count; ++j )
{
size_t w = zFrom->to[ j ].u;
assert( w < ndepth );
float zweight = zFrom->to[ j ].weight;
XMVECTOR* accSlice = sliceActive[ w ].scanline.get();
if ( !accSlice )
return E_POINTER;
for ( size_t k = 0; k < yFrom->count; ++k )
{
size_t v = yFrom->to[ k ].u;
assert( v < nheight );
float yweight = yFrom->to[ k ].weight;
XMVECTOR * accPtr = accSlice + v * nwidth;
for ( size_t l = 0; l < xFrom->count; ++l )
{
size_t u = xFrom->to[ l ].u;
assert( u < nwidth );
XMVECTOR weight = XMVectorReplicate( zweight * yweight * xFrom->to[ l ].weight );
assert( x < width );
accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] );
}
}
}
xFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( xFrom ) + xFrom->sizeInBytes );
}
yFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( yFrom ) + yFrom->sizeInBytes );
}
// Write completed accumulation slices
for ( size_t j = 0; j < zFrom->count; ++j )
{
size_t w = zFrom->to[ j ].u;
assert( w < ndepth );
TriangleRow* sliceAcc = &sliceActive.get()[ w ];
assert( sliceAcc->remaining > 0 );
--sliceAcc->remaining;
if ( !sliceAcc->remaining )
{
const Image* dest = mipChain.GetImage( level, 0, w );
XMVECTOR* pAccSrc = sliceAcc->scanline.get();
if ( !dest || !pAccSrc )
return E_POINTER;
uint8_t* pDest = dest->pixels;
for( size_t h = 0; h < nheight; ++h )
{
if ( !_StoreScanlineLinear( pDest, dest->rowPitch, dest->format, pAccSrc, dest->width, filter ) )
return E_FAIL;
pDest += dest->rowPitch;
pAccSrc += nwidth;
}
// Put slice on freelist to reuse it's allocated scanline
sliceAcc->next = sliceFree;
sliceFree = sliceAcc;
}
}
zFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( zFrom ) + zFrom->sizeInBytes );
}
if ( height > 1 )
height >>= 1;
if ( width > 1 )
width >>= 1;
if ( depth > 1 )
depth >>= 1;
}
return S_OK;
}
//===================================================================================== //=====================================================================================
// Entry-points // Entry-points
//===================================================================================== //=====================================================================================
@ -2172,6 +2583,16 @@ HRESULT GenerateMipMaps( const Image& baseImage, DWORD filter, size_t levels, Sc
mipChain.Release(); mipChain.Release();
return hr; return hr;
case TEX_FILTER_TRIANGLE:
hr = _Setup2DMips( &baseImage, 1, mdata, mipChain );
if ( FAILED(hr) )
return hr;
hr = _Generate2DMipsTriangleFilter( levels, filter, mipChain, 0 );
if ( FAILED(hr) )
mipChain.Release();
return hr;
default: default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
} }
@ -2359,6 +2780,19 @@ HRESULT GenerateMipMaps( const Image* srcImages, size_t nimages, const TexMetada
} }
return hr; return hr;
case TEX_FILTER_TRIANGLE:
hr = _Setup2DMips( &baseImages[0], metadata.arraySize, mdata2, mipChain );
if ( FAILED(hr) )
return hr;
for( size_t item = 0; item < metadata.arraySize; ++item )
{
hr = _Generate2DMipsTriangleFilter( levels, filter, mipChain, item );
if ( FAILED(hr) )
mipChain.Release();
}
return hr;
default: default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
} }
@ -2456,6 +2890,16 @@ HRESULT GenerateMipMaps3D( const Image* baseImages, size_t depth, DWORD filter,
mipChain.Release(); mipChain.Release();
return hr; return hr;
case TEX_FILTER_TRIANGLE:
hr = _Setup3DMips( baseImages, depth, levels, mipChain );
if ( FAILED(hr) )
return hr;
hr = _Generate3DMipsTriangleFilter( depth, levels, filter, mipChain );
if ( FAILED(hr) )
mipChain.Release();
return hr;
default: default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
} }
@ -2554,6 +2998,16 @@ HRESULT GenerateMipMaps3D( const Image* srcImages, size_t nimages, const TexMeta
mipChain.Release(); mipChain.Release();
return hr; return hr;
case TEX_FILTER_TRIANGLE:
hr = _Setup3DMips( &baseImages[0], metadata.depth, levels, mipChain );
if ( FAILED(hr) )
return hr;
hr = _Generate3DMipsTriangleFilter( metadata.depth, levels, filter, mipChain );
if ( FAILED(hr) )
mipChain.Release();
return hr;
default: default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
} }

View File

@ -206,6 +206,10 @@ static bool _UseWICFiltering( _In_ DXGI_FORMAT format, _In_ DWORD filter )
return false; return false;
} }
break; break;
case TEX_FILTER_TRIANGLE:
// WIC does not implement this filter
return false;
} }
return true; return true;
@ -582,6 +586,159 @@ static HRESULT _ResizeCubicFilter( _In_ const Image& srcImage, _In_ DWORD filter
} }
//--- 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<XMVECTOR*>( _aligned_malloc( sizeof(XMVECTOR) * srcImage.width, 16 ) ) );
if ( !scanline )
return E_OUTOFMEMORY;
std::unique_ptr<TriangleRow[]> rowActive( new (std::nothrow) TriangleRow[ destImage.height ] );
if ( !rowActive )
return E_OUTOFMEMORY;
TriangleRow * rowFree = nullptr;
std::unique_ptr<Filter> tfX;
HRESULT hr = _Create( srcImage.width, destImage.width, (filter & TEX_FILTER_WRAP_U) != 0, tfX );
if ( FAILED(hr) )
return hr;
std::unique_ptr<Filter> 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<const FilterFrom*>( reinterpret_cast<const uint8_t*>( tfX.get() ) + tfX->sizeInBytes );
auto yFromEnd = reinterpret_cast<const FilterFrom*>( reinterpret_cast<const uint8_t*>( 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.get()[ v ].remaining;
}
yFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( 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.get()[ 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<XMVECTOR*>( _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<FilterFrom*>( reinterpret_cast<uint8_t*>( 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.get()[ v ];
assert( rowAcc->remaining > 0 );
--rowAcc->remaining;
if ( !rowAcc->remaining )
{
if ( !_StoreScanlineLinear( pDest + (destImage.rowPitch * v), destImage.rowPitch, destImage.format, rowAcc->scanline.get(), destImage.width, filter ) )
return E_FAIL;
// Put row on freelist to reuse it's allocated scanline
rowAcc->next = rowFree;
rowFree = rowAcc;
}
}
yFrom = reinterpret_cast<FilterFrom*>( reinterpret_cast<uint8_t*>( yFrom ) + yFrom->sizeInBytes );
}
return S_OK;
}
//--- Custom filter resize --- //--- Custom filter resize ---
static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage )
{ {
@ -612,6 +769,9 @@ static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In
case TEX_FILTER_CUBIC: case TEX_FILTER_CUBIC:
return _ResizeCubicFilter( srcImage, filter, destImage ); return _ResizeCubicFilter( srcImage, filter, destImage );
case TEX_FILTER_TRIANGLE:
return _ResizeTriangleFilter( srcImage, filter, destImage );
default: default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
} }

View File

@ -22,6 +22,10 @@
#include <directxpackedvector.h> #include <directxpackedvector.h>
#endif #endif
#include <memory>
#include "scoped.h"
namespace DirectX namespace DirectX
{ {
@ -163,7 +167,7 @@ struct CubicFilter
float x; float x;
}; };
inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, bool mirror, _Out_writes_(dest) CubicFilter* cf ) inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _In_ bool mirror, _Out_writes_(dest) CubicFilter* cf )
{ {
assert( source > 0 ); assert( source > 0 );
assert( dest > 0 ); assert( dest > 0 );
@ -206,4 +210,219 @@ inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool
res = a0 + a1*vdx + a2*vdx2 + a3*vdx3; \ res = a0 + a1*vdx + a2*vdx2 + a3*vdx3; \
} }
//-------------------------------------------------------------------------------------
// Triangle filtering helpers
//-------------------------------------------------------------------------------------
namespace TriangleFilter
{
struct FilterTo
{
size_t u;
float weight;
};
struct FilterFrom
{
size_t count;
size_t sizeInBytes;
FilterTo to[1]; // variable-sized array
};
struct Filter
{
size_t sizeInBytes;
size_t totalSize;
FilterFrom from[1]; // variable-sized array
};
struct TriangleRow
{
size_t remaining;
TriangleRow* next;
ScopedAlignedArrayXMVECTOR scanline;
TriangleRow() : remaining(0), next(nullptr) {}
};
static const size_t TF_FILTER_SIZE = sizeof(Filter) - sizeof(FilterFrom);
static const size_t TF_FROM_SIZE = sizeof(FilterFrom) - sizeof(FilterTo);
static const size_t TF_TO_SIZE = sizeof(FilterTo);
static const float TF_EPSILON = 0.00001f;
inline HRESULT _Create( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _Inout_ std::unique_ptr<Filter>& tf )
{
assert( source > 0 );
assert( dest > 0 );
float scale = float(dest) / float(source);
float scaleInv = 0.5f / scale;
// Determine storage required for filter and allocate memory if needed
size_t totalSize = TF_FILTER_SIZE + TF_FROM_SIZE + TF_TO_SIZE;
float repeat = (wrap) ? 1.f : 0.f;
for( size_t u = 0; u < source; ++u )
{
float src = float(u) - 0.5f;
float destMin = src * scale;
float destMax = destMin + scale;
totalSize += TF_FROM_SIZE + TF_TO_SIZE + size_t( destMax - destMin + repeat + 1.f ) * TF_TO_SIZE * 2;
}
uint8_t* pFilter = nullptr;
if ( tf )
{
// See if existing filter memory block is large enough to reuse
if ( tf->totalSize >= totalSize )
{
pFilter = reinterpret_cast<uint8_t*>( tf.get() );
}
else
{
// Need to reallocate filter memory block
tf.reset( nullptr );
}
}
if ( !tf )
{
// Allocate filter memory block
pFilter = new (std::nothrow) uint8_t[ totalSize ];
if ( !pFilter )
return E_OUTOFMEMORY;
tf.reset( reinterpret_cast<Filter*>( pFilter ) );
tf->totalSize = totalSize;
}
assert( pFilter != 0 );
// Filter setup
size_t sizeInBytes = TF_FILTER_SIZE;
size_t accumU = 0;
float accumWeight = 0.f;
for( size_t u = 0; u < source; ++u )
{
// Setup from entry
size_t sizeFrom = sizeInBytes;
auto pFrom = reinterpret_cast<FilterFrom*>( pFilter + sizeInBytes );
sizeInBytes += TF_FROM_SIZE;
if ( sizeInBytes > totalSize )
return E_FAIL;
size_t toCount = 0;
// Perform two passes to capture the influences from both sides
for( size_t j = 0; j < 2; ++j )
{
float src = float( u + j ) - 0.5f;
float destMin = src * scale;
float destMax = destMin + scale;
if ( !wrap )
{
// Clamp
if ( destMin < 0.f )
destMin = 0.f;
if ( destMax > float(dest) )
destMax = float(dest);
}
for( auto k = static_cast<ptrdiff_t>( floorf( destMin ) ); float(k) < destMax; ++k )
{
float d0 = float(k);
float d1 = d0 + 1.f;
size_t u0;
if ( k < 0 )
{
// Handle wrap
u0 = size_t( k + ptrdiff_t(dest) );
}
else if ( k >= ptrdiff_t(dest) )
{
// Handle wrap
u0 = size_t( k - ptrdiff_t(dest) );
}
else
{
u0 = size_t( k );
}
// Save previous accumulated weight (if any)
if ( u0 != accumU )
{
if ( accumWeight > TF_EPSILON )
{
auto pTo = reinterpret_cast<FilterTo*>( pFilter + sizeInBytes );
sizeInBytes += TF_TO_SIZE;
++toCount;
if ( sizeInBytes > totalSize )
return E_FAIL;
pTo->u = accumU;
pTo->weight = accumWeight;
}
accumWeight = 0.f;
accumU = u0;
}
// Clip destination
if ( d0 < destMin )
d0 = destMin;
if ( d1 > destMax )
d1 = destMax;
// Calculate average weight over destination pixel
float weight;
if ( !wrap && src < 0.f )
weight = 1.f;
else if ( !wrap && ( ( src + 1.f ) >= float(source) ) )
weight = 0.f;
else
weight = (d0 + d1) * scaleInv - src;
accumWeight += (d1 - d0) * ( j ? (1.f - weight) : weight );
}
}
// Store accumulated weight
if ( accumWeight > TF_EPSILON )
{
auto pTo = reinterpret_cast<FilterTo*>( pFilter + sizeInBytes );
sizeInBytes += TF_TO_SIZE;
++toCount;
if ( sizeInBytes > totalSize )
return E_FAIL;
pTo->u = accumU;
pTo->weight = accumWeight;
}
accumWeight = 0.f;
// Finalize from entry
pFrom->count = toCount;
pFrom->sizeInBytes = sizeInBytes - sizeFrom;
}
tf->sizeInBytes = sizeInBytes;
return S_OK;
}
}; // namespace
}; // namespace }; // namespace

View File

@ -183,16 +183,19 @@ SValue g_pFilters[] =
{ L"CUBIC", TEX_FILTER_CUBIC }, { L"CUBIC", TEX_FILTER_CUBIC },
{ L"FANT", TEX_FILTER_FANT }, { L"FANT", TEX_FILTER_FANT },
{ L"BOX", TEX_FILTER_BOX }, { L"BOX", TEX_FILTER_BOX },
{ L"TRIANGLE", TEX_FILTER_TRIANGLE },
{ L"POINT_DITHER", TEX_FILTER_POINT | TEX_FILTER_DITHER }, { L"POINT_DITHER", TEX_FILTER_POINT | TEX_FILTER_DITHER },
{ L"LINEAR_DITHER", TEX_FILTER_LINEAR | TEX_FILTER_DITHER }, { L"LINEAR_DITHER", TEX_FILTER_LINEAR | TEX_FILTER_DITHER },
{ L"CUBIC_DITHER", TEX_FILTER_CUBIC | TEX_FILTER_DITHER }, { L"CUBIC_DITHER", TEX_FILTER_CUBIC | TEX_FILTER_DITHER },
{ L"FANT_DITHER", TEX_FILTER_FANT | TEX_FILTER_DITHER }, { L"FANT_DITHER", TEX_FILTER_FANT | TEX_FILTER_DITHER },
{ L"BOX_DITHER", TEX_FILTER_BOX | TEX_FILTER_DITHER }, { L"BOX_DITHER", TEX_FILTER_BOX | TEX_FILTER_DITHER },
{ L"TRIANGLE_DITHER", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER },
{ L"POINT_DITHER_DIFFUSION", TEX_FILTER_POINT | TEX_FILTER_DITHER_DIFFUSION }, { L"POINT_DITHER_DIFFUSION", TEX_FILTER_POINT | TEX_FILTER_DITHER_DIFFUSION },
{ L"LINEAR_DITHER_DIFFUSION", TEX_FILTER_LINEAR | TEX_FILTER_DITHER_DIFFUSION }, { L"LINEAR_DITHER_DIFFUSION", TEX_FILTER_LINEAR | TEX_FILTER_DITHER_DIFFUSION },
{ L"CUBIC_DITHER_DIFFUSION", TEX_FILTER_CUBIC | TEX_FILTER_DITHER_DIFFUSION }, { L"CUBIC_DITHER_DIFFUSION", TEX_FILTER_CUBIC | TEX_FILTER_DITHER_DIFFUSION },
{ L"FANT_DITHER_DIFFUSION", TEX_FILTER_FANT | TEX_FILTER_DITHER_DIFFUSION }, { L"FANT_DITHER_DIFFUSION", TEX_FILTER_FANT | TEX_FILTER_DITHER_DIFFUSION },
{ L"BOX_DITHER_DIFFUSION", TEX_FILTER_BOX | TEX_FILTER_DITHER_DIFFUSION }, { L"BOX_DITHER_DIFFUSION", TEX_FILTER_BOX | TEX_FILTER_DITHER_DIFFUSION },
{ L"TRIANGLE_DITHER_DIFFUSION", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER_DIFFUSION },
{ nullptr, TEX_FILTER_DEFAULT } { nullptr, TEX_FILTER_DEFAULT }
}; };