msg_tool\scripts\bgi\image/
img.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::img::*;
6use anyhow::Result;
7
8fn try_parse(buf: &[u8]) -> Result<u8> {
9 let mut reader = MemReaderRef::new(buf);
10 let width = reader.read_u16()?;
11 let height = reader.read_u16()?;
12 let bpp = reader.read_u16()?;
13 let _flag = reader.read_u16()?;
14 let padding = reader.read_u64()?;
15 if padding != 0 {
16 return Err(anyhow::anyhow!("Invalid padding: {}", padding));
17 }
18 if width == 0 || height == 0 {
19 return Err(anyhow::anyhow!("Invalid dimensions: {}x{}", width, height));
20 }
21 if width > 4096 || height > 4096 {
22 return Err(anyhow::anyhow!(
23 "Dimensions too large: {}x{}",
24 width,
25 height
26 ));
27 }
28 if bpp != 8 && bpp != 24 && bpp != 32 {
29 return Err(anyhow::anyhow!("Unsupported BPP: {}", bpp));
30 }
31 Ok(1)
32}
33
34#[derive(Debug)]
35pub struct BgiImageBuilder {}
37
38impl BgiImageBuilder {
39 pub const fn new() -> Self {
41 BgiImageBuilder {}
42 }
43}
44
45impl ScriptBuilder for BgiImageBuilder {
46 fn default_encoding(&self) -> Encoding {
47 Encoding::Cp932
48 }
49
50 fn build_script(
51 &self,
52 data: Vec<u8>,
53 _filename: &str,
54 _encoding: Encoding,
55 _archive_encoding: Encoding,
56 config: &ExtraConfig,
57 _archive: Option<&Box<dyn Script>>,
58 ) -> Result<Box<dyn Script>> {
59 Ok(Box::new(BgiImage::new(data, config)?))
60 }
61
62 fn extensions(&self) -> &'static [&'static str] {
63 &[]
64 }
65
66 fn script_type(&self) -> &'static ScriptType {
67 &ScriptType::BGIImg
68 }
69
70 fn is_image(&self) -> bool {
71 true
72 }
73
74 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
75 if buf_len >= 0x10 {
76 return try_parse(&buf[0..0x10]).ok();
77 }
78 None
79 }
80
81 fn can_create_image_file(&self) -> bool {
82 true
83 }
84
85 fn create_image_file<'a>(
86 &'a self,
87 data: ImageData,
88 _filename: &str,
89 writer: Box<dyn WriteSeek + 'a>,
90 options: &ExtraConfig,
91 ) -> Result<()> {
92 create_image(data, writer, options.bgi_img_scramble.unwrap_or(false))
93 }
94}
95
96#[derive(Debug)]
97pub struct BgiImage {
99 data: MemReader,
100 width: u32,
101 height: u32,
102 color_type: ImageColorType,
103 is_scrambled: bool,
104 opt_is_scrambled: Option<bool>,
105}
106
107fn create_image<'a>(
108 mut data: ImageData,
109 mut writer: Box<dyn WriteSeek + 'a>,
110 scrambled: bool,
111) -> Result<()> {
112 writer.write_u16(data.width as u16)?;
113 writer.write_u16(data.height as u16)?;
114 if data.depth != 8 {
115 return Err(anyhow::anyhow!("Unsupported image depth: {}", data.depth));
116 }
117 match data.color_type {
118 ImageColorType::Bgr => {}
119 ImageColorType::Bgra => {}
120 ImageColorType::Grayscale => {}
121 ImageColorType::Rgb => {
122 convert_rgb_to_bgr(&mut data)?;
123 }
124 ImageColorType::Rgba => {
125 convert_rgba_to_bgra(&mut data)?;
126 }
127 }
128 let bpp = data.color_type.bpp(8);
129 writer.write_u16(bpp)?;
130 let flag = if scrambled { 1 } else { 0 };
131 writer.write_u16(flag)?;
132 writer.write_u64(0)?; let stride = data.width as usize * ((data.color_type.bpp(8) as usize + 7) / 8);
134 let buf_size = stride * data.height as usize;
135 if scrambled {
136 let bpp = data.color_type.bpp(1) as usize;
137 for i in 0..bpp {
138 let mut dst = i;
139 let mut incr = 0u8;
140 let mut h = data.height;
141 while h > 0 {
142 for _ in 0..data.width {
143 writer.write_u8(data.data[dst].wrapping_sub(incr))?;
144 incr = data.data[dst];
145 dst += bpp;
146 }
147 h -= 1;
148 if h == 0 {
149 break;
150 }
151 dst += stride;
152 let mut pos = dst;
153 for _ in 0..data.width {
154 pos -= bpp;
155 writer.write_u8(data.data[pos].wrapping_sub(incr))?;
156 incr = data.data[pos];
157 }
158 h -= 1;
159 }
160 }
161 } else {
162 writer.write_all(&data.data[..buf_size])?;
165 }
166 Ok(())
167}
168
169impl BgiImage {
170 pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
175 let mut reader = MemReader::new(buf);
176 let width = reader.read_u16()? as u32;
177 let height = reader.read_u16()? as u32;
178 let bpp = reader.read_u16()?;
179 let color_type = match bpp {
180 8 => ImageColorType::Grayscale,
181 24 => ImageColorType::Bgr,
182 32 => ImageColorType::Bgra,
183 _ => return Err(anyhow::anyhow!("Unsupported BPP: {}", bpp)),
184 };
185 let flag = reader.read_u16()?;
186 let padding = reader.read_u64()?;
187 if padding != 0 {
188 return Err(anyhow::anyhow!("Invalid padding: {}", padding));
189 }
190 let is_scrambled = flag != 0;
191
192 Ok(BgiImage {
193 data: reader,
194 width,
195 height,
196 color_type,
197 is_scrambled,
198 opt_is_scrambled: config.bgi_img_scramble,
199 })
200 }
201}
202
203impl Script for BgiImage {
204 fn default_output_script_type(&self) -> OutputScriptType {
205 OutputScriptType::Json
206 }
207
208 fn default_format_type(&self) -> FormatOptions {
209 FormatOptions::None
210 }
211
212 fn is_image(&self) -> bool {
213 true
214 }
215
216 fn export_image(&self) -> Result<ImageData> {
217 let stride = self.width as usize * ((self.color_type.bpp(8) as usize + 7) / 8);
218 let buf_size = stride * self.height as usize;
219 let mut data = Vec::with_capacity(buf_size);
220 data.resize(buf_size, 0);
221 if self.is_scrambled {
222 let mut reader = self.data.to_ref();
223 reader.pos = 0x10;
224 let bpp = self.color_type.bpp(1) as usize;
225 for i in 0..bpp {
226 let mut dst = i;
227 let mut incr = 0u8;
228 let mut h = self.height;
229 while h > 0 {
230 for _ in 0..self.width {
231 incr = incr.wrapping_add(reader.read_u8()?);
232 data[dst] = incr;
233 dst += bpp;
234 }
235 h -= 1;
236 if h == 0 {
237 break;
238 }
239 dst += stride;
240 let mut pos = dst;
241 for _ in 0..self.width {
242 pos -= bpp;
243 incr = incr.wrapping_add(reader.read_u8()?);
244 data[pos] = incr;
245 }
246 h -= 1;
247 }
248 }
249 } else {
250 self.data.cpeek_exact_at(0x10, &mut data)?;
251 }
252 Ok(ImageData {
253 width: self.width,
254 height: self.height,
255 color_type: self.color_type,
256 depth: 8,
257 data,
258 })
259 }
260
261 fn import_image<'a>(
262 &'a self,
263 data: ImageData,
264 _filename: &str,
265 file: Box<dyn WriteSeek + 'a>,
266 ) -> Result<()> {
267 create_image(
268 data,
269 file,
270 self.opt_is_scrambled.unwrap_or(self.is_scrambled),
271 )
272 }
273}