Add artifact related functions

This makes it possible to blend images with user configurable percent
(see `set_image_artifact` documentation)
This commit is contained in:
5ohue
2024-05-11 21:29:36 +03:00
parent 1a66649c47
commit b8147a2b06
3 changed files with 251 additions and 4 deletions

View File

@ -15,6 +15,7 @@ mod pixel_interpolate_method;
mod rendering_intent; mod rendering_intent;
mod resolution_type; mod resolution_type;
mod resource_type; mod resource_type;
mod statistic_type;
pub use self::alpha_channel_option::AlphaChannelOption; pub use self::alpha_channel_option::AlphaChannelOption;
pub use self::colorspace_type::ColorspaceType; pub use self::colorspace_type::ColorspaceType;
@ -33,3 +34,4 @@ pub use self::pixel_interpolate_method::PixelInterpolateMethod;
pub use self::rendering_intent::RenderingIntent; pub use self::rendering_intent::RenderingIntent;
pub use self::resolution_type::ResolutionType; pub use self::resolution_type::ResolutionType;
pub use self::resource_type::ResourceType; pub use self::resource_type::ResourceType;
pub use self::statistic_type::StatisticType;

View File

@ -0,0 +1,46 @@
use crate::bindings;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum StatisticType {
Undefined = bindings::StatisticType_UndefinedStatistic,
Gradient = bindings::StatisticType_GradientStatistic,
Maximum = bindings::StatisticType_MaximumStatistic,
Mean = bindings::StatisticType_MeanStatistic,
Median = bindings::StatisticType_MedianStatistic,
Minimum = bindings::StatisticType_MinimumStatistic,
Mode = bindings::StatisticType_ModeStatistic,
Nonpeak = bindings::StatisticType_NonpeakStatistic,
RootMeanSquare = bindings::StatisticType_RootMeanSquareStatistic,
StandardDeviation = bindings::StatisticType_StandardDeviationStatistic,
Contrast = bindings::StatisticType_ContrastStatistic,
}
impl Default for StatisticType {
fn default() -> Self {
return StatisticType::Undefined;
}
}
impl From<StatisticType> for bindings::StatisticType {
fn from(value: StatisticType) -> Self {
return value as bindings::StatisticType;
}
}
impl From<bindings::StatisticType> for StatisticType {
fn from(value: bindings::StatisticType) -> Self {
/*
* SAFETY:
*
* `StatisticType` has the same repr as `bindings::StatisticType` - u32
*
* If `value` is less than Contrast than it is in the vaild range and can be safely
* reinterpreted as `StatisticType`
*/
if value <= bindings::StatisticType_ContrastStatistic {
return unsafe { std::mem::transmute(value) };
}
return StatisticType::default();
}
}

View File

@ -47,7 +47,8 @@ use crate::{
PixelInterpolateMethod, PixelInterpolateMethod,
RenderingIntent, RenderingIntent,
ResolutionType, ResolutionType,
ResourceType ResourceType,
StatisticType,
}; };
wand_common!( wand_common!(
@ -414,11 +415,21 @@ impl MagickWand {
} }
/// Apply sigmoidal contrast to the image /// Apply sigmoidal contrast to the image
/// Midpoint is a number in range [0, 1] ///
/// Adjusts the contrast of an image with a non-linear sigmoidal contrast algorithm. Increase
/// the contrast of the image using a sigmoidal transfer function without saturating highlights
/// or shadows. Contrast indicates how much to increase the contrast (0 is none; 3 is typical;
/// 20 is pushing it); mid-point indicates where midtones fall in the resultant image (0.0 is
/// white; 0.5 is middle-gray; 1.0 is black). Set sharpen to `true` to increase the image
/// contrast otherwise the contrast is reduced.
///
/// * `sharpen`: increase or decrease image contrast
/// * `strength`: strength of the contrast, the larger the number the more 'threshold-like' it becomes.
/// * `midpoint`: midpoint of the function as a number in range [0, 1]
pub fn sigmoidal_contrast_image( pub fn sigmoidal_contrast_image(
&self, &self,
sharpen: bool, sharpen: bool,
contrast: f64, strength: f64,
midpoint: f64, midpoint: f64,
) -> Result<()> { ) -> Result<()> {
let quantum_range = self.quantum_range()?; let quantum_range = self.quantum_range()?;
@ -427,7 +438,7 @@ impl MagickWand {
bindings::MagickSigmoidalContrastImage( bindings::MagickSigmoidalContrastImage(
self.wand, self.wand,
sharpen.to_magick(), sharpen.to_magick(),
contrast, strength,
midpoint * quantum_range, midpoint * quantum_range,
) )
}; };
@ -521,6 +532,37 @@ impl MagickWand {
} }
} }
/// Replace each pixel with corresponding statistic from the neighborhood of the specified width and height.
///
/// * `statistic_type`: the statistic type (e.g. `StatisticType::Median`, `StatisticType::Mode`, etc.).
/// * `width`: the width of the pixel neighborhood.
/// * `height`: the height of the pixel neighborhood.
pub fn statistic_image(
&self,
statistic_type: StatisticType,
width: usize,
height: usize,
) -> Result<()> {
match unsafe {
bindings::MagickStatisticImage(
self.wand,
statistic_type.into(),
width.into(),
height.into()
)
} {
MagickTrue => Ok(()),
_ => Err(MagickError("failed to calculate statistics for image")),
}
}
/// Calculate median for each pixel's neighborhood.
///
/// See [statistic_image](Self::statistic_image)
pub fn median_blur_image(&self, width: usize, height: usize) -> Result<()> {
return self.statistic_image(StatisticType::Median, width, height);
}
/// Adaptively resize the currently selected image. /// Adaptively resize the currently selected image.
pub fn adaptive_resize_image(&self, width: usize, height: usize) -> Result<()> { pub fn adaptive_resize_image(&self, width: usize, height: usize) -> Result<()> {
match unsafe { bindings::MagickAdaptiveResizeImage(self.wand, width, height) } { match unsafe { bindings::MagickAdaptiveResizeImage(self.wand, width, height) } {
@ -577,6 +619,131 @@ impl MagickWand {
} }
} }
/// Returns a value associated with the specified artifact.
///
/// * `artifact`: the artifact.
pub fn get_image_artifact(&self, artifact: &str) -> Result<String> {
let c_artifact = CString::new(artifact).map_err(|_| MagickError("artifact string contains null byte"))?;
let result = unsafe {
bindings::MagickGetImageArtifact(
self.wand,
c_artifact.as_ptr()
)
};
let value = if result.is_null() {
Err(MagickError("missing artifact"))
} else {
// convert (and copy) the C string to a Rust string
let cstr = unsafe { CStr::from_ptr(result) };
Ok(cstr.to_string_lossy().into_owned())
};
unsafe {
bindings::MagickRelinquishMemory(result as *mut c_void);
}
value
}
pub fn get_image_artifacts(&self, pattern: &str) -> Result<Vec<String>> {
let c_pattern = CString::new(pattern).map_err(|_| MagickError("artifact string contains null byte"))?;
let mut num_of_artifacts: size_t = 0;
let result = unsafe {
bindings::MagickGetImageArtifacts(
self.wand,
c_pattern.as_ptr(),
&mut num_of_artifacts
)
};
let value = if result.is_null() {
Err(MagickError("no artifacts found"))
} else {
let mut artifacts: Vec<String> = Vec::with_capacity(num_of_artifacts);
for i in 0..num_of_artifacts {
// convert (and copy) the C string to a Rust string
let cstr = unsafe { CStr::from_ptr(*result.add(i)) };
artifacts.push(cstr.to_string_lossy().into_owned());
}
Ok(artifacts)
};
unsafe {
bindings::MagickRelinquishMemory(result as *mut c_void);
}
value
}
/// Sets a key-value pair in the image artifact namespace. Artifacts differ from properties.
/// Properties are public and are generally exported to an external image format if the format
/// supports it. Artifacts are private and are utilized by the internal ImageMagick API to
/// modify the behavior of certain algorithms.
///
/// * `artifact`: the artifact.
/// * `value`: the value.
///
/// # Example
///
/// This example shows how you can blend an image with its blurred copy with 50% opacity by
/// setting "compose:args" to "50". This is equivalent to having `-define compose:args=50` when
/// using imagemagick cli.
///
/// ```
/// let mut wand1 = MagickWand::new();
/// wand1.read_image("test.jpg")?;
/// let wand2 = wand1.clone();
///
/// wand1.median_blur_image(10, 10)?;
///
/// wand1.set_image_artifact("compose:args", "50")?;
/// wand1.compose_images(&wand2, CompositeOperator::Blend, false, 0, 0)?;
///
/// wand1.write_image("res.jpeg")?;
/// ```
pub fn set_image_artifact(
&mut self,
artifact: &str,
value: &str
) -> Result<()> {
let c_artifact = CString::new(artifact).map_err(|_| MagickError("artifact string contains null byte"))?;
let c_value = CString::new(value).map_err(|_| MagickError("value string contains null byte"))?;
let result = unsafe {
bindings::MagickSetImageArtifact(
self.wand,
c_artifact.as_ptr(),
c_value.as_ptr()
)
};
match result {
MagickTrue => Ok(()),
_ => Err(MagickError("failed to set image artifact")),
}
}
/// Deletes a wand artifact.
///
/// * `artifact`: the artifact.
pub fn delete_image_artifact(&mut self, artifact: &str) -> Result<()> {
let c_artifact = CString::new(artifact).map_err(|_| MagickError("artifact string contains null byte"))?;
match unsafe {
bindings::MagickDeleteImageArtifact(
self.wand,
c_artifact.as_ptr()
)
} {
MagickTrue => Ok(()),
_ => Err(MagickError("failed to delete image artifact")),
}
}
/// Retrieve the named image property value. /// Retrieve the named image property value.
pub fn get_image_property(&self, name: &str) -> Result<String> { pub fn get_image_property(&self, name: &str) -> Result<String> {
let c_name = CString::new(name).unwrap(); let c_name = CString::new(name).unwrap();
@ -594,6 +761,38 @@ impl MagickWand {
value value
} }
pub fn get_image_properties(&self, pattern: &str) -> Result<Vec<String>> {
let c_pattern = CString::new(pattern).map_err(|_| MagickError("artifact string contains null byte"))?;
let mut num_of_artifacts: size_t = 0;
let result = unsafe {
bindings::MagickGetImageProperties(
self.wand,
c_pattern.as_ptr(),
&mut num_of_artifacts
)
};
let value = if result.is_null() {
Err(MagickError("no artifacts found"))
} else {
let mut artifacts: Vec<String> = Vec::with_capacity(num_of_artifacts);
for i in 0..num_of_artifacts {
// convert (and copy) the C string to a Rust string
let cstr = unsafe { CStr::from_ptr(*result.add(i)) };
artifacts.push(cstr.to_string_lossy().into_owned());
}
Ok(artifacts)
};
unsafe {
bindings::MagickRelinquishMemory(result as *mut c_void);
}
value
}
/// Set the named image property. /// Set the named image property.
pub fn set_image_property(&self, name: &str, value: &str) -> Result<()> { pub fn set_image_property(&self, name: &str, value: &str) -> Result<()> {
let c_name = CString::new(name).unwrap(); let c_name = CString::new(name).unwrap();