MayaFlux 0.5.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#include <Eigen/Core>
6
7#ifdef MAYAFLUX_ARCH_X64
8#include <immintrin.h>
9#endif
10#ifdef MAYAFLUX_ARCH_ARM64
11#include <arm_neon.h>
12#endif
13
14namespace P = MayaFlux::Parallel;
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 P::for_each(P::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 P::for_each(P::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 float r = rgba[i * 4];
104 const float g = rgba[i * 4 + 1];
105 const float b = rgba[i * 4 + 2];
106
107 const float cmax = std::max({ r, g, b });
108 const float cmin = std::min({ r, g, b });
109 const float delta = cmax - cmin;
110
111 float h_val = 0.0F;
112 if (delta > 0.0F) {
113 if (cmax == r) {
114 h_val = std::fmod((g - b) / delta, 6.0F);
115 } else if (cmax == g) {
116 h_val = (b - r) / delta + 2.0F;
117 } else {
118 h_val = (r - g) / delta + 4.0F;
119 }
120 h_val /= 6.0F;
121 if (h_val < 0.0F)
122 h_val += 1.0F;
123 }
124
125 dst[i * 3] = h_val;
126 dst[i * 3 + 1] = cmax > 0.0F ? delta / cmax : 0.0F;
127 dst[i * 3 + 2] = cmax;
128 });
129}
130
131std::vector<float> rgba_to_hsv(std::span<const float> rgba, uint32_t w, uint32_t h)
132{
133 std::vector<float> out(static_cast<size_t>(w) * h * 3);
134 rgba_to_hsv(rgba, out, w, h);
135 return out;
136}
137
138void gray_to_rgba(std::span<const float> gray, std::span<float> dst, uint32_t w, uint32_t h)
139{
140 const size_t n = static_cast<size_t>(w) * h;
141
142 P::for_each(P::par_unseq,
143 std::views::iota(uint32_t { 0 }, h).begin(),
144 std::views::iota(uint32_t { 0 }, h).end(),
145 [&](uint32_t row) {
146 const float* src_row = gray.data() + static_cast<size_t>(row) * w;
147 float* dst_row = dst.data() + static_cast<size_t>(row) * w * 4;
148
149#ifdef MAYAFLUX_ARCH_X64
150 // broadcasts each gray scalar into [g,g,g,1] using shuffle
151 // process 2 pixels per iteration to get 8 floats = 1 AVX store
152 const __m256 alpha_mask = _mm256_set_ps(1.0F, 0.0F, 0.0F, 0.0F,
153 1.0F, 0.0F, 0.0F, 0.0F);
154 uint32_t px = 0;
155 for (; px + 2 <= w; px += 2) {
156 const float g0 = src_row[px];
157 const float g1 = src_row[px + 1];
158 // [g0 g0 g0 1 g1 g1 g1 1]
159 __m256 v = _mm256_set_ps(1.0F, g1, g1, g1, 1.0F, g0, g0, g0);
160 _mm256_storeu_ps(dst_row + px * 4, v);
161 }
162 for (; px < w; ++px) {
163 const float v = src_row[px];
164 dst_row[px * 4] = dst_row[px * 4 + 1] = dst_row[px * 4 + 2] = v;
165 dst_row[px * 4 + 3] = 1.0F;
166 }
167
168#elif defined(MAYAFLUX_ARCH_ARM64)
169 const float32x4_t alpha = vdupq_n_f32(1.0F);
170 uint32_t px = 0;
171 for (; px + 4 <= w; px += 4) {
172 float32x4_t g = vld1q_f32(src_row + px);
173 float32x4_t lo = vzip1q_f32(g, g); // g0 g0 g1 g1
174 float32x4_t hi = vzip2q_f32(g, g); // g2 g2 g3 g3
175 float32x4x2_t p01 = vzipq_f32(lo, vdupq_lane_f32(vget_low_f32(alpha), 0));
176
177 float32x4x4_t rgba;
178 rgba.val[0] = g;
179 rgba.val[1] = g;
180 rgba.val[2] = g;
181 rgba.val[3] = alpha;
182 vst4q_f32(dst_row + px * 4, rgba);
183 (void)lo;
184 (void)hi;
185 (void)p01;
186 }
187 for (; px < w; ++px) {
188 const float v = src_row[px];
189 dst_row[px * 4] = v;
190 dst_row[px * 4 + 1] = v;
191 dst_row[px * 4 + 2] = v;
192 dst_row[px * 4 + 3] = 1.0F;
193 }
194
195#else
196 for (uint32_t px = 0; px < w; ++px) {
197 const float v = src_row[px];
198 dst_row[px * 4] = v;
199 dst_row[px * 4 + 1] = v;
200 dst_row[px * 4 + 2] = v;
201 dst_row[px * 4 + 3] = 1.0F;
202 }
203#endif
204 });
205}
206
207std::vector<float> gray_to_rgba(std::span<const float> gray, uint32_t w, uint32_t h)
208{
209 std::vector<float> out(static_cast<size_t>(w) * h * 4);
210 gray_to_rgba(gray, out, w, h);
211 return out;
212}
213
214void threshold(std::span<const float> gray, std::span<float> dst, float value)
215{
216 const size_t n = gray.size();
217 const float* src = gray.data();
218 float* out = dst.data();
219
220#ifdef MAYAFLUX_ARCH_X64
221 const __m256 thresh = _mm256_set1_ps(value);
222 const __m256 one = _mm256_set1_ps(1.0F);
223 size_t i = 0;
224 for (; i + 8 <= n; i += 8) {
225 __m256 v = _mm256_loadu_ps(src + i);
226 __m256 mask = _mm256_cmp_ps(v, thresh, _CMP_GE_OS);
227 _mm256_storeu_ps(out + i, _mm256_and_ps(mask, one));
228 }
229 for (; i < n; ++i)
230 out[i] = src[i] >= value ? 1.0F : 0.0F;
231
232#elif defined(MAYAFLUX_ARCH_ARM64)
233 const float32x4_t thresh = vdupq_n_f32(value);
234 const float32x4_t one = vdupq_n_f32(1.0F);
235 size_t i = 0;
236 for (; i + 4 <= n; i += 4) {
237 float32x4_t v = vld1q_f32(src + i);
238 uint32x4_t mask = vcgeq_f32(v, thresh);
239 vst1q_f32(out + i,
240 vreinterpretq_f32_u32(vandq_u32(mask, vreinterpretq_u32_f32(one))));
241 }
242 for (; i < n; ++i)
243 out[i] = src[i] >= value ? 1.0F : 0.0F;
244
245#else
246 P::transform(P::par_unseq, gray.begin(), gray.end(), dst.begin(),
247 [value](float v) { return v >= value ? 1.0F : 0.0F; });
248#endif
249}
250
251std::vector<float> threshold(std::span<const float> gray, float value)
252{
253 std::vector<float> out(gray.size());
254 threshold(gray, out, value);
255 return out;
256}
257
259 std::span<const float> gray, std::span<float> dst,
260 uint32_t w, uint32_t h,
261 uint32_t block_size, float offset)
262{
263 const size_t n = static_cast<size_t>(w) * h;
264 const auto half = static_cast<int32_t>(block_size / 2);
265
266 P::for_each(P::par_unseq,
267 std::views::iota(size_t { 0 }, n).begin(),
268 std::views::iota(size_t { 0 }, n).end(),
269 [&](size_t idx) {
270 const auto px = static_cast<int32_t>(idx % w);
271 const auto py = static_cast<int32_t>(idx / w);
272
273 float sum = 0.0F;
274 int32_t count = 0;
275
276 for (int32_t dy = -half; dy <= half; ++dy) {
277 const int32_t ny = std::clamp(py + dy, 0, static_cast<int32_t>(h) - 1);
278 for (int32_t dx = -half; dx <= half; ++dx) {
279 const int32_t nx = std::clamp(px + dx, 0, static_cast<int32_t>(w) - 1);
280 sum += gray[static_cast<size_t>(ny) * w + nx];
281 ++count;
282 }
283 }
284
285 const float mean = sum / static_cast<float>(count);
286 dst[idx] = gray[idx] >= (mean - offset) ? 1.0F : 0.0F;
287 });
288}
289
290std::vector<float> threshold_adaptive(
291 std::span<const float> gray, uint32_t w, uint32_t h,
292 uint32_t block_size, float offset)
293{
294 std::vector<float> out(static_cast<size_t>(w) * h);
295 threshold_adaptive(gray, out, w, h, block_size, offset);
296 return out;
297}
298
299void threshold_otsu(std::span<const float> gray, std::span<float> dst)
300{
301 constexpr int32_t bins = 256;
302 std::array<float, bins> hist {};
303
304 for (float v : gray)
305 hist[static_cast<int32_t>(std::clamp(v, 0.0F, 1.0F) * (bins - 1))]++;
306
307 const auto total = static_cast<float>(gray.size());
308 for (auto& h : hist)
309 h /= total;
310
311 float sum_all = 0.0F;
312 for (int32_t i = 0; i < bins; ++i)
313 sum_all += static_cast<float>(i) * hist[i];
314
315 float sum_bg = 0.0F;
316 float w_bg = 0.0F;
317 float best_var = 0.0F;
318 int32_t best_t = 0;
319
320 for (int32_t t = 0; t < bins; ++t) {
321 w_bg += hist[t];
322 if (w_bg == 0.0F)
323 continue;
324
325 const float w_fg = 1.0F - w_bg;
326 if (w_fg == 0.0F)
327 break;
328
329 sum_bg += static_cast<float>(t) * hist[t];
330
331 const float mean_bg = sum_bg / w_bg;
332 const float mean_fg = (sum_all - sum_bg) / w_fg;
333 const float diff = mean_fg - mean_bg;
334 const float var = w_bg * w_fg * diff * diff;
335
336 if (var > best_var) {
337 best_var = var;
338 best_t = t;
339 }
340 }
341
342 const float t_norm = static_cast<float>(best_t) / static_cast<float>(bins - 1);
343 threshold(gray, dst, t_norm);
344}
345
346std::vector<float> threshold_otsu(std::span<const float> gray)
347{
348 std::vector<float> out(gray.size());
349 threshold_otsu(gray, out);
350 return out;
351}
352
353void normalize_inplace(std::span<float> data)
354{
355 if (data.empty())
356 return;
357
358 const Eigen::Index en = static_cast<Eigen::Index>(data.size());
359 auto m = Eigen::Map<Eigen::ArrayXf>(data.data(), en);
360 const float mn = m.minCoeff();
361 const float mx = m.maxCoeff();
362 const float range = mx - mn;
363 if (range == 0.0F)
364 return;
365
366 const float inv = 1.0F / range;
367 float* out = data.data();
368 const size_t n = data.size();
369
370#ifdef MAYAFLUX_ARCH_X64
371 const __m256 v_mn = _mm256_set1_ps(mn);
372 const __m256 v_inv = _mm256_set1_ps(inv);
373 size_t i = 0;
374 for (; i + 8 <= n; i += 8) {
375 __m256 v = _mm256_loadu_ps(out + i);
376 _mm256_storeu_ps(out + i, _mm256_mul_ps(_mm256_sub_ps(v, v_mn), v_inv));
377 }
378 for (; i < n; ++i)
379 out[i] = (out[i] - mn) * inv;
380
381#elif defined(MAYAFLUX_ARCH_ARM64)
382 const float32x4_t v_mn = vdupq_n_f32(mn);
383 const float32x4_t v_inv = vdupq_n_f32(inv);
384 size_t i = 0;
385 for (; i + 4 <= n; i += 4) {
386 float32x4_t v = vld1q_f32(out + i);
387 vst1q_f32(out + i, vmulq_f32(vsubq_f32(v, v_mn), v_inv));
388 }
389 for (; i < n; ++i)
390 out[i] = (out[i] - mn) * inv;
391
392#else
393 P::transform(P::par_unseq, data.begin(), data.end(), data.begin(),
394 [mn, inv](float v) { return (v - mn) * inv; });
395#endif
396}
397
398void normalize_range_inplace(std::span<float> data, float lo, float hi)
399{
400 if (lo == hi)
401 return;
402
403 const float inv = 1.0F / (hi - lo);
404 float* out = data.data();
405 const size_t n = data.size();
406
407#ifdef MAYAFLUX_ARCH_X64
408 const __m256 v_lo = _mm256_set1_ps(lo);
409 const __m256 v_inv = _mm256_set1_ps(inv);
410 const __m256 v_zero = _mm256_setzero_ps();
411 const __m256 v_one = _mm256_set1_ps(1.0F);
412 size_t i = 0;
413 for (; i + 8 <= n; i += 8) {
414 __m256 v = _mm256_loadu_ps(out + i);
415 v = _mm256_mul_ps(_mm256_sub_ps(v, v_lo), v_inv);
416 v = _mm256_min_ps(_mm256_max_ps(v, v_zero), v_one);
417 _mm256_storeu_ps(out + i, v);
418 }
419 for (; i < n; ++i)
420 out[i] = std::clamp((out[i] - lo) * inv, 0.0F, 1.0F);
421
422#elif defined(MAYAFLUX_ARCH_ARM64)
423 const float32x4_t v_lo = vdupq_n_f32(lo);
424 const float32x4_t v_inv = vdupq_n_f32(inv);
425 const float32x4_t v_zero = vdupq_n_f32(0.0F);
426 const float32x4_t v_one = vdupq_n_f32(1.0F);
427 size_t i = 0;
428 for (; i + 4 <= n; i += 4) {
429 float32x4_t v = vld1q_f32(out + i);
430 v = vmulq_f32(vsubq_f32(v, v_lo), v_inv);
431 v = vminq_f32(vmaxq_f32(v, v_zero), v_one);
432 vst1q_f32(out + i, v);
433 }
434 for (; i < n; ++i)
435 out[i] = std::clamp((out[i] - lo) * inv, 0.0F, 1.0F);
436
437#else
438 P::transform(P::par_unseq, data.begin(), data.end(), data.begin(),
439 [lo, inv](float v) { return std::clamp((v - lo) * inv, 0.0F, 1.0F); });
440#endif
441}
442
444 std::span<const float> src,
445 std::span<float> dst,
446 uint32_t w, uint32_t h,
447 uint32_t& new_w, uint32_t& new_h)
448{
449 new_w = w / 2;
450 new_h = h / 2;
451
452 P::for_each(P::par_unseq,
453 std::views::iota(uint32_t { 0 }, new_h).begin(),
454 std::views::iota(uint32_t { 0 }, new_h).end(),
455 [&](uint32_t oy) {
456 const float* row0 = src.data() + static_cast<size_t>(oy * 2) * w;
457 const float* row1 = src.data() + static_cast<size_t>(oy * 2 + 1) * w;
458 float* drow = dst.data() + static_cast<size_t>(oy) * new_w;
459
460#ifdef MAYAFLUX_ARCH_X64
461 const __m256 quarter = _mm256_set1_ps(0.25F);
462 uint32_t ox = 0;
463 for (; ox + 8 <= new_w; ox += 8) {
464
465 __m256 r0a = _mm256_loadu_ps(row0 + ox * 2); // px 0..7 of row0
466 __m256 r0b = _mm256_loadu_ps(row0 + ox * 2 + 8); // px 8..15 of row0
467 __m256 r1a = _mm256_loadu_ps(row1 + ox * 2);
468 __m256 r1b = _mm256_loadu_ps(row1 + ox * 2 + 8);
469
470 // horizontal add pairs: hadd gains (a0+a1, a2+a3, b0+b1, b2+b3 | ...)
471 __m256 sum0 = _mm256_hadd_ps(r0a, r0b); // (r00+r01, r02+r03, r08+r09, r010+r011 | ...)
472 __m256 sum1 = _mm256_hadd_ps(r1a, r1b);
473 __m256 sum = _mm256_add_ps(sum0, sum1);
474
475 __m256d perm_d = _mm256_permute4x64_pd(_mm256_castps_pd(sum), _MM_SHUFFLE(3, 1, 2, 0));
476 __m256 result = _mm256_mul_ps(_mm256_castpd_ps(perm_d), quarter);
477 _mm256_storeu_ps(drow + ox, result);
478 }
479 for (; ox < new_w; ++ox) {
480 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
481 + row1[ox * 2] + row1[ox * 2 + 1])
482 * 0.25F;
483 }
484
485#elif defined(MAYAFLUX_ARCH_ARM64)
486 const float32x4_t quarter = vdupq_n_f32(0.25F);
487 uint32_t ox = 0;
488 for (; ox + 4 <= new_w; ox += 4) {
489 // vld2q_f32 loads 8 floats deinterleaved into 2 registers:
490 // val[0] = even elements, val[1] = odd elements
491 float32x4x2_t r0 = vld2q_f32(row0 + ox * 2);
492 float32x4x2_t r1 = vld2q_f32(row1 + ox * 2);
493 // r0.val[0] = [row0[0], row0[2], row0[4], row0[6]]
494 // r0.val[1] = [row0[1], row0[3], row0[5], row0[7]]
495 float32x4_t sum = vaddq_f32(
496 vaddq_f32(r0.val[0], r0.val[1]),
497 vaddq_f32(r1.val[0], r1.val[1]));
498 vst1q_f32(drow + ox, vmulq_f32(sum, quarter));
499 }
500 for (; ox < new_w; ++ox) {
501 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
502 + row1[ox * 2] + row1[ox * 2 + 1])
503 * 0.25F;
504 }
505
506#else
507 for (uint32_t ox = 0; ox < new_w; ++ox) {
508 drow[ox] = (row0[ox * 2] + row0[ox * 2 + 1]
509 + row1[ox * 2] + row1[ox * 2 + 1])
510 * 0.25F;
511 }
512#endif
513 });
514}
515
516std::vector<float> downsample_2x(
517 std::span<const float> src,
518 uint32_t w, uint32_t h,
519 uint32_t& new_w, uint32_t& new_h)
520{
521 new_w = w / 2;
522 new_h = h / 2;
523 std::vector<float> out(static_cast<size_t>(new_w) * new_h);
524 downsample_2x(src, out, w, h, new_w, new_h);
525 return out;
526}
527
529 std::span<const float> src,
530 std::span<float> dst,
531 uint32_t w, uint32_t h,
532 uint32_t channels,
533 uint32_t& new_w, uint32_t& new_h)
534{
535 if (channels == 1) {
536 downsample_2x(src, dst, w, h, new_w, new_h);
537 return;
538 }
539
540 new_w = w / 2;
541 new_h = h / 2;
542 const uint32_t ch = channels;
543 const uint32_t row_stride = w * ch;
544 const uint32_t out_stride = new_w * ch;
545
546 P::for_each(P::par_unseq,
547 std::views::iota(uint32_t { 0 }, new_h).begin(),
548 std::views::iota(uint32_t { 0 }, new_h).end(),
549 [&](uint32_t oy) {
550 const float* row0 = src.data() + static_cast<size_t>(oy * 2) * row_stride;
551 const float* row1 = src.data() + static_cast<size_t>(oy * 2 + 1) * row_stride;
552 float* drow = dst.data() + static_cast<size_t>(oy) * out_stride;
553
554 uint32_t ox = 0;
555
556#ifdef MAYAFLUX_ARCH_X64
557 if (ch == 4) {
558 const __m256 quarter = _mm256_set1_ps(0.25F);
559 for (; ox + 1 <= new_w; ++ox) {
560 const float* p00 = row0 + ox * 8;
561 const float* p01 = row0 + ox * 8 + 4;
562 const float* p10 = row1 + ox * 8;
563 const float* p11 = row1 + ox * 8 + 4;
564
565 __m128 v00 = _mm_loadu_ps(p00);
566 __m128 v01 = _mm_loadu_ps(p01);
567 __m128 v10 = _mm_loadu_ps(p10);
568 __m128 v11 = _mm_loadu_ps(p11);
569
570 __m128 sum = _mm_add_ps(_mm_add_ps(v00, v01), _mm_add_ps(v10, v11));
571 __m128 res = _mm_mul_ps(sum, _mm_set1_ps(0.25F));
572 _mm_storeu_ps(drow + ox * 4, res);
573 }
574 return;
575 }
576#elif defined(MAYAFLUX_ARCH_ARM64)
577 if (ch == 4) {
578 for (; ox + 1 <= new_w; ++ox) {
579 const float* p00 = row0 + ox * 8;
580 const float* p01 = row0 + ox * 8 + 4;
581 const float* p10 = row1 + ox * 8;
582 const float* p11 = row1 + ox * 8 + 4;
583
584 float32x4_t v00 = vld1q_f32(p00);
585 float32x4_t v01 = vld1q_f32(p01);
586 float32x4_t v10 = vld1q_f32(p10);
587 float32x4_t v11 = vld1q_f32(p11);
588
589 float32x4_t sum = vaddq_f32(vaddq_f32(v00, v01), vaddq_f32(v10, v11));
590 vst1q_f32(drow + ox * 4, vmulq_n_f32(sum, 0.25F));
591 }
592 return;
593 }
594#endif
595
596 for (; ox < new_w; ++ox) {
597 const float* p00 = row0 + ox * 2 * ch;
598 const float* p01 = row0 + (ox * 2 + 1) * ch;
599 const float* p10 = row1 + ox * 2 * ch;
600 const float* p11 = row1 + (ox * 2 + 1) * ch;
601 float* out = drow + ox * ch;
602 for (uint32_t c = 0; c < ch; ++c)
603 out[c] = (p00[c] + p01[c] + p10[c] + p11[c]) * 0.25F;
604 }
605 });
606}
607
608} // namespace MayaFlux::Kinesis::Vision
609// NOLINTEND
uint32_t h
Definition InkPress.cpp:28
Pointwise pixel operations for MayaFlux::Kinesis::Vision.
size_t b
size_t count
float value
float lo
float threshold
float offset
uint32_t block_size
float hi
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:398
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:353
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:138
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:299
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:258
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:443
double mean(const std::vector< double > &data)
Calculate mean of single-channel data.
Definition Yantra.cpp:55