wow_blp/encode/
mod.rs

1/// Error types for BLP encoding operations
2pub 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
13/// BLP file bytes with vector of external mipmaps encoded
14pub struct BlpWithMipmaps {
15    /// The main BLP file data
16    pub blp_bytes: Vec<u8>,
17    /// External mipmap data (for BLP0 format)
18    pub blp_mipmaps: Vec<Vec<u8>>,
19}
20
21/// Save given BLP image to given path. For BLP0 it will create mipmaps
22/// in the save directory with names like `<root_name>.b<num bitmap>`
23pub 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
43/// Encode BLP0 with external mipmaps
44pub fn encode_blp0(image: &BlpImage) -> Result<BlpWithMipmaps, Error> {
45    encode_blp_with_external(image)
46}
47
48/// Encode BLP1 or BLP2 into bytes
49pub 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    // To produce identical files, reproducting bug that leads to leave 2 bytes
146    // of header uncovered by length
147    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}