Add kernel and convolve functions
This commit is contained in:
325
src/types/kernel.rs
Normal file
325
src/types/kernel.rs
Normal 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) }
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user