From 4e8baa641eef3f50ee7a322d23eb927cfe33c4ad Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Thu, 6 Apr 2023 16:21:13 +0900 Subject: [PATCH 1/1] Import imgref 1.9.4 --- .cargo_vcs_info.json | 6 + .github/workflows/cargo.yml | 31 ++ .gitignore | 2 + Cargo.toml | 39 ++ Cargo.toml.orig | 19 + LICENSE | 121 ++++++ README.md | 65 ++++ benches/iterbench.rs | 41 +++ imgref.png | Bin 0 -> 44923 bytes src/iter.rs | 338 +++++++++++++++++ src/lib.rs | 877 ++++++++++++++++++++++++++++++++++++++++++++ src/ops.rs | 116 ++++++ src/traits.rs | 148 ++++++++ 13 files changed, 1803 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 .github/workflows/cargo.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 benches/iterbench.rs create mode 100644 imgref.png create mode 100644 src/iter.rs create mode 100644 src/lib.rs create mode 100644 src/ops.rs create mode 100644 src/traits.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..f5cda61 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "40f08c14b437cb4aab2e37b11889a4245bcd655f" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml new file mode 100644 index 0000000..fb0ee84 --- /dev/null +++ b/.github/workflows/cargo.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + pull_request: + branches: + # Branches from forks have the form 'user:branch-name' so we only run + # this job on pull_request events for branches that look like fork + # branches. Without this we would end up running this job twice for non + # forked PRs, once for the push and then once for opening the PR. + - '**:**' + +jobs: + test: + name: Test + strategy: + matrix: + rust: + - stable + - nightly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - uses: actions-rs/cargo@v1 + with: + command: test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..318febb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "imgref" +version = "1.9.4" +authors = ["Kornel Lesiński "] +description = "A trivial struct for interchange of 2d-dimensional pixel buffers with width, height & stride" +homepage = "https://lib.rs/crates/imgref" +documentation = "https://docs.rs/imgref/" +readme = "README.md" +keywords = [ + "interoperability", + "stride", + "image", + "frame", + "vec2d", +] +categories = [ + "rust-patterns", + "multimedia::images", +] +license = "CC0-1.0 OR Apache-2.0" +repository = "https://github.com/kornelski/imgref" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[badges.maintenance] +status = "actively-developed" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..e0b9a98 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,19 @@ +[package] +authors = ["Kornel Lesiński "] +categories = ["rust-patterns", "multimedia::images"] +description = "A trivial struct for interchange of 2d-dimensional pixel buffers with width, height & stride" +documentation = "https://docs.rs/imgref/" +homepage = "https://lib.rs/crates/imgref" +keywords = ["interoperability", "stride", "image", "frame", "vec2d"] +license = "CC0-1.0 OR Apache-2.0" +name = "imgref" +readme = "README.md" +repository = "https://github.com/kornelski/imgref" +version = "1.9.4" +edition = "2018" + +[badges] +maintenance = { status = "actively-developed" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2703ac --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# 2D slice of a `Vec` + +This is a lowest common denominator struct for working with image fragments in Rust code. It represents a 2-dimensional vector and rectangular slices of it. + +* [API Reference](https://docs.rs/imgref) +* [Installation](https://crates.io/crates/imgref) + +In graphics code it's very common to pass `width` and `height` along with a `Vec` of pixels — all as separate arguments. This gets very repetitive, and can lead to errors. + +This crate is a simple struct that adds dimensions to the underlying buffer. This makes it easier to correctly keep track of the image size and allows passing images with just one function argument instead three or four. + +Additionally, it has a concept of a `stride`, which allows defining sub-regions of images without copying, as well as padding (e.g. buffers for video frames may require to be a multiple of 8, regardless of logical image size). + +For convenience, it implements iterators for pixels/rows and indexing with `img[(x,y)]`. + +```rust +use imgref::*; + +fn main() { + let img = Img::new(vec![0; 1000], 50, 20); // 1000 pixels of a 50×20 image + + let new_image = some_image_processing_function(img.as_ref()); // Use imgvec.as_ref() instead of &imgvec for better efficiency + + println!("New size is {}×{}", new_image.width(), new_image.height()); + println!("And the top left pixel is {:?}", new_image[(0u32,0u32)]); + + let first_row_slice = &new_image[0]; + + for row in new_image.rows() { + … + } + for px in new_image.pixels() { + … + } + + // slice (x, y, width, height) by reference - no copy! + let fragment = img.sub_image(5, 5, 15, 15); + + // create a vec of pixels without stride, for compatibility with software + // that expects pixels without any "gaps" + let (vec, width, height) = fragment.to_contiguous_buf(); +} +``` + +## Type aliases + +Illustration: stride is width of the whole buffer. + +These are described in [more detail in the reference](https://docs.rs/imgref). + +### `ImgVec` + +It owns its pixels (held in a `Vec`). It's analogous to a 2-dimensional `Vec`. Use this type to create and return new images from functions. + +Don't use `&ImgVec`. Instead call `ImgVec.as_ref()` to get a reference (`ImgRef`) from it (explicit `.as_ref()` call is required, because Rust doesn't support [custom conversions](https://github.com/rust-lang/rfcs/pull/1524) yet.) + +### `ImgRef` + +`ImgRef` is a reference to pixels owned by some other `ImgVec` or a slice. It's analogous to a 2-dimensional `&[]`. + +Use this type to accept read-only images as arguments in functions. Note that `ImgRef` is a `Copy` type. Pass `ImgRef`, and *not* `&ImgRef`. + +### Requirements + +* Latest stable Rust (1.42+) diff --git a/benches/iterbench.rs b/benches/iterbench.rs new file mode 100644 index 0000000..3f771a7 --- /dev/null +++ b/benches/iterbench.rs @@ -0,0 +1,41 @@ +#![feature(test)] + +extern crate test; +use imgref::*; +use test::Bencher; + +#[bench] +fn iter_count(bench: &mut Bencher) { + let img = Img::new_stride(vec![0x11223344u32; 802*600], 800, 600, 802); + + bench.iter(|| { + img.pixels().count() + }); +} + +#[bench] +fn iter_sum(bench: &mut Bencher) { + let img = Img::new_stride(vec![0x11223344u32; 802*600], 800, 600, 802); + + bench.iter(|| { + img.pixels().map(|p| p as usize).sum::() + }); +} + +#[bench] +fn stride_ignorant_sum(bench: &mut Bencher) { + let img = Img::new_stride(vec![0x11223344u32; 802*600], 800, 600, 802); + + bench.iter(|| { + img.buf().iter().cloned().map(|p| p as usize).sum::() + }); +} + +#[bench] +fn chunked_sum(bench: &mut Bencher) { + let img = Img::new_stride(vec![0x11223344u32; 802*600], 800, 600, 802); + + bench.iter(|| { + img.buf().chunks(img.stride()).flat_map(|row| row[0..img.width()].iter()).cloned().map(|p| p as usize).sum::() + }); +} diff --git a/imgref.png b/imgref.png new file mode 100644 index 0000000000000000000000000000000000000000..f534f6f6d3c7b23abe79442092c41ed75aa60a2c GIT binary patch literal 44923 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

cUKi!gDrXLt0q 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 { + pub(crate) inner: slice::Chunks<'a, T>, + pub(crate) width: usize, +} + +impl<'a, T: 'a> Iterator for RowsIter<'a, T> { + type Item = &'a [T]; + + #[inline] + fn next(&mut self) -> Option { + match self.inner.next() { + Some(s) => { + // guaranteed during creation of chunks iterator + debug_assert!(s.len() >= self.width); + unsafe { + Some(s.get_unchecked(0..self.width)) + } + }, + None => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + match self.inner.nth(n) { + Some(s) => { + // guaranteed during creation of chunks iterator + debug_assert!(s.len() >= self.width); + unsafe { + Some(s.get_unchecked(0..self.width)) + } + }, + None => None, + } + } + + #[inline] + fn count(self) -> usize { + self.inner.count() + } +} + +impl<'a, T> ExactSizeIterator for RowsIter<'a, T> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, T> FusedIterator for RowsIter<'a, T> {} + +impl<'a, T: 'a> DoubleEndedIterator for RowsIter<'a, T> { + #[inline] + fn next_back(&mut self) -> Option { + match self.inner.next_back() { + Some(s) => { + // guaranteed during creation of chunks iterator + debug_assert!(s.len() >= self.width); + unsafe { + Some(s.get_unchecked(0..self.width)) + } + }, + None => None, + } + } +} + +/// Rows of the image. Call `Img.rows_mut()` to create it. +/// +/// Each element is a slice `width` pixels wide. Ignores padding, if there's any. +#[derive(Debug)] +#[must_use] +pub struct RowsIterMut<'a, T> { + pub(crate) width: usize, + pub(crate) inner: slice::ChunksMut<'a, T>, +} + +impl<'a, T: 'a> Iterator for RowsIterMut<'a, T> { + type Item = &'a mut [T]; + + #[inline] + fn next(&mut self) -> Option { + match self.inner.next() { + Some(s) => Some(&mut s[0..self.width]), + None => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + match self.inner.nth(n) { + Some(s) => Some(&mut s[0..self.width]), + None => None, + } + } + + #[inline] + fn count(self) -> usize { + self.inner.count() + } +} + +impl<'a, T> ExactSizeIterator for RowsIterMut<'a, T> {} +impl<'a, T> FusedIterator for RowsIterMut<'a, T> {} + +impl<'a, T: 'a> DoubleEndedIterator for RowsIterMut<'a, T> { + #[inline] + fn next_back(&mut self) -> Option { + match self.inner.next_back() { + Some(s) => Some(&mut s[0..self.width]), + None => None, + } + } +} + + +/// Iterates over pixels in the (sub)image. Call `Img.pixels()` to create it. +/// +/// Ignores padding, if there's any. +#[must_use] +pub struct PixelsIter<'a, T: Copy> { + inner: PixelsRefIter<'a, T> +} + +impl<'a, T: Copy + 'a> PixelsIter<'a, T> { + #[inline(always)] + pub(crate) fn new(img: super::ImgRef<'a, T>) -> Self { + Self { + inner: PixelsRefIter::new(img) + } + } +} + +impl<'a, T: Copy + 'a> Iterator for PixelsIter<'a, T> { + type Item = T; + + #[inline(always)] + fn next(&mut self) -> Option { + self.inner.next().copied() + } +} + +impl<'a, T: Copy> ExactSizeIterator for PixelsIter<'a, T> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +/// Iterates over pixels in the (sub)image. Call `Img.pixels_ref()` to create it. +/// +/// Ignores padding, if there's any. +#[derive(Debug)] +#[must_use] +pub struct PixelsRefIter<'a, T> { + current: *const T, + current_line_end: *const T, + rows_left: usize, + width: NonZeroUsize, + pad: usize, + _dat: PhantomData<&'a [T]>, +} + +unsafe impl Send for PixelsRefIter<'_, T> where T: Send {} +unsafe impl Sync for PixelsRefIter<'_, T> where T: Sync {} + +impl<'a, T: 'a> PixelsRefIter<'a, T> { + #[inline] + pub(crate) fn new(img: super::ImgRef<'a, T>) -> Self { + let width = NonZeroUsize::new(img.width()).expect("width > 0"); + let height = img.height(); + let stride = img.stride(); + assert!(stride >= width.get()); + let pad = stride - width.get(); + debug_assert!(img.buf().len() + stride >= stride * height + width.get(), + "buffer len {} is less than {} (({}+{})x{})", img.buf().len(), + stride * height - pad, width, pad, height); + Self { + current: img.buf().as_ptr(), + current_line_end: img.buf()[width.get()..].as_ptr(), + width, + rows_left: height, + pad, + _dat: PhantomData, + } + } +} + +impl<'a, T: 'a> Iterator for PixelsRefIter<'a, T> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option { + unsafe { + if self.current >= self.current_line_end { + if self.rows_left <= 1 { + return None; + } + self.rows_left -= 1; + self.current = self.current_line_end.add(self.pad); + self.current_line_end = self.current.add(self.width.get()); + } + let px = &*self.current; + self.current = self.current.add(1); + Some(px) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let this_line = unsafe { + self.current_line_end.offset_from(self.current) + }; + debug_assert!(this_line >= 0); + let len = this_line as usize + (self.rows_left - 1) * self.width.get(); + (len, Some(len)) + } +} + +impl<'a, T: Copy> ExactSizeIterator for PixelsRefIter<'a, T> { +} + +/// Iterates over pixels in the (sub)image. Call `Img.pixels_mut()` to create it. +/// +/// Ignores padding, if there's any. +#[derive(Debug)] +#[must_use] +pub struct PixelsIterMut<'a, T> { + current: *mut T, + current_line_end: *mut T, + y: usize, + width: NonZeroUsize, + pad: usize, + _dat: PhantomData<&'a mut [T]>, +} + +unsafe impl Send for PixelsIterMut<'_, T> where T: Send {} +unsafe impl Sync for PixelsIterMut<'_, T> where T: Sync {} + +impl<'a, T: 'a> PixelsIterMut<'a, T> { + #[inline] + pub(crate) fn new(img: &mut super::ImgRefMut<'a, T>) -> Self { + let width = NonZeroUsize::new(img.width()).expect("width > 0"); + let stride = img.stride(); + debug_assert!(!img.buf().is_empty() && img.buf().len() + stride >= stride * img.height() + width.get()); + Self { + current: img.buf_mut().as_mut_ptr(), + current_line_end: img.buf_mut()[width.get()..].as_mut_ptr(), + width, + y: img.height(), + pad: stride - width.get(), + _dat: PhantomData, + } + } +} + +impl<'a, T: 'a> Iterator for PixelsIterMut<'a, T> { + type Item = &'a mut T; + + #[inline(always)] + fn next(&mut self) -> Option { + unsafe { + if self.current >= self.current_line_end { + self.y -= 1; + if self.y == 0 { + return None; + } + self.current = self.current_line_end.add(self.pad); + self.current_line_end = self.current.add(self.width.get()); + } + let px = &mut *self.current; + self.current = self.current.add(1); + Some(px) + } + } +} + +#[test] +fn iter() { + let img = super::Img::new(vec![1u8, 2], 1, 2); + let mut it = img.pixels(); + assert_eq!(Some(1), it.next()); + assert_eq!(Some(2), it.next()); + assert_eq!(None, it.next()); + + let buf = vec![1u8; (16 + 3) * (8 + 1)]; + for width in 1..16 { + for height in 1..8 { + for pad in 0..3 { + let stride = width + pad; + let img = super::Img::new_stride(&buf[..stride * height + stride - width], width, height, stride); + assert_eq!(width * height, img.pixels().map(|a| a as usize).sum(), "{}x{}", width, height); + assert_eq!(width * height, img.pixels().count(), "{}x{}", width, height); + assert_eq!(height, img.rows().count()); + + let mut iter1 = img.pixels(); + let mut left = width * height; + while let Some(_px) = iter1.next() { + left -= 1; + assert_eq!(left, iter1.len()); + } + assert_eq!(0, iter1.len()); iter1.next(); + assert_eq!(0, iter1.len()); + + let mut iter2 = img.rows(); + match iter2.next() { + Some(_) => { + assert_eq!(height - 1, iter2.size_hint().0); + assert_eq!(height - 1, iter2.filter(|_| true).count()); + }, + None => { + assert_eq!(height, 0); + }, + }; + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1c05346 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,877 @@ +//! In graphics code it's very common to pass `width` and `height` along with a `Vec` of pixels, +//! all as separate arguments. This is tedious, and can lead to errors. +//! +//! This crate is a simple struct that adds dimensions to the underlying buffer. This makes it easier to correctly keep track +//! of the image size and allows passing images with just one function argument instead three or four. +//! +//! Additionally, it has a concept of a `stride`, which allows defining sub-regions of images without copying, +//! as well as handling padding (e.g. buffers for video frames may require to be a multiple of 8, regardless of logical image size). +//! +//! For convenience, there are iterators over rows or all pixels of a (sub)image and +//! pixel-based indexing directly with `img[(x,y)]` (where `x`/`y` can be `u32` as well as `usize`). +//! +//! `Img` type has aliases for common uses: +//! +//! * Owned: `ImgVec` → `Img>` (use it in `struct`s and return types) +//! * Reference: `ImgRef` → `Img<&[T]>` (use it in function arguments) +//! * Mutable reference: `ImgRefMut` → `Img<&mut [T]>` +//! +//! It is assumed that the container is [one element per pixel](https://crates.io/crates/rgb/), e.g. `Vec`, +//! and _not_ a `Vec` where 4 `u8` elements are interpreted as one pixel. +//! +//! +//! ```rust +//! use imgref::*; +//! # fn some_image_processing_function(img: ImgRef) -> ImgVec { img.new_buf(img.buf().to_vec()) } +//! +//! fn main() { +//! let img = Img::new(vec![0; 1000], 50, 20); // 1000 pixels of a 50×20 image +//! +//! let new_image = some_image_processing_function(img.as_ref()); // Use imgvec.as_ref() instead of &imgvec for better efficiency +//! +//! println!("New size is {}×{}", new_image.width(), new_image.height()); +//! println!("And the top left pixel is {:?}", new_image[(0u32,0u32)]); +//! +//! let first_row_slice = &new_image[0]; +//! +//! for row in new_image.rows() { +//! // … +//! } +//! for px in new_image.pixels() { +//! // … +//! } +//! +//! // slice (x, y, width, height) by reference - no copy! +//! let fragment = img.sub_image(5, 5, 15, 15); +//! +//! // +//! let (vec, width, height) = fragment.to_contiguous_buf(); +//! } +//! ``` + +use std::borrow::Cow; +use std::slice; + +mod traits; + +mod ops; +pub use ops::*; + +mod iter; +pub use iter::*; + +/// Image owning its pixels. +/// +/// A 2D array of pixels. The pixels are oriented top-left first and rows are `stride` pixels wide. +/// +/// If size of the `buf` is larger than `width`*`height`, then any excess space is a padding (see `width_padded()`/`height_padded()`). +pub type ImgVec = Img>; + +/// Reference to pixels inside another image. +/// Pass this structure by value (i.e. `ImgRef`, not `&ImgRef`). +/// +/// Only `width` of pixels of every `stride` can be modified. The `buf` may be longer than `height`*`stride`, but the extra space should be ignored. +pub type ImgRef<'a, Pixel> = Img<&'a [Pixel]>; + +/// Same as `ImgRef`, but mutable +/// Pass this structure by value (i.e. `ImgRef`, not `&ImgRef`). +/// +pub type ImgRefMut<'a, Pixel> = Img<&'a mut [Pixel]>; + +/// Additional methods that depend on buffer size +/// +/// To use these methods you need: +/// +/// ```rust +/// use imgref::*; +/// ``` +pub trait ImgExt { + /// Maximum possible width of the data, including the stride. + /// + /// # Panics + /// + /// This method may panic if the underlying buffer is not at least `height()*stride()` pixels large. + fn width_padded(&self) -> usize; + + /// Height in number of full strides. + /// If the underlying buffer is not an even multiple of strides, the last row is ignored. + /// + /// # Panics + /// + /// This method may panic if the underlying buffer is not at least `height()*stride()` pixels large. + fn height_padded(&self) -> usize; + + /// Iterate over the entire buffer as rows, including all padding + /// + /// Rows will have up to `stride` width, but the last row may be shorter. + fn rows_padded(&self) -> slice::Chunks<'_, Pixel>; + + /// Borrow the container + fn as_ref(&self) -> ImgRef; +} + +/// Additional methods that depend on buffer size +/// +/// To use these methods you need: +/// +/// ```rust +/// use imgref::*; +/// ``` +pub trait ImgExtMut { + /// Iterate over the entire buffer as rows, including all padding + /// + /// Rows will have up to `stride` width, but the last row may be shorter. + fn rows_padded_mut(&mut self) -> slice::ChunksMut<'_, Pixel>; + + /// Borrow the container mutably + fn as_mut(&mut self) -> ImgRefMut; +} + +/// Basic struct used for both owned (alias `ImgVec`) and borrowed (alias `ImgRef`) image fragments. +/// +/// Note: the fields are `pub` only because of borrow checker limitations. Please consider them as read-only. +#[derive(Debug, Copy, Clone)] +pub struct Img { + /// Storage for the pixels. Usually `Vec` or `&[Pixel]`. See `ImgVec` and `ImgRef`. + /// + /// Note that future version will make this field private. Use `.rows()` and `.pixels()` iterators where possible, or `buf()`/`buf_mut()`/`into_buf()`. + #[deprecated(note = "Don't access struct fields directly. Use buf(), buf_mut() or into_buf()")] + pub buf: Container, + + /// Number of pixels to skip in the container to advance to the next row. + /// + /// Note: pixels between `width` and `stride` may not be usable, and may not even exist in the last row. + #[deprecated(note = "Don't access struct fields directly. Use stride()")] + pub stride: usize, + /// Width of the image in pixels. + /// + /// Note that this isn't same as the width of the row in the `buf`, see `stride` + #[deprecated(note = "Don't access struct fields directly. Use width()")] + pub width: u32, + /// Height of the image in pixels. + #[deprecated(note = "Don't access struct fields directly. Use height()")] + pub height: u32, +} + +impl Img { + /// Width of the image in pixels. + /// + /// Note that this isn't same as the width of the row in image data, see `stride()` + #[inline(always)] + #[allow(deprecated)] + pub fn width(&self) -> usize {self.width as usize} + + /// Height of the image in pixels. + #[inline(always)] + #[allow(deprecated)] + pub fn height(&self) -> usize {self.height as usize} + + /// Number of _pixels_ to skip in the container to advance to the next row. + /// + /// Note the last row may have fewer pixels than the stride. + /// Some APIs use number of *bytes* for a stride. You may need to multiply this one by number of pixels. + #[inline(always)] + #[allow(deprecated)] + pub fn stride(&self) -> usize {self.stride} + + /// Immutable reference to the pixel storage. Warning: exposes stride. Use `pixels()` or `rows()` insetad. + /// + /// See also `into_contiguous_buf()`. + #[inline(always)] + #[allow(deprecated)] + pub fn buf(&self) -> &Container {&self.buf} + + /// Mutable reference to the pixel storage. Warning: exposes stride. Use `pixels_mut()` or `rows_mut()` insetad. + /// + /// See also `into_contiguous_buf()`. + #[inline(always)] + #[allow(deprecated)] + pub fn buf_mut(&mut self) -> &mut Container {&mut self.buf} + + /// Get the pixel storage by consuming the image. Be careful about stride — see `into_contiguous_buf()` for a safe version. + #[inline(always)] + #[allow(deprecated)] + pub fn into_buf(self) -> Container {self.buf} + + #[deprecated(note = "this was meant to be private, use new_buf() and/or rows()")] + pub fn rows_buf<'a, T: 'a>(&self, buf: &'a [T]) -> RowsIter<'a, T> { + self.rows_buf_internal(buf) + } + + #[inline] + fn rows_buf_internal<'a, T: 'a>(&self, buf: &'a [T]) -> RowsIter<'a, T> { + let stride = self.stride(); + debug_assert!(self.width() <= self.stride()); + debug_assert!(buf.len() >= self.width() * self.height()); + assert!(stride > 0); + let non_padded = &buf[0..stride * self.height() + self.width() - stride]; + RowsIter { + width: self.width(), + inner: non_padded.chunks(stride), + } + } +} + +impl ImgExt for Img where Container: AsRef<[Pixel]> { + #[inline(always)] + fn width_padded(&self) -> usize { + self.stride() + } + + #[inline(always)] + fn height_padded(&self) -> usize { + let len = self.buf().as_ref().len(); + assert_eq!(0, len % self.stride()); + len / self.stride() + } + + /// Iterate over the entire buffer as rows, including all padding + /// + /// Rows will have up to `stride` width, but the last row may be shorter. + #[inline(always)] + fn rows_padded(&self) -> slice::Chunks<'_, Pixel> { + self.buf().as_ref().chunks(self.stride()) + } + + #[inline(always)] + #[allow(deprecated)] + fn as_ref(&self) -> ImgRef { + Img { + buf: self.buf.as_ref(), + width: self.width, + height: self.height, + stride: self.stride, + } + } +} + +impl ImgExtMut for Img where Container: AsMut<[Pixel]> { + /// Iterate over the entire buffer as rows, including all padding + /// + /// Rows will have up to `stride` width, but the last row may be shorter. + /// + /// # Panics + /// + /// If stride is 0 + #[inline] + #[must_use] + fn rows_padded_mut(&mut self) -> slice::ChunksMut<'_, Pixel> { + let stride = self.stride(); + self.buf_mut().as_mut().chunks_mut(stride) + } + + #[inline(always)] + #[allow(deprecated)] + fn as_mut(&mut self) -> ImgRefMut { + Img { + buf: self.buf.as_mut(), + width: self.width, + height: self.height, + stride: self.stride, + } + } +} + +#[inline] +fn sub_image(left: usize, top: usize, width: usize, height: usize, stride: usize, buf_len: usize) -> (usize, usize, usize) { + let start = stride * top + left; + let full_strides_end = start + stride * height; + // when left > 0 and height is full, the last line is shorter than the stride + let end = if buf_len >= full_strides_end { + full_strides_end + } else { + debug_assert!(height > 0); + let min_strides_len = full_strides_end + width - stride; + debug_assert!(buf_len >= min_strides_len, "the buffer is too small to fit the subimage"); + // if can't use full buffer, then shrink to min required (last line having exact width) + min_strides_len + }; + (start, end, stride) +} + +impl<'a, T> ImgRef<'a, T> { + /// Make a reference for a part of the image, without copying any pixels. + /// + /// # Panics + /// + /// It will panic if sub_image is outside of the image area + /// (left + width must be <= container width, etc.) + #[inline] + #[must_use] + pub fn sub_image(&self, left: usize, top: usize, width: usize, height: usize) -> Self { + assert!(top + height <= self.height()); + assert!(left + width <= self.width()); + let (start, end, stride) = sub_image(left, top, width, height, self.stride(), self.buf().len()); + let buf = &self.buf()[start..end]; + Self::new_stride(buf, width, height, stride) + } + + #[inline] + /// Iterate over whole rows of pixels as slices + /// + /// # Panics + /// + /// If stride is 0 + /// + /// See also `pixels()` + pub fn rows(&self) -> RowsIter<'_, T> { + self.rows_buf_internal(self.buf()) + } + + /// Deprecated + /// + /// Note: it iterates **all** pixels in the underlying buffer, not just limited by width/height. + #[deprecated(note = "Size of this buffer is unpredictable. Use .rows() instead")] + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.buf().iter() + } +} + +impl<'a, T: Clone> ImgRef<'a, T> { + /// Returns a reference to the buffer, width, height. Guarantees that the buffer is contiguous, + /// i.e. it's `width*height` elements long, and `[x + y*width]` addresses each pixel. + /// + /// It will create a copy if the buffer isn't contiguous (width != stride). + /// For a more efficient version, see `into_contiguous_buf()` + #[allow(deprecated)] + #[must_use] + pub fn to_contiguous_buf(&self) -> (Cow<'a, [T]>, usize, usize) { + let width = self.width(); + let height = self.height(); + let stride = self.stride(); + if width == stride { + return (Cow::Borrowed(self.buf), width, height) + } + let mut buf = Vec::with_capacity(width*height); + for row in self.rows() { + buf.extend_from_slice(row); + } + (Cow::Owned(buf), width, height) + } +} + +impl<'a, T> ImgRefMut<'a, T> { + /// Turn this into immutable reference, and slice a subregion of it + #[inline] + #[allow(deprecated)] + #[must_use] + pub fn sub_image(&'a mut self, left: usize, top: usize, width: usize, height: usize) -> ImgRef<'a, T> { + self.as_ref().sub_image(left, top, width, height) + } + + /// Trim this image without copying. + /// Note that mutable borrows are exclusive, so it's not possible to have more than + /// one mutable subimage at a time. + #[inline] + #[allow(deprecated)] + #[must_use] + pub fn sub_image_mut(&mut self, left: usize, top: usize, width: usize, height: usize) -> ImgRefMut<'_, T> { + assert!(top+height <= self.height()); + assert!(left+width <= self.width()); + let (start, end, stride) = sub_image(left, top, width, height, self.stride(), self.buf.len()); + let buf = &mut self.buf[start..end]; + ImgRefMut::new_stride(buf, width, height, stride) + } + + /// Make mutable reference immutable + #[inline] + #[must_use] + pub fn as_ref(&self) -> ImgRef<'_, T> { + self.new_buf(self.buf().as_ref()) + } +} + +impl<'a, T: Copy> ImgRef<'a, T> { + /// Iterate `width*height` pixels in the `Img`, ignoring padding area + /// + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// + /// # Panics + /// + /// if width is 0 + #[inline] + pub fn pixels(&self) -> PixelsIter<'_, T> { + PixelsIter::new(*self) + } +} + +impl<'a, T> ImgRef<'a, T> { + /// Iterate `width*height` pixels in the `Img`, by reference, ignoring padding area + /// + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// + /// # Panics + /// + /// if width is 0 + #[inline] + pub fn pixels_ref(&self) -> PixelsRefIter<'_, T> { + PixelsRefIter::new(*self) + } +} + +impl<'a, T: Copy> ImgRefMut<'a, T> { + /// # Panics + /// + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// + /// if width is 0 + #[inline] + pub fn pixels(&self) -> PixelsIter<'_, T> { + PixelsIter::new(self.as_ref()) + } + + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// # Panics + /// + /// if width is 0 + #[inline] + pub fn pixels_mut(&mut self) -> PixelsIterMut<'_, T> { + PixelsIterMut::new(self) + } +} + +impl ImgVec { + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// # Panics + /// + /// if width is 0 + #[inline] + pub fn pixels(&self) -> PixelsIter<'_, T> { + PixelsIter::new(self.as_ref()) + } + + /// If you want to iterate in parallel, parallelize `rows()` instead. + /// # Panics + /// + /// if width is 0 + #[inline] + pub fn pixels_mut(&mut self) -> PixelsIterMut<'_, T> { + PixelsIterMut::new(&mut self.as_mut()) + } +} + +impl<'a, T> ImgRefMut<'a, T> { + /// Iterate over whole rows as slices + /// + /// # Panics + /// + /// if stride is 0 + #[inline] + pub fn rows(&self) -> RowsIter<'_, T> { + self.rows_buf_internal(&self.buf()[..]) + } + + /// Iterate over whole rows as slices + /// + /// # Panics + /// + /// if stride is 0 + #[inline] + #[allow(deprecated)] + pub fn rows_mut(&mut self) -> RowsIterMut<'_, T> { + let stride = self.stride(); + let width = self.width(); + let height = self.height(); + let non_padded = &mut self.buf[0..stride * height + width - stride]; + RowsIterMut { + width, + inner: non_padded.chunks_mut(stride), + } + } +} + +/// Deprecated. Use .rows() or .pixels() iterators which are more predictable +impl IntoIterator for Img where Container: IntoIterator { + type Item = Container::Item; + type IntoIter = Container::IntoIter; + /// Deprecated. Use .rows() or .pixels() iterators which are more predictable + fn into_iter(self) -> Container::IntoIter { + self.into_buf().into_iter() + } +} + +impl ImgVec { + /// Create a mutable view into a region within the image. See `sub_image()` for read-only views. + #[allow(deprecated)] + #[must_use] + pub fn sub_image_mut(&mut self, left: usize, top: usize, width: usize, height: usize) -> ImgRefMut<'_, T> { + assert!(top+height <= self.height()); + assert!(left+width <= self.width()); + let start = self.stride * top + left; + let min_buf_size = if self.height > 0 {self.stride * height + width - self.stride} else {0}; + let buf = &mut self.buf[start .. start + min_buf_size]; + Img::new_stride(buf, width, height, self.stride) + } + + #[inline] + #[must_use] + /// Make a reference for a part of the image, without copying any pixels. + pub fn sub_image(&self, left: usize, top: usize, width: usize, height: usize) -> ImgRef<'_, T> { + self.as_ref().sub_image(left, top, width, height) + } + + /// Make a reference to this image to pass it to functions without giving up ownership + /// + /// The reference should be passed by value (`ImgRef`, not `&ImgRef`). + /// + /// If you need a mutable reference, see `as_mut()` and `sub_image_mut()` + #[inline] + #[must_use] + pub fn as_ref(&self) -> ImgRef<'_, T> { + self.new_buf(self.buf().as_ref()) + } + + /// Make a mutable reference to the entire image + /// + /// The reference should be passed by value (`ImgRefMut`, not `&mut ImgRefMut`). + /// + /// See also `sub_image_mut()` and `rows_mut()` + #[inline] + pub fn as_mut(&mut self) -> ImgRefMut<'_, T> { + let width = self.width(); + let height = self.height(); + let stride = self.stride(); + Img::new_stride(self.buf_mut().as_mut(), width, height, stride) + } + + #[deprecated(note = "Size of this buffer may be unpredictable. Use .rows() instead")] + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.buf().iter() + } + + /// Iterate over rows of the image as slices + /// + /// Each slice is guaranteed to be exactly `width` pixels wide. + /// + /// This iterator is a good candidate for parallelization (e.g. rayon's `par_bridge()`) + #[inline] + pub fn rows(&self) -> RowsIter<'_, T> { + self.rows_buf_internal(self.buf()) + } + + /// Iterate over rows of the image as mutable slices + /// + /// Each slice is guaranteed to be exactly `width` pixels wide. + /// + /// This iterator is a good candidate for parallelization (e.g. rayon's `par_bridge()`) + #[inline] + #[allow(deprecated)] + pub fn rows_mut(&mut self) -> RowsIterMut<'_, T> { + let stride = self.stride(); + let width = self.width(); + let height = self.height(); + let non_padded = &mut self.buf[0..stride * height + width - stride]; + RowsIterMut { + width, + inner: non_padded.chunks_mut(stride), + } + } +} + +impl Img { + /// Same as `new()`, except each row is located `stride` number of pixels after the previous one. + /// + /// Stride can be equal to `width` or larger. If it's larger, then pixels between end of previous row and start of the next are considered a padding, and may be ignored. + /// + /// The `Container` is usually a `Vec` or a slice. + #[inline] + #[allow(deprecated)] + pub fn new_stride(buf: Container, width: usize, height: usize, stride: usize) -> Self { + assert!(stride > 0); + assert!(stride >= width as usize); + debug_assert!(height < ::max_value() as usize); + debug_assert!(width < ::max_value() as usize); + Img { + buf, + width: width as u32, + height: height as u32, + stride, + } + } + + /// Create new image with `Container` (which can be `Vec`, `&[]` or something else) with given `width` and `height` in pixels. + /// + /// Assumes the pixels in container are contiguous, layed out row by row with `width` pixels per row and at least `height` rows. + /// + /// If the container is larger than `width`×`height` pixels, the extra rows are a considered a padding and may be ignored. + #[inline] + pub fn new(buf: Container, width: usize, height: usize) -> Self { + Self::new_stride(buf, width, height, width) + } +} + +impl Img> { + /// Returns the buffer, width, height. Guarantees that the buffer is contiguous, + /// i.e. it's `width*height` elements long, and `[x + y*width]` addresses each pixel. + /// + /// Efficiently performs operation in-place. For other containers use `pixels().collect()`. + #[allow(deprecated)] + #[must_use] + pub fn into_contiguous_buf(mut self) -> (Vec, usize, usize) { + let (_, w, h) = self.as_contiguous_buf(); + (self.buf, w, h) + } + + /// Returns a reference to the buffer, width, height. Guarantees that the buffer is contiguous, + /// i.e. it's `width*height` elements long, and `[x + y*width]` addresses each pixel. + /// + /// Efficiently performs operation in-place. For other containers use `pixels().collect()`. + #[allow(deprecated)] + #[must_use] + pub fn as_contiguous_buf(&mut self) -> (&[T], usize, usize) { + let width = self.width(); + let height = self.height(); + let stride = self.stride(); + if width != stride { + for row in 1..height { + self.buf.copy_within(row * stride .. row * stride + width, row * width); + } + } + self.buf.truncate(width * height); + (&mut self.buf, width, height) + } +} + +impl Img { + /// A convenience method for creating an image of the same size and stride, but with a new buffer. + #[inline] + pub fn map_buf(self, callback: F) -> Img + where NewContainer: AsRef<[NewPixel]>, OldContainer: AsRef<[OldPixel]>, F: FnOnce(OldContainer) -> NewContainer { + let width = self.width(); + let height = self.height(); + let stride = self.stride(); + let old_buf_len = self.buf().as_ref().len(); + #[allow(deprecated)] + let new_buf = callback(self.buf); + assert_eq!(old_buf_len, new_buf.as_ref().len()); + Img::new_stride(new_buf, width, height, stride) + } + + /// A convenience method for creating an image of the same size and stride, but with a new buffer. + #[inline] + pub fn new_buf(&self, new_buf: NewContainer) -> Img + where NewContainer: AsRef<[NewPixel]>, OldContainer: AsRef<[OldPixel]> { + assert_eq!(self.buf().as_ref().len(), new_buf.as_ref().len()); + Img::new_stride(new_buf, self.width(), self.height(), self.stride()) + } +} + +impl From>> for Img> { + #[allow(deprecated)] + fn from(img: Img>) -> Self { + Img { + width: img.width, + height: img.height, + stride: img.stride, + buf: img.buf.into_owned(), + } + } +} + +impl From> for Img> { + #[allow(deprecated)] + fn from(img: ImgVec) -> Self { + Img { + width: img.width, + height: img.height, + stride: img.stride, + buf: img.buf.into(), + } + } +} + +impl<'a, T: Clone> From> for Img> { + #[allow(deprecated)] + fn from(img: ImgRef<'a, T>) -> Self { + Img { + buf: img.buf.into(), + width: img.width, + height: img.height, + stride: img.stride, + } + } +} + +impl Img> { + /// Convert underlying buffer to owned (e.g. slice to vec) + /// + /// See also `to_contiguous_buf().0.into_owned()` + #[allow(deprecated)] + pub fn into_owned(self) -> ImgVec { + match self.buf { + Cow::Borrowed(_) => { + let tmp = self.as_ref(); + let (buf, w, h) = tmp.to_contiguous_buf(); + ImgVec::new(buf.into_owned(), w, h) + }, + Cow::Owned(buf) => Img { + buf, + width: self.width, + height: self.height, + stride: self.stride, + }, + } + } +} + +impl Img where T: ToOwned { + /// Convert underlying buffer to owned (e.g. slice to vec) + /// + /// See also `to_contiguous_buf().0.into_owned()` + #[allow(deprecated)] + pub fn to_owned(&self) -> Img { + Img { + buf: self.buf.to_owned(), + width: self.width, + height: self.height, + stride: self.stride, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod with_opinionated_container { + use super::*; + + struct IDontDeriveAnything; + + #[test] + fn compiles() { + let _ = Img::new(IDontDeriveAnything, 1, 1); + } + } + + #[test] + fn with_vec() { + let bytes = vec![0u8;20]; + let old = Img::new_stride(bytes, 10,2,10); + let _ = old.new_buf(vec![6u16;20]); + } + + #[test] + fn zero() { + let bytes = vec![0u8]; + let mut img = Img::new_stride(bytes,0,0,1); + let _ = img.sub_image(0,0,0,0); + let _ = img.sub_image_mut(0,0,0,0); + let _ = img.as_ref(); + } + + #[test] + fn zero_width() { + let bytes = vec![0u8]; + let mut img = Img::new_stride(bytes,0,1,1); + let _ = img.sub_image(0,1,0,0); + let _ = img.sub_image_mut(0,0,0,1); + } + + #[test] + fn zero_height() { + let bytes = vec![0u8]; + let mut img = Img::new_stride(bytes,1,0,1); + assert_eq!(0, img.rows().count()); + let _ = img.sub_image(1,0,0,0); + let _ = img.sub_image_mut(0,0,1,0); + } + + #[test] + #[allow(deprecated)] + fn with_slice() { + let bytes = vec![0u8;20]; + let _ = Img::new_stride(bytes.as_slice(), 10,2,10); + let vec = ImgVec::new_stride(bytes, 10,2,10); + for _ in vec.iter() {} + assert_eq!(2, vec.rows().count()); + for _ in vec.as_ref().buf().iter() {} + for _ in vec {} + } + + #[test] + fn sub() { + let img = Img::new_stride(vec![1,2,3,4, + 5,6,7,8, + 9], 3, 2, 4); + assert_eq!(img.buf()[img.stride()], 5); + assert_eq!(img.buf()[img.stride() + img.width()-1], 7); + + assert_eq!(img.pixels().count(), img.width() * img.height()); + assert_eq!(img.pixels().sum::(), 24); + + { + let refimg = img.as_ref(); + let refimg2 = refimg; // Test is Copy + + // sub-image with stride hits end of the buffer + let s1 = refimg.sub_image(1, 0, refimg.width()-1, refimg.height()); + let _ = s1.sub_image(1, 0, s1.width()-1, s1.height()); + + let subimg = refimg.sub_image(1, 1, 2, 1); + assert_eq!(subimg.pixels().count(), subimg.width() * subimg.height()); + + assert_eq!(subimg.buf()[0], 6); + assert_eq!(subimg.stride(), refimg2.stride()); + assert!(subimg.stride() * subimg.height() + subimg.width() - subimg.stride() <= subimg.buf().len()); + assert_eq!(refimg.buf()[0], 1); + assert_eq!(1, subimg.rows().count()); + } + + let mut img = img; + let mut subimg = img.sub_image_mut(1, 1, 2, 1); + assert_eq!(1, subimg.rows().count()); + assert_eq!(1, subimg.rows_mut().count()); + assert_eq!(1, subimg.rows_mut().rev().count()); + assert_eq!(1, subimg.rows_mut().fuse().rev().count()); + assert_eq!(subimg.buf()[0], 6); + } + + #[test] + fn rows() { + let img = ImgVec::new_stride(vec![0u8; 10000], 10, 15, 100); + assert_eq!(img.height(), img.rows().count()); + assert_eq!(img.height(), img.rows().rev().count()); + assert_eq!(img.height(), img.rows().fuse().rev().count()); + } + + #[test] + fn mut_pixels() { + for y in 1..15 { + for x in 1..10 { + let mut img = ImgVec::new_stride(vec![0u8; 10000], x, y, 100); + assert_eq!(x*y, img.pixels_mut().count()); + assert_eq!(x*y, img.as_mut().pixels().count()); + assert_eq!(x*y, img.as_mut().pixels_mut().count()); + assert_eq!(x*y, img.as_mut().as_ref().pixels().count()); + } + } + } + + #[test] + fn into_contiguous_buf() { + for in_h in [1, 2, 3, 38, 39, 40, 41].iter().copied() { + for in_w in [1, 2, 3, 120, 121].iter().copied() { + for stride in [in_w, 121, 122, 166, 242, 243].iter().copied() { + let img = ImgVec::new_stride((0..10000).map(|x| x as u8).collect(), in_w, in_h, stride) + .map_buf(|x| x); + let pixels: Vec<_> = img.pixels().collect(); + let (buf, w, h) = img.into_contiguous_buf(); + assert_eq!(pixels, buf); + assert_eq!(in_w*in_h, buf.len()); + assert_eq!(10000, buf.capacity()); + assert_eq!(in_w, w); + assert_eq!(in_h, h); + } + } + } + + let img = ImgVec::new((0..55*33).map(|x| x as u8).collect(), 55, 33); + let pixels: Vec<_> = img.pixels().collect(); + let tmp = img.as_ref(); + let (buf, ..) = tmp.to_contiguous_buf(); + assert_eq!(&pixels[..], &buf[..]); + let (buf, ..) = img.into_contiguous_buf(); + assert_eq!(pixels, buf); + } +} diff --git a/src/ops.rs b/src/ops.rs new file mode 100644 index 0000000..9efea60 --- /dev/null +++ b/src/ops.rs @@ -0,0 +1,116 @@ +use super::Img; +use std::ops; + +macro_rules! impl_imgref_index { + ($container:ty, $index:ty) => { + impl<'a, Pixel: Copy> ops::Index<($index, $index)> for Img<$container> { + type Output = Pixel; + #[inline(always)] + /// Read a pixel at `(x,y)` location (e.g. px = `img[(x,y)]`) + /// + /// Coordinates may be outside `width`/`height` if the buffer has enough padding. + /// The x coordinate can't exceed `stride`. + fn index(&self, index: ($index, $index)) -> &Self::Output { + let stride = self.stride(); + debug_assert_eq!(stride, stride as $index as usize); + debug_assert!(index.0 < stride as $index); + &self.buf()[(index.1 * (stride as $index) + index.0) as usize] + } + } + }; +} + +macro_rules! impl_imgref_index_mut { + ($container:ty, $index:ty) => { + impl<'a, Pixel: Copy> ops::IndexMut<($index, $index)> for Img<$container> { + #[inline(always)] + /// Write a pixel at `(x,y)` location (e.g. `img[(x,y)] = px`) + /// + /// Coordinates may be outside `width`/`height` if the buffer has enough padding. + /// The x coordinate can't exceed `stride`. + fn index_mut(&mut self, index: ($index, $index)) -> &mut Self::Output { + let stride = self.stride(); + debug_assert_eq!(stride, stride as $index as usize); + debug_assert!(index.0 < stride as $index); + &mut self.buf_mut()[(index.1 * (stride as $index) + index.0) as usize] + } + } + }; +} + +impl_imgref_index! {&'a [Pixel], usize} +impl_imgref_index! {&'a [Pixel], u32} +impl_imgref_index! {&'a mut [Pixel], usize} +impl_imgref_index! {&'a mut [Pixel], u32} +impl_imgref_index_mut! {&'a mut [Pixel], usize} +impl_imgref_index_mut! {&'a mut [Pixel], u32} +impl_imgref_index! {Vec, usize} +impl_imgref_index! {Vec, u32} +impl_imgref_index_mut! {Vec, usize} +impl_imgref_index_mut! {Vec, u32} + +#[test] +fn index() { + let mut img = Img::new_stride(vec![1,2,3,4,5,6,7,8], 2, 2, 3); + assert_eq!(1, img[(0u32,0u32)]); + assert_eq!(2, img.as_ref()[(1usize,0usize)]); + assert_eq!(3, img.as_ref()[(2u32,0u32)]); + assert_eq!(4, img[(0usize,1usize)]); + assert_eq!(8, img[(1usize,2usize)]); + assert_eq!(5, img.sub_image_mut(1,1,1,1)[(0usize,0usize)]); +} + + +macro_rules! impl_imgref_row_index { + ($container:ty) => { + impl<'a, Pixel: Copy> ops::Index for Img<$container> { + type Output = [Pixel]; + #[inline(always)] + /// Take n-th row as a slice. Same as .rows().nth(n).unwrap() + /// + /// Slice length is guaranteed to equal image width. + /// Row must be within image height. + fn index(&self, row: usize) -> &Self::Output { + let stride = self.stride(); + let width = self.width(); + let start = row * stride; + &self.buf()[start .. start + width] + } + } + }; +} + +macro_rules! impl_imgref_row_index_mut { + ($container:ty) => { + impl<'a, Pixel: Copy> ops::IndexMut for Img<$container> { + #[inline(always)] + /// Take n-th row as a mutable slice. Same as .rows().nth(n).unwrap() + /// + /// Slice length is guaranteed to equal image width. + /// Row must be within image height. + fn index_mut(&mut self, row: usize) -> &mut Self::Output { + let stride = self.stride(); + let width = self.width(); + let start = row * stride; + &mut self.buf_mut()[start .. start + width] + } + } + }; +} + +impl_imgref_row_index! {&'a [Pixel]} +impl_imgref_row_index! {&'a mut [Pixel]} +impl_imgref_row_index_mut! {&'a mut [Pixel]} +impl_imgref_row_index! {Vec} +impl_imgref_row_index_mut! {Vec} + +#[test] +fn index_by_row() { + let mut img = Img::new_stride(vec![1,2,3,4,5,6,7,8], 2, 2, 3); + assert_eq!(&[1,2], &img[0]); + assert_eq!(&[4,5], &img[1]); + assert_eq!(&[1,2], &img.as_ref()[0]); + assert_eq!(&[4,5], &img.as_ref()[1]); + assert_eq!(&[1,2], &img.as_mut()[0]); + assert_eq!(&[4,5], &img.as_mut()[1]); +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..69954f3 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,148 @@ +use std::hash::{Hasher, Hash}; +use crate::{ImgRef, ImgVec, ImgRefMut}; + +impl<'a, T: Hash> Hash for ImgRef<'a, T> { + #[allow(deprecated)] + #[inline] + fn hash(&self, state: &mut H) { + self.width.hash(state); + self.height.hash(state); + for row in self.rows() { + Hash::hash_slice(row, state); + } + } +} + +impl<'a, T: Hash> Hash for ImgRefMut<'a, T> { + #[allow(deprecated)] + #[inline] + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl Hash for ImgVec { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl<'a, 'b, T, U> PartialEq> for ImgRef<'a, T> where T: PartialEq { + #[allow(deprecated)] + #[inline] + fn eq(&self, other: &ImgRef<'b, U>) -> bool { + self.width == other.width && + self.height == other.height && + self.rows().zip(other.rows()).all(|(a,b)| a == b) + } +} + +impl<'a, 'b, T, U> PartialEq> for ImgRefMut<'a, T> where T: PartialEq { + #[allow(deprecated)] + #[inline] + fn eq(&self, other: &ImgRefMut<'b, U>) -> bool { + self.as_ref().eq(&other.as_ref()) + } +} + + +impl PartialEq> for ImgVec where T: PartialEq { + #[allow(deprecated)] + #[inline(always)] + fn eq(&self, other: &ImgVec) -> bool { + self.as_ref().eq(&other.as_ref()) + } +} + +impl<'a, T, U> PartialEq> for ImgVec where T: PartialEq { + #[allow(deprecated)] + #[inline(always)] + fn eq(&self, other: &ImgRef<'a, U>) -> bool { + self.as_ref().eq(other) + } +} + +impl<'a, T, U> PartialEq> for ImgRef<'a, T> where T: PartialEq { + #[allow(deprecated)] + #[inline(always)] + fn eq(&self, other: &ImgVec) -> bool { + self.eq(&other.as_ref()) + } +} + +impl<'a, 'b, T, U> PartialEq> for ImgRefMut<'a, T> where T: PartialEq { + #[allow(deprecated)] + #[inline(always)] + fn eq(&self, other: &ImgRef<'b, U>) -> bool { + self.as_ref().eq(other) + } +} + +impl<'a, 'b, T, U> PartialEq> for ImgRef<'a, T> where T: PartialEq { + #[allow(deprecated)] + #[inline(always)] + fn eq(&self, other: &ImgRefMut<'b, U>) -> bool { + self.eq(&other.as_ref()) + } +} + +impl<'a, T: Eq> Eq for ImgRefMut<'a, T> { +} + +impl<'a, T: Eq> Eq for ImgRef<'a, T> { +} + +impl Eq for ImgVec { +} + +#[test] +fn test_eq_hash() { + #[derive(Debug)] + struct Comparable(u16); + impl PartialEq for Comparable { + fn eq(&self, other: &u8) -> bool { self.0 == *other as u16 } + } + + let newtype = ImgVec::new(vec![Comparable(0), Comparable(1), Comparable(2), Comparable(3)], 2, 2); + let mut img1 = ImgVec::new(vec![0u8, 1, 2, 3], 2, 2); + let img_ne = ImgVec::new(vec![0u8, 1, 2, 3], 4, 1); + let img2 = ImgVec::new_stride(vec![0u8, 1, 255, 2, 3, 255], 2, 2, 3); + let mut img3 = ImgVec::new_stride(vec![0u8, 1, 255, 2, 3], 2, 2, 3); + + assert_eq!(newtype, img1); + equiv(&img1, &img2); + equiv(&img2, &img3); + equiv(&img1, &img3); + + assert_ne!(img1, img_ne); + assert_eq!(img1.as_ref(), img2); + assert_eq!(img2, img3.as_ref()); + equiv(&img1.as_ref(), &img3.as_ref()); + equiv(&img1.as_mut(), &img3.as_mut()); + assert_eq!(img2.as_ref(), img3.as_mut()); + + let mut map = HashSet::new(); + img3[(0usize,0usize)] = 100; + assert_ne!(img1, img3); + assert!(map.insert(img1)); + assert!(map.insert(img3)); + assert!(map.insert(img_ne)); + assert!(!map.insert(img2)); +} + +#[cfg(test)] +use std::fmt::Debug; +#[cfg(test)] +use std::collections::HashSet; + +#[cfg(test)] +fn equiv(a: &A, b: &A) where A: Eq + PartialEq + Hash + Debug { + assert_eq!(a, b); + let mut map = HashSet::new(); + assert!(map.insert(a)); + assert!(!map.insert(b)); + assert!(!map.insert(a)); + assert!(map.remove(b)); + assert!(map.is_empty()); +} -- 2.7.4