use crate::basislz::etc1s::Etc1sTranscoder;
use crate::etc::uastc_to_etc1::transcode_uastc_to_etc1;
use crate::etc::uastc_to_etc2::transcode_uastc_to_etc2_rgba;
use crate::uastc::astc_pack::transcode_uastc_to_astc;
use crate::uastc::bc1::transcode_uastc_to_bc1;
use crate::uastc::bc3_bc5::{transcode_uastc_to_bc3, transcode_uastc_to_bc5};
use crate::uastc::bc4::transcode_uastc_to_bc4;
use crate::uastc::bc7::transcode_uastc_to_bc7;
use crate::uastc::eac::{transcode_uastc_to_etc2_eac_r11, transcode_uastc_to_etc2_eac_rg11};
use crate::uastc::unpack::unpack_uastc;
pub mod target {
pub const ETC1_RGB: i32 = 0;
pub const ETC2_RGBA: i32 = 1;
pub const BC1_RGB: i32 = 2;
pub const BC3_RGBA: i32 = 3;
pub const BC4_R: i32 = 4;
pub const BC5_RG: i32 = 5;
pub const BC7_RGBA: i32 = 6;
pub const PVRTC1_4_RGB: i32 = 8;
pub const PVRTC1_4_RGBA: i32 = 9;
pub const ASTC_4X4_RGBA: i32 = 10;
pub const ATC_RGB: i32 = 11;
pub const ATC_RGBA: i32 = 12;
pub const PVRTC2_4_RGB: i32 = 18;
pub const PVRTC2_4_RGBA: i32 = 19;
pub const RGBA32: i32 = 13;
pub const RGB565: i32 = 14;
pub const BGR565: i32 = 15;
pub const RGBA4444: i32 = 16;
pub const FXT1_RGB: i32 = 17;
pub const EAC_R11: i32 = 20;
pub const EAC_RG11: i32 = 21;
pub const BC6H: i32 = 22;
pub const ASTC_HDR_4X4_RGBA: i32 = 23;
pub const RGB_HALF: i32 = 24;
pub const RGBA_HALF: i32 = 25;
pub const RGB_9E5: i32 = 26;
pub const ASTC_HDR_6X6_RGBA: i32 = 27;
}
pub mod decode_flags {
pub const BC1_FORBID_THREE_COLOR_BLOCKS: u32 = 8;
pub const HIGH_QUALITY: u32 = 32;
pub const NO_ETC1S_CHROMA_FILTERING: u32 = 64;
}
#[inline]
fn mul_8(v: u32, q: u32) -> u32 {
let v = v * q + 128;
(v + (v >> 8)) >> 8
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_etc1s(
t: &Etc1sTranscoder,
rgb: &[u8],
alpha: Option<&[u8]>,
bx: u32,
by: u32,
lw: u32,
lh: u32,
fmt: i32,
flags: u32,
video: Option<(&mut crate::basislz::etc1s::VideoState, u32)>,
out: &mut [u8],
) -> Option<()> {
let vid = video.map(|(state, level)| state.slots(level as usize, (bx * by) as usize));
match fmt {
target::ASTC_4X4_RGBA => match alpha {
Some(a) => t.transcode_image_astc_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_astc(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::BC1_RGB => {
let forbid = (flags & decode_flags::BC1_FORBID_THREE_COLOR_BLOCKS) != 0;
t.transcode_slice_bc1(rgb, bx, by, forbid, vid.map(|(c, _)| c), out)
}
target::BC3_RGBA => match alpha {
Some(a) => t.transcode_image_bc3_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_bc3_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::PVRTC1_4_RGBA => match alpha {
Some(a) => t.transcode_slice_pvrtc1_4_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_pvrtc1_4_rgb(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::PVRTC1_4_RGB => {
t.transcode_slice_pvrtc1_4_rgb(rgb, bx, by, vid.map(|(c, _)| c), out)
}
target::BC4_R => t.transcode_slice_bc4(rgb, bx, by, vid.map(|(c, _)| c), out),
target::BC5_RG => match alpha {
Some(a) => t.transcode_image_bc5_rg(rgb, a, bx, by, vid, out),
None => t.transcode_slice_bc5_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::BC7_RGBA => {
let chroma = (flags & decode_flags::NO_ETC1S_CHROMA_FILTERING) == 0;
match alpha {
Some(a) => t.transcode_image_bc7_rgba(rgb, a, bx, by, chroma, vid, out),
None => t.transcode_slice_bc7(rgb, bx, by, chroma, vid.map(|(c, _)| c), out),
}
}
target::ETC2_RGBA => match alpha {
Some(a) => t.transcode_image_etc2_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_etc2_rgba_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::EAC_R11 => t.transcode_slice_eac_r11(rgb, bx, by, vid.map(|(c, _)| c), out),
target::EAC_RG11 => match alpha {
Some(a) => t.transcode_image_eac_rg11(rgb, a, bx, by, vid, out),
None => t.transcode_slice_eac_rg11_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::ATC_RGB => t.transcode_slice_atc(rgb, bx, by, vid.map(|(c, _)| c), out),
target::ATC_RGBA => match alpha {
Some(a) => t.transcode_image_atc_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_atc_rgba_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::PVRTC2_4_RGB => t.transcode_slice_pvrtc2_rgb(rgb, bx, by, vid.map(|(c, _)| c), out),
target::PVRTC2_4_RGBA => match alpha {
Some(a) => t.transcode_image_pvrtc2_rgba(rgb, a, bx, by, vid, out),
None => t.transcode_slice_pvrtc2_rgba_opaque(rgb, bx, by, vid.map(|(c, _)| c), out),
},
target::RGBA32 => match alpha {
Some(a) => t.transcode_image_rgba32(rgb, a, bx, by, lw, lh, vid, out),
None => t.transcode_slice_rgba32(rgb, bx, by, lw, lh, vid.map(|(c, _)| c), out),
},
target::ETC1_RGB => t.transcode_slice_etc1(rgb, bx, by, vid.map(|(c, _)| c), out),
target::FXT1_RGB => t.transcode_slice_fxt1(rgb, bx, by, lw, lh, vid.map(|(c, _)| c), out),
target::RGB565 => {
t.transcode_slice_565(rgb, bx, by, lw, lh, false, vid.map(|(c, _)| c), out)
}
target::BGR565 => {
t.transcode_slice_565(rgb, bx, by, lw, lh, true, vid.map(|(c, _)| c), out)
}
target::RGBA4444 => match alpha {
Some(a) => t.transcode_image_rgba4444(rgb, a, bx, by, lw, lh, vid, out),
None => {
t.transcode_slice_rgba4444_opaque(rgb, bx, by, lw, lh, vid.map(|(c, _)| c), out)
}
},
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_uastc(
img: &[u8],
has_alpha: bool,
bx: u32,
by: u32,
lw: u32,
lh: u32,
fmt: i32,
flags: u32,
out: &mut [u8],
) -> Option<()> {
let n = (bx * by) as usize;
if fmt == target::PVRTC1_4_RGB {
return crate::pvrtc::transcode_uastc_pvrtc1_4_rgb(img, bx, by, out);
}
if fmt == target::PVRTC1_4_RGBA {
if has_alpha {
return crate::pvrtc::transcode_uastc_pvrtc1_4_rgba(img, bx, by, out);
}
return crate::pvrtc::transcode_uastc_pvrtc1_4_rgb(img, bx, by, out);
}
if matches!(
fmt,
target::RGBA32 | target::RGB565 | target::BGR565 | target::RGBA4444
) {
let bpp = if fmt == target::RGBA32 { 4 } else { 2 };
if out.len() != (lw * lh) as usize * bpp {
return None;
}
out.fill(0);
for (b, blk) in img.chunks_exact(16).enumerate() {
let src: [u8; 16] = blk.try_into().ok()?;
let px = unpack_uastc(&src, false)?;
let block_x = b as u32 % bx;
let block_y = b as u32 / bx;
let max_x = (lw as i32 - block_x as i32 * 4).clamp(0, 4) as u32;
let max_y = (lh as i32 - block_y as i32 * 4).clamp(0, 4) as u32;
for y in 0..max_y {
for x in 0..max_x {
let c = px[(y * 4 + x) as usize];
let o = ((block_x * 4 + x) + (block_y * 4 + y) * lw) as usize * bpp;
match fmt {
target::RGBA32 => {
out[o] = c.r();
out[o + 1] = c.g();
out[o + 2] = c.b();
out[o + 3] = c.a();
}
target::RGB565 => {
let p = (mul_8(c.r() as u32, 31) << 11)
| (mul_8(c.g() as u32, 63) << 5)
| mul_8(c.b() as u32, 31);
out[o..o + 2].copy_from_slice(&(p as u16).to_le_bytes());
}
target::BGR565 => {
let p = (mul_8(c.b() as u32, 31) << 11)
| (mul_8(c.g() as u32, 63) << 5)
| mul_8(c.r() as u32, 31);
out[o..o + 2].copy_from_slice(&(p as u16).to_le_bytes());
}
_ => {
let p = (mul_8(c.r() as u32, 15) << 12)
| (mul_8(c.g() as u32, 15) << 8)
| (mul_8(c.b() as u32, 15) << 4)
| mul_8(c.a() as u32, 15);
out[o..o + 2].copy_from_slice(&(p as u16).to_le_bytes());
}
}
}
}
}
return Some(());
}
if fmt == target::BC1_RGB {
let high_quality = (flags & decode_flags::HIGH_QUALITY) != 0;
if out.len() != n * 8 {
return None;
}
out.fill(0);
for b in 0..n {
let src: [u8; 16] = img[b * 16..b * 16 + 16].try_into().ok()?;
let block = transcode_uastc_to_bc1(&src, high_quality)?;
out[b * 8..b * 8 + 8].copy_from_slice(&block);
}
return Some(());
}
if fmt == target::BC4_R {
if out.len() != n * 8 {
return None;
}
out.fill(0);
for b in 0..n {
let src: [u8; 16] = img[b * 16..b * 16 + 16].try_into().ok()?;
let block = transcode_uastc_to_bc4(&src, false, 0)?;
out[b * 8..b * 8 + 8].copy_from_slice(&block);
}
return Some(());
}
if fmt == target::ETC1_RGB {
if out.len() != n * 8 {
return None;
}
out.fill(0);
for b in 0..n {
let src: [u8; 16] = img[b * 16..b * 16 + 16].try_into().ok()?;
let block = transcode_uastc_to_etc1(&src)?;
out[b * 8..b * 8 + 8].copy_from_slice(&block);
}
return Some(());
}
if fmt == target::EAC_R11 {
let high_quality = (flags & decode_flags::HIGH_QUALITY) != 0;
if out.len() != n * 8 {
return None;
}
out.fill(0);
for b in 0..n {
let src: [u8; 16] = img[b * 16..b * 16 + 16].try_into().ok()?;
let block = transcode_uastc_to_etc2_eac_r11(&src, high_quality, 0)?;
out[b * 8..b * 8 + 8].copy_from_slice(&block);
}
return Some(());
}
let high_quality = (flags & decode_flags::HIGH_QUALITY) != 0;
if out.len() != n * 16 {
return None;
}
out.fill(0);
for b in 0..n {
let src: [u8; 16] = img[b * 16..b * 16 + 16].try_into().ok()?;
let block = match fmt {
target::ASTC_4X4_RGBA => transcode_uastc_to_astc(&src)?,
target::BC7_RGBA => transcode_uastc_to_bc7(&src)?,
target::ETC2_RGBA => transcode_uastc_to_etc2_rgba(&src)?,
target::BC3_RGBA => transcode_uastc_to_bc3(&src, high_quality)?,
target::BC5_RG => transcode_uastc_to_bc5(&src, high_quality, 0, 3)?,
target::EAC_RG11 => transcode_uastc_to_etc2_eac_rg11(&src, high_quality, 0, 3)?,
_ => return None,
};
out[b * 16..b * 16 + 16].copy_from_slice(&block);
}
Some(())
}
pub fn transcode_uastc_hdr(
img: &[u8],
bx: u32,
by: u32,
lw: u32,
lh: u32,
fmt: i32,
out: &mut [u8],
) -> Option<()> {
match fmt {
target::ASTC_HDR_4X4_RGBA => crate::uastc_hdr::transcode_astc_hdr_4x4(img, bx, by, out),
target::RGB_HALF => crate::uastc_hdr::transcode_rgb_half(img, bx, by, lw, lh, out),
target::RGBA_HALF => crate::uastc_hdr::transcode_rgba_half(img, bx, by, lw, lh, out),
target::RGB_9E5 => crate::uastc_hdr::transcode_rgb_9e5(img, bx, by, lw, lh, out),
target::BC6H => crate::uastc_hdr::transcode_bc6h(img, bx, by, out),
_ => None,
}
}
fn astc_passthrough(img: &[u8], bx: u32, by: u32, out: &mut [u8]) -> Option<()> {
let total = (bx * by) as usize * 16;
let src = img.get(..total)?;
if out.len() != total {
return None;
}
out.copy_from_slice(src);
Some(())
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_astc_ldr(
img: &[u8],
block: crate::api::AstcBlock,
bx: u32,
by: u32,
lw: u32,
lh: u32,
fmt: i32,
flags: u32,
is_srgb: bool,
has_alpha: bool,
out: &mut [u8],
) -> Option<()> {
use crate::astc::slice::{transcode_ldr, Target4x4};
if fmt == block.passthrough_target().as_i32() {
return astc_passthrough(img, bx, by, out);
}
let t4 = match fmt {
target::RGBA32 => Target4x4::Rgba32,
target::RGB565 => Target4x4::Rgb565,
target::BGR565 => Target4x4::Bgr565,
target::RGBA4444 => Target4x4::Rgba4444,
target::BC1_RGB => Target4x4::Bc1,
target::BC3_RGBA => Target4x4::Bc3,
target::BC4_R => Target4x4::Bc4,
target::BC5_RG => Target4x4::Bc5,
target::EAC_R11 => Target4x4::EacR11,
target::EAC_RG11 => Target4x4::EacRg11,
target::ETC1_RGB => Target4x4::Etc1,
target::ETC2_RGBA => Target4x4::Etc2Rgba,
target::PVRTC1_4_RGB => Target4x4::Pvrtc1Rgb,
target::PVRTC1_4_RGBA => Target4x4::Pvrtc1Rgba,
target::BC7_RGBA => Target4x4::Bc7,
_ => return None,
};
let (sbw, sbh) = block.dims();
transcode_ldr(
img, sbw, sbh, bx, by, lw, lh, t4, flags, is_srgb, has_alpha, out,
)
}
#[allow(clippy::too_many_arguments)]
pub fn transcode_astc_hdr_6x6(
img: &[u8],
bx: u32,
by: u32,
lw: u32,
lh: u32,
fmt: i32,
flags: u32,
out: &mut [u8],
) -> Option<()> {
if fmt == target::ASTC_HDR_6X6_RGBA {
return astc_passthrough(img, bx, by, out);
}
use crate::astc::hdr6x6;
match fmt {
target::BC6H => {
let high_quality = (flags & decode_flags::HIGH_QUALITY) != 0;
hdr6x6::transcode_bc6h(img, bx, by, lw, lh, high_quality, out)
}
target::RGB_HALF => hdr6x6::transcode_half(img, bx, by, lw, lh, 3, out),
target::RGBA_HALF => hdr6x6::transcode_half(img, bx, by, lw, lh, 4, out),
target::RGB_9E5 => hdr6x6::transcode_9e5(img, bx, by, lw, lh, out),
_ => None,
}
}