From e3edb225ec5f61a859b689cf5d23f0912b8a7e6d Mon Sep 17 00:00:00 2001 From: 5ohue <86558263+5ohue@users.noreply.github.com> Date: Sun, 12 May 2024 17:43:19 +0300 Subject: [PATCH] Add kernel and convolve functions --- src/types/kernel.rs | 325 ++++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 2 + src/wand/magick.rs | 61 +++++++++ 3 files changed, 388 insertions(+) create mode 100644 src/types/kernel.rs diff --git a/src/types/kernel.rs b/src/types/kernel.rs new file mode 100644 index 0000000..fb8a636 --- /dev/null +++ b/src/types/kernel.rs @@ -0,0 +1,325 @@ +use std::ffi::CString; + +use crate::bindings; +use crate::{Result, MagickError}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum KernelInfoType { + Undefined = bindings::KernelInfoType_UndefinedKernel, + Unity = bindings::KernelInfoType_UnityKernel, + Gaussian = bindings::KernelInfoType_GaussianKernel, + DoG = bindings::KernelInfoType_DoGKernel, + LoG = bindings::KernelInfoType_LoGKernel, + Blur = bindings::KernelInfoType_BlurKernel, + Comet = bindings::KernelInfoType_CometKernel, + Binomial = bindings::KernelInfoType_BinomialKernel, + Laplacian = bindings::KernelInfoType_LaplacianKernel, + Sobel = bindings::KernelInfoType_SobelKernel, + FreiChen = bindings::KernelInfoType_FreiChenKernel, + Roberts = bindings::KernelInfoType_RobertsKernel, + Prewitt = bindings::KernelInfoType_PrewittKernel, + Compass = bindings::KernelInfoType_CompassKernel, + Kirsch = bindings::KernelInfoType_KirschKernel, + Diamond = bindings::KernelInfoType_DiamondKernel, + Square = bindings::KernelInfoType_SquareKernel, + Rectangle = bindings::KernelInfoType_RectangleKernel, + Octagon = bindings::KernelInfoType_OctagonKernel, + Disk = bindings::KernelInfoType_DiskKernel, + Plus = bindings::KernelInfoType_PlusKernel, + Cross = bindings::KernelInfoType_CrossKernel, + Ring = bindings::KernelInfoType_RingKernel, + Peaks = bindings::KernelInfoType_PeaksKernel, + Edges = bindings::KernelInfoType_EdgesKernel, + Corners = bindings::KernelInfoType_CornersKernel, + Diagonals = bindings::KernelInfoType_DiagonalsKernel, + LineEnds = bindings::KernelInfoType_LineEndsKernel, + LineJunctions = bindings::KernelInfoType_LineJunctionsKernel, + Ridges = bindings::KernelInfoType_RidgesKernel, + ConvexHull = bindings::KernelInfoType_ConvexHullKernel, + ThinSE = bindings::KernelInfoType_ThinSEKernel, + Skeleton = bindings::KernelInfoType_SkeletonKernel, + Chebyshev = bindings::KernelInfoType_ChebyshevKernel, + Manhattan = bindings::KernelInfoType_ManhattanKernel, + Octagonal = bindings::KernelInfoType_OctagonalKernel, + Euclidean = bindings::KernelInfoType_EuclideanKernel, + UserDefined = bindings::KernelInfoType_UserDefinedKernel, +} + +impl Default for KernelInfoType { + fn default() -> Self { + return KernelInfoType::Undefined; + } +} + +impl From for bindings::KernelInfoType { + fn from(value: KernelInfoType) -> Self { + return value as bindings::KernelInfoType; + } +} + + + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum MorphologyMethod { + Undefined = bindings::MorphologyMethod_UndefinedMorphology, + Convolve = bindings::MorphologyMethod_ConvolveMorphology, + Correlate = bindings::MorphologyMethod_CorrelateMorphology, + Erode = bindings::MorphologyMethod_ErodeMorphology, + Dilate = bindings::MorphologyMethod_DilateMorphology, + ErodeIntensity = bindings::MorphologyMethod_ErodeIntensityMorphology, + DilateIntensity = bindings::MorphologyMethod_DilateIntensityMorphology, + IterativeDistance = bindings::MorphologyMethod_IterativeDistanceMorphology, + Open = bindings::MorphologyMethod_OpenMorphology, + Close = bindings::MorphologyMethod_CloseMorphology, + OpenIntensity = bindings::MorphologyMethod_OpenIntensityMorphology, + CloseIntensity = bindings::MorphologyMethod_CloseIntensityMorphology, + Smooth = bindings::MorphologyMethod_SmoothMorphology, + EdgeIn = bindings::MorphologyMethod_EdgeInMorphology, + EdgeOut = bindings::MorphologyMethod_EdgeOutMorphology, + Edge = bindings::MorphologyMethod_EdgeMorphology, + TopHat = bindings::MorphologyMethod_TopHatMorphology, + BottomHat = bindings::MorphologyMethod_BottomHatMorphology, + HitAndMiss = bindings::MorphologyMethod_HitAndMissMorphology, + Thinning = bindings::MorphologyMethod_ThinningMorphology, + Thicken = bindings::MorphologyMethod_ThickenMorphology, + Distance = bindings::MorphologyMethod_DistanceMorphology, + Voronoi = bindings::MorphologyMethod_VoronoiMorphology, +} + +impl Default for MorphologyMethod { + fn default() -> Self { + return MorphologyMethod::Undefined; + } +} + +impl From for bindings::KernelInfoType { + fn from(value: MorphologyMethod) -> Self { + return value as bindings::MorphologyMethod; + } +} + + + + +/// Builder, that creates instances of [KernelInfo](self::KernelInfo) +/// +/// # Example +/// +/// Here is an example of how you can use this struct to create a kernel to convolve an image: +/// +/// ``` +/// use magick_rust::{MagickWand, PixelWand, KernelBuilder}; +/// +/// fn main() -> Result<(), magick_rust::MagickError> { +/// let mut wand1 = MagickWand::new(); +/// wand1.new_image(4, 4, &PixelWand::new())?; // Replace with `read_image` to open your image file +/// let wand2 = wand1.clone(); +/// +/// let kernel_info = KernelBuilder::new() +/// .set_size((3, 3)) +/// .set_center((1, 1)) // Not really needed here - the center is in the middle of kernel +/// // by default +/// .set_values(&[0.111, 0.111, 0.111, +/// 0.111, 0.111, 0.111, +/// 0.111, 0.111, 0.111]) +/// .build()?; +/// +/// wand1.convolve_image(&kernel_info)?; +/// +/// Ok(()) +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct KernelBuilder { + size: Option<(usize, usize)>, + center: Option<(usize, usize)>, + values: Option>, +} + +impl KernelBuilder { + pub fn new() -> KernelBuilder { + return KernelBuilder { + size: None, + center: None, + values: None, + }; + } + + pub fn set_size(mut self, size: (usize, usize)) -> KernelBuilder { + self.size = Some(size); + return self; + } + + pub fn set_center(mut self, center: (usize, usize)) -> KernelBuilder { + self.center = Some(center); + return self; + } + + pub fn set_values(mut self, values: &[f64]) -> KernelBuilder { + self.values = Some(values.into()); + return self; + } + + pub fn build(&self) -> Result { + let size = self.size.ok_or(MagickError("no kernel size given"))?; + let values = self.values.as_ref().ok_or(MagickError("no kernel values given"))?; + + if values.len() != size.0 * size.1 { + return Err(MagickError("kernel size doesn't match kernel values size")); + } + + // Create kernel string + let mut kernel_string = if let Some(center) = self.center { + format!( + "{}x{}+{}+{}:", + size.0, + size.1, + center.0, + center.1 + ) + } else { + format!( + "{}x{}:", + size.0, + size.1, + ) + }; + + // Add values + values.iter().for_each(|x| { + kernel_string.push_str(&format!("{x},")); + }); + + // Remove trailing "," + kernel_string.pop(); + + // Create null terminated string + let c_kernel_string = CString::new(kernel_string).expect("CString::new() has failed"); + + // Create kernel info + let kernel_info = unsafe { + bindings::AcquireKernelInfo( + c_kernel_string.as_ptr(), + std::ptr::null_mut() + ) + }; + + if kernel_info.is_null() { + return Err(MagickError("failed to acquire kernel info")); + } + + Ok(KernelInfo::new(kernel_info)) + } +} + +pub struct KernelInfo { + kernel_info: *mut bindings::KernelInfo, +} + +impl KernelInfo { + fn new(kernel_info: *mut bindings::KernelInfo) -> KernelInfo { + return KernelInfo { + kernel_info + }; + } + + /// The values within the kernel is scaled directly using given scaling factor without change. + pub fn scale(&mut self, factor: f64) { + unsafe { + bindings::ScaleKernelInfo( + self.kernel_info, + factor, + 0 + ) + } + } + + /// Kernel normalization is designed to ensure that any use of the kernel scaling factor with + /// 'Convolve' or 'Correlate' morphology methods will fall into -1.0 to +1.0 range. Note that + /// for non-HDRI versions of IM this may cause images to have any negative results clipped, + /// unless some 'bias' is used. + /// + /// More specifically. Kernels which only contain positive values (such as a 'Gaussian' kernel) + /// will be scaled so that those values sum to +1.0, ensuring a 0.0 to +1.0 output range for + /// non-HDRI images. + /// + /// For Kernels that contain some negative values, (such as 'Sharpen' kernels) the kernel will + /// be scaled by the absolute of the sum of kernel values, so that it will generally fall + /// within the +/- 1.0 range. + /// + /// For kernels whose values sum to zero, (such as 'Laplacian' kernels) kernel will be scaled + /// by just the sum of the positive values, so that its output range will again fall into the + /// +/- 1.0 range. + pub fn normalize(&mut self) { + unsafe { + bindings::ScaleKernelInfo( + self.kernel_info, + 1.0, + bindings::GeometryFlags_NormalizeValue + ) + } + } + + /// For special kernels designed for locating shapes using 'Correlate', (often only containing + /// +1 and -1 values, representing foreground/background matching) a special normalization + /// method is provided to scale the positive values separately to those of the negative values, + /// so the kernel will be forced to become a zero-sum kernel better suited to such searches. + pub fn correlate_normalize(&mut self) { + unsafe { + bindings::ScaleKernelInfo( + self.kernel_info, + 1.0, + bindings::GeometryFlags_CorrelateNormalizeValue + ) + } + } + + /// Adds a given amount of the 'Unity' Convolution Kernel to the given pre-scaled and + /// normalized Kernel. This in effect adds that amount of the original image into the resulting + /// convolution kernel. This value is usually provided by the user as a percentage value in the + /// 'convolve:scale' setting. + /// + /// The resulting effect is to convert the defined kernels into blended soft-blurs, unsharp + /// kernels or into sharpening kernels. + pub fn unity_add(&mut self, scale: f64) { + unsafe { + bindings::UnityAddKernelInfo( + self.kernel_info, + scale + ) + } + } + + pub unsafe fn get_ptr(&self) -> *mut bindings::KernelInfo { + return self.kernel_info; + } +} + +impl Drop for KernelInfo { + fn drop(&mut self) { + unsafe { bindings::DestroyKernelInfo(self.kernel_info) }; + } +} + +impl Clone for KernelInfo { + fn clone(&self) -> Self { + let kernel_info = unsafe { + bindings::CloneKernelInfo(self.kernel_info) + }; + + if kernel_info.is_null() { + panic!("failed to clone kernel info"); + } + + return KernelInfo::new(kernel_info); + } +} + +impl std::fmt::Debug for KernelInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + unsafe { write!(f, "{:?}", *self.kernel_info) } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index ada8d0f..50b2d7f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -16,6 +16,7 @@ mod filter_type; mod gravity_type; mod image_type; mod interlace_type; +mod kernel; mod line_cap; mod line_join; mod magick_evaluate_operator; @@ -48,6 +49,7 @@ pub use self::filter_type::FilterType; pub use self::gravity_type::GravityType; pub use self::image_type::ImageType; pub use self::interlace_type::InterlaceType; +pub use self::kernel::*; pub use self::line_cap::LineCap; pub use self::line_join::LineJoin; pub use self::magick_evaluate_operator::MagickEvaluateOperator; diff --git a/src/wand/magick.rs b/src/wand/magick.rs index b3a177e..a0b30b0 100644 --- a/src/wand/magick.rs +++ b/src/wand/magick.rs @@ -44,9 +44,11 @@ use crate::{ GravityType, ImageType, InterlaceType, + KernelInfo, MagickEvaluateOperator, MagickFunction, MetricType, + MorphologyMethod, OrientationType, PixelInterpolateMethod, RenderingIntent, @@ -1410,6 +1412,65 @@ impl MagickWand { } } + /// Applies a custom convolution kernel to the image. + /// + /// * `kernel_info`: An array of doubles representing the convolution kernel. + pub fn convolve_image(&self, kernel_info: &KernelInfo) -> Result<()> { + match unsafe { + bindings::MagickConvolveImage( + self.wand, + kernel_info.get_ptr() + ) + } { + MagickTrue => Ok(()), + _ => Err(MagickError("failed to convolve image")), + } + } + + /// Applies a user supplied kernel to the image according to the given morphology method. + /// + /// * `morphology_method`: the morphology method to be applied. + /// * `iterations`: apply the operation this many times (or no change). A value of -1 means loop until no change found. How this is applied may depend on the morphology method. Typically this is a value of 1. + /// * `kernel_info`: An array of doubles representing the morphology kernel. + pub fn morphology_image( + &self, + morphology_method: MorphologyMethod, + iterations: isize, + kernel_info: &KernelInfo + ) -> Result<()> { + match unsafe { + bindings::MagickMorphologyImage( + self.wand, + morphology_method.into(), + iterations.into(), + kernel_info.get_ptr() + ) + } { + MagickTrue => Ok(()), + _ => Err(MagickError("failed to morphology image")), + } + } + + /// Apply color transformation to an image. The method permits saturation changes, hue rotation, + /// luminance to alpha, and various other effects. Although variable-sized transformation + /// matrices can be used, typically one uses a 5x5 matrix for an RGBA image and a 6x6 for CMYKA + /// (or RGBA with offsets). The matrix is similar to those used by Adobe Flash except offsets + /// are in column 6 rather than 5 (in support of CMYKA images) and offsets are normalized + /// (divide Flash offset by 255). + /// + /// * `color_matrix`: the color matrix. + pub fn color_matrix_image(&self, color_matrix: &KernelInfo) -> Result<()> { + match unsafe { + bindings::MagickColorMatrixImage( + self.wand, + color_matrix.get_ptr() + ) + } { + MagickTrue => Ok(()), + _ => Err(MagickError("failed to color matrix image")), + } + } + mutations!( /// Sets the image to the specified alpha level. MagickSetImageAlpha => set_image_alpha(alpha: f64)