diff --git a/build.rs b/build.rs index 416601f..9fe805b 100644 --- a/build.rs +++ b/build.rs @@ -28,13 +28,16 @@ fn main() { // If the MagickWand bindings are missing, generate them using // rust-bindgen. // - let bindings_path = Path::new("src/bindings.rs"); - if !bindings_path.exists() { - let bindgen_path = Path::new("rust-bindgen"); + let out_dir = ::std::env::var("OUT_DIR").unwrap(); + let bindings_path_str = out_dir.clone() + "/bindings.rs"; + if !Path::new(&bindings_path_str).exists() { + let bindgen_path_str = out_dir.clone() + "/rust-bindgen"; + let bindgen_path = Path::new(&bindgen_path_str); if !bindgen_path.exists() { Command::new("git") .arg("clone") .arg("https://github.com/crabtw/rust-bindgen.git") + .arg(bindgen_path) .status().unwrap(); // Checkout a version of rust-bindgen that is known to work; // more recent versions produce code that does not compile (the @@ -42,39 +45,47 @@ fn main() { Command::new("git") .arg("checkout") .arg("8a51860") - .current_dir("rust-bindgen") - .status().unwrap(); - Command::new("cargo") - .arg("build") - .current_dir("rust-bindgen") + .current_dir(bindgen_path) .status().unwrap(); + } - // Ensure MagickWand-config is in the PATH and report clearly if not. - if !Command::new("which").arg("MagickWand-config").status().unwrap().success() { - panic!("MagickWand-config not in the PATH, please install ImageMagick"); + let mut bindgen_bin = bindgen_path.to_path_buf(); + bindgen_bin.push("target/debug/bindgen"); + if !bindgen_bin.exists() { + let mut cmd = Command::new("cargo"); + cmd.arg("build").current_dir(bindgen_path); + println!("BINDGEN_BUILD={:?}", cmd); + cmd.status().unwrap(); } - // Create the header file that rust-bindgen needs as input. - let mut gen_h = match File::create("gen.h") { - Err(why) => panic!("could not create gen.h file: {}", Error::description(&why)), - Ok(file) => file - }; - match gen_h.write_all(HEADER.as_bytes()) { - Err(why) => panic!("could not write to gen.h: {}", Error::description(&why)), - Ok(_) => () - }; // Get the compiler and linker flags for the MagickWand library. - let mw_cflags_output = Command::new("MagickWand-config") + let mw_cflags_output = Command::new("pkg-config") .arg("--cflags") + .arg("MagickWand") .output().unwrap(); let mw_cflags = std::str::from_utf8(&mw_cflags_output.stdout).unwrap().trim(); let mw_cflags_arr: Vec<&str> = mw_cflags.split_whitespace().collect(); - let mw_ldflags_output = Command::new("MagickWand-config") - .arg("--ldflags") + println!("CFLAGS={:?}", mw_cflags_arr); + let mw_ldflags_output = Command::new("pkg-config") + .arg("--libs") + .arg("MagickWand") .output().unwrap(); let mw_ldflags = std::str::from_utf8(&mw_ldflags_output.stdout).unwrap().trim(); let mw_ldflags_arr: Vec<&str> = mw_ldflags.split_whitespace().collect(); + println!("LDFLAGS={:?}", mw_ldflags_arr); + + let gen_h_path = out_dir.clone() + "/gen.h"; + // Create the header file that rust-bindgen needs as input. + let mut gen_h = match File::create(&gen_h_path) { + Err(why) => panic!("could not create {} file: {}", gen_h_path, Error::description(&why)), + Ok(file) => file + }; + match gen_h.write_all(HEADER.as_bytes()) { + Err(why) => panic!("could not write to {}: {}", gen_h_path, Error::description(&why)), + Ok(_) => () + }; + // Combine all of that in the invocation of rust-bindgen. - let mut cmd = &mut Command::new("./rust-bindgen/target/debug/bindgen"); + let mut cmd = &mut Command::new(bindgen_bin); if cfg!(target_os = "macos") { // Mac requires that the xcode tools are installed so that // rustc can find the clang.dylib file. See also issue @@ -84,27 +95,29 @@ fn main() { panic!("missing {}, run xcode-select --install", LIBPATH); } cmd.env("DYLD_LIBRARY_PATH", LIBPATH); + + // For the sake of easily building and testing on Mac, include the path + // to MagickWand. Chances are MagickWand is in /usr/local/lib, or + // somewhere else that rustc can find it. + println!("cargo:rustc-link-search=native=/usr/local/lib"); } cmd.args(&mw_cflags_arr[..]) .arg("-builtins") .arg("-o") - .arg("src/bindings.rs") + .arg(bindings_path_str) .args(&mw_ldflags_arr[..]) - .arg("gen.h") - .status().unwrap(); + .arg(&gen_h_path); + println!("BINDING_GENERATION={:?}", cmd); + cmd.status().unwrap(); // how to get the output of the command... // let output = Commad::new(...).output().unwrap(); // let out = std::str::from_utf8(&output.stdout).unwrap(); // println!("cargo:output={}", out); // let err = std::str::from_utf8(&output.stderr).unwrap(); // println!("cargo:error={}", err); - match std::fs::remove_file("gen.h") { - Err(why) => panic!("could not remove gen.h: {}", Error::description(&why)), + match std::fs::remove_file(&gen_h_path) { + Err(why) => panic!("could not remove {}: {}", gen_h_path, Error::description(&why)), Ok(_) => () } } - // For the sake of easily building and testing on Mac, include the path - // to MagickWand. Chances are MagickWand is in /usr/local/lib, or - // somewhere else that rustc can find it. - println!("cargo:rustc-link-search=native=/usr/local/lib"); } diff --git a/src/conversions.rs b/src/conversions.rs new file mode 100644 index 0000000..b19e9ee --- /dev/null +++ b/src/conversions.rs @@ -0,0 +1,27 @@ +use super::bindings; + +pub trait FromRust { + fn from_rust(t: T) -> Self; +} + +impl FromRust for bindings::MagickBooleanType { + fn from_rust(b: bool) -> Self { + if b { + bindings::MagickTrue + } else { + bindings::MagickFalse + } + } +} + +pub trait ToMagick { + fn to_magick(self) -> T; +} + +impl ToMagick for E + where T: FromRust +{ + fn to_magick(self) -> T { + >::from_rust(self) + } +} diff --git a/src/filters.rs b/src/filters.rs new file mode 100644 index 0000000..73a8e6e --- /dev/null +++ b/src/filters.rs @@ -0,0 +1,36 @@ +use ::bindings; + +pub enum FilterType { + UndefinedFilter = bindings::UndefinedFilter as isize, + PointFilter = bindings::PointFilter as isize, + BoxFilter = bindings::BoxFilter as isize, + TriangleFilter = bindings::TriangleFilter as isize, + HermiteFilter = bindings::HermiteFilter as isize, + HanningFilter = bindings::HanningFilter as isize, + HammingFilter = bindings::HammingFilter as isize, + BlackmanFilter = bindings::BlackmanFilter as isize, + GaussianFilter = bindings::GaussianFilter as isize, + QuadraticFilter = bindings::QuadraticFilter as isize, + CubicFilter = bindings::CubicFilter as isize, + CatromFilter = bindings::CatromFilter as isize, + MitchellFilter = bindings::MitchellFilter as isize, + JincFilter = bindings::JincFilter as isize, + SincFilter = bindings::SincFilter as isize, + SincFastFilter = bindings::SincFastFilter as isize, + KaiserFilter = bindings::KaiserFilter as isize, + WelshFilter = bindings::WelshFilter as isize, + ParzenFilter = bindings::ParzenFilter as isize, + BohmanFilter = bindings::BohmanFilter as isize, + BartlettFilter = bindings::BartlettFilter as isize, + LagrangeFilter = bindings::LagrangeFilter as isize, + LanczosFilter = bindings::LanczosFilter as isize, + LanczosSharpFilter = bindings::LanczosSharpFilter as isize, + Lanczos2Filter = bindings::Lanczos2Filter as isize, + Lanczos2SharpFilter = bindings::Lanczos2SharpFilter as isize, + RobidouxFilter = bindings::RobidouxFilter as isize, + RobidouxSharpFilter = bindings::RobidouxSharpFilter as isize, + CosineFilter = bindings::CosineFilter as isize, + SplineFilter = bindings::SplineFilter as isize, + LanczosRadiusFilter = bindings::LanczosRadiusFilter as isize, + SentinelFilter = bindings::SentinelFilter as isize +} diff --git a/src/lib.rs b/src/lib.rs index b6fc056..a37c7e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,195 +31,16 @@ extern crate libc; -use std::ffi::{CStr, CString}; -use std::ptr; -use libc::{c_uint, c_double, c_void}; -use filters::FilterType; +mod wand; +mod conversions; +pub mod filters; +mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } -mod bindings; +pub use wand::*; -/// MagickWand is a Rustic wrapper to the Rust bindings to ImageMagick. -/// -/// Instantiating a `MagickWand` will construct an ImageMagick "wand" -/// on which operations can be performed via the `MagickWand` functions. -/// When the `MagickWand` is dropped, the ImageMagick wand will be -/// destroyed as well. -pub struct MagickWand { - wand: *mut bindings::MagickWand -} +pub type size_t = ::bindings::size_t; +pub type ssize_t = ::bindings::ssize_t; -impl MagickWand { - - /// Create a new MagickWand instance. This instance will be properly - /// cleaned up once it falls out of scope. - pub fn new() -> MagickWand { - MagickWand { - wand: unsafe { bindings::NewMagickWand() } - } - } - - /// Read the image data from the named file. - pub fn read_image(&self, path: &str) -> Result<(), &'static str> { - let c_name = CString::new(path).unwrap(); - let result = unsafe { - bindings::MagickReadImage(self.wand, c_name.as_ptr()) - }; - match result { - bindings::MagickTrue => Ok(()), - _ => Err("failed to read image") - } - } - - /// Read the image data from the vector of bytes. - pub fn read_image_blob(&self, data: Vec) -> Result<(), &'static str> { - let int_slice = &data[..]; - let size = data.len(); - let result = unsafe { - bindings::MagickReadImageBlob( - self.wand, int_slice.as_ptr() as *const c_void, size as u64) - }; - match result { - bindings::MagickTrue => Ok(()), - _ => Err("failed to read image") - } - } - - /// Retrieve the width of the image. - pub fn get_image_width(&self) -> usize { - unsafe { - bindings::MagickGetImageWidth(self.wand) as usize - } - } - - /// Retrieve the height of the image. - pub fn get_image_height(&self) -> usize { - unsafe { - bindings::MagickGetImageHeight(self.wand) as usize - } - } - - /// Retrieve the named image property value. - pub fn get_image_property(&self, name: &str) -> Result { - let c_name = CString::new(name).unwrap(); - let result = unsafe { - bindings::MagickGetImageProperty(self.wand, c_name.as_ptr()) - }; - let value = if result.is_null() { - Err("missing property") - } else { - // convert (and copy) the C string to a Rust string - let cstr = unsafe { CStr::from_ptr(result) }; - Ok(cstr.to_string_lossy().into_owned()) - }; - unsafe { - bindings::MagickRelinquishMemory(result as *mut c_void); - } - value - } - - /// Resize the image to the specified width and height, using the - /// specified filter type with the specified blur / sharpness factor. - /// - /// blur_factor values greater than 1 create blurriness, while values - /// less than 1 create sharpness. - pub fn resize_image(&self, width: usize, height: usize, - filter: FilterType, blur_factor: f64) { - unsafe { - bindings::MagickResizeImage( - self.wand, width as u64, height as u64, - filter as c_uint, blur_factor as c_double - ); - } - } - - /// Resize the image to find within the given dimensions, maintaining - /// the current aspect ratio. - pub fn fit(&self, width: usize, height: usize) { - let mut width_ratio = width as f64; - width_ratio /= self.get_image_width() as f64; - let mut height_ratio = height as f64; - height_ratio /= self.get_image_height() as f64; - let new_width: usize; - let new_height: usize; - if width_ratio < height_ratio { - new_width = width; - new_height = (self.get_image_height() as f64 * width_ratio) as usize; - } else { - new_width = (self.get_image_width() as f64 * height_ratio) as usize; - new_height = height; - } - unsafe { - bindings::MagickResetIterator(self.wand); - while bindings::MagickNextImage(self.wand) != bindings::MagickFalse { - bindings::MagickResizeImage(self.wand, new_width as u64, new_height as u64, - FilterType::LanczosFilter as c_uint, 1.0); - } - } - } - - /// Detect if the loaded image is not in top-left orientation, and - /// hence should be "auto" oriented so it is suitable for viewing. - pub fn requires_orientation(&self) -> bool { - unsafe { - bindings::MagickGetImageOrientation(self.wand) != bindings::TopLeftOrientation - } - } - - /// Automatically adjusts the loaded image so that its orientation is - /// suitable for viewing (i.e. top-left orientation). - /// - /// Returns `true` if successful or `false` if an error occurred. - pub fn auto_orient(&self) -> bool { - unsafe { - bindings::MagickAutoOrientImage(self.wand) == bindings::MagickTrue - } - } - - /// Write the current image to the provided path. - pub fn write_image(&self, path: &str) -> Result<(), &'static str> { - let c_name = CString::new(path).unwrap(); - let result = unsafe { - bindings::MagickWriteImage(self.wand, c_name.as_ptr()) - }; - match result { - bindings::MagickTrue => Ok(()), - _ => Err("failed to write image") - } - } - - /// Write the image in the desired format to a new blob. - /// - /// The `format` argument may be any ImageMagick supported image - /// format (e.g. GIF, JPEG, PNG, etc). - pub fn write_image_blob(&self, format: &str) -> Result, &'static str> { - let c_format = CString::new(format).unwrap(); - let mut length: u64 = 0; - let blob = unsafe { - bindings::MagickSetImageFormat(self.wand, c_format.as_ptr()); - bindings::MagickResetIterator(self.wand); - bindings::MagickGetImageBlob(self.wand, &mut length) - }; - let mut bytes = Vec::with_capacity(length as usize); - unsafe { - bytes.set_len(length as usize); - ptr::copy_nonoverlapping(blob, bytes.as_mut_ptr(), length as usize); - bindings::MagickRelinquishMemory(blob as *mut c_void); - }; - Ok(bytes) - } -} - -// Automate safe cleanup for MagickWand instances. -impl Drop for MagickWand { - - /// Clear any exceptions and destroy the magic wand. - fn drop(&mut self) { - unsafe { - bindings::MagickClearException(self.wand); - bindings::DestroyMagickWand(self.wand); - } - } -} /// This function must be called before any other ImageMagick operations /// are attempted. This function is safe to be called repeatedly. @@ -243,42 +64,20 @@ pub fn magick_wand_terminus() { } } -pub mod filters { - - use bindings; - - pub enum FilterType { - UndefinedFilter = bindings::UndefinedFilter as isize, - PointFilter = bindings::PointFilter as isize, - BoxFilter = bindings::BoxFilter as isize, - TriangleFilter = bindings::TriangleFilter as isize, - HermiteFilter = bindings::HermiteFilter as isize, - HanningFilter = bindings::HanningFilter as isize, - HammingFilter = bindings::HammingFilter as isize, - BlackmanFilter = bindings::BlackmanFilter as isize, - GaussianFilter = bindings::GaussianFilter as isize, - QuadraticFilter = bindings::QuadraticFilter as isize, - CubicFilter = bindings::CubicFilter as isize, - CatromFilter = bindings::CatromFilter as isize, - MitchellFilter = bindings::MitchellFilter as isize, - JincFilter = bindings::JincFilter as isize, - SincFilter = bindings::SincFilter as isize, - SincFastFilter = bindings::SincFastFilter as isize, - KaiserFilter = bindings::KaiserFilter as isize, - WelshFilter = bindings::WelshFilter as isize, - ParzenFilter = bindings::ParzenFilter as isize, - BohmanFilter = bindings::BohmanFilter as isize, - BartlettFilter = bindings::BartlettFilter as isize, - LagrangeFilter = bindings::LagrangeFilter as isize, - LanczosFilter = bindings::LanczosFilter as isize, - LanczosSharpFilter = bindings::LanczosSharpFilter as isize, - Lanczos2Filter = bindings::Lanczos2Filter as isize, - Lanczos2SharpFilter = bindings::Lanczos2SharpFilter as isize, - RobidouxFilter = bindings::RobidouxFilter as isize, - RobidouxSharpFilter = bindings::RobidouxSharpFilter as isize, - CosineFilter = bindings::CosineFilter as isize, - SplineFilter = bindings::SplineFilter as isize, - LanczosRadiusFilter = bindings::LanczosRadiusFilter as isize, - SentinelFilter = bindings::SentinelFilter as isize +pub fn magick_query_fonts(pattern: &str) -> Result, &'static str> { + let mut number_fonts: size_t = 0; + let c_string = try!(::std::ffi::CString::new(pattern).map_err(|_| "could not convert to cstring")); + let ptr = unsafe { bindings::MagickQueryFonts(c_string.as_ptr(), &mut number_fonts as *mut size_t) }; + if ptr.is_null() { + Err("null ptr returned by magick_query_fonts") + } else { + let mut v = Vec::new(); + let c_str_ptr_slice = unsafe { ::std::slice::from_raw_parts(ptr, number_fonts as usize) }; + for c_str_ptr in c_str_ptr_slice { + let c_str = unsafe { ::std::ffi::CStr::from_ptr(*c_str_ptr) }; + v.push(c_str.to_string_lossy().into_owned()) + } + Ok(v) } + } diff --git a/src/wand/drawing.rs b/src/wand/drawing.rs new file mode 100644 index 0000000..3c36b01 --- /dev/null +++ b/src/wand/drawing.rs @@ -0,0 +1,79 @@ +use std::fmt; +use std::ffi::{CStr, CString}; +use ::bindings; +use ::size_t; + +wand_common!( + DrawingWand, + NewDrawingWand, ClearDrawingWand, IsDrawingWand, CloneDrawingWand, DestroyDrawingWand, + DrawClearException, DrawGetExceptionType, DrawGetException +); + +impl DrawingWand { + pub fn draw_annotation(&mut self, x: f64, y: f64, text: &str) -> Result<(), &'static str> { + let c_string = try!(CString::new(text).map_err(|_| "could not convert to cstring")); + unsafe { bindings::DrawAnnotation(self.wand, x, y, c_string.as_ptr() as *const _) }; + Ok(()) + } + + string_set_get!( + get_font, set_font, DrawGetFont, DrawSetFont + get_font_family, set_font_family, DrawGetFontFamily, DrawSetFontFamily + get_vector_graphics, set_vector_graphics, DrawGetVectorGraphics, DrawSetVectorGraphics + get_clip_path, set_clip_path, DrawGetClipPath, DrawSetClipPath + ); + + string_set_get_unchecked!( + get_text_encoding, set_text_encoding, DrawGetTextEncoding, DrawSetTextEncoding + ); + + pixel_set_get!( + get_border_color, set_border_color, DrawGetBorderColor, DrawSetBorderColor + get_fill_color, set_fill_color, DrawGetFillColor, DrawSetFillColor + get_stroke_color, set_stroke_color, DrawGetStrokeColor, DrawSetStrokeColor + get_text_under_color, set_text_under_color, DrawGetTextUnderColor, DrawSetTextUnderColor + ); + + set_get_unchecked!( + get_gravity, set_gravity, DrawGetGravity, DrawSetGravity, u32 + get_opacity, set_opacity, DrawGetOpacity, DrawSetOpacity, f64 + get_clip_rule, set_clip_rule, DrawGetClipRule, DrawSetClipRule, u32 + get_clip_units, set_clip_units, DrawGetClipUnits, DrawSetClipUnits, u32 + get_fill_rule, set_fill_rule, DrawGetFillRule, DrawSetFillRule, u32 + get_fill_opacity, set_fill_opacity, DrawGetFillOpacity, DrawSetFillOpacity, f64 + + get_font_size, set_font_size, DrawGetFontSize, DrawSetFontSize, f64 + get_font_style, set_font_style, DrawGetFontStyle, DrawSetFontStyle, u32 + get_font_weight, set_font_weight, DrawGetFontWeight, DrawSetFontWeight, size_t + get_font_stretch, set_font_stretch, DrawGetFontStretch, DrawSetFontStretch, u32 + + get_stroke_dash_offset, set_stroke_dash_offset, DrawGetStrokeDashOffset, DrawSetStrokeDashOffset, f64 + get_stroke_line_cap, set_stroke_line_cap, DrawGetStrokeLineCap, DrawSetStrokeLineCap, u32 + get_stroke_line_join, set_stroke_line_join, DrawGetStrokeLineJoin, DrawSetStrokeLineJoin, u32 + get_stroke_miter_limit, set_stroke_miter_limit, DrawGetStrokeMiterLimit, DrawSetStrokeMiterLimit, size_t + get_stroke_opacity, set_stroke_opacity, DrawGetStrokeOpacity, DrawSetStrokeOpacity, f64 + get_stroke_width, set_stroke_width, DrawGetStrokeWidth, DrawSetStrokeWidth, f64 + get_stroke_antialias, set_stroke_antialias, DrawGetStrokeAntialias, DrawSetStrokeAntialias, u32 + + get_text_alignment, set_text_alignment, DrawGetTextAlignment, DrawSetTextAlignment, u32 + get_text_antialias, set_text_antialias, DrawGetTextAntialias, DrawSetTextAntialias, u32 + get_text_decoration, set_text_decoration, DrawGetTextDecoration, DrawSetTextDecoration, u32 + get_text_direction, set_text_direction, DrawGetTextDirection, DrawSetTextDirection, u32 + get_text_kerning, set_text_kerning, DrawGetTextKerning, DrawSetTextKerning, f64 + get_text_interline_spacing, set_text_interline_spacing, DrawGetTextInterlineSpacing, DrawSetTextInterlineSpacing, f64 + get_text_interword_spacing, set_text_interword_spacing, DrawGetTextInterwordSpacing, DrawSetTextInterwordSpacing, f64 + ); +} + +impl fmt::Debug for DrawingWand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "DrawingWand {{")); + try!(writeln!(f, " Exception: {:?}", self.get_exception())); + try!(writeln!(f, " IsWand: {:?}", self.is_wand())); + try!(self.fmt_unchecked_settings(f, " ")); + try!(self.fmt_string_settings(f, " ")); + try!(self.fmt_string_unchecked_settings(f, " ")); + try!(self.fmt_pixel_settings(f, " ")); + writeln!(f, "}}") + } +} diff --git a/src/wand/macros.rs b/src/wand/macros.rs new file mode 100644 index 0000000..d1aa154 --- /dev/null +++ b/src/wand/macros.rs @@ -0,0 +1,214 @@ +macro_rules! wand_common { + ( $wand:ident, + $new_wand:ident, $clear_wand:ident, $is_wand:ident, $clone:ident, $destroy:ident, + $clear_exc:ident, $get_exc_type:ident, $get_exc:ident + ) => { + pub struct $wand { + pub wand: *mut ::bindings::$wand + } + + impl $wand { + pub fn new() -> Self { + $wand { + wand: unsafe { ::bindings::$new_wand() } + } + } + + fn clear(&mut self) { + unsafe { ::bindings::$clear_wand(self.wand) } + } + + fn clear_exception(&mut self) -> Result<(), &'static str> { + match unsafe { ::bindings::$clear_exc(self.wand) } { + ::bindings::MagickTrue => Ok(()), + _ => Err(concat!("failed to clear", stringify!($wand), "exception")) + } + } + + fn get_exception_type(&self) -> u32 { + unsafe { ::bindings::$get_exc_type(self.wand) } + } + + fn get_exception(&self) -> Result<(String, u32), &'static str> { + let mut severity: u32 = 0; + // TODO: memory management + let ptr = unsafe { ::bindings::$get_exc(self.wand, &mut severity as *mut _) }; + if ptr.is_null() { + Err(concat!("null ptr returned by", stringify!($wand), "get_exception")) + } else { + let c_str = unsafe { CStr::from_ptr(ptr) }; + Ok((c_str.to_string_lossy().into_owned(), severity)) + } + } + + pub fn is_wand(&self) -> Result<(), &'static str> { + match unsafe { ::bindings::$is_wand(self.wand) } { + ::bindings::MagickTrue => Ok(()), + _ => Err(concat!(stringify!($wand), " not a wand")) + } + } + } + + impl Clone for $wand { + fn clone(&self) -> Self { + $wand { + wand: unsafe { ::bindings::$clone(self.wand) } + } + } + } + + impl Drop for $wand { + fn drop(&mut self) { + unsafe { + ::bindings::$clear_exc(self.wand); + ::bindings::$destroy(self.wand); + } + } + } + } +} + +macro_rules! set_get { + ($($get:ident, $set:ident, $c_get:ident, $c_set:ident, $typ:ty )*) => { + $( + pub fn $get(&self) -> $typ { + unsafe { ::bindings::$c_get(self.wand) } + } + pub fn $set(&mut self, v: $typ) -> Result<(), &'static str> { + match unsafe { ::bindings::$c_set(self.wand, v) } { + ::bindings::MagickTrue => Ok(()), + _ => Err(concat!(stringify!($set), " returned false")) + } + } + )* + pub fn fmt_checked_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + $( try!(writeln!(f, "{}{:<50}: {:?}", prefix, stringify!($c_get), self.$get())); )* + Ok(()) + } + } +} + +macro_rules! set_get_unchecked { + ($($get:ident, $set:ident, $c_get:ident, $c_set:ident, $typ:ty )*) => { + $( + pub fn $get(&self) -> $typ { + unsafe { ::bindings::$c_get(self.wand) } + } + pub fn $set(&mut self, v: $typ) { + unsafe { ::bindings::$c_set(self.wand, v) } + } + )* + pub fn fmt_unchecked_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + $( try!(writeln!(f, "{}{:<50}: {:?}", prefix, stringify!($c_get), self.$get())); )* + Ok(()) + } + } +} + +macro_rules! string_get { + ($get:ident, $c_get:ident) => { + pub fn $get(&self) -> Result { + // TODO: memory management + let ptr = unsafe { ::bindings::$c_get(self.wand) }; + if ptr.is_null() { + Err(concat!("null ptr returned by ", stringify!($get))) + } else { + let c_str = unsafe { ::std::ffi::CStr::from_ptr(ptr) }; + Ok(c_str.to_string_lossy().into_owned()) + } + } + } +} + +macro_rules! string_set_get { + ($($get:ident, $set:ident, $c_get:ident, $c_set:ident)*) => { + $( + string_get!($get, $c_get); + pub fn $set(&mut self, s: &str) -> Result<(), &'static str> { + let c_string = try!(::std::ffi::CString::new(s).map_err(|_| "could not convert to cstring")); + match unsafe { ::bindings::$c_set(self.wand, c_string.as_ptr()) } { + ::bindings::MagickTrue => Ok(()), + _ => Err(concat!(stringify!($set), " returned false")) + } + } + )* + pub fn fmt_string_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + $( try!(writeln!(f, "{}{:<50}: {:?}", prefix, stringify!($c_get), self.$get())); )* + Ok(()) + } + } +} + + +macro_rules! string_set_get_unchecked { + ($($get:ident, $set:ident, $c_get:ident, $c_set:ident )*) => { + $( + string_get!($get, $c_get); + pub fn $set(&mut self, s: &str) -> Result<(), &'static str> { + let c_string = try!(::std::ffi::CString::new(s).map_err(|_| "could not convert to cstring")); + unsafe { ::bindings::$c_set(self.wand, c_string.as_ptr()) }; + Ok(()) + } + )* + pub fn fmt_string_unchecked_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + $( try!(writeln!(f, "{}{:<50}: {:?}", prefix, stringify!($c_get), self.$get())); )* + Ok(()) + } + } +} + + +macro_rules! pixel_set_get { + ($($get:ident, $set:ident, $c_get:ident, $c_set:ident )*) => { + $( + pub fn $get(&self) -> ::PixelWand { + let pw = ::PixelWand::new(); + unsafe { ::bindings::$c_get(self.wand, pw.wand) }; + pw + } + pub fn $set(&mut self, pw: &::PixelWand) { + unsafe { ::bindings::$c_set(self.wand, pw.wand) } + } + )* + pub fn fmt_pixel_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + $( + try!(writeln!(f, "{}{:<50}: ", prefix, stringify!($c_get))); + try!(self.$get().fmt_w_prefix(f, &format!("{}{:<53}", prefix, " ") )); + )* + Ok(()) + } + } +} + +macro_rules! color_set_get { + ($( + $get:ident, $get_quantum:ident, $set:ident, $set_quantum:ident, + $c_get:ident, $c_get_quantum:ident, $c_set:ident, $c_set_quantum:ident + )*) => { + $( + pub fn $get(&self) -> f64 { + unsafe { ::bindings::$c_get(self.wand) } + } + pub fn $get_quantum(&self) -> u16 { + unsafe { ::bindings::$c_get_quantum(self.wand) } + } + pub fn $set(&mut self, v: f64) { + unsafe { ::bindings::$c_set(self.wand, v) } + } + pub fn $set_quantum(&mut self, v: u16) { + unsafe { ::bindings::$c_set_quantum(self.wand, v) } + } + )* + pub fn fmt_color_settings(&self, f: &mut ::std::fmt::Formatter, prefix: &str) -> ::std::fmt::Result { + try!(writeln!(f, "{}Color: {:?}, normalized: {:?}\n{}hsl: {:?}", + prefix, + self.get_color_as_string(), + self.get_color_as_normalized_string(), + prefix, + self.get_hsl() + )); + $( try!(writeln!(f, "{}{:<10}: {:>} quantum: {}", prefix, stringify!($c_get).split_at(8).1, self.$get(), self.$get_quantum())); )* + Ok(()) + } + } +} diff --git a/src/wand/magick.rs b/src/wand/magick.rs new file mode 100644 index 0000000..f3daf24 --- /dev/null +++ b/src/wand/magick.rs @@ -0,0 +1,273 @@ +use std::fmt; +use std::ptr; +use std::ffi::{CStr, CString}; +use libc::{c_uint, c_double, c_void}; + +use ::{size_t, ssize_t}; +use ::filters::FilterType; +use ::bindings; +use ::conversions::*; +use super::{DrawingWand, PixelWand}; + + +/// MagickWand is a Rustic wrapper to the Rust bindings to ImageMagick. +/// +/// Instantiating a `MagickWand` will construct an ImageMagick "wand" +/// on which operations can be performed via the `MagickWand` functions. +/// When the `MagickWand` is dropped, the ImageMagick wand will be +/// destroyed as well. +wand_common!( + MagickWand, + NewMagickWand, ClearMagickWand, IsMagickWand, CloneMagickWand, DestroyMagickWand, + MagickClearException, MagickGetExceptionType, MagickGetException +); + +impl MagickWand { + + pub fn new_image(&self, columns: size_t, rows: size_t, pixel_wand: &PixelWand) -> Result<(), &'static str> { + match unsafe { bindings::MagickNewImage(self.wand, columns, rows, pixel_wand.wand) } { + bindings::MagickTrue => Ok(()), + _ => Err("Could not create image"), + } + } + + pub fn annotate_image(&mut self, drawing_wand: &DrawingWand, x: f64, y: f64, angle: f64, text: &str) -> Result<(), &'static str> { + let c_string = try!(CString::new(text).map_err(|_| "could not convert to cstring")); + match unsafe { bindings::MagickAnnotateImage(self.wand, drawing_wand.wand, x, y, angle, c_string.as_ptr() as *const _) } { + bindings::MagickTrue => Ok(()), + _ => Err("unable to annotate image") + } + } + + pub fn append_all(&mut self, stack: bool) -> MagickWand { + unsafe { bindings::MagickResetIterator(self.wand) }; + MagickWand { + wand: unsafe { bindings::MagickAppendImages(self.wand, stack.to_magick()) } + } + } + + pub fn label_image(&self, label: &str) -> Result<(), &'static str> { + let c_label = CString::new(label).unwrap(); + let result = unsafe { + bindings::MagickLabelImage(self.wand, c_label.as_ptr()) + }; + match result { + bindings::MagickTrue => Ok(()), + _ => Err("failed to add label") + } + } + + pub fn write_images(&self, path: &str, adjoin: bool) -> Result<(), &'static str> { + let c_name = CString::new(path).unwrap(); + let result = unsafe { + bindings::MagickWriteImages(self.wand, c_name.as_ptr(), adjoin.to_magick()) + }; + match result { + bindings::MagickTrue => Ok(()), + _ => Err("failed to write images") + } + } + + /// Read the image data from the named file. + pub fn read_image(&self, path: &str) -> Result<(), &'static str> { + let c_name = CString::new(path).unwrap(); + let result = unsafe { + bindings::MagickReadImage(self.wand, c_name.as_ptr()) + }; + match result { + bindings::MagickTrue => Ok(()), + _ => Err("failed to read image") + } + } + + /// Read the image data from the vector of bytes. + pub fn read_image_blob(&self, data: Vec) -> Result<(), &'static str> { + let int_slice = &data[..]; + let size = data.len(); + let result = unsafe { + bindings::MagickReadImageBlob( + self.wand, int_slice.as_ptr() as *const c_void, size as size_t) + }; + match result { + bindings::MagickTrue => Ok(()), + _ => Err("failed to read image") + } + } + + /// Retrieve the width of the image. + pub fn get_image_width(&self) -> usize { + unsafe { + bindings::MagickGetImageWidth(self.wand) as usize + } + } + + /// Retrieve the height of the image. + pub fn get_image_height(&self) -> usize { + unsafe { + bindings::MagickGetImageHeight(self.wand) as usize + } + } + + /// Retrieve the named image property value. + pub fn get_image_property(&self, name: &str) -> Result { + let c_name = CString::new(name).unwrap(); + let result = unsafe { + bindings::MagickGetImageProperty(self.wand, c_name.as_ptr()) + }; + let value = if result.is_null() { + Err("missing property") + } else { + // convert (and copy) the C string to a Rust string + let cstr = unsafe { CStr::from_ptr(result) }; + Ok(cstr.to_string_lossy().into_owned()) + }; + unsafe { + bindings::MagickRelinquishMemory(result as *mut c_void); + } + value + } + + /// Resize the image to the specified width and height, using the + /// specified filter type with the specified blur / sharpness factor. + /// + /// blur_factor values greater than 1 create blurriness, while values + /// less than 1 create sharpness. + pub fn resize_image(&self, width: usize, height: usize, + filter: FilterType, blur_factor: f64) { + unsafe { + bindings::MagickResizeImage( + self.wand, width as size_t, height as size_t, + filter as c_uint, blur_factor as c_double + ); + } + } + + /// Resize the image to find within the given dimensions, maintaining + /// the current aspect ratio. + pub fn fit(&self, width: size_t, height: size_t) { + let mut width_ratio = width as f64; + width_ratio /= self.get_image_width() as f64; + let mut height_ratio = height as f64; + height_ratio /= self.get_image_height() as f64; + let new_width: size_t; + let new_height: size_t; + if width_ratio < height_ratio { + new_width = width; + new_height = (self.get_image_height() as f64 * width_ratio) as size_t; + } else { + new_width = (self.get_image_width() as f64 * height_ratio) as size_t; + new_height = height; + } + unsafe { + bindings::MagickResetIterator(self.wand); + while bindings::MagickNextImage(self.wand) != bindings::MagickFalse { + bindings::MagickResizeImage(self.wand, new_width, new_height, + FilterType::LanczosFilter as c_uint, 1.0); + } + } + } + + /// Detect if the loaded image is not in top-left orientation, and + /// hence should be "auto" oriented so it is suitable for viewing. + pub fn requires_orientation(&self) -> bool { + unsafe { + bindings::MagickGetImageOrientation(self.wand) != bindings::TopLeftOrientation + } + } + + /// Automatically adjusts the loaded image so that its orientation is + /// suitable for viewing (i.e. top-left orientation). + /// + /// Returns `true` if successful or `false` if an error occurred. + pub fn auto_orient(&self) -> bool { + unsafe { + bindings::MagickAutoOrientImage(self.wand) == bindings::MagickTrue + } + } + + /// Write the current image to the provided path. + pub fn write_image(&self, path: &str) -> Result<(), &'static str> { + let c_name = CString::new(path).unwrap(); + let result = unsafe { + bindings::MagickWriteImage(self.wand, c_name.as_ptr()) + }; + match result { + bindings::MagickTrue => Ok(()), + _ => Err("failed to write image") + } + } + + /// Write the image in the desired format to a new blob. + /// + /// The `format` argument may be any ImageMagick supported image + /// format (e.g. GIF, JPEG, PNG, etc). + pub fn write_image_blob(&self, format: &str) -> Result, &'static str> { + let c_format = CString::new(format).unwrap(); + let mut length: size_t = 0; + let blob = unsafe { + bindings::MagickSetImageFormat(self.wand, c_format.as_ptr()); + bindings::MagickResetIterator(self.wand); + bindings::MagickGetImageBlob(self.wand, &mut length) + }; + let mut bytes = Vec::with_capacity(length as usize); + unsafe { + bytes.set_len(length as usize); + ptr::copy_nonoverlapping(blob, bytes.as_mut_ptr(), length as usize); + bindings::MagickRelinquishMemory(blob as *mut c_void); + }; + Ok(bytes) + } + + string_set_get!( + get_filename, set_filename, MagickGetFilename, MagickSetFilename + get_font, set_font, MagickGetFont, MagickSetFont + get_format, set_format, MagickGetFormat, MagickSetFormat + get_image_filename, set_image_filename, MagickGetImageFilename, MagickSetImageFilename + get_image_format, set_image_format, MagickGetImageFormat, MagickSetImageFormat + ); + + set_get!( + get_colorspace, set_colorspace, MagickGetColorspace, MagickSetColorspace, u32 + get_compression, set_compression, MagickGetCompression, MagickSetCompression, u32 + get_compression_quality, set_compression_quality, MagickGetCompressionQuality, MagickSetCompressionQuality, size_t + get_gravity, set_gravity, MagickGetGravity, MagickSetGravity, u32 + get_image_colorspace, set_image_colorspace, MagickGetImageColorspace, MagickSetImageColorspace, u32 + get_image_compose, set_image_compose, MagickGetImageCompose, MagickSetImageCompose, u32 + get_image_compression, set_image_compression, MagickGetImageCompression, MagickSetImageCompression, u32 + get_image_compression_quality, set_image_compression_quality, MagickGetImageCompressionQuality, MagickSetImageCompressionQuality, size_t + get_image_delay, set_image_delay, MagickGetImageDelay, MagickSetImageDelay, size_t + get_image_depth, set_image_depth, MagickGetImageDepth, MagickSetImageDepth, size_t + get_image_dispose, set_image_dispose, MagickGetImageDispose, MagickSetImageDispose, u32 + get_image_endian, set_image_endian, MagickGetImageEndian, MagickSetImageEndian, u32 + get_image_fuzz, set_image_fuzz, MagickGetImageFuzz, MagickSetImageFuzz, f64 + get_image_gamma, set_image_gamma, MagickGetImageGamma, MagickSetImageGamma, f64 + get_image_gravity, set_image_gravity, MagickGetImageGravity, MagickSetImageGravity, u32 + get_image_index, set_image_index, MagickGetImageIndex, MagickSetImageIndex, ssize_t + get_image_interlace_scheme, set_image_interlace_scheme, MagickGetImageInterlaceScheme, MagickSetImageInterlaceScheme, u32 + get_image_interpolate_method, set_image_interpolate_method, MagickGetImageInterpolateMethod, MagickSetImageInterpolateMethod, u32 + get_image_iterations, set_image_iterations, MagickGetImageIterations, MagickSetImageIterations, size_t + get_image_orientation, set_image_orientation, MagickGetImageOrientation, MagickSetImageOrientation, u32 + get_image_rendering_intent, set_image_rendering_intent, MagickGetImageRenderingIntent, MagickSetImageRenderingIntent, u32 + get_image_scene, set_image_scene, MagickGetImageScene, MagickSetImageScene, size_t + get_image_type, set_image_type, MagickGetImageType, MagickSetImageType, u32 + get_image_units, set_image_units, MagickGetImageUnits, MagickSetImageUnits, u32 + get_interlace_scheme, set_interlace_scheme, MagickGetInterlaceScheme, MagickSetInterlaceScheme, u32 + get_interpolate_method, set_interpolate_method, MagickGetInterpolateMethod, MagickSetInterpolateMethod, u32 + get_iterator_index, set_iterator_index, MagickGetIteratorIndex, MagickSetIteratorIndex, ssize_t + get_orientation, set_orientation, MagickGetOrientation, MagickSetOrientation, u32 + get_pointsize, set_pointsize, MagickGetPointsize, MagickSetPointsize, f64 + get_type, set_type, MagickGetType, MagickSetType, u32 + ); + +} + +impl fmt::Debug for MagickWand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "MagickWand {{")); + try!(writeln!(f, " Exception: {:?}", self.get_exception())); + try!(writeln!(f, " IsWand: {:?}", self.is_wand())); + try!(self.fmt_string_settings(f, " ")); + try!(self.fmt_checked_settings(f, " ")); + writeln!(f, "}}") + } +} diff --git a/src/wand/mod.rs b/src/wand/mod.rs new file mode 100644 index 0000000..bad31d4 --- /dev/null +++ b/src/wand/mod.rs @@ -0,0 +1,8 @@ +#[macro_use] mod macros; +mod magick; +mod drawing; +mod pixel; + +pub use self::magick::MagickWand; +pub use self::drawing::DrawingWand; +pub use self::pixel::{HSL, PixelWand}; diff --git a/src/wand/pixel.rs b/src/wand/pixel.rs new file mode 100644 index 0000000..83f4f7e --- /dev/null +++ b/src/wand/pixel.rs @@ -0,0 +1,100 @@ +use std::fmt; +use std::ffi::{CStr, CString}; +use ::bindings; +use ::size_t; + + +#[derive(Default, Debug)] +pub struct HSL { + hue: f64, + saturation: f64, + lightness: f64 +} + +wand_common!( + PixelWand, + NewPixelWand, ClearPixelWand, IsPixelWand, ClonePixelWand, DestroyPixelWand, + PixelClearException, PixelGetExceptionType, PixelGetException +); + +impl PixelWand { + pub fn is_similar(&self, other: &PixelWand, fuzz: f64) -> Result<(), &'static str> { + match unsafe { bindings::IsPixelWandSimilar(self.wand, other.wand, fuzz) } { + bindings::MagickTrue => Ok(()), + _ => Err("not similar") + } + } + + pub fn get_hsl(&self) -> HSL { + let mut hsl = HSL::default(); + unsafe { bindings::PixelGetHSL( + self.wand, + &mut hsl.hue as *mut _, + &mut hsl.saturation as *mut _, + &mut hsl.lightness as *mut _ + );} + hsl + } + + pub fn set_hsl(&self, hsl: &HSL) { + unsafe { bindings::PixelSetHSL( + self.wand, + hsl.hue, + hsl.saturation, + hsl.lightness + );} + } + + pub fn fmt_w_prefix(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result { + let mut prf = prefix.to_string(); + prf.push_str(" "); + try!(writeln!(f, "{}PixelWand {{", prefix)); + try!(writeln!(f, "{}Exception: {:?}", prf, self.get_exception())); + try!(writeln!(f, "{}IsWand: {:?}", prf, self.is_wand())); + try!(self.fmt_unchecked_settings(f, &prf)); + try!(self.fmt_color_settings(f, &prf)); + writeln!(f, "{}}}", prefix) + } + + pub fn set_color(&mut self, s: &str) -> Result<(), &'static str> { + let c_string = try!(CString::new(s).map_err(|_| "could not convert to cstring")); + match unsafe { bindings::PixelSetColor(self.wand, c_string.as_ptr())} { + bindings::MagickTrue => Ok(()), + _ => Err("failed to set color") + } + } + + string_get!(get_color_as_string, PixelGetColorAsString); + string_get!(get_color_as_normalized_string, PixelGetColorAsNormalizedString); + + set_get_unchecked!( + get_color_count, set_color_count, PixelGetColorCount, PixelSetColorCount, size_t + get_index, set_index, PixelGetIndex, PixelSetIndex, u16 + get_fuzz, set_fuzz, PixelGetFuzz, PixelSetFuzz, f64 + ); + + color_set_get!( + get_alpha, get_alpha_quantum, set_alpha, set_alpha_quantum, + PixelGetAlpha, PixelGetAlphaQuantum, PixelSetAlpha, PixelSetAlphaQuantum + get_black, get_black_quantum, set_black, set_black_quantum, + PixelGetBlack, PixelGetBlackQuantum, PixelSetBlack, PixelSetBlackQuantum + get_blue, get_blue_quantum, set_blue, set_blue_quantum, + PixelGetBlue, PixelGetBlueQuantum, PixelSetBlue, PixelSetBlueQuantum + get_cyan, get_cyan_quantum, set_cyan, set_cyan_quantum, + PixelGetCyan, PixelGetCyanQuantum, PixelSetCyan, PixelSetCyanQuantum + get_green, get_green_quantum, set_green, set_green_quantum, + PixelGetGreen, PixelGetGreenQuantum, PixelSetGreen, PixelSetGreenQuantum + get_magenta, get_magenta_quantum, set_magenta, set_magenta_quantum, + PixelGetMagenta, PixelGetMagentaQuantum, PixelSetMagenta, PixelSetMagentaQuantum + get_red, get_red_quantum, set_red, set_red_quantum, + PixelGetRed, PixelGetRedQuantum, PixelSetRed, PixelSetRedQuantum + get_yellow, get_yellow_quantum, set_yellow, set_yellow_quantum, + PixelGetYellow, PixelGetYellowQuantum, PixelSetYellow, PixelSetYellowQuantum + ); +} + +impl fmt::Debug for PixelWand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt_w_prefix(f, "") + } +}