extern crate libc;
extern crate rand;
mod bindings;
use std::fmt;
use self::libc::{c_char, c_int, c_float, c_double};
use self::rand::{thread_rng, Rng};
use self::bindings::*;
use matrix::Matrix;
pub enum FontFace {
CvFontHersheyComplex,
CvFontHersheyComplexSmall,
CvFontHersheyDuplex,
CvFontHersheyPlain,
CvFontHersheyScriptComplex,
CvFontHersheyScriptSimplex,
CvFontHersheySimplex,
CvFontHersheyTriplex
}
pub struct Font {
font: Box<CvFont>
}
impl Font {
pub fn new(font: FontFace) -> Font {
let mut f = Box::new(CvFont {
namefont: 0 as *const c_char,
color: CvScalar {
val: [0 as c_double, 0 as c_double, 0 as c_double, 0 as c_double]
},
font_face: 0 as c_int,
ascii: 0 as *const c_int,
greek: 0 as *const c_int,
cyrillic: 0 as *const c_int,
hscale: 0 as c_float,
vscale: 0 as c_float,
shear: 0 as c_float,
thickness: 0 as c_int,
dx: 0 as c_float,
line_type: 0 as c_int
});
let font_face: c_int = match font {
FontFace::CvFontHersheySimplex => 0,
FontFace::CvFontHersheyComplex => 3,
FontFace::CvFontHersheyComplexSmall => 5,
FontFace::CvFontHersheyDuplex => 2,
FontFace::CvFontHersheyPlain => 1,
FontFace::CvFontHersheyScriptComplex => 7,
FontFace::CvFontHersheyScriptSimplex => 6,
FontFace::CvFontHersheyTriplex => 4,
};
unsafe {
cvInitFont(
(&mut *f) as *mut CvFont, font_face, 1 as c_double, 1 as c_double,
0 as c_double, 1 as c_int, 8 as c_int
);
}
Font {
font: f
}
}
}
pub struct Window {
name: String
}
impl Window {
pub fn new() -> Window {
unsafe {
let mut s: String = thread_rng().gen_ascii_chars().take(30).collect();
s.push('\0');
cvNamedWindow(s.as_ptr() as *const c_char, CV_WINDOW_AUTOSIZE);
Window {
name: s
}
}
}
pub fn show_image<T: Image>(&self, img: &T) -> &Self {
unsafe {
cvShowImage(
self.name.as_ptr() as *const c_char,
img.buffer() as *const CvArr
);
}
self
}
pub fn wait(&self, delay: i32) -> &Self {
unsafe {
if delay > 0 {
cvWaitKey(delay as c_int);
}
self
}
}
pub fn wait_key(&self) -> &Self {
unsafe {
cvWaitKey(0 as c_int);
self
}
}
pub fn destroy(&self) {
unsafe {
cvDestroyWindow(self.name.as_ptr() as *const c_char);
}
}
}
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8
}
impl fmt::Display for Rgb {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} {} {})", self.r, self.g, self.b)
}
}
pub struct GrayValue {
pub val: u8,
}
impl fmt::Display for GrayValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({})", self.val)
}
}
pub trait Image {
fn new(width: usize, height: usize) -> Self;
fn buffer(&self) -> *const IplImage;
fn pixel_as_rgb(&self, x: usize, y: usize) -> Option<Rgb>;
fn set_pixel_from_rgb(&self, x: usize, y: usize, p: &Rgb);
fn width(&self) -> usize { unsafe { (*self.buffer()).width as usize } }
fn height(&self) -> usize { unsafe { (*self.buffer()).height as usize } }
fn depth(&self) -> usize { unsafe { (*self.buffer()).depth as usize } }
fn widthstep(&self) -> usize { unsafe { (*self.buffer()).widthstep as usize } }
fn channels(&self) -> usize { unsafe { (*self.buffer()).nchannels as usize } }
fn to_file(&self, fname: &str) -> bool {
let mut s = fname.to_string();
s.push('\0');
unsafe {
let r = cvSaveImage(
s.as_ptr() as *const c_char,
self.buffer() as *const CvArr,
0 as *const c_int
);
r != 0
}
}
fn copy_from<T: Image>(&mut self,
img: &T, x: usize, y: usize, width: usize, height: usize, dstx: usize, dsty: usize) {
for iy in 0..height {
for ix in 0..width {
self.set_pixel_from_rgb(
dstx + ix, dsty + iy, &img.pixel_as_rgb(x + ix, y + iy).unwrap()
);
}
}
}
fn draw_text(&mut self, txt: &str, x: usize, y: usize, font: &Font) {
let mut s = txt.to_string();
s.push('\0');
let p = CvPoint {
x: x as c_int,
y: y as c_int
};
let sc = CvScalar {
val: [255 as c_double, 255 as c_double, 255 as c_double, 255 as c_double]
};
unsafe {
cvPutText(
self.buffer() as *mut CvArr,
s.as_ptr() as *const c_char,
p,
& *(font.font) as *const CvFont,
sc
);
}
}
fn resize(&self, height: usize, width: usize) -> Self;
}
impl Image for GrayImage {
fn new(w: usize, h: usize) -> GrayImage {
let siz = CvSize {
width: w as c_int,
height: h as c_int
};
GrayImage {
iplimage: unsafe { cvCreateImage(siz, 8, 1) }
}
}
fn buffer(&self) -> *const IplImage { self.iplimage }
fn pixel_as_rgb(&self, x: usize, y: usize) -> Option<Rgb> {
unsafe {
let img = &(*self.buffer());
let off = (y * self.widthstep() + x) as isize;
if off < 0 || off >= (img.imagesize as isize) {
return None;
}
let gr = *img.imagedata.offset(off) as u8;
Some(Rgb {
r: gr,
g: gr,
b: gr
})
}
}
fn set_pixel_from_rgb(&self, x: usize, y: usize, p: &Rgb) {
let g =
0.299 * (p.r as f64) +
0.587 * (p.g as f64) +
0.114 * (p.b as f64);
unsafe {
let img = &(*self.buffer());
let off = (y * self.widthstep() + x) as isize;
let p = img.imagedata.offset(off) as *mut u8;
*p = g as u8;
}
}
fn resize(&self, width: usize, height: usize) -> Self {
let mut _dst = GrayImage::new(width, height);
unsafe {
cvResize(
self.buffer() as *const CvArr,
_dst.buffer() as *mut CvArr,
INTER_LINEAR
);
}
_dst
}
}
impl Image for RgbImage {
fn resize(&self, width: usize, height: usize) -> Self {
let mut _dst = RgbImage::new(width, height);
unsafe {
cvResize(
self.buffer() as *const CvArr,
_dst.buffer() as *mut CvArr,
INTER_LINEAR
);
}
_dst
}
fn new(w: usize, h: usize) -> RgbImage {
let siz = CvSize {
width: w as c_int,
height: h as c_int
};
RgbImage {
iplimage: unsafe { cvCreateImage(siz, 8, 3) }
}
}
fn buffer(&self) -> *const IplImage { self.iplimage }
fn pixel_as_rgb(&self, x: usize, y: usize) -> Option<Rgb> {
unsafe {
let img = &(*self.buffer());
let off = (y * self.widthstep() + x * 3) as isize;
if off < 0 || off + 2 >= (img.imagesize as isize) {
return None;
}
Some(Rgb {
b: *img.imagedata.offset(off) as u8,
g: *img.imagedata.offset(off + 1) as u8,
r: *img.imagedata.offset(off + 2) as u8
})
}
}
fn set_pixel_from_rgb(&self, x: usize, y: usize, px: &Rgb) {
unsafe {
let img = &(*self.buffer());
let off = (y * self.widthstep() + x * 3) as isize;
let p = img.imagedata.offset(off) as *mut u8;
let q = img.imagedata.offset(off + 1) as *mut u8;
let r = img.imagedata.offset(off + 2) as *mut u8;
*p = px.b;
*q = px.g;
*r = px.r;
}
}
}
pub fn grid<T: Image>(images: &Vec<T>, cols: usize, space: usize) -> T {
assert!(images.len() > 0, "At least one image is required.");
assert!(cols > 0, "At least on ecolumn is required.");
let mut rows = images.len() / cols;
if rows * cols < images.len() {
rows += 1;
}
let img_width = images.last().unwrap().width();
let img_height = images.last().unwrap().height();
let w = img_width * cols + (cols - 1) * space;
let h = img_height * rows + (rows - 1) * space;
let mut dst = T::new(w, h);
let mut col = 0;
let mut row = 0;
for img in images {
assert!(img_width == img.width() && img_height == img.height(), "Size of images must be equal.");
dst.copy_from(
img, 0, 0, img_width, img_height, col * (img_width + space), row * (img_height + space)
);
col += 1;
if col >= cols {
col = 0;
row += 1;
}
}
dst
}
pub struct RgbImage {
iplimage: *const IplImage
}
impl RgbImage {
pub fn grid(images: &Vec<Self>, cols: usize, space: usize) -> RgbImage {
grid::<RgbImage>(images, cols, space)
}
pub fn from_file(fname: &str) -> Option<RgbImage> {
unsafe {
let mut s = fname.to_string();
s.push('\0');
let r = cvLoadImage(s.as_ptr() as *const c_char, 1 as c_int);
let i = RgbImage{ iplimage: r };
if r.is_null() || i.depth() != 8 || i.channels() != 3 {
return None;
}
Some(i)
}
}
pub fn from_raw(image: *const IplImage) -> RgbImage {
unsafe {
assert!((*image).depth == 8 && (*image).nchannels == 3);
RgbImage {
iplimage: image
}
}
}
pub fn pixel(&self, x: usize, y: usize) -> Option<Rgb> {
self.pixel_as_rgb(x, y)
}
pub fn draw_box(&mut self, x: usize, y: usize, w: usize, h: usize, color: &Rgb) {
let p1 = CvPoint { x: x as c_int, y: y as c_int };
let p2 = CvPoint { x: (x + w - 1) as c_int, y: (y + h - 1) as c_int };
let c = CvScalar {
val: [color.b as c_double, color.g as c_double, color.r as c_double, 0 as c_double]
};
unsafe {
cvRectangle(
self.buffer() as *mut CvArr,
p1, p2, c,
1 as c_int, 8 as c_int, 0 as c_int
);
}
}
pub fn dup(&self) -> RgbImage {
let mut img = RgbImage::new(self.width(), self.height());
img.copy_from(self, 0, 0, self.width(), self.height(), 0, 0);
img
}
}
pub struct GrayImage {
iplimage: *const IplImage
}
impl GrayImage {
pub fn to_matrix(&self) -> Matrix<u8> {
Matrix::from_it(self.pixel_iter(), self.width())
}
pub fn grid(images: &Vec<Self>, cols: usize, space: usize) -> GrayImage {
grid::<GrayImage>(images, cols, space)
}
pub fn from_file(fname: &str) -> Option<GrayImage> {
unsafe {
let mut s = fname.to_string();
s.push('\0');
let r = cvLoadImage(s.as_ptr() as *const c_char, 0 as c_int);
if r.is_null() {
return None;
}
Some(GrayImage::from_raw(r))
}
}
pub fn from_matrix(m: &Matrix<u8>) -> GrayImage {
GrayImage::from_slice(m.buf(), m.rows(), m.cols()).unwrap()
}
pub fn from_slice(v: &[u8], rows: usize, cols: usize) -> Option<GrayImage> {
if rows * cols != v.len() {
return None;
}
let siz = CvSize {
width: cols as c_int,
height: rows as c_int
};
let mut dst = GrayImage { iplimage: unsafe { cvCreateImage(siz, 8, 1) } };
for y in 0..rows {
for x in 0..cols {
dst.set_pixel(x, y, *v.get(y * cols + x).unwrap());
}
}
Some(dst)
}
pub fn from_raw(image: *const IplImage) -> GrayImage {
unsafe {
if (*image).depth != 8 || (*image).nchannels != 1 {
let siz = CvSize {
width: (*image).width,
height: (*image).height
};
let ipl = cvCreateImage(siz, 8, 1);
cvCvtColor(image as *const CvArr, ipl as *mut CvArr, CV_BGR2GRAY);
GrayImage {
iplimage: ipl
}
} else {
GrayImage {
iplimage: image
}
}
}
}
pub fn pixel(&self, x: usize, y: usize) -> Option<GrayValue> {
match self.pixel_as_rgb(x, y) {
Some(p) => {
Some(GrayValue {
val: p.r
})
}
_ => None
}
}
pub fn set_pixel(&mut self, x: usize, y: usize, newval: u8) {
unsafe {
let img = &(*self.buffer());
let off = (y * self.widthstep() + x) as isize;
let p = img.imagedata.offset(off) as *mut u8;
*p = newval;
}
}
pub fn set_pixel_mask(&mut self, mask: &GrayImage, newval: u8) {
for y in 0..self.height() {
for x in 0..self.width() {
if mask.pixel(x, y).unwrap().val == 255 {
self.set_pixel(x, y, newval);
}
}
}
}
pub fn pixels_from_mask_as_u8(&self, i: &GrayImage) -> Option<Vec<u8>> {
if self.width() != i.width() || self.height() != i.height() {
return None;
}
let mut pixels: Vec<u8> = Vec::new();
unsafe {
for y in 0..self.height() {
let s: *const c_char = (*self.iplimage).imagedata.offset(y as isize * self.widthstep() as isize);
let m: *const c_char = (*i.iplimage).imagedata.offset(y as isize * i.widthstep() as isize);
for x in 0..self.width() {
if *m.offset(x as isize) != 0 {
pixels.push(*s.offset(x as isize) as u8);
}
}
}
}
Some(pixels)
}
pub fn rectangle(&self, x: usize, y: usize, width: usize, height: usize) -> Vec<u8> {
let mut v = Vec::new();
for i in y..y+height {
for j in x..x+width {
v.push(self.pixel(j, i).unwrap().val);
}
}
v
}
pub fn mask_iter<'q>(&'q self, i: &'q GrayImage) -> MaskIter {
MaskIter {
src: self,
mask: i,
x: 0,
y: 0,
}
}
pub fn pixel_iter(&self) -> GrayValueIterator {
GrayValueIterator {
x: 0,
y: 0,
image: self
}
}
}
pub struct GrayValueIterator<'t> {
x: usize,
y: usize,
image: &'t GrayImage
}
impl <'t> Iterator for GrayValueIterator<'t> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
if self.x >= self.image.width() {
self.x = 0;
self.y += 1;
}
if self.y >= self.image.height() {
return None;
}
self.x += 1;
Some(self.image.pixel(self.x - 1, self.y).unwrap().val)
}
}
pub struct MaskIter<'t> {
src: &'t GrayImage,
mask: &'t GrayImage,
x: usize,
y: usize,
}
impl <'t> Iterator for MaskIter<'t> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
if self.src.width() != self.mask.width() || self.src.height() != self.mask.height() {
return None;
}
loop {
if self.x >= self.src.width() {
self.x = 0;
self.y += 1;
}
if self.y >= self.src.height() {
return None;
}
if self.mask.pixel(self.x, self.y).unwrap().val != 0 {
let r = self.src.pixel(self.x, self.y).unwrap().val;
self.x += 1;
return Some(r);
}
self.x += 1;
}
}
}
pub struct Video {
pub cvcapture: *const CvCapture
}
impl Video {
pub fn from_file(fname: &str) -> Option<Video> {
let mut f = fname.to_string();
f.push('\0');
unsafe {
let c = cvCreateFileCapture(f.as_ptr() as *const c_char);
match c.is_null() {
true => None,
false => {
Some(Video {
cvcapture: c
})
}
}
}
}
pub fn color_frame_iter(&self) -> VideoColorFrameIterator {
VideoColorFrameIterator {
video: self,
}
}
pub fn gray_frame_iter(&self) -> VideoGrayFrameIterator {
VideoGrayFrameIterator {
video: self,
}
}
}
impl Drop for Video {
fn drop(&mut self) {
unsafe { cvReleaseCapture(&self.cvcapture); }
}
}
pub struct VideoColorFrameIterator<'q> {
video: &'q Video,
}
impl <'q> Iterator for VideoColorFrameIterator<'q> {
type Item = RgbImage;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let i = cvGrabFrame(self.video.cvcapture);
match i {
0 => None,
_ => {
let f = cvRetrieveFrame(self.video.cvcapture, 0 as c_int);
match f.is_null() {
true => None,
false => {
Some(RgbImage::from_raw(f))
}
}
}
}
}
}
}
pub struct VideoGrayFrameIterator<'q> {
video: &'q Video,
}
impl <'q> Iterator for VideoGrayFrameIterator<'q> {
type Item = GrayImage;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let i = cvGrabFrame(self.video.cvcapture);
match i {
0 => None,
_ => {
let f = cvRetrieveFrame(self.video.cvcapture, 0 as c_int);
match f.is_null() {
true => None,
false => {
Some(GrayImage::from_raw(f))
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
extern crate libc;
use super::*;
#[test]
fn test_colorimage_error() {
assert!(RgbImage::from_file("xxxxxxxxxxxx.png").is_none());
}
#[test]
fn test_colorimage() {
let i = RgbImage::from_file("datasets/testing/colors.png").unwrap();
assert_eq!(i.width(), 100);
assert_eq!(i.height(), 100);
for y in 0..50 {
for x in 0..50 {
let p = i.pixel(x, y).unwrap();
assert!(p.r == 255 && p.b == 0 && p.g == 0);
}
}
for y in 0..50 {
for x in 50..100 {
let p = i.pixel(x, y).unwrap();
assert!(p.r == 255 && p.b == 255 && p.g == 255);
}
}
for y in 50..100 {
for x in 0..50 {
let p = i.pixel(x, y).unwrap();
assert!(p.r == 0 && p.b == 0 && p.g == 0);
}
}
for y in 50..100 {
for x in 50..100 {
let p = i.pixel(x, y).unwrap();
assert!(p.r == 0 && p.b == 0 && p.g == 255);
}
}
}
#[test]
fn test_mask_image() {
let mask = GrayImage::from_file("datasets/testing/10x10colors_mask.png").unwrap();
let gray = GrayImage::from_file("datasets/testing/10x10gray.png").unwrap();
let v = gray.pixels_from_mask_as_u8(&mask);
assert_eq!(v.unwrap(),
vec![0x36, 0x36, 0xed, 0x12, 0x12, 0x36, 0x36, 0xff, 0x36, 0x49, 0x00, 0xff]
);
let x: Vec<u8> = gray.mask_iter(&mask).collect();
assert_eq!(x,
vec![0x36, 0x36, 0xed, 0x12, 0x12, 0x36, 0x36, 0xff, 0x36, 0x49, 0x00, 0xff]
);
}
#[test]
fn test_mask_video() {
let video = Video::from_file("datasets/testing/colors.mp4").unwrap();
let mask = GrayImage::from_file("datasets/testing/colors_mask.png").unwrap();
let img = video.gray_frame_iter().next().unwrap();
let pixels = img.mask_iter(&mask).collect::<Vec<u8>>();
assert_eq!(pixels, vec![
76, 76, 76, 76,
255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 0, 149, 149, 149, 0, 149, 149, 149, 0, 149, 149, 149, 0
]);
}
#[test]
fn test_image_to_file() {
let img = RgbImage::from_file("datasets/testing/tree.png").unwrap();
assert!(img.to_file("/tmp/ab.jpg"));
assert!(!img.to_file("datasets/nulldir/ab.jpg"));
}
#[test]
fn test_font() {
let mut img = RgbImage::new(300, 300);
let f = vec![
Font::new(FontFace::CvFontHersheySimplex),
Font::new(FontFace::CvFontHersheyComplex),
Font::new(FontFace::CvFontHersheyComplexSmall),
Font::new(FontFace::CvFontHersheyDuplex),
Font::new(FontFace::CvFontHersheyPlain),
Font::new(FontFace::CvFontHersheyScriptComplex),
Font::new(FontFace::CvFontHersheyScriptSimplex),
Font::new(FontFace::CvFontHersheyTriplex),
];
for i in 0..8 {
img.draw_text("hallo", 10, i * 20 + 20, f.get(i).unwrap());
}
img.to_file("/tmp/blabla.jpg");
}
}