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}