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