Add kernel and convolve functions

This commit is contained in:
5ohue
2024-05-12 17:43:19 +03:00
parent 26a8dd3df7
commit e3edb225ec
3 changed files with 388 additions and 0 deletions

325
src/types/kernel.rs Normal file
View File

@ -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<KernelInfoType> 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<MorphologyMethod> 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<Vec<f64>>,
}
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<KernelInfo> {
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) }
}
}

View File

@ -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;

View File

@ -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)