auto_thumbnail/decode/
mod.rs1pub(crate) mod ffmpeg_decode;
6mod ffmpeg_image;
7pub(crate) mod ffmpeg_log;
8pub(crate) mod ffmpeg_probe;
9mod ico;
10mod j2k;
11mod jxl;
12mod mng;
13mod reader;
14pub mod tone_map;
15
16use std::path::Path;
17use std::sync::Once;
18
19use image::DynamicImage;
20use thiserror::Error;
21
22static REGISTER_EXTRAS: Once = Once::new();
23
24fn ensure_extras_registered() {
25 REGISTER_EXTRAS.call_once(|| {
26 image_extras::register();
27 });
28}
29
30#[derive(Error, Debug)]
31pub enum DecodeError {
32 #[error("无法解码图像")]
33 Unsupported,
34}
35
36pub fn decode_image(path: &Path) -> Result<DynamicImage, DecodeError> {
38 ensure_extras_registered();
39 #[cfg(feature = "video")]
40 ffmpeg_log::init_ffmpeg_logging();
41 reader::try_decode_reader(path)
42 .or_else(|| jxl::try_decode_jxl(path))
43 .or_else(|| j2k::try_decode_j2k(path))
44 .or_else(|| j2k::try_decode_j2k_rgba(path))
45 .or_else(|| j2k::try_decode_j2k_via_ffmpeg(path))
46 .or_else(|| ico::try_decode_ico(path))
47 .or_else(|| mng::try_decode_mng(path))
48 .or_else(|| ffmpeg_image::try_decode_via_ffmpeg(path))
49 .ok_or(DecodeError::Unsupported)
50}
51
52pub fn decode_and_thumbnail(path: &Path, max_dim: u32) -> Result<DynamicImage, DecodeError> {
54 let img = decode_image(path)?;
55 Ok(if img.width() > max_dim || img.height() > max_dim {
56 img.thumbnail(max_dim, max_dim)
57 } else {
58 img
59 })
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use std::path::Path;
66
67 #[test]
68 fn decode_reported_hdr_samples() {
69 let samples = [
70 r"D:\xsmspace\data\1\file\图像\photo_hdr.hdr",
71 r"D:\xsmspace\data\2\file\图像\sample.hdr",
72 ];
73 for sample in samples {
74 let path = Path::new(sample);
75 if !path.exists() {
76 eprintln!("跳过不存在样本: {sample}");
77 continue;
78 }
79 let img = decode_image(path).unwrap_or_else(|_| panic!("无法解码 HDR: {sample}"));
80 assert!(img.width() > 0 && img.height() > 0);
81 }
82 }
83
84 #[test]
86 fn decode_reported_avif_best_effort() {
87 let samples = [
88 r"D:\xsmspace\data\2\_clone_sample-files\images\sample.avif",
89 r"D:\xsmspace\data\1\file\图像\hdr_cosmos.avif",
90 r"D:\xsmspace\data\2\file\图像\sample.avif",
91 ];
92 for sample in samples {
93 let path = Path::new(sample);
94 if !path.exists() {
95 continue;
96 }
97 match decode_image(path) {
98 Ok(img) => assert!(img.width() > 0 && img.height() > 0),
99 Err(_) => eprintln!("AVIF 纯 Rust 解码跳过(可能为 HDR AVIF): {sample}"),
100 }
101 }
102 }
103
104 #[test]
106 fn decode_j2k_ffmpeg_fallback_samples() {
107 let samples = [
108 r"D:\xsmspace\data\1\file\图像\d2_colr.j2c",
109 r"D:\xsmspace\data\1\file\图像\imagery.jpc",
110 r"D:\xsmspace\data\1\file\图像\balloon.jp2",
111 ];
112 for sample in samples {
113 let path = Path::new(sample);
114 if !path.exists() {
115 eprintln!("跳过不存在: {sample}");
116 continue;
117 }
118 let img = decode_image(path).unwrap_or_else(|_| panic!("必须能解码: {sample}"));
119 assert!(img.width() > 0 && img.height() > 0, "{sample}");
120 eprintln!("OK: {sample} {}x{}", img.width(), img.height());
121 }
122 }
123
124 #[test]
125 fn decode_logged_failure_samples_best_effort() {
126 let required = [r"D:\xsmspace\data\1\file\图像\animated.mng"];
127 for sample in required {
128 let path = Path::new(sample);
129 if !path.exists() {
130 eprintln!("跳过不存在样本: {sample}");
131 continue;
132 }
133 let img = decode_image(path).unwrap_or_else(|_| panic!("必须能解码: {sample}"));
134 assert!(img.width() > 0 && img.height() > 0, "{sample}");
135 }
136
137 let samples = [
138 r"D:\xsmspace\data\1\file\图像\BLOOD02.pcx",
139 r"D:\xsmspace\data\1\file\图像\balloon.jp2",
140 r"D:\xsmspace\data\1\file\图像\imagery.jpc",
141 r"D:\xsmspace\data\1\file\图像\cropped_16bit.j2k",
142 r"D:\xsmspace\data\1\file\图像\d2_colr.j2c",
143 r"D:\xsmspace\data\1\file\图像\balloon.jpf",
144 r"D:\xsmspace\data\1\file\图像\deerstalker.cur",
145 r"D:\xsmspace\data\1\file\图像\hdr_cosmos.avif",
146 r"D:\xsmspace\data\1\file\图像\g4-multi.tiff",
147 r"D:\xsmspace\data\1\file\图像\dscf0013.tif",
148 ];
149 let mut ok_count = 0;
150 for sample in samples {
151 let path = Path::new(sample);
152 if !path.exists() {
153 continue;
154 }
155 if decode_image(path).is_ok() {
156 ok_count += 1;
157 }
158 }
159 assert!(
160 ok_count >= 8,
161 "auto-thumbnail 解码样本成功率过低: {ok_count}/{}",
162 samples.len()
163 );
164 }
165}