1use std::path::Path;
2
3use crate::config::Config;
4use crate::processor::ProcessWithConfig;
5
6const DEFAULT_PIPED_OUTPUT_FORMAT: image::ImageOutputFormat = image::ImageOutputFormat::BMP;
7
8#[derive(Debug, Default)]
9pub struct EncodingFormatDecider;
10
11impl EncodingFormatDecider {
12 fn get_output_extension(config: &Config) -> Result<String, String> {
14 match &config.output {
15 Some(v) => {
16 let path = &Path::new(v);
17 let extension = path.extension();
18
19 extension
20 .and_then(std::ffi::OsStr::to_str)
21 .ok_or_else(|| "No extension was found".into())
22 .map(str::to_lowercase)
23 }
24 None => Err("No valid output path found (type: efd/ext)".into()),
25 }
26 }
27
28 fn sample_encoding(config: &Config) -> image::pnm::SampleEncoding {
29 if config.encoding_settings.pnm_settings.ascii {
30 image::pnm::SampleEncoding::Ascii
31 } else {
32 image::pnm::SampleEncoding::Binary
33 }
34 }
35
36 fn determine_format_string(config: &Config) -> Result<String, String> {
38 if let Some(v) = &config.forced_output_format {
39 Ok(v.to_lowercase())
40 } else {
41 EncodingFormatDecider::get_output_extension(config)
42 }
43 }
44
45 fn determine_format_from_str(
46 config: &Config,
47 identifier: &str,
48 ) -> Result<image::ImageOutputFormat, String> {
49 match identifier {
50 "bmp" => Ok(image::ImageOutputFormat::BMP),
51 "gif" => Ok(image::ImageOutputFormat::GIF),
52 "ico" => Ok(image::ImageOutputFormat::ICO),
53 "jpeg" | "jpg" => Ok(image::ImageOutputFormat::JPEG(
54 config.encoding_settings.jpeg_settings.quality,
55 )),
56 "png" => Ok(image::ImageOutputFormat::PNG),
57 "pbm" => {
58 let sample_encoding = EncodingFormatDecider::sample_encoding(&config);
59
60 Ok(image::ImageOutputFormat::PNM(
61 image::pnm::PNMSubtype::Bitmap(sample_encoding),
62 ))
63 }
64 "pgm" => {
65 let sample_encoding = EncodingFormatDecider::sample_encoding(&config);
66
67 Ok(image::ImageOutputFormat::PNM(
68 image::pnm::PNMSubtype::Graymap(sample_encoding),
69 ))
70 }
71 "ppm" => {
72 let sample_encoding = EncodingFormatDecider::sample_encoding(&config);
73
74 Ok(image::ImageOutputFormat::PNM(
75 image::pnm::PNMSubtype::Pixmap(sample_encoding),
76 ))
77 }
78 "pam" => Ok(image::ImageOutputFormat::PNM(
79 image::pnm::PNMSubtype::ArbitraryMap,
80 )),
81 _ => Err(format!(
82 "No supported image output format was found, input: {}.",
83 identifier
84 )),
85 }
86 }
87
88 fn compute_format(config: &Config) -> Result<image::ImageOutputFormat, String> {
89 if config.output.is_none() && config.forced_output_format.is_none() {
90 return Ok(DEFAULT_PIPED_OUTPUT_FORMAT);
91 }
92
93 let format = EncodingFormatDecider::determine_format_string(&config);
101
102 EncodingFormatDecider::determine_format_from_str(&config, &format?)
105 }
106}
107
108impl ProcessWithConfig<Result<image::ImageOutputFormat, String>> for EncodingFormatDecider {
109 fn process(&self, config: &Config) -> Result<image::ImageOutputFormat, String> {
110 EncodingFormatDecider::compute_format(&config)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use crate::config::{
117 Config, ConfigItem, FormatEncodingSettings, JPEGEncodingSettings, PNMEncodingSettings,
118 };
119 use crate::processor::mod_test_includes::*;
120
121 use super::*;
122
123 const OUTPUT_NO_EXT: &str = "dont_care";
124 const INPUT_FORMATS: &[&str] = &[
125 "bmp", "gif", "ico", "jpg", "jpeg", "png", "pbm", "pgm", "ppm", "pam",
126 ];
127 const EXPECTED_VALUES: &[image::ImageOutputFormat] = &[
128 image::ImageOutputFormat::BMP,
129 image::ImageOutputFormat::GIF,
130 image::ImageOutputFormat::ICO,
131 image::ImageOutputFormat::JPEG(80),
132 image::ImageOutputFormat::JPEG(80),
133 image::ImageOutputFormat::PNG,
134 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Bitmap(
135 image::pnm::SampleEncoding::Binary,
136 )),
137 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Graymap(
138 image::pnm::SampleEncoding::Binary,
139 )),
140 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Pixmap(
141 image::pnm::SampleEncoding::Binary,
142 )),
143 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::ArbitraryMap),
144 ];
145
146 fn setup_dummy_config(
147 output: &str,
148 ext: &str,
149 force_format: Option<String>,
150 pnm_ascii: bool,
151 ) -> Config {
152 Config {
153 tool_name: env!("CARGO_PKG_NAME"),
154 licenses: vec![],
155 forced_output_format: force_format,
156 disable_automatic_color_type_adjustment: false,
157
158 encoding_settings: FormatEncodingSettings {
159 jpeg_settings: JPEGEncodingSettings::new_result((false, None))
160 .expect("Invalid jpeg settings"),
161 pnm_settings: PNMEncodingSettings::new(pnm_ascii),
162 },
163
164 output: setup_output_path(&format!("{}.{}", output, ext))
165 .to_str()
166 .map(|v| v.into()),
167
168 application_specific: vec![
169 ConfigItem::OptionStringItem(None),
170 ConfigItem::OptionStringItem(None),
171 ],
172 }
173 }
174
175 fn test_with_extension(ext: &str, expected: &image::ImageOutputFormat) {
176 let output_name = &format!("encoding_processing_w_ext_{}", OUTPUT_NO_EXT);
177
178 let settings = setup_dummy_config(output_name, ext, None, false);
179
180 let conversion_processor = EncodingFormatDecider::default();
181 let result = conversion_processor
182 .process(&settings)
183 .expect("Failed to compute image format.");
184
185 assert_eq!(*expected, result);
186 }
187
188 fn test_with_force_format(format: &str, expected: &image::ImageOutputFormat) {
189 let output_name = &format!("encoding_processing_w_ff_{}", OUTPUT_NO_EXT);
190
191 let settings = setup_dummy_config(output_name, "", Some(String::from(format)), false);
192
193 let conversion_processor = EncodingFormatDecider::default();
194 let result = conversion_processor
195 .process(&settings)
196 .expect("Failed to compute image format.");
197
198 assert_eq!(*expected, result);
199 }
200
201 #[test]
202 fn test_with_extensions_with_defaults() {
203 let zipped = INPUT_FORMATS.iter().zip(EXPECTED_VALUES.iter());
204
205 for (ext, exp) in zipped {
206 println!("testing `test_with_extension`: {}", ext);
207 test_with_extension(ext, exp);
208 }
209 }
210
211 #[test]
212 fn test_with_force_formats_with_defaults() {
213 let zipped = INPUT_FORMATS.iter().zip(EXPECTED_VALUES.iter());
214
215 for (format, exp) in zipped {
216 println!("testing `test_with_force_format`: {}", format);
217 test_with_force_format(format, exp);
218 }
219 }
220
221 #[test]
222 fn test_with_extension_jpg_and_force_format_png() {
223 let output_name = &format!("encoding_processing_w_ext_and_ff_{}", OUTPUT_NO_EXT);
224
225 let settings = setup_dummy_config(output_name, "jpg", Some(String::from("png")), false);
226
227 let conversion_processor = EncodingFormatDecider::default();
228 let result = conversion_processor
229 .process(&settings)
230 .expect("Failed to compute image format.");
231
232 assert_eq!(image::ImageOutputFormat::PNG, result);
233 }
234
235 #[test]
236 fn test_with_extension_and_ascii_pbm() {
237 let output_name = &format!("encoding_processing_ascii_pbm_{}", OUTPUT_NO_EXT);
238
239 let settings = setup_dummy_config(output_name, "pbm", None, true);
240
241 let conversion_processor = EncodingFormatDecider::default();
242 let result = conversion_processor
243 .process(&settings)
244 .expect("Failed to compute image format.");
245
246 assert_eq!(
247 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Bitmap(
248 image::pnm::SampleEncoding::Ascii,
249 )),
250 result
251 );
252 }
253
254 #[test]
255 fn test_with_extension_and_ascii_pgm() {
256 let output_name = &format!("encoding_processing_ascii_pgm_{}", OUTPUT_NO_EXT);
257
258 let settings = setup_dummy_config(output_name, "pgm", None, true);
259
260 let conversion_processor = EncodingFormatDecider::default();
261 let result = conversion_processor
262 .process(&settings)
263 .expect("Failed to compute image format.");
264
265 assert_eq!(
266 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Graymap(
267 image::pnm::SampleEncoding::Ascii,
268 )),
269 result
270 );
271 }
272
273 #[test]
274 fn test_with_extension_and_ascii_ppm() {
275 let output_name = &format!("encoding_processing_ascii_ppm_{}", OUTPUT_NO_EXT);
276
277 let settings = setup_dummy_config(output_name, "ppm", None, true);
278
279 let conversion_processor = EncodingFormatDecider::default();
280 let result = conversion_processor
281 .process(&settings)
282 .expect("Failed to compute image format.");
283
284 assert_eq!(
285 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::Pixmap(
286 image::pnm::SampleEncoding::Ascii,
287 )),
288 result
289 );
290 }
291
292 #[test]
293 fn test_with_extension_and_ascii_pam_doesnt_care() {
294 let output_name = &format!(
296 "encoding_processing_ascii_pam_doesnt_care_{}",
297 OUTPUT_NO_EXT
298 );
299
300 let settings = setup_dummy_config(output_name, "pam", None, true);
301
302 let conversion_processor = EncodingFormatDecider::default();
303 let result = conversion_processor
304 .process(&settings)
305 .expect("Failed to compute image format.");
306
307 assert_eq!(
308 image::ImageOutputFormat::PNM(image::pnm::PNMSubtype::ArbitraryMap),
309 result
310 );
311 }
312
313 #[test]
314 fn test_jpeg_custom_quality() {
315 let jpeg_conf = Config {
316 tool_name: env!("CARGO_PKG_NAME"),
317 licenses: vec![],
318 forced_output_format: None,
319 disable_automatic_color_type_adjustment: false,
320
321 encoding_settings: FormatEncodingSettings {
322 jpeg_settings: JPEGEncodingSettings::new_result((true, Some("40")))
323 .expect("Invalid jpeg settings"),
324 pnm_settings: PNMEncodingSettings::new(false),
325 },
326
327 output: setup_output_path("encoding_processing_jpeg_quality_valid.jpg")
328 .to_str()
329 .map(|v| v.into()),
330
331 application_specific: vec![
332 ConfigItem::OptionStringItem(None),
333 ConfigItem::OptionStringItem(None),
334 ],
335 };
336
337 let conversion_processor = EncodingFormatDecider::default();
338 let result = conversion_processor
339 .process(&jpeg_conf)
340 .expect("Failed to compute image format.");
341
342 assert_eq!(image::ImageOutputFormat::JPEG(40), result);
343 }
344
345 #[should_panic]
346 #[test]
347 fn test_output_unsupported_extension() {
348 let jpeg_conf = Config {
349 tool_name: env!("CARGO_PKG_NAME"),
350 licenses: vec![],
351 forced_output_format: None,
352 disable_automatic_color_type_adjustment: false,
353
354 encoding_settings: FormatEncodingSettings {
355 jpeg_settings: JPEGEncodingSettings { quality: 90 },
356 pnm_settings: PNMEncodingSettings::new(false),
357 },
358
359 output: setup_output_path("encoding_processing_invalid.😉")
360 .to_str()
361 .map(|v| v.into()),
362
363 application_specific: vec![
364 ConfigItem::OptionStringItem(None),
365 ConfigItem::OptionStringItem(None),
366 ],
367 };
368
369 let conversion_processor = EncodingFormatDecider::default();
370 let _ = conversion_processor
371 .process(&jpeg_conf)
372 .expect("Failed to compute image format.");
373 }
374
375 #[should_panic]
376 #[test]
377 fn test_output_no_ext_or_ff() {
378 let jpeg_conf = Config {
379 tool_name: env!("CARGO_PKG_NAME"),
380 licenses: vec![],
381 forced_output_format: None,
382 disable_automatic_color_type_adjustment: false,
383
384 encoding_settings: FormatEncodingSettings {
385 jpeg_settings: JPEGEncodingSettings { quality: 90 },
386 pnm_settings: PNMEncodingSettings::new(false),
387 },
388
389 output: setup_output_path("encoding_processing_invalid.")
390 .to_str()
391 .map(|v| v.into()),
392
393 application_specific: vec![
394 ConfigItem::OptionStringItem(None),
395 ConfigItem::OptionStringItem(None),
396 ],
397 };
398
399 let conversion_processor = EncodingFormatDecider::default();
400 let _ = conversion_processor
401 .process(&jpeg_conf)
402 .expect("Failed to compute image format.");
403 }
404
405 #[should_panic]
406 #[test]
407 fn test_output_unsupported_ff_with_ext() {
408 let jpeg_conf = Config {
409 tool_name: env!("CARGO_PKG_NAME"),
410 licenses: vec![],
411 forced_output_format: Some("OiOi".into()), disable_automatic_color_type_adjustment: false,
413
414 encoding_settings: FormatEncodingSettings {
415 jpeg_settings: JPEGEncodingSettings { quality: 90 },
416 pnm_settings: PNMEncodingSettings::new(false),
417 },
418
419 output: setup_output_path("encoding_processing_invalid.jpg")
420 .to_str()
421 .map(|v| v.into()),
422
423 application_specific: vec![
424 ConfigItem::OptionStringItem(None),
425 ConfigItem::OptionStringItem(None),
426 ],
427 };
428
429 let conversion_processor = EncodingFormatDecider::default();
430 let _ = conversion_processor
431 .process(&jpeg_conf)
432 .expect("Unable to save file to the test computer");
433 }
434
435 #[should_panic]
436 #[test]
437 fn test_output_unsupported_ff_without_ext() {
438 let jpeg_conf = Config {
439 tool_name: env!("CARGO_PKG_NAME"),
440 licenses: vec![],
441 forced_output_format: Some("OiOi".into()), disable_automatic_color_type_adjustment: false,
443
444 encoding_settings: FormatEncodingSettings {
445 jpeg_settings: JPEGEncodingSettings { quality: 90 },
446 pnm_settings: PNMEncodingSettings::new(false),
447 },
448
449 output: setup_output_path("encoding_processing_invalid")
450 .to_str()
451 .map(|v| v.into()),
452
453 application_specific: vec![
454 ConfigItem::OptionStringItem(None),
455 ConfigItem::OptionStringItem(None),
456 ],
457 };
458
459 let conversion_processor = EncodingFormatDecider::default();
460 let _ = conversion_processor
461 .process(&jpeg_conf)
462 .expect("Unable to save file to the test computer");
463 }
464
465 }