Merge pull request #33 from little-bobby-tables/color-transformations

Add color-related getters and transforms
This commit is contained in:
Nathan Fiedler
2018-01-03 10:38:30 -08:00
committed by GitHub
6 changed files with 148 additions and 16 deletions

View File

@ -4,15 +4,15 @@ RUN apt-get update \
&& apt-get -y install curl build-essential clang pkg-config libjpeg-turbo-progs libpng-dev \
&& rm -rfv /var/lib/apt/lists/*
ENV MAGICK_VERSION 7.0.7-15
ENV MAX_MAGICK_VERSION 7.0
RUN curl https://www.imagemagick.org/download/ImageMagick-${MAGICK_VERSION}.tar.gz | tar xz \
&& cd ImageMagick-${MAGICK_VERSION} \
RUN curl https://www.imagemagick.org/download/ImageMagick.tar.gz | tar xz \
&& cd ImageMagick-${MAX_MAGICK_VERSION}* \
&& ./configure --with-magick-plus-plus=no --with-perl=no \
&& make \
&& make install \
&& cd .. \
&& rm -r ImageMagick-${MAGICK_VERSION}
&& rm -r ImageMagick-${MAX_MAGICK_VERSION}*
RUN adduser --disabled-password --gecos '' magick-rust

View File

@ -37,7 +37,8 @@ mod conversions;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub use wand::*;
pub use bindings::MetricType;
pub use bindings::{MetricType, FilterType, ColorspaceType, DitherMethod};
pub use conversions::ToMagick;
use libc::size_t;
#[cfg(not(target_os = "freebsd"))]

View File

@ -83,6 +83,16 @@ macro_rules! wand_common {
}
}
macro_rules! get {
($($get:ident, $c_get:ident, $typ:ty )*) => {
$(
pub fn $get(&self) -> $typ {
unsafe { ::bindings::$c_get(self.wand) }
}
)*
}
}
macro_rules! set_get {
($($get:ident, $set:ident, $c_get:ident, $c_set:ident, $typ:ty )*) => {
$(
@ -227,3 +237,17 @@ macro_rules! color_set_get {
}
}
}
macro_rules! mutations {
($($(#[$attr:meta])* $c_fun:ident => $fun:ident($($arg:ident: $ty:ty),*))*) => {
$(
$(#[$attr])*
pub fn $fun(&self $(, $arg: $ty)*) -> Result<(), &'static str> {
match unsafe { bindings::$c_fun(self.wand $(, $arg)*) } {
bindings::MagickBooleanType::MagickTrue => Ok(()),
_ => Err(concat!(stringify!($c_fun), " invocation failed"))
}
}
)*
}
}

View File

@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use std::fmt;
use std::ptr;
use std::{fmt, ptr, slice};
use std::ffi::{CStr, CString};
use libc::{c_void};
@ -180,6 +179,50 @@ impl MagickWand {
value
}
/// Returns a `PixelWand` instance for the pixel specified by x and y offests.
pub fn get_image_pixel_color(&self, x: isize, y: isize) -> Option<PixelWand> {
let pw = PixelWand::new();
unsafe {
if bindings::MagickGetImagePixelColor(self.wand, x, y, pw.wand) == bindings::MagickBooleanType::MagickTrue {
Some(pw)
} else {
None
}
}
}
/// Returns the image histogram as a vector of `PixelWand` instances for every unique color.
pub fn get_image_histogram(&self) -> Option<Vec<PixelWand>> {
let mut color_count: size_t = 0;
unsafe {
bindings::MagickGetImageHistogram(self.wand, &mut color_count)
.as_mut()
.map(|ptrs| slice::from_raw_parts(ptrs, color_count)
.iter().map(|raw_wand| PixelWand { wand: *raw_wand })
.collect())
}
}
/// Extracts pixel data from the image as a vector of 0..255 values defined by `map`.
/// See https://www.imagemagick.org/api/magick-image.php#MagickExportImagePixels for more information.
pub fn export_image_pixels(&self, x: isize, y: isize, width: usize, height: usize, map: &str) -> Option<Vec<u8>> {
let c_map = CString::new(map).unwrap();
let capacity = width * height * map.len();
let mut pixels = Vec::with_capacity(capacity);
unsafe {
pixels.set_len(capacity as usize);
if bindings::MagickExportImagePixels(self.wand, x, y, width, height, c_map.as_ptr(),
bindings::StorageType::CharPixel, pixels.as_mut_ptr() as *mut c_void) == bindings::MagickBooleanType::MagickTrue {
Some(pixels)
} else {
None
}
}
}
/// Resize the image to the specified width and height, using the
/// specified filter type.
pub fn resize_image(&self, width: usize, height: usize,
@ -288,6 +331,30 @@ impl MagickWand {
Ok(bytes)
}
mutations!(
/// Set the image colorspace, transforming (unlike `set_image_colorspace`) image data in
/// the process.
MagickTransformImageColorspace => transform_image_colorspace(
colorspace: bindings::ColorspaceType)
/// Reduce the number of colors in the image.
MagickQuantizeImage => quantize_image(
number_of_colors: size_t, colorspace: bindings::ColorspaceType,
tree_depth: size_t, dither_method: bindings::DitherMethod, measure_error: bindings::MagickBooleanType)
/// Reduce the number of colors in the image.
MagickQuantizeImages => quantize_images(
number_of_colors: size_t, colorspace: bindings::ColorspaceType,
tree_depth: size_t, dither_method: bindings::DitherMethod, measure_error: bindings::MagickBooleanType)
/// Discard all but one of any pixel color.
MagickUniqueImageColors => unique_image_colors()
);
get!(
get_image_colors, MagickGetImageColors, size_t
);
string_set_get!(
get_filename, set_filename, MagickGetFilename, MagickSetFilename
get_font, set_font, MagickGetFont, MagickSetFont

View File

@ -23,9 +23,9 @@ use ::size_t;
#[derive(Default, Debug)]
pub struct HSL {
hue: f64,
saturation: f64,
lightness: f64
pub hue: f64,
pub saturation: f64,
pub lightness: f64
}
wand_common!(

View File

@ -16,17 +16,14 @@
extern crate magick_rust;
use magick_rust::{MagickWand, magick_wand_genesis, MetricType};
use magick_rust::{MagickWand, magick_wand_genesis, MetricType, ColorspaceType, FilterType, DitherMethod};
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::sync::{Once, ONCE_INIT};
// TODO: nathan does not understand how to expose the FilterType without
// this ugliness, his Rust skills are sorely lacking
use magick_rust::bindings;
use magick_rust::ToMagick;
// 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.
@ -57,7 +54,7 @@ fn test_resize_image() {
1 => 1,
height => height / 2
};
wand.resize_image(halfwidth, halfheight, bindings::FilterType::LanczosFilter);
wand.resize_image(halfwidth, halfheight, FilterType::LanczosFilter);
assert_eq!(256, wand.get_image_width());
assert_eq!(192, wand.get_image_height());
}
@ -220,3 +217,46 @@ fn test_page_geometry() {
assert_eq!(80, wand.get_image_width());
assert_eq!(76, wand.get_image_height());
}
#[test]
fn test_transform_image_colorspace() {
START.call_once(|| {
magick_wand_genesis();
});
let wand = MagickWand::new();
assert!(wand.read_image("tests/data/IMG_5745.JPG").is_ok());
assert_eq!(wand.get_image_colorspace(), ColorspaceType::sRGBColorspace);
let pixel_color = wand.get_image_pixel_color(10, 10).unwrap();
assert_ne!(pixel_color.get_hsl().hue, 0.0);
assert!(wand.transform_image_colorspace(ColorspaceType::GRAYColorspace).is_ok());
assert_eq!(wand.get_image_colorspace(), ColorspaceType::GRAYColorspace);
let pixel_grayscale = wand.get_image_pixel_color(10, 10).unwrap();
assert_eq!(pixel_grayscale.get_hsl().hue, 0.0);
/* The output of `export_image_pixels` should match
* `convert -type Grayscale tests/data/IMG_5745.JPG[2x2+0+0] txt:` */
assert_eq!(wand.export_image_pixels(0, 0, 2, 2, "I").unwrap(),
vec![212, 212, 210, 210])
}
#[test]
fn test_color_reduction() {
START.call_once(|| {
magick_wand_genesis();
});
let wand = MagickWand::new();
assert!(wand.read_image("tests/data/IMG_5745.JPG").is_ok());
assert_eq!(38322, wand.get_image_colors());
assert!(wand.quantize_image(6, ColorspaceType::RGBColorspace, 1,
DitherMethod::UndefinedDitherMethod, false.to_magick()).is_ok());
assert_eq!(6, wand.get_image_colors());
let histogram = wand.get_image_histogram().unwrap();
assert_eq!(6, histogram.len());
assert_eq!(wand.get_image_width() * wand.get_image_height(),
histogram.iter().fold(0, |total_colors, wand| total_colors + wand.get_color_count()));
}