1use std::collections::HashMap;
2use std::hash::{Hash, Hasher};
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use crate::assets::{load_image_any, RgbaImage};
7use anyhow::{Context, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct ImageId(pub u32);
11
12impl ImageId {
13 pub fn index(self) -> usize {
14 self.0 as usize
15 }
16}
17
18#[derive(Debug, Clone)]
19struct ImageKey {
20 path: PathBuf,
21 frame_index: usize,
22}
23
24impl PartialEq for ImageKey {
25 fn eq(&self, other: &Self) -> bool {
26 self.path == other.path && self.frame_index == other.frame_index
27 }
28}
29
30impl Eq for ImageKey {}
31
32impl Hash for ImageKey {
33 fn hash<H: Hasher>(&self, state: &mut H) {
34 self.path.hash(state);
35 self.frame_index.hash(state);
36 }
37}
38
39#[derive(Debug)]
40pub struct ImageManager {
41 project_dir: PathBuf,
42 current_append_dir: String,
43 key_to_id: HashMap<ImageKey, ImageId>,
44 solid_to_id: HashMap<(u8, u8, u8, u8), ImageId>,
45 images: Vec<ImageEntry>,
46}
47
48#[derive(Debug, Clone)]
49struct ImageEntry {
50 img: Arc<RgbaImage>,
51 version: u64,
52}
53
54#[derive(Debug, Clone)]
55pub struct DebugImageInfo {
56 pub id: ImageId,
57 pub width: u32,
58 pub height: u32,
59 pub version: u64,
60 pub source_path: Option<PathBuf>,
61 pub frame_index: Option<usize>,
62}
63
64impl ImageManager {
65 pub fn new(project_dir: PathBuf) -> Self {
66 Self {
67 project_dir,
68 current_append_dir: String::new(),
69 key_to_id: HashMap::new(),
70 solid_to_id: HashMap::new(),
71 images: Vec::new(),
72 }
73 }
74
75 pub fn project_dir(&self) -> &Path {
76 &self.project_dir
77 }
78
79 pub fn current_append_dir(&self) -> &str {
80 &self.current_append_dir
81 }
82
83 pub fn set_current_append_dir(&mut self, append_dir: impl Into<String>) {
84 self.current_append_dir = append_dir.into();
85 }
86
87 pub fn get(&self, id: ImageId) -> Option<&Arc<RgbaImage>> {
88 self.images.get(id.index()).map(|e| &e.img)
89 }
90
91 pub fn get_entry(&self, id: ImageId) -> Option<(&Arc<RgbaImage>, u64)> {
92 self.images.get(id.index()).map(|e| (&e.img, e.version))
93 }
94
95 pub fn solid_rgba(&mut self, rgba: (u8, u8, u8, u8)) -> ImageId {
100 if let Some(id) = self.solid_to_id.get(&rgba) {
101 return *id;
102 }
103 let img = RgbaImage {
104 width: 1,
105 height: 1,
106 center_x: 0,
107 center_y: 0,
108 rgba: vec![rgba.0, rgba.1, rgba.2, rgba.3],
109 };
110 let id = ImageId(self.images.len() as u32);
111 self.images.push(ImageEntry {
112 img: Arc::new(img),
113 version: 0,
114 });
115 self.solid_to_id.insert(rgba, id);
116 id
117 }
118
119 pub fn load_bg(&mut self, name: &str) -> Result<ImageId> {
123 let (path, _ty) = crate::resource::find_bg_image_with_append_dir(
124 &self.project_dir,
125 &self.current_append_dir,
126 name,
127 )
128 .with_context(|| format!("find bg resource {name}"))?;
129 self.load_file(&path, 0)
130 }
131
132 pub fn load_bg_frame(&mut self, name: &str, frame_index: usize) -> Result<ImageId> {
134 let (path, _ty) = crate::resource::find_bg_image_with_append_dir(
135 &self.project_dir,
136 &self.current_append_dir,
137 name,
138 )
139 .with_context(|| format!("find bg resource {name}"))?;
140 self.load_file(&path, frame_index)
141 }
142
143 pub fn load_g00(&mut self, name: &str, frame_index: u32) -> Result<ImageId> {
147 let (path, _ty) = crate::resource::find_g00_image_with_append_dir(
148 &self.project_dir,
149 &self.current_append_dir,
150 name,
151 )
152 .with_context(|| format!("find g00 resource {name}"))?;
153 self.load_file(&path, frame_index as usize)
154 }
155
156 pub fn load_file(&mut self, path: &Path, frame_index: usize) -> Result<ImageId> {
158 let resolved = if path.is_absolute() {
159 path.to_path_buf()
160 } else if path.is_file() {
161 path.to_path_buf()
167 } else {
168 self.project_dir.join(path)
169 };
170
171 let key = ImageKey {
172 path: resolved.clone(),
173 frame_index,
174 };
175
176 if let Some(id) = self.key_to_id.get(&key) {
177 return Ok(*id);
178 }
179
180 let img = load_image_any(&resolved, frame_index)
181 .with_context(|| format!("load image {:?}", resolved))?;
182 let id = self.insert_image(img);
183 self.key_to_id.insert(key, id);
184 Ok(id)
185 }
186
187 pub fn insert_image(&mut self, img: RgbaImage) -> ImageId {
189 let id = ImageId(self.images.len() as u32);
190 self.images.push(ImageEntry {
191 img: Arc::new(img),
192 version: 0,
193 });
194 id
195 }
196
197 pub fn insert_image_arc(&mut self, img: Arc<RgbaImage>) -> ImageId {
198 let id = ImageId(self.images.len() as u32);
199 self.images.push(ImageEntry { img, version: 0 });
200 id
201 }
202
203 pub fn replace_image(&mut self, id: ImageId, img: RgbaImage) -> Result<()> {
207 let Some(entry) = self.images.get_mut(id.index()) else {
208 anyhow::bail!("replace_image: invalid ImageId {}", id.index());
209 };
210 entry.img = Arc::new(img);
211 entry.version = entry.version.wrapping_add(1);
212 Ok(())
213 }
214
215 pub fn replace_image_arc(&mut self, id: ImageId, img: Arc<RgbaImage>) -> Result<()> {
216 let Some(entry) = self.images.get_mut(id.index()) else {
217 anyhow::bail!("replace_image_arc: invalid ImageId {}", id.index());
218 };
219 entry.img = img;
220 entry.version = entry.version.wrapping_add(1);
221 Ok(())
222 }
223
224 pub fn debug_image_info(&self, id: ImageId) -> Option<DebugImageInfo> {
225 let entry = self.images.get(id.index())?;
226 let mut source_path = None;
227 let mut frame_index = None;
228 for (key, key_id) in &self.key_to_id {
229 if *key_id == id {
230 source_path = Some(key.path.clone());
231 frame_index = Some(key.frame_index);
232 break;
233 }
234 }
235 Some(DebugImageInfo {
236 id,
237 width: entry.img.width,
238 height: entry.img.height,
239 version: entry.version,
240 source_path,
241 frame_index,
242 })
243 }
244}