├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── gray_put.rs └── red_put.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | 6 | # Ctags 7 | tags 8 | tags.lock 9 | tags.temp 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndarray-image" 3 | version = "0.3.1-alpha.0" 4 | authors = ["Geordon Worley "] 5 | edition = "2018" 6 | description = "Zero-copy conversion between ndarray and image crates" 7 | keywords = ["ndarray", "image", "conversion"] 8 | categories = ["multimedia::images"] 9 | repository = "https://github.com/rust-cv/ndarray-image" 10 | documentation = "https://docs.rs/ndarray-image/" 11 | license = "MIT" 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | image = { version = "0.23.12", default-features = false } 16 | ndarray = { version = "0.15.3", default-features = false } 17 | 18 | [dev-dependencies] 19 | structopt = "0.3.21" 20 | image = "0.23.12" 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 rust-photogrammetry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndarray-image 2 | 3 | [![Crates.io][ci]][cl] ![MIT/Apache][li] [![docs.rs][di]][dl] ![LoC][lo] 4 | 5 | [ci]: https://img.shields.io/crates/v/ndarray-image.svg 6 | [cl]: https://crates.io/crates/ndarray-image/ 7 | 8 | [li]: https://img.shields.io/crates/l/specs.svg?maxAge=2592000 9 | 10 | [di]: https://docs.rs/ndarray-image/badge.svg 11 | [dl]: https://docs.rs/ndarray-image/ 12 | 13 | [lo]: https://tokei.rs/b1/github/rust-cv/ndarray-image?category=code 14 | 15 | Allows conversion between ndarray's types and image's types 16 | 17 | ## Deprecated 18 | 19 | WARNING: This crate is currently deprecated in favor of https://github.com/rust-cv/nshare. Use `nshare` instead. 20 | This crate may eventually be repurposed, or if someone else wants to take the name, just reach out on the Rust CV Discord. 21 | 22 | This crate allows zero-copy conversion between `ArrayView` from `ndarray` and `ImageBuffer` from `image`. 23 | 24 | ## Output of `red_put` example 25 | 26 | ![red_put](http://vadixidav.github.io/ndarray-image/red_put.png) 27 | -------------------------------------------------------------------------------- /examples/gray_put.rs: -------------------------------------------------------------------------------- 1 | use ndarray::s; 2 | use ndarray_image::{open_gray_image, save_gray_image}; 3 | use std::path::PathBuf; 4 | use structopt::StructOpt; 5 | 6 | #[derive(Debug, StructOpt)] 7 | #[structopt(name = "image", about = "Loads image and puts red dots on it")] 8 | struct Opt { 9 | /// File to put red dots on 10 | #[structopt(parse(from_os_str))] 11 | file: PathBuf, 12 | /// Output file with red dots 13 | #[structopt(parse(from_os_str))] 14 | output: PathBuf, 15 | } 16 | 17 | fn main() { 18 | let opt = Opt::from_args(); 19 | let mut image = open_gray_image(opt.file).expect("unable to open input image"); 20 | for n in image.slice_mut(s![..;10, ..;2]) { 21 | *n = 255; 22 | } 23 | save_gray_image(opt.output, image.view()).expect("failed to write output"); 24 | } 25 | -------------------------------------------------------------------------------- /examples/red_put.rs: -------------------------------------------------------------------------------- 1 | use ndarray::s; 2 | use ndarray_image::{open_image, save_image, Colors}; 3 | use std::path::PathBuf; 4 | use structopt::StructOpt; 5 | 6 | #[derive(Debug, StructOpt)] 7 | #[structopt(name = "image", about = "Loads image and puts red dots on it")] 8 | struct Opt { 9 | /// File to put red dots on 10 | #[structopt(parse(from_os_str))] 11 | file: PathBuf, 12 | /// Output file with red dots 13 | #[structopt(parse(from_os_str))] 14 | output: PathBuf, 15 | } 16 | 17 | fn main() { 18 | let opt = Opt::from_args(); 19 | let mut image = open_image(opt.file, Colors::Rgb).expect("unable to open input image"); 20 | for n in image.slice_mut(s![..;10, ..;2, 0]) { 21 | *n = 255; 22 | } 23 | save_image(opt.output, image.view(), Colors::Rgb).expect("failed to write output"); 24 | } 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use image::{ 2 | Bgr, Bgra, ImageBuffer, ImageError, ImageResult, Luma, LumaA, Pixel, Primitive, Rgb, Rgba, 3 | }; 4 | use ndarray::ShapeBuilder; 5 | use ndarray::{Array2, Array3, ArrayView, ArrayViewMut, Ix2, Ix3}; 6 | use std::ops::Deref; 7 | use std::path::Path; 8 | 9 | /// This newtype struct can wrap an image from either the `ndarray` or `image` crates to 10 | /// automatically allow them to be turned `into()` the equivalents in the other crate. 11 | /// This works without copying. 12 | pub struct NdImage(pub T); 13 | 14 | pub type NdGray<'a, A = u8> = ArrayView<'a, A, Ix2>; 15 | pub type NdGrayMut<'a, A = u8> = ArrayViewMut<'a, A, Ix2>; 16 | pub type NdColor<'a, A = u8> = ArrayView<'a, A, Ix3>; 17 | pub type NdColorMut<'a, A = u8> = ArrayViewMut<'a, A, Ix3>; 18 | 19 | pub type ImgLuma<'a, A = u8> = ImageBuffer, &'a [A]>; 20 | pub type ImgLumaA<'a, A = u8> = ImageBuffer, &'a [A]>; 21 | pub type ImgRgb<'a, A = u8> = ImageBuffer, &'a [A]>; 22 | pub type ImgRgba<'a, A = u8> = ImageBuffer, &'a [A]>; 23 | pub type ImgBgr<'a, A = u8> = ImageBuffer, &'a [A]>; 24 | pub type ImgBgra<'a, A = u8> = ImageBuffer, &'a [A]>; 25 | 26 | pub enum Colors { 27 | Luma, 28 | LumaA, 29 | Rgb, 30 | Rgba, 31 | Bgr, 32 | Bgra, 33 | } 34 | 35 | /// Opens a gray image using the `image` crate and loads it into a 2d array. 36 | /// This performs a copy. 37 | pub fn open_gray_image(path: impl AsRef) -> ImageResult> { 38 | let image = image::open(path)?; 39 | let image = image.to_luma8(); 40 | let image: NdGray = NdImage(&image).into(); 41 | Ok(image.to_owned()) 42 | } 43 | 44 | /// Opens a color image using the `image` crate and loads it into a 3d array. 45 | /// This performs a copy. 46 | pub fn open_image(path: impl AsRef, colors: Colors) -> ImageResult> { 47 | let image = image::open(path)?; 48 | let image = match colors { 49 | Colors::Luma => { 50 | let image = image.to_luma8(); 51 | let image: NdColor = NdImage(&image).into(); 52 | image.to_owned() 53 | } 54 | Colors::LumaA => { 55 | let image = image.to_luma_alpha8(); 56 | let image: NdColor = NdImage(&image).into(); 57 | image.to_owned() 58 | } 59 | Colors::Rgb => { 60 | let image = image.to_rgb8(); 61 | let image: NdColor = NdImage(&image).into(); 62 | image.to_owned() 63 | } 64 | Colors::Rgba => { 65 | let image = image.to_rgba8(); 66 | let image: NdColor = NdImage(&image).into(); 67 | image.to_owned() 68 | } 69 | Colors::Bgr => { 70 | let image = image.to_bgr8(); 71 | let image: NdColor = NdImage(&image).into(); 72 | image.to_owned() 73 | } 74 | Colors::Bgra => { 75 | let image = image.to_bgra8(); 76 | let image: NdColor = NdImage(&image).into(); 77 | image.to_owned() 78 | } 79 | }; 80 | Ok(image) 81 | } 82 | 83 | /// Saves a gray image using the `image` crate from a 3d array. 84 | pub fn save_gray_image(path: impl AsRef, image: NdGray<'_, u8>) -> ImageResult<()> { 85 | let image: Option = NdImage(image.view()).into(); 86 | let image = image.ok_or_else(|| { 87 | ImageError::Decoding(image::error::DecodingError::new( 88 | image::error::ImageFormatHint::Unknown, 89 | "non-contiguous ndarray Array", 90 | )) 91 | })?; 92 | image.save(path)?; 93 | Ok(()) 94 | } 95 | 96 | /// Saves a color image using the `image` crate from a 3d array. 97 | pub fn save_image( 98 | path: impl AsRef, 99 | image: NdColor<'_, u8>, 100 | colors: Colors, 101 | ) -> ImageResult<()> { 102 | match colors { 103 | Colors::Luma => { 104 | let image: Option = NdImage(image.view()).into(); 105 | let image = image.ok_or_else(|| { 106 | ImageError::Decoding(image::error::DecodingError::new( 107 | image::error::ImageFormatHint::Unknown, 108 | "non-contiguous ndarray Array", 109 | )) 110 | })?; 111 | image.save(path)?; 112 | } 113 | Colors::LumaA => { 114 | let image: Option = NdImage(image.view()).into(); 115 | let image = image.ok_or_else(|| { 116 | ImageError::Decoding(image::error::DecodingError::new( 117 | image::error::ImageFormatHint::Unknown, 118 | "non-contiguous ndarray Array", 119 | )) 120 | })?; 121 | image.save(path)?; 122 | } 123 | Colors::Rgb => { 124 | let image: Option = NdImage(image.view()).into(); 125 | let image = image.ok_or_else(|| { 126 | ImageError::Decoding(image::error::DecodingError::new( 127 | image::error::ImageFormatHint::Unknown, 128 | "non-contiguous ndarray Array", 129 | )) 130 | })?; 131 | image.save(path)?; 132 | } 133 | Colors::Rgba => { 134 | let image: Option = NdImage(image.view()).into(); 135 | let image = image.ok_or_else(|| { 136 | ImageError::Decoding(image::error::DecodingError::new( 137 | image::error::ImageFormatHint::Unknown, 138 | "non-contiguous ndarray Array", 139 | )) 140 | })?; 141 | image.save(path)?; 142 | } 143 | Colors::Bgr => { 144 | let image: Option = NdImage(image.view()).into(); 145 | let image = image.ok_or_else(|| { 146 | ImageError::Decoding(image::error::DecodingError::new( 147 | image::error::ImageFormatHint::Unknown, 148 | "non-contiguous ndarray Array", 149 | )) 150 | })?; 151 | image.save(path)?; 152 | } 153 | Colors::Bgra => { 154 | let image: Option = NdImage(image.view()).into(); 155 | let image = image.ok_or_else(|| { 156 | ImageError::Decoding(image::error::DecodingError::new( 157 | image::error::ImageFormatHint::Unknown, 158 | "non-contiguous ndarray Array", 159 | )) 160 | })?; 161 | image.save(path)?; 162 | } 163 | } 164 | Ok(()) 165 | } 166 | 167 | /// Turn grayscale images into 2d array views. 168 | impl<'a, C, A: 'static> Into> for NdImage<&'a ImageBuffer, C>> 169 | where 170 | A: Primitive, 171 | C: Deref + AsRef<[A]>, 172 | { 173 | fn into(self) -> NdGray<'a, A> { 174 | let NdImage(image) = self; 175 | let (width, height) = image.dimensions(); 176 | let (width, height) = (width as usize, height as usize); 177 | let slice: &'a [A] = unsafe { std::mem::transmute(image.as_flat_samples().as_slice()) }; 178 | ArrayView::from_shape((height, width).strides((width, 1)), slice).unwrap() 179 | } 180 | } 181 | 182 | /// Turn grayscale images into mutable 2d array views. 183 | impl<'a, C, A: 'static> Into> for NdImage<&'a mut ImageBuffer, C>> 184 | where 185 | A: Primitive, 186 | C: Deref + AsRef<[A]>, 187 | { 188 | fn into(self) -> NdGrayMut<'a, A> { 189 | let NdImage(image) = self; 190 | let (width, height) = image.dimensions(); 191 | let (width, height) = (width as usize, height as usize); 192 | #[allow(clippy::transmute_ptr_to_ref)] 193 | let slice: &'a mut [A] = 194 | unsafe { std::mem::transmute(image.as_flat_samples().as_slice() as *const [A]) }; 195 | ArrayViewMut::from_shape((height, width).strides((width, 1)), slice).unwrap() 196 | } 197 | } 198 | 199 | /// Turn arbitrary images into 3d array views with one dimension for the color channel. 200 | impl<'a, C, P: 'static, A: 'static> Into> for NdImage<&'a ImageBuffer> 201 | where 202 | A: Primitive, 203 | P: Pixel, 204 | C: Deref + AsRef<[A]>, 205 | { 206 | fn into(self) -> NdColor<'a, A> { 207 | let NdImage(image) = self; 208 | let (width, height) = image.dimensions(); 209 | let (width, height) = (width as usize, height as usize); 210 | let channels = P::CHANNEL_COUNT as usize; 211 | let slice: &'a [A] = unsafe { std::mem::transmute(image.as_flat_samples().as_slice()) }; 212 | ArrayView::from_shape( 213 | (height, width, channels).strides((width * channels, channels, 1)), 214 | slice, 215 | ) 216 | .unwrap() 217 | } 218 | } 219 | 220 | /// Turn arbitrary images into mutable 3d array views with one dimension for the color channel. 221 | impl<'a, C, P: 'static, A: 'static> Into> for NdImage<&'a mut ImageBuffer> 222 | where 223 | A: Primitive, 224 | P: Pixel, 225 | C: Deref + AsRef<[A]>, 226 | { 227 | fn into(self) -> NdColorMut<'a, A> { 228 | let NdImage(image) = self; 229 | let (width, height) = image.dimensions(); 230 | let (width, height) = (width as usize, height as usize); 231 | let channels = P::CHANNEL_COUNT as usize; 232 | #[allow(clippy::transmute_ptr_to_ref)] 233 | let slice: &'a mut [A] = 234 | unsafe { std::mem::transmute(image.as_flat_samples().as_slice() as *const [A]) }; 235 | ArrayViewMut::from_shape( 236 | (height, width, channels).strides((width * channels, channels, 1)), 237 | slice, 238 | ) 239 | .unwrap() 240 | } 241 | } 242 | 243 | /// Turn 2d `ArrayView` into a `Luma` image. 244 | /// 245 | /// Can fail if the `ArrayView` is not contiguous. 246 | impl<'a, A: 'static> Into>> for NdImage> 247 | where 248 | A: Primitive, 249 | { 250 | fn into(self) -> Option> { 251 | let NdImage(image) = self; 252 | if let [height, width] = *image.shape() { 253 | image.to_slice().map(|slice| { 254 | ImageBuffer::from_raw(width as u32, height as u32, slice) 255 | .expect("failed to create image from slice") 256 | }) 257 | } else { 258 | unreachable!("the ndarray had more than 2 dimensions"); 259 | } 260 | } 261 | } 262 | 263 | /// Turn 3d `ArrayView` into a `Luma` image. 264 | /// 265 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 266 | impl<'a, A: 'static> Into>> for NdImage> 267 | where 268 | A: Primitive, 269 | { 270 | fn into(self) -> Option> { 271 | let NdImage(image) = self; 272 | if let [height, width, 1] = *image.shape() { 273 | image.to_slice().map(|slice| { 274 | ImageBuffer::from_raw(width as u32, height as u32, slice) 275 | .expect("failed to create image from raw vec") 276 | }) 277 | } else { 278 | None 279 | } 280 | } 281 | } 282 | 283 | /// Turn 3d `ArrayView` into a `LumaA` image. 284 | /// 285 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 286 | impl<'a, A: 'static> Into>> for NdImage> 287 | where 288 | A: Primitive, 289 | { 290 | fn into(self) -> Option> { 291 | let NdImage(image) = self; 292 | if let [height, width, 2] = *image.shape() { 293 | image.to_slice().map(|slice| { 294 | ImageBuffer::from_raw(width as u32, height as u32, slice) 295 | .expect("failed to create image from raw vec") 296 | }) 297 | } else { 298 | None 299 | } 300 | } 301 | } 302 | 303 | /// Turn 3d `ArrayView` into a `Rgb` image. 304 | /// 305 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 306 | impl<'a, A: 'static> Into>> for NdImage> 307 | where 308 | A: Primitive, 309 | { 310 | fn into(self) -> Option> { 311 | let NdImage(image) = self; 312 | if let [height, width, 3] = *image.shape() { 313 | image.to_slice().map(|slice| { 314 | ImageBuffer::from_raw(width as u32, height as u32, slice) 315 | .expect("failed to create image from raw vec") 316 | }) 317 | } else { 318 | None 319 | } 320 | } 321 | } 322 | 323 | /// Turn 3d `ArrayView` into a `Rgba` image. 324 | /// 325 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 326 | impl<'a, A: 'static> Into>> for NdImage> 327 | where 328 | A: Primitive, 329 | { 330 | fn into(self) -> Option> { 331 | let NdImage(image) = self; 332 | if let [height, width, 4] = *image.shape() { 333 | image.to_slice().map(|slice| { 334 | ImageBuffer::from_raw(width as u32, height as u32, slice) 335 | .expect("failed to create image from raw vec") 336 | }) 337 | } else { 338 | None 339 | } 340 | } 341 | } 342 | 343 | /// Turn 3d `ArrayView` into a `Bgr` image. 344 | /// 345 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 346 | impl<'a, A: 'static> Into>> for NdImage> 347 | where 348 | A: Primitive, 349 | { 350 | fn into(self) -> Option> { 351 | let NdImage(image) = self; 352 | if let [height, width, 3] = *image.shape() { 353 | image.to_slice().map(|slice| { 354 | ImageBuffer::from_raw(width as u32, height as u32, slice) 355 | .expect("failed to create image from raw vec") 356 | }) 357 | } else { 358 | None 359 | } 360 | } 361 | } 362 | 363 | /// Turn 3d `ArrayView` into a `Bgra` image. 364 | /// 365 | /// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels. 366 | impl<'a, A: 'static> Into>> for NdImage> 367 | where 368 | A: Primitive, 369 | { 370 | fn into(self) -> Option> { 371 | let NdImage(image) = self; 372 | if let [height, width, 4] = *image.shape() { 373 | image.to_slice().map(|slice| { 374 | ImageBuffer::from_raw(width as u32, height as u32, slice) 375 | .expect("failed to create image from raw vec") 376 | }) 377 | } else { 378 | None 379 | } 380 | } 381 | } 382 | --------------------------------------------------------------------------------