pub(super) fn quality_to_qp(quality: u8) -> u32 {
((100 - quality as u32) * 63 + 50) / 100
}
pub(super) fn rgb_to_yuv420_10bit(
pixels: &[u8],
w: usize,
h: usize,
channels: usize,
y_plane: &mut Vec<u16>,
cb_plane: &mut Vec<u16>,
cr_plane: &mut Vec<u16>,
) {
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
for (plane, len) in [
(&mut *y_plane, w * h),
(&mut *cb_plane, cw * ch),
(&mut *cr_plane, cw * ch),
] {
if plane.len() < len {
plane.resize(len, 0);
}
plane.truncate(len);
}
luma_rows(&pixels[..w * h * channels], channels, y_plane);
chroma_rows(pixels, w, h, channels, cb_plane, cr_plane);
}
pub(crate) fn luma_rows(pixels: &[u8], channels: usize, y_plane: &mut [u16]) {
#[cfg(target_arch = "aarch64")]
if crate::yuv::neon() {
return unsafe { neon_enc::luma_rows(pixels, channels, y_plane) };
}
#[cfg(target_arch = "x86_64")]
if avx2_enc::detect() {
return unsafe { avx2_enc::luma_rows(pixels, channels, y_plane) };
}
luma_rows_scalar(pixels, channels, y_plane);
}
pub(super) fn luma_rows_scalar(pixels: &[u8], channels: usize, y_plane: &mut [u16]) {
for (px, y) in pixels.chunks_exact(channels).zip(y_plane.iter_mut()) {
*y = luma_px(px[0], px[1], px[2]);
}
}
#[inline]
pub(super) fn luma_px(r: u8, g: u8, b: u8) -> u16 {
let (r, g, b) = (r as u32, g as u32, b as u32);
(((1225 * r + 2404 * g + 467 * b) * 1023 + 522_240) / 1_044_480) as u16
}
pub(super) fn chroma_rows(
pixels: &[u8],
w: usize,
h: usize,
channels: usize,
cb_plane: &mut [u16],
cr_plane: &mut [u16],
) {
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
let row_bytes = w * channels;
for cy in 0..ch {
let y0 = cy * 2;
let row0 = &pixels[y0 * row_bytes..][..row_bytes];
let row1 = (y0 + 1 < h).then(|| &pixels[(y0 + 1) * row_bytes..][..row_bytes]);
chroma_row_pair(
row0,
row1,
w,
channels,
&mut cb_plane[cy * cw..][..cw],
&mut cr_plane[cy * cw..][..cw],
);
}
}
pub(crate) fn chroma_row_pair(
row0: &[u8],
row1: Option<&[u8]>,
w: usize,
channels: usize,
cb_row: &mut [u16],
cr_row: &mut [u16],
) {
if let Some(row1) = row1 {
#[cfg(target_arch = "aarch64")]
if crate::yuv::neon() {
return unsafe { neon_enc::chroma_row_pair(row0, row1, w, channels, cb_row, cr_row) };
}
#[cfg(target_arch = "x86_64")]
if avx2_enc::detect() {
return unsafe { avx2_enc::chroma_row_pair(row0, row1, w, channels, cb_row, cr_row) };
}
}
for cx in 0..w.div_ceil(2) {
let (cb, cr) = chroma_block_rows(row0, row1, channels, cx, w);
cb_row[cx] = cb;
cr_row[cx] = cr;
}
}
#[inline]
pub(super) fn chroma_block_rows(
row0: &[u8],
row1: Option<&[u8]>,
channels: usize,
cx: usize,
w: usize,
) -> (u16, u16) {
let (mut rs, mut gs, mut bs, mut n) = (0u32, 0u32, 0u32, 0u32);
for row in [Some(row0), row1].into_iter().flatten() {
for dx in 0..2 {
let x = cx * 2 + dx;
if x < w {
let p = x * channels;
rs += row[p] as u32;
gs += row[p + 1] as u32;
bs += row[p + 2] as u32;
n += 1;
}
}
}
let (r, g, b) = (
rs as f32 / n as f32,
gs as f32 / n as f32,
bs as f32 / n as f32,
);
let y = 0.299 * r + 0.587 * g + 0.114 * b;
let cb = (b - y) * (0.5 / (1.0 - 0.114)) * (1023.0 / 255.0) + 512.0;
let cr = (r - y) * (0.5 / (1.0 - 0.299)) * (1023.0 / 255.0) + 512.0;
(
(cb.round() as i32).clamp(0, 1023) as u16,
(cr.round() as i32).clamp(0, 1023) as u16,
)
}
#[cfg(target_arch = "aarch64")]
#[allow(unused_unsafe)] pub(super) mod neon_enc {
use std::arch::aarch64::*;
#[inline]
unsafe fn load8(p: *const u8, channels: usize) -> (uint8x8_t, uint8x8_t, uint8x8_t) {
unsafe {
if channels == 3 {
let v = vld3_u8(p);
(v.0, v.1, v.2)
} else {
let v = vld4_u8(p);
(v.0, v.1, v.2)
}
}
}
#[inline]
unsafe fn luma4(r: uint16x4_t, g: uint16x4_t, b: uint16x4_t) -> uint16x4_t {
unsafe {
let acc = vmlal_n_u16(vmlal_n_u16(vmull_n_u16(r, 1225), g, 2404), b, 467);
let acc = vaddq_u32(vmulq_n_u32(acc, 1023), vdupq_n_u32(522_240));
let t = vshrq_n_u32::<12>(acc);
let lo = vshrq_n_u64::<31>(vmull_n_u32(vget_low_u32(t), 8_421_505));
let hi = vshrq_n_u64::<31>(vmull_n_u32(vget_high_u32(t), 8_421_505));
vmovn_u32(vcombine_u32(vmovn_u64(lo), vmovn_u64(hi)))
}
}
#[target_feature(enable = "neon")]
pub(in crate::avif) unsafe fn luma_rows(pixels: &[u8], channels: usize, y_plane: &mut [u16]) {
unsafe {
let n = y_plane.len();
let mut i = 0;
while i + 8 <= n {
let (r, g, b) = load8(pixels.as_ptr().add(i * channels), channels);
let (r, g, b) = (vmovl_u8(r), vmovl_u8(g), vmovl_u8(b));
let y = vcombine_u16(
luma4(vget_low_u16(r), vget_low_u16(g), vget_low_u16(b)),
luma4(vget_high_u16(r), vget_high_u16(g), vget_high_u16(b)),
);
vst1q_u16(y_plane.as_mut_ptr().add(i), y);
i += 8;
}
for (j, y) in y_plane.iter_mut().enumerate().skip(i) {
let p = j * channels;
*y = super::luma_px(pixels[p], pixels[p + 1], pixels[p + 2]);
}
}
}
#[target_feature(enable = "neon")]
pub(in crate::avif) unsafe fn chroma_row_pair(
row0: &[u8],
row1: &[u8],
w: usize,
channels: usize,
cb_row: &mut [u16],
cr_row: &mut [u16],
) {
const CB1: f32 = 0.5 / (1.0 - 0.114);
const CR1: f32 = 0.5 / (1.0 - 0.299);
const SCALE: f32 = 1023.0 / 255.0;
unsafe {
let cw = w.div_ceil(2);
let four = vdupq_n_f32(4.0);
let v512 = vdupq_n_f32(512.0);
let vmax = vdupq_n_s32(1023);
let mut cx = 0usize;
while cx * 2 + 8 <= w {
let p0 = row0.as_ptr().add(cx * 2 * channels);
let p1 = row1.as_ptr().add(cx * 2 * channels);
let (r0, g0, b0) = load8(p0, channels);
let (r1, g1, b1) = load8(p1, channels);
let rs = vadd_u16(vpaddl_u8(r0), vpaddl_u8(r1));
let gs = vadd_u16(vpaddl_u8(g0), vpaddl_u8(g1));
let bs = vadd_u16(vpaddl_u8(b0), vpaddl_u8(b1));
let r = vdivq_f32(vcvtq_f32_u32(vmovl_u16(rs)), four);
let g = vdivq_f32(vcvtq_f32_u32(vmovl_u16(gs)), four);
let b = vdivq_f32(vcvtq_f32_u32(vmovl_u16(bs)), four);
let y = vaddq_f32(
vaddq_f32(vmulq_n_f32(r, 0.299), vmulq_n_f32(g, 0.587)),
vmulq_n_f32(b, 0.114),
);
let cb = vaddq_f32(vmulq_n_f32(vmulq_n_f32(vsubq_f32(b, y), CB1), SCALE), v512);
let cr = vaddq_f32(vmulq_n_f32(vmulq_n_f32(vsubq_f32(r, y), CR1), SCALE), v512);
let cb = vqmovun_s32(vminq_s32(vcvtaq_s32_f32(cb), vmax));
let cr = vqmovun_s32(vminq_s32(vcvtaq_s32_f32(cr), vmax));
vst1_u16(cb_row.as_mut_ptr().add(cx), cb);
vst1_u16(cr_row.as_mut_ptr().add(cx), cr);
cx += 4;
}
for cx in cx..cw {
let (cb, cr) = super::chroma_block_rows(row0, Some(row1), channels, cx, w);
cb_row[cx] = cb;
cr_row[cx] = cr;
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[allow(unused_unsafe)]
pub(super) mod avx2_enc {
use std::arch::x86_64::*;
#[inline]
pub(in crate::avif) fn detect() -> bool {
is_x86_feature_detected!("avx2")
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn load16(p: *const u8, channels: usize) -> (__m128i, __m128i, __m128i) {
unsafe {
if channels == 3 {
let x0 = _mm_loadu_si128(p.cast()); let x1 = _mm_loadu_si128(p.add(16).cast()); let x2 = _mm_loadu_si128(p.add(32).cast()); let z = -1i8;
let r = _mm_or_si128(
_mm_or_si128(
_mm_shuffle_epi8(
x0,
_mm_setr_epi8(0, 3, 6, 9, 12, 15, z, z, z, z, z, z, z, z, z, z),
),
_mm_shuffle_epi8(
x1,
_mm_setr_epi8(z, z, z, z, z, z, 2, 5, 8, 11, 14, z, z, z, z, z),
),
),
_mm_shuffle_epi8(
x2,
_mm_setr_epi8(z, z, z, z, z, z, z, z, z, z, z, 1, 4, 7, 10, 13),
),
);
let g = _mm_or_si128(
_mm_or_si128(
_mm_shuffle_epi8(
x0,
_mm_setr_epi8(1, 4, 7, 10, 13, z, z, z, z, z, z, z, z, z, z, z),
),
_mm_shuffle_epi8(
x1,
_mm_setr_epi8(z, z, z, z, z, 0, 3, 6, 9, 12, 15, z, z, z, z, z),
),
),
_mm_shuffle_epi8(
x2,
_mm_setr_epi8(z, z, z, z, z, z, z, z, z, z, z, 2, 5, 8, 11, 14),
),
);
let b = _mm_or_si128(
_mm_or_si128(
_mm_shuffle_epi8(
x0,
_mm_setr_epi8(2, 5, 8, 11, 14, z, z, z, z, z, z, z, z, z, z, z),
),
_mm_shuffle_epi8(
x1,
_mm_setr_epi8(z, z, z, z, z, 1, 4, 7, 10, 13, z, z, z, z, z, z),
),
),
_mm_shuffle_epi8(
x2,
_mm_setr_epi8(z, z, z, z, z, z, z, z, z, z, 0, 3, 6, 9, 12, 15),
),
);
(r, g, b)
} else {
let x0 = _mm_loadu_si128(p.cast());
let x1 = _mm_loadu_si128(p.add(16).cast());
let x2 = _mm_loadu_si128(p.add(32).cast());
let x3 = _mm_loadu_si128(p.add(48).cast());
let z = -1i8;
let pick = |off: i8| -> [__m128i; 4] {
let m = |s: i8| unsafe {
_mm_setr_epi8(
if s == 0 { off } else { z },
if s == 0 { off + 4 } else { z },
if s == 0 { off + 8 } else { z },
if s == 0 { off + 12 } else { z },
if s == 1 { off } else { z },
if s == 1 { off + 4 } else { z },
if s == 1 { off + 8 } else { z },
if s == 1 { off + 12 } else { z },
if s == 2 { off } else { z },
if s == 2 { off + 4 } else { z },
if s == 2 { off + 8 } else { z },
if s == 2 { off + 12 } else { z },
if s == 3 { off } else { z },
if s == 3 { off + 4 } else { z },
if s == 3 { off + 8 } else { z },
if s == 3 { off + 12 } else { z },
)
};
[m(0), m(1), m(2), m(3)]
};
let gather = |off: i8| unsafe {
let m = pick(off);
_mm_or_si128(
_mm_or_si128(_mm_shuffle_epi8(x0, m[0]), _mm_shuffle_epi8(x1, m[1])),
_mm_or_si128(_mm_shuffle_epi8(x2, m[2]), _mm_shuffle_epi8(x3, m[3])),
)
};
(gather(0), gather(1), gather(2))
}
}
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn luma8(r: __m128i, g: __m128i, b: __m128i) -> __m128i {
unsafe {
let r = _mm256_cvtepu16_epi32(r);
let g = _mm256_cvtepu16_epi32(g);
let b = _mm256_cvtepu16_epi32(b);
let acc = _mm256_add_epi32(
_mm256_add_epi32(
_mm256_mullo_epi32(r, _mm256_set1_epi32(1225)),
_mm256_mullo_epi32(g, _mm256_set1_epi32(2404)),
),
_mm256_mullo_epi32(b, _mm256_set1_epi32(467)),
);
let acc = _mm256_add_epi32(
_mm256_mullo_epi32(acc, _mm256_set1_epi32(1023)),
_mm256_set1_epi32(522_240),
);
let t = _mm256_srli_epi32::<12>(acc);
let m = _mm256_set1_epi64x(8_421_505);
let even = _mm256_srli_epi64::<31>(_mm256_mul_epu32(t, m));
let odd = _mm256_srli_epi64::<31>(_mm256_mul_epu32(_mm256_srli_epi64::<32>(t), m));
let y32 = _mm256_or_si256(even, _mm256_slli_epi64::<32>(odd));
let packed = _mm256_packus_epi32(y32, y32);
let packed = _mm256_permute4x64_epi64::<0b11_01_10_00>(packed);
_mm256_castsi256_si128(packed)
}
}
#[target_feature(enable = "avx2")]
pub(in crate::avif) unsafe fn luma_rows(pixels: &[u8], channels: usize, y_plane: &mut [u16]) {
unsafe {
let n = y_plane.len();
let mut i = 0;
while i + 16 <= n {
let (r, g, b) = load16(pixels.as_ptr().add(i * channels), channels);
let lo = luma8(
_mm_cvtepu8_epi16(r),
_mm_cvtepu8_epi16(g),
_mm_cvtepu8_epi16(b),
);
let hi = luma8(
_mm_cvtepu8_epi16(_mm_srli_si128::<8>(r)),
_mm_cvtepu8_epi16(_mm_srli_si128::<8>(g)),
_mm_cvtepu8_epi16(_mm_srli_si128::<8>(b)),
);
_mm_storeu_si128(y_plane.as_mut_ptr().add(i).cast(), lo);
_mm_storeu_si128(y_plane.as_mut_ptr().add(i + 8).cast(), hi);
i += 16;
}
for (j, y) in y_plane.iter_mut().enumerate().skip(i) {
let p = j * channels;
*y = super::luma_px(pixels[p], pixels[p + 1], pixels[p + 2]);
}
}
}
#[target_feature(enable = "avx2")]
pub(in crate::avif) unsafe fn chroma_row_pair(
row0: &[u8],
row1: &[u8],
w: usize,
channels: usize,
cb_row: &mut [u16],
cr_row: &mut [u16],
) {
const CB1: f32 = 0.5 / (1.0 - 0.114);
const CR1: f32 = 0.5 / (1.0 - 0.299);
const SCALE: f32 = 1023.0 / 255.0;
unsafe {
let cw = w.div_ceil(2);
let ones = _mm_set1_epi8(1);
let four = _mm256_set1_ps(4.0);
let half = _mm256_set1_ps(0.5);
let v512 = _mm256_set1_ps(512.0);
let vmax = _mm256_set1_epi32(1023);
let zero = _mm256_setzero_si256();
let pair16 = |x: __m128i| unsafe { _mm_maddubs_epi16(x, ones) };
let mut cx = 0usize;
while cx * 2 + 16 <= w {
let p0 = row0.as_ptr().add(cx * 2 * channels);
let p1 = row1.as_ptr().add(cx * 2 * channels);
let (r0, g0, b0) = load16(p0, channels);
let (r1, g1, b1) = load16(p1, channels);
let rs = _mm_add_epi16(pair16(r0), pair16(r1));
let gs = _mm_add_epi16(pair16(g0), pair16(g1));
let bs = _mm_add_epi16(pair16(b0), pair16(b1));
let to_f = |s: __m128i| unsafe {
_mm256_div_ps(_mm256_cvtepi32_ps(_mm256_cvtepu16_epi32(s)), four)
};
let (r, g, b) = (to_f(rs), to_f(gs), to_f(bs));
let y = _mm256_add_ps(
_mm256_add_ps(
_mm256_mul_ps(r, _mm256_set1_ps(0.299)),
_mm256_mul_ps(g, _mm256_set1_ps(0.587)),
),
_mm256_mul_ps(b, _mm256_set1_ps(0.114)),
);
let chan = |base: __m256, c1: f32| unsafe {
let v = _mm256_add_ps(
_mm256_mul_ps(
_mm256_mul_ps(_mm256_sub_ps(base, y), _mm256_set1_ps(c1)),
_mm256_set1_ps(SCALE),
),
v512,
);
let i32s = _mm256_cvttps_epi32(_mm256_floor_ps(_mm256_add_ps(v, half)));
let i32s = _mm256_min_epi32(_mm256_max_epi32(i32s, zero), vmax);
let p = _mm256_packus_epi32(i32s, i32s);
_mm256_castsi256_si128(_mm256_permute4x64_epi64::<0b11_01_10_00>(p))
};
let cb = chan(b, CB1);
let cr = chan(r, CR1);
_mm_storeu_si128(cb_row.as_mut_ptr().add(cx).cast(), cb);
_mm_storeu_si128(cr_row.as_mut_ptr().add(cx).cast(), cr);
cx += 8;
}
for cx in cx..cw {
let (cb, cr) = super::chroma_block_rows(row0, Some(row1), channels, cx, w);
cb_row[cx] = cb;
cr_row[cx] = cr;
}
}
}
}