Skip to main content

magick_rust/types/
kernel.rs

1/*
2 * Copyright 2024 5ohue
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16use crate::{GeometryInfo, KernelInfoType, bindings};
17use crate::{MagickError, Result};
18use std::ffi::CString;
19
20/// Builder, that creates instances of [KernelInfo](self::KernelInfo)
21///
22/// # Examples
23///
24/// Here is an example of how you can use this struct to create a kernel to convolve an image:
25///
26/// ```
27/// use magick_rust::{MagickWand, PixelWand, KernelBuilder};
28///
29/// fn main() -> Result<(), magick_rust::MagickError> {
30///     let mut wand1 = MagickWand::new();
31///     wand1.new_image(4, 4, &PixelWand::new())?; // Replace with `read_image` to open your image file
32///     let wand2 = wand1.clone();
33///
34///     let kernel_info = KernelBuilder::default()
35///         .set_size((3, 3))
36///         .set_center((1, 1)) // Not really needed here - the center is in the middle of kernel
37///                             // by default
38///         .set_values(&[0.111, 0.111, 0.111,
39///                       0.111, 0.111, 0.111,
40///                       0.111, 0.111, 0.111])
41///         .build()?;
42///
43///     wand1.convolve_image(&kernel_info)?;
44///
45///     Ok(())
46/// }
47/// ```
48///
49/// Here is an example of how you can use this struct to create builtin kernel to gaussian blur an
50/// image (not the best way to do it, just an example):
51///
52/// ```
53/// use magick_rust::{MagickWand, PixelWand, KernelBuilder, KernelInfoType, GeometryInfo};
54///
55/// fn main() -> Result<(), magick_rust::MagickError> {
56///     let mut wand1 = MagickWand::new();
57///     wand1.new_image(4, 4, &PixelWand::new())?; // Replace with `read_image` to open your image file
58///     let wand2 = wand1.clone();
59///
60///     let mut geom_info = GeometryInfo::new();
61///     geom_info.set_sigma(15.0);
62///     let kernel_info = KernelBuilder::default()
63///         .set_info_type(KernelInfoType::Gaussian)
64///         .set_geom_info(geom_info)
65///         .build_builtin()?;
66///
67///     wand1.convolve_image(&kernel_info)?;
68///
69///     Ok(())
70/// }
71/// ```
72#[derive(Debug, Clone, Default)]
73pub struct KernelBuilder {
74    size: Option<(usize, usize)>,
75    center: Option<(usize, usize)>,
76    values: Option<Vec<f64>>,
77
78    info_type: Option<KernelInfoType>,
79    geom_info: Option<GeometryInfo>,
80}
81
82impl KernelBuilder {
83    /// Used for user defined kernels
84    pub fn set_size(mut self, size: (usize, usize)) -> KernelBuilder {
85        self.size = Some(size);
86        self
87    }
88
89    /// Used for user defined kernels
90    pub fn set_center(mut self, center: (usize, usize)) -> KernelBuilder {
91        self.center = Some(center);
92        self
93    }
94
95    /// Used for user defined kernels
96    pub fn set_values(mut self, values: &[f64]) -> KernelBuilder {
97        self.values = Some(values.into());
98        self
99    }
100
101    /// Build a user-defined [`KernelInfo`] from the configured size and values.
102    pub fn build(&self) -> Result<KernelInfo> {
103        let size = self
104            .size
105            .ok_or(MagickError("no kernel size given".to_string()))?;
106        let values = self
107            .values
108            .as_ref()
109            .ok_or(MagickError("no kernel values given".to_string()))?;
110
111        if values.len() != size.0 * size.1 {
112            return Err(MagickError(
113                "kernel size doesn't match kernel values size".to_string(),
114            ));
115        }
116
117        // Create kernel string
118        let mut kernel_string = if let Some(center) = self.center {
119            format!("{}x{}+{}+{}:", size.0, size.1, center.0, center.1)
120        } else {
121            format!("{}x{}:", size.0, size.1,)
122        };
123
124        // Add values
125        values.iter().for_each(|x| {
126            kernel_string.push_str(&format!("{x},"));
127        });
128
129        // Remove trailing ","
130        kernel_string.pop();
131
132        // Create null terminated string
133        let c_kernel_string = CString::new(kernel_string).expect("CString::new() has failed");
134
135        // Create kernel info
136        let kernel_info =
137            unsafe { bindings::AcquireKernelInfo(c_kernel_string.as_ptr(), std::ptr::null_mut()) };
138
139        if kernel_info.is_null() {
140            return Err(MagickError("failed to acquire kernel info".to_string()));
141        }
142
143        Ok(KernelInfo::new(kernel_info))
144    }
145
146    /// Used for builtin kernels
147    pub fn set_info_type(mut self, info_type: crate::KernelInfoType) -> KernelBuilder {
148        self.info_type = Some(info_type);
149        self
150    }
151
152    /// Used for builtin kernels
153    pub fn set_geom_info(mut self, geom_info: crate::GeometryInfo) -> KernelBuilder {
154        self.geom_info = Some(geom_info);
155        self
156    }
157
158    /// Build a built-in [`KernelInfo`] from the configured info type and geometry.
159    pub fn build_builtin(&self) -> Result<KernelInfo> {
160        let info_type = self
161            .info_type
162            .ok_or(MagickError("no info type given".to_string()))?;
163        let geom_info = self
164            .geom_info
165            .ok_or(MagickError("no geometry info given".to_string()))?;
166
167        // Create kernel info
168        let kernel_info = unsafe {
169            bindings::AcquireKernelBuiltIn(info_type, geom_info.inner(), std::ptr::null_mut())
170        };
171
172        if kernel_info.is_null() {
173            return Err(MagickError(
174                "failed to acquire builtin kernel info".to_string(),
175            ));
176        }
177
178        Ok(KernelInfo::new(kernel_info))
179    }
180}
181
182/// A convolution or morphology kernel, wrapping ImageMagick's `KernelInfo`.
183///
184/// Construct one via [`KernelBuilder`]. The underlying kernel is freed when this
185/// value is dropped.
186pub struct KernelInfo {
187    kernel_info: *mut bindings::KernelInfo,
188}
189
190impl KernelInfo {
191    fn new(kernel_info: *mut bindings::KernelInfo) -> KernelInfo {
192        KernelInfo { kernel_info }
193    }
194
195    /// The values within the kernel is scaled directly using given scaling factor without change.
196    pub fn scale(&mut self, factor: f64) {
197        unsafe {
198            bindings::ScaleKernelInfo(self.kernel_info, factor, bindings::GeometryFlags::NoValue)
199        }
200    }
201
202    /// Kernel normalization is designed to ensure that any use of the kernel scaling factor with
203    /// 'Convolve' or 'Correlate' morphology methods will fall into -1.0 to +1.0 range. Note that
204    /// for non-HDRI versions of IM this may cause images to have any negative results clipped,
205    /// unless some 'bias' is used.
206    ///
207    /// More specifically. Kernels which only contain positive values (such as a 'Gaussian' kernel)
208    /// will be scaled so that those values sum to +1.0, ensuring a 0.0 to +1.0 output range for
209    /// non-HDRI images.
210    ///
211    /// For Kernels that contain some negative values, (such as 'Sharpen' kernels) the kernel will
212    /// be scaled by the absolute of the sum of kernel values, so that it will generally fall
213    /// within the +/- 1.0 range.
214    ///
215    /// For kernels whose values sum to zero, (such as 'Laplacian' kernels) kernel will be scaled
216    /// by just the sum of the positive values, so that its output range will again fall into the
217    /// +/- 1.0 range.
218    pub fn normalize(&mut self) {
219        unsafe {
220            bindings::ScaleKernelInfo(
221                self.kernel_info,
222                1.0,
223                bindings::GeometryFlags::NormalizeValue,
224            )
225        }
226    }
227
228    /// For special kernels designed for locating shapes using 'Correlate', (often only containing
229    /// +1 and -1 values, representing foreground/background matching) a special normalization
230    /// method is provided to scale the positive values separately to those of the negative values,
231    /// so the kernel will be forced to become a zero-sum kernel better suited to such searches.
232    pub fn correlate_normalize(&mut self) {
233        unsafe {
234            bindings::ScaleKernelInfo(
235                self.kernel_info,
236                1.0,
237                bindings::GeometryFlags::CorrelateNormalizeValue,
238            )
239        }
240    }
241
242    /// Adds a given amount of the 'Unity' Convolution Kernel to the given pre-scaled and
243    /// normalized Kernel. This in effect adds that amount of the original image into the resulting
244    /// convolution kernel. This value is usually provided by the user as a percentage value in the
245    /// 'convolve:scale' setting.
246    ///
247    /// The resulting effect is to convert the defined kernels into blended soft-blurs, unsharp
248    /// kernels or into sharpening kernels.
249    pub fn unity_add(&mut self, scale: f64) {
250        unsafe { bindings::UnityAddKernelInfo(self.kernel_info, scale) }
251    }
252
253    pub(crate) unsafe fn get_ptr(&self) -> *mut bindings::KernelInfo {
254        self.kernel_info
255    }
256}
257
258impl Drop for KernelInfo {
259    fn drop(&mut self) {
260        unsafe { bindings::DestroyKernelInfo(self.kernel_info) };
261    }
262}
263
264impl Clone for KernelInfo {
265    fn clone(&self) -> Self {
266        let kernel_info = unsafe { bindings::CloneKernelInfo(self.kernel_info) };
267
268        if kernel_info.is_null() {
269            panic!("failed to clone kernel info");
270        }
271
272        KernelInfo::new(kernel_info)
273    }
274}
275
276impl std::fmt::Debug for KernelInfo {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        unsafe { write!(f, "{:?}", *self.kernel_info) }
279    }
280}