use std::io::Cursor;
use std::path::Path;
use image::{DynamicImage, ImageReader};
const PNG_SIGNATURE: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
const MNG_SIGNATURE: [u8; 8] = [0x8A, b'M', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
pub fn try_decode_mng(path: &Path) -> Option<DynamicImage> {
if path.extension()?.to_str()?.to_ascii_lowercase() != "mng" {
return None;
}
let data = std::fs::read(path).ok()?;
if data.starts_with(&PNG_SIGNATURE) {
return decode_png_bytes(&data);
}
if !data.starts_with(&MNG_SIGNATURE) {
return None;
}
let png = extract_first_png_from_mng(&data)?;
decode_png_bytes(&png)
}
fn decode_png_bytes(bytes: &[u8]) -> Option<DynamicImage> {
ImageReader::new(Cursor::new(bytes))
.with_guessed_format()
.ok()?
.decode()
.ok()
}
fn extract_first_png_from_mng(data: &[u8]) -> Option<Vec<u8>> {
let mut pos = MNG_SIGNATURE.len();
let mut png = Vec::new();
let mut in_png = false;
while pos + 12 <= data.len() {
let length = u32::from_be_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
let chunk_type: [u8; 4] = data[pos + 4..pos + 8].try_into().ok()?;
let chunk_end = pos + 8 + length + 4;
if chunk_end > data.len() {
break;
}
let chunk_bytes = &data[pos..chunk_end];
if chunk_type == *b"IHDR" {
in_png = true;
png.clear();
png.extend_from_slice(&PNG_SIGNATURE);
png.extend_from_slice(chunk_bytes);
} else if in_png {
if is_mng_control_chunk(&chunk_type) {
if chunk_type == *b"IEND" {
}
break;
}
if is_png_chunk(&chunk_type) {
png.extend_from_slice(chunk_bytes);
if chunk_type == *b"IEND" {
return Some(png);
}
}
} else if is_mng_control_chunk(&chunk_type) && chunk_type == *b"MEND" {
break;
}
pos = chunk_end;
}
if png.len() > PNG_SIGNATURE.len() {
return Some(png);
}
None
}
fn is_mng_control_chunk(chunk_type: &[u8; 4]) -> bool {
matches!(
chunk_type,
b"MHDR"
| b"FRAM"
| b"MOVE"
| b"CLON"
| b"LOOP"
| b"ENDL"
| b"DEFI"
| b"BASE"
| b"BASI"
| b"DHDR"
| b"DROP"
| b"PAST"
| b"MAGN"
| b"MEND"
| b"SEEK"
| b"SHOW"
| b"BACK"
| b"TERM"
| b"SAVE"
| b"DISC"
| b"CBRD"
)
}
fn is_png_chunk(chunk_type: &[u8; 4]) -> bool {
if is_mng_control_chunk(chunk_type) {
return false;
}
chunk_type[0].is_ascii_alphabetic()
&& chunk_type[1].is_ascii_alphabetic()
&& chunk_type[2].is_ascii_alphabetic()
&& chunk_type[3].is_ascii_alphabetic()
}