From f65c0f7a04f82394cd0b53a81bcf8084a26742cc Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Thu, 6 Apr 2023 09:10:47 +0900 Subject: [PATCH 1/1] Import rgb 0.8.36 --- .cargo_vcs_info.json | 6 + Cargo.lock | 96 ++++++++ Cargo.toml | 84 +++++++ Cargo.toml.orig | 44 ++++ LICENSE | 21 ++ README.md | 70 ++++++ examples/example.rs | 15 ++ examples/serde.rs | 14 ++ src/alt.rs | 386 +++++++++++++++++++++++++++++ src/internal/convert/array.rs | 115 +++++++++ src/internal/convert/mod.rs | 431 ++++++++++++++++++++++++++++++++ src/internal/convert/tuple.rs | 89 +++++++ src/internal/ops.rs | 412 +++++++++++++++++++++++++++++++ src/internal/pixel.rs | 85 +++++++ src/internal/rgb.rs | 253 +++++++++++++++++++ src/internal/rgba.rs | 445 ++++++++++++++++++++++++++++++++++ src/lib.rs | 229 +++++++++++++++++ 17 files changed, 2795 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/example.rs create mode 100644 examples/serde.rs create mode 100644 src/alt.rs create mode 100644 src/internal/convert/array.rs create mode 100644 src/internal/convert/mod.rs create mode 100644 src/internal/convert/tuple.rs create mode 100644 src/internal/ops.rs create mode 100644 src/internal/pixel.rs create mode 100644 src/internal/rgb.rs create mode 100644 src/internal/rgba.rs create mode 100644 src/lib.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..0462f4b --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "789553fdf3e9679ca82207c079cdfbd138fb886c" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e812772 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,96 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rgb" +version = "0.8.36" +dependencies = [ + "bytemuck", + "serde", + "serde_json", +] + +[[package]] +name = "ryu" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d0dba8c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,84 @@ +# 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 = "rgb" +version = "0.8.36" +authors = ["Kornel Lesiński "] +include = [ + "src/**/*", + "Cargo.toml", + "README.md", + "examples/*.rs", + "LICENSE", +] +description = """ +`struct RGB/RGBA/etc.` for sharing pixels between crates + convenience methods for color manipulation. +Allows no-copy high-level interoperability. Also adds common convenience methods and implements standard Rust traits to make `RGB`/`RGBA` pixels and slices first-class Rust objects.""" +homepage = "https://lib.rs/crates/rgb" +documentation = "https://docs.rs/rgb" +readme = "README.md" +keywords = [ + "rgb", + "rgba", + "bgra", + "pixel", + "color", +] +categories = [ + "graphics", + "rust-patterns", + "multimedia::images", +] +license = "MIT" +repository = "https://github.com/kornelski/rust-rgb" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +features = [ + "argb", + "serde", + "as-bytes", +] + +[[example]] +name = "serde" +required-features = ["serde"] + +[[example]] +name = "example" +required-features = ["as-bytes"] + +[dependencies.bytemuck] +version = "1.7.2" +optional = true + +[dependencies.serde] +version = "1.0.130" +features = ["derive"] +optional = true +default-features = false + +[dev-dependencies.serde_json] +version = "1.0.68" + +[features] +argb = [] +as-bytes = ["bytemuck"] +default = ["as-bytes"] +grb = [] + +[badges.maintenance] +status = "actively-developed" + +[badges.travis-ci] +repository = "kornelski/rust-rgb" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..db5379a --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,44 @@ +[package] +name = "rgb" +version = "0.8.36" +authors = ["Kornel Lesiński "] +include = ["src/**/*", "Cargo.toml", "README.md", "examples/*.rs", "LICENSE"] +description = "`struct RGB/RGBA/etc.` for sharing pixels between crates + convenience methods for color manipulation.\nAllows no-copy high-level interoperability. Also adds common convenience methods and implements standard Rust traits to make `RGB`/`RGBA` pixels and slices first-class Rust objects." +documentation = "https://docs.rs/rgb" +repository = "https://github.com/kornelski/rust-rgb" +homepage = "https://lib.rs/crates/rgb" +readme = "README.md" +keywords = ["rgb", "rgba", "bgra", "pixel", "color"] +license = "MIT" +categories = ["graphics", "rust-patterns", "multimedia::images"] +edition = "2018" + +[features] +default = ["as-bytes"] +# safe as_bytes() casts require a marker trait for non-padded, non-pointery types. +as-bytes = ["bytemuck"] +argb = [] +grb = [] + +[badges] +travis-ci = { repository = "kornelski/rust-rgb" } +maintenance = { status = "actively-developed" } + +[dependencies] +serde = { version = "1.0.130", optional = true, default-features = false, features = ["derive"] } +bytemuck = { version = "1.7.2", optional = true } + +[dev-dependencies] +serde_json = "1.0.68" + +[[example]] +name = "serde" +required-features = ["serde"] + +[[example]] +name = "example" +required-features = ["as-bytes"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +features = [ "argb", "serde", "as-bytes" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aea3f4c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Kornel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfb8ed8 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# `struct RGB` for [Rust](https://www.rust-lang.org) [![crate](https://img.shields.io/crates/v/rgb.svg)](https://lib.rs/crates/rgb) + +Operating on pixels as weakly-typed vectors of `u8` is error-prone and inconvenient. It's better to use vectors of pixel structs. However, Rust is so strongly typed that *your* RGB pixel struct is not compatible with *my* RGB pixel struct. So let's all use mine :P + +[![xkcd standards](https://imgs.xkcd.com/comics/standards.png)](https://xkcd.com/927/) + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +rgb = "0.8" +``` + +## Usage + +### `RGB` and `RGBA` structs + +The structs implement common Rust traits and a few convenience functions, e.g. `map` that repeats an operation on every subpixel: + +```rust +use rgb::*; // Laziest way to use traits which add extra methods to the structs + +let px = RGB { + r:255_u8, + g:0, + b:255, +}; +let inverted = px.map(|ch| 255 - ch); + +println!("{}", inverted); // Display: rgb(0,255,0) +assert_eq!(RGB8::new(0, 255, 0), inverted); +``` + +### Byte slices to pixel slices + +For interoperability with functions operating on generic arrays of bytes there are functions for safe casting to and from pixel slices. + +```rust +let raw = vec![0u8; width*height*3]; +let pixels: &[RGB8] = raw.as_rgb(); /// Safe casts without copying +let raw_again = pixels.as_bytes(); +``` + +Note: if you get an error about "no method named `as_bytes` found", add `use rgb::ComponentBytes`. If you're using a custom component type (`RGB`), implement `rgb::Pod` (plain old data) and `rgb::Zeroable` trait for the component (these traits are from [`bytemuck`](//lib.rs/bytemuck) crate). + +---- + +## About colorspaces + +*Correct* color management is a complex problem, and this crate aims to be the lowest common denominator, so it's intentionally agnostic about it. + +However, this library supports any subpixel type for `RGB`, and `RGBA`, so you can use them with a newtype, e.g.: + +```rust +struct LinearLight(u16); +type LinearRGB = RGB; +``` + + +### `BGRA`, `ARGB`, `Gray`, etc. + +There are other color types in `rgb::alt::*`. To enable `ARGB` and `ABGR`, use the "argb" feature: + +```toml +rgb = { version = "0.8", features = ["argb"] } +``` + +There's also an optional `serde` feature that makes all types (de)serializable. diff --git a/examples/example.rs b/examples/example.rs new file mode 100644 index 0000000..a390b2f --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,15 @@ +use rgb::*; + +fn main() { + + let px = RGB{r:255_u8,g:0,b:100}; + assert_eq!([px].as_bytes()[0], 255); + + let bigpx = RGB16{r:65535_u16,g:0,b:0}; + assert_eq!(bigpx.as_slice()[0], 65535); + + let px = RGB8::new(255, 0, 255); + let inverted: RGB8 = px.map(|ch| 255 - ch); + + println!("{}", inverted); // rgb(0,255,0) +} diff --git a/examples/serde.rs b/examples/serde.rs new file mode 100644 index 0000000..2fb903a --- /dev/null +++ b/examples/serde.rs @@ -0,0 +1,14 @@ +extern crate rgb; +extern crate serde_json; + +use rgb::*; + +// Run using: cargo run --features=serde --example serde + +fn main() { + let color = RGB { r:255_u8, g:0, b:100 }; + println!("{}", serde_json::to_string(&color).unwrap()); + + let color: RGB8 = serde_json::from_str("{\"r\":10,\"g\":20,\"b\":30}").unwrap(); + println!("{}", color); +} diff --git a/src/alt.rs b/src/alt.rs new file mode 100644 index 0000000..cd1043a --- /dev/null +++ b/src/alt.rs @@ -0,0 +1,386 @@ +use crate::internal::pixel::*; +use core::ops; +use core::slice; + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// RGB in reverse byte order +pub struct BGR { + /// Blue first + pub b: ComponentType, + /// Green + pub g: ComponentType, + /// Red last + pub r: ComponentType, +} + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// BGR+A +pub struct BGRA { + /// Blue first + pub b: ComponentType, + /// Green + pub g: ComponentType, + /// Red + pub r: ComponentType, + /// Alpha last + pub a: AlphaComponentType, +} + +#[cfg(feature = "argb")] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// A+BGR +pub struct ABGR { + /// Alpha first + pub a: AlphaComponentType, + /// Blue + pub b: ComponentType, + /// Green + pub g: ComponentType, + /// Red last + pub r: ComponentType, +} + +#[cfg(feature = "argb")] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// A+RGB +pub struct ARGB { + /// Alpha first + pub a: AlphaComponentType, + /// Red + pub r: ComponentType, + /// Green + pub g: ComponentType, + /// Blue last + pub b: ComponentType, +} + +/// 8-bit BGR +pub type BGR8 = BGR; + +/// 16-bit BGR in machine's native endian +pub type BGR16 = BGR; + +/// 8-bit BGRA +pub type BGRA8 = BGRA; + +/// 8-bit ABGR, alpha is first. 0 = transparent, 255 = opaque. +#[cfg(feature = "argb")] +pub type ABGR8 = ABGR; + +/// 8-bit ARGB, alpha is first. 0 = transparent, 255 = opaque. +#[cfg(feature = "argb")] +pub type ARGB8 = ARGB; + +/// 16-bit BGR in machine's native endian +pub type BGRA16 = BGRA; + +/// 16-bit ABGR in machine's native endian. 0 = transparent, 65535 = opaque. +#[cfg(feature = "argb")] +pub type ABGR16 = ABGR; + +/// 16-bit ARGB in machine's native endian. 0 = transparent, 65535 = opaque. +#[cfg(feature = "argb")] +pub type ARGB16 = ARGB; + +#[cfg(feature = "grb")] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// RGB with red-green swapped (may be useful for LEDs) +pub struct GRB { + /// Green first + pub g: ComponentType, + /// Red + pub r: ComponentType, + /// Blue last + pub b: ComponentType, +} + +/// 8-bit GRB +#[cfg(feature = "grb")] +pub type GRB8 = GRB; + +//////////////////////////////////////// + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// Grayscale. Use `.0` or `*` (deref) to access the value. +pub struct Gray( + /// brightness level + pub ComponentType, +); + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// Grayscale with alpha. Use `.0`/`.1` to access. +pub struct GrayAlpha( + /// brightness level + pub ComponentType, + /// alpha + pub AlphaComponentType, +); + +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for Gray where T: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for GrayAlpha where T: crate::Pod, A: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for Gray where T: crate::Zeroable {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for GrayAlpha where T: crate::Zeroable, A: crate::Zeroable {} + +/// 8-bit gray +pub type GRAY8 = Gray; + +/// 16-bit gray in machine's native endian +pub type GRAY16 = Gray; + +/// 8-bit gray with alpha in machine's native endian +pub type GRAYA8 = GrayAlpha; + +/// 16-bit gray with alpha in machine's native endian +pub type GRAYA16 = GrayAlpha; + +impl Gray { + /// New grayscale pixel + #[inline(always)] + pub const fn new(brightness: T) -> Self { + Self(brightness) + } +} + +impl ops::Deref for Gray { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + &self.0 + } +} + +impl From for Gray { + #[inline(always)] + fn from(component: T) -> Self { + Gray(component) + } +} + +impl GrayAlpha { + /// Copy `Gray` component out of the `GrayAlpha` struct + #[inline(always)] + pub fn gray(&self) -> Gray { + Gray(self.0.clone()) + } +} + +impl GrayAlpha { + /// New grayscale+alpha pixel + #[inline(always)] + pub const fn new(brightness: T, alpha: A) -> Self { + Self(brightness, alpha) + } + + /// Provide a mutable view of only `Gray` component (leaving out alpha). + #[inline(always)] + pub fn gray_mut(&mut self) -> &mut Gray { + unsafe { + &mut *(self as *mut _ as *mut _) + } + } +} + +impl GrayAlpha { + /// Create a new `GrayAlpha` with the new alpha value, but same gray value + #[inline(always)] + pub fn alpha(&self, a: A) -> Self { + Self(self.0, a) + } + + /// Create a new `GrayAlpha` with a new alpha value created by the callback. + #[inline(always)] + pub fn map_alpha(&self, f: F) -> GrayAlpha + where F: FnOnce(A) -> B + { + GrayAlpha (self.0, f(self.1.clone())) + } + + /// Create new `GrayAlpha` with the same alpha value, but different `Gray` value + #[inline(always)] + pub fn map_gray(&self, f: F) -> GrayAlpha + where F: FnOnce(T) -> U, U: Clone, B: From + Clone { + GrayAlpha(f(self.0), self.1.clone().into()) + } +} + +impl ComponentMap, T, B> for Gray { + #[inline(always)] + fn map(&self, mut f: F) -> Gray where F: FnMut(T) -> B { + Gray(f(self.0)) + } +} + +impl ColorComponentMap, T, B> for Gray { + #[inline(always)] + fn map_c(&self, mut f: F) -> Gray where F: FnMut(T) -> B { + Gray(f(self.0)) + } +} + +impl ComponentMap, T, B> for GrayAlpha { + #[inline(always)] + fn map(&self, mut f: F) -> GrayAlpha + where + F: FnMut(T) -> B, + { + GrayAlpha(f(self.0), f(self.1)) + } +} + +impl ColorComponentMap, T, B> for GrayAlpha { + #[inline(always)] + fn map_c(&self, mut f: F) -> GrayAlpha + where + F: FnMut(T) -> B, + { + GrayAlpha(f(self.0), self.1) + } +} + +impl ComponentSlice for GrayAlpha { + #[inline(always)] + fn as_slice(&self) -> &[T] { + unsafe { + slice::from_raw_parts(self as *const Self as *const T, 2) + } + } + + #[inline(always)] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + slice::from_raw_parts_mut(self as *mut Self as *mut T, 2) + } + } +} + +impl ComponentSlice for [GrayAlpha] { + #[inline] + fn as_slice(&self) -> &[T] { + unsafe { + slice::from_raw_parts(self.as_ptr() as *const _, self.len() * 2) + } + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + slice::from_raw_parts_mut(self.as_ptr() as *mut _, self.len() * 2) + } + } +} + +#[cfg(feature = "as-bytes")] +impl ComponentBytes for [GrayAlpha] {} + +impl ComponentSlice for Gray { + #[inline(always)] + fn as_slice(&self) -> &[T] { + slice::from_ref(&self.0) + } + + #[inline(always)] + fn as_mut_slice(&mut self) -> &mut [T] { + slice::from_mut(&mut self.0) + } +} + +impl ComponentSlice for [Gray] { + #[inline] + fn as_slice(&self) -> &[T] { + unsafe { + slice::from_raw_parts(self.as_ptr() as *const _, self.len()) + } + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + slice::from_raw_parts_mut(self.as_ptr() as *mut _, self.len()) + } + } +} + +#[cfg(feature = "as-bytes")] +impl ComponentBytes for [Gray] {} + +/// Assumes 255 is opaque +impl From> for GrayAlpha { + #[inline(always)] + fn from(other: Gray) -> Self { + GrayAlpha(other.0, 0xFF) + } +} + +/// Assumes 65535 is opaque +impl From> for GrayAlpha { + #[inline(always)] + fn from(other: Gray) -> Self { + GrayAlpha(other.0, 0xFFFF) + } +} + +#[test] +fn gray() { + let rgb: crate::RGB<_> = Gray(1).into(); + assert_eq!(rgb.r, 1); + assert_eq!(rgb.g, 1); + assert_eq!(rgb.b, 1); + + let rgba: crate::RGBA<_> = Gray(1u8).into(); + assert_eq!(rgba.r, 1); + assert_eq!(rgba.g, 1); + assert_eq!(rgba.b, 1); + assert_eq!(rgba.a, 255); + + let g: GRAY8 = 200.into(); + let g = g.map(|c| c/2); + assert_eq!(110, *g + 10); + assert_eq!(110, 10 + Gray(100).as_ref()); + + let ga: GRAYA8 = GrayAlpha(1, 2); + assert_eq!(ga.gray(), Gray::new(1)); + let mut g2 = ga.clone(); + *g2.gray_mut() = Gray(3); + assert_eq!(g2.map_gray(|g| g+1), GRAYA8::new(4, 2)); + assert_eq!(g2.map(|g| g+1), GrayAlpha(4, 3)); + assert_eq!(g2.0, 3); + assert_eq!(g2.as_slice(), &[3, 2]); + assert_eq!(g2.as_mut_slice(), &[3, 2]); + assert_eq!(g2.alpha(13), GrayAlpha(3, 13)); + assert_eq!(g2.map_alpha(|x| x+3), GrayAlpha(3, 5)); + + assert_eq!((&[Gray(1u16), Gray(2)][..]).as_slice(), &[1, 2]); + assert_eq!((&[GrayAlpha(1u16, 2), GrayAlpha(3, 4)][..]).as_slice(), &[1, 2, 3, 4]); + + let rgba: crate::RGBA<_> = ga.into(); + assert_eq!(rgba.r, 1); + assert_eq!(rgba.g, 1); + assert_eq!(rgba.b, 1); + assert_eq!(rgba.a, 2); + + let ga: GRAYA16 = GrayAlpha(1,2); + let rgba: crate::RGBA = ga.into(); + assert_eq!(rgba.r, 1); + assert_eq!(rgba.g, 1); + assert_eq!(rgba.b, 1); + assert_eq!(rgba.a, 2); +} + diff --git a/src/internal/convert/array.rs b/src/internal/convert/array.rs new file mode 100644 index 0000000..9fbc5a5 --- /dev/null +++ b/src/internal/convert/array.rs @@ -0,0 +1,115 @@ +#[cfg(feature = "argb")] +use crate::alt::{ARGB}; +use crate::alt::{BGR, BGRA}; +use crate::{RGB, RGBA}; + +impl From<[T; 3]> for RGB { + #[inline(always)] + fn from(other: [T; 3]) -> Self { + Self { + r: other[0], + g: other[1], + b: other[2], + } + } +} + +impl Into<[T; 3]> for RGB { + #[inline(always)] + fn into(self) -> [T; 3] { + [self.r, self.g, self.b] + } +} + +impl From<[T; 4]> for RGBA { + #[inline(always)] + fn from(other: [T; 4]) -> Self { + Self { + r: other[0], + g: other[1], + b: other[2], + a: other[3], + } + } +} + +impl Into<[T; 4]> for RGBA { + #[inline(always)] + fn into(self) -> [T; 4] { + [self.r, self.g, self.b, self.a] + } +} + +#[cfg(feature = "argb")] +impl From<[T; 4]> for ARGB { + #[inline(always)] + fn from(other: [T; 4]) -> Self { + Self { + a: other[0], + r: other[1], + g: other[2], + b: other[3], + } + } +} + +#[cfg(feature = "argb")] +impl Into<[T; 4]> for ARGB { + #[inline(always)] + fn into(self) -> [T; 4] { + [self.a, self.r, self.g, self.b] + } +} + +impl From<[T; 3]> for BGR { + #[inline(always)] + fn from(other: [T; 3]) -> Self { + Self { + b: other[0], + g: other[1], + r: other[2], + } + } +} + +impl Into<[T; 3]> for BGR { + #[inline(always)] + fn into(self) -> [T; 3] { + [self.b, self.g, self.r] + } +} + +impl From<[T; 4]> for BGRA { + #[inline(always)] + fn from(other: [T; 4]) -> Self { + Self { + b: other[0], + g: other[1], + r: other[2], + a: other[3], + } + } +} + +impl Into<[T; 4]> for BGRA { + #[inline(always)] + fn into(self) -> [T; 4] { + [self.b, self.g, self.r, self.a] + } +} + +#[test] +#[allow(deprecated)] +fn convert_array() { + use crate::alt::{BGR8, BGRA8}; + use crate::{RGB8, RGBA8}; + + assert_eq!(RGB8::from([1, 2, 3]), RGB8::new(1, 2, 3)); + assert_eq!(Into::<[u8; 3]>::into(RGB8::new(1, 2, 3)), [1, 2, 3]); + assert_eq!(RGBA8::from([1, 2, 3, 4]), RGBA8::new(1, 2, 3, 4)); + assert_eq!(Into::<[u8; 4]>::into(RGBA8::new(1, 2, 3, 4)), [1, 2, 3, 4]); + assert_eq!(BGR8::from([3, 2, 1]), BGR8::new(1, 2, 3)); + assert_eq!(Into::<[u8; 3]>::into(BGR8::new(1, 2, 3)), [3, 2, 1]); + assert_eq!(BGRA8::from([3, 2, 1, 4]), BGRA8::new(1, 2, 3, 4)); + assert_eq!(Into::<[u8; 4]>::into(BGRA8::new(1, 2, 3, 4)), [3, 2, 1, 4]); +} diff --git a/src/internal/convert/mod.rs b/src/internal/convert/mod.rs new file mode 100644 index 0000000..2dc8af8 --- /dev/null +++ b/src/internal/convert/mod.rs @@ -0,0 +1,431 @@ +use super::pixel::*; +use crate::alt::*; +use crate::RGB; +use crate::RGBA; +use core::convert::*; +use core::mem; +use core::slice; + +mod array; +mod tuple; + +/// Casts a slice of bytes into a slice of pixels, e.g. `[u8]` to `[RGB8]`. +/// +/// See also `FromSlice` +pub trait AsPixels { + /// Reinterpret the slice as a read-only/shared slice of pixels. + /// Multiple consecutive elements in the slice are intepreted as a single pixel + /// (depending on format, e.g. 3 for RGB, 4 for RGBA). + /// + /// Leftover elements are ignored if the slice isn't evenly divisible into pixels. + /// + /// Use this method only when the type is known from context. + /// See also `FromSlice`. + fn as_pixels(&self) -> &[PixelType]; + /// Reinterpret the slice as a mutable/exclusive slice of pixels. + /// Multiple consecutive elements in the slice are intepreted as a single pixel + /// (depending on format, e.g. 3 for RGB, 4 for RGBA). + /// + /// Leftover elements are ignored if the slice isn't evenly divisible into pixels. + /// + /// Use this method only when the type is known from context. + /// See also `FromSlice`. + fn as_pixels_mut(&mut self) -> &mut [PixelType]; +} + +macro_rules! as_pixels_impl { + ($typ:ident, $elems:expr) => { + impl AsPixels<$typ> for [T] { + fn as_pixels(&self) -> &[$typ] { + unsafe { + slice::from_raw_parts(self.as_ptr() as *const _, self.len() / $elems) + } + } + fn as_pixels_mut(&mut self) -> &mut [$typ] { + unsafe { + slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len() / $elems) + } + } + } + } +} + +as_pixels_impl!{RGB, 3} +as_pixels_impl!{RGBA, 4} +as_pixels_impl!{BGR, 3} +as_pixels_impl!{BGRA, 4} +#[cfg(feature = "grb")] +as_pixels_impl!{GRB, 3} +as_pixels_impl!{Gray, 1} +as_pixels_impl!{GrayAlpha, 2} +#[cfg(feature = "argb")] +as_pixels_impl!{ARGB, 4} +#[cfg(feature = "argb")] +as_pixels_impl!{ABGR, 4} + +/// Cast a slice of component values (bytes) as a slice of RGB/RGBA pixels +/// +/// If there's any incomplete pixel at the end of the slice it is ignored. +pub trait FromSlice { + /// Reinterpert slice as RGB pixels + fn as_rgb(&self) -> &[RGB]; + /// Reinterpert slice as RGBA pixels + fn as_rgba(&self) -> &[RGBA]; + /// Reinterpert slice as alpha-first ARGB pixels + #[cfg(feature = "argb")] + fn as_argb(&self) -> &[ARGB]; + /// Reinterpert mutable slice as RGB pixels + fn as_rgb_mut(&mut self) -> &mut [RGB]; + /// Reinterpert mutable slice as RGBA pixels + fn as_rgba_mut(&mut self) -> &mut [RGBA]; + /// Reinterpert mutable slice as alpha-first ARGB pixels + #[cfg(feature = "argb")] + fn as_argb_mut(&mut self) -> &mut [ARGB]; + + /// Reinterpert mutable slice as grayscale pixels + fn as_gray(&self) -> &[Gray]; + /// Reinterpert mutable slice as grayscale pixels with alpha + fn as_gray_alpha(&self) -> &[GrayAlpha]; + /// Reinterpert mutable slice as grayscale pixels + fn as_gray_mut(&mut self) -> &mut [Gray]; + /// Reinterpert mutable slice as grayscale pixels with alpha + fn as_gray_alpha_mut(&mut self) -> &mut [GrayAlpha]; + + /// Reinterpert slice as reverse-order BGR pixels + fn as_bgr(&self) -> &[BGR]; + /// Reinterpert slice as reverse-order BGRA pixels + fn as_bgra(&self) -> &[BGRA]; + /// Reinterpert slice as reverse-order ABGR pixels + #[cfg(feature = "argb")] + fn as_abgr(&self) -> &[ABGR]; + /// Reinterpert ntable slice as reverse-order BGR pixels + fn as_bgr_mut(&mut self) -> &mut [BGR]; + /// Reinterpert mutable slice as reverse-order alpha-last BGRA pixels + fn as_bgra_mut(&mut self) -> &mut [BGRA]; + /// Reinterpert mutable slice as reverse-order alpha-first ABGR pixels + #[cfg(feature = "argb")] + fn as_abgr_mut(&mut self) -> &mut [ABGR]; +} + +impl FromSlice for [T] { + #[inline] + fn as_rgb(&self) -> &[RGB] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_rgba(&self) -> &[RGBA] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + #[cfg(feature = "argb")] + fn as_argb(&self) -> &[ARGB] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_rgb_mut(&mut self) -> &mut [RGB] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + fn as_rgba_mut(&mut self) -> &mut [RGBA] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + #[cfg(feature = "argb")] + fn as_argb_mut(&mut self) -> &mut [ARGB] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + fn as_gray(&self) -> &[Gray] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_gray_alpha(&self) -> &[GrayAlpha] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_gray_mut(&mut self) -> &mut [Gray] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + fn as_gray_alpha_mut(&mut self) -> &mut [GrayAlpha] { + unsafe { from_items_to_struct_mut(self) } + } + + + #[inline] + fn as_bgr(&self) -> &[BGR] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + #[cfg(feature = "argb")] + fn as_abgr(&self) -> &[ABGR] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_bgra(&self) -> &[BGRA] { + unsafe { from_items_to_struct(self) } + } + + #[inline] + fn as_bgr_mut(&mut self) -> &mut [BGR] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + fn as_bgra_mut(&mut self) -> &mut [BGRA] { + unsafe { from_items_to_struct_mut(self) } + } + + #[inline] + #[cfg(feature = "argb")] + fn as_abgr_mut(&mut self) -> &mut [ABGR] { + unsafe { from_items_to_struct_mut(self) } + } +} + +#[inline(always)] +unsafe fn from_items_to_struct(from: &[F]) -> &[T] { + debug_assert_eq!(0, mem::size_of::() % mem::size_of::()); + let len = from.len() / (mem::size_of::() / mem::size_of::()); + slice::from_raw_parts(from.as_ptr() as *const T, len) +} + +#[inline(always)] +unsafe fn from_items_to_struct_mut(from: &mut [F]) -> &mut [T] { + debug_assert_eq!(0, mem::size_of::() % mem::size_of::()); + let len = from.len() / (mem::size_of::() / mem::size_of::()); + slice::from_raw_parts_mut(from.as_mut_ptr() as *mut T, len) +} + +macro_rules! rgb_impl_from { + ($typename:ident, $from:ty, $to:ty) => { + impl From<$typename<$from>> for $typename<$to> { + + #[inline(always)] + fn from(other: $typename<$from>) -> Self { + other.map(core::convert::Into::into) + } + } + } +} + +rgb_impl_from!{RGB, u8,i16} +rgb_impl_from!{RGB, u8,u16} +rgb_impl_from!{RGB, u8,u32} +rgb_impl_from!{RGB, u16,i32} +rgb_impl_from!{RGB, u16,u32} +rgb_impl_from!{RGB, u16,u64} + +rgb_impl_from!{RGB, u8,f32} +rgb_impl_from!{RGB, u8,f64} +rgb_impl_from!{RGB, u16,f32} +rgb_impl_from!{RGB, u16,f64} + +rgb_impl_from!{RGB, i16,f32} +rgb_impl_from!{RGB, i16,f64} + +rgb_impl_from!{RGB, i32,f64} +rgb_impl_from!{RGB, f32,f64} + +rgb_impl_from!{RGBA, u16,i32} +rgb_impl_from!{RGBA, u16,u32} +rgb_impl_from!{RGBA, u16,u64} + +rgb_impl_from!{RGBA, u8,i16} +rgb_impl_from!{RGBA, u8,u16} +rgb_impl_from!{RGBA, u8,u32} +rgb_impl_from!{RGBA, u8,f32} +rgb_impl_from!{RGBA, u8,f64} +rgb_impl_from!{RGBA, u16,f32} +rgb_impl_from!{RGBA, u16,f64} + +rgb_impl_from!{RGBA, i16,f32} +rgb_impl_from!{RGBA, i16,f64} + +rgb_impl_from!{RGBA, i32,f64} +rgb_impl_from!{RGBA, f32,f64} + +macro_rules! reorder_impl_from { + (@rgb $t1:ident, $t2:ident) => { + reorder_impl_from!(@once $t1, $t2, r, g, b); + reorder_impl_from!(@once $t2, $t1, r, g, b); + }; + (@rgba $t1:ident, $t2:ident) => { + reorder_impl_from!(@once $t1, $t2, r, g, b, a); + reorder_impl_from!(@once $t2, $t1, r, g, b, a); + }; + (@once $t1:ident, $t2:ident, $($component:ident),+) => { + impl From<$t1> for $t2 where T: ::core::clone::Clone { + fn from(other: $t1) -> Self { + let $t1 { $($component),+ } = other; + Self { + $($component),+ + } + } + } + } +} + +#[cfg(feature = "argb")] +reorder_impl_from!(@rgba RGBA, ARGB); +#[cfg(feature = "argb")] +reorder_impl_from!(@rgba ABGR, ARGB); +#[cfg(feature = "argb")] +reorder_impl_from!(@rgba BGRA, ARGB); +#[cfg(feature = "argb")] +reorder_impl_from!(@rgba BGRA, ABGR); + +reorder_impl_from!(@rgb RGB, BGR); +reorder_impl_from!(@rgba BGRA, RGBA); +#[cfg(feature = "argb")] +reorder_impl_from!(@rgba ABGR, RGBA); +#[cfg(feature = "grb")] +reorder_impl_from!(@rgb RGB, GRB); + +impl From> for RGB { + #[inline(always)] + fn from(other: Gray) -> Self { + Self { + r: other.0.clone(), + g: other.0.clone(), + b: other.0, + } + } +} + +impl From> for RGBA { + #[inline(always)] + fn from(other: Gray) -> Self { + Self { + r: other.0.clone(), + g: other.0.clone(), + b: other.0, + a: 255, + } + } +} + +impl From> for RGBA { + #[inline(always)] + fn from(other: GrayAlpha) -> Self { + Self { + r: other.0.clone(), + g: other.0.clone(), + b: other.0, + a: other.1, + } + } +} + +impl AsRef for Gray { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsRef<[T]> for RGB { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl AsRef<[T]> for RGBA { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl AsRef for GrayAlpha { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.0 + } +} + + +impl AsMut for Gray { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl AsMut<[T]> for RGB { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl AsMut<[T]> for RGBA { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl AsMut for GrayAlpha { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +#[cfg(feature = "argb")] +#[test] +fn argb_converts() { + let argb = ARGB {a: 0xffu8, r: 0xff, g: 0xff, b: 0xff}; + let rgba = RGBA {a: 0xffu8, r: 0xff, g: 0xff, b: 0xff}; + + assert_eq!(RGBA::from(argb), rgba); + assert_eq!(ARGB::from(rgba), argb); + + let bgra = BGRA {a: 0xffu8, r: 0xff, g: 0xff, b: 0xff}; + let abgr = ABGR {a: 0xffu8, r: 0xff, g: 0xff, b: 0xff}; + + assert_eq!(BGRA::from(abgr), bgra); + assert_eq!(ABGR::from(bgra), abgr); +} + + +#[test] +fn converts() { + assert_eq!([1,2].as_gray(), [Gray::new(1), Gray::new(2)]); + assert_eq!([3].as_gray_mut(), [Gray::new(3)]); + assert_eq!([1,2].as_gray_alpha(), [GrayAlpha::new(1, 2)]); + // excess bytes are ignored + assert_eq!([1,2,3].as_gray_alpha_mut(), [GrayAlpha::new(1, 2)]); + assert_eq!([1,2,3,4].as_gray_alpha_mut(), [GrayAlpha::new(1, 2), GrayAlpha::new(3, 4)]); + + assert_eq!(RGBA::new(1u8,2,3,255), RGB::new(1u8,2,3).into()); + assert_eq!(RGBA::new(1u16,2,3,65535), RGB::new(1u16,2,3).into()); + assert_eq!(BGRA{r:1u8,g:2u8,b:3u8,a:255u8}, BGR{r:1u8,g:2u8,b:3u8}.into()); + assert_eq!(BGRA{r:1u8,g:2u8,b:3u8,a:255u8}, RGB{r:1u8,g:2u8,b:3u8}.into()); + assert_eq!(RGBA {r:1u8,g:2,b:3,a:4u8}, BGRA{r:1u8,g:2u8,b:3u8,a:4u8}.into()); + assert_eq!(BGR {r:1u8,g:2,b:3u8}, RGB {r:1u8,g:2,b:3u8}.into()); + assert_eq!(RGB {r:1u16,g:0x5678,b:0xABCDu16}, BGR {r:1u16,g:0x5678,b:0xABCDu16}.into()); + assert_eq!(BGR {r:0x1234567u32,g:2,b:3u32}, RGB {r:0x1234567u32,g:2,b:3u32}.into()); + + assert_eq!(&[1u8,2,3,4], RGBA {r:1u8,g:2,b:3,a:4u8}.as_slice()); + assert_eq!(&[1u8,2,3,4], RGBA {r:1u8,g:2,b:3,a:4u8}.as_ref()); + assert_eq!(&[1u8,2,3], RGB {r:1u8,g:2,b:3}.as_slice()); + assert_eq!(&[1u8,2,3], RGB {r:1u8,g:2,b:3}.as_ref()); + + assert_eq!(&[1u8,2,3], RGB {r:1u8,g:2,b:3}.as_mut_slice()); + assert_eq!(&[1u8,2,3], RGB {r:1u8,g:2,b:3}.as_mut()); +} + diff --git a/src/internal/convert/tuple.rs b/src/internal/convert/tuple.rs new file mode 100644 index 0000000..ad2aa43 --- /dev/null +++ b/src/internal/convert/tuple.rs @@ -0,0 +1,89 @@ +use crate::alt::BGR; +use crate::alt::BGRA; +use crate::RGB; +use crate::RGBA; +use core::convert::*; + +impl From<(T,T,T)> for RGB { + #[inline] + fn from(other: (T,T,T)) -> Self { + Self { + r: other.0, + g: other.1, + b: other.2, + } + } +} + +impl Into<(T,T,T)> for RGB { + #[inline] + fn into(self) -> (T,T,T) { + (self.r, self.g, self.b) + } +} + +impl From<(T,T,T,A)> for RGBA { + #[inline] + fn from(other: (T,T,T,A)) -> Self { + Self { + r: other.0, + g: other.1, + b: other.2, + a: other.3, + } + } +} + +impl Into<(T,T,T,A)> for RGBA { + #[inline] + fn into(self) -> (T,T,T,A) { + (self.r, self.g, self.b, self.a) + } +} + +impl From<(T,T,T)> for BGR { + #[inline(always)] + fn from(other: (T,T,T)) -> Self { + Self { + b: other.0, + g: other.1, + r: other.2, + } + } +} + +impl Into<(T,T,T)> for BGR { + #[inline(always)] + fn into(self) -> (T,T,T) { + (self.b, self.g, self.r) + } +} + +impl From<(T,T,T,A)> for BGRA { + #[inline(always)] + fn from(other: (T,T,T,A)) -> Self { + Self { + b: other.0, + g: other.1, + r: other.2, + a: other.3, + } + } +} + +impl Into<(T,T,T,A)> for BGRA { + #[inline(always)] + fn into(self) -> (T,T,T,A) { + (self.b, self.g, self.r, self.a) + } +} + +#[test] +fn converts() { + assert_eq!((1,2,3), RGB {r:1u8,g:2,b:3}.into()); + assert_eq!(RGB {r:1u8,g:2,b:3}, (1,2,3).into()); + assert_eq!((1,2,3,4), RGBA {r:1,g:2,b:3,a:4}.into()); + assert_eq!(RGBA {r:1u8,g:2,b:3,a:4}, (1,2,3,4).into()); + assert_eq!(BGRA {r:1u8,g:2,b:3,a:4}, (3,2,1,4).into()); + assert_eq!(BGR {r:1u8,g:2,b:3}, (3,2,1).into()); +} diff --git a/src/internal/ops.rs b/src/internal/ops.rs new file mode 100644 index 0000000..6731d66 --- /dev/null +++ b/src/internal/ops.rs @@ -0,0 +1,412 @@ +use crate::alt::Gray; +use crate::alt::GrayAlpha; +use super::pixel::*; +use crate::RGB; +use crate::RGBA; +use core::ops::*; +use core::iter::Sum; +#[cfg(feature = "argb")] +use crate::alt::ARGB; +#[cfg(feature = "grb")] +use crate::alt::GRB; + +macro_rules! impl_struct_ops_opaque { + ($ty:ident => $($field:tt)+) => { + /// `px + px` + impl Add for $ty { + type Output = $ty<::Output>; + + #[inline(always)] + fn add(self, other: $ty) -> Self::Output { + $ty { + $( + $field: self.$field + other.$field, + )+ + } + } + } + + /// `px + px` + impl AddAssign for $ty where + T: Add + Copy + { + #[inline(always)] + fn add_assign(&mut self, other: $ty) { + *self = Self { + $( + $field: self.$field + other.$field, + )+ + }; + } + } + + /// `px * px` + impl Mul for $ty { + type Output = $ty<::Output>; + + #[inline(always)] + fn mul(self, other: $ty) -> Self::Output { + $ty { + $( + $field: self.$field * other.$field, + )+ + } + } + } + + /// `px * px` + impl MulAssign for $ty where + T: Mul + Copy + { + #[inline(always)] + fn mul_assign(&mut self, other: $ty) { + *self = Self { + $( + $field: self.$field * other.$field, + )+ + }; + } + } + + /// `px - px` + impl Sub for $ty { + type Output = $ty<::Output>; + + #[inline(always)] + fn sub(self, other: $ty) -> Self::Output { + $ty { + $( + $field: self.$field - other.$field, + )+ + } + } + } + + /// `px - px` + impl SubAssign for $ty where + T: Sub + Copy + { + #[inline(always)] + fn sub_assign(&mut self, other: $ty) { + *self = Self { + $( + $field: self.$field - other.$field, + )+ + }; + } + } + + impl Sum<$ty> for $ty where T: Default + Add { + #[inline(always)] + fn sum>(iter: I) -> Self { + iter.fold($ty::default(), Add::add) + } + } + }; +} + +macro_rules! impl_struct_ops_alpha { + ($ty:ident => $($field:tt)+) => { + /// `px + px` + impl Add for $ty { + type Output = $ty<::Output, ::Output>; + + #[inline(always)] + fn add(self, other: $ty) -> Self::Output { + $ty { + $( + $field: self.$field + other.$field, + )+ + } + } + } + + /// `px + px` + impl AddAssign for $ty where + T: Add + Copy, + A: Add + Copy + { + #[inline(always)] + fn add_assign(&mut self, other: $ty) { + *self = Self { + $( + $field: self.$field + other.$field, + )+ + }; + } + } + + /// `px - px` + impl Sub for $ty { + type Output = $ty<::Output, ::Output>; + + #[inline(always)] + fn sub(self, other: $ty) -> Self::Output { + $ty { + $( + $field: self.$field - other.$field, + )+ + } + } + } + + /// `px - px` + impl SubAssign for $ty where + T: Sub + Copy, + A: Sub + Copy + { + #[inline(always)] + fn sub_assign(&mut self, other: $ty) { + *self = Self { + $( + $field: self.$field - other.$field, + )+ + }; + } + } + + impl Sum<$ty> for $ty where T: Default + Add, A: Default + Add { + #[inline(always)] + fn sum>(iter: I) -> Self { + iter.fold($ty::default(), Add::add) + } + } + }; +} + +macro_rules! impl_scalar { + ($ty:ident) => { + /// `px - 1` + impl Sub for $ty where + T: Copy + Sub + { + type Output = $ty<::Output>; + + #[inline(always)] + fn sub(self, r: T) -> Self::Output { + self.map(|l| l-r) + } + } + + /// `px - 1` + impl SubAssign for $ty where + T: Copy + Sub + { + #[inline(always)] + fn sub_assign(&mut self, r: T) { + *self = self.map(|l| l-r); + } + } + + /// `px + 1` + impl Add for $ty where + T: Copy + Add + { + type Output = $ty; + + #[inline(always)] + fn add(self, r: T) -> Self::Output { + self.map(|l|l+r) + } + } + + /// `px + 1` + impl AddAssign for $ty where + T: Copy + Add + { + #[inline(always)] + fn add_assign(&mut self, r: T) { + *self = self.map(|l| l+r); + } + } + + /// `px * 1` + impl Mul for $ty where + T: Copy + Mul + { + type Output = $ty; + + #[inline(always)] + fn mul(self, r: T) -> Self::Output { + self.map(|l|l*r) + } + } + + /// `px * 1` + impl MulAssign for $ty where + T: Copy + Mul + { + #[inline(always)] + fn mul_assign(&mut self, r: T) { + *self = self.map(|l| l*r); + } + } + + /// `px / 1` + impl Div for $ty where + T: Copy + Div + { + type Output = $ty; + + #[inline(always)] + fn div(self, r: T) -> Self::Output { + self.map(|l| l / r) + } + } + + /// `px * 1` + impl DivAssign for $ty where + T: Copy + Div + { + #[inline(always)] + fn div_assign(&mut self, r: T) { + *self = self.map(|l| l / r); + } + } + } +} + +impl_scalar!{RGB} +impl_scalar!{RGBA} +#[cfg(feature = "argb")] +impl_scalar!{ARGB} +#[cfg(feature = "grb")] +impl_scalar!{GRB} +impl_scalar!{Gray} +impl_scalar!{GrayAlpha} + +impl_struct_ops_opaque! {RGB => r g b} +#[cfg(feature = "grb")] +impl_struct_ops_opaque! {GRB => g r b} +impl_struct_ops_opaque! {Gray => 0} + +impl_struct_ops_alpha! {RGBA => r g b a} +#[cfg(feature = "argb")] +impl_struct_ops_alpha! {ARGB => a r g b} +impl_struct_ops_alpha! {GrayAlpha => 0 1} + +#[cfg(test)] +mod test { + use super::*; + const WHITE_RGB: RGB = RGB::new(255, 255, 255); + const BLACK_RGB: RGB = RGB::new(0, 0, 0); + const RED_RGB: RGB = RGB::new(255, 0, 0); + const GREEN_RGB: RGB = RGB::new(0, 255, 0); + const BLUE_RGB: RGB = RGB::new(0, 0, 255); + + const WHITE_RGBA: RGBA = RGBA::new(255, 255, 255, 255); + const BLACK_RGBA: RGBA = RGBA::new(0, 0, 0, 0); + const RED_RGBA: RGBA = RGBA::new(255, 0, 0, 255); + const GREEN_RGBA: RGBA = RGBA::new(0, 255, 0, 0); + const BLUE_RGBA: RGBA = RGBA::new(0, 0, 255, 255); + + #[test] + fn test_add() { + assert_eq!(RGB::new(2,4,6), RGB::new(1,2,3) + RGB{r:1,g:2,b:3}); + assert_eq!(RGB::new(2.,4.,6.), RGB::new(1.,3.,5.) + 1.); + + assert_eq!(RGBA::new_alpha(2u8,4,6,8u16), RGBA::new_alpha(1u8,2,3,4u16) + RGBA{r:1u8,g:2,b:3,a:4u16}); + assert_eq!(RGBA::new(2i16,4,6,8), RGBA::new(1,3,5,7) + 1); + + assert_eq!(RGB::new(255, 255, 0), RED_RGB+GREEN_RGB); + assert_eq!(RGB::new(255, 0, 0), RED_RGB+RGB::new(0, 0, 0)); + assert_eq!(WHITE_RGB, BLACK_RGB + 255); + + assert_eq!(RGBA::new(255, 255, 0, 255), RED_RGBA+GREEN_RGBA); + assert_eq!(RGBA::new(255, 0, 0, 255), RED_RGBA+RGBA::new(0, 0, 0, 0)); + assert_eq!(WHITE_RGBA, BLACK_RGBA + 255); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn test_add_overflow() { + assert_ne!(RGBA::new(255u8, 255, 0, 0), RED_RGBA+BLUE_RGBA); + } + + #[test] + fn test_sub() { + assert_eq!(RED_RGB, (WHITE_RGB - GREEN_RGB) - BLUE_RGB); + assert_eq!(BLACK_RGB, WHITE_RGB - 255); + + assert_eq!(RGBA::new(255, 255, 0, 0), WHITE_RGBA - BLUE_RGBA); + assert_eq!(BLACK_RGBA, WHITE_RGBA - 255); + } + + #[test] + fn test_add_assign() { + let mut green_rgb = RGB::new(0, 255, 0); + green_rgb += RGB::new(255, 0, 255); + assert_eq!(WHITE_RGB, green_rgb); + + let mut black_rgb = RGB::new(0, 0, 0); + black_rgb += 255; + assert_eq!(WHITE_RGB, black_rgb); + + let mut green_rgba = RGBA::new(0, 255, 0, 0); + green_rgba += RGBA::new(255, 0, 255, 255); + assert_eq!(WHITE_RGBA, green_rgba); + + let mut black_rgba = RGBA::new(0, 0, 0, 0); + black_rgba += 255; + assert_eq!(WHITE_RGBA, black_rgba); + } + + #[test] + fn test_sub_assign() { + let mut green_rgb = RGB::new(0, 255, 0); + green_rgb -= RGB::new(0, 255, 0); + assert_eq!(BLACK_RGB, green_rgb); + + let mut white_rgb = RGB::new(255, 255, 255); + white_rgb -= 255; + assert_eq!(BLACK_RGB, white_rgb); + + let mut green_rgba = RGBA::new(0, 255, 0, 0); + green_rgba -= RGBA::new(0, 255, 0, 0); + assert_eq!(BLACK_RGBA, green_rgba); + + let mut white_rgba = RGBA::new(255, 255, 255, 255); + white_rgba -= 255; + assert_eq!(BLACK_RGBA, white_rgba); + } + + #[test] + fn test_mult() { + assert_eq!(RGB::new(0.5,1.5,2.5), RGB::new(1.,3.,5.) * 0.5); + assert_eq!(RGBA::new(2,4,6,8), RGBA::new(1,2,3,4) * 2); + assert_eq!(RGB::new(0.5,1.5,2.5) * RGB::new(1.,3.,5.), + RGB::new(0.5,4.5,12.5)); + } + + #[test] + fn test_mult_assign() { + let mut green_rgb = RGB::new(0u16, 255, 0); + green_rgb *= 1; + assert_eq!(RGB::new(0, 255, 0), green_rgb); + green_rgb *= 2; + assert_eq!(RGB::new(0, 255*2, 0), green_rgb); + + let mut rgb = RGB::new(0.5,1.5,2.5); + rgb *= RGB::new(1.,3.,5.); + assert_eq!(rgb, RGB::new(0.5,4.5,12.5)); + + let mut green_rgba = RGBA::new(0u16, 255, 0, 0); + green_rgba *= 1; + assert_eq!(RGBA::new(0, 255, 0, 0), green_rgba); + green_rgba *= 2; + assert_eq!(RGBA::new(0, 255*2, 0, 0), green_rgba); + } + + #[test] + fn sum() { + let s1 = [RGB::new(1u8,1,1), RGB::new(2,3,4)].iter().copied().sum::>(); + let s2 = [RGB::new(1u16,1,1), RGB::new(2,3,4)].iter().copied().sum::>(); + let s3 = [RGBA::new_alpha(1u8,1,1,1u16), RGBA::new_alpha(2,3,4,5)].iter().copied().sum::>(); + let s4 = [RGBA::new_alpha(1u16,1,1,1u8), RGBA::new_alpha(2,3,4,5)].iter().copied().sum::>(); + assert_eq!(s1, RGB::new(3, 4, 5)); + assert_eq!(s2, RGB::new(3, 4, 5)); + assert_eq!(s3, RGBA::new_alpha(3, 4, 5, 6)); + assert_eq!(s4, RGBA::new_alpha(3, 4, 5, 6)); + } +} diff --git a/src/internal/pixel.rs b/src/internal/pixel.rs new file mode 100644 index 0000000..f2f8e2d --- /dev/null +++ b/src/internal/pixel.rs @@ -0,0 +1,85 @@ + +/// Casting the struct to slices of its components +pub trait ComponentSlice { + /// The components interpreted as an array, e.g. one `RGB` expands to 3 elements. + /// + /// It's implemented for individual pixels as well as slices of pixels. + fn as_slice(&self) -> &[T]; + + /// The components interpreted as a mutable array, e.g. one `RGB` expands to 3 elements. + /// + /// It's implemented for individual pixels as well as slices of pixels. + /// + /// If you get an error when calling this on an array, add `[..]` + /// + /// > use of unstable library feature 'array_methods' + /// + /// ```rust,ignore + /// arr[..].as_mut_slice() + /// ``` + fn as_mut_slice(&mut self) -> &mut [T]; +} + +/// Casting a slice of `RGB/A` values to a slice of `u8` +/// +/// If instead of `RGB8` you use `RGB`, and you want to cast from/to that custom type, +/// implement the `Plain` trait for it: +/// +/// ```rust +/// # #[derive(Copy, Clone)] +/// # struct MyCustomType; +/// unsafe impl rgb::Pod for MyCustomType {} +/// unsafe impl rgb::Zeroable for MyCustomType {} +/// ``` +/// +/// Plain types are not allowed to contain struct padding, booleans, chars, enums, references or pointers. +#[cfg(feature = "as-bytes")] +pub trait ComponentBytes where Self: ComponentSlice { + /// The components interpreted as raw bytes, in machine's native endian. In `RGB` bytes of the red component are first. + #[inline] + fn as_bytes(&self) -> &[u8] { + assert_ne!(0, core::mem::size_of::()); + let slice = self.as_slice(); + unsafe { + core::slice::from_raw_parts(slice.as_ptr() as *const _, slice.len() * core::mem::size_of::()) + } + } + + /// The components interpreted as raw bytes, in machine's native endian. In `RGB` bytes of the red component are first. + #[inline] + fn as_bytes_mut(&mut self) -> &mut [u8] { + assert_ne!(0, core::mem::size_of::()); + let slice = self.as_mut_slice(); + unsafe { + core::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut _, slice.len() * core::mem::size_of::()) + } + } +} + +/// Applying operation to every component +/// +/// ```rust +/// use rgb::ComponentMap; +/// # let pixel = rgb::RGB::new(0u8,0,0); +/// let inverted = pixel.map(|c| 255 - c); +/// +/// // For simple math there are Add/Sub/Mul implementations: +/// let halved = pixel.map(|c| c / 2); +/// let doubled = pixel * 2; +/// ``` +pub trait ComponentMap { + /// Convenience function (equivalent of `self.iter().map().collect()`) for applying the same formula to every component. + /// + /// Note that it returns the pixel directly, not an Interator. + fn map(&self, f: Callback) -> DestPixel + where Callback: FnMut(SrcComponent) -> DestComponent; +} + +/// Same as `ComponentMap`, but doesn't change the alpha channel (if there's any alpha). +pub trait ColorComponentMap { + /// Convenience function for applying the same formula to every rgb/gray component, but skipping the alpha component. + /// + /// Note that it returns the pixel directly, not an Interator. + fn map_c(&self, f: Callback) -> DestPixel + where Callback: FnMut(SrcComponent) -> DestComponent; +} diff --git a/src/internal/rgb.rs b/src/internal/rgb.rs new file mode 100644 index 0000000..ad78dff --- /dev/null +++ b/src/internal/rgb.rs @@ -0,0 +1,253 @@ +use super::pixel::*; +use crate::alt::BGR; +use crate::alt::BGRA; +use crate::RGB; +use crate::RGBA; +use core::fmt; +#[cfg(feature = "grb")] +use crate::alt::GRB; + +impl RGB { + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B + #[inline(always)] + pub const fn new(r: T, g: T, b: T) -> Self { + Self { r, g, b } + } +} + +impl BGR { + /// Convenience function for creating a new pixel + /// Wargning: The order of arguments is R,G,B + #[deprecated(note="This function has a misleading order of arguments. Use BGR{} literal instead")] + #[inline(always)] + pub const fn new(r: T, g: T, b: T) -> Self { + Self { b, g, r } + } +} + +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for RGB where T: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for BGR where T: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for RGB where T: crate::Zeroable {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for BGR where T: crate::Zeroable {} + +macro_rules! impl_rgb { + ($RGB:ident) => { + impl $RGB { + /// Iterate over color components (R, G, and B) + #[inline(always)] + pub fn iter(&self) -> core::iter::Cloned> { + self.as_slice().iter().cloned() + } + } + + impl ComponentMap<$RGB, T, B> for $RGB { + #[inline(always)] + fn map(&self, mut f: F) -> $RGB + where F: FnMut(T) -> B { + $RGB { + r:f(self.r), + g:f(self.g), + b:f(self.b), + } + } + } + + impl ColorComponentMap<$RGB, T, B> for $RGB { + #[inline(always)] + fn map_c(&self, mut f: F) -> $RGB + where F: FnMut(T) -> B { + $RGB { + r:f(self.r), + g:f(self.g), + b:f(self.b), + } + } + } + + impl ComponentSlice for $RGB { + #[inline(always)] + fn as_slice(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts(self as *const Self as *const T, 3) + } + } + + #[inline(always)] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + core::slice::from_raw_parts_mut(self as *mut Self as *mut T, 3) + } + } + } + + impl ComponentSlice for [$RGB] { + #[inline] + fn as_slice(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts(self.as_ptr() as *const _, self.len() * 3) + } + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + core::slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len() * 3) + } + } + } + + #[cfg(feature = "as-bytes")] + impl ComponentBytes for [$RGB] {} + } +} + +macro_rules! impl_rgb_to_alpha { + ($RGB:ident, $RGBA:ident) => { + impl $RGB { + /// Convenience function for converting to RGBA + #[inline(always)] + pub fn alpha(&self, a: T) -> $RGBA { + $RGBA { + r: self.r.clone(), + g: self.g.clone(), + b: self.b.clone(), + a, + } + } + + /// Convenience function for converting to RGBA with alpha channel of a different type than type of the pixels + #[inline(always)] + pub fn new_alpha(&self, a: A) -> $RGBA { + $RGBA { + r: self.r.clone(), + g: self.g.clone(), + b: self.b.clone(), + a, + } + } + } + } +} + + +impl core::iter::FromIterator for RGB { + /// Takes exactly 3 elements from the iterator and creates a new instance. + /// Panics if there are fewer elements in the iterator. + #[inline(always)] + fn from_iter>(into_iter: I) -> Self { + let mut iter = into_iter.into_iter(); + Self { + r: iter.next().unwrap(), + g: iter.next().unwrap(), + b: iter.next().unwrap(), + } + } +} + +impl_rgb!{RGB} +impl_rgb_to_alpha!{RGB, RGBA} +impl_rgb!{BGR} +impl_rgb_to_alpha!{BGR, BGRA} +#[cfg(feature = "grb")] +impl_rgb!{GRB} + +impl fmt::Display for RGB { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"rgb({},{},{})", self.r,self.g,self.b) + } +} + +impl fmt::UpperHex for RGB { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"RGB {{ #{:02X}{:02X}{:02X} }}", self.r, self.g, self.b) + } +} + +impl fmt::LowerHex for RGB { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"RGB {{ #{:02x}{:02x}{:02x} }}", self.r, self.g, self.b) + } +} + +impl fmt::Display for BGR { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"bgr({},{},{})", self.b, self.g, self.r) + } +} + +impl fmt::UpperHex for BGR { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"BGR {{ #{:02X}{:02X}{:02X} }}", self.b, self.g, self.r) + } +} + +impl fmt::LowerHex for BGR { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f,"BGR {{ #{:02x}{:02x}{:02x} }}", self.b, self.g, self.r) + } +} + +#[cfg(test)] +mod rgb_test { + use super::*; + use std; + + #[test] + #[cfg(feature = "grb")] + fn grb_test() { + let grb = GRB {g:1,r:2,b:3}.map(|c| c * 2) + 1; + let rgb: crate::RGB8 = grb.into(); + assert_eq!(rgb, RGB::new(5,3,7)); + } + + #[test] + fn sanity_check() { + let neg = RGB::new(1,2,3i32).map(|x| -x); + assert_eq!(neg.r, -1); + assert_eq!(neg.g, -2); + assert_eq!(neg.b, -3); + + let mut px = RGB::new(3,4,5); + px.as_mut_slice()[1] = 111; + assert_eq!(111, px.g); + + assert_eq!(RGBA::new(250,251,252,253), RGB::new(250,251,252).alpha(253)); + + assert_eq!(RGB{r:1u8,g:2,b:3}, RGB::new(1u8,2,3)); + assert!(RGB{r:1u8,g:1,b:2} < RGB::new(2,1,1)); + + let mut h = std::collections::HashSet::new(); + h.insert(px); + assert!(h.contains(&RGB::new(3,111,5))); + assert!(!h.contains(&RGB::new(111,5,3))); + + + #[cfg(feature = "as-bytes")] + { + let v = vec![RGB::new(1u8,2,3), RGB::new(4,5,6)]; + assert_eq!(&[1,2,3,4,5,6], v.as_bytes()); + } + + assert_eq!(RGB::new(0u8,0,0), Default::default()); + } + + #[test] + #[allow(deprecated)] + fn test_fmt() { + let red_rgb = RGB::new(255, 0, 0); + let red_bgr = BGR::new(255, 0, 0); + assert_eq!("RGB { #FF0000 }", &format!("{:X}", red_rgb)); + assert_eq!("BGR { #0000FF }", &format!("{:X}", red_bgr)); + + assert_eq!("RGB { #ff0000 }", &format!("{:x}", red_rgb)); + assert_eq!("BGR { #0000ff }", &format!("{:x}", red_bgr)); + + assert_eq!("rgb(255,0,0)", &format!("{}", red_rgb)); + assert_eq!("bgr(0,0,255)", &format!("{}", red_bgr)); + } +} diff --git a/src/internal/rgba.rs b/src/internal/rgba.rs new file mode 100644 index 0000000..3804603 --- /dev/null +++ b/src/internal/rgba.rs @@ -0,0 +1,445 @@ +use core::fmt; +use crate::alt::*; +use crate::RGB; +use crate::RGBA; +use super::pixel::*; + +impl RGBA { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + pub const fn new(r: T, g: T, b: T, a: T) -> Self { + Self {r,g,b,a} + } +} + +impl RGBA { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + pub const fn new_alpha(r: T, g: T, b: T, a: A) -> Self { + Self {r,g,b,a} + } +} + +impl BGRA { + #[inline(always)] + /// Convenience function for creating a new pixel + /// Warning: The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use BGRA{} literal instead")] + pub const fn new(r: T, g: T, b: T, a: T) -> Self { + Self {r,g,b,a} + } +} + +impl BGRA { + #[inline(always)] + /// Convenience function for creating a new pixel + /// Warning: The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use BGRA{} literal instead")] + pub const fn new_alpha(r: T, g: T, b: T, a: A) -> Self { + Self {r,g,b,a} + } +} + +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for RGBA where T: crate::Pod, A: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Pod for BGRA where T: crate::Pod, A: crate::Pod {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for RGBA where T: crate::Zeroable, A: crate::Zeroable {} +#[cfg(feature = "as-bytes")] +unsafe impl crate::Zeroable for BGRA where T: crate::Zeroable, A: crate::Zeroable {} + +#[cfg(feature = "argb")] +impl ARGB { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use ARGB{} literal instead")] + pub const fn new(r: T, g: T, b: T, a: T) -> Self { + Self {r,g,b,a} + } +} + +#[cfg(feature = "argb")] +impl ARGB { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use ARGB{} literal instead")] + pub const fn new_alpha(r: T, g: T, b: T, a: A) -> Self { + Self {r,g,b,a} + } +} + +#[cfg(feature = "argb")] +impl ABGR { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use ABGR{} literal instead")] + pub const fn new(r: T, g: T, b: T, a: T) -> Self { + Self {r,g,b,a} + } +} + +#[cfg(feature = "argb")] +impl ABGR { + #[inline(always)] + /// Convenience function for creating a new pixel + /// The order of arguments is R,G,B,A + #[deprecated(note="This function has a misleading order of arguments. Use ABGR{} literal instead")] + pub const fn new_alpha(r: T, g: T, b: T, a: A) -> Self { + Self {r,g,b,a} + } +} + +#[cfg(all(feature = "as-bytes", feature = "argb"))] +unsafe impl crate::Pod for ARGB where T: crate::Pod, A: crate::Pod {} +#[cfg(all(feature = "as-bytes", feature = "argb"))] +unsafe impl crate::Pod for ABGR where T: crate::Pod, A: crate::Pod {} +#[cfg(all(feature = "as-bytes", feature = "argb"))] +unsafe impl crate::Zeroable for ARGB where T: crate::Zeroable, A: crate::Zeroable {} +#[cfg(all(feature = "as-bytes", feature = "argb"))] +unsafe impl crate::Zeroable for ABGR where T: crate::Zeroable, A: crate::Zeroable {} + +macro_rules! impl_rgba { + ($RGBA:ident) => { + impl $RGBA { + /// Iterate over all components (length=4) + #[inline(always)] + pub fn iter(&self) -> core::iter::Cloned> { + self.as_slice().iter().cloned() + } + } + + impl $RGBA { + /// Copy RGB components out of the RGBA struct + /// + /// Note: you can use `.into()` to convert between other types + #[inline(always)] + pub fn bgr(&self) -> BGR { + BGR {r:self.r.clone(), g:self.g.clone(), b:self.b.clone()} + } + } + + impl $RGBA { + /// Create new RGBA with the same alpha value, but different RGB values + #[inline(always)] + pub fn map_rgb(&self, mut f: F) -> $RGBA + where F: FnMut(T) -> U, U: Clone, B: From + Clone + { + $RGBA { + r: f(self.r), + g: f(self.g), + b: f(self.b), + a: self.a.clone().into(), + } + } + + #[inline(always)] + /// Create a new RGBA with the new alpha value, but same RGB values + pub fn alpha(&self, a: A) -> Self { + Self { + r: self.r, g: self.g, b: self.b, a, + } + } + + /// Create a new RGBA with a new alpha value created by the callback. + /// Allows changing of the type used for the alpha channel. + #[inline] + pub fn map_alpha(&self, f: F) -> $RGBA + where F: FnOnce(A) -> B { + $RGBA { + r: self.r, + g: self.g, + b: self.b, + a: f(self.a.clone()), + } + } + } + + impl ComponentMap<$RGBA, T, B> for $RGBA { + #[inline(always)] + fn map(&self, mut f: F) -> $RGBA + where + F: FnMut(T) -> B, + { + $RGBA { + r: f(self.r), + g: f(self.g), + b: f(self.b), + a: f(self.a), + } + } + } + + impl ColorComponentMap<$RGBA, T, B> for $RGBA { + #[inline(always)] + fn map_c(&self, mut f: F) -> $RGBA + where + F: FnMut(T) -> B, + { + $RGBA { + r: f(self.r), + g: f(self.g), + b: f(self.b), + a: self.a, + } + } + } + + impl ComponentSlice for $RGBA { + #[inline(always)] + fn as_slice(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts(self as *const Self as *const T, 4) + } + } + + #[inline(always)] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + core::slice::from_raw_parts_mut(self as *mut Self as *mut T, 4) + } + } + } + + impl ComponentSlice for [$RGBA] { + #[inline] + fn as_slice(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts(self.as_ptr() as *const _, self.len() * 4) + } + } + #[inline] + fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + core::slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len() * 4) + } + } + } + + #[cfg(feature = "as-bytes")] + impl ComponentBytes for [$RGBA] {} + } +} + +macro_rules! impl_alpha_conv { + ($RGB:ident, $RGBA:ident) => { + /// Assumes 255 is opaque + impl From<$RGB> for $RGBA { + #[inline(always)] + fn from(other: $RGB) -> Self { + Self { + r: other.r, + g: other.g, + b: other.b, + a: 0xFF, + } + } + } + + /// Assumes 65535 is opaque + impl From<$RGB> for $RGBA { + #[inline(always)] + fn from(other: $RGB) -> Self { + Self { + r: other.r, + g: other.g, + b: other.b, + a: 0xFFFF, + } + } + } + } +} + +impl RGBA { + /// Provide a mutable view of only RGB components (leaving out alpha). + /// Useful to change color without changing opacity. + #[inline(always)] + pub fn rgb_mut(&mut self) -> &mut RGB { + unsafe { + &mut *(self as *mut _ as *mut RGB) + } + } +} + +impl BGRA { + /// Provide a mutable view of only RGB components (leaving out alpha). + /// Useful to change color without changing opacity. + #[inline(always)] + #[deprecated(note = "This function will change. Use bgr_mut()")] + pub fn rgb_mut(&mut self) -> &mut BGR { + unsafe { + &mut *(self as *mut _ as *mut BGR) + } + } + + /// Provide a mutable view of only RGB components (leaving out alpha). + /// Useful to change color without changing opacity. + #[inline(always)] + pub fn bgr_mut(&mut self) -> &mut BGR { + unsafe { + &mut *(self as *mut _ as *mut BGR) + } + } +} + +impl core::iter::FromIterator for RGBA { + #[inline(always)] + /// Takes exactly 4 elements from the iterator and creates a new instance. + /// Panics if there are fewer elements in the iterator. + fn from_iter>(into_iter: I) -> Self { + let mut iter = into_iter.into_iter(); + Self { + r: iter.next().unwrap(), + g: iter.next().unwrap(), + b: iter.next().unwrap(), + a: iter.next().unwrap(), + } + } +} + +impl RGBA { + /// Copy RGB components out of the RGBA struct + /// + /// Note: you can use `.into()` to convert between other types + #[inline(always)] + pub fn rgb(&self) -> RGB { + RGB {r:self.r.clone(), g:self.g.clone(), b:self.b.clone()} + } +} + +impl BGRA { + /// Copy RGB components out of the RGBA struct + /// + /// Note: you can use `.into()` to convert between other types + #[inline(always)] + #[deprecated(note = "This function will change. Use bgr()")] + pub fn rgb(&self) -> BGR { + BGR {r:self.r.clone(), g:self.g.clone(), b:self.b.clone()} + } +} + +impl_rgba! {RGBA} +impl_rgba! {BGRA} +#[cfg(feature = "argb")] +impl_rgba! {ARGB} +#[cfg(feature = "argb")] +impl_rgba! {ABGR} + +impl_alpha_conv! {BGR, BGRA} +impl_alpha_conv! {RGB, BGRA} +impl_alpha_conv! {BGR, RGBA} +impl_alpha_conv! {RGB, RGBA} +#[cfg(feature = "argb")] +impl_alpha_conv! {BGR, ABGR} +#[cfg(feature = "argb")] +impl_alpha_conv! {RGB, ABGR} +#[cfg(feature = "argb")] +impl_alpha_conv! {BGR, ARGB} +#[cfg(feature = "argb")] +impl_alpha_conv! {RGB, ARGB} + +impl fmt::Display for RGBA { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "rgba({},{},{},{})", self.r, self.g, self.b, self.a) + } +} + +impl fmt::Display for BGRA { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "bgra({},{},{},{})", self.r, self.g, self.b, self.a) + } +} + +#[test] +fn rgba_test() { + let neg = RGBA::new(1,2,3i32,1000).map(|x| -x); + assert_eq!(neg.r, -1); + assert_eq!(neg.rgb().r, -1); + assert_eq!(neg.g, -2); + assert_eq!(neg.rgb().g, -2); + assert_eq!(neg.b, -3); + assert_eq!(neg.rgb().b, -3); + assert_eq!(neg.a, -1000); + assert_eq!(neg.map_alpha(|x| x+1).a, -999); + assert_eq!(neg, neg.as_slice().iter().cloned().collect()); + assert!(neg < RGBA::new(0,0,0,0)); + + let neg = RGBA::new(1u8,2,3,4).map_rgb(|c| -(c as i16)); + assert_eq!(-1i16, neg.r); + assert_eq!(4i16, neg.a); + let neg = RGBA::new(1u8,2,3,4).map_c(|c| -(c as i16)); + assert_eq!(-1i16, neg.r); + assert_eq!(4u8, neg.a); + + let mut px = RGBA{r:1,g:2,b:3,a:4}; + px.as_mut_slice()[3] = 100; + assert_eq!(1, px.rgb_mut().r); + assert_eq!(2, px.rgb_mut().g); + px.rgb_mut().b = 4; + assert_eq!(4, px.rgb_mut().b); + assert_eq!(100, px.a); + + #[cfg(feature = "as-bytes")] + { + let v = vec![RGBA::new(1u8,2,3,4), RGBA::new(5,6,7,8)]; + assert_eq!(&[1,2,3,4,5,6,7,8], v.as_bytes()); + } +} + +#[test] +#[cfg(feature = "argb")] +fn abgr_test() { + let abgr = ABGR {r:1,g:2,b:3,a:4}; + assert_eq!(4, abgr.as_slice()[0]); + use crate::AsPixels; + assert_eq!(abgr, [abgr].as_bytes().as_pixels()[0]); +} + +#[test] +#[allow(deprecated)] +fn bgra_test() { + let neg = BGRA::new(1, 2, 3i32, 1000).map(|x| -x); + let _ = neg.as_slice(); + + #[cfg(feature = "as-bytes")] + { + let _ = [neg].as_bytes(); + } + assert_eq!(neg.r, -1); + assert_eq!(neg.bgr().r, -1); + assert_eq!(neg.g, -2); + assert_eq!(neg.bgr().g, -2); + assert_eq!(neg.b, -3); + assert_eq!(neg.bgr().b, -3); + assert_eq!(neg.a, -1000); + assert_eq!(&[-3,-2,-1,-1000], neg.as_slice()); + assert!(neg < BGRA::new(0, 0, 0, 0)); + + let neg = BGRA::new(1u8, 2u8, 3u8, 4u8).map_rgb(|c| -(c as i16)); + assert_eq!(-1i16, neg.r); + assert_eq!(4i16, neg.a); + let neg = BGRA::new(1u8, 2u8, 3u8, 4u8).map_c(|c| -(c as i16)); + assert_eq!(-1i16, neg.r); + assert_eq!(4u8, neg.a); + + let mut px = BGRA{r:1,g:2,b:3,a:-9}.alpha(4); + px.as_mut_slice()[3] = 100; + assert_eq!(1, px.bgr_mut().r); + assert_eq!(2, px.bgr_mut().g); + px.bgr_mut().b = 4; + assert_eq!(4, px.bgr_mut().b); + assert_eq!(100, px.a); + + + #[cfg(feature = "as-bytes")] + { + let v = vec![BGRA::new(3u8, 2, 1, 4), BGRA::new(7, 6, 5, 8)]; + assert_eq!(&[1,2,3,4,5,6,7,8], v.as_bytes()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..905c4a0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,229 @@ +//! Basic struct for `RGB` and `RGBA` pixels. Packed, with red first, alpha last. +//! +//! This crate is intended to be the lowest common denominator for sharing `RGB`/`RGBA` bitmaps between other crates. +//! +//! The crate includes convenience functions for converting between the struct and bytes, +//! and overloaded operators that work on all channels at once. +//! +//! This crate intentionally doesn't implement color management (due to complexity of the problem), +//! but the structs can be parametrized to implement this if necessary. Other colorspaces are out of scope. +//! +//! ```rust +//! # use rgb::*; +//! let pixel = RGB8 {r:0, g:100, b:255}; +//! +//! let pixel_rgba = pixel.alpha(255); +//! let pixel = pixel_rgba.rgb(); +//! +//! let pixels = vec![pixel; 100]; +//! use rgb::ComponentBytes; // Import byte conversion trait +//! let bytes = pixels.as_bytes(); +//! +//! use rgb::ComponentMap; // Import pixel map trait +//! let half_bright = pixel.map(|channel| channel / 2); +//! let doubled = half_bright * 2; +//! # let _ = doubled; +//! ``` +#![doc(html_logo_url = "https://kornel.ski/rgb-logo.png")] +#![no_std] + +#![warn(missing_docs)] + +// std is required to run unit tests +#[cfg(test)] +#[macro_use] extern crate std; + +#[cfg(feature = "serde")] +#[macro_use] extern crate serde; + +mod internal { + pub mod convert; + pub mod ops; + pub mod pixel; + pub mod rgb; + pub mod rgba; +} + +/// BGR/BGRA alernative layouts & grayscale +/// +/// BGR might be useful for some Windows or OpenGL APIs. +pub mod alt; + +/// Re-export from `bytemuck` crate +#[cfg(feature = "as-bytes")] +pub use bytemuck::Pod; +/// Re-export from `bytemuck` crate +#[cfg(feature = "as-bytes")] +pub use bytemuck::Zeroable; + +pub use crate::internal::convert::*; +pub use crate::internal::ops::*; +pub use crate::internal::pixel::*; +pub use crate::internal::rgb::*; +pub use crate::internal::rgba::*; + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// The RGB pixel +/// +/// The component type can be `u8` (aliased as `RGB8`), `u16` (aliased as `RGB16`), +/// or any other type (but simple copyable types are recommended.) +pub struct RGB { + /// Red + pub r: ComponentType, + /// Green + pub g: ComponentType, + /// Blue + pub b: ComponentType, +} + +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +/// The RGBA pixel +/// +/// The component type can be `u8` (aliased as `RGBA8`), `u16` (aliased as `RGBA16`), +/// or any other type (but simple copyable types are recommended.) +/// +/// You can specify a different type for alpha, but it's only for special cases +/// (e.g. if you use a newtype like `RGBA, u16>`). +pub struct RGBA { + /// Red + pub r: ComponentType, + /// Green + pub g: ComponentType, + /// Blue + pub b: ComponentType, + /// Alpha + pub a: AlphaComponentType, +} + +/// 8-bit RGB +/// +/// The colorspace is technically undefined, but generally sRGB is assumed. +pub type RGB8 = RGB; + +/// 16-bit RGB in machine's native endian +/// +/// Be careful to perform byte-swapping when reading from files. +pub type RGB16 = RGB; + +/// 8-bit RGBA, alpha is last. 0 = transparent, 255 = opaque. +pub type RGBA8 = RGBA; + +/// 16-bit RGB in machine's native endian. 0 = transparent, 65535 = opaque. +/// +/// Alpha is last. +pub type RGBA16 = RGBA; + +#[test] +fn rgb_works() { + let rgb = RGB{r:0u8,g:128,b:255}.clone(); + assert_eq!(rgb.b, 255); + + assert_eq!(rgb, rgb.iter().map(|ch| ch).collect()); + + #[cfg(feature = "as-bytes")] + { + assert_eq!(0, [rgb].as_bytes()[0]); + assert_eq!(128, [rgb].as_bytes()[1]); + assert_eq!(255, [rgb].as_bytes()[2]); + } + + let rgb = RGB16{r:0u16,g:0x7F7F,b:65535}; + assert_eq!(rgb.b, 65535); + assert_eq!(rgb.as_slice()[1], 0x7F7F); + + + #[cfg(feature = "as-bytes")] + { + assert_eq!(0, [rgb].as_bytes()[0]); + assert_eq!(0, [rgb].as_bytes()[1]); + assert_eq!(0x7F, [rgb].as_bytes()[2]); + assert_eq!(0x7F, [rgb].as_bytes()[3]); + assert_eq!(0xFF, [rgb].as_bytes()[4]); + assert_eq!(0xFF, [rgb].as_bytes()[5]); + } + + assert_eq!("rgb(1,2,3)", format!("{}", RGB::new(1,2,3))); +} + +#[test] +fn sub_floats() { + assert_eq!(RGBA{r:2.5_f64, g:-1.5, b:0., a:5.}, RGBA{r:3.5_f64, g:-0.5, b:-2., a:0.} - RGBA{r:1.0_f64, g:1., b:-2., a:-5.}); +} + +#[test] +fn into() { + let a:RGB8 = RGB{r:0,g:1,b:2}; + let b:RGB = a.into(); + let c:RGB = b.into(); + let d:RGB = a.into(); + assert_eq!(c, d); +} + +#[test] +fn rgba_works() { + let rgba = RGBA{r:0u8,g:128,b:255,a:33}.clone(); + assert_eq!(rgba.b, 255); + assert_eq!(rgba.a, 33); + + assert_eq!(rgba, rgba.iter().map(|ch| ch).collect()); + + assert_eq!("rgba(1,2,3,4)", format!("{}", RGBA::new(1,2,3,4))); + + assert_eq!(rgba - rgba, RGBA::new(0,0,0,0)); +} + +#[test] +fn bytes() { + let rgb = RGB8::new(1,2,3); + + #[cfg(feature = "as-bytes")] + { + let rgb_arr = [rgb]; + let rgb_bytes = rgb_arr.as_bytes(); + assert_eq!(&[1,2,3], rgb_bytes); + assert_eq!(rgb_bytes.as_rgba().len(), 0); + assert_eq!({let t: &[RGBA8] = rgb_bytes.as_pixels(); t}.len(), 0); + assert_eq!(rgb, rgb_bytes.into_iter().cloned().collect()); + assert_eq!(&[rgb], rgb_bytes.as_rgb()); + assert_eq!(&[rgb], rgb_bytes.as_pixels()); + } + let mut rgb2 = [rgb]; + assert_eq!(rgb2[..].as_mut_slice().as_rgb_mut(), &mut [rgb]); + assert_eq!(&mut [rgb], rgb2[..].as_mut_slice().as_pixels_mut()); + + + #[cfg(feature = "as-bytes")] + { + let rgba = RGBA8::new(1,2,3,4); + let mut rgba_arr = [rgba]; + let rgba_bytes = rgba_arr.as_bytes_mut(); + assert_eq!(&[1,2,3,4], rgba_bytes); + assert_eq!(&[rgba], rgba_bytes.as_rgba()); + rgba_bytes[3] = 99; + assert_eq!(RGBA8::new(1,2,3,99), rgba_arr.as_bytes().into_iter().cloned().collect()); + } + + let rgb = RGB16::new(1,2,3); + let rgb_slice = rgb.as_slice(); + assert_eq!(&[1,2,3], rgb_slice); + assert_eq!(rgb_slice.as_rgba(), &[]); + assert_eq!(&[rgb], rgb_slice.as_rgb()); + assert_eq!(rgb, rgb_slice.into_iter().cloned().collect()); + + let rgba = RGBA16::new(1,2,3,4); + let rgba_slice = rgba.as_slice(); + assert_eq!(&[1,2,3,4], rgba_slice); + assert_eq!(&[1,2,3], rgba_slice.as_rgb()[0].as_slice()); + assert_eq!(&[rgba], rgba_slice.as_rgba()); + assert_eq!(rgba, rgba_slice.into_iter().cloned().collect()); + let mut rgba2 = [rgba]; + assert_eq!(rgba2[..].as_mut_slice().as_rgba_mut(), &mut [rgba]); + + let mut foo = vec![0u8; 8]; + foo.as_rgba_mut()[1] = RGBA::new(1,2,3,4); + assert_eq!(&[0u8,0,0,0,1,2,3,4], &foo[..]); +} -- 2.34.1