1 | //! Basic struct for `RGB` and `RGBA` pixels. Packed, with red first, alpha last. |
2 | //! |
3 | //! This crate is intended to be the lowest common denominator for sharing `RGB`/`RGBA` bitmaps between other crates. |
4 | //! |
5 | //! The crate includes convenience functions for converting between the struct and bytes, |
6 | //! and overloaded operators that work on all channels at once. |
7 | //! |
8 | //! This crate intentionally doesn't implement color management (due to complexity of the problem), |
9 | //! but the structs can be parametrized to implement this if necessary. Other colorspaces are out of scope. |
10 | //! |
11 | //! ```rust |
12 | //! # use rgb::*; |
13 | //! let pixel = RGB8 {r:0, g:100, b:255}; |
14 | //! |
15 | //! let pixel_rgba = pixel.alpha(255); |
16 | //! let pixel = pixel_rgba.rgb(); |
17 | //! |
18 | //! let pixels = vec![pixel; 100]; |
19 | //! use rgb::ComponentBytes; // Import byte conversion trait |
20 | //! let bytes = pixels.as_bytes(); |
21 | //! |
22 | //! use rgb::ComponentMap; // Import pixel map trait |
23 | //! let half_bright = pixel.map(|channel| channel / 2); |
24 | //! let doubled = half_bright * 2; |
25 | //! # let _ = doubled; |
26 | //! ``` |
27 | #![doc (html_logo_url = "https://kornel.ski/rgb-logo.png" )] |
28 | #![no_std ] |
29 | |
30 | #![warn (missing_docs)] |
31 | |
32 | // std is required to run unit tests |
33 | #[cfg (test)] |
34 | #[macro_use ] extern crate std; |
35 | |
36 | #[cfg (feature = "serde" )] |
37 | #[macro_use ] extern crate serde; |
38 | |
39 | mod internal { |
40 | pub mod convert; |
41 | pub mod ops; |
42 | pub mod pixel; |
43 | pub mod rgb; |
44 | pub mod rgba; |
45 | } |
46 | |
47 | /// BGR/BGRA alernative layouts & grayscale |
48 | /// |
49 | /// BGR might be useful for some Windows or OpenGL APIs. |
50 | pub mod alt; |
51 | |
52 | /// Re-export from `bytemuck` crate |
53 | #[cfg (feature = "as-bytes" )] |
54 | pub use bytemuck::Pod; |
55 | /// Re-export from `bytemuck` crate |
56 | #[cfg (feature = "as-bytes" )] |
57 | pub use bytemuck::Zeroable; |
58 | |
59 | pub use crate::internal::convert::*; |
60 | pub use crate::internal::ops::*; |
61 | pub use crate::internal::pixel::*; |
62 | pub use crate::internal::rgb::*; |
63 | pub use crate::internal::rgba::*; |
64 | |
65 | #[repr (C)] |
66 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
67 | #[derive (Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] |
68 | /// The RGB pixel |
69 | /// |
70 | /// The component type can be `u8` (aliased as `RGB8`), `u16` (aliased as `RGB16`), |
71 | /// or any other type (but simple copyable types are recommended.) |
72 | pub struct RGB<ComponentType> { |
73 | /// Red |
74 | pub r: ComponentType, |
75 | /// Green |
76 | pub g: ComponentType, |
77 | /// Blue |
78 | pub b: ComponentType, |
79 | } |
80 | |
81 | #[repr (C)] |
82 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
83 | #[derive (Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] |
84 | /// The RGBA pixel |
85 | /// |
86 | /// The component type can be `u8` (aliased as `RGBA8`), `u16` (aliased as `RGBA16`), |
87 | /// or any other type (but simple copyable types are recommended.) |
88 | /// |
89 | /// You can specify a different type for alpha, but it's only for special cases |
90 | /// (e.g. if you use a newtype like `RGBA<LinearLight<u16>, u16>`). |
91 | pub struct RGBA<ComponentType, AlphaComponentType = ComponentType> { |
92 | /// Red |
93 | pub r: ComponentType, |
94 | /// Green |
95 | pub g: ComponentType, |
96 | /// Blue |
97 | pub b: ComponentType, |
98 | /// Alpha |
99 | pub a: AlphaComponentType, |
100 | } |
101 | |
102 | /// 8-bit RGB |
103 | /// |
104 | /// The colorspace is technically undefined, but generally sRGB is assumed. |
105 | pub type RGB8 = RGB<u8>; |
106 | |
107 | /// 16-bit RGB in machine's native endian |
108 | /// |
109 | /// Be careful to perform byte-swapping when reading from files. |
110 | pub type RGB16 = RGB<u16>; |
111 | |
112 | /// 8-bit RGBA, alpha is last. 0 = transparent, 255 = opaque. |
113 | pub type RGBA8 = RGBA<u8>; |
114 | |
115 | /// 16-bit RGB in machine's native endian. 0 = transparent, 65535 = opaque. |
116 | /// |
117 | /// Alpha is last. |
118 | pub type RGBA16 = RGBA<u16>; |
119 | |
120 | #[test ] |
121 | fn rgb_works() { |
122 | let rgb = RGB{r:0u8,g:128,b:255}.clone(); |
123 | assert_eq!(rgb.b, 255); |
124 | |
125 | assert_eq!(rgb, rgb.iter().map(|ch| ch).collect()); |
126 | |
127 | #[cfg (feature = "as-bytes" )] |
128 | { |
129 | assert_eq!(0, [rgb].as_bytes()[0]); |
130 | assert_eq!(128, [rgb].as_bytes()[1]); |
131 | assert_eq!(255, [rgb].as_bytes()[2]); |
132 | } |
133 | |
134 | let rgb = RGB16{r:0u16,g:0x7F7F,b:65535}; |
135 | assert_eq!(rgb.b, 65535); |
136 | assert_eq!(rgb.as_slice()[1], 0x7F7F); |
137 | |
138 | |
139 | #[cfg (feature = "as-bytes" )] |
140 | { |
141 | assert_eq!(0, [rgb].as_bytes()[0]); |
142 | assert_eq!(0, [rgb].as_bytes()[1]); |
143 | assert_eq!(0x7F, [rgb].as_bytes()[2]); |
144 | assert_eq!(0x7F, [rgb].as_bytes()[3]); |
145 | assert_eq!(0xFF, [rgb].as_bytes()[4]); |
146 | assert_eq!(0xFF, [rgb].as_bytes()[5]); |
147 | } |
148 | |
149 | assert_eq!("rgb(1,2,3)" , format!("{}" , RGB::new(1,2,3))); |
150 | } |
151 | |
152 | #[test ] |
153 | fn sub_floats() { |
154 | 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.}); |
155 | } |
156 | |
157 | #[test ] |
158 | fn into() { |
159 | let a:RGB8 = RGB{r:0,g:1,b:2}; |
160 | let b:RGB<i16> = a.into(); |
161 | let c:RGB<f32> = b.into(); |
162 | let d:RGB<f32> = a.into(); |
163 | assert_eq!(c, d); |
164 | } |
165 | |
166 | #[test ] |
167 | fn rgba_works() { |
168 | let rgba: RGBA = RGBA{r:0u8,g:128,b:255,a:33}.clone(); |
169 | assert_eq!(rgba.b, 255); |
170 | assert_eq!(rgba.a, 33); |
171 | |
172 | assert_eq!(rgba, rgba.iter().map(|ch| ch).collect()); |
173 | |
174 | assert_eq!("rgba(1,2,3,4)" , format!("{}" , RGBA::new(1,2,3,4))); |
175 | |
176 | assert_eq!(rgba - rgba, RGBA::new(0,0,0,0)); |
177 | } |
178 | |
179 | #[test ] |
180 | fn bytes() { |
181 | let rgb = RGB8::new(1,2,3); |
182 | |
183 | #[cfg (feature = "as-bytes" )] |
184 | { |
185 | let rgb_arr = [rgb]; |
186 | let rgb_bytes = rgb_arr.as_bytes(); |
187 | assert_eq!(&[1,2,3], rgb_bytes); |
188 | assert_eq!(rgb_bytes.as_rgba().len(), 0); |
189 | assert_eq!({let t: &[RGBA8] = rgb_bytes.as_pixels(); t}.len(), 0); |
190 | assert_eq!(rgb, rgb_bytes.into_iter().cloned().collect()); |
191 | assert_eq!(&[rgb], rgb_bytes.as_rgb()); |
192 | assert_eq!(&[rgb], rgb_bytes.as_pixels()); |
193 | } |
194 | let mut rgb2 = [rgb]; |
195 | assert_eq!(rgb2[..].as_mut_slice().as_rgb_mut(), &mut [rgb]); |
196 | assert_eq!(&mut [rgb], rgb2[..].as_mut_slice().as_pixels_mut()); |
197 | |
198 | |
199 | #[cfg (feature = "as-bytes" )] |
200 | { |
201 | let rgba = RGBA8::new(1,2,3,4); |
202 | let mut rgba_arr = [rgba]; |
203 | let rgba_bytes = rgba_arr.as_bytes_mut(); |
204 | assert_eq!(&[1,2,3,4], rgba_bytes); |
205 | assert_eq!(&[rgba], rgba_bytes.as_rgba()); |
206 | rgba_bytes[3] = 99; |
207 | assert_eq!(RGBA8::new(1,2,3,99), rgba_arr.as_bytes().into_iter().cloned().collect()); |
208 | } |
209 | |
210 | let rgb = RGB16::new(1,2,3); |
211 | let rgb_slice = rgb.as_slice(); |
212 | assert_eq!(&[1,2,3], rgb_slice); |
213 | assert_eq!(rgb_slice.as_rgba(), &[]); |
214 | assert_eq!(&[rgb], rgb_slice.as_rgb()); |
215 | assert_eq!(rgb, rgb_slice.into_iter().cloned().collect()); |
216 | |
217 | let rgba = RGBA16::new(1,2,3,4); |
218 | let rgba_slice = rgba.as_slice(); |
219 | assert_eq!(&[1,2,3,4], rgba_slice); |
220 | assert_eq!(&[1,2,3], rgba_slice.as_rgb()[0].as_slice()); |
221 | assert_eq!(&[rgba], rgba_slice.as_rgba()); |
222 | assert_eq!(rgba, rgba_slice.into_iter().cloned().collect()); |
223 | let mut rgba2 = [rgba]; |
224 | assert_eq!(rgba2[..].as_mut_slice().as_rgba_mut(), &mut [rgba]); |
225 | |
226 | let mut foo = vec![0u8; 8]; |
227 | foo.as_rgba_mut()[1] = RGBA::new(1,2,3,4); |
228 | assert_eq!(&[0u8,0,0,0,1,2,3,4], &foo[..]); |
229 | } |
230 | |