PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
simd_photometric.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
19#ifndef PACS_ENCODING_SIMD_PHOTOMETRIC_HPP
20#define PACS_ENCODING_SIMD_PHOTOMETRIC_HPP
21
22#include "simd_config.h"
23#include "simd_types.h"
24
25#include <cstddef>
26#include <cstdint>
27#include <cstring>
28
30
31// Forward declarations
32void invert_monochrome_8bit(const uint8_t* src, uint8_t* dst,
33 size_t pixel_count) noexcept;
34void invert_monochrome_16bit(const uint16_t* src, uint16_t* dst,
35 size_t pixel_count, uint16_t max_value) noexcept;
36void rgb_to_ycbcr_8bit(const uint8_t* src, uint8_t* dst,
37 size_t pixel_count) noexcept;
38void ycbcr_to_rgb_8bit(const uint8_t* src, uint8_t* dst,
39 size_t pixel_count) noexcept;
40
41namespace detail {
42
43// ============================================================================
44// Scalar fallback implementations
45// ============================================================================
46
50inline void invert_monochrome_8bit_scalar(const uint8_t* src, uint8_t* dst,
51 size_t pixel_count) noexcept {
52 for (size_t i = 0; i < pixel_count; ++i) {
53 dst[i] = 255 - src[i];
54 }
55}
56
64inline void invert_monochrome_16bit_scalar(const uint16_t* src, uint16_t* dst,
65 size_t pixel_count,
66 uint16_t max_value) noexcept {
67 for (size_t i = 0; i < pixel_count; ++i) {
68 dst[i] = max_value - src[i];
69 }
70}
71
79inline void rgb_to_ycbcr_8bit_scalar(const uint8_t* src, uint8_t* dst,
80 size_t pixel_count) noexcept {
81 for (size_t i = 0; i < pixel_count; ++i) {
82 const int r = src[i * 3];
83 const int g = src[i * 3 + 1];
84 const int b = src[i * 3 + 2];
85
86 // Fixed-point arithmetic (16.16 format for precision)
87 // Coefficients scaled by 65536
88 const int y = (19595 * r + 38470 * g + 7471 * b + 32768) >> 16;
89 const int cb = (-11056 * r - 21712 * g + 32768 * b + 32768) >> 16;
90 const int cr = (32768 * r - 27440 * g - 5328 * b + 32768) >> 16;
91
92 dst[i * 3] = static_cast<uint8_t>(y < 0 ? 0 : (y > 255 ? 255 : y));
93 dst[i * 3 + 1] = static_cast<uint8_t>(cb + 128);
94 dst[i * 3 + 2] = static_cast<uint8_t>(cr + 128);
95 }
96}
97
105inline void ycbcr_to_rgb_8bit_scalar(const uint8_t* src, uint8_t* dst,
106 size_t pixel_count) noexcept {
107 for (size_t i = 0; i < pixel_count; ++i) {
108 const int y = src[i * 3];
109 const int cb = src[i * 3 + 1] - 128;
110 const int cr = src[i * 3 + 2] - 128;
111
112 // Fixed-point arithmetic (coefficients scaled by 65536)
113 const int r = y + ((91881 * cr + 32768) >> 16);
114 const int g = y - ((22554 * cb + 46802 * cr + 32768) >> 16);
115 const int b = y + ((116130 * cb + 32768) >> 16);
116
117 dst[i * 3] = static_cast<uint8_t>(r < 0 ? 0 : (r > 255 ? 255 : r));
118 dst[i * 3 + 1] = static_cast<uint8_t>(g < 0 ? 0 : (g > 255 ? 255 : g));
119 dst[i * 3 + 2] = static_cast<uint8_t>(b < 0 ? 0 : (b > 255 ? 255 : b));
120 }
121}
122
123// ============================================================================
124// SSE2 implementations
125// ============================================================================
126
127#if defined(PACS_SIMD_SSE2)
128
133inline void invert_monochrome_8bit_sse2(const uint8_t* src, uint8_t* dst,
134 size_t pixel_count) noexcept {
135 const __m128i all_ones = _mm_set1_epi8(static_cast<char>(0xFF));
136 const size_t simd_count = (pixel_count / 16) * 16;
137
138 size_t i = 0;
139 for (; i < simd_count; i += 16) {
140 __m128i v = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src + i));
141 v = _mm_xor_si128(v, all_ones);
142 _mm_storeu_si128(reinterpret_cast<__m128i*>(dst + i), v);
143 }
144
145 // Handle remainder
146 invert_monochrome_8bit_scalar(src + i, dst + i, pixel_count - i);
147}
148
153inline void invert_monochrome_16bit_sse2(const uint16_t* src, uint16_t* dst,
154 size_t pixel_count,
155 uint16_t max_value) noexcept {
156 const __m128i max_vec = _mm_set1_epi16(static_cast<int16_t>(max_value));
157 const size_t simd_count = (pixel_count / 8) * 8;
158
159 size_t i = 0;
160 for (; i < simd_count; i += 8) {
161 __m128i v = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src + i));
162 v = _mm_sub_epi16(max_vec, v);
163 _mm_storeu_si128(reinterpret_cast<__m128i*>(dst + i), v);
164 }
165
166 // Handle remainder
167 invert_monochrome_16bit_scalar(src + i, dst + i, pixel_count - i, max_value);
168}
169
170#endif // PACS_SIMD_SSE2
171
172// ============================================================================
173// AVX2 implementations
174// ============================================================================
175
176#if defined(PACS_SIMD_AVX2)
177
182inline void invert_monochrome_8bit_avx2(const uint8_t* src, uint8_t* dst,
183 size_t pixel_count) noexcept {
184 const __m256i all_ones = _mm256_set1_epi8(static_cast<char>(0xFF));
185 const size_t simd_count = (pixel_count / 32) * 32;
186
187 size_t i = 0;
188 for (; i < simd_count; i += 32) {
189 __m256i v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src + i));
190 v = _mm256_xor_si256(v, all_ones);
191 _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + i), v);
192 }
193
194 // Handle remainder with SSE2
195#if defined(PACS_SIMD_SSE2)
196 if (pixel_count - i >= 16) {
197 invert_monochrome_8bit_sse2(src + i, dst + i, pixel_count - i);
198 } else
199#endif
200 {
201 invert_monochrome_8bit_scalar(src + i, dst + i, pixel_count - i);
202 }
203}
204
209inline void invert_monochrome_16bit_avx2(const uint16_t* src, uint16_t* dst,
210 size_t pixel_count,
211 uint16_t max_value) noexcept {
212 const __m256i max_vec = _mm256_set1_epi16(static_cast<int16_t>(max_value));
213 const size_t simd_count = (pixel_count / 16) * 16;
214
215 size_t i = 0;
216 for (; i < simd_count; i += 16) {
217 __m256i v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src + i));
218 v = _mm256_sub_epi16(max_vec, v);
219 _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + i), v);
220 }
221
222 // Handle remainder with SSE2
223#if defined(PACS_SIMD_SSE2)
224 if (pixel_count - i >= 8) {
225 invert_monochrome_16bit_sse2(src + i, dst + i, pixel_count - i, max_value);
226 } else
227#endif
228 {
229 invert_monochrome_16bit_scalar(src + i, dst + i, pixel_count - i, max_value);
230 }
231}
232
239inline void rgb_to_ycbcr_8bit_avx2(const uint8_t* src, uint8_t* dst,
240 size_t pixel_count) noexcept {
241 // Coefficients scaled by 256 (8-bit precision)
242 // Y coefficients: 77, 150, 29 (sum = 256)
243 // Cb coefficients: -43, -85, 128
244 // Cr coefficients: 128, -107, -21
245 const __m256i coeff_y_r = _mm256_set1_epi16(77);
246 const __m256i coeff_y_g = _mm256_set1_epi16(150);
247 const __m256i coeff_y_b = _mm256_set1_epi16(29);
248
249 const __m256i coeff_cb_r = _mm256_set1_epi16(-43);
250 const __m256i coeff_cb_g = _mm256_set1_epi16(-85);
251 const __m256i coeff_cb_b = _mm256_set1_epi16(128);
252
253 const __m256i coeff_cr_r = _mm256_set1_epi16(128);
254 const __m256i coeff_cr_g = _mm256_set1_epi16(-107);
255 const __m256i coeff_cr_b = _mm256_set1_epi16(-21);
256
257 const __m256i offset_128 = _mm256_set1_epi16(128);
258 const __m256i round_add = _mm256_set1_epi16(128); // Rounding for >>8
259
260 const size_t simd_count = (pixel_count / 8) * 8;
261
262 size_t i = 0;
263 for (; i < simd_count; i += 8) {
264 // Load 24 bytes (8 RGB pixels)
265 // Note: We need to handle the scattered RGB data
266 alignas(32) int16_t r[8], g[8], b[8];
267 for (int j = 0; j < 8; ++j) {
268 r[j] = src[(i + j) * 3];
269 g[j] = src[(i + j) * 3 + 1];
270 b[j] = src[(i + j) * 3 + 2];
271 }
272
273 __m256i r_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(r));
274 __m256i g_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(g));
275 __m256i b_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(b));
276
277 // Calculate Y = (77*R + 150*G + 29*B + 128) >> 8
278 __m256i y = _mm256_add_epi16(
279 _mm256_add_epi16(_mm256_mullo_epi16(r_vec, coeff_y_r),
280 _mm256_mullo_epi16(g_vec, coeff_y_g)),
281 _mm256_mullo_epi16(b_vec, coeff_y_b));
282 y = _mm256_add_epi16(y, round_add);
283 y = _mm256_srai_epi16(y, 8);
284
285 // Calculate Cb = (-43*R - 85*G + 128*B + 128) >> 8 + 128
286 __m256i cb = _mm256_add_epi16(
287 _mm256_add_epi16(_mm256_mullo_epi16(r_vec, coeff_cb_r),
288 _mm256_mullo_epi16(g_vec, coeff_cb_g)),
289 _mm256_mullo_epi16(b_vec, coeff_cb_b));
290 cb = _mm256_add_epi16(cb, round_add);
291 cb = _mm256_srai_epi16(cb, 8);
292 cb = _mm256_add_epi16(cb, offset_128);
293
294 // Calculate Cr = (128*R - 107*G - 21*B + 128) >> 8 + 128
295 __m256i cr = _mm256_add_epi16(
296 _mm256_add_epi16(_mm256_mullo_epi16(r_vec, coeff_cr_r),
297 _mm256_mullo_epi16(g_vec, coeff_cr_g)),
298 _mm256_mullo_epi16(b_vec, coeff_cr_b));
299 cr = _mm256_add_epi16(cr, round_add);
300 cr = _mm256_srai_epi16(cr, 8);
301 cr = _mm256_add_epi16(cr, offset_128);
302
303 // Store results
304 alignas(32) int16_t y_out[8], cb_out[8], cr_out[8];
305 _mm256_store_si256(reinterpret_cast<__m256i*>(y_out), y);
306 _mm256_store_si256(reinterpret_cast<__m256i*>(cb_out), cb);
307 _mm256_store_si256(reinterpret_cast<__m256i*>(cr_out), cr);
308
309 for (int j = 0; j < 8; ++j) {
310 dst[(i + j) * 3] = static_cast<uint8_t>(
311 y_out[j] < 0 ? 0 : (y_out[j] > 255 ? 255 : y_out[j]));
312 dst[(i + j) * 3 + 1] = static_cast<uint8_t>(
313 cb_out[j] < 0 ? 0 : (cb_out[j] > 255 ? 255 : cb_out[j]));
314 dst[(i + j) * 3 + 2] = static_cast<uint8_t>(
315 cr_out[j] < 0 ? 0 : (cr_out[j] > 255 ? 255 : cr_out[j]));
316 }
317 }
318
319 // Handle remainder
320 rgb_to_ycbcr_8bit_scalar(src + i * 3, dst + i * 3, pixel_count - i);
321}
322
326inline void ycbcr_to_rgb_8bit_avx2(const uint8_t* src, uint8_t* dst,
327 size_t pixel_count) noexcept {
328 // Coefficients scaled by 256
329 // R = Y + 1.402*Cr -> coeff = 359
330 // G = Y - 0.344*Cb - 0.714*Cr -> coeffs = -88, -183
331 // B = Y + 1.772*Cb -> coeff = 454
332 const __m256i coeff_r_cr = _mm256_set1_epi16(359);
333 const __m256i coeff_g_cb = _mm256_set1_epi16(-88);
334 const __m256i coeff_g_cr = _mm256_set1_epi16(-183);
335 const __m256i coeff_b_cb = _mm256_set1_epi16(454);
336
337 const __m256i round_add = _mm256_set1_epi16(128);
338 const __m256i zero = _mm256_setzero_si256();
339 const __m256i max_255 = _mm256_set1_epi16(255);
340
341 const size_t simd_count = (pixel_count / 8) * 8;
342
343 size_t i = 0;
344 for (; i < simd_count; i += 8) {
345 // Load YCbCr values
346 alignas(32) int16_t y[8], cb[8], cr[8];
347 for (int j = 0; j < 8; ++j) {
348 y[j] = src[(i + j) * 3];
349 cb[j] = src[(i + j) * 3 + 1] - 128;
350 cr[j] = src[(i + j) * 3 + 2] - 128;
351 }
352
353 __m256i y_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(y));
354 __m256i cb_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(cb));
355 __m256i cr_vec = _mm256_load_si256(reinterpret_cast<const __m256i*>(cr));
356
357 // Calculate R = Y + (359*Cr + 128) >> 8
358 __m256i r = _mm256_add_epi16(
359 _mm256_srai_epi16(
360 _mm256_add_epi16(_mm256_mullo_epi16(cr_vec, coeff_r_cr), round_add),
361 8),
362 y_vec);
363
364 // Calculate G = Y + (-88*Cb - 183*Cr + 128) >> 8
365 __m256i g = _mm256_add_epi16(
366 _mm256_srai_epi16(
367 _mm256_add_epi16(
368 _mm256_add_epi16(_mm256_mullo_epi16(cb_vec, coeff_g_cb),
369 _mm256_mullo_epi16(cr_vec, coeff_g_cr)),
370 round_add),
371 8),
372 y_vec);
373
374 // Calculate B = Y + (454*Cb + 128) >> 8
375 __m256i b = _mm256_add_epi16(
376 _mm256_srai_epi16(
377 _mm256_add_epi16(_mm256_mullo_epi16(cb_vec, coeff_b_cb), round_add),
378 8),
379 y_vec);
380
381 // Clamp to [0, 255]
382 r = _mm256_max_epi16(_mm256_min_epi16(r, max_255), zero);
383 g = _mm256_max_epi16(_mm256_min_epi16(g, max_255), zero);
384 b = _mm256_max_epi16(_mm256_min_epi16(b, max_255), zero);
385
386 // Store results
387 alignas(32) int16_t r_out[8], g_out[8], b_out[8];
388 _mm256_store_si256(reinterpret_cast<__m256i*>(r_out), r);
389 _mm256_store_si256(reinterpret_cast<__m256i*>(g_out), g);
390 _mm256_store_si256(reinterpret_cast<__m256i*>(b_out), b);
391
392 for (int j = 0; j < 8; ++j) {
393 dst[(i + j) * 3] = static_cast<uint8_t>(r_out[j]);
394 dst[(i + j) * 3 + 1] = static_cast<uint8_t>(g_out[j]);
395 dst[(i + j) * 3 + 2] = static_cast<uint8_t>(b_out[j]);
396 }
397 }
398
399 // Handle remainder
400 ycbcr_to_rgb_8bit_scalar(src + i * 3, dst + i * 3, pixel_count - i);
401}
402
403#endif // PACS_SIMD_AVX2
404
405// ============================================================================
406// NEON implementations (ARM)
407// ============================================================================
408
409#if defined(PACS_SIMD_NEON)
410
415inline void invert_monochrome_8bit_neon(const uint8_t* src, uint8_t* dst,
416 size_t pixel_count) noexcept {
417 const uint8x16_t all_ones = vdupq_n_u8(0xFF);
418 const size_t simd_count = (pixel_count / 16) * 16;
419
420 size_t i = 0;
421 for (; i < simd_count; i += 16) {
422 uint8x16_t v = vld1q_u8(src + i);
423 v = veorq_u8(v, all_ones);
424 vst1q_u8(dst + i, v);
425 }
426
427 // Handle remainder
428 invert_monochrome_8bit_scalar(src + i, dst + i, pixel_count - i);
429}
430
435inline void invert_monochrome_16bit_neon(const uint16_t* src, uint16_t* dst,
436 size_t pixel_count,
437 uint16_t max_value) noexcept {
438 const uint16x8_t max_vec = vdupq_n_u16(max_value);
439 const size_t simd_count = (pixel_count / 8) * 8;
440
441 size_t i = 0;
442 for (; i < simd_count; i += 8) {
443 uint16x8_t v = vld1q_u16(src + i);
444 v = vsubq_u16(max_vec, v);
445 vst1q_u16(dst + i, v);
446 }
447
448 // Handle remainder
449 invert_monochrome_16bit_scalar(src + i, dst + i, pixel_count - i, max_value);
450}
451
456inline void rgb_to_ycbcr_8bit_neon(const uint8_t* src, uint8_t* dst,
457 size_t pixel_count) noexcept {
458 // Coefficients (scaled by 256)
459 const int16x8_t coeff_y_r = vdupq_n_s16(77);
460 const int16x8_t coeff_y_g = vdupq_n_s16(150);
461 const int16x8_t coeff_y_b = vdupq_n_s16(29);
462
463 const int16x8_t coeff_cb_r = vdupq_n_s16(-43);
464 const int16x8_t coeff_cb_g = vdupq_n_s16(-85);
465 const int16x8_t coeff_cb_b = vdupq_n_s16(128);
466
467 const int16x8_t coeff_cr_r = vdupq_n_s16(128);
468 const int16x8_t coeff_cr_g = vdupq_n_s16(-107);
469 const int16x8_t coeff_cr_b = vdupq_n_s16(-21);
470
471 const int16x8_t offset_128 = vdupq_n_s16(128);
472
473 const size_t simd_count = (pixel_count / 8) * 8;
474
475 size_t i = 0;
476 for (; i < simd_count; i += 8) {
477 // Load and deinterleave 8 RGB pixels
478 uint8x8x3_t rgb = vld3_u8(src + i * 3);
479
480 // Widen to 16-bit for arithmetic
481 int16x8_t r = vreinterpretq_s16_u16(vmovl_u8(rgb.val[0]));
482 int16x8_t g = vreinterpretq_s16_u16(vmovl_u8(rgb.val[1]));
483 int16x8_t b = vreinterpretq_s16_u16(vmovl_u8(rgb.val[2]));
484
485 // Y = (77*R + 150*G + 29*B + 128) >> 8
486 int16x8_t y = vaddq_s16(vaddq_s16(vmulq_s16(r, coeff_y_r),
487 vmulq_s16(g, coeff_y_g)),
488 vmulq_s16(b, coeff_y_b));
489 y = vaddq_s16(y, offset_128);
490 y = vshrq_n_s16(y, 8);
491
492 // Cb = (-43*R - 85*G + 128*B + 128) >> 8 + 128
493 int16x8_t cb = vaddq_s16(vaddq_s16(vmulq_s16(r, coeff_cb_r),
494 vmulq_s16(g, coeff_cb_g)),
495 vmulq_s16(b, coeff_cb_b));
496 cb = vaddq_s16(cb, offset_128);
497 cb = vshrq_n_s16(cb, 8);
498 cb = vaddq_s16(cb, offset_128);
499
500 // Cr = (128*R - 107*G - 21*B + 128) >> 8 + 128
501 int16x8_t cr = vaddq_s16(vaddq_s16(vmulq_s16(r, coeff_cr_r),
502 vmulq_s16(g, coeff_cr_g)),
503 vmulq_s16(b, coeff_cr_b));
504 cr = vaddq_s16(cr, offset_128);
505 cr = vshrq_n_s16(cr, 8);
506 cr = vaddq_s16(cr, offset_128);
507
508 // Narrow back to 8-bit with saturation
509 uint8x8x3_t ycbcr;
510 ycbcr.val[0] = vqmovun_s16(y);
511 ycbcr.val[1] = vqmovun_s16(cb);
512 ycbcr.val[2] = vqmovun_s16(cr);
513
514 // Store interleaved YCbCr
515 vst3_u8(dst + i * 3, ycbcr);
516 }
517
518 // Handle remainder
519 rgb_to_ycbcr_8bit_scalar(src + i * 3, dst + i * 3, pixel_count - i);
520}
521
525inline void ycbcr_to_rgb_8bit_neon(const uint8_t* src, uint8_t* dst,
526 size_t pixel_count) noexcept {
527 const int16x8_t coeff_r_cr = vdupq_n_s16(359);
528 const int16x8_t coeff_g_cb = vdupq_n_s16(-88);
529 const int16x8_t coeff_g_cr = vdupq_n_s16(-183);
530 const int16x8_t coeff_b_cb = vdupq_n_s16(454);
531 const int16x8_t offset_128 = vdupq_n_s16(128);
532
533 const size_t simd_count = (pixel_count / 8) * 8;
534
535 size_t i = 0;
536 for (; i < simd_count; i += 8) {
537 // Load and deinterleave 8 YCbCr pixels
538 uint8x8x3_t ycbcr = vld3_u8(src + i * 3);
539
540 // Widen to 16-bit
541 int16x8_t y = vreinterpretq_s16_u16(vmovl_u8(ycbcr.val[0]));
542 int16x8_t cb = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(ycbcr.val[1])),
543 offset_128);
544 int16x8_t cr = vsubq_s16(vreinterpretq_s16_u16(vmovl_u8(ycbcr.val[2])),
545 offset_128);
546
547 // R = Y + (359*Cr + 128) >> 8
548 int16x8_t r = vaddq_s16(y, vshrq_n_s16(vaddq_s16(vmulq_s16(cr, coeff_r_cr),
549 offset_128), 8));
550
551 // G = Y + (-88*Cb - 183*Cr + 128) >> 8
552 int16x8_t g = vaddq_s16(y, vshrq_n_s16(
553 vaddq_s16(vaddq_s16(vmulq_s16(cb, coeff_g_cb),
554 vmulq_s16(cr, coeff_g_cr)),
555 offset_128), 8));
556
557 // B = Y + (454*Cb + 128) >> 8
558 int16x8_t bl = vaddq_s16(y, vshrq_n_s16(vaddq_s16(vmulq_s16(cb, coeff_b_cb),
559 offset_128), 8));
560
561 // Narrow with saturation
562 uint8x8x3_t rgb;
563 rgb.val[0] = vqmovun_s16(r);
564 rgb.val[1] = vqmovun_s16(g);
565 rgb.val[2] = vqmovun_s16(bl);
566
567 // Store interleaved RGB
568 vst3_u8(dst + i * 3, rgb);
569 }
570
571 // Handle remainder
572 ycbcr_to_rgb_8bit_scalar(src + i * 3, dst + i * 3, pixel_count - i);
573}
574
575#endif // PACS_SIMD_NEON
576
577} // namespace detail
578
579// ============================================================================
580// Public API with runtime dispatch
581// ============================================================================
582
593inline void invert_monochrome_8bit(const uint8_t* src, uint8_t* dst,
594 size_t pixel_count) noexcept {
595#if defined(PACS_SIMD_AVX2)
596 if (has_avx2()) {
597 detail::invert_monochrome_8bit_avx2(src, dst, pixel_count);
598 return;
599 }
600#endif
601#if defined(PACS_SIMD_SSE2)
602 if (has_sse2()) {
603 detail::invert_monochrome_8bit_sse2(src, dst, pixel_count);
604 return;
605 }
606#endif
607#if defined(PACS_SIMD_NEON)
608 if (has_neon()) {
609 detail::invert_monochrome_8bit_neon(src, dst, pixel_count);
610 return;
611 }
612#endif
613 detail::invert_monochrome_8bit_scalar(src, dst, pixel_count);
614}
615
624inline void invert_monochrome_16bit(const uint16_t* src, uint16_t* dst,
625 size_t pixel_count,
626 uint16_t max_value) noexcept {
627#if defined(PACS_SIMD_AVX2)
628 if (has_avx2()) {
629 detail::invert_monochrome_16bit_avx2(src, dst, pixel_count, max_value);
630 return;
631 }
632#endif
633#if defined(PACS_SIMD_SSE2)
634 if (has_sse2()) {
635 detail::invert_monochrome_16bit_sse2(src, dst, pixel_count, max_value);
636 return;
637 }
638#endif
639#if defined(PACS_SIMD_NEON)
640 if (has_neon()) {
641 detail::invert_monochrome_16bit_neon(src, dst, pixel_count, max_value);
642 return;
643 }
644#endif
645 detail::invert_monochrome_16bit_scalar(src, dst, pixel_count, max_value);
646}
647
655inline void rgb_to_ycbcr_8bit(const uint8_t* src, uint8_t* dst,
656 size_t pixel_count) noexcept {
657#if defined(PACS_SIMD_AVX2)
658 if (has_avx2()) {
659 detail::rgb_to_ycbcr_8bit_avx2(src, dst, pixel_count);
660 return;
661 }
662#endif
663#if defined(PACS_SIMD_NEON)
664 if (has_neon()) {
665 detail::rgb_to_ycbcr_8bit_neon(src, dst, pixel_count);
666 return;
667 }
668#endif
669 detail::rgb_to_ycbcr_8bit_scalar(src, dst, pixel_count);
670}
671
679inline void ycbcr_to_rgb_8bit(const uint8_t* src, uint8_t* dst,
680 size_t pixel_count) noexcept {
681#if defined(PACS_SIMD_AVX2)
682 if (has_avx2()) {
683 detail::ycbcr_to_rgb_8bit_avx2(src, dst, pixel_count);
684 return;
685 }
686#endif
687#if defined(PACS_SIMD_NEON)
688 if (has_neon()) {
689 detail::ycbcr_to_rgb_8bit_neon(src, dst, pixel_count);
690 return;
691 }
692#endif
693 detail::ycbcr_to_rgb_8bit_scalar(src, dst, pixel_count);
694}
695
696} // namespace kcenon::pacs::encoding::simd
697
698#endif // PACS_ENCODING_SIMD_PHOTOMETRIC_HPP
void invert_monochrome_8bit_scalar(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Scalar 8-bit monochrome inversion (MONOCHROME1 <-> MONOCHROME2)
void ycbcr_to_rgb_8bit_scalar(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Scalar YCbCr to RGB conversion (ITU-R BT.601)
void invert_monochrome_16bit_scalar(const uint16_t *src, uint16_t *dst, size_t pixel_count, uint16_t max_value) noexcept
Scalar 16-bit monochrome inversion.
void rgb_to_ycbcr_8bit_scalar(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Scalar RGB to YCbCr conversion (ITU-R BT.601)
void invert_monochrome_8bit(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Invert 8-bit monochrome pixels (MONOCHROME1 <-> MONOCHROME2)
bool has_neon() noexcept
Check if NEON is available.
bool has_sse2() noexcept
Check if SSE2 is available.
void invert_monochrome_16bit(const uint16_t *src, uint16_t *dst, size_t pixel_count, uint16_t max_value) noexcept
Invert 16-bit monochrome pixels.
bool has_avx2() noexcept
Check if AVX2 is available.
void ycbcr_to_rgb_8bit(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Convert YCbCr to RGB color space (ITU-R BT.601)
void rgb_to_ycbcr_8bit(const uint8_t *src, uint8_t *dst, size_t pixel_count) noexcept
Convert RGB to YCbCr color space (ITU-R BT.601)
SIMD configuration and CPU feature detection.
Platform-specific SIMD type definitions and wrappers.