1 //-------------------------------------------------------------------------------------
2 // filters.h
3 //
4 // Utility header with helpers for implementing image filters
5 //
6 // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
7 // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
9 // PARTICULAR PURPOSE.
10 //
11 // Copyright (c) Microsoft Corporation. All rights reserved.
12 //-------------------------------------------------------------------------------------
13
14 #pragma once
15
16 #ifdef USE_XNAMATH
17 #include <xnamath.h>
18 #else
19 #include <directxmath.h>
20 #include <directxpackedvector.h>
21 #endif
22
23 #include <memory>
24
25 #include "scoped.h"
26
27 namespace DirectX
28 {
29
30 //-------------------------------------------------------------------------------------
31 // Box filtering helpers
32 //-------------------------------------------------------------------------------------
33
34 XMGLOBALCONST XMVECTORF32 g_boxScale = { 0.25f, 0.25f, 0.25f, 0.25f };
35 XMGLOBALCONST XMVECTORF32 g_boxScale3D = { 0.125f, 0.125f, 0.125f, 0.125f };
36
37 #define AVERAGE4( res, p0, p1, p2, p3 ) \
38 { \
39 XMVECTOR v = XMVectorAdd( (p0), (p1) ); \
40 v = XMVectorAdd( v, (p2) ); \
41 v = XMVectorAdd( v, (p3) ); \
42 res = XMVectorMultiply( v, g_boxScale ); \
43 }
44
45 #define AVERAGE8( res, p0, p1, p2, p3, p4, p5, p6, p7) \
46 { \
47 XMVECTOR v = XMVectorAdd( (p0), (p1) ); \
48 v = XMVectorAdd( v, (p2) ); \
49 v = XMVectorAdd( v, (p3) ); \
50 v = XMVectorAdd( v, (p4) ); \
51 v = XMVectorAdd( v, (p5) ); \
52 v = XMVectorAdd( v, (p6) ); \
53 v = XMVectorAdd( v, (p7) ); \
54 res = XMVectorMultiply( v, g_boxScale3D ); \
55 }
56
57
58 //-------------------------------------------------------------------------------------
59 // Linear filtering helpers
60 //-------------------------------------------------------------------------------------
61
62 struct LinearFilter
63 {
64 size_t u0;
65 float weight0;
66 size_t u1;
67 float weight1;
68 };
69
_CreateLinearFilter(_In_ size_t source,_In_ size_t dest,_In_ bool wrap,_Out_writes_ (dest)LinearFilter * lf)70 inline void _CreateLinearFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _Out_writes_(dest) LinearFilter* lf )
71 {
72 assert( source > 0 );
73 assert( dest > 0 );
74 assert( lf != 0 );
75
76 float scale = float(source) / float(dest);
77
78 // Mirror is the same case as clamp for linear
79
80 for( size_t u = 0; u < dest; ++u )
81 {
82 float srcB = ( float(u) + 0.5f ) * scale + 0.5f;
83
84 ptrdiff_t isrcB = ptrdiff_t(srcB);
85 ptrdiff_t isrcA = isrcB - 1;
86
87 if ( isrcA < 0 )
88 {
89 isrcA = ( wrap ) ? ( source - 1) : 0;
90 }
91
92 if ( size_t(isrcB) >= source )
93 {
94 isrcB = ( wrap ) ? 0 : ( source - 1);
95 }
96
97 float weight = 1.0f + float(isrcB) - srcB;
98
99 auto& entry = lf[ u ];
100 entry.u0 = size_t(isrcA);
101 entry.weight0 = weight;
102
103 entry.u1 = size_t(isrcB);
104 entry.weight1 = 1.0f - weight;
105 }
106 }
107
108 #define BILINEAR_INTERPOLATE( res, x, y, r0, r1 ) \
109 res = ( y.weight0 * ( (r0)[ x.u0 ] * x.weight0 + (r0)[ x.u1 ] * x.weight1 ) ) \
110 + ( y.weight1 * ( (r1)[ x.u0 ] * x.weight0 + (r1)[ x.u1 ] * x.weight1 ) )
111
112 #define TRILINEAR_INTERPOLATE( res, x, y, z, r0, r1, r2, r3 ) \
113 res = ( z.weight0 * ( ( y.weight0 * ( (r0)[ x.u0 ] * x.weight0 + (r0)[ x.u1 ] * x.weight1 ) ) \
114 + ( y.weight1 * ( (r1)[ x.u0 ] * x.weight0 + (r1)[ x.u1 ] * x.weight1 ) ) ) ) \
115 + ( z.weight1 * ( ( y.weight0 * ( (r2)[ x.u0 ] * x.weight0 + (r2)[ x.u1 ] * x.weight1 ) ) \
116 + ( y.weight1 * ( (r3)[ x.u0 ] * x.weight0 + (r3)[ x.u1 ] * x.weight1 ) ) ) )
117
118
119 //-------------------------------------------------------------------------------------
120 // Cubic filtering helpers
121 //-------------------------------------------------------------------------------------
122
123 XMGLOBALCONST XMVECTORF32 g_cubicThird = { 1.f/3.f, 1.f/3.f, 1.f/3.f, 1.f/3.f };
124 XMGLOBALCONST XMVECTORF32 g_cubicSixth = { 1.f/6.f, 1.f/6.f, 1.f/6.f, 1.f/6.f };
125 XMGLOBALCONST XMVECTORF32 g_cubicHalf = { 1.f/2.f, 1.f/2.f, 1.f/2.f, 1.f/2.f };
126
bounduvw(ptrdiff_t u,ptrdiff_t maxu,bool wrap,bool mirror)127 inline ptrdiff_t bounduvw( ptrdiff_t u, ptrdiff_t maxu, bool wrap, bool mirror )
128 {
129 if ( wrap )
130 {
131 if ( u < 0 )
132 {
133 u = maxu + u + 1;
134 }
135 else if ( u > maxu )
136 {
137 u = u - maxu - 1;
138 }
139 }
140 else if ( mirror )
141 {
142 if ( u < 0 )
143 {
144 u = ( -u ) - 1;
145 }
146 else if ( u > maxu )
147 {
148 u = maxu - (u - maxu - 1);
149 }
150 }
151
152 // Handles clamp, but also a safety factor for degenerate images for wrap/mirror
153 u = std::min<ptrdiff_t>( u, maxu );
154 u = std::max<ptrdiff_t>( u, 0 );
155
156 return u;
157 }
158
159 struct CubicFilter
160 {
161 size_t u0;
162 size_t u1;
163 size_t u2;
164 size_t u3;
165 float x;
166 };
167
_CreateCubicFilter(_In_ size_t source,_In_ size_t dest,_In_ bool wrap,_In_ bool mirror,_Out_writes_ (dest)CubicFilter * cf)168 inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _In_ bool mirror, _Out_writes_(dest) CubicFilter* cf )
169 {
170 assert( source > 0 );
171 assert( dest > 0 );
172 assert( cf != 0 );
173
174 float scale = float(source) / float(dest);
175
176 for( size_t u = 0; u < dest; ++u )
177 {
178 float srcB = ( float(u) + 0.5f ) * scale - 0.5f;
179
180 ptrdiff_t isrcB = bounduvw( ptrdiff_t(srcB), source - 1, wrap, mirror );
181 ptrdiff_t isrcA = bounduvw( isrcB - 1, source - 1, wrap, mirror );
182 ptrdiff_t isrcC = bounduvw( isrcB + 1, source - 1, wrap, mirror );
183 ptrdiff_t isrcD = bounduvw( isrcB + 2, source - 1, wrap, mirror );
184
185 auto& entry = cf[ u ];
186 entry.u0 = size_t(isrcA);
187 entry.u1 = size_t(isrcB);
188 entry.u2 = size_t(isrcC);
189 entry.u3 = size_t(isrcD);
190
191 float x = srcB - float(isrcB);
192 entry.x = x;
193 }
194 }
195
196 #define CUBIC_INTERPOLATE( res, dx, p0, p1, p2, p3 ) \
197 { \
198 XMVECTOR a0 = (p1); \
199 XMVECTOR d0 = (p0) - a0; \
200 XMVECTOR d2 = (p2) - a0; \
201 XMVECTOR d3 = (p3) - a0; \
202 XMVECTOR a1 = d2 - g_cubicThird*d0 - g_cubicSixth*d3; \
203 XMVECTOR a2 = g_cubicHalf*d0 + g_cubicHalf*d2; \
204 XMVECTOR a3 = g_cubicSixth*d3 - g_cubicSixth*d0 - g_cubicHalf*d2; \
205 XMVECTOR vdx = XMVectorReplicate( dx ); \
206 XMVECTOR vdx2 = vdx * vdx; \
207 XMVECTOR vdx3 = vdx2 * vdx; \
208 res = a0 + a1*vdx + a2*vdx2 + a3*vdx3; \
209 }
210
211
212 //-------------------------------------------------------------------------------------
213 // Triangle filtering helpers
214 //-------------------------------------------------------------------------------------
215
216 namespace TriangleFilter
217 {
218 struct FilterTo
219 {
220 size_t u;
221 float weight;
222 };
223
224 struct FilterFrom
225 {
226 size_t count;
227 size_t sizeInBytes;
228 FilterTo to[1]; // variable-sized array
229 };
230
231 struct Filter
232 {
233 size_t sizeInBytes;
234 size_t totalSize;
235 FilterFrom from[1]; // variable-sized array
236 };
237
238 struct TriangleRow
239 {
240 size_t remaining;
241 TriangleRow* next;
242 ScopedAlignedArrayXMVECTOR scanline;
243
TriangleRowTriangleRow244 TriangleRow() : remaining(0), next(nullptr) {}
245 };
246
247 static const size_t TF_FILTER_SIZE = sizeof(Filter) - sizeof(FilterFrom);
248 static const size_t TF_FROM_SIZE = sizeof(FilterFrom) - sizeof(FilterTo);
249 static const size_t TF_TO_SIZE = sizeof(FilterTo);
250
251 static const float TF_EPSILON = 0.00001f;
252
_Create(_In_ size_t source,_In_ size_t dest,_In_ bool wrap,_Inout_ std::unique_ptr<Filter> & tf)253 inline HRESULT _Create( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _Inout_ std::unique_ptr<Filter>& tf )
254 {
255 assert( source > 0 );
256 assert( dest > 0 );
257
258 float scale = float(dest) / float(source);
259 float scaleInv = 0.5f / scale;
260
261 // Determine storage required for filter and allocate memory if needed
262 size_t totalSize = TF_FILTER_SIZE + TF_FROM_SIZE + TF_TO_SIZE;
263 float repeat = (wrap) ? 1.f : 0.f;
264
265 for( size_t u = 0; u < source; ++u )
266 {
267 float src = float(u) - 0.5f;
268 float destMin = src * scale;
269 float destMax = destMin + scale;
270
271 totalSize += TF_FROM_SIZE + TF_TO_SIZE + size_t( destMax - destMin + repeat + 1.f ) * TF_TO_SIZE * 2;
272 }
273
274 uint8_t* pFilter = nullptr;
275
276 if ( tf )
277 {
278 // See if existing filter memory block is large enough to reuse
279 if ( tf->totalSize >= totalSize )
280 {
281 pFilter = reinterpret_cast<uint8_t*>( tf.get() );
282 }
283 else
284 {
285 // Need to reallocate filter memory block
286 tf.reset( nullptr );
287 }
288 }
289
290 if ( !tf )
291 {
292 // Allocate filter memory block
293 pFilter = new (std::nothrow) uint8_t[ totalSize ];
294 if ( !pFilter )
295 return E_OUTOFMEMORY;
296
297 tf.reset( reinterpret_cast<Filter*>( pFilter ) );
298 tf->totalSize = totalSize;
299 }
300
301 assert( pFilter != 0 );
302
303 // Filter setup
304 size_t sizeInBytes = TF_FILTER_SIZE;
305 size_t accumU = 0;
306 float accumWeight = 0.f;
307
308 for( size_t u = 0; u < source; ++u )
309 {
310 // Setup from entry
311 size_t sizeFrom = sizeInBytes;
312 auto pFrom = reinterpret_cast<FilterFrom*>( pFilter + sizeInBytes );
313 sizeInBytes += TF_FROM_SIZE;
314
315 if ( sizeInBytes > totalSize )
316 return E_FAIL;
317
318 size_t toCount = 0;
319
320 // Perform two passes to capture the influences from both sides
321 for( size_t j = 0; j < 2; ++j )
322 {
323 float src = float( u + j ) - 0.5f;
324
325 float destMin = src * scale;
326 float destMax = destMin + scale;
327
328 if ( !wrap )
329 {
330 // Clamp
331 if ( destMin < 0.f )
332 destMin = 0.f;
333 if ( destMax > float(dest) )
334 destMax = float(dest);
335 }
336
337 for( auto k = static_cast<ptrdiff_t>( floorf( destMin ) ); float(k) < destMax; ++k )
338 {
339 float d0 = float(k);
340 float d1 = d0 + 1.f;
341
342 size_t u0;
343 if ( k < 0 )
344 {
345 // Handle wrap
346 u0 = size_t( k + ptrdiff_t(dest) );
347 }
348 else if ( k >= ptrdiff_t(dest) )
349 {
350 // Handle wrap
351 u0 = size_t( k - ptrdiff_t(dest) );
352 }
353 else
354 {
355 u0 = size_t( k );
356 }
357
358 // Save previous accumulated weight (if any)
359 if ( u0 != accumU )
360 {
361 if ( accumWeight > TF_EPSILON )
362 {
363 auto pTo = reinterpret_cast<FilterTo*>( pFilter + sizeInBytes );
364 sizeInBytes += TF_TO_SIZE;
365 ++toCount;
366
367 if ( sizeInBytes > totalSize )
368 return E_FAIL;
369
370 pTo->u = accumU;
371 pTo->weight = accumWeight;
372 }
373
374 accumWeight = 0.f;
375 accumU = u0;
376 }
377
378 // Clip destination
379 if ( d0 < destMin )
380 d0 = destMin;
381 if ( d1 > destMax )
382 d1 = destMax;
383
384 // Calculate average weight over destination pixel
385
386 float weight;
387 if ( !wrap && src < 0.f )
388 weight = 1.f;
389 else if ( !wrap && ( ( src + 1.f ) >= float(source) ) )
390 weight = 0.f;
391 else
392 weight = (d0 + d1) * scaleInv - src;
393
394 accumWeight += (d1 - d0) * ( j ? (1.f - weight) : weight );
395 }
396 }
397
398 // Store accumulated weight
399 if ( accumWeight > TF_EPSILON )
400 {
401 auto pTo = reinterpret_cast<FilterTo*>( pFilter + sizeInBytes );
402 sizeInBytes += TF_TO_SIZE;
403 ++toCount;
404
405 if ( sizeInBytes > totalSize )
406 return E_FAIL;
407
408 pTo->u = accumU;
409 pTo->weight = accumWeight;
410 }
411
412 accumWeight = 0.f;
413
414 // Finalize from entry
415 pFrom->count = toCount;
416 pFrom->sizeInBytes = sizeInBytes - sizeFrom;
417 }
418
419 tf->sizeInBytes = sizeInBytes;
420
421 return S_OK;
422 }
423
424 }; // namespace
425
426 }; // namespace
427