From 8c154511b1cdc0d61514d360e5aac8d4aa48aa76 Mon Sep 17 00:00:00 2001 From: 5ohue <86558263+5ohue@users.noreply.github.com> Date: Tue, 14 May 2024 12:18:24 +0300 Subject: [PATCH] Add a few more functions and typed Adds `Image` type and a few image related functions. --- src/types/image.rs | 21 ++++++++ src/types/layer_method.rs | 35 +++++++++++++ src/types/mod.rs | 4 ++ src/wand/magick.rs | 106 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 src/types/image.rs create mode 100644 src/types/layer_method.rs diff --git a/src/types/image.rs b/src/types/image.rs new file mode 100644 index 0000000..17ae07a --- /dev/null +++ b/src/types/image.rs @@ -0,0 +1,21 @@ +use std::marker::PhantomData; + +use crate::bindings; + +pub struct Image<'a> { + image: *mut bindings::Image, + phantom_data: PhantomData<&'a bindings::Image>, +} + +impl Image<'_> { + pub unsafe fn new(img: *mut bindings::Image) -> Self { + Image { + image: img, + phantom_data: PhantomData + } + } + + pub unsafe fn get_ptr(&self) -> *mut bindings::Image { + self.image + } +} diff --git a/src/types/layer_method.rs b/src/types/layer_method.rs new file mode 100644 index 0000000..e502af9 --- /dev/null +++ b/src/types/layer_method.rs @@ -0,0 +1,35 @@ +use crate::bindings; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum LayerMethod { + Undefined = bindings::LayerMethod_UndefinedLayer, + Coalesce = bindings::LayerMethod_CoalesceLayer, + CompareAny = bindings::LayerMethod_CompareAnyLayer, + CompareClear = bindings::LayerMethod_CompareClearLayer, + CompareOverlay = bindings::LayerMethod_CompareOverlayLayer, + Dispose = bindings::LayerMethod_DisposeLayer, + Optimize = bindings::LayerMethod_OptimizeLayer, + OptimizeImage = bindings::LayerMethod_OptimizeImageLayer, + OptimizePlus = bindings::LayerMethod_OptimizePlusLayer, + OptimizeTrans = bindings::LayerMethod_OptimizeTransLayer, + RemoveDups = bindings::LayerMethod_RemoveDupsLayer, + RemoveZero = bindings::LayerMethod_RemoveZeroLayer, + Composite = bindings::LayerMethod_CompositeLayer, + Merge = bindings::LayerMethod_MergeLayer, + Flatten = bindings::LayerMethod_FlattenLayer, + Mosaic = bindings::LayerMethod_MosaicLayer, + TrimBounds = bindings::LayerMethod_TrimBoundsLayer, +} + +impl Default for LayerMethod { + fn default() -> Self { + return LayerMethod::Undefined; + } +} + +impl From for bindings::LayerMethod { + fn from(value: LayerMethod) -> Self { + return value as bindings::LayerMethod; + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 047d9b4..8edb988 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -15,9 +15,11 @@ mod fill_rule; mod filter_type; mod geometry_info; mod gravity_type; +mod image; mod image_type; mod interlace_type; mod kernel; +mod layer_method; mod line_cap; mod line_join; mod magick_evaluate_operator; @@ -50,9 +52,11 @@ pub use self::fill_rule::FillRule; pub use self::filter_type::FilterType; pub use self::geometry_info::GeometryInfo; pub use self::gravity_type::GravityType; +pub use self::image::Image; pub use self::image_type::ImageType; pub use self::interlace_type::InterlaceType; pub use self::kernel::*; +pub use self::layer_method::LayerMethod; pub use self::line_cap::LineCap; pub use self::line_join::LineJoin; pub use self::magick_evaluate_operator::MagickEvaluateOperator; diff --git a/src/wand/magick.rs b/src/wand/magick.rs index 2f8c47e..3335101 100644 --- a/src/wand/magick.rs +++ b/src/wand/magick.rs @@ -42,9 +42,11 @@ use crate::{ EndianType, FilterType, GravityType, + Image, ImageType, InterlaceType, KernelInfo, + LayerMethod, MagickEvaluateOperator, MagickFunction, MetricType, @@ -77,6 +79,21 @@ wand_common!( /// When the `MagickWand` is dropped, the ImageMagick wand will be /// destroyed as well. impl MagickWand { + /// Creates new wand by cloning the image. + /// + /// * `img`: the image. + pub fn new_from_image(img: &Image<'_>) -> Result { + let result = unsafe { + bindings::NewMagickWandFromImage(img.get_ptr()) + }; + + return if result.is_null() { + Err(MagickError("failed to create wand from image")) + } else { + Ok(MagickWand { wand: result }) + } + } + pub fn new_image(&self, columns: usize, rows: usize, background: &PixelWand) -> Result<()> { match unsafe { bindings::MagickNewImage(self.wand, columns.into(), rows.into(), background.wand) } { MagickTrue => Ok(()), @@ -224,6 +241,37 @@ impl MagickWand { } } + /// Composes all the image layers from the current given image onward to produce a single image + /// of the merged layers. + /// + /// The inital canvas's size depends on the given LayerMethod, and is initialized using the + /// first images background color. The images are then composited onto that image in sequence + /// using the given composition that has been assigned to each individual image. + /// + /// * `method`: the method of selecting the size of the initial canvas. + /// MergeLayer: Merge all layers onto a canvas just large enough to hold all the actual + /// images. The virtual canvas of the first image is preserved but otherwise ignored. + /// + /// FlattenLayer: Use the virtual canvas size of first image. Images which fall outside + /// this canvas is clipped. This can be used to 'fill out' a given virtual canvas. + /// + /// MosaicLayer: Start with the virtual canvas of the first image, enlarging left and right + /// edges to contain all images. Images with negative offsets will be clipped. + pub fn merge_image_layers(&self, method: LayerMethod) -> Result { + let result = unsafe { + bindings::MagickMergeImageLayers(self.wand, method.into()) + }; + if result.is_null() { + return Err(MagickError("failed to merge image layres")); + } + return Ok(MagickWand { wand: result }); + } + + /// Returns the number of images associated with a magick wand. + pub fn get_number_images(&self) -> usize { + return unsafe { bindings::MagickGetNumberImages(self.wand).into() }; + } + /// Compare two images and return tuple `(distortion, diffImage)` /// `diffImage` is `None` if `distortion == 0` pub fn compare_images( @@ -1540,6 +1588,64 @@ impl MagickWand { } } + /// Applies a channel expression to the specified image. The expression + /// consists of one or more channels, either mnemonic or numeric (e.g. red, 1), separated by + /// actions as follows: + /// + /// <=> exchange two channels (e.g. red<=>blue) => transfer a channel to another (e.g. + /// red=>green) , separate channel operations (e.g. red, green) | read channels from next input + /// image (e.g. red | green) ; write channels to next output image (e.g. red; green; blue) A + /// channel without a operation symbol implies extract. For example, to create 3 grayscale + /// images from the red, green, and blue channels of an image, use: + /// + /// * `expression`: the expression. + pub fn channel_fx_image(&self, expression: &str) -> Result { + let c_expression = CString::new(expression).map_err(|_| MagickError("artifact string contains null byte"))?; + + let result = unsafe { + bindings::MagickChannelFxImage( + self.wand, + c_expression.as_ptr() + ) + }; + + return if result.is_null() { + Err(MagickError("failed to apply expression to image")) + } else { + Ok(MagickWand{ wand: result }) + }; + } + + /// Combines one or more images into a single image. The grayscale value of the pixels of each + /// image in the sequence is assigned in order to the specified channels of the combined image. + /// The typical ordering would be image 1 => Red, 2 => Green, 3 => Blue, etc. + /// + /// * `colorspace`: the colorspace. + pub fn combine_images(&self, colorspace: ColorspaceType) -> Result { + let result = unsafe { + bindings::MagickCombineImages(self.wand, colorspace.into()) + }; + + return if result.is_null() { + Err(MagickError("failed to combine images")) + } else { + Ok(MagickWand{ wand: result }) + } + } + + /// Returns the current image from the magick wand. + pub fn get_image<'wand>(&'wand self) -> Result> { + let result = unsafe { + bindings::GetImageFromMagickWand(self.wand) + }; + + return if result.is_null() { + Err(MagickError("no image in wand")) + } else { + unsafe { Ok(Image::new(result)) } + } + } + mutations!( /// Sets the image to the specified alpha level. MagickSetImageAlpha => set_image_alpha(alpha: f64)