use std::path::Path;
use std::time::Duration;
use image::{AnimationDecoder, DynamicImage};
pub fn decode_static(path: &Path) -> Option<DynamicImage> {
image::ImageReader::open(path)
.ok()?
.with_guessed_format()
.ok()?
.decode()
.ok()
}
pub fn dimensions(path: &Path) -> Option<(u32, u32)> {
image::ImageReader::open(path)
.ok()?
.with_guessed_format()
.ok()?
.into_dimensions()
.ok()
}
const MIN_FRAME_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_FRAME_DELAY: Duration = Duration::from_millis(100);
pub fn decode_gif(path: &Path) -> Option<Vec<(DynamicImage, Duration)>> {
let file = std::fs::File::open(path).ok()?;
let decoder = image::codecs::gif::GifDecoder::new(std::io::BufReader::new(file)).ok()?;
let frames = decoder.into_frames().collect_frames().ok()?;
if frames.len() < 2 {
return None; }
let out: Vec<(DynamicImage, Duration)> = frames
.into_iter()
.map(|f| {
let delay: Duration = f.delay().into();
let delay = if delay < MIN_FRAME_DELAY {
DEFAULT_FRAME_DELAY
} else {
delay
};
(DynamicImage::ImageRgba8(f.into_buffer()), delay)
})
.collect();
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_gif_real_sample_has_multiple_frames() {
let p = Path::new("samples/sample.gif");
if !p.exists() {
return; }
let frames = decode_gif(p).expect("sample.gif はアニメーションとしてデコードできるはず");
assert!(frames.len() > 1, "アニメ GIF は 2 フレーム以上");
let (w0, h0) = (frames[0].0.width(), frames[0].0.height());
assert!(w0 > 0 && h0 > 0);
assert!(frames
.iter()
.all(|(img, d)| { img.width() == w0 && img.height() == h0 && *d >= MIN_FRAME_DELAY }));
}
#[test]
fn decode_static_reads_png_and_rejects_non_image() {
let dir = std::env::temp_dir().join("konoma_decode_static_test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let png = dir.join("tiny.png");
image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(7, 3, image::Rgb([9, 9, 9])))
.save(&png)
.unwrap();
let img = decode_static(&png).expect("PNG はデコードできる");
assert_eq!((img.width(), img.height()), (7, 3));
let bad = dir.join("notimg.png");
std::fs::write(&bad, b"definitely not an image").unwrap();
assert!(decode_static(&bad).is_none(), "非画像は None");
assert!(decode_static(&dir.join("missing.png")).is_none());
std::fs::remove_dir_all(&dir).ok();
}
}