1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::img::*;
6use crate::utils::psd::*;
7use crate::utils::struct_pack::*;
8use anyhow::Result;
9use msg_tool_macro::*;
10use std::io::{Read, Seek, Write};
11
12#[derive(StructPack, StructUnpack, Debug, Clone)]
13struct DpngHeader {
14 magic: [u8; 4],
16 _unk1: u32,
18 tile_count: u32,
19 image_width: u32,
20 image_height: u32,
21}
22
23#[derive(StructPack, StructUnpack, Debug, Clone)]
24struct Tile {
25 x: u32,
26 y: u32,
27 width: u32,
28 height: u32,
29 size: u32,
30 _unk: u64,
31 #[pack_vec_len(self.size)]
32 #[unpack_vec_len(size)]
33 png_data: Vec<u8>,
34}
35
36#[derive(StructPack, StructUnpack, Debug, Clone)]
37struct DpngFile {
38 header: DpngHeader,
39 #[pack_vec_len(self.header.tile_count)]
40 #[unpack_vec_len(header.tile_count)]
41 tiles: Vec<Tile>,
42}
43
44#[derive(Debug)]
45pub struct DpngImageBuilder {}
47
48impl DpngImageBuilder {
49 pub fn new() -> Self {
50 Self {}
51 }
52}
53
54impl ScriptBuilder for DpngImageBuilder {
55 fn default_encoding(&self) -> Encoding {
56 Encoding::Utf8
57 }
58
59 fn build_script(
60 &self,
61 buf: Vec<u8>,
62 _filename: &str,
63 _encoding: Encoding,
64 _archive_encoding: Encoding,
65 config: &ExtraConfig,
66 _archive: Option<&Box<dyn Script>>,
67 ) -> Result<Box<dyn Script>> {
68 Ok(Box::new(DpngImage::new(MemReader::new(buf), config)?))
69 }
70
71 fn extensions(&self) -> &'static [&'static str] {
72 &["png"]
73 }
74
75 fn script_type(&self) -> &'static ScriptType {
76 &ScriptType::QlieDpng
77 }
78
79 fn is_image(&self) -> bool {
80 true
81 }
82
83 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
84 if buf_len >= 4 && buf.starts_with(b"DPNG") {
85 Some(20)
86 } else {
87 None
88 }
89 }
90
91 fn can_create_image_file(&self) -> bool {
92 true
93 }
94
95 fn create_image_file<'a>(
96 &'a self,
97 data: ImageData,
98 filename: &str,
99 writer: Box<dyn WriteSeek + 'a>,
100 options: &ExtraConfig,
101 ) -> Result<()> {
102 if options.qlie_dpng_use_raw_png {
103 create_raw_png_image(filename, writer, None)
104 } else {
105 create_image(data, writer, options)
106 }
107 }
108}
109
110#[derive(Debug)]
111pub struct DpngImage {
112 img: DpngFile,
113 config: ExtraConfig,
114}
115
116impl DpngImage {
117 pub fn new<T: Read + Seek>(mut data: T, config: &ExtraConfig) -> Result<Self> {
118 let img = DpngFile::unpack(&mut data, false, Encoding::Utf8, &None)?;
119 if img.header.magic != *b"DPNG" {
120 anyhow::bail!("Not a valid DPNG image");
121 }
122 if img.tiles.is_empty() {
123 anyhow::bail!("DPNG image has no tiles");
124 }
125 Ok(DpngImage {
126 img,
127 config: config.clone(),
128 })
129 }
130}
131
132impl Script for DpngImage {
133 fn default_output_script_type(&self) -> OutputScriptType {
134 OutputScriptType::Custom
135 }
136
137 fn is_output_supported(&self, output: OutputScriptType) -> bool {
138 matches!(output, OutputScriptType::Custom)
139 }
140
141 fn default_format_type(&self) -> FormatOptions {
142 FormatOptions::None
143 }
144
145 fn custom_output_extension<'a>(&'a self) -> &'a str {
146 "psd"
147 }
148
149 fn is_image(&self) -> bool {
150 if self.config.qlie_dpng_psd {
151 false
152 } else {
153 true
154 }
155 }
156
157 fn export_image(&self) -> Result<ImageData> {
158 let (idx, tile) = self
159 .img
160 .tiles
161 .iter()
162 .enumerate()
163 .find(|(_, t)| t.size != 0)
164 .ok_or_else(|| anyhow::anyhow!("DPNG image has no valid tiles with PNG data"))?;
165 let mut base = load_png(MemReaderRef::new(&tile.png_data))?;
166 convert_to_rgba(&mut base)?;
167 let mut base = draw_on_canvas(
168 base,
169 self.img.header.image_width,
170 self.img.header.image_height,
171 tile.x,
172 tile.y,
173 )?;
174 for tile in &self.img.tiles[idx + 1..] {
175 if tile.size == 0 {
176 continue;
177 }
178 let mut diff = load_png(MemReaderRef::new(&tile.png_data))?;
179 convert_to_rgba(&mut diff)?;
180 draw_on_image(&mut base, &diff, tile.x, tile.y)?;
181 }
182 Ok(base)
183 }
184
185 fn import_image<'a>(
186 &'a self,
187 data: ImageData,
188 filename: &str,
189 file: Box<dyn WriteSeek + 'a>,
190 ) -> Result<()> {
191 if self.config.qlie_dpng_use_raw_png {
192 let img = load_png(std::fs::File::open(filename)?)?;
193 if img.width != self.img.header.image_width
194 || img.height != self.img.header.image_height
195 {
196 eprintln!(
197 "Warning: Image dimensions do not match original DPNG image (expected {}x{}, got {}x{})",
198 self.img.header.image_width,
199 self.img.header.image_height,
200 img.width,
201 img.height
202 );
203 crate::COUNTER.inc_warning();
204 }
205 create_raw_png_image(filename, file, Some(img))?;
206 } else {
207 if data.width != self.img.header.image_width
208 || data.height != self.img.header.image_height
209 {
210 eprintln!(
211 "Warning: Image dimensions do not match original DPNG image (expected {}x{}, got {}x{})",
212 self.img.header.image_width,
213 self.img.header.image_height,
214 data.width,
215 data.height
216 );
217 crate::COUNTER.inc_warning();
218 }
219 create_image(data, file, &self.config)?;
220 }
221 Ok(())
222 }
223
224 fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
225 let mut psd = PsdWriter::new(
226 self.img.header.image_width,
227 self.img.header.image_height,
228 ImageColorType::Rgba,
229 8,
230 encoding,
231 )?
232 .compress(self.config.psd_compress)
233 .zlib_compression_level(self.config.zlib_compression_level);
234 let (idx, tile) = self
235 .img
236 .tiles
237 .iter()
238 .enumerate()
239 .find(|(_, t)| t.size != 0)
240 .ok_or_else(|| anyhow::anyhow!("DPNG image has no valid tiles with PNG data"))?;
241 let mut base = load_png(MemReaderRef::new(&tile.png_data))?;
242 convert_to_rgba(&mut base)?;
243 psd.add_layer(
244 &format!("layer_{}", idx),
245 tile.x,
246 tile.y,
247 base.clone(),
248 None,
249 )?;
250 let mut base = draw_on_canvas(
251 base,
252 self.img.header.image_width,
253 self.img.header.image_height,
254 tile.x,
255 tile.y,
256 )?;
257 let mut idx2 = idx;
258 for tile in &self.img.tiles[idx + 1..] {
259 idx2 += 1;
260 if tile.size == 0 {
261 continue;
262 }
263 let mut diff = load_png(MemReaderRef::new(&tile.png_data))?;
264 convert_to_rgba(&mut diff)?;
265 draw_on_image(&mut base, &diff, tile.x, tile.y)?;
266 psd.add_layer(&format!("layer_{}", idx2), tile.x, tile.y, diff, None)?;
267 }
268 let file = std::fs::File::create(filename)?;
269 let mut writer = std::io::BufWriter::new(file);
270 psd.save(base, &mut writer)?;
271 Ok(())
272 }
273
274 fn custom_import<'a>(
275 &'a self,
276 custom_filename: &'a str,
277 mut file: Box<dyn WriteSeek + 'a>,
278 encoding: Encoding,
279 output_encoding: Encoding,
280 ) -> Result<()> {
281 let rfile = std::fs::File::open(custom_filename)?;
282 let mut reader = std::io::BufReader::new(rfile);
283 let psd = PsdReader::new(&mut reader, output_encoding)?;
284 let width = psd.width();
285 let height = psd.height();
286 let layers = psd.read_normal_layers()?;
287 let header = DpngHeader {
288 magic: *b"DPNG",
289 _unk1: 1,
290 tile_count: layers.len() as u32,
291 image_width: width,
292 image_height: height,
293 };
294 let mut tiles = Vec::new();
295 for layer in layers {
296 let data = layer.image()?;
297 let width = data.width;
298 let height = data.height;
299 let mut png_data = MemWriter::new();
300 encode_img_writer(data, ImageOutputType::Png, &mut png_data, &self.config)?;
301 let png_data = png_data.into_inner();
302 let tile = Tile {
303 x: layer.left() as u32,
304 y: layer.top() as u32,
305 width,
306 height,
307 size: png_data.len() as u32,
308 _unk: 0,
309 png_data,
310 };
311 tiles.push(tile);
312 }
313 let dpng = DpngFile { header, tiles };
314 dpng.pack(&mut file, false, encoding, &None)?;
315 Ok(())
316 }
317}
318
319fn create_raw_png_image<'a>(
320 filename: &str,
321 mut file: Box<dyn WriteSeek + 'a>,
322 img: Option<ImageData>,
323) -> Result<()> {
324 let img = match img {
325 Some(img) => img,
326 None => load_png(std::fs::File::open(filename)?)?,
327 };
328 let header = DpngHeader {
329 magic: *b"DPNG",
330 _unk1: 1,
331 tile_count: 1,
332 image_width: img.width,
333 image_height: img.height,
334 };
335 let png_data = crate::utils::files::read_file(filename)?;
336 let tile = Tile {
337 x: 0,
338 y: 0,
339 width: img.width,
340 height: img.height,
341 size: png_data.len() as u32,
342 _unk: 0,
343 png_data,
344 };
345 let dpng = DpngFile {
346 header,
347 tiles: vec![tile],
348 };
349 dpng.pack(&mut file, false, Encoding::Utf8, &None)?;
350 Ok(())
351}
352
353fn create_image<'a>(
354 image: ImageData,
355 mut writer: Box<dyn WriteSeek + 'a>,
356 config: &ExtraConfig,
357) -> Result<()> {
358 let header = DpngHeader {
359 magic: *b"DPNG",
360 _unk1: 1,
361 tile_count: 1,
362 image_width: image.width,
363 image_height: image.height,
364 };
365 let mut png_data = MemWriter::new();
366 let width = image.width;
367 let height = image.height;
368 encode_img_writer(image, ImageOutputType::Png, &mut png_data, config)?;
369 let png_data = png_data.into_inner();
370 let tile = Tile {
371 x: 0,
372 y: 0,
373 width,
374 height,
375 size: png_data.len() as u32,
376 _unk: 0,
377 png_data,
378 };
379 let dpng = DpngFile {
380 header,
381 tiles: vec![tile],
382 };
383 dpng.pack(&mut writer, false, Encoding::Utf8, &None)?;
384 Ok(())
385}