From c69cddb0689d13efcc9da3b421f8db8dc6fb8b15 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Sun, 27 Oct 2024 17:17:47 -0700 Subject: [PATCH 1/8] Add decoding hooks --- src/hooks.rs | 45 +++++++++++++++++++++++++++ src/image_reader/image_reader_type.rs | 10 +++++- src/lib.rs | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/hooks.rs diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 0000000000..0e6c9adebe --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,45 @@ +//! This module provides a way to register decoding hooks for image formats not directly supported +//! by this crate. + +use std::{ + collections::HashMap, + io::{BufReader, Read, Seek}, + sync::RwLock, +}; + +use crate::{ImageDecoder, ImageFormat, ImageResult}; + +pub(crate) trait ReadSeek: Read + Seek {} +impl ReadSeek for T {} + +pub(crate) static DECODING_HOOKS: RwLock>> = + RwLock::new(None); + +/// A wrapper around a type-erased trait object that implements `Read` and `Seek`. +pub struct BoxReadSeek<'a>(pub(crate) Box); +impl<'a> Read for BoxReadSeek<'a> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf) + } +} +impl<'a> Seek for BoxReadSeek<'a> { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.0.seek(pos) + } +} + +/// A function to produce an `ImageDecoder` for a given image format. +pub type DecodingHook = Box< + dyn for<'a> Fn(BufReader>) -> ImageResult> + + Send + + Sync, +>; + +/// Register a new decoding hook or replace an existing one. +pub fn register_decoding_hook(format: ImageFormat, hook: DecodingHook) { + let mut hooks = DECODING_HOOKS.write().unwrap(); + if hooks.is_none() { + *hooks = Some(HashMap::new()); + } + hooks.as_mut().unwrap().insert(format, hook); +} diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index 479316f191..ea5d23c4b0 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -4,6 +4,7 @@ use std::path::Path; use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; +use crate::hooks::{BoxReadSeek, DECODING_HOOKS}; use crate::image::ImageFormat; use crate::{ImageDecoder, ImageError, ImageResult}; @@ -178,9 +179,16 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { #[cfg(feature = "qoi")] ImageFormat::Qoi => Box::new(qoi::QoiDecoder::new(reader)?), format => { + let hooks = DECODING_HOOKS.read().unwrap(); + if let Some(hooks) = hooks.as_ref() { + if let Some(hook) = hooks.get(&format) { + return hook(BufReader::new(BoxReadSeek(Box::new(reader)))); + } + } + return Err(ImageError::Unsupported( ImageFormatHint::Exact(format).into(), - )) + )); } }) } diff --git a/src/lib.rs b/src/lib.rs index 32b25fb924..a70d5f1b7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -287,6 +287,7 @@ mod buffer_; mod buffer_par; mod color; mod dynimage; +pub mod hooks; mod image; mod image_reader; pub mod metadata; From 0cd3fc78f3a3d4c55fe68aa9988022dcd3d46206 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Sun, 27 Oct 2024 17:26:59 -0700 Subject: [PATCH 2/8] Implicit lifetimes --- src/hooks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 0e6c9adebe..92d5570d0f 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -17,12 +17,12 @@ pub(crate) static DECODING_HOOKS: RwLock(pub(crate) Box); -impl<'a> Read for BoxReadSeek<'a> { +impl Read for BoxReadSeek<'_> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.read(buf) } } -impl<'a> Seek for BoxReadSeek<'a> { +impl Seek for BoxReadSeek<'_> { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { self.0.seek(pos) } From e57ab2fb98b4421983617fb53fab617e4a23020d Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:00:48 -0700 Subject: [PATCH 3/8] Make BufReader private --- src/hooks.rs | 19 ++++++++++++++----- src/image_reader/image_reader_type.rs | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 92d5570d0f..d8747fc7ad 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -3,7 +3,7 @@ use std::{ collections::HashMap, - io::{BufReader, Read, Seek}, + io::{BufRead, BufReader, Read, Seek}, sync::RwLock, }; @@ -12,17 +12,26 @@ use crate::{ImageDecoder, ImageFormat, ImageResult}; pub(crate) trait ReadSeek: Read + Seek {} impl ReadSeek for T {} + pub(crate) static DECODING_HOOKS: RwLock>> = RwLock::new(None); /// A wrapper around a type-erased trait object that implements `Read` and `Seek`. -pub struct BoxReadSeek<'a>(pub(crate) Box); -impl Read for BoxReadSeek<'_> { +pub struct GenericReader<'a>(pub(crate) BufReader>); +impl Read for GenericReader<'_> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.read(buf) } } -impl Seek for BoxReadSeek<'_> { +impl BufRead for GenericReader<'_> { + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.0.fill_buf() + } + fn consume(&mut self, amt: usize) { + self.0.consume(amt) + } +} +impl Seek for GenericReader<'_> { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { self.0.seek(pos) } @@ -30,7 +39,7 @@ impl Seek for BoxReadSeek<'_> { /// A function to produce an `ImageDecoder` for a given image format. pub type DecodingHook = Box< - dyn for<'a> Fn(BufReader>) -> ImageResult> + dyn for<'a> Fn(GenericReader<'a>) -> ImageResult> + Send + Sync, >; diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index ea5d23c4b0..6994d5f22b 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -4,7 +4,7 @@ use std::path::Path; use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; -use crate::hooks::{BoxReadSeek, DECODING_HOOKS}; +use crate::hooks::{BoxReadSeek, GenericReader, DECODING_HOOKS}; use crate::image::ImageFormat; use crate::{ImageDecoder, ImageError, ImageResult}; @@ -182,7 +182,7 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { let hooks = DECODING_HOOKS.read().unwrap(); if let Some(hooks) = hooks.as_ref() { if let Some(hook) = hooks.get(&format) { - return hook(BufReader::new(BoxReadSeek(Box::new(reader)))); + return hook(GenericReader(BufReader::new(Box::new(reader)))); } } From 6a476bb4fd4642e414c4ca05e4def47124a0f531 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:01:03 -0700 Subject: [PATCH 4/8] formatting --- src/hooks.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index d8747fc7ad..25938af547 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -12,7 +12,6 @@ use crate::{ImageDecoder, ImageFormat, ImageResult}; pub(crate) trait ReadSeek: Read + Seek {} impl ReadSeek for T {} - pub(crate) static DECODING_HOOKS: RwLock>> = RwLock::new(None); @@ -38,11 +37,8 @@ impl Seek for GenericReader<'_> { } /// A function to produce an `ImageDecoder` for a given image format. -pub type DecodingHook = Box< - dyn for<'a> Fn(GenericReader<'a>) -> ImageResult> - + Send - + Sync, ->; +pub type DecodingHook = + Box Fn(GenericReader<'a>) -> ImageResult> + Send + Sync>; /// Register a new decoding hook or replace an existing one. pub fn register_decoding_hook(format: ImageFormat, hook: DecodingHook) { From 74231d85c81712fba54addc82c1cad75fdf61def Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:09:04 -0700 Subject: [PATCH 5/8] Compile fix --- src/image_reader/image_reader_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index 6994d5f22b..500f761b71 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -4,7 +4,7 @@ use std::path::Path; use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; -use crate::hooks::{BoxReadSeek, GenericReader, DECODING_HOOKS}; +use crate::hooks::{GenericReader, DECODING_HOOKS}; use crate::image::ImageFormat; use crate::{ImageDecoder, ImageError, ImageResult}; From fb81b08747fe8105bb9bed2b44039be6b5da3475 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:15:56 -0700 Subject: [PATCH 6/8] Pass-through implementations for more trait methods --- src/hooks.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/hooks.rs b/src/hooks.rs index 25938af547..116567dea1 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -21,6 +21,18 @@ impl Read for GenericReader<'_> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.read(buf) } + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + self.0.read_vectored(bufs) + } + fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { + self.0.read_to_end(buf) + } + fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { + self.0.read_to_string(buf) + } + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.0.read_exact(buf) + } } impl BufRead for GenericReader<'_> { fn fill_buf(&mut self) -> std::io::Result<&[u8]> { @@ -29,11 +41,25 @@ impl BufRead for GenericReader<'_> { fn consume(&mut self, amt: usize) { self.0.consume(amt) } + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> std::io::Result { + self.0.read_until(byte, buf) + } + fn read_line(&mut self, buf: &mut String) -> std::io::Result { + self.0.read_line(buf) + } } impl Seek for GenericReader<'_> { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { self.0.seek(pos) } + fn rewind(&mut self) -> std::io::Result<()> { + self.0.rewind() + } + fn stream_position(&mut self) -> std::io::Result { + self.0.stream_position() + } + + // TODO: Add `seek_relative` once MSRV is at least 1.80.0 } /// A function to produce an `ImageDecoder` for a given image format. From 8bf0cf2930461d3cd21a1186d43970baa5cfe467 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:19:27 -0700 Subject: [PATCH 7/8] Block registration of hooks for formats that already have them --- src/hooks.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 116567dea1..d605fca7f8 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -66,11 +66,27 @@ impl Seek for GenericReader<'_> { pub type DecodingHook = Box Fn(GenericReader<'a>) -> ImageResult> + Send + Sync>; -/// Register a new decoding hook or replace an existing one. -pub fn register_decoding_hook(format: ImageFormat, hook: DecodingHook) { +/// Register a new decoding hook or returns false if one already exists for the given format. +pub fn register_decoding_hook(format: ImageFormat, hook: DecodingHook) -> bool { let mut hooks = DECODING_HOOKS.write().unwrap(); if hooks.is_none() { *hooks = Some(HashMap::new()); } - hooks.as_mut().unwrap().insert(format, hook); + match hooks.as_mut().unwrap().entry(format) { + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(hook); + true + } + std::collections::hash_map::Entry::Occupied(_) => false, + } +} + +/// Returns whether a decoding hook has been registered for the given format. +pub fn decoding_hook_registered(format: ImageFormat) -> bool { + DECODING_HOOKS + .read() + .unwrap() + .as_ref() + .map(|hooks| hooks.contains_key(&format)) + .unwrap_or(false) } From 97fb13c1a394276509eeb70e065ec240e4a395ff Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 28 Oct 2024 20:20:02 -0700 Subject: [PATCH 8/8] Block registration of hooks for built-in formats --- src/hooks.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks.rs b/src/hooks.rs index d605fca7f8..1d2b83351a 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -68,6 +68,10 @@ pub type DecodingHook = /// Register a new decoding hook or returns false if one already exists for the given format. pub fn register_decoding_hook(format: ImageFormat, hook: DecodingHook) -> bool { + if format.reading_enabled() { + return false; + } + let mut hooks = DECODING_HOOKS.write().unwrap(); if hooks.is_none() { *hooks = Some(HashMap::new());