1pub mod error;
3mod primitives;
4
5use super::types::*;
6use crate::path::make_mipmap_path;
7use error::Error;
8use log::*;
9use primitives::push_le_u32;
10use std::iter::{repeat_n, zip};
11use std::path::Path;
12
13pub struct BlpWithMipmaps {
15 pub blp_bytes: Vec<u8>,
17 pub blp_mipmaps: Vec<Vec<u8>>,
19}
20
21pub fn save_blp<Q>(image: &BlpImage, path: Q) -> Result<(), Error>
24where
25 Q: AsRef<Path>,
26{
27 let BlpWithMipmaps {
28 blp_bytes,
29 blp_mipmaps,
30 } = encode_blp_with_external(image)?;
31 std::fs::write(&path, blp_bytes).map_err(|e| Error::FileSystem(path.as_ref().to_owned(), e))?;
32 if !blp_mipmaps.is_empty() {
33 for (i, image) in blp_mipmaps.iter().enumerate() {
34 let mipmap_path = make_mipmap_path(&path, i)
35 .ok_or_else(|| Error::FileNameInvalid(path.as_ref().to_owned()))?;
36 std::fs::write(&mipmap_path, image)
37 .map_err(|e| Error::FileSystem(mipmap_path.to_owned(), e))?;
38 }
39 }
40 Ok(())
41}
42
43pub fn encode_blp0(image: &BlpImage) -> Result<BlpWithMipmaps, Error> {
45 encode_blp_with_external(image)
46}
47
48pub fn encode_blp(image: &BlpImage) -> Result<Vec<u8>, Error> {
50 let res = encode_blp_with_external(image)?;
51 Ok(res.blp_bytes)
52}
53
54fn encode_blp_with_external(image: &BlpImage) -> Result<BlpWithMipmaps, Error> {
55 let mut output = vec![];
56 let mut mipmaps = vec![];
57 trace!("Encode header");
58 encode_header(&image.header, &mut output)?;
59 trace!("Encode content");
60 encode_content(&image.header, &image.content, &mut output, &mut mipmaps)?;
61 Ok(BlpWithMipmaps {
62 blp_bytes: output,
63 blp_mipmaps: mipmaps,
64 })
65}
66
67fn encode_header(header: &BlpHeader, output: &mut Vec<u8>) -> Result<(), Error> {
68 output.extend(header.version.to_magic());
69 push_le_u32(header.content.into(), output);
70 match header.flags {
71 BlpFlags::Old { alpha_bits, .. } => {
72 push_le_u32(alpha_bits, output);
73 }
74 BlpFlags::Blp2 {
75 compression,
76 alpha_bits,
77 alpha_type,
78 has_mipmaps,
79 } => {
80 output.push(compression.into());
81 output.push(alpha_bits);
82 output.push(alpha_type.into());
83 output.push(has_mipmaps);
84 }
85 }
86
87 if header.width > BLP_MAX_WIDTH {
88 return Err(Error::WidthTooHigh(header.width));
89 }
90 push_le_u32(header.width, output);
91 if header.height > BLP_MAX_HEIGHT {
92 return Err(Error::WidthTooHigh(header.height));
93 }
94 push_le_u32(header.height, output);
95
96 if let BlpFlags::Old {
97 extra, has_mipmaps, ..
98 } = header.flags
99 {
100 push_le_u32(extra, output);
101 push_le_u32(has_mipmaps, output);
102 }
103
104 match header.mipmap_locator {
105 MipmapLocator::Internal { offsets, sizes } => {
106 for offset in offsets {
107 push_le_u32(offset, output);
108 }
109 for size in sizes {
110 push_le_u32(size, output);
111 }
112 }
113 MipmapLocator::External => {
114 if header.version > BlpVersion::Blp0 {
115 error!("External mipmaps are not supported for versions higher than BLP0!");
116 return Err(Error::ExternalMipmapsNotSupported(header.version));
117 }
118 }
119 }
120 Ok(())
121}
122
123fn encode_content(
124 header: &BlpHeader,
125 content: &BlpContent,
126 output: &mut Vec<u8>,
127 mipmaps: &mut Vec<Vec<u8>>,
128) -> Result<(), Error> {
129 match content {
130 BlpContent::Jpeg(jpeg_content) => encode_jpeg(header, jpeg_content, output, mipmaps),
131 BlpContent::Raw1(raw1_content) => encode_raw1(header, raw1_content, output, mipmaps),
132 BlpContent::Raw3(raw3_content) => encode_raw3(header, raw3_content, output, mipmaps),
133 BlpContent::Dxt1(dxt1_content) => encode_dxtn(header, &dxt1_content.images, output),
134 BlpContent::Dxt3(dxt3_content) => encode_dxtn(header, &dxt3_content.images, output),
135 BlpContent::Dxt5(dxt5_content) => encode_dxtn(header, &dxt5_content.images, output),
136 }
137}
138
139fn encode_jpeg(
140 header: &BlpHeader,
141 content: &BlpJpeg,
142 output: &mut Vec<u8>,
143 mipmaps: &mut Vec<Vec<u8>>,
144) -> Result<(), Error> {
145 push_le_u32((content.header.len() - 2) as u32, output);
148 output.extend(content.header.iter());
149
150 match header.mipmap_locator {
151 MipmapLocator::External => {
152 for image in content.images.iter() {
153 mipmaps.push(image.clone())
154 }
155 }
156 MipmapLocator::Internal { offsets, sizes } => {
157 let mut pairs: Vec<(u32, u32)> = zip(offsets, sizes)
158 .take(header.mipmaps_count() + 1)
159 .filter(|(_, size)| *size > 0)
160 .collect();
161 pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).expect("number cmp"));
162
163 trace!(
164 "Mipmaps ordered: {:?}, images count: {}",
165 pairs,
166 content.images.len()
167 );
168 for (i, ((offset, size), image)) in zip(pairs, content.images.iter()).enumerate() {
169 trace!("Writing mipmap {i}");
170 let padding = (if offset as usize >= output.len() {
171 Ok(offset as usize - output.len())
172 } else {
173 Err(Error::InvalidOffset {
174 mipmap: i,
175 offset: offset as usize,
176 filled: output.len(),
177 })
178 })?;
179 if padding > 0 {
180 output.extend(repeat_n(0, padding));
181 }
182 if image.len() != size as usize {
183 return Err(Error::InvalidMipmapSize {
184 mipmap: i,
185 in_header: size as usize,
186 actual: image.len(),
187 });
188 }
189 output.extend(image);
190 }
191 }
192 }
193 Ok(())
194}
195
196fn encode_raw<T, F>(
197 header: &BlpHeader,
198 cmap: &[u32],
199 images: &[T],
200 mut encoder: F,
201 output: &mut Vec<u8>,
202 mipmaps: &mut Vec<Vec<u8>>,
203) -> Result<(), Error>
204where
205 F: FnMut(&T, &mut Vec<u8>),
206{
207 trace!("Header: {header:?}");
208
209 for c in cmap.iter() {
210 push_le_u32(*c, output);
211 }
212
213 match header.mipmap_locator {
214 MipmapLocator::External => {
215 for image in images.iter() {
216 let mut image_bytes = vec![];
217 encoder(image, &mut image_bytes);
218 mipmaps.push(image_bytes);
219 }
220 }
221 MipmapLocator::Internal { offsets, sizes } => {
222 let mut pairs: Vec<(u32, u32)> = zip(offsets, sizes)
223 .take(header.mipmaps_count() + 1)
224 .filter(|(_, size)| *size > 0)
225 .collect();
226 pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).expect("number cmp"));
227
228 trace!(
229 "Mipmaps ordered: {:?}, images count: {}",
230 pairs,
231 images.len()
232 );
233 for (i, ((offset, size), image)) in zip(pairs, images.iter()).enumerate() {
234 trace!("Writing mipmap {i}");
235 let padding = (if offset as usize >= output.len() {
236 Ok(offset as usize - output.len())
237 } else {
238 Err(Error::InvalidOffset {
239 mipmap: i,
240 offset: offset as usize,
241 filled: output.len(),
242 })
243 })?;
244 if padding > 0 {
245 output.extend(repeat_n(0, padding));
246 }
247 let mut image_bytes = vec![];
248 encoder(image, &mut image_bytes);
249 if image_bytes.len() != size as usize {
250 return Err(Error::InvalidMipmapSize {
251 mipmap: i,
252 in_header: size as usize,
253 actual: image_bytes.len(),
254 });
255 }
256 output.extend(image_bytes);
257 }
258 }
259 }
260 Ok(())
261}
262
263fn encode_raw1(
264 header: &BlpHeader,
265 content: &BlpRaw1,
266 output: &mut Vec<u8>,
267 mipmaps: &mut Vec<Vec<u8>>,
268) -> Result<(), Error> {
269 encode_raw(
270 header,
271 &content.cmap,
272 &content.images,
273 encode_raw1_image,
274 output,
275 mipmaps,
276 )
277}
278
279fn encode_raw3(
280 header: &BlpHeader,
281 content: &BlpRaw3,
282 output: &mut Vec<u8>,
283 mipmaps: &mut Vec<Vec<u8>>,
284) -> Result<(), Error> {
285 encode_raw(
286 header,
287 &content.cmap,
288 &content.images,
289 encode_raw3_image,
290 output,
291 mipmaps,
292 )
293}
294
295fn encode_raw1_image(image: &Raw1Image, output: &mut Vec<u8>) {
296 output.extend(image.indexed_rgb.iter());
297 output.extend(image.indexed_alpha.iter());
298}
299
300fn encode_raw3_image(image: &Raw3Image, output: &mut Vec<u8>) {
301 for pixel in image.pixels.iter() {
302 push_le_u32(*pixel, output);
303 }
304}
305
306fn encode_dxtn(
307 header: &BlpHeader,
308 images: &[DxtnImage],
309 output: &mut Vec<u8>,
310) -> Result<(), Error> {
311 trace!("Header: {header:?}");
312 let (offsets, sizes) = if let MipmapLocator::Internal { offsets, sizes } = header.mipmap_locator
313 {
314 (offsets, sizes)
315 } else {
316 return Err(Error::ExternalMipmapsNotSupported(BlpVersion::Blp2));
317 };
318
319 let mut pairs: Vec<(u32, u32)> = zip(offsets, sizes)
320 .take(header.mipmaps_count() + 1)
321 .filter(|(_, size)| *size > 0)
322 .collect();
323 pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).expect("number cmp"));
324
325 trace!(
326 "Mipmaps ordered: {:?}, images count: {}",
327 pairs,
328 images.len()
329 );
330
331 for (i, ((offset, size), image)) in zip(pairs, images.iter()).enumerate() {
332 trace!("Writing mipmap {i}");
333 let padding = (if offset as usize >= output.len() {
334 Ok(offset as usize - output.len())
335 } else {
336 Err(Error::InvalidOffset {
337 mipmap: i,
338 offset: offset as usize,
339 filled: output.len(),
340 })
341 })?;
342 if padding > 0 {
343 output.extend(repeat_n(0, padding));
344 }
345 if image.content.len() != size as usize {
346 return Err(Error::InvalidMipmapSize {
347 mipmap: i,
348 in_header: size as usize,
349 actual: image.len(),
350 });
351 }
352 output.extend(&image.content);
353 }
354 Ok(())
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_sorting() {
363 let offsets = vec![356, 123, 567, 421];
364 let sizes = vec![1, 2, 3, 4];
365 let mut pairs: Vec<(u32, u32)> = zip(offsets, sizes).collect();
366 pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).expect("number cmp"));
367 assert_eq!(pairs, vec![(123, 2), (356, 1), (421, 4), (567, 3)]);
368 }
369}