use super::*;
pub fn encode_bc7_mode6(pixels: [[u8; 4]; 16], out: &mut [u8]) {
let block: &mut [u8; 16] = match (&mut out[..16]).try_into() {
Ok(b) => b,
Err(_) => return,
};
encode_bc7_mode6_scored(pixels, block);
}
pub(crate) fn encode_bc7_mode6_scored(pixels: [[u8; 4]; 16], out: &mut [u8; 16]) -> i64 {
let (bits6, err6, a_lo, a_hi) = encode_bc7_mode6_inner(&pixels);
let mut best_bits = bits6;
let mut best_err = err6;
if err6 > 0 && a_hi - a_lo > 2 {
let seeds = ColorSeeds::new(&pixels);
if let Some((bits5, err5)) = try_bc7_mode5(&pixels, 0, &seeds, best_err) {
if err5 < best_err {
best_err = err5;
best_bits = bits5;
}
}
if best_err > 0 {
if let Some((bits4, err4)) = try_bc7_mode4(&pixels, &seeds, best_err) {
if err4 < best_err {
best_err = err4;
best_bits = bits4;
}
}
}
}
if best_err > bc7_m1_min_err().max(4) && best_err <= 1024 && a_lo == 255 {
if let Some((bits1, err1)) = m1::try_bc7_mode1(&pixels, best_err) {
if err1 < best_err {
best_err = err1;
best_bits = bits1;
}
}
}
if best_err > 0 {
let (mx, mn) = channel_minmax_rgba(&pixels);
let spans: [i32; 4] = [
(mx[0] - mn[0]) as i32,
(mx[1] - mn[1]) as i32,
(mx[2] - mn[2]) as i32,
(mx[3] - mn[3]) as i32,
];
for rot in 1u8..=3 {
let c = rot as usize - 1; let rest = spans[3].max(spans[(c + 1) % 3]).max(spans[(c + 2) % 3]);
if spans[c] > 2 && spans[c] > rest {
let mut rotated = pixels;
for p in rotated.iter_mut() {
p.swap(c, 3);
}
let rseeds = ColorSeeds::new(&rotated);
if let Some((bits5, err5)) = try_bc7_mode5(&rotated, rot, &rseeds, best_err) {
if err5 < best_err {
best_err = err5;
best_bits = bits5;
}
}
}
}
}
out.copy_from_slice(&best_bits);
best_err
}
pub(super) const W2: [u32; 4] = [0, 21, 43, 64];
pub(super) const W3: [u32; 8] = [0, 9, 18, 27, 37, 46, 55, 64];
#[inline]
pub(super) fn unquant7(v: u8) -> u8 {
(v << 1) | (v >> 6)
}
pub(super) struct ColorSeeds {
extrema: ([u8; 3], [u8; 3]),
cminmax: ([u8; 3], [u8; 3]),
pca: std::cell::OnceCell<Option<([u8; 3], [u8; 3])>>,
}
impl ColorSeeds {
pub(super) fn new(pixels: &[[u8; 4]; 16]) -> Self {
Self {
extrema: extrema_opaque(pixels),
cminmax: channel_minmax_rgb(pixels),
pca: std::cell::OnceCell::new(),
}
}
fn pca(&self, pixels: &[[u8; 4]; 16]) -> Option<([u8; 3], [u8; 3])> {
*self.pca.get_or_init(|| pca_extremes_rgb(pixels))
}
}
pub(super) fn try_bc7_mode5(
pixels: &[[u8; 4]; 16],
rotation: u8,
seeds: &ColorSeeds,
err_limit: i64,
) -> Option<([u8; 16], i64)> {
let alpha: [u8; 16] = super::alpha::alpha_channel(pixels);
let (a0, a1) = super::alpha::sample_minmax(&alpha);
let (a_ep0, a_ep1, a_idx, a_err) = fit_alpha_mode5(&alpha, a1, a0);
if a_err as i64 >= err_limit {
return None;
}
let (mut best_c, mut c_err) = fit_color_mode5(pixels, seeds.extrema.0, seeds.extrema.1);
{
if seeds.cminmax != seeds.extrema {
let cand = fit_color_mode5(pixels, seeds.cminmax.0, seeds.cminmax.1);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
if let Some((pa, pb)) = seeds.pca(pixels) {
if (pa, pb) != seeds.extrema && (pa, pb) != seeds.cminmax {
let cand = fit_color_mode5(pixels, pa, pb);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
}
if let Some((e0, e1)) = ls_endpoints_mode5(pixels, &best_c.2) {
let cand = fit_color_mode5(pixels, e0, e1);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
}
let (c_ep0, c_ep1, c_idx) = best_c;
let err = c_err as i64 + a_err as i64;
Some((
pack_bc7_mode5(rotation, c_ep0, c_ep1, a_ep0, a_ep1, &c_idx, &a_idx),
err,
))
}
pub(super) fn try_bc7_mode4(
pixels: &[[u8; 4]; 16],
seeds: &ColorSeeds,
err_limit: i64,
) -> Option<([u8; 16], i64)> {
let (mut best_c, mut c_err) = fit_color_mode4(pixels, seeds.extrema.0, seeds.extrema.1);
{
if seeds.cminmax != seeds.extrema {
let cand = fit_color_mode4(pixels, seeds.cminmax.0, seeds.cminmax.1);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
if let Some((pa, pb)) = seeds.pca(pixels) {
if (pa, pb) != seeds.extrema && (pa, pb) != seeds.cminmax {
let cand = fit_color_mode4(pixels, pa, pb);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
}
if let Some((e0, e1)) = ls_endpoints_mode5(pixels, &best_c.2) {
let cand = fit_color_mode4(pixels, e0, e1);
if cand.1 < c_err {
c_err = cand.1;
best_c = cand.0;
}
}
}
let (c_ep0, c_ep1, c_idx) = best_c;
if c_err as i64 >= err_limit {
return None;
}
let alpha: [u8; 16] = super::alpha::alpha_channel(pixels);
let (lo, hi) = super::alpha::sample_minmax(&alpha);
let (mut a_ep0, mut a_ep1, mut a_idx, mut a_err) = score_alpha_mode4(&alpha, hi >> 2, lo >> 2);
if a_err > 0 {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
let vectorised = simd::has_avx2();
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
let vectorised = false;
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if vectorised {
let (q0, q1, e) =
simd::alpha_nbhd_avx2::<8>(&alpha, hi >> 2, lo >> 2, 63, a_err);
if e < a_err {
(a_ep0, a_ep1, a_idx, a_err) = score_alpha_mode4(&alpha, q0, q1);
}
}
if !vectorised {
for d0 in -2i32..=2 {
for d1 in -2i32..=2 {
if d0 == 0 && d1 == 0 {
continue;
}
let q0 = ((hi >> 2) as i32 + d0).clamp(0, 63) as u8;
let q1 = ((lo >> 2) as i32 + d1).clamp(0, 63) as u8;
let cand = score_alpha_mode4(&alpha, q0, q1);
if cand.3 < a_err {
(a_ep0, a_ep1, a_idx, a_err) = cand;
}
}
}
}
}
let err = c_err as i64 + a_err as i64;
Some((pack_bc7_mode4(c_ep0, c_ep1, a_ep0, a_ep1, &c_idx, &a_idx), err))
}
#[inline]
pub(super) fn unquant5(v: u8) -> u8 {
(v << 3) | (v >> 2)
}
#[inline]
pub(super) fn unquant6(v: u8) -> u8 {
(v << 2) | (v >> 4)
}
#[allow(clippy::type_complexity)]
#[inline]
fn fit_indices_rgb4(pixels: &[[u8; 4]; 16], pal: &[[u8; 3]; 4]) -> ([u8; 16], i32) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
if let Some((table, err)) = simd::bc1_fit_4color_avx2(pixels, pal, i32::MAX) {
return (
core::array::from_fn(|i| ((table >> (2 * i)) & 3) as u8),
err,
);
}
}
fit_indices_rgb4_scalar(pixels, pal)
}
#[cold]
#[inline(never)]
fn fit_indices_rgb4_scalar(pixels: &[[u8; 4]; 16], pal: &[[u8; 3]; 4]) -> ([u8; 16], i32) {
let mut idx = [0u8; 16];
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let mut bi = 0u8;
let mut be = i32::MAX;
for (j, pc) in pal.iter().enumerate() {
let e = sqr_rgb([p[0], p[1], p[2]], *pc);
if e < be {
be = e;
bi = j as u8;
}
}
idx[i] = bi;
err += be;
}
(idx, err)
}
#[inline]
fn fit_indices_alpha8(alpha: &[u8; 16], pal: &[u8; 8]) -> ([u8; 16], i32) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::alpha_fit_avx2(pal, alpha);
}
fit_indices_alpha8_scalar(alpha, pal)
}
#[cold]
#[inline(never)]
fn fit_indices_alpha8_scalar(alpha: &[u8; 16], pal: &[u8; 8]) -> ([u8; 16], i32) {
let mut idx = [0u8; 16];
let mut err = 0i32;
for (i, &a) in alpha.iter().enumerate() {
let mut bi = 0u8;
let mut be = i32::MAX;
for (j, &p) in pal.iter().enumerate() {
let d = (p as i32 - a as i32).pow(2);
if d < be {
be = d;
bi = j as u8;
}
}
idx[i] = bi;
err += be;
}
(idx, err)
}
#[inline]
fn fit_indices_alpha4(alpha: &[u8; 16], pal: &[u8; 4]) -> ([u8; 16], i32) {
let pal8 = [pal[0], pal[1], pal[2], pal[3], pal[0], pal[0], pal[0], pal[0]];
fit_indices_alpha8(alpha, &pal8)
}
pub(super) fn fit_color_mode4(
pixels: &[[u8; 4]; 16],
e0: [u8; 3],
e1: [u8; 3],
) -> (([u8; 3], [u8; 3], [u8; 16]), i32) {
let mut q0 = [0u8; 3];
let mut q1 = [0u8; 3];
for c in 0..3 {
q0[c] = e0[c] >> 3;
q1[c] = e1[c] >> 3;
}
let c0 = [unquant5(q0[0]), unquant5(q0[1]), unquant5(q0[2])];
let c1 = [unquant5(q1[0]), unquant5(q1[1]), unquant5(q1[2])];
let mut pal = [[0u8; 3]; 4];
for (k, &w) in W2.iter().enumerate() {
for c in 0..3 {
pal[k][c] = (((64 - w) * c0[c] as u32 + w * c1[c] as u32 + 32) / 64) as u8;
}
}
let (mut idx, err) = fit_indices_rgb4(pixels, &pal);
if idx[0] >= 2 {
std::mem::swap(&mut q0, &mut q1);
for v in idx.iter_mut() {
*v = 3 - *v;
}
}
((q0, q1, idx), err)
}
pub(super) fn score_alpha_mode4(alpha: &[u8; 16], q0: u8, q1: u8) -> (u8, u8, [u8; 16], i32) {
let c0 = unquant6(q0);
let c1 = unquant6(q1);
let mut pal = [0u8; 8];
for (k, &w) in W3.iter().enumerate() {
pal[k] = (((64 - w) * c0 as u32 + w * c1 as u32 + 32) / 64) as u8;
}
let (mut idx, err) = fit_indices_alpha8(alpha, &pal);
let (mut r0, mut r1) = (q0, q1);
if idx[0] >= 4 {
std::mem::swap(&mut r0, &mut r1);
for v in idx.iter_mut() {
*v = 7 - *v;
}
}
(r0, r1, idx, err)
}
pub(super) fn pack_bc7_mode4(
c0: [u8; 3],
c1: [u8; 3],
a0: u8,
a1: u8,
c_idx: &[u8; 16],
a_idx: &[u8; 16],
) -> [u8; 16] {
let mut bw = BitWriter::default();
for _ in 0..4 {
bw.write_bits(0, 1);
}
bw.write_bits(1, 1);
bw.write_bits(0, 2); bw.write_bits(0, 1); for c in 0..3 {
bw.write_bits(c0[c] as u32, 5);
bw.write_bits(c1[c] as u32, 5);
}
bw.write_bits(a0 as u32, 6);
bw.write_bits(a1 as u32, 6);
bw.write_bits(c_idx[0] as u32, 1);
for &v in &c_idx[1..] {
bw.write_bits(v as u32, 2);
}
bw.write_bits(a_idx[0] as u32, 2);
for &v in &a_idx[1..] {
bw.write_bits(v as u32, 3);
}
bw.into_array()
}
#[allow(clippy::type_complexity)]
pub(super) fn fit_color_mode5(
pixels: &[[u8; 4]; 16],
e0: [u8; 3],
e1: [u8; 3],
) -> (([u8; 3], [u8; 3], [u8; 16]), i32) {
let mut q0 = [0u8; 3];
let mut q1 = [0u8; 3];
for c in 0..3 {
q0[c] = e0[c] >> 1;
q1[c] = e1[c] >> 1;
}
let pal = palette_mode5_color(q0, q1);
let (mut idx, err) = fit_indices_rgb4(pixels, &pal);
if idx[0] >= 2 {
std::mem::swap(&mut q0, &mut q1);
for v in idx.iter_mut() {
*v = 3 - *v;
}
}
((q0, q1, idx), err)
}
pub(super) fn palette_mode5_color(q0: [u8; 3], q1: [u8; 3]) -> [[u8; 3]; 4] {
let c0 = [unquant7(q0[0]), unquant7(q0[1]), unquant7(q0[2])];
let c1 = [unquant7(q1[0]), unquant7(q1[1]), unquant7(q1[2])];
let mut pal = [[0u8; 3]; 4];
for (k, &w) in W2.iter().enumerate() {
for c in 0..3 {
pal[k][c] = (((64 - w) * c0[c] as u32 + w * c1[c] as u32 + 32) / 64) as u8;
}
}
pal
}
pub(super) fn ls_endpoints_mode5(pixels: &[[u8; 4]; 16], indices: &[u8; 16]) -> Option<([u8; 3], [u8; 3])> {
const WF: [f32; 4] = [0.0, 21.0 / 64.0, 43.0 / 64.0, 1.0];
let mut a00 = 0f32;
let mut a01 = 0f32;
let mut a11 = 0f32;
let mut b0 = [0f32; 3];
let mut b1 = [0f32; 3];
for (i, p) in pixels.iter().enumerate() {
debug_assert!(indices[i] < 4);
let w = WF[(indices[i] & 3) as usize];
let u = 1.0 - w;
a00 += u * u;
a01 += u * w;
a11 += w * w;
for c in 0..3 {
let x = p[c] as f32;
b0[c] += u * x;
b1[c] += w * x;
}
}
let det = a00 * a11 - a01 * a01;
if det.abs() < 1e-4 {
return None;
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (q0, q1) = simd::bc1_ls_solve(
[b0[0], b0[1], b0[2], 0.0],
[b1[0], b1[1], b1[2], 0.0],
a00,
a01,
a11,
det,
);
return Some(([q0[0], q0[1], q0[2]], [q1[0], q1[1], q1[2]]));
}
Some(ls_solve_mode5_scalar(b0, b1, a00, a01, a11, det))
}
#[cold]
#[inline(never)]
fn ls_solve_mode5_scalar(
b0: [f32; 3],
b1: [f32; 3],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> ([u8; 3], [u8; 3]) {
let mut e0 = [0u8; 3];
let mut e1 = [0u8; 3];
for c in 0..3 {
e0[c] = super::round_clamp_u8((a11 * b0[c] - a01 * b1[c]) / det);
e1[c] = super::round_clamp_u8((a00 * b1[c] - a01 * b0[c]) / det);
}
(e0, e1)
}
pub(super) fn fit_alpha_mode5(alpha: &[u8; 16], hi: u8, lo: u8) -> (u8, u8, [u8; 16], i32) {
let (mut e0, mut e1, mut idx, mut err) = score_alpha_mode5(alpha, hi, lo);
if err > 0 && hi != lo {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (c0, c1, e) = simd::alpha_nbhd_avx2::<4>(alpha, hi, lo, 255, err);
if e < err {
(e0, e1, idx, err) = score_alpha_mode5(alpha, c0, c1);
}
return (e0, e1, idx, err);
}
for d0 in -2i32..=2 {
for d1 in -2i32..=2 {
if d0 == 0 && d1 == 0 {
continue;
}
let c0 = (hi as i32 + d0).clamp(0, 255) as u8;
let c1 = (lo as i32 + d1).clamp(0, 255) as u8;
let cand = score_alpha_mode5(alpha, c0, c1);
if cand.3 < err {
(e0, e1, idx, err) = cand;
}
}
}
}
(e0, e1, idx, err)
}
pub(super) fn score_alpha_mode5(alpha: &[u8; 16], c0: u8, c1: u8) -> (u8, u8, [u8; 16], i32) {
let mut pal = [0u8; 4];
for (k, &w) in W2.iter().enumerate() {
pal[k] = (((64 - w) * c0 as u32 + w * c1 as u32 + 32) / 64) as u8;
}
let (mut idx, err) = fit_indices_alpha4(alpha, &pal);
let (mut r0, mut r1) = (c0, c1);
if idx[0] >= 2 {
std::mem::swap(&mut r0, &mut r1);
for v in idx.iter_mut() {
*v = 3 - *v;
}
}
(r0, r1, idx, err)
}
pub(super) fn pack_bc7_mode5(
rotation: u8,
c0: [u8; 3],
c1: [u8; 3],
a0: u8,
a1: u8,
c_idx: &[u8; 16],
a_idx: &[u8; 16],
) -> [u8; 16] {
let mut bw = BitWriter::default();
for _ in 0..5 {
bw.write_bits(0, 1);
}
bw.write_bits(1, 1);
bw.write_bits(rotation as u32, 2);
for c in 0..3 {
bw.write_bits(c0[c] as u32, 7);
bw.write_bits(c1[c] as u32, 7);
}
bw.write_bits(a0 as u32, 8);
bw.write_bits(a1 as u32, 8);
bw.write_bits(c_idx[0] as u32, 1);
for &v in &c_idx[1..] {
bw.write_bits(v as u32, 2);
}
bw.write_bits(a_idx[0] as u32, 1);
for &v in &a_idx[1..] {
bw.write_bits(v as u32, 2);
}
bw.into_array()
}
pub(super) fn encode_bc7_mode6_inner(pixels: &[[u8; 4]; 16]) -> ([u8; 16], i64, u8, u8) {
let mut best_bits = [0u8; 16];
let mut best_err = i64::MAX;
let ex = extrema_rgba(pixels);
let cm = channel_minmax_rgba(pixels);
let span: i32 = (0..4).map(|c| (cm.0[c] - cm.1[c]) as i32).sum();
let mut best_seed = ex;
let mut have = false;
let mut best_fit: Option<Mode6Fit> = None;
let (mut seeds, mut n_seeds) = bc7_mode6_seeds_base(ex, cm);
let mut tried = 0usize;
loop {
for &(ep0, ep1) in seeds.iter().take(n_seeds).skip(tried) {
let f = mode6_base(pixels, ep0, ep1);
if f.err < best_err {
best_err = f.err;
let (bits, _) = f.pack();
best_bits = bits;
best_seed = (ep0, ep1);
best_fit = Some(f);
have = true;
}
}
tried = n_seeds;
if best_err <= SEED_EXTRA_ERR_GATE {
break;
}
bc7_mode6_seeds_extra(pixels, ex, span, &mut seeds, &mut n_seeds);
if n_seeds == tried {
break;
}
}
let do_ls = span > 8;
if do_ls {
if let Some(base) = best_fit {
let (bits, err) = mode6_refine(pixels, base).pack();
if err < best_err {
}
if err <= best_err {
best_bits = bits;
best_err = err;
have = true;
}
}
}
if !have {
if let Some((bits, err)) = try_bc7_mode6(pixels, best_seed.0, best_seed.1, true) {
best_bits = bits;
best_err = err;
}
}
(best_bits, best_err, cm.1[3], cm.0[3])
}
pub(super) type Seed = ([u8; 4], [u8; 4]);
#[inline]
pub(super) fn push_seed(seeds: &mut [Seed; 5], n: &mut usize, s: Seed) {
debug_assert!(*n < seeds.len());
if *n >= seeds.len() {
return;
}
for seed in seeds.iter().take(*n) {
if *seed == s {
return;
}
}
seeds[*n] = s;
*n += 1;
}
const SEED_EXTRA_ERR_GATE: i64 = 256;
pub(super) fn bc7_mode6_seeds_base(ex: Seed, cm: Seed) -> ([Seed; 5], usize) {
let mut seeds = [([0u8; 4], [0u8; 4]); 5];
let mut n = 0usize;
push_seed(&mut seeds, &mut n, ex);
push_seed(&mut seeds, &mut n, cm);
(seeds, n)
}
pub(super) fn bc7_mode6_seeds_extra(
pixels: &[[u8; 4]; 16],
ex: Seed,
span: i32,
seeds: &mut [Seed; 5],
n: &mut usize,
) {
if span <= 16 {
return;
}
let (mx, mn) = ex;
let mut mean = [0u32; 4];
for p in pixels {
for c in 0..4 {
mean[c] += p[c] as u32;
}
}
let mean = mean.map(|v| (v / 16) as u8);
push_seed(seeds, n, (mx, mean));
push_seed(seeds, n, (mean, mn));
if span > 48 {
let mut best_d = -1i32;
let mut pa = pixels[0];
let mut pb = pixels[0];
for i in 0..16 {
for j in (i + 1)..16 {
let mut d = 0i32;
for c in 0..4 {
let t = pixels[i][c] as i32 - pixels[j][c] as i32;
d += t * t;
}
if d > best_d {
best_d = d;
pa = pixels[i];
pb = pixels[j];
}
}
}
push_seed(seeds, n, (pa, pb));
}
}
pub(super) fn channel_minmax_rgba(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::channel_minmax_avx2(pixels);
}
channel_minmax_rgba_scalar(pixels)
}
#[cold]
#[inline(never)]
fn channel_minmax_rgba_scalar(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
let mut mn = [255u8; 4];
let mut mx = [0u8; 4];
for p in pixels {
for c in 0..4 {
mn[c] = mn[c].min(p[c]);
mx[c] = mx[c].max(p[c]);
}
}
(mx, mn)
}
pub(super) const W6M: [u32; 16] = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
#[inline]
pub(super) fn palette_mode6_base(c0: [u8; 4]) -> [i32; 4] {
[
c0[0] as i32 * 64 + 32,
c0[1] as i32 * 64 + 32,
c0[2] as i32 * 64 + 32,
c0[3] as i32 * 64 + 32,
]
}
pub(super) fn palette_mode6_from_base(base: [i32; 4], c0: [u8; 4], c1: [u8; 4]) -> [[u8; 4]; 16] {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::palette_mode6_avx2(base, c0, c1);
}
let delta = [
c1[0] as i32 - c0[0] as i32,
c1[1] as i32 - c0[1] as i32,
c1[2] as i32 - c0[2] as i32,
c1[3] as i32 - c0[3] as i32,
];
let mut pal = [[0u8; 4]; 16];
for (k, &w) in W6M.iter().enumerate() {
let w = w as i32;
for c in 0..4 {
pal[k][c] = ((base[c] + w * delta[c]) >> 6) as u8;
}
}
pal
}
#[cfg(test)]
pub(super) fn palette_mode6(c0: [u8; 4], c1: [u8; 4]) -> [[u8; 4]; 16] {
palette_mode6_from_base(palette_mode6_base(c0), c0, c1)
}
#[inline]
pub(super) fn best_index_pal(px: &[u8; 4], pal: &[[u8; 4]; 16]) -> (u8, i32) {
let mut best_i = 0u8;
let mut best_e = i32::MAX;
for (k, p) in pal.iter().enumerate() {
let mut e = 0i32;
for c in 0..4 {
let d = p[c] as i32 - px[c] as i32;
e += d * d;
}
if e < best_e {
best_e = e;
best_i = k as u8;
}
}
(best_i, best_e)
}
#[inline]
pub(super) fn palette_and_fit_mode6(
pixels: &[[u8; 4]; 16],
base: [i32; 4],
c0: [u8; 4],
c1: [u8; 4],
) -> ([u8; 16], i64) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::palette_fit_mode6_avx2(pixels, base, c0, c1);
}
palette_and_fit_mode6_scalar(pixels, base, c0, c1)
}
#[cold]
#[inline(never)]
fn palette_and_fit_mode6_scalar(
pixels: &[[u8; 4]; 16],
base: [i32; 4],
c0: [u8; 4],
c1: [u8; 4],
) -> ([u8; 16], i64) {
let pal = palette_mode6_from_base(base, c0, c1);
fit_indices_mode6(pixels, &pal)
}
pub(super) fn fit_indices_mode6(pixels: &[[u8; 4]; 16], pal: &[[u8; 4]; 16]) -> ([u8; 16], i64) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::fit_indices_mode6_avx2(pixels, pal);
}
fit_indices_mode6_exhaustive(pixels, pal)
}
#[inline]
pub(super) fn fit_indices_mode6_exhaustive(pixels: &[[u8; 4]; 16], pal: &[[u8; 4]; 16]) -> ([u8; 16], i64) {
let mut indices = [0u8; 16];
let mut err = 0i64;
for (i, px) in pixels.iter().enumerate() {
let (idx, e) = best_index_pal(px, pal);
indices[i] = idx;
err += e as i64;
}
(indices, err)
}
#[derive(Clone, Copy)]
pub(super) struct Mode6Fit {
q0: [u8; 4],
p0: u8,
q1: [u8; 4],
p1: u8,
indices: [u8; 16],
err: i64,
}
impl Mode6Fit {
#[inline]
fn pack(&self) -> ([u8; 16], i64) {
(
pack_bc7_mode6(self.q0, self.p0, self.q1, self.p1, self.indices),
self.err,
)
}
}
pub(super) fn mode6_base(pixels: &[[u8; 4]; 16], ep0: [u8; 4], ep1: [u8; 4]) -> Mode6Fit {
let (mut q0, mut p0) = quantize_7p(ep0);
let (mut q1, mut p1) = quantize_7p(ep1);
let (u0, u1) = (unquantize_7p(q0, p0), unquantize_7p(q1, p1));
let (mut indices, err) = palette_and_fit_mode6(pixels, palette_mode6_base(u0), u0, u1);
if indices[0] > 7 {
std::mem::swap(&mut q0, &mut q1);
std::mem::swap(&mut p0, &mut p1);
for idx in indices.iter_mut() {
*idx = 15 - *idx;
}
}
Mode6Fit {
q0,
p0,
q1,
p1,
indices,
err,
}
}
pub(super) fn mode6_refine(pixels: &[[u8; 4]; 16], base: Mode6Fit) -> Mode6Fit {
let Some((r0, r1)) = ls_endpoints_mode6(pixels, &base.indices) else {
return base;
};
let (nq0, np0) = quantize_7p(r0);
let (nq1, np1) = quantize_7p(r1);
let (n0, n1) = (unquantize_7p(nq0, np0), unquantize_7p(nq1, np1));
let (mut nidx, nerr) = palette_and_fit_mode6(pixels, palette_mode6_base(n0), n0, n1);
let (q0, p0, q1, p1) = if nidx[0] > 7 {
for idx in nidx.iter_mut() {
*idx = 15 - *idx;
}
(nq1, np1, nq0, np0)
} else {
(nq0, np0, nq1, np1)
};
Mode6Fit {
q0,
p0,
q1,
p1,
indices: nidx,
err: nerr,
}
}
pub(super) fn try_bc7_mode6(
pixels: &[[u8; 4]; 16],
ep0: [u8; 4],
ep1: [u8; 4],
refine: bool,
) -> Option<([u8; 16], i64)> {
let f = mode6_base(pixels, ep0, ep1);
let f = if refine { mode6_refine(pixels, f) } else { f };
Some(f.pack())
}
pub(super) fn ls_endpoints_mode6(
pixels: &[[u8; 4]; 16],
indices: &[u8; 16],
) -> Option<([u8; 4], [u8; 4])> {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return ls_endpoints_mode6_pxv(&simd::ls_pixels_mode6(pixels), indices);
}
ls_endpoints_mode6_scalar(pixels, indices)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub(super) fn ls_endpoints_mode6_pxv(
pxv: &[[f32; 8]; 16],
indices: &[u8; 16],
) -> Option<([u8; 4], [u8; 4])> {
if !simd::has_avx2() {
unreachable!("ls_endpoints_mode6_pxv requires AVX2");
}
let (a, b0, b1) = simd::ls_accum_mode6(pxv, indices);
let (a00, a01, a11) = (a[0], a[1], a[2]);
let det = a00 * a11 - a01 * a01;
if det.abs() < 1e-4 {
return None;
}
Some(simd::bc1_ls_solve(b0, b1, a00, a01, a11, det))
}
#[cold]
#[inline(never)]
pub(super) fn ls_endpoints_mode6_scalar(
pixels: &[[u8; 4]; 16],
indices: &[u8; 16],
) -> Option<([u8; 4], [u8; 4])> {
const W: [f32; 16] = [
0.0, 4.0 / 64.0, 9.0 / 64.0, 13.0 / 64.0, 17.0 / 64.0, 21.0 / 64.0, 26.0 / 64.0,
30.0 / 64.0, 34.0 / 64.0, 38.0 / 64.0, 43.0 / 64.0, 47.0 / 64.0, 51.0 / 64.0, 55.0 / 64.0,
60.0 / 64.0, 1.0,
];
let mut a00 = 0.0f32;
let mut a01 = 0.0f32;
let mut a11 = 0.0f32;
let mut b0 = [0.0f32; 4];
let mut b1 = [0.0f32; 4];
for i in 0..16 {
debug_assert!(indices[i] < 16);
let w = W[(indices[i] & 15) as usize];
let u = 1.0 - w;
a00 += u * u;
a01 += u * w;
a11 += w * w;
for c in 0..4 {
let x = pixels[i][c] as f32;
b0[c] += u * x;
b1[c] += w * x;
}
}
let det = a00 * a11 - a01 * a01;
if det.abs() < 1e-4 {
return None;
}
let mut e0 = [0u8; 4];
let mut e1 = [0u8; 4];
for c in 0..4 {
let x0 = (a11 * b0[c] - a01 * b1[c]) / det;
let x1 = (a00 * b1[c] - a01 * b0[c]) / det;
e0[c] = super::round_clamp_u8(x0);
e1[c] = super::round_clamp_u8(x1);
}
Some((e0, e1))
}
pub(super) fn pack_bc7_mode6(q0: [u8; 4], p0: u8, q1: [u8; 4], p1: u8, indices: [u8; 16]) -> [u8; 16] {
let mut v: u128 = 1 << 6;
let mut pos = 7u32;
for c in 0..4 {
v |= (q0[c] as u128) << pos;
v |= (q1[c] as u128) << (pos + 7);
pos += 14;
}
v |= (p0 as u128) << pos;
v |= (p1 as u128) << (pos + 1);
pos += 2;
fn nib8(x: u64) -> u64 {
let x = (x | (x >> 4)) & 0x00FF_00FF_00FF_00FF;
let x = (x | (x >> 8)) & 0x0000_FFFF_0000_FFFF;
(x | (x >> 16)) & 0x0000_0000_FFFF_FFFF
}
let lo = u64::from_le_bytes([
indices[0], indices[1], indices[2], indices[3],
indices[4], indices[5], indices[6], indices[7],
]);
let hi = u64::from_le_bytes([
indices[8], indices[9], indices[10], indices[11],
indices[12], indices[13], indices[14], indices[15],
]);
let packed = nib8(lo) | (nib8(hi) << 32);
let _ = pos;
v |= ((packed & 0xF) as u128) << 65;
v |= ((packed >> 4) as u128) << 68;
v.to_le_bytes()
}
const fn qtab_entry(c: u8, p: u8) -> (u8, u16) {
let q0 = c >> 1; let lo = if q0 == 0 { 0 } else { q0 - 1 };
let hi = if q0 >= 127 { 127 } else { q0 + 1 };
let mut best_qi = q0;
let mut best_e = i32::MAX;
let mut cand = lo;
while cand <= hi {
let recon = ((cand as u32) << 1) | (p as u32);
let d = recon as i32 - c as i32;
let e = d * d;
if e < best_e {
best_e = e;
best_qi = cand;
}
cand += 1;
}
(best_qi, best_e as u16)
}
const fn build_qtab() -> ([[u8; 256]; 2], [[u16; 256]; 2]) {
let mut q = [[0u8; 256]; 2];
let mut e = [[0u16; 256]; 2];
let mut p = 0usize;
while p < 2 {
let mut c = 0usize;
while c < 256 {
let (qi, ei) = qtab_entry(c as u8, p as u8);
q[p][c] = qi;
e[p][c] = ei;
c += 1;
}
p += 1;
}
(q, e)
}
static QTAB: ([[u8; 256]; 2], [[u16; 256]; 2]) = build_qtab();
pub(super) fn quantize_7p(c: [u8; 4]) -> ([u8; 4], u8) {
let (qt, et) = (&QTAB.0, &QTAB.1);
let (e0, e1) = (&et[0], &et[1]);
let s0 = e0[c[0] as usize] as i32
+ e0[c[1] as usize] as i32
+ e0[c[2] as usize] as i32
+ e0[c[3] as usize] as i32;
let s1 = e1[c[0] as usize] as i32
+ e1[c[1] as usize] as i32
+ e1[c[2] as usize] as i32
+ e1[c[3] as usize] as i32;
let p = usize::from(s1 < s0);
let q = &qt[p];
(
[
q[c[0] as usize],
q[c[1] as usize],
q[c[2] as usize],
q[c[3] as usize],
],
p as u8,
)
}
#[cfg(test)]
mod qtab_tests {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[test]
fn palette_mode6_vector_matches_scalar() {
if !super::simd::has_avx2() {
return;
}
let mut state = 0x2b7e_1516_28ae_d2a6u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let (c0, c1) = match case {
0 => ([0u8; 4], [0u8; 4]),
1 => ([255u8; 4], [255u8; 4]),
2 => ([0u8; 4], [255u8; 4]),
3 => ([255u8; 4], [0u8; 4]),
_ => {
let (a, b) = (next(), next());
(
[a as u8, (a >> 8) as u8, (a >> 16) as u8, (a >> 24) as u8],
[b as u8, (b >> 8) as u8, (b >> 16) as u8, (b >> 24) as u8],
)
}
};
let base = super::palette_mode6_base(c0);
let got = super::simd::palette_mode6_avx2(base, c0, c1);
let mut want = [[0u8; 4]; 16];
for (k, &w) in super::W6M.iter().enumerate() {
for c in 0..4 {
want[k][c] = ((base[c] + w as i32 * (c1[c] as i32 - c0[c] as i32)) >> 6) as u8;
}
}
assert_eq!(got, want, "case {case} c0={c0:?} c1={c1:?}");
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[test]
fn ls_endpoints_mode6_vector_matches_scalar_bitwise() {
if !super::simd::has_avx2() {
return;
}
let mut state = 0x51ac_1d0e_7717_3355u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut px = [[0u8; 4]; 16];
for q in px.iter_mut() {
let r = next();
*q = [r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
let mut idx = [0u8; 16];
match case {
0 => {}
1 => idx = [15u8; 16],
2 => {
for (i, s) in idx.iter_mut().enumerate() {
*s = if i % 2 == 0 { 0 } else { 15 };
}
}
_ => {
for s in idx.iter_mut() {
*s = (next() >> 20) as u8 & 15;
}
}
}
let pxv = super::simd::ls_pixels_mode6(&px);
let got = super::ls_endpoints_mode6_pxv(&pxv, &idx);
let want = super::ls_endpoints_mode6_scalar(&px, &idx);
assert_eq!(got, want, "case {case}");
}
}
use super::{quantize_7p, unquantize_7p_chan};
#[test]
fn qtab_matches_the_direct_search() {
fn direct_chan(c: u8, p: u8) -> (u8, i32) {
let q0 = (c >> 1).min(127);
let mut best_qi = q0;
let mut best_e = i32::MAX;
for cand in q0.saturating_sub(1)..=q0.saturating_add(1).min(127) {
let recon = unquantize_7p_chan(cand, p);
let e = (recon as i32 - c as i32).pow(2);
if e < best_e {
best_e = e;
best_qi = cand;
}
}
(best_qi, best_e)
}
fn direct(c: [u8; 4]) -> ([u8; 4], u8) {
let mut best_p = 0u8;
let mut best_q = [0u8; 4];
let mut best_err = i32::MAX;
for p in 0..2u8 {
let mut q = [0u8; 4];
let mut err = 0i32;
for i in 0..4 {
let (qi, e) = direct_chan(c[i], p);
q[i] = qi;
err += e;
}
if err < best_err {
best_err = err;
best_p = p;
best_q = q;
}
}
(best_q, best_p)
}
for p in 0..2u8 {
for c in 0..=255u8 {
let want = direct_chan(c, p);
let got = (super::QTAB.0[p as usize][c as usize],
super::QTAB.1[p as usize][c as usize] as i32);
assert_eq!(got, want, "channel c={c} p={p}");
}
}
let mut state = 0x1234_5678_9abc_def0u64;
for case in 0..200_000 {
let c = if case < 256 {
[case as u8; 4]
} else {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let b = state.to_le_bytes();
[b[0], b[1], b[2], b[3]]
};
assert_eq!(quantize_7p(c), direct(c), "colour {c:?}");
}
}
}
pub(super) fn unquantize_7p(q: [u8; 4], p: u8) -> [u8; 4] {
let v = u32::from_le_bytes(q);
(((v << 1) & 0xFEFE_FEFE) | (p as u32 * 0x0101_0101)).to_le_bytes()
}
pub(super) fn unquantize_7p_chan(q: u8, p: u8) -> u8 {
let v = ((q as u32) << 1) | (p as u32);
v as u8
}
pub(super) fn extrema_opaque(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::extrema_opaque_avx2(pixels);
}
extrema_opaque_scalar(pixels)
}
#[cold]
#[inline(never)]
fn extrema_opaque_scalar(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
let mut min_l = i32::MAX;
let mut max_l = i32::MIN;
let mut min_rgb = [0u8; 3];
let mut max_rgb = [0u8; 3];
for p in pixels {
let l = p[0] as i32 * 2 + p[1] as i32 * 3 + p[2] as i32;
if l < min_l {
min_l = l;
min_rgb = [p[0], p[1], p[2]];
}
if l > max_l {
max_l = l;
max_rgb = [p[0], p[1], p[2]];
}
}
(max_rgb, min_rgb)
}
pub(super) fn extrema_rgba(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::extrema_rgba_avx2(pixels);
}
let mut min_l = i32::MAX;
let mut max_l = i32::MIN;
let mut min_p = [0u8; 4];
let mut max_p = [255u8; 4];
for p in pixels {
let l = p[0] as i32 + p[1] as i32 + p[2] as i32 + p[3] as i32;
if l < min_l {
min_l = l;
min_p = *p;
}
if l > max_l {
max_l = l;
max_p = *p;
}
}
(max_p, min_p)
}