diff --git a/README.md b/README.md index db3bc8b..11b7d52 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ A "safe" Rust interface to the [ImageMagick](http://www.imagemagick.org/) system 1. Write unit tests 1. Test it on lots of images in batches to stress test it; should not crash -## Building the Bindings +## Generating Bindings -To build the ImageMagick bindings, we use [rust-bindgen](https://github.com/crabtw/rust-bindgen), which reads the C header files and produces a suitable wrapper in Rust. +To generate the ImageMagick bindings, we use [rust-bindgen](https://github.com/crabtw/rust-bindgen), which reads the C header files and produces a suitable wrapper in Rust. This example is using the [Homebrew](http://brew.sh) installed version of ImageMagick, and the LLVM compiler suite provided in the Command Line Tools from Apple. The only real difference for Mac OS X is the `DYLD_LIBRARY_PATH` that is needed to work around [issue #89](https://github.com/crabtw/rust-bindgen/issues/89) in rust-bindgen. Otherwise, the same basic steps should work on any Rust-supported system. @@ -32,4 +32,4 @@ $ DYLD_LIBRARY_PATH=/Library/Developer/CommandLineTools/usr/lib \ ~/gen.h ``` -Then copy the `~/bindings.rs` file into the `src` directory of this project, and rebuild everything. Hopefully it still works. +Then copy the `~/bindings.rs` file into the `src` directory of this project, and rebuild everything (`cargo clean` and `cargo test`). Hopefully it still works. diff --git a/src/lib.rs b/src/lib.rs index 0da5687..d9f7bfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,10 @@ extern crate libc; +use std::ffi::CString; +use libc::{c_uint, size_t, c_double}; +use filters::FilterType; + mod bindings; /// MagickWand is a Rustic wrapper to the Rust bindings to ImageMagick. @@ -43,16 +47,65 @@ impl MagickWand { } } -// wand-of-rust wrapper around MagickResizeImage -// pub fn resize_image(&self, width: uint, height: uint, -// 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 -// ); -// } -// } + /// 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") + } + } + + // TODO: make a Rustic wrapper for reading a blob from bytes + // pub fn MagickReadImageBlob(arg1: *mut MagickWand, + // arg2: *const ::libc::c_void, arg3: size_t) + // -> MagickBooleanType; + + /// 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 + } + } + + /// 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 + ); + } + } + + // TODO: get the image from the wand somehow (maybe GetImageFromMagickWand()) + + /// 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") + } + } } // Automate safe cleanup for MagickWand instances. @@ -68,27 +121,63 @@ impl Drop for MagickWand { } /// This function must be called before any other ImageMagick operations -/// are attempted. +/// are attempted. This function is safe to be called repeatedly. pub fn magick_wand_genesis() { unsafe { - bindings::MagickWandGenesis(); + match bindings::IsMagickWandInstantiated() { + bindings::MagickTrue => (), + _ => bindings::MagickWandGenesis() + } } } /// This function should be called when ImageMagick is no longer needed. +/// This function is safe to be called repeatedly. pub fn magick_wand_terminus() { unsafe { - bindings::MagickWandTerminus(); + match bindings::IsMagickWandInstantiated() { + bindings::MagickTrue => bindings::MagickWandTerminus(), + _ => () + } } } -#[cfg(test)] -mod test { +pub mod filters { - use super::{MagickWand}; + use bindings; - #[test] - fn test_new_drop() { - MagickWand::new(); + 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/tests/data/IMG_5745.JPG b/tests/data/IMG_5745.JPG new file mode 100644 index 0000000..2dfe6db Binary files /dev/null and b/tests/data/IMG_5745.JPG differ diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..0561f08 --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Nathan Fiedler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +extern crate magick_rust; + +use magick_rust::{MagickWand, magick_wand_genesis}; +use magick_rust::filters::{FilterType}; + +use std::sync::{Once, ONCE_INIT}; + +// Used to make sure MagickWand is initialized exactly once. Note that we +// do not bother shutting down, we simply exit when the tests are done. +static START: Once = ONCE_INIT; + +#[test] +fn test_new_drop() { + START.call_once(|| { + magick_wand_genesis(); + }); + MagickWand::new(); +} + +#[test] +fn test_resize_image() { + START.call_once(|| { + magick_wand_genesis(); + }); + let wand = MagickWand::new(); + assert!(wand.read_image("tests/data/IMG_5745.JPG").is_ok()); + assert_eq!(512, wand.get_image_width()); + assert_eq!(384, wand.get_image_height()); + let halfwidth = match wand.get_image_width() { + 1 => 1, + width => width / 2 + }; + let halfheight = match wand.get_image_height() { + 1 => 1, + height => height / 2 + }; + wand.resize_image(halfwidth, halfheight, FilterType::LanczosFilter, 1.0); + assert_eq!(256, wand.get_image_width()); + assert_eq!(192, wand.get_image_height()); +}