image_blp/encode/
mod.rs

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
12/// BLP file bytes with vector of external mipmaps encoded
13pub struct BlpWithMipmaps {
14    pub blp_bytes: Vec<u8>,
15    pub blp_mipmaps: Vec<Vec<u8>>,
16}
17
18/// Save given BLP image to given path. For BLP0 it will create mipmaps
19/// in the save directory with names like `<root_name>.b<num bitmap>`
20pub 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
40/// Encode BLP0 with external mipmaps
41pub fn encode_blp0(image: &BlpImage) -> Result<BlpWithMipmaps, Error> {
42    encode_blp_with_external(image)
43}
44
45/// Encode BLP1 or BLP2 into bytes
46pub 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    // To produce identical files, reproducting bug that leads to leave 2 bytes
143    // of header uncovered by length
144    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}