Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
# Conflicts:
#	Cargo.toml
  • Loading branch information
tiffany1618 committed Mar 2, 2021
2 parents 81d1603 + 802194a commit 6d16831
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 27 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@ categories = ["multimedia::images"]
[dependencies]
image = "0.23.12"
rulinalg = "0.4.2"
rayon = "1.5.0"
#arrayfire = "3.8.0"
rayon = "1.5.0"
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

A Rust image processing library.

## Features
* Multithreading support for some functions (indicated by the `_par` suffix in the function name) via
[rayon](https://github.com/rayon-rs/rayon)

## Supported Image Formats

`imgproc-rs` uses the i/o functions provided in the [`image`](https://github.com/image-rs/image) crate. A list of
Expand All @@ -28,3 +32,86 @@ fn main() {
io::write(&img, "path/to/save_image.png").unwrap();
}
```

### Creating Images

Images can be created from existing vectors, slices, vectors of vectors, and vectors of slices. A few examples are shown
below.

```rust
use imgproc_rs::image::{Image, ImageInfo};

fn main() {
let vec = vec![1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12];

// Create an image from a slice
let img_slice = Image::from_slice(2, 2, 3, false, &vec);

// Create an image from a vector
let img_vec = Image::from_vec(2, 2, 3, false, vec);

// Create a blank (black) image
let img_blank: Image<u8> = Image::blank(ImageInfo::new(2, 2, 3, false));

// Create an empty image
let img_empty: Image<u8> = Image::empty(ImageInfo::new(2, 2, 3, false));
}
```

### Getting Image Information
```rust
use imgproc_rs::io;
use imgproc_rs::image::{Image, ImageInfo};

fn main() {
let img = io::read("path/to/some_image.png").unwrap();

// Get width and height of image
let (width, height) = img.info().wh();

// Get width, height, and channels of image
let (width, height, channels) = img.info().whc();

// Get width, height, channels, and alpha of image
let (width, height, channels, alpha) = img.info().whca();

/* Print image information
* Example output:
*
* width: 2
* height: 2
* channels: 3
* alpha: false
*
*/
println!("{}", img.info());
}
```

### Getting/Setting Image Pixels

Image pixels can be accessed using either 1D or 2D indexing. 1D indexing reads the image data row by row from left to
right, starting in the upper left corner of the image. 2D coordinates start at zero in the upper left corner of the
image and increase downwards and to the right.

```rust
use imgproc_rs::io;
use imgproc_rs::image::{Image, BaseImage};

fn main() {
let img = io::read("path/to/some_image.png").unwrap();

// Set an image pixel using a 1D index
img.set_pixel_indexed(0, &[1, 1, 1]);

// Get an image pixel using a 1D index
let pixel_1d = &img[0];

// Set an image pixel using 2D coordinates
img.set_pixel(1, 1, &[1, 1, 1]);

// Get an image pixel using 2D coordinates
let pixel_2d = img.get_pixel(1, 1);
}
```
16 changes: 16 additions & 0 deletions src/error/messages.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
use crate::error::{ImgProcResult, ImgProcError};
use crate::image::Number;

pub(crate) fn check_channels(channels: u8, len: usize) {
if channels != len as u8 {
panic!("invalid pixel length: the number of channels is {}, \
but the pixel length is {}", channels, len);
}
}

pub(crate) fn check_xy(x: u32, y: u32, width: u32, height: u32) {
if x >= width {
panic!("index out of bounds: the width is {}, but the x index is {}", width, x)
}
if y >= height {
panic!("index out of bounds: the height is {}, but the y index is {}", height, y)
}
}

pub(crate) fn check_odd<T: Number>(val: T, name: &str) -> ImgProcResult<()> {
if val % 2.into() == 0.into() {
return Err(ImgProcError::InvalidArgError(format!("{} must be odd", name)));
Expand Down
116 changes: 91 additions & 25 deletions src/image/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,59 @@
//! A module for the core image structs and traits
//!
//! # Examples
//! ```rust
//! # use imgproc_rs::error::ImgIoResult;
//!
//! # fn main() {
//! use imgproc_rs::image::{Image, BaseImage, ImageInfo};
//!
//! let vec = vec![1, 2, 3, 4, 5, 6,
//! 7, 8, 9, 10, 11, 12];
//!
//! // Create an image from a slice
//! let img_slice = Image::from_slice(2, 2, 3, false, &vec);
//!
//! // Create an image from a vector
//! let img_vec = Image::from_vec(2, 2, 3, false, vec);
//!
//! // Create a blank (black) image
//! let mut img_blank: Image<u8> = Image::blank(ImageInfo::new(2, 2, 3, false));
//!
//! // Create an empty image
//! let mut img_empty: Image<u8> = Image::empty(ImageInfo::new(2, 2, 3, false));
//!
//! // Get width and height of image
//! let (width, height) = img_slice.info().wh();
//!
//! // Get width, height, and channels of image
//! let (width, height, channels) = img_slice.info().whc();
//!
//! // Get width, height, channels, and alpha of image
//! let (width, height, channels, alpha) = img_slice.info().whca();
//!
//! // Set and get an image pixel using a 1D index (reads the image data row by row from left to
//! // right, starting in the upper left corner of the image)
//! img_blank.set_pixel_indexed(0, &[1, 1, 1]);
//! let pixel_1d = &img_blank[0];
//!
//! // Set and get an image pixel using 2D coordinates (coordinates start at zero in the upper
//! // left corner of the image and increase downwards and to the right)
//! img_blank.set_pixel(1, 1, &[1, 1, 1]);
//! let pixel_2d = img_vec.get_pixel(1, 1);
//!
//! /* Print image information
//! * Example output:
//! *
//! * width: 2
//! * height: 2
//! * channels: 3
//! * alpha: false
//! *
//! */
//! println!("{}", img_slice.info());
//!
//! # }
//! ```

pub use self::sub_image::*;
pub use self::pixel::*;
Expand All @@ -10,6 +65,8 @@ mod pixel;
mod from_impl;
mod pixel_iter;

use crate::error;

/// A struct representing an image
#[derive(Debug, Clone, PartialEq)]
pub struct Image<T: Number> {
Expand Down Expand Up @@ -73,6 +130,10 @@ pub trait BaseImage<T: Number> {
fn info(&self) -> ImageInfo;

/// Returns a slice representing the pixel located at `(x, y)`
///
/// # Panics
///
/// Panics if `x` or `y` is out of bounds
fn get_pixel(&self, x: u32, y: u32) -> &[T];
}

Expand Down Expand Up @@ -208,12 +269,7 @@ impl<T: Number> Image<T> {
///
/// Panics if `x` or `y` is out of bounds
pub fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut [T] {
if x >= self.info.width {
panic!("index out of bounds: the width is {}, but the x index is {}", self.info.width, x)
}
if y >= self.info.height {
panic!("index out of bounds: the height is {}, but the y index is {}", self.info.height, y)
}
error::check_xy(x, y, self.info.width, self.info.height);

let start = self.index(x, y);
&mut self.data[start..(start + self.info.channels as usize)]
Expand All @@ -228,7 +284,13 @@ impl<T: Number> Image<T> {

/// Returns a `SubImage<T>` representing the part of the image of width `width` and height
/// `height`, with upper left hand corner located at `(x, y)`
///
/// # Panics
///
/// Panics if `x` or `y` is out of bounds
pub fn get_subimage(&self, x: u32, y: u32, width: u32, height: u32) -> SubImage<T> {
error::check_xy(x, y, self.info.width, self.info.height);

let mut data = Vec::new();

for i in x..(x + width) {
Expand All @@ -243,7 +305,13 @@ impl<T: Number> Image<T> {
/// Returns a `SubImage<T>` representing the row or column of pixels of length `size` centered at
/// `(x, y)`. If `is_vert` is `true`, returns the column; otherwise, returns the row.
/// Uses clamp padding for edge pixels (edge pixels are repeated indefinitely)
///
/// # Panics
///
/// Panics if `x` or `y` is out of bounds
pub fn get_neighborhood_1d(&self, x: u32, y: u32, size: u32, is_vert: bool) -> SubImage<T> {
error::check_xy(x, y, self.info.width, self.info.height);

let mut data = Vec::new();

if is_vert {
Expand Down Expand Up @@ -273,7 +341,13 @@ impl<T: Number> Image<T> {

/// Returns a `SubImage<T>` representing the "square" of pixels of side length `size` centered
/// at `(x, y)`. Uses clamp padding for edge pixels (edge pixels are repeated indefinitely)
///
/// # Panics
///
/// Panics if `x` or `y` is out of bounds
pub fn get_neighborhood_2d(&self, x: u32, y: u32, size: u32) -> SubImage<T> {
error::check_xy(x, y, self.info.width, self.info.height);

let start_x = (x as i32) - (size as i32) / 2;
let start_y = (y as i32) - (size as i32) / 2;

Expand All @@ -297,12 +371,11 @@ impl<T: Number> Image<T> {
///
/// # Panics
///
/// Panics if the length of `pixel` is not equal to the number of channels in the image
/// Panics if the length of `pixel` is not equal to the number of channels in the image or `x`
/// or `y` is out of bounds
pub fn set_pixel(&mut self, x: u32, y: u32, pixel: &[T]) {
if pixel.len() != self.info.channels as usize {
panic!("invalid pixel length: the number of channels is {}, \
but the pixel length is {}", self.info.channels, pixel.len())
}
error::check_channels(self.info.channels, pixel.len());
error::check_xy(x, y, self.info.width, self.info.height);

let start = self.index(x, y);
for i in 0..(self.info.channels as usize) {
Expand All @@ -311,7 +384,13 @@ impl<T: Number> Image<T> {
}

/// Replaces the pixel at index `index` with `pixel`
///
/// # Panics
///
/// Panics if the length of `pixel` is not equal to the number of channels in the image
pub fn set_pixel_indexed(&mut self, index: usize, pixel: &[T]) {
error::check_channels(self.info.channels, pixel.len());

let start = index * self.info.channels as usize;
for i in 0..(self.info.channels as usize) {
self.data[start + i] = pixel[i];
Expand Down Expand Up @@ -471,12 +550,7 @@ impl<T: Number> BaseImage<T> for Image<T> {
}

fn get_pixel(&self, x: u32, y: u32) -> &[T] {
if x >= self.info.width {
panic!("index out of bounds: the width is {}, but the x index is {}", self.info.width, x)
}
if y >= self.info.height {
panic!("index out of bounds: the height is {}, but the y index is {}", self.info.height, y)
}
error::check_xy(x, y, self.info.width, self.info.height);

&self[(y * self.info.width + x) as usize]
}
Expand All @@ -486,21 +560,13 @@ impl<T: Number> std::ops::Index<usize> for Image<T> {
type Output = [T];

fn index(&self, i: usize) -> &Self::Output {
if i >= self.info.size() as usize {
panic!("index out of bounds: the len is {}, but the index is {}", self.info.size(), i)
}

let start = i * (self.info.channels as usize);
&self.data[start..(start + self.info.channels as usize)]
}
}

impl<T: Number> std::ops::IndexMut<usize> for Image<T> {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
if i >= self.info.size() as usize {
panic!("index out of bounds: the len is {}, but the index is {}", self.info.size(), i)
}

let start = i * (self.info.channels as usize);
&mut self.data[start..(start + self.info.channels as usize)]
}
Expand Down
3 changes: 3 additions & 0 deletions src/image/sub_image.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::image::{Number, ImageInfo, BaseImage};
use crate::error::check_xy;

/// A struct representing a part of an image
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -39,6 +40,8 @@ impl<T: Number> BaseImage<T> for SubImage<'_, T> {
}

fn get_pixel(&self, x: u32, y: u32) -> &[T] {
check_xy(x, y, self.info.width, self.info.height);

&self[(y * self.info.width + x) as usize]
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pub mod tone;
pub mod filter;
pub mod transform;
pub mod convert;
pub mod morphology;
Loading

0 comments on commit 6d16831

Please sign in to comment.