use alloc::vec::Vec;
mod tables;
use crate::basislz::etc1s::{Endpoint, Selector};
use crate::color::Color32;
use crate::etc::tables::{ETC_5_TO_8, INTEN_TABLES};
use crate::uastc::unpack::unpack_uastc;
use tables::{
ETC1_X_SELECTOR_UNPACK, PVRTC_3, PVRTC_3_FLOOR, PVRTC_4, PVRTC_4_CEIL, PVRTC_4_FLOOR, PVRTC_5,
PVRTC_5_CEIL, PVRTC_5_FLOOR, PVRTC_ALPHA, PVRTC_ALPHA_CEIL, PVRTC_ALPHA_FLOOR,
PVRTC_SWIZZLE_TABLE,
};
#[inline]
fn total_bits(mut v: u32) -> u32 {
let mut l = 0;
while v > 0 {
v >>= 1;
l += 1;
}
l
}
#[inline]
fn pack_opaque_endpoint0_floor(c: Color32) -> u32 {
let r = PVRTC_5_FLOOR[c.r() as usize] as u32;
let g = PVRTC_5_FLOOR[c.g() as usize] as u32;
let b = (PVRTC_4_FLOOR[c.b() as usize] as u32) << 1;
(0x8000 | (r << 10) | (g << 5) | b) & !1
}
#[inline]
fn pack_opaque_endpoint1_ceil(c: Color32) -> u32 {
let r = PVRTC_5_CEIL[c.r() as usize] as u32;
let g = PVRTC_5_CEIL[c.g() as usize] as u32;
let b = PVRTC_5_CEIL[c.b() as usize] as u32;
0x8000 | (r << 10) | (g << 5) | b
}
#[inline]
fn pack_endpoints(low: Color32, high: Color32) -> u32 {
pack_opaque_endpoint0_floor(low) | (pack_opaque_endpoint1_ceil(high) << 16)
}
#[inline]
fn pack_endpoint_raw(
endpoint_index: u32,
r: u32,
g: u32,
b: u32,
a: u32,
opaque: bool,
m: u32,
) -> u32 {
if opaque {
if endpoint_index == 0 {
(0x8000 | (r << 10) | (g << 5) | (b << 1) | m) & 0xFFFF
} else {
(0x8000 | (r << 10) | (g << 5) | b) & 0xFFFF
}
} else if endpoint_index == 0 {
((a << 12) | (r << 8) | (g << 4) | (b << 1) | m) & 0xFFFF
} else {
((a << 12) | (r << 8) | (g << 4) | b) & 0xFFFF
}
}
#[inline]
fn pack_endpoint0_floor_rgba(c: Color32) -> u32 {
let a = PVRTC_ALPHA_FLOOR[c.a() as usize] as u32;
if a == 8 {
let r = PVRTC_5_FLOOR[c.r() as usize] as u32;
let g = PVRTC_5_FLOOR[c.g() as usize] as u32;
let b = PVRTC_4_FLOOR[c.b() as usize] as u32;
pack_endpoint_raw(0, r, g, b, a, true, 0)
} else {
let r = PVRTC_4_FLOOR[c.r() as usize] as u32;
let g = PVRTC_4_FLOOR[c.g() as usize] as u32;
let b = PVRTC_3_FLOOR[c.b() as usize] as u32;
pack_endpoint_raw(0, r, g, b, a, false, 0)
}
}
#[inline]
fn pack_endpoint1_ceil_rgba(c: Color32) -> u32 {
let a = PVRTC_ALPHA_CEIL[c.a() as usize] as u32;
if a == 8 {
let r = PVRTC_5_CEIL[c.r() as usize] as u32;
let g = PVRTC_5_CEIL[c.g() as usize] as u32;
let b = PVRTC_5_CEIL[c.b() as usize] as u32;
pack_endpoint_raw(1, r, g, b, a, true, 0)
} else {
let r = PVRTC_4_CEIL[c.r() as usize] as u32;
let g = PVRTC_4_CEIL[c.g() as usize] as u32;
let b = PVRTC_4_CEIL[c.b() as usize] as u32;
pack_endpoint_raw(1, r, g, b, a, false, 0)
}
}
#[inline]
fn pack_endpoints_rgba(low: Color32, high: Color32) -> u32 {
pack_endpoint0_floor_rgba(low) | (pack_endpoint1_ceil_rgba(high) << 16)
}
#[inline]
fn endpoint_l8(endpoints: u32, endpoint_index: u32) -> i32 {
let mask = if endpoint_index == 0 {
0xFFFEu32
} else {
0xFFFFu32
};
let packed = (endpoints >> (if endpoint_index != 0 { 16 } else { 0 })) & mask;
let (r, g, b, a);
if packed & 0x8000 != 0 {
r = PVRTC_5[((packed >> 10) & 31) as usize] as i32;
g = PVRTC_5[((packed >> 5) & 31) as usize] as i32;
b = if endpoint_index == 0 {
PVRTC_4[((packed & 31) >> 1) as usize] as i32
} else {
PVRTC_5[(packed & 31) as usize] as i32
};
a = 255;
} else {
r = PVRTC_4[((packed >> 8) & 0xF) as usize] as i32;
g = PVRTC_4[((packed >> 4) & 0xF) as usize] as i32;
b = if endpoint_index == 0 {
PVRTC_3[((packed & 0xF) >> 1) as usize] as i32
} else {
PVRTC_4[(packed & 0xF) as usize] as i32
};
a = PVRTC_ALPHA[((packed >> 12) & 7) as usize] as i32;
}
r + g + b + a
}
#[inline]
fn opaque_endpoint_l0(endpoints: u32) -> u32 {
let packed = endpoints;
let r = (packed >> 10) & 31;
let g = (packed >> 5) & 31;
let mut b = packed & 30;
b |= b >> 4;
r + g + b
}
#[inline]
fn opaque_endpoint_l1(endpoints: u32) -> u32 {
let packed = endpoints >> 16;
let r = (packed >> 10) & 31;
let g = (packed >> 5) & 31;
let b = packed & 31;
r + g + b
}
#[inline]
fn block_colors5_bounds(
base_color5: Color32,
inten_table: u32,
l: u32,
h: u32,
) -> (Color32, Color32) {
let br = ((base_color5.r() as i32) << 3) | ((base_color5.r() as i32) >> 2);
let bg = ((base_color5.g() as i32) << 3) | ((base_color5.g() as i32) >> 2);
let bb = ((base_color5.b() as i32) << 3) | ((base_color5.b() as i32) >> 2);
let it = &INTEN_TABLES[inten_table as usize];
let lo = Color32::new_clamped(
br + it[l as usize],
bg + it[l as usize],
bb + it[l as usize],
255,
);
let hi = Color32::new_clamped(
br + it[h as usize],
bg + it[h as usize],
bb + it[h as usize],
255,
);
(lo, hi)
}
const DO_PIX: [(u32, u32, i32, i32, i32, i32); 16] = [
(0, 0, 4, 4, 4, 4),
(1, 0, 2, 6, 2, 6),
(0, 1, 2, 2, 6, 6),
(1, 1, 1, 3, 3, 9),
(2, 0, 8, 0, 8, 0),
(3, 0, 6, 2, 6, 2),
(2, 1, 4, 0, 12, 0),
(3, 1, 3, 1, 9, 3),
(0, 2, 8, 8, 0, 0),
(1, 2, 4, 12, 0, 0),
(0, 3, 6, 6, 2, 2),
(1, 3, 3, 9, 1, 3),
(2, 2, 16, 0, 0, 0),
(3, 2, 12, 4, 0, 0),
(2, 3, 12, 0, 4, 0),
(3, 3, 9, 3, 3, 1),
];
#[inline]
fn do_pix_sub_quadrant(i: usize) -> (usize, usize) {
match i / 4 {
0 => (0, 0),
1 => (1, 0),
2 => (0, 1),
_ => (1, 1),
}
}
#[inline]
fn is_pow2(v: u32) -> bool {
v != 0 && (v & (v - 1)) == 0
}
pub struct Pvrtc1Image {
num_blocks_x: u32,
num_blocks_y: u32,
endpoints: Vec<u32>,
luma16: Vec<[i32; 16]>,
rgba: bool,
}
impl Pvrtc1Image {
fn new(num_blocks_x: u32, num_blocks_y: u32, rgba: bool) -> Option<Self> {
if num_blocks_x == 0 || num_blocks_y == 0 {
return None;
}
if !is_pow2(num_blocks_x * 4) || !is_pow2(num_blocks_y * 4) {
return None;
}
let n = (num_blocks_x * num_blocks_y) as usize;
Some(Self {
num_blocks_x,
num_blocks_y,
endpoints: vec![0u32; n],
luma16: vec![[0i32; 16]; n],
rgba,
})
}
fn prep_etc1s(&mut self, block_index: usize, ep: &Endpoint, sel: &Selector) {
let (low, high) = block_colors5_bounds(
ep.color5,
ep.inten5 as u32,
sel.lo_selector as u32,
sel.hi_selector as u32,
);
self.endpoints[block_index] = pack_endpoints(low, high);
let base_r = ETC_5_TO_8[ep.color5.r() as usize] as i32;
let base_g = ETC_5_TO_8[ep.color5.g() as usize] as i32;
let base_b = ETC_5_TO_8[ep.color5.b() as usize] as i32;
let it = &INTEN_TABLES[ep.inten5 as usize];
let by = (base_r + base_g + base_b) * 16;
let block_colors_y_x16 = [
by + it[2] * 48,
by + it[3] * 48,
by + it[1] * 48,
by + it[0] * 48,
];
let bytes = &sel.bytes;
let mut lookup_x = [0u32; 4];
for (lx, lk) in lookup_x.iter_mut().enumerate() {
let byte_ofs = 7 - ((lx * 4) >> 3);
let lsb_bits = (bytes[byte_ofs - 4] as u32) >> ((lx & 1) * 4);
let msb_bits = (bytes[byte_ofs - 6] as u32) >> ((lx & 1) * 4);
*lk = (lsb_bits & 0xF) | ((msb_bits & 0xF) << 4);
}
let luma = &mut self.luma16[block_index];
for ly in 0..4usize {
for lx in 0..4usize {
let idx = ETC1_X_SELECTOR_UNPACK[ly][lookup_x[lx] as usize] as usize;
luma[ly * 4 + lx] = block_colors_y_x16[idx];
}
}
}
fn prep_uastc(&mut self, block_index: usize, pixels: &[Color32; 16]) {
let mut low = Color32::new(255, 255, 255, 255);
let mut high = Color32::new(0, 0, 0, 0);
for &p in pixels.iter() {
low = Color32::comp_min(low, p);
high = Color32::comp_max(high, p);
}
self.endpoints[block_index] = pack_endpoints(low, high);
let luma = &mut self.luma16[block_index];
for (i, &p) in pixels.iter().enumerate() {
luma[i] = (p.r() as i32 + p.g() as i32 + p.b() as i32) * 16;
}
}
fn prep_etc1s_rgba(
&mut self,
block_index: usize,
ep: &Endpoint,
sel: &Selector,
alpha_ep: &Endpoint,
alpha_sel: &Selector,
) {
let (mut low, mut high) = block_colors5_bounds(
ep.color5,
ep.inten5 as u32,
sel.lo_selector as u32,
sel.hi_selector as u32,
);
let ag = ETC_5_TO_8[alpha_ep.color5.g() as usize] as i32;
let ait = &INTEN_TABLES[alpha_ep.inten5 as usize];
let alo = (ag + ait[alpha_sel.lo_selector as usize]).clamp(0, 255);
let ahi = (ag + ait[alpha_sel.hi_selector as usize]).clamp(0, 255);
low = Color32::new(low.r() as u32, low.g() as u32, low.b() as u32, alo as u32);
high = Color32::new(
high.r() as u32,
high.g() as u32,
high.b() as u32,
ahi as u32,
);
self.endpoints[block_index] = pack_endpoints_rgba(low, high);
let base_r = ETC_5_TO_8[ep.color5.r() as usize] as i32;
let base_g = ETC_5_TO_8[ep.color5.g() as usize] as i32;
let base_b = ETC_5_TO_8[ep.color5.b() as usize] as i32;
let it = &INTEN_TABLES[ep.inten5 as usize];
let by = (base_r + base_g + base_b) * 16;
let color_y = [
(by + it[0] * 48).clamp(0, 48 * 255),
(by + it[1] * 48).clamp(0, 48 * 255),
(by + it[2] * 48).clamp(0, 48 * 255),
(by + it[3] * 48).clamp(0, 48 * 255),
];
let alpha_base_g = ETC_5_TO_8[alpha_ep.color5.g() as usize] as i32 * 16;
let alpha_y = [
(alpha_base_g + ait[0] * 16).clamp(0, 16 * 255),
(alpha_base_g + ait[1] * 16).clamp(0, 16 * 255),
(alpha_base_g + ait[2] * 16).clamp(0, 16 * 255),
(alpha_base_g + ait[3] * 16).clamp(0, 16 * 255),
];
let luma = &mut self.luma16[block_index];
for ly in 0..4usize {
let crow = sel.selectors[ly] as u32;
let arow = alpha_sel.selectors[ly] as u32;
for lx in 0..4usize {
let cs = ((crow >> (lx * 2)) & 3) as usize;
let as_ = ((arow >> (lx * 2)) & 3) as usize;
luma[ly * 4 + lx] = color_y[cs] + alpha_y[as_];
}
}
}
fn prep_uastc_rgba(&mut self, block_index: usize, pixels: &[Color32; 16]) {
let mut low = Color32::new(255, 255, 255, 255);
let mut high = Color32::new(0, 0, 0, 0);
for &p in pixels.iter() {
low = Color32::comp_min(low, p);
high = Color32::comp_max(high, p);
}
self.endpoints[block_index] = pack_endpoints_rgba(low, high);
let luma = &mut self.luma16[block_index];
for (i, &p) in pixels.iter().enumerate() {
luma[i] = (p.r() as i32 + p.g() as i32 + p.b() as i32 + p.a() as i32) * 16;
}
}
fn apply(&self, out: &mut [u8]) -> Option<()> {
let nbx = self.num_blocks_x;
let nby = self.num_blocks_y;
let x_mask = nbx - 1;
let y_mask = nby - 1;
let x_bits = total_bits(x_mask);
let y_bits = total_bits(y_mask);
let min_bits = x_bits.min(y_bits);
let swizzle_mask = (1u32 << (min_bits * 2)).wrapping_sub(1);
let n = (nbx * nby) as usize;
if out.len() != n * 8 {
return None;
}
out.fill(0);
let rgba = self.rgba;
let win_l0 = |e: u32| -> i32 {
if rgba {
endpoint_l8(e, 0)
} else {
(opaque_endpoint_l0(e) * 255 / 31) as i32
}
};
let win_l1 = |e: u32| -> i32 {
if rgba {
endpoint_l8(e, 1)
} else {
(opaque_endpoint_l1(e) * 255 / 31) as i32
}
};
let mut e0 = [[0i32; 4]; 4];
let mut e1 = [[0i32; 4]; 4];
let mut block_index = 0usize;
for y in 0..nby as i32 {
let mut pe_rows: [usize; 3] = [0; 3];
for ey in 0..3usize {
let by = y + ey as i32 - 1;
let row_base = ((by as u32 & y_mask) * nbx) as usize;
pe_rows[ey] = row_base;
for ex in 0..3usize {
let bx = ex as i32 - 1;
let e = self.endpoints[row_base + (bx as u32 & x_mask) as usize];
e0[ex][ey] = win_l0(e);
e1[ex][ey] = win_l1(e);
}
}
let y_swizzle = ((PVRTC_SWIZZLE_TABLE[(y >> 8) as usize] as u32) << 16)
| (PVRTC_SWIZZLE_TABLE[(y & 0xFF) as usize] as u32);
for x in 0..nbx as i32 {
let x_swizzle = ((PVRTC_SWIZZLE_TABLE[(x >> 8) as usize] as u32) << 17)
| ((PVRTC_SWIZZLE_TABLE[(x & 0xFF) as usize] as u32) << 1);
let mut swizzled = x_swizzle | y_swizzle;
if nbx != nby {
swizzled &= swizzle_mask;
if nbx > nby {
swizzled |= ((x as u32) >> min_bits) << (min_bits * 2);
} else {
swizzled |= ((y as u32) >> min_bits) << (min_bits * 2);
}
}
let dst = (swizzled as usize) * 8;
let endpoints_word = self.endpoints[block_index];
{
let bx = ((x + 1) as u32 & x_mask) as usize;
for ey in 0..3usize {
let e = self.endpoints[pe_rows[ey] + bx];
e0[2][ey] = win_l0(e);
e1[2][ey] = win_l1(e);
}
}
let luma = &self.luma16[block_index];
let mut modulation = 0u32;
for (i, &(lx, ly, w0, w1, w2, w3)) in DO_PIX.iter().enumerate() {
let (ex, ey) = do_pix_sub_quadrant(i);
let a0 = e0[ex][ey];
let a1 = e0[ex + 1][ey];
let a2 = e0[ex][ey + 1];
let a3 = e0[ex + 1][ey + 1];
let b0 = e1[ex][ey];
let b1 = e1[ex + 1][ey];
let b2 = e1[ex][ey + 1];
let b3 = e1[ex + 1][ey + 1];
let ca_l = a0 * w0 + a1 * w1 + a2 * w2 + a3 * w3;
let cb_l = b0 * w0 + b1 * w1 + b2 * w2 + b3 * w3;
let cl = luma[(ly * 4 + lx) as usize];
let mut dl = cb_l - ca_l;
let vl = cl - ca_l;
let mut p = vl * 16;
if ca_l > cb_l {
p = -p;
dl = -dl;
}
let shift = ly * 8 + lx * 2;
let mut m = 0u32;
if p > 3 * dl {
m = 1 << shift;
}
if p > 8 * dl {
m = 2 << shift;
}
if p > 13 * dl {
m = 3 << shift;
}
modulation |= m;
}
out[dst..dst + 4].copy_from_slice(&modulation.to_le_bytes());
out[dst + 4..dst + 8].copy_from_slice(&endpoints_word.to_le_bytes());
for ey in 0..3usize {
e0[0][ey] = e0[1][ey];
e0[1][ey] = e0[2][ey];
e1[0][ey] = e1[1][ey];
e1[1][ey] = e1[2][ey];
}
block_index += 1;
}
}
Some(())
}
}
pub(crate) fn encode_pvrtc1_raw(
num_blocks_x: u32,
num_blocks_y: u32,
rgba: bool,
from_alpha: bool,
mut extract: impl FnMut(u32, u32) -> [Color32; 16],
out: &mut [u8],
) -> Option<()> {
let mut img = Pvrtc1Image::new(num_blocks_x, num_blocks_y, rgba)?;
for by in 0..num_blocks_y {
for bx in 0..num_blocks_x {
let mut px = extract(bx, by);
if !rgba && from_alpha {
for p in px.iter_mut() {
let a = p.a() as u32;
*p = Color32::new(a, a, a, 255);
}
}
let idx = (bx + by * num_blocks_x) as usize;
if rgba {
img.prep_uastc_rgba(idx, &px);
} else {
img.prep_uastc(idx, &px);
}
}
}
img.apply(out)
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_etc1s_pvrtc1_4_rgb(
indices: &[(u16, u16)],
endpoints: &[Endpoint],
selectors: &[Selector],
num_blocks_x: u32,
num_blocks_y: u32,
out: &mut [u8],
) -> Option<()> {
let mut img = Pvrtc1Image::new(num_blocks_x, num_blocks_y, false)?;
for (b, &(ei, si)) in indices.iter().enumerate() {
img.prep_etc1s(b, &endpoints[ei as usize], &selectors[si as usize]);
}
img.apply(out)
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_etc1s_pvrtc1_4_rgba(
color_indices: &[(u16, u16)],
alpha_indices: &[(u16, u16)],
endpoints: &[Endpoint],
selectors: &[Selector],
num_blocks_x: u32,
num_blocks_y: u32,
out: &mut [u8],
) -> Option<()> {
let mut img = Pvrtc1Image::new(num_blocks_x, num_blocks_y, true)?;
if alpha_indices.len() != color_indices.len() {
return None;
}
for (b, (&(ei, si), &(aei, asi))) in color_indices.iter().zip(alpha_indices.iter()).enumerate()
{
img.prep_etc1s_rgba(
b,
&endpoints[ei as usize],
&selectors[si as usize],
&endpoints[aei as usize],
&selectors[asi as usize],
);
}
img.apply(out)
}
pub fn transcode_uastc_pvrtc1_4_rgb(
uastc_blocks: &[u8],
num_blocks_x: u32,
num_blocks_y: u32,
out: &mut [u8],
) -> Option<()> {
let mut img = Pvrtc1Image::new(num_blocks_x, num_blocks_y, false)?;
let n = (num_blocks_x * num_blocks_y) as usize;
let data = uastc_blocks.get(..n * 16)?;
for (b, blk) in data.chunks_exact(16).enumerate() {
let src: [u8; 16] = blk.try_into().ok()?;
let pixels = unpack_uastc(&src, false)?;
img.prep_uastc(b, &pixels);
}
img.apply(out)
}
pub fn transcode_uastc_pvrtc1_4_rgba(
uastc_blocks: &[u8],
num_blocks_x: u32,
num_blocks_y: u32,
out: &mut [u8],
) -> Option<()> {
let mut img = Pvrtc1Image::new(num_blocks_x, num_blocks_y, true)?;
let n = (num_blocks_x * num_blocks_y) as usize;
let data = uastc_blocks.get(..n * 16)?;
for (b, blk) in data.chunks_exact(16).enumerate() {
let src: [u8; 16] = blk.try_into().ok()?;
let pixels = unpack_uastc(&src, false)?;
img.prep_uastc_rgba(b, &pixels);
}
img.apply(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn total_bits_matches_reference() {
assert_eq!(total_bits(0), 0);
assert_eq!(total_bits(1), 1);
assert_eq!(total_bits(0b1111), 4);
assert_eq!(total_bits(0b1000_0000), 8);
}
#[test]
fn pow2_guard_rejects_non_pow2() {
assert!(Pvrtc1Image::new(3, 2, false).is_none());
assert!(Pvrtc1Image::new(2, 2, false).is_some());
}
}