|
1 | 1 | use crate::error::{Error, Result};
|
2 |
| -use png::{Decoder, Encoder, OutputInfo, Reader}; |
| 2 | +use dmi::{ |
| 3 | + error::DmiError, |
| 4 | + icon::{Icon, Looping}, |
| 5 | +}; |
| 6 | +use png::{text_metadata::ZTXtChunk, Decoder, Encoder, OutputInfo, Reader}; |
| 7 | +use serde::{Deserialize, Serialize}; |
| 8 | +use serde_repr::{Deserialize_repr, Serialize_repr}; |
3 | 9 | use std::{
|
| 10 | + fmt::Write, |
4 | 11 | fs::{create_dir_all, File},
|
5 | 12 | io::BufReader,
|
| 13 | + num::NonZeroU32, |
6 | 14 | path::Path,
|
7 | 15 | };
|
8 | 16 |
|
@@ -30,6 +38,17 @@ byond_fn!(fn dmi_icon_states(path) {
|
30 | 38 | read_states(path).ok()
|
31 | 39 | });
|
32 | 40 |
|
| 41 | +byond_fn!(fn dmi_read_metadata(path) { |
| 42 | + match read_metadata(path) { |
| 43 | + Ok(metadata) => Some(metadata), |
| 44 | + Err(error) => Some(serde_json::to_string(&error.to_string()).unwrap()), |
| 45 | + } |
| 46 | +}); |
| 47 | + |
| 48 | +byond_fn!(fn dmi_inject_metadata(path, metadata) { |
| 49 | + inject_metadata(path, metadata).err() |
| 50 | +}); |
| 51 | + |
33 | 52 | fn strip_metadata(path: &str) -> Result<()> {
|
34 | 53 | let (reader, frame_info, image) = read_png(path)?;
|
35 | 54 | write_png(path, &reader, &frame_info, &image, true)
|
@@ -146,3 +165,140 @@ fn read_states(path: &str) -> Result<String> {
|
146 | 165 | }
|
147 | 166 | Ok(serde_json::to_string(&states)?)
|
148 | 167 | }
|
| 168 | + |
| 169 | +#[derive(Serialize_repr, Deserialize_repr, Clone, Copy)] |
| 170 | +#[repr(u8)] |
| 171 | +enum DmiStateDirCount { |
| 172 | + One = 1, |
| 173 | + Four = 4, |
| 174 | + Eight = 8, |
| 175 | +} |
| 176 | + |
| 177 | +impl TryFrom<u8> for DmiStateDirCount { |
| 178 | + type Error = u8; |
| 179 | + fn try_from(value: u8) -> std::result::Result<Self, Self::Error> { |
| 180 | + match value { |
| 181 | + 1 => Ok(Self::One), |
| 182 | + 4 => Ok(Self::Four), |
| 183 | + 8 => Ok(Self::Eight), |
| 184 | + n => Err(n), |
| 185 | + } |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +#[derive(Serialize, Deserialize)] |
| 190 | +struct DmiState { |
| 191 | + name: String, |
| 192 | + dirs: DmiStateDirCount, |
| 193 | + #[serde(default)] |
| 194 | + delay: Option<Vec<f32>>, |
| 195 | + #[serde(default)] |
| 196 | + rewind: Option<u8>, |
| 197 | + #[serde(default)] |
| 198 | + movement: Option<u8>, |
| 199 | + #[serde(default)] |
| 200 | + loop_count: Option<NonZeroU32>, |
| 201 | + #[serde(default)] |
| 202 | + hotspot: Option<(u32, u32, u32)>, |
| 203 | +} |
| 204 | + |
| 205 | +#[derive(Serialize, Deserialize)] |
| 206 | +struct DmiMetadata { |
| 207 | + width: u32, |
| 208 | + height: u32, |
| 209 | + states: Vec<DmiState>, |
| 210 | +} |
| 211 | + |
| 212 | +fn read_metadata(path: &str) -> Result<String> { |
| 213 | + let dmi = Icon::load(File::open(path).map(BufReader::new)?)?; |
| 214 | + let metadata = DmiMetadata { |
| 215 | + width: dmi.width, |
| 216 | + height: dmi.height, |
| 217 | + states: dmi |
| 218 | + .states |
| 219 | + .iter() |
| 220 | + .map(|state| { |
| 221 | + Ok(DmiState { |
| 222 | + name: state.name.clone(), |
| 223 | + dirs: DmiStateDirCount::try_from(state.dirs).map_err(|n| { |
| 224 | + DmiError::IconState(format!( |
| 225 | + "State \"{}\" has invalid dir count (expected 1, 4, or 8, got {})", |
| 226 | + state.name, n |
| 227 | + )) |
| 228 | + })?, |
| 229 | + delay: state.delay.clone(), |
| 230 | + movement: state.movement.then_some(1), |
| 231 | + rewind: state.rewind.then_some(1), |
| 232 | + loop_count: match state.loop_flag { |
| 233 | + Looping::Indefinitely => None, |
| 234 | + Looping::NTimes(n) => Some(n), |
| 235 | + }, |
| 236 | + hotspot: state.hotspot.map(|hotspot| (hotspot.x, hotspot.y, 1)), |
| 237 | + }) |
| 238 | + }) |
| 239 | + .collect::<Result<Vec<DmiState>>>()?, |
| 240 | + }; |
| 241 | + Ok(serde_json::to_string(&metadata)?) |
| 242 | +} |
| 243 | + |
| 244 | +fn inject_metadata(path: &str, metadata: &str) -> Result<()> { |
| 245 | + let read_file = File::open(path).map(BufReader::new)?; |
| 246 | + let decoder = png::Decoder::new(read_file); |
| 247 | + let mut reader = decoder.read_info().map_err(|_| Error::InvalidPngData)?; |
| 248 | + let new_dmi_metadata: DmiMetadata = serde_json::from_str(metadata)?; |
| 249 | + let mut new_metadata_string = String::new(); |
| 250 | + writeln!(new_metadata_string, "# BEGIN DMI")?; |
| 251 | + writeln!(new_metadata_string, "version = 4.0")?; |
| 252 | + writeln!(new_metadata_string, "\twidth = {}", new_dmi_metadata.width)?; |
| 253 | + writeln!( |
| 254 | + new_metadata_string, |
| 255 | + "\theight = {}", |
| 256 | + new_dmi_metadata.height |
| 257 | + )?; |
| 258 | + for state in new_dmi_metadata.states { |
| 259 | + writeln!(new_metadata_string, "state = \"{}\"", state.name)?; |
| 260 | + writeln!(new_metadata_string, "\tdirs = {}", state.dirs as u8)?; |
| 261 | + writeln!( |
| 262 | + new_metadata_string, |
| 263 | + "\tframes = {}", |
| 264 | + state.delay.as_ref().map_or(1, Vec::len) |
| 265 | + )?; |
| 266 | + if let Some(delay) = state.delay { |
| 267 | + writeln!( |
| 268 | + new_metadata_string, |
| 269 | + "\tdelay = {}", |
| 270 | + delay |
| 271 | + .iter() |
| 272 | + .map(f32::to_string) |
| 273 | + .collect::<Vec<_>>() |
| 274 | + .join(",") |
| 275 | + )?; |
| 276 | + } |
| 277 | + if state.rewind.is_some_and(|r| r != 0) { |
| 278 | + writeln!(new_metadata_string, "\trewind = 1")?; |
| 279 | + } |
| 280 | + if state.movement.is_some_and(|m| m != 0) { |
| 281 | + writeln!(new_metadata_string, "\tmovement = 1")?; |
| 282 | + } |
| 283 | + if let Some(loop_count) = state.loop_count { |
| 284 | + writeln!(new_metadata_string, "\tloop = {loop_count}")?; |
| 285 | + } |
| 286 | + if let Some((hotspot_x, hotspot_y, hotspot_frame)) = state.hotspot { |
| 287 | + writeln!( |
| 288 | + new_metadata_string, |
| 289 | + "\totspot = {hotspot_x},{hotspot_y},{hotspot_frame}" |
| 290 | + )?; |
| 291 | + } |
| 292 | + } |
| 293 | + writeln!(new_metadata_string, "# END DMI")?; |
| 294 | + let mut info = reader.info().clone(); |
| 295 | + info.compressed_latin1_text |
| 296 | + .push(ZTXtChunk::new("Description", new_metadata_string)); |
| 297 | + let mut raw_image_data: Vec<u8> = vec![]; |
| 298 | + while let Some(row) = reader.next_row()? { |
| 299 | + raw_image_data.append(&mut row.data().to_vec()); |
| 300 | + } |
| 301 | + let encoder = png::Encoder::with_info(File::create(path)?, info)?; |
| 302 | + encoder.write_header()?.write_image_data(&raw_image_data)?; |
| 303 | + Ok(()) |
| 304 | +} |
0 commit comments