image_blp/parser/
mod.rs

1mod direct;
2pub mod error;
3mod header;
4mod jpeg;
5pub mod types;
6
7#[cfg(test)]
8mod tests;
9
10use super::types::*;
11use crate::path::make_mipmap_path;
12use direct::parse_direct_content;
13pub use error::{Error, LoadError};
14use header::parse_header;
15use jpeg::parse_jpeg_content;
16use nom::error::context;
17use std::path::{Path, PathBuf};
18use types::Parser;
19
20/// Read BLP file from file system. If it BLP0 format, uses the mipmaps near the root file.
21pub fn load_blp<Q>(path: Q) -> Result<BlpImage, LoadError>
22where
23    Q: AsRef<Path>,
24{
25    let input =
26        std::fs::read(&path).map_err(|e| LoadError::FileSystem(path.as_ref().to_owned(), e))?;
27    load_blp_ex(Some(path), &input)
28}
29
30/// Read BLP file from buffer(Vec<u8>). If it BLP0 format, uses the mipmaps in the temp dir.
31///
32/// Since: 1.2.0
33pub fn load_blp_from_buf(buf: &[u8]) -> Result<BlpImage, LoadError> {
34    let input = buf;
35    let path: Option<PathBuf> = None;
36    load_blp_ex(path, input)
37}
38
39fn load_blp_ex<Q>(path: Option<Q>, input: &[u8]) -> Result<BlpImage, LoadError>
40where
41    Q: AsRef<Path>,
42{
43    // We have to preload all mipmaps in memory as we are constrained with Nom 'a lifetime that
44    // should be equal of lifetime of root input stream.
45    let mut mipmaps = vec![];
46    if let Some(path) = path.as_ref() {
47        for i in 0..16 {
48            let mipmap_path = make_mipmap_path(path, i)
49                .ok_or_else(|| LoadError::InvalidFilename(path.as_ref().to_owned()))?;
50            if mipmap_path.is_file() {
51                let mipmap = std::fs::read(mipmap_path)
52                    .map_err(|e| LoadError::FileSystem(path.as_ref().to_owned(), e))?;
53                mipmaps.push(mipmap);
54            } else {
55                break;
56            }
57        }
58    }
59
60    let image = match parse_blp_with_externals(input, |i| preloaded_mipmaps(&mipmaps, i)) {
61        Ok((_, image)) => Ok(image),
62        Err(nom::Err::Incomplete(needed)) => Err(LoadError::Incomplete(needed)),
63        Err(nom::Err::Error(e)) => Err(LoadError::Parsing(format!("{}", e))),
64        Err(nom::Err::Failure(e)) => Err(LoadError::Parsing(format!("{}", e))),
65    }?;
66    Ok(image)
67}
68
69/// Parse BLP file from slice and fail if we require parse external files (case BLP0)
70pub fn parse_blp(input: &[u8]) -> Parser<BlpImage> {
71    parse_blp_with_externals(input, no_mipmaps)
72}
73
74/// Helper for `parse_blp` when no external mipmaps are needed
75pub fn no_mipmaps<'a>(_: usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> {
76    Ok(None)
77}
78
79/// Helper for `parse_blp` when external mipmaps are located in filesystem near the
80/// root file and loaded in memory when reading the main file.
81pub fn preloaded_mipmaps(
82    mipmaps: &[Vec<u8>],
83    i: usize,
84) -> Result<Option<&[u8]>, Box<dyn std::error::Error>> {
85    if i >= mipmaps.len() {
86        Ok(None)
87    } else {
88        Ok(Some(&mipmaps[i]))
89    }
90}
91
92/// Parse BLP file from slice and use user provided callback to read mipmaps
93pub fn parse_blp_with_externals<'a, F>(
94    root_input: &'a [u8],
95    external_mipmaps: F,
96) -> Parser<'a, BlpImage>
97where
98    F: FnMut(usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> + Clone,
99{
100    // Parse header
101    let (input, header) = context("header", parse_header)(root_input)?;
102
103    // Parse image content
104    let (input, content) = context("image content", |input| {
105        parse_content(&header, external_mipmaps.clone(), root_input, input)
106    })(input)?;
107
108    Ok((input, BlpImage { header, content }))
109}
110
111fn parse_content<'a, F>(
112    blp_header: &BlpHeader,
113    external_mipmaps: F,
114    original_input: &'a [u8],
115    input: &'a [u8],
116) -> Parser<'a, BlpContent>
117where
118    F: FnMut(usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> + Clone,
119{
120    match blp_header.content {
121        BlpContentTag::Jpeg => {
122            let (input, content) = context("jpeg content", |input| {
123                parse_jpeg_content(blp_header, external_mipmaps.clone(), original_input, input)
124            })(input)?;
125            Ok((input, BlpContent::Jpeg(content)))
126        }
127        BlpContentTag::Direct => {
128            let (input, content) = context("direct content", |input| {
129                parse_direct_content(blp_header, external_mipmaps.clone(), original_input, input)
130            })(input)?;
131            Ok((input, content))
132        }
133    }
134}