magick_rust/wand/
magick.rs

1/*
2 * Copyright 2016 Mattis Marjak
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 std::ffi::{CStr, CString};
17use std::{fmt, ptr, slice};
18
19#[cfg(target_os = "freebsd")]
20use libc::size_t;
21use libc::{c_char, c_uchar, c_void};
22
23use crate::bindings;
24use crate::result::MagickError;
25#[cfg(not(target_os = "freebsd"))]
26use crate::size_t;
27
28use super::{MagickFalse, MagickTrue};
29use crate::result::Result;
30
31use super::{DrawingWand, PixelWand};
32#[cfg(any(target_os = "linux", target_os = "macos"))]
33use crate::ResourceType;
34use crate::bindings::MagickBooleanType;
35use crate::{
36    AlphaChannelOption, AutoThresholdMethod, ChannelType, ColorspaceType, CompositeOperator,
37    CompressionType, DisposeType, DitherMethod, EndianType, FilterType, GravityType, Image,
38    ImageType, InterlaceType, KernelInfo, LayerMethod, MagickEvaluateOperator, MagickFunction,
39    MetricType, MorphologyMethod, OrientationType, PixelInterpolateMethod, PixelMask,
40    RenderingIntent, ResolutionType, StatisticType,
41};
42
43wand_common!(
44    MagickWand,
45    NewMagickWand,
46    ClearMagickWand,
47    IsMagickWand,
48    CloneMagickWand,
49    DestroyMagickWand,
50    MagickClearException,
51    MagickGetExceptionType,
52    MagickGetException
53);
54
55/// MagickWand is a Rustic wrapper to the Rust bindings to ImageMagick.
56///
57/// Instantiating a `MagickWand` will construct an ImageMagick "wand"
58/// on which operations can be performed via the `MagickWand` functions.
59/// When the `MagickWand` is dropped, the ImageMagick wand will be
60/// destroyed as well.
61impl MagickWand {
62    /// Creates new wand by cloning the image.
63    ///
64    /// * `img`: the image.
65    pub fn new_from_image(img: &Image<'_>) -> Result<MagickWand> {
66        let wand_ptr = unsafe { bindings::NewMagickWandFromImage(img.get_ptr()) };
67        Self::result_from_ptr_with_error_message(
68            wand_ptr,
69            MagickWand::from_ptr,
70            "failed to create magick wand from image",
71        )
72    }
73
74    pub fn new_image(&self, columns: usize, rows: usize, background: &PixelWand) -> Result<()> {
75        self.result_from_boolean(unsafe {
76            bindings::MagickNewImage(self.wand, columns, rows, background.as_ptr())
77        })
78    }
79
80    /// opt-in platforms that have resource limits support
81    #[cfg(any(target_os = "linux", target_os = "macos"))]
82    pub fn set_resource_limit(resource: ResourceType, limit: u64) -> Result<()> {
83        Self::result_from_boolean_with_error_message(
84            unsafe {
85                bindings::MagickSetResourceLimit(resource, limit as bindings::MagickSizeType)
86            },
87            "failed to set resource limit",
88        )
89    }
90
91    pub fn set_option(&mut self, key: &str, value: &str) -> Result<()> {
92        let c_key = CString::new(key).map_err(|_| "key string contains null byte")?;
93        let c_value = CString::new(value).map_err(|_| "value string contains null byte")?;
94        self.result_from_boolean(unsafe {
95            bindings::MagickSetOption(self.wand, c_key.as_ptr(), c_value.as_ptr())
96        })
97    }
98
99    pub fn annotate_image(
100        &mut self,
101        drawing_wand: &DrawingWand,
102        x: f64,
103        y: f64,
104        angle: f64,
105        text: &str,
106    ) -> Result<()> {
107        let c_string = CString::new(text).map_err(|_| "could not convert to cstring")?;
108        self.result_from_boolean(unsafe {
109            bindings::MagickAnnotateImage(
110                self.wand,
111                drawing_wand.as_ptr(),
112                x,
113                y,
114                angle,
115                c_string.as_ptr() as *const _,
116            )
117        })
118    }
119
120    /// Add all images from another wand to this wand at the current index.
121    pub fn add_image(&mut self, other_wand: &MagickWand) -> Result<()> {
122        self.result_from_boolean(unsafe { bindings::MagickAddImage(self.wand, other_wand.wand) })
123    }
124
125    pub fn append_all(&mut self, stack: bool) -> Result<MagickWand> {
126        unsafe { bindings::MagickResetIterator(self.wand) };
127        let wand_ptr = unsafe { bindings::MagickAppendImages(self.wand, stack.into()) };
128        Self::result_from_ptr_with_error_message(
129            wand_ptr,
130            MagickWand::from_ptr,
131            "failed to append image",
132        )
133    }
134
135    pub fn label_image(&self, label: &str) -> Result<()> {
136        let c_label = CString::new(label).map_err(|_| "label string contains null byte")?;
137        self.result_from_boolean(unsafe { bindings::MagickLabelImage(self.wand, c_label.as_ptr()) })
138    }
139
140    pub fn write_images(&self, path: &str, adjoin: bool) -> Result<()> {
141        let c_name = CString::new(path).map_err(|_| "path string contains null byte")?;
142        self.result_from_boolean(unsafe {
143            bindings::MagickWriteImages(self.wand, c_name.as_ptr(), adjoin.into())
144        })
145    }
146
147    /// Read the image data from the named file.
148    pub fn read_image(&self, path: &str) -> Result<()> {
149        let c_name = CString::new(path).map_err(|_| "path string contains null byte")?;
150        self.result_from_boolean(unsafe { bindings::MagickReadImage(self.wand, c_name.as_ptr()) })
151    }
152
153    /// Read the image data from the vector of bytes.
154    pub fn read_image_blob<T: AsRef<[u8]>>(&self, data: T) -> Result<()> {
155        let int_slice = data.as_ref();
156        let size = int_slice.len();
157        self.result_from_boolean(unsafe {
158            bindings::MagickReadImageBlob(self.wand, int_slice.as_ptr() as *const c_void, size)
159        })
160    }
161
162    /// Same as read_image, but reads only the width, height, size and format of an image,
163    /// without reading data.
164    pub fn ping_image(&self, path: &str) -> Result<()> {
165        let c_name = CString::new(path).map_err(|_| "path string contains null byte")?;
166        self.result_from_boolean(unsafe { bindings::MagickPingImage(self.wand, c_name.as_ptr()) })
167    }
168
169    /// Same as read_image, but reads only the width, height, size and format of an image,
170    /// without reading data.
171    pub fn ping_image_blob<T: AsRef<[u8]>>(&self, data: T) -> Result<()> {
172        let int_slice = data.as_ref();
173        let size = int_slice.len();
174        self.result_from_boolean(unsafe {
175            bindings::MagickPingImageBlob(self.wand, int_slice.as_ptr() as *const c_void, size)
176        })
177    }
178
179    /// Composes all the image layers from the current given image onward to produce a single image
180    /// of the merged layers.
181    ///
182    /// The inital canvas's size depends on the given LayerMethod, and is initialized using the
183    /// first images background color. The images are then composited onto that image in sequence
184    /// using the given composition that has been assigned to each individual image.
185    ///
186    /// * `method`: the method of selecting the size of the initial canvas.
187    ///   MergeLayer: Merge all layers onto a canvas just large enough to hold all the actual
188    ///   images. The virtual canvas of the first image is preserved but otherwise ignored.
189    ///
190    ///     FlattenLayer: Use the virtual canvas size of first image. Images which fall outside
191    ///   this canvas is clipped. This can be used to 'fill out' a given virtual canvas.
192    ///
193    ///     MosaicLayer: Start with the virtual canvas of the first image, enlarging left and right
194    ///   edges to contain all images. Images with negative offsets will be clipped.
195    pub fn merge_image_layers(&self, method: LayerMethod) -> Result<MagickWand> {
196        let wand_ptr = unsafe { bindings::MagickMergeImageLayers(self.wand, method) };
197        Self::result_from_ptr_with_error_message(
198            wand_ptr,
199            MagickWand::from_ptr,
200            "failed to merge image layers",
201        )
202    }
203
204    /// Returns the number of images associated with a magick wand.
205    pub fn get_number_images(&self) -> usize {
206        unsafe { bindings::MagickGetNumberImages(self.wand) }
207    }
208
209    /// Compare two images and return tuple `(distortion, diffImage)`
210    /// `diffImage` is `None` if `distortion == 0`
211    pub fn compare_images(
212        &self,
213        reference: &MagickWand,
214        metric: MetricType,
215    ) -> (f64, Option<MagickWand>) {
216        let mut distortion: f64 = 0.0;
217        let wand_ptr = unsafe {
218            bindings::MagickCompareImages(self.wand, reference.wand, metric, &mut distortion)
219        };
220
221        let wand = Self::result_from_ptr_with_error_message(wand_ptr, MagickWand::from_ptr, "").ok();
222        (distortion, wand)
223    }
224
225    /// Compose another image onto self at (x, y) using composition_operator
226    pub fn compose_images(
227        &self,
228        reference: &MagickWand,
229        composition_operator: CompositeOperator,
230        clip_to_self: bool,
231        x: isize,
232        y: isize,
233    ) -> Result<()> {
234        self.result_from_boolean(unsafe {
235            bindings::MagickCompositeImage(
236                self.wand,
237                reference.wand,
238                composition_operator,
239                MagickBooleanType::from(clip_to_self),
240                x,
241                y,
242            )
243        })
244    }
245
246    /// Compose another image onto self with gravity using composition_operator
247    pub fn compose_images_gravity(
248        &self,
249        reference: &MagickWand,
250        composition_operator: CompositeOperator,
251        gravity_type: GravityType,
252    ) -> Result<()> {
253        self.result_from_boolean(unsafe {
254            bindings::MagickCompositeImageGravity(
255                self.wand,
256                reference.wand,
257                composition_operator,
258                gravity_type,
259            )
260        })
261    }
262
263    /// Rebuilds image sequence with each frame size the same as first frame, and composites each frame atop of previous.
264    /// Only affects GIF, and other formats with multiple pages/layers.
265    pub fn coalesce(&mut self) -> Result<MagickWand> {
266        let wand_ptr = unsafe { bindings::MagickCoalesceImages(self.wand) };
267        Self::result_from_ptr_with_error_message(
268            wand_ptr,
269            MagickWand::from_ptr,
270            "failed to coalesce images",
271        )
272    }
273
274    /// Replaces colors in the image from a color lookup table.
275    pub fn clut_image(&self, clut_wand: &MagickWand, method: PixelInterpolateMethod) -> Result<()> {
276        self.result_from_boolean(unsafe {
277            bindings::MagickClutImage(self.wand, clut_wand.wand, method)
278        })
279    }
280
281    pub fn hald_clut_image(&self, clut_wand: &MagickWand) -> Result<()> {
282        self.result_from_boolean(unsafe {
283            bindings::MagickHaldClutImage(self.wand, clut_wand.wand)
284        })
285    }
286
287    pub fn fx(&mut self, expression: &str) -> Result<MagickWand> {
288        let c_expression =
289            CString::new(expression).map_err(|_| "expression string contains null byte")?;
290        let wand_ptr = unsafe { bindings::MagickFxImage(self.wand, c_expression.as_ptr()) };
291        Self::result_from_ptr_with_error_message(
292            wand_ptr,
293            MagickWand::from_ptr,
294            "failed to fx the image",
295        )
296    }
297
298    pub fn set_size(&self, columns: usize, rows: usize) -> Result<()> {
299        self.result_from_boolean(unsafe { bindings::MagickSetSize(self.wand, columns, rows) })
300    }
301
302    /// Define two 'quantum_range' functions because the bindings::QuantumRange symbol
303    /// is not available if hdri is disabled in the compiled ImageMagick libs
304    #[cfg(not(feature = "disable-hdri"))]
305    fn quantum_range(&self) -> Result<f64> {
306        Ok(bindings::QuantumRange)
307    }
308
309    /// with disable-hdri enabled we define our own quantum_range
310    /// values lifted directly from magick-type.h
311    #[cfg(feature = "disable-hdri")]
312    fn quantum_range(&self) -> Result<f64> {
313        match bindings::MAGICKCORE_QUANTUM_DEPTH {
314            8 => Ok(255.0f64),
315            16 => Ok(65535.0f64),
316            32 => Ok(4294967295.0f64),
317            64 => Ok(18446744073709551615.0f64),
318            _ => Err(MagickError(
319                "Quantum depth must be one of 8, 16, 32 or 64".to_string(),
320            )),
321        }
322    }
323
324    /// Level an image. Black and white points are multiplied with QuantumRange to
325    /// decrease dependencies on the end user.
326    pub fn level_image(&self, black_point: f64, gamma: f64, white_point: f64) -> Result<()> {
327        let quantum_range = self.quantum_range()?;
328
329        self.result_from_boolean(unsafe {
330            bindings::MagickLevelImage(
331                self.wand,
332                black_point * quantum_range,
333                gamma,
334                white_point * quantum_range,
335            )
336        })
337    }
338
339    /// Applies the reversed [level_image](Self::level_image). It compresses the full range of color values, so
340    /// that they lie between the given black and white points. Gamma is applied before the values
341    /// are mapped. It can be used to de-contrast a greyscale image to the exact levels specified.
342    pub fn levelize_image(&self, black_point: f64, gamma: f64, white_point: f64) -> Result<()> {
343        let quantum_range = self.quantum_range()?;
344
345        self.result_from_boolean(unsafe {
346            bindings::MagickLevelizeImage(
347                self.wand,
348                black_point * quantum_range,
349                gamma,
350                white_point * quantum_range,
351            )
352        })
353    }
354
355    /// MagickNormalizeImage enhances the contrast of a color image by adjusting the pixels color
356    /// to span the entire range of colors available
357    pub fn normalize_image(&self) -> Result<()> {
358        self.result_from_boolean(unsafe { bindings::MagickNormalizeImage(self.wand) })
359    }
360
361    /// MagickOrderedDitherImage performs an ordered dither based on a number of pre-defined
362    /// dithering threshold maps, but over multiple intensity levels, which can be different for
363    /// different channels, according to the input arguments.
364    pub fn ordered_dither_image(&self, threshold_map: &str) -> Result<()> {
365        let c_threshold_map =
366            CString::new(threshold_map).map_err(|_| "threshold_map string contains null byte")?;
367
368        self.result_from_boolean(unsafe {
369            bindings::MagickOrderedDitherImage(self.wand, c_threshold_map.as_ptr())
370        })
371    }
372
373    /// Apply sigmoidal contrast to the image
374    ///
375    /// Adjusts the contrast of an image with a non-linear sigmoidal contrast algorithm. Increase
376    /// the contrast of the image using a sigmoidal transfer function without saturating highlights
377    /// or shadows. Contrast indicates how much to increase the contrast (0 is none; 3 is typical;
378    /// 20 is pushing it); mid-point indicates where midtones fall in the resultant image (0.0 is
379    /// white; 0.5 is middle-gray; 1.0 is black). Set sharpen to `true` to increase the image
380    /// contrast otherwise the contrast is reduced.
381    ///
382    /// * `sharpen`: increase or decrease image contrast
383    /// * `strength`: strength of the contrast, the larger the number the more 'threshold-like' it becomes.
384    /// * `midpoint`: midpoint of the function as a number in range [0, 1]
385    pub fn sigmoidal_contrast_image(
386        &self,
387        sharpen: bool,
388        strength: f64,
389        midpoint: f64,
390    ) -> Result<()> {
391        let quantum_range = self.quantum_range()?;
392
393        self.result_from_boolean(unsafe {
394            bindings::MagickSigmoidalContrastImage(
395                self.wand,
396                sharpen.into(),
397                strength,
398                midpoint * quantum_range,
399            )
400        })
401    }
402
403    /// Extend the image as defined by the geometry, gravity, and wand background color. Set the
404    /// (x,y) offset of the geometry to move the original wand relative to the extended wand.
405    pub fn extend_image(&self, width: usize, height: usize, x: isize, y: isize) -> Result<()> {
406        self.result_from_boolean(unsafe {
407            bindings::MagickExtentImage(self.wand, width, height, x, y)
408        })
409    }
410
411    pub fn profile_image<'a, T: Into<Option<&'a [u8]>>>(
412        &self,
413        name: &str,
414        profile: T,
415    ) -> Result<()> {
416        let c_name = CString::new(name).map_err(|_| "name string contains null byte")?;
417        let result = unsafe {
418            let profile = profile.into();
419            let profile_ptr = match profile {
420                Some(data) => data.as_ptr(),
421                None => ptr::null(),
422            } as *const c_void;
423            let profile_len = match profile {
424                Some(data) => data.len(),
425                None => 0,
426            };
427            bindings::MagickProfileImage(self.wand, c_name.as_ptr(), profile_ptr, profile_len)
428        };
429        self.result_from_boolean(result)
430    }
431
432    pub fn strip_image(&self) -> Result<()> {
433        self.result_from_boolean(unsafe { bindings::MagickStripImage(self.wand) })
434    }
435
436    pub fn flip_image(&self) -> Result<()> {
437        self.result_from_boolean(unsafe { bindings::MagickFlipImage(self.wand) })
438    }
439
440    pub fn negate_image(&self) -> Result<()> {
441        self.result_from_boolean(unsafe { bindings::MagickNegateImage(self.wand, MagickTrue) })
442    }
443
444    pub fn flop_image(&self) -> Result<()> {
445        self.result_from_boolean(unsafe { bindings::MagickFlopImage(self.wand) })
446    }
447
448    pub fn blur_image(&self, radius: f64, sigma: f64) -> Result<()> {
449        self.result_from_boolean(unsafe { bindings::MagickBlurImage(self.wand, radius, sigma) })
450    }
451
452    pub fn gaussian_blur_image(&self, radius: f64, sigma: f64) -> Result<()> {
453        self.result_from_boolean(unsafe {
454            bindings::MagickGaussianBlurImage(self.wand, radius, sigma)
455        })
456    }
457
458    /// Replace each pixel with corresponding statistic from the neighborhood of the specified width and height.
459    ///
460    /// * `statistic_type`: the statistic type (e.g. `StatisticType::Median`, `StatisticType::Mode`, etc.).
461    /// * `width`: the width of the pixel neighborhood.
462    /// * `height`: the height of the pixel neighborhood.
463    pub fn statistic_image(
464        &self,
465        statistic_type: StatisticType,
466        width: usize,
467        height: usize,
468    ) -> Result<()> {
469        self.result_from_boolean(unsafe {
470            bindings::MagickStatisticImage(self.wand, statistic_type, width, height)
471        })
472    }
473
474    /// Calculate median for each pixel's neighborhood.
475    ///
476    /// See [statistic_image](Self::statistic_image)
477    pub fn median_blur_image(&self, width: usize, height: usize) -> Result<()> {
478        self.statistic_image(StatisticType::Median, width, height)
479    }
480
481    /// Adaptively resize the currently selected image.
482    pub fn adaptive_resize_image(&self, width: usize, height: usize) -> Result<()> {
483        self.result_from_boolean(unsafe {
484            bindings::MagickAdaptiveResizeImage(self.wand, width, height)
485        })
486    }
487
488    /// Rotate the currently selected image by the given number of degrees,
489    /// filling any empty space with the background color of a given PixelWand
490    pub fn rotate_image(&self, background: &PixelWand, degrees: f64) -> Result<()> {
491        self.result_from_boolean(unsafe {
492            bindings::MagickRotateImage(self.wand, background.as_ptr(), degrees)
493        })
494    }
495
496    /// Trim the image removing the backround color from the edges.
497    pub fn trim_image(&self, fuzz: f64) -> Result<()> {
498        self.result_from_boolean(unsafe { bindings::MagickTrimImage(self.wand, fuzz) })
499    }
500
501    /// Retrieve the width of the image.
502    pub fn get_image_width(&self) -> usize {
503        unsafe { bindings::MagickGetImageWidth(self.wand) }
504    }
505
506    /// Retrieve the height of the image.
507    pub fn get_image_height(&self) -> usize {
508        unsafe { bindings::MagickGetImageHeight(self.wand) }
509    }
510
511    /// Retrieve the page geometry (width, height, x offset, y offset) of the image.
512    pub fn get_image_page(&self) -> (usize, usize, isize, isize) {
513        let (mut width, mut height, mut x, mut y) = (0usize, 0usize, 0isize, 0isize);
514        unsafe {
515            // Note: The C MagickGetImagePage function always returns true
516            // and exits on error, so we don't check the return value here.
517            bindings::MagickGetImagePage(self.wand, &mut width, &mut height, &mut x, &mut y);
518        }
519        (width, height, x, y)
520    }
521
522    /// Reset the Wand page canvas and position.
523    pub fn reset_image_page(&self, page_geometry: &str) -> Result<()> {
524        let c_page_geometry =
525            CString::new(page_geometry).map_err(|_| "page_geometry contains null byte")?;
526        self.result_from_boolean(unsafe {
527            bindings::MagickResetImagePage(self.wand, c_page_geometry.as_ptr())
528        })
529    }
530
531    /// Returns a value associated with the specified artifact.
532    ///
533    /// * `artifact`: the artifact.
534    pub fn get_image_artifact(&self, artifact: &str) -> Result<String> {
535        let c_artifact =
536            CString::new(artifact).map_err(|_| "artifact string contains null byte")?;
537
538        let c_value = unsafe { bindings::MagickGetImageArtifact(self.wand, c_artifact.as_ptr()) };
539        Self::result_from_ptr_with_error_message(
540            c_value,
541            Self::c_char_into_string,
542            format!("missing artifact: {artifact}"),
543        )
544    }
545
546    pub fn get_image_artifacts(&self, pattern: &str) -> Result<Vec<String>> {
547        let c_pattern = CString::new(pattern)
548            .map_err(|_| MagickError("artifact string contains null byte".to_string()))?;
549        let mut num_of_artifacts: size_t = 0;
550
551        let c_values = unsafe {
552            bindings::MagickGetImageArtifacts(self.wand, c_pattern.as_ptr(), &mut num_of_artifacts)
553        };
554
555        Self::result_from_ptr_with_error_message(
556            c_values,
557            |c_values| Self::c_char_to_string_vec(c_values, num_of_artifacts),
558            "image has no artifacts",
559        )
560    }
561
562    /// Sets a key-value pair in the image artifact namespace. Artifacts differ from properties.
563    /// Properties are public and are generally exported to an external image format if the format
564    /// supports it. Artifacts are private and are utilized by the internal ImageMagick API to
565    /// modify the behavior of certain algorithms.
566    ///
567    /// * `artifact`: the artifact.
568    /// * `value`: the value.
569    ///
570    /// # Example
571    ///
572    /// This example shows how you can blend an image with its blurred copy with 50% opacity by
573    /// setting "compose:args" to "50". This is equivalent to having `-define compose:args=50` when
574    /// using imagemagick cli.
575    ///
576    /// ```
577    /// use magick_rust::{MagickWand, PixelWand, CompositeOperator};
578    ///
579    /// fn main() -> Result<(), magick_rust::MagickError> {
580    ///     let mut wand1 = MagickWand::new();
581    ///     wand1.new_image(4, 4, &PixelWand::new())?; // Replace with `read_image` to open your image file
582    ///     let wand2 = wand1.clone();
583    ///
584    ///     wand1.median_blur_image(10, 10)?;
585    ///
586    ///     wand1.set_image_artifact("compose:args", "50")?;
587    ///     wand1.compose_images(&wand2, CompositeOperator::Blend, false, 0, 0)?;
588    ///
589    ///     Ok(())
590    /// }
591    /// ```
592    pub fn set_image_artifact(&mut self, artifact: &str, value: &str) -> Result<()> {
593        let c_artifact =
594            CString::new(artifact).map_err(|_| "artifact string contains null byte")?;
595        let c_value = CString::new(value).map_err(|_| "value string contains null byte")?;
596
597        self.result_from_boolean(unsafe {
598            bindings::MagickSetImageArtifact(self.wand, c_artifact.as_ptr(), c_value.as_ptr())
599        })
600    }
601
602    /// Deletes a wand artifact.
603    ///
604    /// * `artifact`: the artifact.
605    pub fn delete_image_artifact(&mut self, artifact: &str) -> Result<()> {
606        let c_artifact =
607            CString::new(artifact).map_err(|_| "artifact string contains null byte")?;
608
609        Self::result_from_boolean_with_error_message(
610            unsafe { bindings::MagickDeleteImageArtifact(self.wand, c_artifact.as_ptr()) },
611            format!("missing artifact: {artifact}"),
612        )
613    }
614
615    /// Retrieve the named image property value.
616    pub fn get_image_property(&self, name: &str) -> Result<String> {
617        let c_name = CString::new(name).map_err(|_| "name string contains null byte")?;
618        let c_value = unsafe { bindings::MagickGetImageProperty(self.wand, c_name.as_ptr()) };
619
620        Self::result_from_ptr_with_error_message(
621            c_value,
622            Self::c_char_into_string,
623            format!("missing property: {name}"),
624        )
625    }
626
627    pub fn get_image_properties(&self, pattern: &str) -> Result<Vec<String>> {
628        let c_pattern = CString::new(pattern)
629            .map_err(|_| MagickError("artifact string contains null byte".to_string()))?;
630        let mut num_of_artifacts: size_t = 0;
631
632        let c_values = unsafe {
633            bindings::MagickGetImageProperties(self.wand, c_pattern.as_ptr(), &mut num_of_artifacts)
634        };
635
636        self.result_from_ptr(c_values, |c_values| Self::c_char_to_string_vec(c_values, num_of_artifacts))
637    }
638
639    /// Set the named image property.
640    pub fn set_image_property(&self, name: &str, value: &str) -> Result<()> {
641        let c_name = CString::new(name).map_err(|_| "name string contains null byte")?;
642        let c_value = CString::new(value).map_err(|_| "value string contains null byte")?;
643        self.result_from_boolean(unsafe {
644            bindings::MagickSetImageProperty(self.wand, c_name.as_ptr(), c_value.as_ptr())
645        })
646    }
647
648    /// Returns a `PixelWand` instance for the pixel specified by x and y offests.
649    pub fn get_image_pixel_color(&self, x: isize, y: isize) -> Option<PixelWand> {
650        let pw = PixelWand::new();
651
652        let result = unsafe { bindings::MagickGetImagePixelColor(self.wand, x, y, pw.as_ptr()) };
653        self.result_from_boolean(result).map(|_| pw).ok()
654    }
655
656    /// Sets the image sampling factors.
657    ///
658    /// samplingFactors: An array of floats representing the sampling factor for each color component (in RGB order).
659    pub fn set_sampling_factors(&self, samplingFactors: &[f64]) -> Result<()> {
660        self.result_from_boolean( unsafe {
661            bindings::MagickSetSamplingFactors(
662                self.wand,
663                samplingFactors.len(),
664                &samplingFactors[0],
665            )
666        })
667    }
668
669    /// Returns the image histogram as a vector of `PixelWand` instances for every unique color.
670    pub fn get_image_histogram(&self) -> Option<Vec<PixelWand>> {
671        let mut color_count: size_t = 0;
672
673        unsafe {
674            bindings::MagickGetImageHistogram(self.wand, &mut color_count)
675                .as_mut()
676                .map(|ptrs| {
677                    slice::from_raw_parts(ptrs, color_count)
678                        .iter()
679                        .map(|wand_ptr| PixelWand::from_ptr(*wand_ptr))
680                        .collect()
681                })
682        }
683    }
684
685    /// Sharpens an image. We convolve the image with a Gaussian operator of the
686    /// given radius and standard deviation (sigma). For reasonable results, the
687    /// radius should be larger than sigma. Use a radius of 0 and SharpenImage()
688    /// selects a suitable radius for you.
689    ///
690    /// radius: the radius of the Gaussian, in pixels, not counting the center pixel.
691    ///
692    /// sigma: the standard deviation of the Gaussian, in pixels.
693    ///
694    pub fn sharpen_image(&self, radius: f64, sigma: f64) -> Result<()> {
695        self.result_from_boolean(unsafe { bindings::MagickSharpenImage(self.wand, radius, sigma) })
696    }
697
698    /// Set the background color.
699    pub fn set_background_color(&self, pixel_wand: &PixelWand) -> Result<()> {
700        self.result_from_boolean(unsafe {
701            bindings::MagickSetBackgroundColor(self.wand, pixel_wand.as_ptr())
702        })
703    }
704
705    /// Set the image background color.
706    pub fn set_image_background_color(&self, pixel_wand: &PixelWand) -> Result<()> {
707        self.result_from_boolean(unsafe {
708            bindings::MagickSetImageBackgroundColor(self.wand, pixel_wand.as_ptr())
709        })
710    }
711
712    /// Returns the image resolution as a pair (horizontal resolution, vertical resolution)
713    pub fn get_image_resolution(&self) -> Result<(f64, f64)> {
714        let mut x_resolution = 0f64;
715        let mut y_resolution = 0f64;
716        self.result_from_boolean(unsafe {
717            bindings::MagickGetImageResolution(self.wand, &mut x_resolution, &mut y_resolution)
718        })
719        .map(|_| (x_resolution, y_resolution))
720    }
721
722    /// Sets the image resolution
723    pub fn set_image_resolution(&self, x_resolution: f64, y_resolution: f64) -> Result<()> {
724        self.result_from_boolean(unsafe {
725            bindings::MagickSetImageResolution(self.wand, x_resolution, y_resolution)
726        })
727    }
728
729    /// Sets the wand resolution
730    pub fn set_resolution(&self, x_resolution: f64, y_resolution: f64) -> Result<()> {
731        self.result_from_boolean(unsafe {
732            bindings::MagickSetResolution(self.wand, x_resolution, y_resolution)
733        })
734    }
735
736    /// Returns the image resolution as a pair (horizontal resolution, vertical resolution)
737    pub fn sepia_tone_image(&self, threshold: f64) -> Result<()> {
738        self.result_from_boolean(unsafe {
739            bindings::MagickSepiaToneImage(self.wand, threshold * self.quantum_range()?)
740        })
741    }
742
743    /// Extracts pixel data from the image as a vector of 0..255 values defined by `map`.
744    /// See <https://imagemagick.org/api/magick-image.php#MagickExportImagePixels> for more information.
745    pub fn export_image_pixels(
746        &self,
747        x: isize,
748        y: isize,
749        width: usize,
750        height: usize,
751        map: &str,
752    ) -> Option<Vec<u8>> {
753        let c_map = CString::new(map).ok()?;
754        let capacity = width * height * map.len();
755        let mut pixels = vec![0; capacity];
756
757        unsafe {
758            if bindings::MagickExportImagePixels(
759                self.wand,
760                x,
761                y,
762                width,
763                height,
764                c_map.as_ptr(),
765                bindings::StorageType::CharPixel,
766                pixels.as_mut_ptr() as *mut c_void,
767            ) == MagickTrue
768            {
769                Some(pixels)
770            } else {
771                None
772            }
773        }
774    }
775
776    pub fn export_image_pixels_double(
777        &self,
778        x: isize,
779        y: isize,
780        width: usize,
781        height: usize,
782        map: &str,
783    ) -> Option<Vec<f64>> {
784        let c_map = CString::new(map).expect("map contains null byte");
785        let capacity = width * height * map.len();
786        let mut pixels = Vec::with_capacity(capacity);
787        pixels.resize(capacity, 0.0);
788
789        unsafe {
790            if bindings::MagickExportImagePixels(
791                self.wand,
792                x,
793                y,
794                width,
795                height,
796                c_map.as_ptr(),
797                bindings::StorageType::DoublePixel,
798                pixels.as_mut_ptr() as *mut c_void,
799            ) == MagickTrue
800            {
801                Some(pixels)
802            } else {
803                None
804            }
805        }
806    }
807
808    /// Resize the image to the specified width and height, using the
809    /// specified filter type.
810    pub fn resize_image(&self, width: usize, height: usize, filter: FilterType) -> Result<()> {
811        self.result_from_boolean(unsafe {
812            bindings::MagickResizeImage(self.wand, width, height, filter)
813        })
814    }
815
816    /// Resize image by specifying the new size in percent of last size.
817    ///
818    /// Effectively resizes image to (current width * `width_scale`, current height *
819    /// `height_scale`)
820    pub fn scale_image(
821        &self,
822        width_scale: f64,
823        height_scale: f64,
824        filter: FilterType,
825    ) -> Result<()> {
826        if width_scale < 0.0 {
827            return Err(MagickError("negative width scale given".to_string()));
828        }
829        if height_scale < 0.0 {
830            return Err(MagickError("negative height scale given".to_string()));
831        }
832
833        let width = self.get_image_width();
834        let height = self.get_image_height();
835
836        let width = ((width as f64) * width_scale) as usize;
837        let height = ((height as f64) * height_scale) as usize;
838
839        self.resize_image(width, height, filter)
840    }
841
842    /// Resize the image to the specified width and height, using the
843    /// 'thumbnail' optimizations which remove a lot of image meta-data with the goal
844    /// of producing small low cost images suited for display on the web.
845    pub fn thumbnail_image(&self, width: usize, height: usize) -> Result<()> {
846        self.result_from_boolean(unsafe {
847            bindings::MagickThumbnailImage(self.wand, width, height)
848        })
849    }
850
851    /// Extract a region of the image. The width and height is used as the size
852    /// of the region. X and Y is the offset.
853    pub fn crop_image(&self, width: usize, height: usize, x: isize, y: isize) -> Result<()> {
854        self.result_from_boolean(unsafe {
855            bindings::MagickCropImage(self.wand, width, height, x, y)
856        })
857    }
858
859    /// Sample the image to the target resolution
860    ///
861    /// This is incredibly fast, as it does 1-1 pixel mapping for downscales, and box filtering for
862    /// upscales
863    pub fn sample_image(&self, width: usize, height: usize) -> Result<()> {
864        self.result_from_boolean(unsafe { bindings::MagickSampleImage(self.wand, width, height) })
865    }
866
867    /// Resample the image to the specified horizontal and vertical resolution, using the
868    /// specified filter type.
869    pub fn resample_image(
870        &self,
871        x_resolution: f64,
872        y_resolution: f64,
873        filter: FilterType,
874    ) -> Result<()> {
875        self.result_from_boolean(unsafe {
876            bindings::MagickResampleImage(self.wand, x_resolution, y_resolution, filter)
877        })
878    }
879
880    /// Rescale the image using seam carving algorithm
881    pub fn liquid_rescale_image(
882        &self,
883        width: usize,
884        height: usize,
885        delta_x: f64,
886        rigidity: f64,
887    ) -> Result<()> {
888        self.result_from_boolean(unsafe {
889            bindings::MagickLiquidRescaleImage(self.wand, width, height, delta_x, rigidity)
890        })
891    }
892
893    /// Implodes the image towards the center by the specified percentage
894    pub fn implode(&self, amount: f64, method: PixelInterpolateMethod) -> Result<()> {
895        self.result_from_boolean(unsafe { bindings::MagickImplodeImage(self.wand, amount, method) })
896    }
897
898    /// Resize the image to fit within the given dimensions, maintaining
899    /// the current aspect ratio.
900    pub fn fit(&self, width: usize, height: usize) {
901        let mut width_ratio = width as f64;
902        width_ratio /= self.get_image_width() as f64;
903        let mut height_ratio = height as f64;
904        height_ratio /= self.get_image_height() as f64;
905        let (new_width, new_height) = if width_ratio < height_ratio {
906            (
907                width,
908                (self.get_image_height() as f64 * width_ratio) as usize,
909            )
910        } else {
911            (
912                (self.get_image_width() as f64 * height_ratio) as usize,
913                height,
914            )
915        };
916        unsafe {
917            bindings::MagickResetIterator(self.wand);
918            while bindings::MagickNextImage(self.wand) != MagickFalse {
919                bindings::MagickResizeImage(self.wand, new_width, new_height, FilterType::Lanczos);
920            }
921        }
922    }
923
924    /// Detect if the loaded image is not in top-left orientation, and
925    /// hence should be "auto" oriented so it is suitable for viewing.
926    pub fn requires_orientation(&self) -> bool {
927        self.get_image_orientation() != OrientationType::TopLeft
928    }
929
930    /// Automatically adjusts the loaded image so that its orientation is
931    /// suitable for viewing (i.e. top-left orientation).
932    ///
933    /// Returns `true` if successful or `false` if an error occurred.
934    pub fn auto_orient(&self) -> bool {
935        unsafe { bindings::MagickAutoOrientImage(self.wand) == MagickTrue }
936    }
937
938    /// Write the current image to the provided path.
939    pub fn write_image(&self, path: &str) -> Result<()> {
940        let c_name = CString::new(path).map_err(|_| "name string contains null byte")?;
941        self.result_from_boolean(unsafe { bindings::MagickWriteImage(self.wand, c_name.as_ptr()) })
942    }
943
944    /// Write the image in the desired format to a new blob.
945    ///
946    /// The `format` argument may be any ImageMagick supported image
947    /// format (e.g. GIF, JPEG, PNG, etc).
948    pub fn write_image_blob(&self, format: &str) -> Result<Vec<u8>> {
949        let c_format = CString::new(format).map_err(|_| "format string contains null byte")?;
950        let mut length: size_t = 0;
951        let blob = unsafe {
952            bindings::MagickResetIterator(self.wand);
953            bindings::MagickSetImageFormat(self.wand, c_format.as_ptr());
954            bindings::MagickGetImageBlob(self.wand, &mut length)
955        };
956
957        self.result_from_ptr(blob, |blob| Self::c_array_into_vec(blob, length))
958    }
959
960    /// Write the images in the desired format to a new blob.
961    ///
962    /// The `format` argument may be any ImageMagick supported image
963    /// format (e.g. GIF, JPEG, PNG, etc).
964    pub fn write_images_blob(&self, format: &str) -> Result<Vec<u8>> {
965        let c_format = CString::new(format).map_err(|_| "format string contains null byte")?;
966        let mut length: size_t = 0;
967        let blob = unsafe {
968            bindings::MagickSetIteratorIndex(self.wand, 0);
969            bindings::MagickSetImageFormat(self.wand, c_format.as_ptr());
970            bindings::MagickGetImagesBlob(self.wand, &mut length)
971        };
972
973        Ok(Self::c_array_into_vec(blob, length))
974    }
975
976    /// Return false if the image alpha channel is not activated.
977    /// That is, the image is RGB rather than RGBA or CMYK rather than CMYKA
978    pub fn get_image_alpha_channel(&self) -> bool {
979        let res = unsafe { bindings::MagickGetImageAlphaChannel(self.wand) };
980        res == MagickTrue
981    }
982
983    /// Renders the drawing wand on the current image
984    pub fn draw_image(&mut self, drawing_wand: &DrawingWand) -> Result<()> {
985        self.result_from_boolean(unsafe { bindings::MagickDrawImage(self.wand, drawing_wand.as_ptr()) })
986    }
987
988    /// Removes skew from the image. Skew is an artifact that
989    /// occurs in scanned images because of the camera being misaligned,
990    /// imperfections in the scanning or surface, or simply because the paper was
991    /// not placed completely flat when scanned
992    pub fn deskew_image(&mut self, threshold: f64) -> Result<()> {
993        self.result_from_boolean(unsafe { bindings::MagickDeskewImage(self.wand, threshold) })
994    }
995
996    /// Sets image clip mask.
997    ///
998    /// * `pixel_mask`: type of mask, Read or Write.
999    /// * `clip_mask`: the clip_mask wand.
1000    pub fn set_image_mask(&mut self, pixel_mask: PixelMask, clip_mask: &MagickWand) -> Result<()> {
1001        self.result_from_boolean(unsafe {
1002            bindings::MagickSetImageMask(self.wand, pixel_mask, clip_mask.wand)
1003        })
1004    }
1005
1006    /// Set image channel mask
1007    pub fn set_image_channel_mask(&mut self, option: ChannelType) -> ChannelType {
1008        unsafe { bindings::MagickSetImageChannelMask(self.wand, option) }
1009    }
1010
1011    /// Apply an arithmetic, relational, or logical
1012    /// expression to an image.  Use these operators to lighten or darken an image,
1013    /// to increase or decrease contrast in an image, or to produce the "negative"
1014    /// of an image.
1015    pub fn evaluate_image(&mut self, op: MagickEvaluateOperator, val: f64) -> Result<()> {
1016        self.result_from_boolean(unsafe { bindings::MagickEvaluateImage(self.wand, op, val) })
1017    }
1018
1019    /// Surround the image with a border of the color defined
1020    /// by the `pixel_wand`.
1021    pub fn border_image(
1022        &self,
1023        pixel_wand: &PixelWand,
1024        width: usize,
1025        height: usize,
1026        compose: CompositeOperator,
1027    ) -> Result<()> {
1028        self.result_from_boolean(unsafe {
1029            bindings::MagickBorderImage(self.wand, pixel_wand.as_ptr(), width, height, compose)
1030        })
1031    }
1032
1033    /// Simulate an image shadow
1034    pub fn shadow_image(&self, alpha: f64, sigma: f64, x: isize, y: isize) -> Result<()> {
1035        self.result_from_boolean(unsafe {
1036            bindings::MagickShadowImage(self.wand, alpha, sigma, x, y)
1037        })
1038    }
1039
1040    /// Accepts pixel data and stores it in the image at the location you specify.
1041    /// See <https://imagemagick.org/api/magick-image.php#MagickImportImagePixels> for more information.
1042    pub fn import_image_pixels(
1043        &mut self,
1044        x: isize,
1045        y: isize,
1046        columns: usize,
1047        rows: usize,
1048        pixels: &[u8],
1049        map: &str,
1050    ) -> Result<()> {
1051        let pixel_map = CString::new(map).map_err(|_| "map string contains null byte")?;
1052        self.result_from_boolean(unsafe {
1053            bindings::MagickImportImagePixels(
1054                self.wand,
1055                x,
1056                y,
1057                columns,
1058                rows,
1059                pixel_map.as_ptr(),
1060                bindings::StorageType::CharPixel,
1061                pixels.as_ptr() as *const libc::c_void,
1062            )
1063        })
1064    }
1065
1066    pub fn import_image_pixels_double(
1067        &mut self,
1068        x: isize,
1069        y: isize,
1070        columns: usize,
1071        rows: usize,
1072        pixels: &[f64],
1073        map: &str,
1074    ) -> Result<()> {
1075        let pixel_map = CString::new(map).expect("map string contains null byte");
1076        Self::result_from_boolean_with_error_message( unsafe {
1077            bindings::MagickImportImagePixels(
1078                self.wand,
1079                x,
1080                y,
1081                columns,
1082                rows,
1083                pixel_map.as_ptr(),
1084                bindings::StorageType::DoublePixel,
1085                pixels.as_ptr() as *const c_void,
1086            )
1087        }, "unable to import pixels")
1088    }
1089
1090    /// Set the wand iterator to the first image.
1091    /// See <https://imagemagick.org/api/magick-wand.php#MagickSetFirstIterator> for more information.
1092    pub fn set_first_iterator(&self) {
1093        unsafe {
1094            bindings::MagickSetFirstIterator(self.wand);
1095        }
1096    }
1097
1098    /// Set the next image in the wand as the current image.
1099    /// See <https://imagemagick.org/api/magick-image.php#MagickNextImage> for more information.
1100    pub fn next_image(&self) -> bool {
1101        let res = unsafe { bindings::MagickNextImage(self.wand) };
1102        res == MagickTrue
1103    }
1104
1105    /// Automatically performs threshold method to reduce grayscale data
1106    /// down to a binary black & white image. Included algorithms are
1107    /// Kapur, Otsu, and Triangle methods.
1108    /// See <https://imagemagick.org/api/magick-image.php#MagickAutoThresholdImage> for more information.
1109    pub fn auto_threshold(&self, method: AutoThresholdMethod) -> Result<()> {
1110        self.result_from_boolean(unsafe { bindings::MagickAutoThresholdImage(self.wand, method) })
1111    }
1112
1113    /// Set the image colorspace, transforming (unlike `set_image_colorspace`) image data in
1114    /// the process.
1115    pub fn transform_image_colorspace(&self, colorspace: ColorspaceType) -> Result<()> {
1116        self.result_from_boolean(unsafe {
1117            bindings::MagickTransformImageColorspace(self.wand, colorspace)
1118        })
1119    }
1120
1121    /// Reduce the number of colors in the image.
1122    pub fn quantize_image(
1123        &self,
1124        number_of_colors: usize,
1125        colorspace: ColorspaceType,
1126        tree_depth: usize,
1127        dither_method: DitherMethod,
1128        measure_error: bool,
1129    ) -> Result<()> {
1130        self.result_from_boolean(unsafe {
1131            bindings::MagickQuantizeImage(
1132                self.wand,
1133                number_of_colors,
1134                colorspace,
1135                tree_depth,
1136                dither_method,
1137                measure_error.into(),
1138            )
1139        })
1140    }
1141
1142    /// Reduce the number of colors in the images.
1143    pub fn quantize_images(
1144        &self,
1145        number_of_colors: usize,
1146        colorspace: ColorspaceType,
1147        tree_depth: usize,
1148        dither_method: DitherMethod,
1149        measure_error: bool,
1150    ) -> Result<()> {
1151        self.result_from_boolean(unsafe {
1152            bindings::MagickQuantizeImages(
1153                self.wand,
1154                number_of_colors,
1155                colorspace,
1156                tree_depth,
1157                dither_method,
1158                measure_error.into(),
1159            )
1160        })
1161    }
1162
1163    /// Applies an arithmetic, relational, or logical expression to an image. Use these operators
1164    /// to lighten or darken an image, to increase or decrease contrast in an image, or to produce
1165    /// the "negative" of an image.
1166    ///
1167    /// * `function`: the image function.
1168    /// * `args`: the function arguments.
1169    ///
1170    /// # Example
1171    ///
1172    /// This example show how you can apply smoothstep function (a polynomial `-2x^3 + 3x^2`) to
1173    /// every image pixel.
1174    ///
1175    /// ```
1176    /// use magick_rust::{MagickWand, PixelWand, MagickFunction};
1177    ///
1178    /// fn main() -> Result<(), magick_rust::MagickError> {
1179    ///     let mut wand1 = MagickWand::new();
1180    ///     wand1.new_image(4, 4, &PixelWand::new())?; // Replace with `read_image` to open your image file
1181    ///
1182    ///     // Apply smoothstep polynomial
1183    ///     wand1.function_image(MagickFunction::Polynomial, &[-2.0, 3.0, 0.0, 0.0])?;
1184    ///
1185    ///     Ok(())
1186    /// }
1187    /// ```
1188    pub fn function_image(&self, function: MagickFunction, args: &[f64]) -> Result<()> {
1189        let num_of_args: size_t = args.len();
1190        self.result_from_boolean(unsafe {
1191            bindings::MagickFunctionImage(self.wand, function, num_of_args, args.as_ptr())
1192        })
1193    }
1194
1195    /// Returns an image where each pixel is the sum of the pixels in the image sequence after
1196    /// applying its corresponding terms (coefficient and degree pairs).
1197    ///
1198    /// * `terms`: the list of polynomial coefficients and degree pairs and a constant.
1199    pub fn polynomial_image(&self, terms: &[f64]) -> Result<()> {
1200        if terms.len() & 1 != 1 {
1201            return Err(MagickError("no constant coefficient given".to_string()));
1202        }
1203
1204        let num_of_terms: size_t = terms.len() >> 1;
1205
1206        self.result_from_boolean(unsafe {
1207            bindings::MagickPolynomialImage(self.wand, num_of_terms, terms.as_ptr())
1208        })
1209    }
1210
1211    /// Applies a custom convolution kernel to the image.
1212    ///
1213    /// * `kernel_info`: An array of doubles representing the convolution kernel.
1214    pub fn convolve_image(&self, kernel_info: &KernelInfo) -> Result<()> {
1215        self.result_from_boolean(unsafe {
1216            bindings::MagickConvolveImage(self.wand, kernel_info.get_ptr())
1217        })
1218    }
1219
1220    /// Applies a user supplied kernel to the image according to the given morphology method.
1221    ///
1222    /// * `morphology_method`: the morphology method to be applied.
1223    /// * `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.
1224    /// * `kernel_info`: An array of doubles representing the morphology kernel.
1225    pub fn morphology_image(
1226        &self,
1227        morphology_method: MorphologyMethod,
1228        iterations: isize,
1229        kernel_info: &KernelInfo,
1230    ) -> Result<()> {
1231        self.result_from_boolean(unsafe {
1232            bindings::MagickMorphologyImage(
1233                self.wand,
1234                morphology_method,
1235                iterations,
1236                kernel_info.get_ptr(),
1237            )
1238        })
1239    }
1240
1241    /// Apply color transformation to an image. The method permits saturation changes, hue rotation,
1242    /// luminance to alpha, and various other effects. Although variable-sized transformation
1243    /// matrices can be used, typically one uses a 5x5 matrix for an RGBA image and a 6x6 for CMYKA
1244    /// (or RGBA with offsets). The matrix is similar to those used by Adobe Flash except offsets
1245    /// are in column 6 rather than 5 (in support of CMYKA images) and offsets are normalized
1246    /// (divide Flash offset by 255).
1247    ///
1248    /// * `color_matrix`: the color matrix.
1249    pub fn color_matrix_image(&self, color_matrix: &KernelInfo) -> Result<()> {
1250        self.result_from_boolean(unsafe {
1251            bindings::MagickColorMatrixImage(self.wand, color_matrix.get_ptr())
1252        })
1253    }
1254
1255    /// Applies a channel expression to the specified image. The expression
1256    /// consists of one or more channels, either mnemonic or numeric (e.g. red, 1), separated by
1257    /// actions as follows:
1258    ///
1259    /// <=> exchange two channels (e.g. red<=>blue) => transfer a channel to another (e.g.
1260    /// red=>green) , separate channel operations (e.g. red, green) | read channels from next input
1261    /// image (e.g. red | green) ; write channels to next output image (e.g. red; green; blue) A
1262    /// channel without a operation symbol implies extract. For example, to create 3 grayscale
1263    /// images from the red, green, and blue channels of an image, use:
1264    ///
1265    /// * `expression`: the expression.
1266    pub fn channel_fx_image(&self, expression: &str) -> Result<MagickWand> {
1267        let c_expression =
1268            CString::new(expression).map_err(|_| "artifact string contains null byte")?;
1269
1270        let wand_ptr = unsafe { bindings::MagickChannelFxImage(self.wand, c_expression.as_ptr()) };
1271        self.result_from_ptr(wand_ptr, MagickWand::from_ptr)
1272    }
1273
1274    /// Combines one or more images into a single image. The grayscale value of the pixels of each
1275    /// image in the sequence is assigned in order to the specified channels of the combined image.
1276    /// The typical ordering would be image 1 => Red, 2 => Green, 3 => Blue, etc.
1277    ///
1278    /// * `colorspace`: the colorspace.
1279    pub fn combine_images(&self, colorspace: ColorspaceType) -> Result<MagickWand> {
1280        let wand_ptr = unsafe { bindings::MagickCombineImages(self.wand, colorspace) };
1281        self.result_from_ptr(wand_ptr, MagickWand::from_ptr)
1282    }
1283
1284    /// Returns the current image from the magick wand.
1285    pub fn get_image(&self) -> Result<Image<'_>> {
1286        self.result_from_ptr(
1287            unsafe { bindings::GetImageFromMagickWand(self.wand) },
1288            Image::new,
1289        )
1290    }
1291
1292    /// Enhances contrast of an image by stretching the range of intensity values.
1293    pub fn contrast_stretch_image(&self, black_point: f64, white_point: f64) -> Result<()> {
1294        self.result_from_boolean(unsafe {
1295            bindings::MagickContrastStretchImage(self.wand, black_point, white_point)
1296        })
1297    }
1298
1299    mutations!(
1300        /// Sets the image to the specified alpha level.
1301        MagickSetImageAlpha => set_image_alpha(alpha: f64)
1302
1303        /// Control the brightness, saturation, and hue of an image
1304        MagickModulateImage => modulate_image(brightness: f64, saturation: f64, hue: f64)
1305
1306        /// Control the brightness and contrast
1307        MagickBrightnessContrastImage => brightness_contrast_image(brightness: f64, contrast: f64)
1308
1309        /// Set the image alpha channel mode.
1310        MagickSetImageAlphaChannel => set_image_alpha_channel(alpha_channel: AlphaChannelOption)
1311
1312        /// Discard all but one of any pixel color.
1313        MagickUniqueImageColors => unique_image_colors()
1314
1315        /// Applies k-means color reduction to the image.
1316        MagickKmeansImage => kmeans(number_colors: usize, max_iterations: usize, tolerance: f64)
1317
1318        /// Extracts the 'mean' from the image and adjust the image to try make set its gamma appropriately.
1319        MagickAutoGammaImage => auto_gamma()
1320
1321        /// Adjusts the levels of a particular image channel by scaling the minimum and maximum values to the full quantum range.
1322        MagickAutoLevelImage => auto_level()
1323    );
1324
1325    get!(get_image_colors, MagickGetImageColors, usize);
1326
1327    string_set_get!(
1328        get_filename,                    set_filename,                    MagickGetFilename,                 MagickSetFilename
1329        get_font,                        set_font,                        MagickGetFont,                     MagickSetFont
1330        get_format,                      set_format,                      MagickGetFormat,                   MagickSetFormat
1331        get_image_filename,              set_image_filename,              MagickGetImageFilename,            MagickSetImageFilename
1332        get_image_format,                set_image_format,                MagickGetImageFormat,              MagickSetImageFormat
1333    );
1334
1335    set_get!(
1336        get_colorspace,                  set_colorspace,                  MagickGetColorspace,               MagickSetColorspace,              ColorspaceType
1337        get_image_compose,               set_image_compose,               MagickGetImageCompose,             MagickSetImageCompose,            CompositeOperator
1338        get_compression,                 set_compression,                 MagickGetCompression,              MagickSetCompression,             CompressionType
1339        get_compression_quality,         set_compression_quality,         MagickGetCompressionQuality,       MagickSetCompressionQuality,      usize
1340        get_gravity,                     set_gravity,                     MagickGetGravity,                  MagickSetGravity,                 GravityType
1341        get_image_colorspace,            set_image_colorspace,            MagickGetImageColorspace,          MagickSetImageColorspace,         ColorspaceType
1342        get_image_compression,           set_image_compression,           MagickGetImageCompression,         MagickSetImageCompression,        CompressionType
1343        get_image_compression_quality,   set_image_compression_quality,   MagickGetImageCompressionQuality,  MagickSetImageCompressionQuality, usize
1344        get_image_delay,                 set_image_delay,                 MagickGetImageDelay,               MagickSetImageDelay,              usize
1345        get_image_depth,                 set_image_depth,                 MagickGetImageDepth,               MagickSetImageDepth,              usize
1346        get_image_dispose,               set_image_dispose,               MagickGetImageDispose,             MagickSetImageDispose,            DisposeType
1347        get_image_endian,                set_image_endian,                MagickGetImageEndian,              MagickSetImageEndian,             EndianType
1348        get_image_fuzz,                  set_image_fuzz,                  MagickGetImageFuzz,                MagickSetImageFuzz,               f64
1349        get_image_gamma,                 set_image_gamma,                 MagickGetImageGamma,               MagickSetImageGamma,              f64
1350        get_image_gravity,               set_image_gravity,               MagickGetImageGravity,             MagickSetImageGravity,            GravityType
1351        get_image_interlace_scheme,      set_image_interlace_scheme,      MagickGetImageInterlaceScheme,     MagickSetImageInterlaceScheme,    InterlaceType
1352        get_image_interpolate_method,    set_image_interpolate_method,    MagickGetImageInterpolateMethod,   MagickSetImageInterpolateMethod,  PixelInterpolateMethod
1353        get_image_iterations,            set_image_iterations,            MagickGetImageIterations,          MagickSetImageIterations,         usize
1354        get_image_orientation,           set_image_orientation,           MagickGetImageOrientation,         MagickSetImageOrientation,        OrientationType
1355        get_image_rendering_intent,      set_image_rendering_intent,      MagickGetImageRenderingIntent,     MagickSetImageRenderingIntent,    RenderingIntent
1356        get_image_scene,                 set_image_scene,                 MagickGetImageScene,               MagickSetImageScene,              usize
1357        get_image_type,                  set_image_type,                  MagickGetImageType,                MagickSetImageType,               ImageType
1358        get_image_units,                 set_image_units,                 MagickGetImageUnits,               MagickSetImageUnits,              ResolutionType
1359        get_interlace_scheme,            set_interlace_scheme,            MagickGetInterlaceScheme,          MagickSetInterlaceScheme,         InterlaceType
1360        get_interpolate_method,          set_interpolate_method,          MagickGetInterpolateMethod,        MagickSetInterpolateMethod,       PixelInterpolateMethod
1361        get_iterator_index,              set_iterator_index,              MagickGetIteratorIndex,            MagickSetIteratorIndex,           isize
1362        get_orientation,                 set_orientation,                 MagickGetOrientation,              MagickSetOrientation,             OrientationType
1363        get_pointsize,                   set_pointsize,                   MagickGetPointsize,                MagickSetPointsize,               f64
1364        get_type,                        set_type,                        MagickGetType,                     MagickSetType,                    ImageType
1365    );
1366
1367    fn result_from_boolean(&self, no_error: MagickBooleanType) -> Result<()> {
1368        if no_error == MagickTrue {
1369            Ok(())
1370        } else {
1371            Err(MagickError(self.get_exception()?.0))
1372        }
1373    }
1374
1375    fn result_from_boolean_with_error_message(
1376        no_error: MagickBooleanType,
1377        message: impl Into<String>,
1378    ) -> Result<()> {
1379        if no_error == MagickTrue {
1380            Ok(())
1381        } else {
1382            Err(MagickError(message.into()))
1383        }
1384    }
1385
1386    fn result_from_ptr<P, T>(&self, ptr: *mut P, new: impl FnOnce(*mut P) -> T) -> Result<T> {
1387        if ptr.is_null() {
1388            Err(MagickError(self.get_exception()?.0))
1389        } else {
1390            Ok(new(ptr))
1391        }
1392    }
1393
1394    fn result_from_ptr_with_error_message<P, T>(
1395        ptr: *mut P,
1396        new: impl FnOnce(*mut P) -> T,
1397        message: impl Into<String>,
1398    ) -> Result<T> {
1399        if ptr.is_null() {
1400            Err(MagickError(message.into()))
1401        } else {
1402            Ok(new(ptr))
1403        }
1404    }
1405
1406    fn c_char_to_string_vec(c_values: *mut *mut c_char, num_of_artifacts: usize) -> Vec<String> {
1407        let mut values: Vec<String> = Vec::with_capacity(num_of_artifacts);
1408        for i in 0..num_of_artifacts {
1409            // convert (and copy) the C string to a Rust string
1410            let cstr = unsafe { CStr::from_ptr(*c_values.add(i)) };
1411            values.push(cstr.to_string_lossy().into_owned());
1412        }
1413
1414        unsafe {
1415            bindings::MagickRelinquishMemory(c_values as *mut c_void);
1416        }
1417
1418        values
1419    }
1420
1421    fn c_char_into_string(c_value: *mut c_char) -> String {
1422        let value = unsafe { CStr::from_ptr(c_value) }
1423            .to_string_lossy()
1424            .into_owned();
1425
1426        unsafe {
1427            bindings::MagickRelinquishMemory(c_value as *mut c_void);
1428        }
1429
1430        value
1431    }
1432
1433    fn c_array_into_vec(blob: *mut c_uchar, length: usize) -> Vec<u8> {
1434        let mut bytes = vec![0; length];
1435
1436        unsafe {
1437            let ptr = bytes.as_mut_ptr();
1438            ptr::copy_nonoverlapping(blob, ptr, length);
1439            bindings::MagickRelinquishMemory(blob as *mut c_void);
1440        }
1441
1442        bytes
1443    }
1444}
1445
1446impl fmt::Debug for MagickWand {
1447    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1448        writeln!(f, "MagickWand {{")?;
1449        writeln!(f, "    Exception: {:?}", self.get_exception())?;
1450        writeln!(f, "    IsWand: {:?}", self.is_wand())?;
1451        self.fmt_string_settings(f, "    ")?;
1452        self.fmt_checked_settings(f, "    ")?;
1453        writeln!(f, "}}")
1454    }
1455}