Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
Permutohedral.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 johannes hanika.
4 Copyright (C) 2011 Bruce Guenter.
5 Copyright (C) 2011, 2014 Ulrich Pegelow.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2012, 2014, 2016 Tobias Ellinghaus.
8 Copyright (C) 2016 Roman Lebedev.
9 Copyright (C) 2020 Heiko Bauke.
10 Copyright (C) 2020 Ralf Brown.
11 Copyright (C) 2022 Martin Baƙinka.
12
13 darktable is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 darktable is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with darktable. If not, see <http://www.gnu.org/licenses/>.
25*/
26/*
27 this file has been taken from ImageStack (http://code.google.com/p/imagestack/)
28 and adjusted slightly to fit darktable.
29
30 ImageStack is released under the new bsd license:
31
32Copyright (c) 2010, Andrew Adams
33All rights reserved.
34
35Redistribution and use in source and binary forms, with or without modification, are permitted provided that
36the following conditions are met:
37
38 * Redistributions of source code must retain the above copyright notice, this list of conditions and the
39following disclaimer.
40 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
41the following disclaimer in the documentation and/or other materials provided with the distribution.
42 * Neither the name of the Stanford Graphics Lab nor the names of its contributors may be used to endorse
43or promote products derived from this software without specific prior written permission.
44
45THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
46WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
47PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
48DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
49PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
50CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
51OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
52DAMAGE.
53*/
54
55#ifndef DT_IOP_PERMUTOHEDRAL_H
56#define DT_IOP_PERMUTOHEDRAL_H
57
58/*******************************************************************
59 * Permutohedral Lattice implementation from: *
60 * Fast High-Dimensional Filtering using the Permutohedral Lattice *
61 * Andrew Adams, Jongmin Baek, Abe Davis *
62 *******************************************************************/
63
64#include <algorithm>
65#include <math.h>
66#include <stdio.h>
67#include <stdlib.h>
68#include <string.h>
69
70#include <iostream>
71
72/*******************************************************************
73 * Hash table implementation for permutohedral lattice *
74 * *
75 * The lattice points are stored sparsely using a hash table. *
76 * The key for each point is its spatial location in the (d+1)- *
77 * dimensional space. *
78 * *
79 *******************************************************************/
80template <int KD, int VD> class HashTablePermutohedral
81{
82public:
83 // Struct for a key
84 struct Key
85 {
86 Key() = default;
87
88 Key(const Key &origin, int dim, int direction) // construct neighbor in dimension 'dim'
89 {
90 for(int i = 0; i < KD; i++) key[i] = origin.key[i] + direction;
91 key[dim] = origin.key[dim] - direction * KD;
92 setHash();
93 }
94
95 Key(const Key &) = default; // let the compiler write the copy constructor
96
97 Key &operator=(const Key &) = default;
98
99 void setKey(int idx, short val)
100 {
101 key[idx] = val;
102 }
103
104 void setHash()
105 {
106 size_t k = 0;
107 for(int i = 0; i < KD; i++)
108 {
109 k += key[i];
110 k *= 2531011;
111 }
112 hash = (unsigned)k;
113 }
114
115 bool operator==(const Key &other) const
116 {
117 if(hash != other.hash) return false;
118 for(int i = 0; i < KD; i++)
119 {
120 if(key[i] != other.key[i]) return false;
121 }
122 return true;
123 }
124
125 unsigned hash{ 0 }; // cache the hash value for this key
126 short key[KD]{}; // key is a KD-dimensional vector
127 };
128
129public:
130 // Struct for an associated value
131 struct Value
132 {
133 Value() = default;
134
136 {
137 for(int i = 0; i < VD; i++)
138 {
139 value[i] = init;
140 }
141 }
142
143 Value(const Value &) = default; // let the compiler write the copy constructor
144
145 Value &operator=(const Value &) = default;
146
147 static void clear(float *val)
148 {
149 for(int i = 0; i < VD; i++) val[i] = 0;
150 }
151
152 void setValue(int idx, short val)
153 {
154 value[idx] = val;
155 }
156
157 void addValue(int idx, short val)
158 {
159 value[idx] += val;
160 }
161
162 void add(const Value &other)
163 {
164 for(int i = 0; i < VD; i++)
165 {
166 value[i] += other.value[i];
167 }
168 }
169
170 void add(const float *other, float weight)
171 {
172 for(int i = 0; i < VD; i++)
173 {
174 value[i] += weight * other[i];
175 }
176 }
177
178 void addTo(float *dest, float weight) const
179 {
180 for(int i = 0; i < VD; i++)
181 {
182 dest[i] += weight * value[i];
183 }
184 }
185
186 void mix(const Value *left, const Value *center, const Value *right)
187 {
188 for(int i = 0; i < VD; i++)
189 {
190 value[i] = (0.25f * left->value[i] + 0.5f * center->value[i] + 0.25f * right->value[i]);
191 }
192 }
193
194 Value &operator+=(const Value &other)
195 {
196 for(int i = 0; i < VD; i++)
197 {
198 value[i] += other.value[i];
199 }
200 return *this;
201 }
202
203 float value[VD]{};
204 };
205
206public:
207 /* Constructor
208 * kd_: the dimensionality of the position vectors on the hyperplane.
209 * vd_: the dimensionality of the value vectors
210 */
212 {
213 capacity = 1 << 15;
214 capacity_bits = 0x7fff;
215 filled = 0;
216 entries = new Entry[capacity];
217 keys = new Key[maxFill()];
218 values = new Value[maxFill()]{ 0 };
219 }
220
222
224 {
225 delete[] entries;
226 delete[] keys;
227 delete[] values;
228 }
229
231
232 // Returns the number of vectors stored.
233 int size() const
234 {
235 return filled;
236 }
237
238 size_t maxFill() const
239 {
240 return capacity / 2;
241 }
242
243 // Returns a pointer to the keys array.
244 const Key *getKeys() const
245 {
246 return keys;
247 }
248
249 // Returns a pointer to the values array.
251 {
252 return values;
253 }
254
255 /* Returns the index into the hash table for a given key.
256 * key: a reference to the position vector.
257 * create: a flag specifying whether an entry should be created,
258 * should an entry with the given key not found.
259 */
260 int lookupOffset(const Key &key, bool create = true)
261 {
262 size_t h = key.hash & capacity_bits;
263 // Find the entry with the given key
264 while(1)
265 {
266 Entry e = entries[h];
267 // check if the cell is empty
268 if(e.keyIdx == -1)
269 {
270 if(!create) return -1; // Return not found.
271 // Double hash table size if necessary
272 if(filled >= maxFill())
273 {
274 grow();
275 }
276 // need to create an entry. Store the given key.
277 keys[filled] = key;
278 entries[h].keyIdx = filled;
279 return filled++;
280 }
281
282 // check if the cell has a matching key
283 if(keys[e.keyIdx] == key) return e.keyIdx;
284
285 // increment the bucket with wraparound
286 h = (h + 1) & capacity_bits;
287 }
288 }
289
290 /* Looks up the value vector associated with a given key vector.
291 * k : reference to the key vector to be looked up.
292 * create : true if a non-existing key should be created.
293 */
294 Value *lookup(const Key &k, bool create = true)
295 {
296 int offset = lookupOffset(k, create);
297 return (offset < 0) ? nullptr : values + offset;
298 };
299
300 /* Grows the size of the hash table */
301 void grow(int order = 1)
302 {
303 size_t oldCapacity = capacity;
304 while(order-- > 0)
305 {
306 capacity *= 2;
307 capacity_bits = (capacity_bits << 1) | 1;
308 }
309
310 // Migrate the value vectors.
311 Value *newValues = new Value[maxFill()];
312 std::copy(values, values + filled, newValues);
313 delete[] values;
314 values = newValues;
315
316 // Migrate the key vectors.
317 Key *newKeys = new Key[maxFill()];
318 std::copy(keys, keys + filled, newKeys);
319 delete[] keys;
320 keys = newKeys;
321
322 Entry *newEntries = new Entry[capacity];
323
324 // Migrate the table of indices.
325 for(size_t i = 0; i < oldCapacity; i++)
326 {
327 if(entries[i].keyIdx == -1) continue;
328 size_t h = keys[entries[i].keyIdx].hash & capacity_bits;
329 while(newEntries[h].keyIdx != -1)
330 {
331 h = (h + 1) & capacity_bits;
332 }
333 newEntries[h] = entries[i];
334 }
335 delete[] entries;
336 entries = newEntries;
337 }
338
339private:
340 // Private struct for the hash table entries.
341 struct Entry
342 {
343 int keyIdx{ -1 };
344 };
345
350 unsigned long capacity_bits;
351};
352
353
354/******************************************************************
355 * The algorithm class that performs the filter *
356 * *
357 * PermutohedralLattice::splat(...) and *
358 * PermutohedralLattic::slice() do almost all the work. *
359 * *
360 ******************************************************************/
361template <int D, int VD> class PermutohedralLattice
362{
363private:
364 // short-hand for types we use
366 typedef typename HashTable::Key Key;
367 typedef typename HashTable::Value Value;
368
369public:
370 /* Constructor
371 * d_ : dimensionality of key vectors
372 * vd_ : dimensionality of value vectors
373 * nData_ : number of points in the input
374 */
375 // nThreads is clamped to at least 1 rather than trusted: it is a signed int reaching
376 // `new HashTable[nThreads]` below, so a zero or negative value would convert to a huge
377 // size_t. GCC cannot prove the range and says so (-Walloc-size-larger-than), and it is
378 // right -- nothing in the signature stops a caller passing 0.
379 PermutohedralLattice(size_t nData_, int nThreads_ = 1) : nData(nData_), nThreads(nThreads_ > 0 ? (nThreads_ < 1024 ? nThreads_ : 1024) : 1)
380 {
381 // Allocate storage for various arrays
382 float *scaleFactorTmp = new float[D];
383 int *canonicalTmp = new int[(D + 1) * (D + 1)];
384
385 replay = new ReplayEntry[nData];
386
387 // compute the coordinates of the canonical simplex, in which
388 // the difference between a contained point and the zero
389 // remainder vertex is always in ascending order. (See pg.4 of paper.)
390 for(int i = 0; i <= D; i++)
391 {
392 for(int j = 0; j <= D - i; j++) canonicalTmp[i * (D + 1) + j] = i;
393 for(int j = D - i + 1; j <= D; j++) canonicalTmp[i * (D + 1) + j] = i - (D + 1);
394 }
395 canonical = canonicalTmp;
396
397 // Compute parts of the rotation matrix E. (See pg.4-5 of paper.)
398 for(int i = 0; i < D; i++)
399 {
400 // the diagonal entries for normalization
401 scaleFactorTmp[i] = 1.0f / (sqrtf((float)(i + 1) * (i + 2)));
402
403 /* We presume that the user would like to do a Gaussian blur of standard deviation
404 * 1 in each dimension (or a total variance of d, summed over dimensions.)
405 * Because the total variance of the blur performed by this algorithm is not d,
406 * we must scale the space to offset this.
407 *
408 * The total variance of the algorithm is (See pg.6 and 10 of paper):
409 * [variance of splatting] + [variance of blurring] + [variance of splatting]
410 * = d(d+1)(d+1)/12 + d(d+1)(d+1)/2 + d(d+1)(d+1)/12
411 * = 2d(d+1)(d+1)/3.
412 *
413 * So we need to scale the space by (d+1)sqrt(2/3).
414 */
415 scaleFactorTmp[i] *= (D + 1) * sqrtf(2.0 / 3);
416 }
417 scaleFactor = scaleFactorTmp;
418
419 // A std::vector, not `new HashTable[nThreads]`. With a non-constant count GCC emits its
420 // own overflow check -- a call to operator new[](SIZE_MAX) raising
421 // std::bad_array_new_length -- and then reports that generated SIZE_MAX as an allocation
422 // exceeding PTRDIFF_MAX. That diagnostic is NOT disabled by -Wno-alloc-size-larger-than
423 // (tried, per-file, verified present on the compile line) nor by a #pragma, which the
424 // middle end ignores. The container's allocation does not take that shape.
425 //
426 // HashTable is default-constructible with copy construction and assignment deleted, and
427 // has no move constructor. That is fine here and must stay fine: vector's count
428 // constructor value-initialises in place and needs only DefaultInsertable. Do NOT add a
429 // push_back(), resize() or copy of this vector -- those need the element to be
430 // MoveInsertable, which it is not, and the failure would be a compile error rather than
431 // anything subtle.
432 // KNOWN WARNING, deliberately left standing. GCC reports
433 // "argument 1 value '18446744073709551615' exceeds maximum object size" here.
434 // It is complaining about a branch IT generated: `new T[n]` with a non-constant n emits
435 // an overflow check calling operator new[](SIZE_MAX) to raise std::bad_array_new_length.
436 // The branch is unreachable -- nThreads is clamped to [1, 1024] in the initialiser list
437 // above -- and the arithmetic here is correct.
438 //
439 // It cannot be suppressed and should not be worked around. Measured, in this order:
440 // - -Wno-alloc-size-larger-than, per-file, verified present on the compile line: no
441 // effect. The diagnostic is the PTRDIFF_MAX form, which that option does not disable.
442 // - #pragma GCC diagnostic around the statement: no effect, it is a middle-end warning.
443 // - std::vector instead of new[]: compiles on GCC, breaks on clang. OpenMP regions here
444 // use default(firstprivate), which copy-constructs every variable they touch, and
445 // HashTable's copy constructor is deleted. A raw pointer copies trivially; a
446 // container does not. That attempt turned CI red on all three clang jobs.
447 // Leave it alone.
449 }
450
452
454 {
455 delete[] hashTables;
456 delete[] scaleFactor;
457 delete[] replay;
458 delete[] canonical;
459 }
460
462
463 /* Performs splatting with given position and value vectors */
464 void splat(float *position, float *value, size_t replay_index, int thread_index = 0) const
465 {
466 float elevated[D + 1];
467 int greedy[D + 1];
468 int rank[D + 1];
469 float barycentric[D + 2];
470 Key key;
471
472 // first rotate position into the (d+1)-dimensional hyperplane
473 elevated[D] = -D * position[D - 1] * scaleFactor[D - 1];
474 for(int i = D - 1; i > 0; i--)
475 elevated[i]
476 = (elevated[i + 1] - i * position[i - 1] * scaleFactor[i - 1] + (i + 2) * position[i] * scaleFactor[i]);
477 elevated[0] = elevated[1] + 2 * position[0] * scaleFactor[0];
478
479 // prepare to find the closest lattice points
480 constexpr float scale = 1.0f / (D + 1);
481
482 // greedily search for the closest zero-colored lattice point
483 int sum = 0;
484 for(int i = 0; i <= D; i++)
485 {
486 float v = elevated[i] * scale;
487 float up = ceilf(v) * (D + 1);
488 float down = floorf(v) * (D + 1);
489
490 if(up - elevated[i] < elevated[i] - down)
491 greedy[i] = up;
492 else
493 greedy[i] = down;
494
495 sum += greedy[i];
496 }
497 sum /= D + 1;
498
499 // rank differential to find the permutation between this simplex and the canonical one.
500 // (See pg. 3-4 in paper.)
501 memset(rank, 0, sizeof rank);
502 for(int i = 0; i < D; i++)
503 for(int j = i + 1; j <= D; j++)
504 if(elevated[i] - greedy[i] < elevated[j] - greedy[j])
505 rank[i]++;
506 else
507 rank[j]++;
508
509 if(sum > 0)
510 {
511 // sum too large - the point is off the hyperplane.
512 // need to bring down the ones with the smallest differential
513 for(int i = 0; i <= D; i++)
514 {
515 if(rank[i] >= D + 1 - sum)
516 {
517 greedy[i] -= D + 1;
518 rank[i] += sum - (D + 1);
519 }
520 else
521 rank[i] += sum;
522 }
523 }
524 else if(sum < 0)
525 {
526 // sum too small - the point is off the hyperplane
527 // need to bring up the ones with largest differential
528 for(int i = 0; i <= D; i++)
529 {
530 if(rank[i] < -sum)
531 {
532 greedy[i] += D + 1;
533 rank[i] += (D + 1) + sum;
534 }
535 else
536 rank[i] += sum;
537 }
538 }
539
540 // Compute barycentric coordinates (See pg.10 of paper.)
541 memset(barycentric, 0, sizeof barycentric);
542 for(int i = 0; i <= D; i++)
543 {
544 barycentric[D - rank[i]] += (elevated[i] - greedy[i]) * scale;
545 barycentric[D + 1 - rank[i]] -= (elevated[i] - greedy[i]) * scale;
546 }
547 barycentric[0] += 1.0f + barycentric[D + 1];
548
549 // Splat the value into each vertex of the simplex, with barycentric weights.
550 replay[replay_index].table = thread_index;
551 for(int remainder = 0; remainder <= D; remainder++)
552 {
553 // Compute the location of the lattice point explicitly (all but the last coordinate - it's redundant
554 // because they sum to zero)
555 for(int i = 0; i < D; i++) key.key[i] = greedy[i] + canonical[remainder * (D + 1) + rank[i]];
556 key.setHash();
557
558 // Retrieve pointer to the value at this vertex.
559 Value *val = hashTables[thread_index].lookup(key, true);
560
561 // Accumulate values with barycentric weight.
562 val->add(value, barycentric[remainder]);
563
564 // Record this interaction to use later when slicing
565 replay[replay_index].offset[remainder] = val - hashTables[thread_index].getValues();
566 replay[replay_index].weight[remainder] = barycentric[remainder];
567 }
568 }
569
570 /* Merge the multiple threads' hash tables into the totals. */
572 {
573 if(nThreads <= 1) return;
574
575 /* Because growing the hash table is expensive, we want to avoid having to do it multiple times.
576 * Only a small percentage of entries in the individual hash tables have the same key, so we
577 * won't waste much space if we simply grow the destination table enough to hold the sum of the
578 * entries in the individual tables
579 */
580 size_t total_entries = hashTables[0].size();
581 for(int i = 1; i < nThreads; i++) total_entries += hashTables[i].size();
582 int order = 0;
583 while(total_entries > hashTables[0].maxFill())
584 {
585 order++;
586 total_entries /= 2;
587 }
588 if(order > 0) hashTables[0].grow(order);
589 /* Merge the multiple hash tables into one, creating an offset remap table. */
590 int **offset_remap = new int *[nThreads];
591 for(int i = 1; i < nThreads; i++)
592 {
593 const Key *oldKeys = hashTables[i].getKeys();
594 const Value *oldVals = hashTables[i].getValues();
595 const int filled = hashTables[i].size();
596 offset_remap[i] = new int[filled];
597 for(int j = 0; j < filled; j++)
598 {
599 Value *val = hashTables[0].lookup(oldKeys[j], true);
600 val->add(oldVals[j]);
601 offset_remap[i][j] = val - hashTables[0].getValues();
602 }
603 }
604
605 /* Rewrite the offsets in the replay structure from the above generated table. */
606 for(int i = 0; i < nData; i++)
607 {
608 if(replay[i].table > 0)
609 {
610 for(int dim = 0; dim <= D; dim++)
611 replay[i].offset[dim] = offset_remap[replay[i].table][replay[i].offset[dim]];
612 }
613 }
614
615 for(int i = 1; i < nThreads; i++) delete[] offset_remap[i];
616 delete[] offset_remap;
617 }
618
619 /* Performs slicing out of position vectors. Note that the barycentric weights and the simplex
620 * containing each position vector were calculated and stored in the splatting step.
621 * We may reuse this to accelerate the algorithm. (See pg. 6 in paper.)
622 */
623 void slice(float *col, size_t replay_index) const
624 {
625 const Value *base = hashTables[0].getValues();
626 Value::clear(col);
627 ReplayEntry &r = replay[replay_index];
628 for(int i = 0; i <= D; i++)
629 {
630 base[r.offset[i]].addTo(col, r.weight[i]);
631 }
632 }
633
634 /* Performs a Gaussian blur along each projected axis in the hyperplane. */
635 void blur() const
636 {
637 // Prepare arrays
638 Value *newValue = new Value[hashTables[0].size()];
639 Value *oldValue = hashTables[0].getValues();
640 const Value *hashTableBase = oldValue;
641 const Key *keyBase = hashTables[0].getKeys();
642 const Value zero{ 0 };
643
644 // For each of d+1 axes,
645 for(int j = 0; j <= D; j++)
646 {
648 // For each vertex in the lattice,
649 for(int i = 0; i < hashTables[0].size(); i++) // blur point i in dimension j
650 {
651 const Key &key = keyBase[i]; // keys to current vertex
652 // construct keys to the neighbors along the given axis.
653 Key neighbor1(key, j, +1);
654 Key neighbor2(key, j, -1);
655
656 const Value *oldVal = oldValue + i;
657
658 const Value *vm1 = hashTables[0].lookup(neighbor1, false); // look up first neighbor
659 vm1 = vm1 ? vm1 - hashTableBase + oldValue : &zero;
660
661 const Value *vp1 = hashTables[0].lookup(neighbor2, false); // look up second neighbor
662 vp1 = vp1 ? vp1 - hashTableBase + oldValue : &zero;
663
664 // Mix values of the three vertices
665 newValue[i].mix(vm1, oldVal, vp1);
666 }
667 std::swap(newValue, oldValue);
668 // the freshest data is now in oldValue, and newValue is ready to be written over
669 }
670
671 // depending where we ended up, we may have to copy data
672 if(oldValue != hashTableBase)
673 {
674 std::copy(oldValue, oldValue + hashTables[0].size(), hashTables[0].getValues());
675 delete[] oldValue;
676 }
677 else
678 {
679 delete[] newValue;
680 }
681 }
682
683private:
684 int nData;
686 const float *scaleFactor;
687 const int *canonical;
688
689 // slicing is done by replaying splatting (ie storing the sparse matrix)
691 {
692 // since every dimension of a lattice point gets handled by the same thread,
693 // we only need to store the id of the hash table once, instead of for each dimension
694 int table;
695 int offset[D + 1];
696 float weight[D + 1];
698
700};
701
702#endif // DT_IOP_PERMUTOHEDRAL_H
703
704// clang-format off
705// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
706// vim: shiftwidth=2 expandtab tabstop=2 cindent
707// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
708// clang-format on
709
void init(dt_imageio_module_format_t *self)
Definition avif.c:157
int position()
HashTablePermutohedral(const HashTablePermutohedral &)=delete
const Key * getKeys() const
unsigned long capacity_bits
HashTablePermutohedral & operator=(const HashTablePermutohedral &)=delete
Value * lookup(const Key &k, bool create=true)
void grow(int order=1)
Value * getValues() const
int lookupOffset(const Key &key, bool create=true)
void slice(float *col, size_t replay_index) const
struct PermutohedralLattice::ReplayEntry * replay
HashTable::Value Value
const float * scaleFactor
PermutohedralLattice(size_t nData_, int nThreads_=1)
PermutohedralLattice(const PermutohedralLattice &)=delete
PermutohedralLattice & operator=(const PermutohedralLattice &)=delete
HashTablePermutohedral< D, VD > HashTable
void splat(float *position, float *value, size_t replay_index, int thread_index=0) const
const float v
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
float *const restrict const size_t k
char * key
size_t size
Definition mipmap_cache.c:3
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
Key(const Key &)=default
Key & operator=(const Key &)=default
Key(const Key &origin, int dim, int direction)
bool operator==(const Key &other) const
void setKey(int idx, short val)
void setValue(int idx, short val)
Value & operator+=(const Value &other)
void add(const Value &other)
Value(const Value &)=default
Value & operator=(const Value &)=default
static void clear(float *val)
void mix(const Value *left, const Value *center, const Value *right)
void addTo(float *dest, float weight) const
void addValue(int idx, short val)
void add(const float *other, float weight)