use crate::core::image::{ImageBlp, MAX_MIPS};
use crate::core::mipmap::Mipmap;
use crate::core::types::SourceKind;
use crate::error::error::BlpError;
use image;
use psd::Psd;
const MAX_POW2: u32 = 8192;
fn is_psd_file(buf: &[u8]) -> bool {
buf.len() >= 4 && &buf[0..4] == b"8BPS"
}
fn get_psd_dimensions(buf: &[u8]) -> Result<(u32, u32), BlpError> {
let psd = Psd::from_bytes(buf).map_err(|e| BlpError::new("error-psd-parse").with_arg("error", e.to_string()))?;
let width = psd.width();
let height = psd.height();
if width == 0 || height == 0 {
return Err(BlpError::new("error-psd-invalid-dimensions")
.with_arg("width", width)
.with_arg("height", height));
}
Ok((width, height))
}
fn pow2_list_up_to(max_v: u32) -> Vec<u32> {
let mut v = 1u32;
let mut out = Vec::new();
while v <= max_v {
out.push(v);
if v == u32::MAX / 2 {
break;
}
v <<= 1;
}
out
}
fn pick_pow2_cover(w0: u32, h0: u32) -> (u32, u32) {
debug_assert!(w0 > 0 && h0 > 0);
let ws = pow2_list_up_to(MAX_POW2);
let hs = pow2_list_up_to(MAX_POW2);
let w0f = w0 as f64;
let h0f = h0 as f64;
let ar0 = w0f / h0f;
let mut best = None::<(f64, f64, u64, u32, u32)>;
for &ww in &ws {
for &hh in &hs {
let s = (ww as f64 / w0f).max(hh as f64 / h0f); if s < 1.0 {
continue;
}
let ar = ww as f64 / hh as f64;
let ar_diff = (ar - ar0).abs();
let area = (ww as u64) * (hh as u64);
let cand = (s, ar_diff, area, ww, hh);
match best {
None => best = Some(cand),
Some(cur) => {
if cand.0 < cur.0 || (cand.0 == cur.0 && (cand.1 < cur.1 || (cand.1 == cur.1 && cand.2 < cur.2))) {
best = Some(cand);
}
}
}
}
}
if let Some((_s, _ard, _area, ww, hh)) = best { (ww, hh) } else { (w0, h0) }
}
fn create_mipmaps(base_w: u32, base_h: u32, first_image: Option<image::RgbaImage>) -> Vec<Mipmap> {
let levels = (32 - base_w.max(base_h).leading_zeros()) as usize;
let mut mipmaps = Vec::with_capacity(MAX_MIPS);
let (mut w, mut h) = (base_w, base_h);
for i in 0..MAX_MIPS {
if i < levels {
mipmaps.push(Mipmap { width: w, height: h, image: if i == 0 { first_image.clone() } else { None }, offset: 0, length: 0 });
w = (w / 2).max(1);
h = (h / 2).max(1);
} else {
mipmaps.push(Mipmap::default());
}
}
mipmaps
}
impl ImageBlp {
pub fn from_buf_image(buf: &[u8]) -> Result<Self, BlpError> {
let (w0, h0) = if is_psd_file(buf) {
get_psd_dimensions(buf)?
} else {
let reader = image::ImageReader::new(std::io::Cursor::new(buf))
.with_guessed_format()
.map_err(|_| BlpError::new("error-image-load"))?;
let dimensions = reader
.into_dimensions()
.map_err(|_| BlpError::new("error-image-load"))?;
dimensions
};
if w0 == 0 || h0 == 0 {
return Err(BlpError::new("error-image-empty")
.with_arg("width", w0)
.with_arg("height", h0));
}
let (base_w, base_h) = pick_pow2_cover(w0, h0);
let mipmaps = create_mipmaps(base_w, base_h, None);
Ok(ImageBlp { width: base_w, height: base_h, mipmaps, source: SourceKind::Image, ..Default::default() })
}
pub fn from_rgba_impl(rgba_buf: &[u8], width: u32, height: u32) -> Result<Self, BlpError> {
if width == 0 || height == 0 {
return Err(BlpError::new("error-image-empty")
.with_arg("width", width)
.with_arg("height", height));
}
let expected_size = (width * height * 4) as usize;
if rgba_buf.len() != expected_size {
return Err(BlpError::new("error-rgba-buffer-size")
.with_arg("expected", expected_size)
.with_arg("actual", rgba_buf.len()));
}
let rgba_image = image::RgbaImage::from_raw(width, height, rgba_buf.to_vec()).ok_or_else(|| BlpError::new("error-rgba-image-creation"))?;
let (base_w, base_h) = pick_pow2_cover(width, height);
let mipmaps = create_mipmaps(base_w, base_h, Some(rgba_image));
Ok(ImageBlp { width: base_w, height: base_h, mipmaps, source: SourceKind::Image, ..Default::default() })
}
}