MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PixelOps.cpp
Go to the documentation of this file.
1// NOLINTBEGIN
2#include "PixelOps.hpp"
3
5
6#include <execution>
7#include <Eigen/Core>
8
9#ifdef MAYAFLUX_ARCH_X64
10#include <immintrin.h>
11#endif
12#ifdef MAYAFLUX_ARCH_ARM64
13#include <arm_neon.h>
14#endif
15
17
18void rgba_to_gray(std::span<const float> rgba, std::span<float> dst, uint32_t w, uint32_t h)
19{
20 const size_t n = static_cast<size_t>(w) * h;
21
22 std::for_each(std::execution::par_unseq,
23 std::views::iota(uint32_t { 0 }, h).begin(),
24 std::views::iota(uint32_t { 0 }, h).end(),
25 [&](uint32_t row) {
26 const float* src_row = rgba.data() + static_cast<size_t>(row) * w * 4;
27 float* dst_row = dst.data() + static_cast<size_t>(row) * w;
28
29#ifdef MAYAFLUX_ARCH_X64
30 uint32_t px = 0;
31 for (; px + 8 <= w; px += 8) {
32 __m128 a0 = _mm_loadu_ps(src_row + px * 4);
33 __m128 a1 = _mm_loadu_ps(src_row + px * 4 + 4);
34 __m128 a2 = _mm_loadu_ps(src_row + px * 4 + 8);
35 __m128 a3 = _mm_loadu_ps(src_row + px * 4 + 12);
36 _MM_TRANSPOSE4_PS(a0, a1, a2, a3);
37 __m128 lo = _mm_fmadd_ps(a0, _mm_set1_ps(0.299F),
38 _mm_fmadd_ps(a1, _mm_set1_ps(0.587F),
39 _mm_mul_ps(a2, _mm_set1_ps(0.114F))));
40
41 __m128 b0 = _mm_loadu_ps(src_row + px * 4 + 16);
42 __m128 b1 = _mm_loadu_ps(src_row + px * 4 + 20);
43 __m128 b2 = _mm_loadu_ps(src_row + px * 4 + 24);
44 __m128 b3 = _mm_loadu_ps(src_row + px * 4 + 28);
45 _MM_TRANSPOSE4_PS(b0, b1, b2, b3);
46 __m128 hi = _mm_fmadd_ps(b0, _mm_set1_ps(0.299F),
47 _mm_fmadd_ps(b1, _mm_set1_ps(0.587F),
48 _mm_mul_ps(b2, _mm_set1_ps(0.114F))));
49
50 _mm256_storeu_ps(dst_row + px, _mm256_set_m128(hi, lo));
51 }
52 for (; px < w; ++px) {
53 const size_t p = px * 4;
54 dst_row[px] = src_row[p] * 0.299F
55 + src_row[p + 1] * 0.587F
56 + src_row[p + 2] * 0.114F;
57 }
58
59#elif defined(MAYAFLUX_ARCH_ARM64)
60 const float32x4_t k_r = vdupq_n_f32(0.299F);
61 const float32x4_t k_g = vdupq_n_f32(0.587F);
62 const float32x4_t k_b = vdupq_n_f32(0.114F);
63 uint32_t px = 0;
64 for (; px + 4 <= w; px += 4) {
65 float32x4x4_t p = vld4q_f32(src_row + px * 4);
66 vst1q_f32(dst_row + px,
67 vmlaq_f32(vmlaq_f32(vmulq_f32(p.val[0], k_r), p.val[1], k_g), p.val[2], k_b));
68 }
69 for (; px < w; ++px) {
70 const size_t p = px * 4;
71 dst_row[px] = src_row[p] * 0.299F
72 + src_row[p + 1] * 0.587F
73 + src_row[p + 2] * 0.114F;
74 }
75
76#else
77 for (uint32_t px = 0; px < w; ++px) {
78 const size_t p = px * 4;
79 dst_row[px] = src_row[p] * 0.299F
80 + src_row[p + 1] * 0.587F
81 + src_row[p + 2] * 0.114F;
82 }
83#endif
84 });
85}
86
87std::vector<float> rgba_to_gray(std::span<const float> rgba, uint32_t w, uint32_t h)
88{
89 std::vector<float> out(static_cast<size_t>(w) * h);
90 rgba_to_gray(rgba, out, w, h);
91 return out;
92}
93
95 std::span<const float> rgba, std::span<float> dst,
96 uint32_t w, uint32_t h)
97{
98 const size_t n = static_cast<size_t>(w) * h;
99 std::for_each(std::execution::par_unseq,
100 std::views::iota(size_t { 0 }, n).begin(),
101 std::views::iota(size_t { 0 }, n).end(),
102 [&](size_t i) {
103 const glm::vec3 hsv = rgb_to_hsv({ rgba[i * 4], rgba[i * 4 + 1], rgba[i * 4 + 2] });
104
105 dst[i * 3] = hsv.x;
106 dst[i * 3 + 1] = hsv.y;
107 dst[i * 3 + 2] = hsv.z;
108 });
109}
110
111std::vector<float> rgba_to_hsv(std::span<const float> rgba, uint32_t w, uint32_t h)
112{
113 std::vector<float> out(static_cast<size_t>(w) * h * 3);
114 rgba_to_hsv(rgba, out, w, h);
115 return out;
116}
117
118void gray_to_rgba(std::span<const float> gray, std::span<float> dst, uint32_t w, uint32_t h)
119{
120 const size_t n = static_cast<size_t>(w) * h;
121
122 std::for_each(std::execution::par_unseq,
123 std::views::iota(uint32_t { 0 }, h).begin(),
124 std::views::iota(uint32_t { 0 }, h).end(),
125 [&](uint32_t row) {
126 const float* src_row = gray.data() + static_cast<size_t>(row) * w;
127 float* dst_row = dst.data() + static_cast<size_t>(row) * w * 4;
128
129#ifdef MAYAFLUX_ARCH_X64
130 // broadcasts each gray scalar into [g,g,g,1] using shuffle
131 // process 2 pixels per iteration to get 8 floats = 1 AVX store
132 const __m256 alpha_mask = _mm256_set_ps(1.0F, 0.0F, 0.0F, 0.0F,
133 1.0F, 0.0F, 0.0F, 0.0F);
134 uint32_t px = 0;
135 for (; px + 2 <= w; px += 2) {
136 const float g0 = src_row[px];
137 const float g1 = src_row[px + 1];
138 // [g0 g0 g0 1 g1 g1 g1 1]
139 __m256 v = _mm256_set_ps(1.0F, g1, g1, g1, 1.0F, g0, g0, g0);
140 _mm256_storeu_ps(dst_row + px * 4, v);
141 }
142 for (; px < w; ++px) {
143 const float v = src_row[px];
144 dst_row[px * 4] = dst_row[px * 4 + 1] = dst_row[px * 4 + 2] = v;
145 dst_row[px * 4 + 3] = 1.0F;
146 }
147
148#elif defined(MAYAFLUX_ARCH_ARM64)
149 const float32x4_t alpha = vdupq_n_f32(1.0F);
150 uint32_t px = 0;
151 for (; px + 4 <= w; px += 4) {
152 float32x4_t g = vld1q_f32(src_row + px);
153 float32x4_t lo = vzip1q_f32(g, g); // g0 g0 g1 g1
154 float32x4_t hi = vzip2q_f32(g, g); // g2 g2 g3 g3
155 float32x4x2_t p01 = vzipq_f32(lo, vdupq_lane_f32(vget_low_f32(alpha), 0));
156
157 float32x4x4_t rgba;
158 rgba.val[0] = g;
159 rgba.val[1] = g;
160 rgba.val[2] = g;
161 rgba.val[3] = alpha;
162 vst4q_f32(dst_row + px * 4, rgba);
163 (void)lo;
164 (void)hi;
165 (void)p01;
166 }
167 for (; px < w; ++px) {
168 const float v = src_row[px];
169 dst_row[px * 4] = v;
170 dst_row[px * 4 + 1] = v;
171 dst_row[px * 4 + 2] = v;
172 dst_row[px * 4 + 3] = 1.0F;
173 }
174
175#else
176 for (uint32_t px = 0; px < w; ++px) {
177 const float v = src_row[px];
178 dst_row[px * 4] = v;
179 dst_row[px * 4 + 1] = v;
180 dst_row[px * 4 + 2] = v;
181 dst_row[px * 4 + 3] = 1.0F;
182 }
183#endif
184 });
185}
186
187std::vector<float> gray_to_rgba(std::span<const float> gray, uint32_t w, uint32_t h)
188{
189 std::vector<float> out(static_cast<size_t>(w) * h * 4);
190 gray_to_rgba(gray, out, w, h);
191 return out;
192}
193
194void threshold(std::span<const float> gray, std::span<float> dst, float value)
195{
196 const size_t n = gray.size();
197 const float* src = gray.data();
198 float* out = dst.data();
199
200#ifdef MAYAFLUX_ARCH_X64
201 const __m256 thresh = _mm256_set1_ps(value);
202 const __m256 one = _mm256_set1_ps(1.0F);
203 size_t i = 0;
204 for (; i + 8 <= n; i += 8) {
205 __m256 v = _mm256_loadu_ps(src + i);
206 __m256 mask = _mm256_cmp_ps(v, thresh, _CMP_GE_OS);
207 _mm256_storeu_ps(out + i, _mm256_and_ps(mask, one));
208 }
209 for (; i < n; ++i)
210 out[i] = src[i] >= value ? 1.0F : 0.0F;
211
212#elif defined(MAYAFLUX_ARCH_ARM64)
213 const float32x4_t thresh = vdupq_n_f32(value);
214 const float32x4_t one = vdupq_n_f32(1.0F);
215 size_t i = 0;
216 for (; i + 4 <= n; i += 4) {
217 float32x4_t v = vld1q_f32(src + i);
218 uint32x4_t mask = vcgeq_f32(v, thresh);
219 vst1q_f32(out + i,
220 vreinterpretq_f32_u32(vandq_u32(mask, vreinterpretq_u32_f32(one))));
221 }
222 for (; i < n; ++i)
223 out[i] = src[i] >= value ? 1.0F : 0.0F;
224
225#else
226 std::transform(std::execution::par_unseq, gray.begin(), gray.end(), dst.begin(),
227 [value](float v) { return v >= value ? 1.0F : 0.0F; });
228#endif
229}
230
231std::vector<float> threshold(std::span<const float> gray, float value)
232{
233 std::vector<float> out(gray.size());
234 threshold(gray, out, value);
235 return out;
236}
237
239 std::span<const float> gray, std::span<float> dst,
240 uint32_t w, uint32_t h,
241 uint32_t block_size, float offset)
242{
243 const size_t n = static_cast<size_t>(w) * h;
244 const auto half = static_cast<int32_t>(block_size / 2);
245
246 std::for_each(std::execution::par_unseq,
247 std::views::iota(size_t { 0 }, n).begin(),
248 std::views::iota(size_t { 0 }, n).end(),
249 [&](size_t idx) {
250 const auto px = static_cast<int32_t>(idx % w);
251 const auto py = static_cast<int32_t>(idx / w);
252
253 float sum = 0.0F;
254 int32_t count = 0;
255
256 for (int32_t dy = -half; dy <= half; ++dy) {
257 const int32_t ny = std::clamp(py + dy, 0, static_cast<int32_t>(h) - 1);
258 for (int32_t dx = -half; dx <= half; ++dx) {
259 const int32_t nx = std::clamp(px + dx, 0, static_cast<int32_t>(w) - 1);
260 sum += gray[static_cast<size_t>(ny) * w + nx];
261 ++count;
262 }
263 }
264
265 const float mean = sum / static_cast<float>(count);
266 dst[idx] = gray[idx] >= (mean - offset) ? 1.0F : 0.0F;
267 });
268}
269
270std::vector<float> threshold_adaptive(
271 std::span<const float> gray, uint32_t w, uint32_t h,
272 uint32_t block_size, float offset)
273{
274 std::vector<float> out(static_cast<size_t>(w) * h);
275 threshold_adaptive(gray, out, w, h, block_size, offset);
276 return out;
277}
278
279void threshold_otsu(std::span<const float> gray, std::span<float> dst)
280{
281 constexpr int32_t bins = 256;
282 std::array<float, bins> hist {};
283
284 for (float v : gray)
285 hist[static_cast<int32_t>(std::clamp(v, 0.0F, 1.0F) * (bins - 1))]++;
286
287 const auto total = static_cast<float>(gray.size());
288 for (auto& h : hist)
289 h /= total;
290
291 float sum_all = 0.0F;
292 for (int32_t i = 0; i < bins; ++i)
293 sum_all += static_cast<float>(i) * hist[i];
294
295 float sum_bg = 0.0F;
296 float w_bg = 0.0F;
297 float best_var = 0.0F;
298 int32_t best_t = 0;
299
300 for (int32_t t = 0; t < bins; ++t) {
301 w_bg += hist[t];
302 if (w_bg == 0.0F)
303 continue;
304
305 const float w_fg = 1.0F - w_bg;
306 if (w_fg == 0.0F)
307 break;
308
309 sum_bg += static_cast<float>(t) * hist[t];
310
311 const float mean_bg = sum_bg / w_bg;
312 const float mean_fg = (sum_all - sum_bg) / w_fg;
313 const float diff = mean_fg - mean_bg;
314 const float var = w_bg * w_fg * diff * diff;
315
316 if (var > best_var) {
317 best_var = var;
318 best_t = t;
319 }
320 }
321
322 const float t_norm = static_cast<float>(best_t) / static_cast<float>(bins - 1);
323 threshold(gray, dst, t_norm);
324}
325
326std::vector<float> threshold_otsu(std::span<const float> gray)
327{
328 std::vector<float> out(gray.size());
329 threshold_otsu(gray, out);
330 return out;
331}
332
333void normalize_inplace(std::span<float> data)
334{
335 if (data.empty())
336 return;
337
338 const Eigen::Index en = static_cast<Eigen::Index>(data.size());
339 auto m = Eigen::Map<Eigen::ArrayXf>(data.data(), en);
340 const float mn = m.minCoeff();
341 const float mx = m.maxCoeff();
342 const float range = mx - mn;
343 if (range == 0.0F)
344 return;
345
346 const float inv = 1.0F / range;
347 float* out = data.data();
348 const size_t n = data.size();
349
350#ifdef MAYAFLUX_ARCH_X64
351 const __m256 v_mn = _mm256_set1_ps(mn);
352 const __m256 v_inv = _mm256_set1_ps(inv);
353 size_t i = 0;
354 for (; i + 8 <= n; i += 8) {
355 __m256 v = _mm256_loadu_ps(out + i);
356 _mm256_storeu_ps(out + i, _mm256_mul_ps(_mm256_sub_ps(v, v_mn), v_inv));
357 }
358 for (; i < n; ++i)
359 out[i] = (out[i] - mn) * inv;
360
361#elif defined(MAYAFLUX_ARCH_ARM64)
362 const float32x4_t v_mn = vdupq_n_f32(mn);
363 const float32x4_t v_inv = vdupq_n_f32(inv);
364 size_t i = 0;
365 for (; i + 4 <= n; i += 4) {
366 float32x4_t v = vld1q_f32(out + i);
367 vst1q_f32(out + i, vmulq_f32(vsubq_f32(v, v_mn), v_inv));
368 }
369 for (; i < n; ++i)
370 out[i] = (out[i] - mn) * inv;
371
372#else
373 std::transform(std::execution::par_unseq, data.begin(), data.end(), data.begin(),
374 [mn, inv](float v) { return (v - mn) * inv; });
375#endif
376}
377
378void normalize_range_inplace(std::span<float> data, float lo, float hi)
379{
380 if (lo == hi)
381 return;
382
383 const float inv = 1.0F / (hi - lo);
384 float* out = data.data();
385 const size_t n = data.size();
386
387#ifdef MAYAFLUX_ARCH_X64
388 const __m256 v_lo = _mm256_set1_ps(lo);
389 const __m256 v_inv = _mm256_set1_ps(inv);
390 const __m256 v_zero = _mm256_setzero_ps();
391 const __m256 v_one = _mm256_set1_ps(1.0F);
392 size_t i = 0;
393 for (; i + 8 <= n; i += 8) {
394 __m256 v = _mm256_loadu_ps(out + i);
395 v = _mm256_mul_ps(_mm256_sub_ps(v, v_lo), v_inv);
396 v = _mm256_min_ps(_mm256_max_ps(v, v_zero), v_one);
397 _mm256_storeu_ps(out + i, v);
398 }
399 for (; i < n; ++i)
400 out[i] = std::clamp((out[i] - lo) * inv, 0.0F, 1.0F);
401
402#elif defined(MAYAFLUX_ARCH_ARM64)
403 const float32x4_t v_lo = vdupq_n_f32(lo);
404 const float32x4_t v_inv = vdupq_n_f32(inv);
405 const float32x4_t v_zero = vdupq_n_f32(0.0F);
406 const float32x4_t v_one = vdupq_n_f32(1.0F);
407 size_t i = 0;
408 for (; i + 4 <= n; i += 4) {
409 float32x4_t v = vld1q_f32(out + i);
410 v = vmulq_f32(vsubq_f32(v, v_lo), v_inv);
411 v = vminq_f32(vmaxq_f32(v, v_zero), v_one);
412 vst1q_f32(out + i, v);
413 }
414 for (; i < n; ++i)
415 out[i] = std::clamp((out[i] - lo) * inv, 0.0F, 1.0F);
416
417#else
418 std::transform(std::execution::par_unseq, data.begin(), data.end(), data.begin(),
419 [lo, inv](float v) { return std::clamp((v - lo) * inv, 0.0F, 1.0F); });
420#endif
421}
422
424 std::span<const float> src,
425 std::span<float> dst,
426 uint32_t w, uint32_t h,
427 uint32_t& new_w, uint32_t& new_h)
428{
429 new_w = w / 2;
430 new_h = h / 2;
431
432 std::for_each(std::execution::par_unseq,
433 std::views::iota(uint32_t { 0 }, new_h).begin(),
434 std::views::iota(uint32_t { 0 }, new_h).end(),
435 [&](uint32_t oy) {
436 const float* row0 = src.data() + static_cast<size_t>(oy * 2) * w;
437 const float* row1 = src.data() + static_cast<size_t>(oy * 2 + 1) * w;
438 float* drow = dst.data() + static_cast<size_t>(oy) * new_w;
439
440#ifdef MAYAFLUX_ARCH_X64
441 const __m256 quarter = _mm256_set1_ps(0.25F);
442 uint32_t ox = 0;
443 for (; ox + 8 <= new_w; ox += 8) {
444
445 __m256 r0a = _mm256_loadu_ps(row0 + ox * 2); // px 0..7 of row0
446 __m256 r0b = _mm256_loadu_ps(row0 + ox * 2 + 8); // px 8..15 of row0
447 __m256 r1a = _mm256_loadu_ps(row1 + ox * 2);
448 __m256 r1b = _mm256_loadu_ps(row1 + ox * 2 + 8);
449
450 // horizontal add pairs: hadd gains (a0+a1, a2+a3, b0+b1, b2+b3 | ...)
451 __m256 sum0 = _mm256_hadd_ps(r0a, r0b); // (r00+r01, r02+r03, r08+r09, r010+r011 | ...)
452 __m256 sum1 = _mm256_hadd_ps(r1a, r1b);
453 __m256 sum = _mm256_add_ps(sum0, sum1);
454
455 __m256d perm_d = _mm256_permute4x64_pd(_mm256_castps_pd(sum), _MM_SHUFFLE(3, 1, 2, 0));
456 __m256 result = _mm256_mul_ps(_mm256_castpd_ps(perm_d), quarter);
457 _mm256_storeu_ps(drow + ox, result);
458 }
459 for (; ox < new_w; ++ox) {
460 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
461 + row1[ox * 2] + row1[ox * 2 + 1])
462 * 0.25F;
463 }
464
465#elif defined(MAYAFLUX_ARCH_ARM64)
466 const float32x4_t quarter = vdupq_n_f32(0.25F);
467 uint32_t ox = 0;
468 for (; ox + 4 <= new_w; ox += 4) {
469 // vld2q_f32 loads 8 floats deinterleaved into 2 registers:
470 // val[0] = even elements, val[1] = odd elements
471 float32x4x2_t r0 = vld2q_f32(row0 + ox * 2);
472 float32x4x2_t r1 = vld2q_f32(row1 + ox * 2);
473 // r0.val[0] = [row0[0], row0[2], row0[4], row0[6]]
474 // r0.val[1] = [row0[1], row0[3], row0[5], row0[7]]
475 float32x4_t sum = vaddq_f32(
476 vaddq_f32(r0.val[0], r0.val[1]),
477 vaddq_f32(r1.val[0], r1.val[1]));
478 vst1q_f32(drow + ox, vmulq_f32(sum, quarter));
479 }
480 for (; ox < new_w; ++ox) {
481 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
482 + row1[ox * 2] + row1[ox * 2 + 1])
483 * 0.25F;
484 }
485
486#else
487 for (uint32_t ox = 0; ox < new_w; ++ox) {
488 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
489 + row1[ox * 2] + row1[ox * 2 + 1])
490 * 0.25F;
491 }
492#endif
493 });
494}
495
496std::vector<float> downsample_2x(
497 std::span<const float> src,
498 uint32_t w, uint32_t h,
499 uint32_t& new_w, uint32_t& new_h)
500{
501 new_w = w / 2;
502 new_h = h / 2;
503 std::vector<float> out(static_cast<size_t>(new_w) * new_h);
504 downsample_2x(src, out, w, h, new_w, new_h);
505 return out;
506}
507
509 std::span<const float> src,
510 std::span<float> dst,
511 uint32_t w, uint32_t h,
512 uint32_t channels,
513 uint32_t& new_w, uint32_t& new_h)
514{
515 if (channels == 1) {
516 downsample_2x(src, dst, w, h, new_w, new_h);
517 return;
518 }
519
520 new_w = w / 2;
521 new_h = h / 2;
522 const uint32_t ch = channels;
523 const uint32_t row_stride = w * ch;
524 const uint32_t out_stride = new_w * ch;
525
526 std::for_each(std::execution::par_unseq,
527 std::views::iota(uint32_t { 0 }, new_h).begin(),
528 std::views::iota(uint32_t { 0 }, new_h).end(),
529 [&](uint32_t oy) {
530 const float* row0 = src.data() + static_cast<size_t>(oy * 2) * row_stride;
531 const float* row1 = src.data() + static_cast<size_t>(oy * 2 + 1) * row_stride;
532 float* drow = dst.data() + static_cast<size_t>(oy) * out_stride;
533
534 uint32_t ox = 0;
535
536#ifdef MAYAFLUX_ARCH_X64
537 if (ch == 4) {
538 const __m256 quarter = _mm256_set1_ps(0.25F);
539 for (; ox + 1 <= new_w; ++ox) {
540 const float* p00 = row0 + ox * 8;
541 const float* p01 = row0 + ox * 8 + 4;
542 const float* p10 = row1 + ox * 8;
543 const float* p11 = row1 + ox * 8 + 4;
544
545 __m128 v00 = _mm_loadu_ps(p00);
546 __m128 v01 = _mm_loadu_ps(p01);
547 __m128 v10 = _mm_loadu_ps(p10);
548 __m128 v11 = _mm_loadu_ps(p11);
549
550 __m128 sum = _mm_add_ps(_mm_add_ps(v00, v01), _mm_add_ps(v10, v11));
551 __m128 res = _mm_mul_ps(sum, _mm_set1_ps(0.25F));
552 _mm_storeu_ps(drow + ox * 4, res);
553 }
554 return;
555 }
556#elif defined(MAYAFLUX_ARCH_ARM64)
557 if (ch == 4) {
558 for (; ox + 1 <= new_w; ++ox) {
559 const float* p00 = row0 + ox * 8;
560 const float* p01 = row0 + ox * 8 + 4;
561 const float* p10 = row1 + ox * 8;
562 const float* p11 = row1 + ox * 8 + 4;
563
564 float32x4_t v00 = vld1q_f32(p00);
565 float32x4_t v01 = vld1q_f32(p01);
566 float32x4_t v10 = vld1q_f32(p10);
567 float32x4_t v11 = vld1q_f32(p11);
568
569 float32x4_t sum = vaddq_f32(vaddq_f32(v00, v01), vaddq_f32(v10, v11));
570 vst1q_f32(drow + ox * 4, vmulq_n_f32(sum, 0.25F));
571 }
572 return;
573 }
574#endif
575
576 for (; ox < new_w; ++ox) {
577 const float* p00 = row0 + ox * 2 * ch;
578 const float* p01 = row0 + (ox * 2 + 1) * ch;
579 const float* p10 = row1 + ox * 2 * ch;
580 const float* p11 = row1 + (ox * 2 + 1) * ch;
581 float* out = drow + ox * ch;
582 for (uint32_t c = 0; c < ch; ++c)
583 out[c] = (p00[c] + p01[c] + p10[c] + p11[c]) * 0.25F;
584 }
585 });
586}
587
588} // namespace MayaFlux::Kinesis::Vision
589// NOLINTEND
double alpha
uint64_t offset
Definition Composite.cpp:10
uint32_t h
Definition InkPress.cpp:29
Pointwise pixel operations for MayaFlux::Kinesis::Vision.
uint32_t * dst
const uint32_t * src
uint32_t total
std::vector< float > * out
glm::vec3 value
size_t count
uint32_t channels
uint32_t py
uint32_t px
float threshold
void normalize_range_inplace(std::span< float > data, float lo, float hi)
Clamp and remap values from [lo, hi] to [0, 1] in-place.
Definition PixelOps.cpp:378
void rgba_to_gray(std::span< const float > rgba, std::span< float > dst, uint32_t w, uint32_t h)
Convert RGBA to luminance gray using BT.601 coefficients.
Definition PixelOps.cpp:18
void normalize_inplace(std::span< float > data)
Stretch values to fill [0, 1] using the span's observed min and max.
Definition PixelOps.cpp:333
void gray_to_rgba(std::span< const float > gray, std::span< float > dst, uint32_t w, uint32_t h)
Global threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:118
void rgba_to_hsv(std::span< const float > rgba, std::span< float > dst, uint32_t w, uint32_t h)
Convert RGBA to HSV writing into caller-supplied buffer.
Definition PixelOps.cpp:94
void threshold_otsu(std::span< const float > gray, std::span< float > dst)
Otsu threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:279
void threshold_adaptive(std::span< const float > gray, std::span< float > dst, uint32_t w, uint32_t h, uint32_t block_size, float offset)
Adaptive threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:238
void downsample_2x(std::span< const float > src, std::span< float > dst, uint32_t w, uint32_t h, uint32_t &new_w, uint32_t &new_h)
2x box-filter downsample writing into a caller-supplied buffer.
Definition PixelOps.cpp:423
glm::vec3 rgb_to_hsv(glm::vec3 rgb) noexcept
Convert red, green and blue to hue, saturation and value.