use super::*;
pub fn encode_bc1(pixels: [[u8; 4]; 16], out: &mut [u8]) {
out[..8].copy_from_slice(&encode_bc1_bytes(pixels));
}
pub(super) fn psq_rgb(pixels: &[[u8; 4]; 16]) -> i32 {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::bc1_psq_rgb_avx2(pixels);
}
psq_rgb_scalar(pixels)
}
#[cold]
#[inline(never)]
fn psq_rgb_scalar(pixels: &[[u8; 4]; 16]) -> i32 {
let mut t = 0i32;
for p in pixels {
for c in 0..3 {
let v = p[c] as i32;
t += v * v;
}
}
t
}
pub(super) fn encode_bc1_bytes(pixels: [[u8; 4]; 16]) -> [u8; 8] {
let (max_c, min_c) = extrema_opaque(&pixels);
let psq = psq_rgb(&pixels);
let s0 = to_565(max_c);
let s1 = to_565(min_c);
let (a, a_err) = pack_bc1_scored_565_nudged(&pixels, s0, s1, psq, i32::MAX)
.expect("unbounded pack always packs");
let (mx, mn) = channel_minmax_rgb(&pixels);
let span = (mx[0] - mn[0]) as i32 + (mx[1] - mn[1]) as i32 + (mx[2] - mn[2]) as i32;
if span < 24 {
return a;
}
let mut best = a;
let mut best_err = a_err;
if best_err == 0 {
return best;
}
let (t0, t1) = (to_565(mx), to_565(mn));
if (t0, t1) != (s0, s1) {
if let Some((cand, err)) = pack_bc1_scored_565_nudged(&pixels, t0, t1, psq, best_err) {
best = cand;
best_err = err;
}
}
if quality_is_fast() || best_err <= 16 {
return best;
}
if bc1_pca_seed_enabled() {
if let Some((pa, pb)) = pca_extremes_rgb(&pixels) {
consider_bc1(&pixels, pa, pb, psq, &mut best, &mut best_err);
}
}
for _ in 0..4 {
if best_err == 0 {
break;
}
let Some((e0, e1)) = ls_endpoints_bc1(&pixels, &best) else {
break;
};
let prev = best_err;
consider_bc1(&pixels, e0, e1, psq, &mut best, &mut best_err);
if best_err >= prev {
break;
}
}
if best_err > bc1_lattice_min_err() {
lattice_refine_bc1(&pixels, psq, &mut best, &mut best_err);
}
best
}
pub(super) fn consider_bc1(
pixels: &[[u8; 4]; 16],
e0: [u8; 3],
e1: [u8; 3],
psq: i32,
best: &mut [u8; 8],
best_err: &mut i32,
) {
if let Some((cand, err)) = pack_bc1_scored(pixels, e0, e1, psq, *best_err) {
*best = cand;
*best_err = err;
}
}
#[inline]
pub(super) fn bc1_fit_4color(
pixels: &[[u8; 4]; 16],
colors: &[[u8; 3]; 4],
err_limit: i32,
) -> Option<(u32, i32)> {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::bc1_fit_4color_avx2(pixels, colors, err_limit);
}
bc1_fit_4color_scalar(pixels, colors, err_limit)
}
#[cold]
#[inline(never)]
pub(super) fn bc1_fit_4color_scalar(
pixels: &[[u8; 4]; 16],
colors: &[[u8; 3]; 4],
err_limit: i32,
) -> Option<(u32, i32)> {
let mut table = 0u32;
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let px = [p[0], p[1], p[2]];
let mut best = 0usize;
let mut best_d = sqr_rgb(px, colors[0]);
for j in 1..4usize {
let d = sqr_rgb(px, colors[j]);
if d < best_d {
best_d = d;
best = j;
}
}
table |= (best as u32) << (2 * i);
err += best_d;
if err >= err_limit {
return None;
}
}
Some((table, err))
}
pub(super) fn pack_bc1_scored_565(
pixels: &[[u8; 4]; 16],
a: u16,
b: u16,
psq: i32,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
debug_assert_ne!(a, b);
let (hi, lo) = if a > b { (a, b) } else { (b, a) };
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (table, err) = simd::bc1_fit_565_avx2(pixels, hi, lo, psq, err_limit)?;
let v = (hi as u64) | ((lo as u64) << 16) | ((table as u64) << 32);
return Some((v.to_le_bytes(), err));
}
pack_bc1_scored_565_cold(pixels, hi, lo, err_limit)
}
#[cold]
#[inline(never)]
fn pack_bc1_scored_565_cold(
pixels: &[[u8; 4]; 16],
hi: u16,
lo: u16,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
pack_bc1_scored_with(pixels, hi, lo, &bc1_palette_565(hi, lo), err_limit)
}
pub(super) fn bc1_palette_565(hi: u16, lo: u16) -> [[u8; 3]; 4] {
let ca = from_565(hi);
let cb = from_565(lo);
[ca, cb, lerp_rgb::<2, 1>(ca, cb), lerp_rgb::<1, 2>(ca, cb)]
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub(super) type Pal16 = simd::Bc1Pal;
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
pub(super) type Pal16 = ();
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub(super) fn widen_pal(colors: &[[u8; 3]; 4]) -> Pal16 {
if simd::has_avx2() {
simd::bc1_widen_palette(colors)
} else {
simd::Bc1Pal::ZERO
}
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
pub(super) fn widen_pal(_colors: &[[u8; 3]; 4]) -> Pal16 {}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub(super) fn byte_pal_if_needed(hi: u16, lo: u16) -> [[u8; 3]; 4] {
if simd::has_avx2() {
[[0u8; 3]; 4]
} else {
bc1_palette_565(hi, lo)
}
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
pub(super) fn byte_pal_if_needed(hi: u16, lo: u16) -> [[u8; 3]; 4] {
bc1_palette_565(hi, lo)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub(super) fn pal16_from_565(hi: u16, lo: u16) -> Pal16 {
if simd::has_avx2() {
simd::bc1_palette_565_i16_avx2(hi, lo)
} else {
pal16_from_565_scalar(hi, lo)
}
}
#[cold]
#[inline(never)]
fn pal16_from_565_scalar(hi: u16, lo: u16) -> Pal16 {
widen_pal(&bc1_palette_565(hi, lo))
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
pub(super) fn pal16_from_565(_hi: u16, _lo: u16) -> Pal16 {}
pub(super) fn pack_bc1_scored_pre(
pixels: &[[u8; 4]; 16],
hi: u16,
lo: u16,
colors: &[[u8; 3]; 4],
pal16: &Pal16,
psq: i32,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (table, err) = simd::bc1_fit_4color_pre_avx2(pixels, pal16, psq, err_limit)?;
let v = (hi as u64) | ((lo as u64) << 16) | ((table as u64) << 32);
return Some((v.to_le_bytes(), err));
}
let _ = pal16;
pack_bc1_scored_with(pixels, hi, lo, colors, err_limit)
}
pub(super) fn pack_bc1_scored_with(
pixels: &[[u8; 4]; 16],
hi: u16,
lo: u16,
colors: &[[u8; 3]; 4],
err_limit: i32,
) -> Option<([u8; 8], i32)> {
let (table, err) = bc1_fit_4color(pixels, colors, err_limit)?;
let v = (hi as u64) | ((lo as u64) << 16) | ((table as u64) << 32);
Some((v.to_le_bytes(), err))
}
pub(super) fn lattice_refine_bc1(
pixels: &[[u8; 4]; 16],
psq: i32,
best: &mut [u8; 8],
best_err: &mut i32,
) {
let mut c0 = u16::from_le_bytes([best[0], best[1]]);
let mut c1 = u16::from_le_bytes([best[2], best[3]]);
if c0 <= c1 {
return; }
for _round in 0..bc1_lattice_rounds() {
let prev = *best_err;
for (base, other, up) in [(c0, c1, false), (c1, c0, true)] {
for (shift, maxv) in [(11u16, 31u16), (5, 63), (0, 31)] {
let cur = (base >> shift) & maxv;
if cur == if up { maxv } else { 0 } {
continue;
}
let step = 1u16 << shift;
let cand = if up { base + step } else { base - step };
if cand == other {
continue;
}
if let Some((blk, e)) = pack_bc1_scored_565(pixels, cand, other, psq, *best_err) {
*best = blk;
*best_err = e;
let (hi, lo) = if cand > other { (cand, other) } else { (other, cand) };
c0 = hi;
c1 = lo;
if e == 0 {
return;
}
}
}
}
if *best_err >= prev {
break;
}
}
}
pub(super) fn pack_bc1_scored_565_nudged(
pixels: &[[u8; 4]; 16],
mut max565: u16,
min565: u16,
psq: i32,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
if max565 == min565 {
max565 = max565.saturating_add(1);
if max565 == min565 {
return pack_bc1_scored_cold(pixels, max565, min565, true, err_limit);
}
}
pack_bc1_scored_565(pixels, max565, min565, psq, err_limit)
}
pub(super) fn pack_bc1_scored(
pixels: &[[u8; 4]; 16],
max_c: [u8; 3],
min_c: [u8; 3],
psq: i32,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
pack_bc1_scored_565_nudged(pixels, to_565(max_c), to_565(min_c), psq, err_limit)
}
#[cold]
#[inline(never)]
fn pack_bc1_scored_cold(
pixels: &[[u8; 4]; 16],
c0: u16,
c1: u16,
punch: bool,
err_limit: i32,
) -> Option<([u8; 8], i32)> {
let colors = {
let ca = from_565(c0);
let cb = from_565(c1);
if punch {
[ca, cb, lerp_rgb::<1, 1>(ca, cb), [0, 0, 0]]
} else {
[ca, cb, lerp_rgb::<2, 1>(ca, cb), lerp_rgb::<1, 2>(ca, cb)]
}
};
let (table, err) = if punch {
let mut table = 0u32;
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let (idx, e) = if p[3] < 128 {
(3usize, sqr_rgb([p[0], p[1], p[2]], colors[3]))
} else {
let mut best = 0usize;
let mut best_d = i32::MAX;
for (j, c) in colors.iter().enumerate() {
let d = sqr_rgb([p[0], p[1], p[2]], *c);
if d < best_d {
best_d = d;
best = j;
}
}
(best, best_d)
};
table |= (idx as u32) << (2 * i);
err += e;
if err >= err_limit {
return None;
}
}
(table, err)
} else {
bc1_fit_4color(pixels, &colors, err_limit)?
};
let mut out = [0u8; 8];
out[0..2].copy_from_slice(&c0.to_le_bytes());
out[2..4].copy_from_slice(&c1.to_le_bytes());
out[4..8].copy_from_slice(&table.to_le_bytes());
Some((out, err))
}
pub(super) fn pca_extremes_rgb(pixels: &[[u8; 4]; 16]) -> Option<([u8; 3], [u8; 3])> {
let mut mean = [0f32; 3];
for p in pixels {
for c in 0..3 {
mean[c] += p[c] as f32;
}
}
for m in mean.iter_mut() {
*m /= 16.0;
}
let mut cov = [0f32; 6]; for p in pixels {
let d = [
p[0] as f32 - mean[0],
p[1] as f32 - mean[1],
p[2] as f32 - mean[2],
];
cov[0] += d[0] * d[0];
cov[1] += d[0] * d[1];
cov[2] += d[0] * d[2];
cov[3] += d[1] * d[1];
cov[4] += d[1] * d[2];
cov[5] += d[2] * d[2];
}
let mut axis = [
cov[0] + cov[1] + cov[2],
cov[1] + cov[3] + cov[4],
cov[2] + cov[4] + cov[5],
];
for _ in 0..3 {
let n = [
cov[0] * axis[0] + cov[1] * axis[1] + cov[2] * axis[2],
cov[1] * axis[0] + cov[3] * axis[1] + cov[4] * axis[2],
cov[2] * axis[0] + cov[4] * axis[1] + cov[5] * axis[2],
];
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len < 1e-6 {
return None;
}
axis = [n[0] / len, n[1] / len, n[2] / len];
}
let mut lo_t = f32::MAX;
let mut hi_t = f32::MIN;
let mut lo_p = [0u8; 3];
let mut hi_p = [0u8; 3];
for p in pixels {
let t = (p[0] as f32 - mean[0]) * axis[0]
+ (p[1] as f32 - mean[1]) * axis[1]
+ (p[2] as f32 - mean[2]) * axis[2];
if t < lo_t {
lo_t = t;
lo_p = [p[0], p[1], p[2]];
}
if t > hi_t {
hi_t = t;
hi_p = [p[0], p[1], p[2]];
}
}
if lo_p == hi_p {
return None;
}
Some((hi_p, lo_p))
}
pub(super) fn ls_endpoints_bc1(pixels: &[[u8; 4]; 16], block: &[u8; 8]) -> Option<([u8; 3], [u8; 3])> {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
if c0 <= c1 {
return None; }
let table = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (e0, e1) = simd::bc1_ls_endpoints_avx2(pixels, table)?;
return Some(([e0[0], e0[1], e0[2]], [e1[0], e1[1], e1[2]]));
}
const W: [f32; 4] = [0.0, 1.0, 1.0 / 3.0, 2.0 / 3.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() {
let w = W[((table >> (2 * 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;
}
let mut e0 = [0u8; 3];
let mut e1 = [0u8; 3];
for c in 0..3 {
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 channel_minmax_rgb(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let (mx, mn) = simd::channel_minmax_avx2(pixels);
return ([mx[0], mx[1], mx[2]], [mn[0], mn[1], mn[2]]);
}
channel_minmax_rgb_scalar(pixels)
}
#[cold]
#[inline(never)]
fn channel_minmax_rgb_scalar(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
let mut mn = [255u8; 3];
let mut mx = [0u8; 3];
for p in pixels {
for c in 0..3 {
mn[c] = mn[c].min(p[c]);
mx[c] = mx[c].max(p[c]);
}
}
(mx, mn)
}
#[cfg(test)]
pub(super) fn bc1_sse(pixels: &[[u8; 4]; 16], block: &[u8]) -> i32 {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
let table = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
let colors = if c0 > c1 {
[
from_565(c0),
from_565(c1),
lerp_rgb::<2, 1>(from_565(c0), from_565(c1)),
lerp_rgb::<1, 2>(from_565(c0), from_565(c1)),
]
} else {
[
from_565(c0),
from_565(c1),
lerp_rgb::<1, 1>(from_565(c0), from_565(c1)),
[0, 0, 0],
]
};
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let idx = ((table >> (2 * i)) & 3) as usize;
err += sqr_rgb([p[0], p[1], p[2]], colors[idx]);
}
err
}
pub fn encode_bc2(pixels: [[u8; 4]; 16], out: &mut [u8]) {
let out = &mut out[..16];
out.fill(0);
for i in 0..16 {
let a = pixels[i][3] >> 4;
let byte = i / 2;
if i % 2 == 0 {
out[byte] = a;
} else {
out[byte] |= a << 4;
}
}
out[8..16].copy_from_slice(&encode_bc1_bytes(pixels));
}
pub fn encode_bc3(pixels: [[u8; 4]; 16], out: &mut [u8]) {
let out = &mut out[..16];
out[..8].copy_from_slice(&encode_alpha_block_unsigned(super::alpha::alpha_channel(&pixels)));
out[8..16].copy_from_slice(&encode_bc1_bytes(pixels));
}
#[cfg(test)]
pub(super) fn pack_bc1(pixels: [[u8; 4]; 16], max_c: [u8; 3], min_c: [u8; 3]) -> [u8; 8] {
let mut max565 = to_565(max_c);
let min565 = to_565(min_c);
if max565 == min565 {
max565 = max565.saturating_add(1);
}
let (c0, c1, table) = if max565 > min565 {
let colors = [
from_565(max565),
from_565(min565),
lerp_rgb::<2, 1>(from_565(max565), from_565(min565)),
lerp_rgb::<1, 2>(from_565(max565), from_565(min565)),
];
(max565, min565, pack_indices_2bit(&pixels, &colors, false))
} else if max565 < min565 {
let colors = [
from_565(min565),
from_565(max565),
lerp_rgb::<2, 1>(from_565(min565), from_565(max565)),
lerp_rgb::<1, 2>(from_565(min565), from_565(max565)),
];
(min565, max565, pack_indices_2bit(&pixels, &colors, false))
} else {
let colors = [
from_565(min565),
from_565(max565),
lerp_rgb::<1, 1>(from_565(min565), from_565(max565)),
[0, 0, 0],
];
(min565, max565, pack_indices_2bit(&pixels, &colors, true))
};
let mut out = [0u8; 8];
out[0..2].copy_from_slice(&c0.to_le_bytes());
out[2..4].copy_from_slice(&c1.to_le_bytes());
out[4..8].copy_from_slice(&table.to_le_bytes());
out
}