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    pub fn build(&self) -> Result<KernelInfo> {
102        let size = self
103            .size
104            .ok_or(MagickError("no kernel size given".to_string()))?;
105        let values = self
106            .values
107            .as_ref()
108            .ok_or(MagickError("no kernel values given".to_string()))?;
109
110        if values.len() != size.0 * size.1 {
111            return Err(MagickError(
112                "kernel size doesn't match kernel values size".to_string(),
113            ));
114        }
115
116        // Create kernel string
117        let mut kernel_string = if let Some(center) = self.center {
118            format!("{}x{}+{}+{}:", size.0, size.1, center.0, center.1)
119        } else {
120            format!("{}x{}:", size.0, size.1,)
121        };
122
123        // Add values
124        values.iter().for_each(|x| {
125            kernel_string.push_str(&format!("{x},"));
126        });
127
128        // Remove trailing ","
129        kernel_string.pop();
130
131        // Create null terminated string
132        let c_kernel_string = CString::new(kernel_string).expect("CString::new() has failed");
133
134        // Create kernel info
135        let kernel_info =
136            unsafe { bindings::AcquireKernelInfo(c_kernel_string.as_ptr(), std::ptr::null_mut()) };
137
138        if kernel_info.is_null() {
139            return Err(MagickError("failed to acquire kernel info".to_string()));
140        }
141
142        Ok(KernelInfo::new(kernel_info))
143    }
144
145    /// Used for builtin kernels
146    pub fn set_info_type(mut self, info_type: crate::KernelInfoType) -> KernelBuilder {
147        self.info_type = Some(info_type);
148        self
149    }
150
151    /// Used for builtin kernels
152    pub fn set_geom_info(mut self, geom_info: crate::GeometryInfo) -> KernelBuilder {
153        self.geom_info = Some(geom_info);
154        self
155    }
156
157    pub fn build_builtin(&self) -> Result<KernelInfo> {
158        let info_type = self
159            .info_type
160            .ok_or(MagickError("no info type given".to_string()))?;
161        let geom_info = self
162            .geom_info
163            .ok_or(MagickError("no geometry info given".to_string()))?;
164
165        // Create kernel info
166        let kernel_info = unsafe {
167            bindings::AcquireKernelBuiltIn(info_type, geom_info.inner(), std::ptr::null_mut())
168        };
169
170        if kernel_info.is_null() {
171            return Err(MagickError(
172                "failed to acquire builtin kernel info".to_string(),
173            ));
174        }
175
176        Ok(KernelInfo::new(kernel_info))
177    }
178}
179
180pub struct KernelInfo {
181    kernel_info: *mut bindings::KernelInfo,
182}
183
184impl KernelInfo {
185    fn new(kernel_info: *mut bindings::KernelInfo) -> KernelInfo {
186        KernelInfo { kernel_info }
187    }
188
189    /// The values within the kernel is scaled directly using given scaling factor without change.
190    pub fn scale(&mut self, factor: f64) {
191        unsafe {
192            bindings::ScaleKernelInfo(self.kernel_info, factor, bindings::GeometryFlags::NoValue)
193        }
194    }
195
196    /// Kernel normalization is designed to ensure that any use of the kernel scaling factor with
197    /// 'Convolve' or 'Correlate' morphology methods will fall into -1.0 to +1.0 range. Note that
198    /// for non-HDRI versions of IM this may cause images to have any negative results clipped,
199    /// unless some 'bias' is used.
200    ///
201    /// More specifically. Kernels which only contain positive values (such as a 'Gaussian' kernel)
202    /// will be scaled so that those values sum to +1.0, ensuring a 0.0 to +1.0 output range for
203    /// non-HDRI images.
204    ///
205    /// For Kernels that contain some negative values, (such as 'Sharpen' kernels) the kernel will
206    /// be scaled by the absolute of the sum of kernel values, so that it will generally fall
207    /// within the +/- 1.0 range.
208    ///
209    /// For kernels whose values sum to zero, (such as 'Laplacian' kernels) kernel will be scaled
210    /// by just the sum of the positive values, so that its output range will again fall into the
211    /// +/- 1.0 range.
212    pub fn normalize(&mut self) {
213        unsafe {
214            bindings::ScaleKernelInfo(
215                self.kernel_info,
216                1.0,
217                bindings::GeometryFlags::NormalizeValue,
218            )
219        }
220    }
221
222    /// For special kernels designed for locating shapes using 'Correlate', (often only containing
223    /// +1 and -1 values, representing foreground/background matching) a special normalization
224    /// method is provided to scale the positive values separately to those of the negative values,
225    /// so the kernel will be forced to become a zero-sum kernel better suited to such searches.
226    pub fn correlate_normalize(&mut self) {
227        unsafe {
228            bindings::ScaleKernelInfo(
229                self.kernel_info,
230                1.0,
231                bindings::GeometryFlags::CorrelateNormalizeValue,
232            )
233        }
234    }
235
236    /// Adds a given amount of the 'Unity' Convolution Kernel to the given pre-scaled and
237    /// normalized Kernel. This in effect adds that amount of the original image into the resulting
238    /// convolution kernel. This value is usually provided by the user as a percentage value in the
239    /// 'convolve:scale' setting.
240    ///
241    /// The resulting effect is to convert the defined kernels into blended soft-blurs, unsharp
242    /// kernels or into sharpening kernels.
243    pub fn unity_add(&mut self, scale: f64) {
244        unsafe { bindings::UnityAddKernelInfo(self.kernel_info, scale) }
245    }
246
247    pub (crate) unsafe fn get_ptr(&self) -> *mut bindings::KernelInfo {
248        self.kernel_info
249    }
250}
251
252impl Drop for KernelInfo {
253    fn drop(&mut self) {
254        unsafe { bindings::DestroyKernelInfo(self.kernel_info) };
255    }
256}
257
258impl Clone for KernelInfo {
259    fn clone(&self) -> Self {
260        let kernel_info = unsafe { bindings::CloneKernelInfo(self.kernel_info) };
261
262        if kernel_info.is_null() {
263            panic!("failed to clone kernel info");
264        }
265
266        KernelInfo::new(kernel_info)
267    }
268}
269
270impl std::fmt::Debug for KernelInfo {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        unsafe { write!(f, "{:?}", *self.kernel_info) }
273    }
274}