1use crate::kernels::pool::SyncPtr;
9
10pub mod hdr;
11
12#[derive(Clone, Copy)]
14pub struct Coeffs {
15 yg: u32,
16 yb: i32,
17 ub: i32,
18 ug: i32,
19 vg: i32,
20 vr: i32,
21}
22
23pub static MATRICES: [Coeffs; 4] = [
27 Coeffs {
29 yg: 18997,
30 yb: -1160,
31 ub: 128,
32 ug: 14,
33 vg: 34,
34 vr: 115,
35 },
36 Coeffs {
38 yg: 16320,
39 yb: 32,
40 ub: 119,
41 ug: 12,
42 vg: 30,
43 vr: 101,
44 },
45 Coeffs {
47 yg: 18997,
48 yb: -1160,
49 ub: 128,
50 ug: 25,
51 vg: 52,
52 vr: 102,
53 },
54 Coeffs {
56 yg: 16320,
57 yb: 32,
58 ub: 113,
59 ug: 22,
60 vg: 46,
61 vr: 90,
62 },
63];
64
65#[derive(Clone, Copy)]
68pub enum Signal {
69 Sdr(&'static Coeffs),
70 Hdr {
71 matrix: hdr::Matrix,
72 full: bool,
73 transfer: hdr::Transfer,
74 },
75}
76
77impl Signal {
78 pub fn from_code(code: u32) -> Option<Signal> {
79 let (full, matrix) = (code & 1 == 1, (code >> 1) & 3);
80 let transfer = match code >> 3 {
81 0 => return MATRICES.get(code as usize).map(Signal::Sdr),
82 1 => hdr::Transfer::Pq,
83 2 => hdr::Transfer::Hlg,
84 _ => return None,
85 };
86 let matrix = match matrix {
87 0 => hdr::Matrix::Bt709,
88 1 => hdr::Matrix::Bt601,
89 2 => hdr::Matrix::Bt2020,
90 _ => return None,
91 };
92 Some(Signal::Hdr {
93 matrix,
94 full,
95 transfer,
96 })
97 }
98}
99
100#[allow(clippy::too_many_arguments)]
103pub fn convert(
104 src: &[u8],
105 format: Format,
106 planes: &[Plane],
107 width: usize,
108 height: usize,
109 signal: Signal,
110 out: &mut [u8],
111) -> usize {
112 match signal {
113 Signal::Sdr(coeffs) => {
114 to_rgba(src, format, planes, width, height, coeffs, out);
115 0
116 }
117 Signal::Hdr {
118 matrix,
119 full,
120 transfer,
121 } => hdr::planes_to_rgba(
122 src, format, planes, width, height, matrix, full, transfer, out,
123 ),
124 }
125}
126
127#[derive(Clone, Copy, PartialEq, Eq)]
128pub enum Format {
129 I420,
130 I422,
131 I444,
132 Nv12,
133}
134
135impl Format {
136 pub fn from_code(code: u32) -> Option<Format> {
137 Some(match code {
138 0 => Format::I420,
139 1 => Format::I422,
140 2 => Format::I444,
141 3 => Format::Nv12,
142 _ => return None,
143 })
144 }
145
146 pub fn planes(self, width: usize, height: usize) -> Vec<(usize, usize)> {
148 let (cw, ch) = (width.div_ceil(2), height.div_ceil(2));
149 match self {
150 Format::I420 => vec![(width, height), (cw, ch), (cw, ch)],
151 Format::I422 => vec![(width, height), (cw, height), (cw, height)],
152 Format::I444 => vec![(width, height); 3],
153 Format::Nv12 => vec![(width, height), (cw * 2, ch)],
154 }
155 }
156}
157
158#[inline]
159fn clamp(v: i32) -> u8 {
160 v.clamp(0, 255) as u8
161}
162
163#[inline]
165pub fn yuv_pixel(y: u8, u: u8, v: u8, c: &Coeffs) -> [u8; 3] {
166 let y1 = ((y as u32 * 0x0101).wrapping_mul(c.yg) >> 16) as i32 + c.yb;
167 let (ui, vi) = (u as i32 - 128, v as i32 - 128);
168 [
169 clamp((y1 + vi * c.vr) >> 6),
170 clamp((y1 - (ui * c.ug + vi * c.vg)) >> 6),
171 clamp((y1 + ui * c.ub) >> 6),
172 ]
173}
174
175#[derive(Clone, Copy)]
177pub struct Plane {
178 pub offset: usize,
179 pub stride: usize,
180}
181
182pub fn to_rgba(
185 src: &[u8],
186 format: Format,
187 planes: &[Plane],
188 width: usize,
189 height: usize,
190 coeffs: &Coeffs,
191 out: &mut [u8],
192) {
193 let chunks = crate::kernels::pool::chunks_for(width * height, 128 * 1024);
194 let dst = SyncPtr(out.as_mut_ptr());
195 let luma = luma_table(coeffs);
196 crate::kernels::pool::par_for(chunks, |c| {
197 for y in crate::kernels::pool::split(height, chunks, c)
198 ..crate::kernels::pool::split(height, chunks, c + 1)
199 {
200 let row =
202 unsafe { std::slice::from_raw_parts_mut(dst.get().add(y * width * 4), width * 4) };
203 convert_row(src, format, planes, width, y, coeffs, &luma, row);
204 }
205 });
206}
207
208fn luma_table(c: &Coeffs) -> [i32; 256] {
210 std::array::from_fn(|y| ((y as u32 * 0x0101).wrapping_mul(c.yg) >> 16) as i32 + c.yb)
211}
212
213#[inline]
215fn chroma(u: u8, v: u8, c: &Coeffs) -> (i32, i32, i32) {
216 let (ui, vi) = (u as i32 - 128, v as i32 - 128);
217 (vi * c.vr, ui * c.ug + vi * c.vg, ui * c.ub)
218}
219
220#[inline]
222fn rgba(y1: i32, (r, g, b): (i32, i32, i32)) -> [u8; 4] {
223 [
224 clamp((y1 + r) >> 6),
225 clamp((y1 - g) >> 6),
226 clamp((y1 + b) >> 6),
227 255,
228 ]
229}
230
231#[allow(clippy::too_many_arguments)]
234fn convert_row(
235 src: &[u8],
236 format: Format,
237 p: &[Plane],
238 width: usize,
239 y: usize,
240 c: &Coeffs,
241 luma: &[i32; 256],
242 row: &mut [u8],
243) {
244 let line = |i: usize, r: usize, len: usize| &src[p[i].offset + r * p[i].stride..][..len];
245 let (cw, cy) = (width.div_ceil(2), y / 2);
246 let ys = line(0, y, width);
247 let put = |pair: &mut [u8], yy: &[u8], u: u8, v: u8| {
249 let terms = chroma(u, v, c);
250 for (px, &yv) in pair.chunks_exact_mut(4).zip(yy) {
251 px.copy_from_slice(&rgba(luma[yv as usize], terms));
252 }
253 };
254 match format {
255 Format::I444 => {
256 let (us, vs) = (line(1, y, width), line(2, y, width));
257 for (((px, &yv), &u), &v) in row.chunks_exact_mut(4).zip(ys).zip(us).zip(vs) {
258 px.copy_from_slice(&rgba(luma[yv as usize], chroma(u, v, c)));
259 }
260 }
261 Format::I420 | Format::I422 => {
262 let chroma_row = if format == Format::I422 { y } else { cy };
263 let (us, vs) = (line(1, chroma_row, cw), line(2, chroma_row, cw));
264 let done = simd::planar(ys, us, vs, c, row);
265 let (row, ys, us, vs) = (
266 &mut row[done * 4..],
267 &ys[done..],
268 &us[done / 2..],
269 &vs[done / 2..],
270 );
271 for (((pair, yy), &u), &v) in row.chunks_mut(8).zip(ys.chunks(2)).zip(us).zip(vs) {
272 put(pair, yy, u, v);
273 }
274 }
275 Format::Nv12 => {
276 let uv = line(1, cy, cw * 2);
277 let done = simd::nv12(ys, uv, c, row);
278 let (row, ys, uv) = (&mut row[done * 4..], &ys[done..], &uv[done..]);
279 for ((pair, yy), uv) in row.chunks_mut(8).zip(ys.chunks(2)).zip(uv.chunks_exact(2)) {
280 put(pair, yy, uv[0], uv[1]);
281 }
282 }
283 }
284}
285
286#[cfg(target_feature = "simd128")]
291mod simd {
292 use super::Coeffs;
293 use std::arch::wasm32::*;
294
295 struct Lanes {
296 k: v128,
297 yb: v128,
298 vr: v128,
299 ug: v128,
300 vg: v128,
301 ub: v128,
302 }
303
304 impl Lanes {
305 fn new(c: &Coeffs) -> Self {
306 Lanes {
307 k: u32x4_splat(0x0101 * c.yg),
309 yb: i32x4_splat(c.yb),
310 vr: i32x4_splat(c.vr),
311 ug: i32x4_splat(c.ug),
312 vg: i32x4_splat(c.vg),
313 ub: i32x4_splat(c.ub),
314 }
315 }
316 }
317
318 #[inline]
320 fn four(y: v128, r: v128, g: v128, b: v128, l: &Lanes) -> v128 {
321 let y1 = i32x4_add(u32x4_shr(i32x4_mul(y, l.k), 16), l.yb);
322 let byte =
323 |t: v128| i32x4_min(i32x4_max(i32x4_shr(t, 6), i32x4_splat(0)), i32x4_splat(255));
324 let (r, g, b) = (
325 byte(i32x4_add(y1, r)),
326 byte(i32x4_sub(y1, g)),
327 byte(i32x4_add(y1, b)),
328 );
329 v128_or(
330 v128_or(r, i32x4_shl(g, 8)),
331 v128_or(i32x4_shl(b, 16), u32x4_splat(0xff00_0000)),
332 )
333 }
334
335 #[inline]
340 unsafe fn eight(ys: *const u8, ui: v128, vi: v128, l: &Lanes, out: *mut u8) {
341 let y16 = u16x8_extend_low_u8x16(v128_load64_zero(ys as *const u64));
342 let (lo, hi) = (u32x4_extend_low_u16x8(y16), u32x4_extend_high_u16x8(y16));
343 let r = i32x4_mul(vi, l.vr);
344 let g = i32x4_add(i32x4_mul(ui, l.ug), i32x4_mul(vi, l.vg));
345 let b = i32x4_mul(ui, l.ub);
346 let first = |t: v128| i32x4_shuffle::<0, 0, 1, 1>(t, t);
347 let second = |t: v128| i32x4_shuffle::<2, 2, 3, 3>(t, t);
348 v128_store(out as *mut v128, four(lo, first(r), first(g), first(b), l));
349 v128_store(
350 out.add(16) as *mut v128,
351 four(hi, second(r), second(g), second(b), l),
352 );
353 }
354
355 #[inline]
357 unsafe fn centred4(p: *const u8) -> v128 {
358 let w = u32x4_extend_low_u16x8(u16x8_extend_low_u8x16(v128_load32_zero(p as *const u32)));
359 i32x4_sub(w, i32x4_splat(128))
360 }
361
362 pub fn planar(ys: &[u8], us: &[u8], vs: &[u8], c: &Coeffs, row: &mut [u8]) -> usize {
363 let (l, groups) = (
364 Lanes::new(c),
365 (ys.len() / 8).min(us.len() / 4).min(vs.len() / 4),
366 );
367 for k in 0..groups {
368 unsafe {
371 let (ui, vi) = (
372 centred4(us.as_ptr().add(4 * k)),
373 centred4(vs.as_ptr().add(4 * k)),
374 );
375 eight(
376 ys.as_ptr().add(8 * k),
377 ui,
378 vi,
379 &l,
380 row.as_mut_ptr().add(32 * k),
381 );
382 }
383 }
384 groups * 8
385 }
386
387 pub fn nv12(ys: &[u8], uv: &[u8], c: &Coeffs, row: &mut [u8]) -> usize {
388 let (l, groups) = (Lanes::new(c), (ys.len() / 8).min(uv.len() / 8));
389 for k in 0..groups {
390 unsafe {
393 let w =
394 u16x8_extend_low_u8x16(v128_load64_zero(uv.as_ptr().add(8 * k) as *const u64));
395 let (lo, hi) = (u32x4_extend_low_u16x8(w), u32x4_extend_high_u16x8(w));
396 let ui = i32x4_sub(i32x4_shuffle::<0, 2, 4, 6>(lo, hi), i32x4_splat(128));
397 let vi = i32x4_sub(i32x4_shuffle::<1, 3, 5, 7>(lo, hi), i32x4_splat(128));
398 eight(
399 ys.as_ptr().add(8 * k),
400 ui,
401 vi,
402 &l,
403 row.as_mut_ptr().add(32 * k),
404 );
405 }
406 }
407 groups * 8
408 }
409}
410
411#[cfg(not(target_feature = "simd128"))]
417mod simd {
418 use super::Coeffs;
419
420 #[inline(always)]
422 fn pair(y0: u8, y1: u8, u: u8, v: u8, c: &Coeffs) -> (u32, u32) {
423 let (ui, vi) = (u as i32 - 128, v as i32 - 128);
424 let (r, g, b) = (vi * c.vr, ui * c.ug + vi * c.vg, ui * c.ub);
425 let k = 0x0101 * c.yg;
426 let byte = |t: i32| (t >> 6).clamp(0, 255) as u32;
427 let px = |y: u8| {
428 let l = ((y as u32 * k) >> 16) as i32 + c.yb;
429 byte(l + r) | byte(l - g) << 8 | byte(l + b) << 16 | 0xff00_0000
430 };
431 (px(y0), px(y1))
432 }
433
434 #[inline(always)]
435 fn store(row: &mut [u8], k: usize, (a, b): (u32, u32)) {
436 let o: &mut [u8; 8] = (&mut row[8 * k..8 * k + 8]).try_into().unwrap();
437 o[..4].copy_from_slice(&a.to_le_bytes());
438 o[4..].copy_from_slice(&b.to_le_bytes());
439 }
440
441 pub fn planar(ys: &[u8], us: &[u8], vs: &[u8], c: &Coeffs, row: &mut [u8]) -> usize {
442 let n = (ys.len() / 2)
443 .min(us.len())
444 .min(vs.len())
445 .min(row.len() / 8);
446 let (ys, us, vs, row) = (&ys[..2 * n], &us[..n], &vs[..n], &mut row[..8 * n]);
447 for k in 0..n {
448 store(row, k, pair(ys[2 * k], ys[2 * k + 1], us[k], vs[k], c));
449 }
450 2 * n
451 }
452
453 pub fn nv12(ys: &[u8], uv: &[u8], c: &Coeffs, row: &mut [u8]) -> usize {
454 let n = (ys.len() / 2).min(uv.len() / 2).min(row.len() / 8);
455 let (ys, uv, row) = (&ys[..2 * n], &uv[..2 * n], &mut row[..8 * n]);
456 for k in 0..n {
457 store(
458 row,
459 k,
460 pair(ys[2 * k], ys[2 * k + 1], uv[2 * k], uv[2 * k + 1], c),
461 );
462 }
463 2 * n
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 fn reference(
476 src: &[u8],
477 format: Format,
478 planes: &[Plane],
479 w: usize,
480 h: usize,
481 c: &Coeffs,
482 ) -> Vec<u8> {
483 let mut out = vec![0u8; w * h * 4];
484 let (cw, shift) = (w.div_ceil(2), usize::from(format != Format::I444));
485 for y in 0..h {
486 for x in 0..w {
487 let cy = match format {
488 Format::I420 | Format::Nv12 => y / 2,
489 _ => y,
490 };
491 let at = |i: usize, r: usize, col: usize| {
492 src[planes[i].offset + r * planes[i].stride + col]
493 };
494 let (u, v) = match format {
495 Format::Nv12 => (at(1, cy, (x >> 1) * 2), at(1, cy, (x >> 1) * 2 + 1)),
496 _ => (at(1, cy, x >> shift), at(2, cy, x >> shift)),
497 };
498 let [r, g, b] = yuv_pixel(at(0, y, x), u, v, c);
499 out[(y * w + x) * 4..][..4].copy_from_slice(&[r, g, b, 255]);
500 }
501 }
502 let _ = cw;
503 out
504 }
505
506 #[test]
507 fn colour_codes_keep_the_sdr_matrices_and_name_every_hdr_signal() {
508 for code in 0..4 {
509 assert!(
510 matches!(Signal::from_code(code), Some(Signal::Sdr(c)) if std::ptr::eq(c, &MATRICES[code as usize]))
511 );
512 }
513 for code in [4, 5, 6, 7, 14, 15, 22, 23, 24, 31] {
515 assert!(Signal::from_code(code).is_none(), "{code}");
516 }
517 let hdr = |code| match Signal::from_code(code) {
518 Some(Signal::Hdr {
519 matrix,
520 full,
521 transfer,
522 }) => (matrix, full, transfer),
523 _ => panic!("{code}"),
524 };
525 use hdr::{Matrix, Transfer};
526 assert_eq!(hdr(8), (Matrix::Bt709, false, Transfer::Pq));
527 assert_eq!(hdr(13), (Matrix::Bt2020, true, Transfer::Pq));
528 assert_eq!(hdr(19), (Matrix::Bt601, true, Transfer::Hlg));
529 assert_eq!(hdr(20), (Matrix::Bt2020, false, Transfer::Hlg));
530 }
531
532 #[test]
533 fn rows_match_the_per_pixel_reference_for_every_layout_and_matrix() {
534 let mut seed = 0x9e37_79b9u32;
535 let mut next = move || {
536 seed ^= seed << 13;
537 seed ^= seed >> 17;
538 seed ^= seed << 5;
539 seed
540 };
541 for format in [Format::I420, Format::I422, Format::I444, Format::Nv12] {
542 for (w, h) in [(1, 1), (2, 2), (7, 5), (33, 9), (64, 3)] {
543 let mut planes = Vec::new();
545 let mut len = 0;
546 for (row_bytes, rows) in format.planes(w, h) {
547 planes.push(Plane {
548 offset: len,
549 stride: row_bytes + 3,
550 });
551 len += (row_bytes + 3) * rows;
552 }
553 for fill in 0..3 {
554 let src: Vec<u8> = (0..len)
555 .map(|_| match fill {
556 0 => next() as u8,
557 1 => [0u8, 255][(next() & 1) as usize],
558 _ => [16u8, 235, 240, 0, 255][(next() % 5) as usize],
559 })
560 .collect();
561 for c in &MATRICES {
562 let mut out = vec![0u8; w * h * 4];
563 to_rgba(&src, format, &planes, w, h, c, &mut out);
564 assert!(
565 out == reference(&src, format, &planes, w, h, c),
566 "{w}x{h} fill {fill}"
567 );
568 }
569 }
570 }
571 }
572 }
573}