1use crate::core::image::{ImageBlp, MAX_MIPS};
2use crate::core::mipmap::Mipmap;
3use crate::core::types::SourceKind;
4use crate::error::error::BlpError;
5use image;
6use psd::Psd;
7
8const MAX_POW2: u32 = 8192; fn is_psd_file(buf: &[u8]) -> bool {
12 buf.len() >= 4 && &buf[0..4] == b"8BPS"
14}
15
16fn get_psd_dimensions(buf: &[u8]) -> Result<(u32, u32), BlpError> {
18 let psd = Psd::from_bytes(buf).map_err(|e| BlpError::new("error-psd-parse").with_arg("error", e.to_string()))?;
19
20 let width = psd.width();
21 let height = psd.height();
22
23 if width == 0 || height == 0 {
24 return Err(BlpError::new("error-psd-invalid-dimensions")
25 .with_arg("width", width)
26 .with_arg("height", height));
27 }
28
29 Ok((width, height))
30}
31
32fn pow2_list_up_to(max_v: u32) -> Vec<u32> {
33 let mut v = 1u32;
34 let mut out = Vec::new();
35 while v <= max_v {
36 out.push(v);
37 if v == u32::MAX / 2 {
38 break;
39 }
40 v <<= 1;
41 }
42 out
43}
44
45fn pick_pow2_cover(w0: u32, h0: u32) -> (u32, u32) {
52 debug_assert!(w0 > 0 && h0 > 0);
53 let ws = pow2_list_up_to(MAX_POW2);
54 let hs = pow2_list_up_to(MAX_POW2);
55
56 let w0f = w0 as f64;
57 let h0f = h0 as f64;
58 let ar0 = w0f / h0f;
59
60 let mut best = None::<(f64, f64, u64, u32, u32)>; for &ww in &ws {
63 for &hh in &hs {
66 let s = (ww as f64 / w0f).max(hh as f64 / h0f); if s < 1.0 {
68 continue;
70 }
71 let ar = ww as f64 / hh as f64;
72 let ar_diff = (ar - ar0).abs();
73 let area = (ww as u64) * (hh as u64);
74
75 let cand = (s, ar_diff, area, ww, hh);
76 match best {
77 None => best = Some(cand),
78 Some(cur) => {
79 if cand.0 < cur.0 || (cand.0 == cur.0 && (cand.1 < cur.1 || (cand.1 == cur.1 && cand.2 < cur.2))) {
81 best = Some(cand);
82 }
83 }
84 }
85 }
86 }
87
88 if let Some((_s, _ard, _area, ww, hh)) = best { (ww, hh) } else { (w0, h0) }
89}
90
91fn create_mipmaps(base_w: u32, base_h: u32, first_image: Option<image::RgbaImage>) -> Vec<Mipmap> {
94 let levels = (32 - base_w.max(base_h).leading_zeros()) as usize;
97
98 let mut mipmaps = Vec::with_capacity(MAX_MIPS);
99 let (mut w, mut h) = (base_w, base_h);
100
101 for i in 0..MAX_MIPS {
102 if i < levels {
103 mipmaps.push(Mipmap { width: w, height: h, image: if i == 0 { first_image.clone() } else { None }, offset: 0, length: 0 });
104 w = (w / 2).max(1);
106 h = (h / 2).max(1);
107 } else {
108 mipmaps.push(Mipmap::default());
110 }
111 }
112
113 mipmaps
114}
115
116impl ImageBlp {
117 pub fn from_buf_image(buf: &[u8]) -> Result<Self, BlpError> {
126 let (w0, h0) = if is_psd_file(buf) {
128 get_psd_dimensions(buf)?
129 } else {
130 let reader = image::ImageReader::new(std::io::Cursor::new(buf))
131 .with_guessed_format()
132 .map_err(|_| BlpError::new("error-image-load"))?;
133 let dimensions = reader
134 .into_dimensions()
135 .map_err(|_| BlpError::new("error-image-load"))?;
136 dimensions
137 };
138
139 if w0 == 0 || h0 == 0 {
140 return Err(BlpError::new("error-image-empty")
141 .with_arg("width", w0)
142 .with_arg("height", h0));
143 }
144
145 let (base_w, base_h) = pick_pow2_cover(w0, h0);
146
147 let mipmaps = create_mipmaps(base_w, base_h, None);
148
149 Ok(ImageBlp { width: base_w, height: base_h, mipmaps, source: SourceKind::Image, ..Default::default() })
150 }
151
152 pub fn from_rgba_impl(rgba_buf: &[u8], width: u32, height: u32) -> Result<Self, BlpError> {
156 if width == 0 || height == 0 {
157 return Err(BlpError::new("error-image-empty")
158 .with_arg("width", width)
159 .with_arg("height", height));
160 }
161
162 let expected_size = (width * height * 4) as usize;
163 if rgba_buf.len() != expected_size {
164 return Err(BlpError::new("error-rgba-buffer-size")
165 .with_arg("expected", expected_size)
166 .with_arg("actual", rgba_buf.len()));
167 }
168
169 let rgba_image = image::RgbaImage::from_raw(width, height, rgba_buf.to_vec()).ok_or_else(|| BlpError::new("error-rgba-image-creation"))?;
171
172 let (base_w, base_h) = pick_pow2_cover(width, height);
173
174 let mipmaps = create_mipmaps(base_w, base_h, Some(rgba_image));
175
176 Ok(ImageBlp { width: base_w, height: base_h, mipmaps, source: SourceKind::Image, ..Default::default() })
177 }
178}