image/
hooks.rs

1//! This module provides a way to register decoding hooks for image formats not directly supported
2//! by this crate.
3
4use std::{
5    collections::HashMap,
6    ffi::{OsStr, OsString},
7    io::{BufRead, BufReader, Read, Seek},
8    sync::RwLock,
9};
10
11use crate::{ImageDecoder, ImageResult};
12
13pub(crate) trait ReadSeek: Read + Seek {}
14impl<T: Read + Seek> ReadSeek for T {}
15
16pub(crate) static DECODING_HOOKS: RwLock<Option<HashMap<OsString, DecodingHook>>> =
17    RwLock::new(None);
18
19pub(crate) type DetectionHook = (&'static [u8], &'static [u8], OsString);
20pub(crate) static GUESS_FORMAT_HOOKS: RwLock<Vec<DetectionHook>> = RwLock::new(Vec::new());
21
22/// A wrapper around a type-erased trait object that implements `Read` and `Seek`.
23pub struct GenericReader<'a>(pub(crate) BufReader<Box<dyn ReadSeek + 'a>>);
24impl Read for GenericReader<'_> {
25    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
26        self.0.read(buf)
27    }
28    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
29        self.0.read_vectored(bufs)
30    }
31    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
32        self.0.read_to_end(buf)
33    }
34    fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
35        self.0.read_to_string(buf)
36    }
37    fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
38        self.0.read_exact(buf)
39    }
40}
41impl BufRead for GenericReader<'_> {
42    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
43        self.0.fill_buf()
44    }
45    fn consume(&mut self, amt: usize) {
46        self.0.consume(amt)
47    }
48    fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> std::io::Result<usize> {
49        self.0.read_until(byte, buf)
50    }
51    fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
52        self.0.read_line(buf)
53    }
54}
55impl Seek for GenericReader<'_> {
56    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
57        self.0.seek(pos)
58    }
59    fn rewind(&mut self) -> std::io::Result<()> {
60        self.0.rewind()
61    }
62    fn stream_position(&mut self) -> std::io::Result<u64> {
63        self.0.stream_position()
64    }
65
66    // TODO: Add `seek_relative` once MSRV is at least 1.80.0
67}
68
69/// A function to produce an `ImageDecoder` for a given image format.
70pub type DecodingHook =
71    Box<dyn for<'a> Fn(GenericReader<'a>) -> ImageResult<Box<dyn ImageDecoder + 'a>> + Send + Sync>;
72
73/// Register a new decoding hook or returns false if one already exists for the given format.
74pub fn register_decoding_hook(extension: OsString, hook: DecodingHook) -> bool {
75    let mut hooks = DECODING_HOOKS.write().unwrap();
76    if hooks.is_none() {
77        *hooks = Some(HashMap::new());
78    }
79    match hooks.as_mut().unwrap().entry(extension) {
80        std::collections::hash_map::Entry::Vacant(entry) => {
81            entry.insert(hook);
82            true
83        }
84        std::collections::hash_map::Entry::Occupied(_) => false,
85    }
86}
87
88/// Returns whether a decoding hook has been registered for the given format.
89pub fn decoding_hook_registered(extension: &OsStr) -> bool {
90    DECODING_HOOKS
91        .read()
92        .unwrap()
93        .as_ref()
94        .map(|hooks| hooks.contains_key(extension))
95        .unwrap_or(false)
96}
97
98/// Registers a format detection hook.
99///
100/// The signature field holds the magic bytes from the start of the file that must be matched to
101/// detect the format. The mask field is optional and can be used to specify which bytes in the
102/// signature should be ignored during the detection.
103///
104/// # Examples
105///
106/// ## Using the mask to ignore some bytes
107///
108/// ```
109/// # use image::hooks::register_format_detection_hook;
110/// // WebP signature is 'riff' followed by 4 bytes of length and then by 'webp'.
111/// // This requires a mask to ignore the length.
112/// register_format_detection_hook("webp".into(),
113///      &[b'r', b'i', b'f', b'f', 0, 0, 0, 0, b'w', b'e', b'b', b'p'],
114/// Some(&[0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff]),
115/// );
116/// ```
117///
118/// ## Multiple signatures
119///
120/// ```
121/// # use image::hooks::register_format_detection_hook;
122/// // JPEG XL has two different signatures: https://en.wikipedia.org/wiki/JPEG_XL
123/// // This function should be called twice to register them both.
124/// register_format_detection_hook("jxl".into(), &[0xff, 0x0a], None);
125/// register_format_detection_hook("jxl".into(),
126///      &[0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a], None,
127/// );
128/// ```
129///
130pub fn register_format_detection_hook(
131    extension: OsString,
132    signature: &'static [u8],
133    mask: Option<&'static [u8]>,
134) {
135    GUESS_FORMAT_HOOKS
136        .write()
137        .unwrap()
138        .push((signature, mask.unwrap_or(&[]), extension));
139}