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}