arcdps_imgui/color.rs
1/// Wraps u32 that represents a packed RGBA color. Mostly used by types in the
2/// low level custom drawing API, such as [`DrawListMut`](crate::DrawListMut).
3///
4/// The bits of a color are in "`0xAABBGGRR`" format (e.g. RGBA as little endian
5/// bytes). For clarity: we don't support an equivalent to the
6/// `IMGUI_USE_BGRA_PACKED_COLOR` define.
7///
8/// This used to be named `ImColor32`, but was renamed to avoid confusion with
9/// the type with that name in the C++ API (which uses 32 bits per channel).
10///
11/// While it doesn't provide methods to access the fields, they can be accessed
12/// via the `Deref`/`DerefMut` impls it provides targeting
13/// [`imgui::color::ImColor32Fields`](crate::color::ImColor32Fields), which has
14/// no other meaningful uses.
15///
16/// # Example
17/// ```
18/// let mut c = arcdps_imgui::ImColor32::from_rgba(0x80, 0xc0, 0x40, 0xff);
19/// assert_eq!(c.to_bits(), 0xff_40_c0_80); // Note: 0xAA_BB_GG_RR
20/// // Field access
21/// assert_eq!(c.r, 0x80);
22/// assert_eq!(c.g, 0xc0);
23/// assert_eq!(c.b, 0x40);
24/// assert_eq!(c.a, 0xff);
25/// c.b = 0xbb;
26/// assert_eq!(c.to_bits(), 0xff_bb_c0_80);
27/// ```
28#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
29#[repr(transparent)]
30pub struct ImColor32(u32); // TBH maybe the wrapped field should be `pub`.
31
32impl ImColor32 {
33 /// Convenience constant for solid black.
34 pub const BLACK: Self = Self(0xff_00_00_00);
35
36 /// Convenience constant for solid white.
37 pub const WHITE: Self = Self(0xff_ff_ff_ff);
38
39 /// Convenience constant for full transparency.
40 pub const TRANSPARENT: Self = Self(0);
41
42 /// Construct a color from 4 single-byte `u8` channel values, which should
43 /// be between 0 and 255.
44 #[inline]
45 pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
46 Self(
47 ((a as u32) << Self::A_SHIFT)
48 | ((r as u32) << Self::R_SHIFT)
49 | ((g as u32) << Self::G_SHIFT)
50 | ((b as u32) << Self::B_SHIFT),
51 )
52 }
53
54 /// Construct a fully opaque color from 3 single-byte `u8` channel values.
55 /// Same as [`Self::from_rgba`] with a == 255
56 #[inline]
57 pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
58 Self::from_rgba(r, g, b, 0xff)
59 }
60
61 /// Construct a fully opaque color from 4 `f32` channel values in the range
62 /// `0.0 ..= 1.0` (values outside this range are clamped to it, with NaN
63 /// mapped to 0.0).
64 ///
65 /// Note: No alpha premultiplication is done, so your input should be have
66 /// premultiplied alpha if needed.
67 #[inline]
68 // not const fn because no float math in const eval yet 😩
69 pub fn from_rgba_f32s(r: f32, g: f32, b: f32, a: f32) -> Self {
70 Self::from_rgba(
71 f32_to_u8_sat(r),
72 f32_to_u8_sat(g),
73 f32_to_u8_sat(b),
74 f32_to_u8_sat(a),
75 )
76 }
77
78 /// Return the channels as an array of f32 in `[r, g, b, a]` order.
79 #[inline]
80 pub fn to_rgba_f32s(self) -> [f32; 4] {
81 let &ImColor32Fields { r, g, b, a } = &*self;
82 [
83 u8_to_f32_sat(r),
84 u8_to_f32_sat(g),
85 u8_to_f32_sat(b),
86 u8_to_f32_sat(a),
87 ]
88 }
89
90 /// Return the channels as an array of u8 in `[r, g, b, a]` order.
91 #[inline]
92 pub fn to_rgba(self) -> [u8; 4] {
93 let &ImColor32Fields { r, g, b, a } = &*self;
94 [r, g, b, a]
95 }
96
97 /// Equivalent to [`Self::from_rgba_f32s`], but with an alpha of 1.0 (e.g.
98 /// opaque).
99 #[inline]
100 pub fn from_rgb_f32s(r: f32, g: f32, b: f32) -> Self {
101 Self::from_rgba(f32_to_u8_sat(r), f32_to_u8_sat(g), f32_to_u8_sat(b), 0xff)
102 }
103
104 /// Construct a color from the `u32` that makes up the bits in `0xAABBGGRR`
105 /// format.
106 ///
107 /// Specifically, this takes the RGBA values as a little-endian u32 with 8
108 /// bits per channel.
109 ///
110 /// Note that [`ImColor32::from_rgba`] may be a bit easier to use.
111 #[inline]
112 pub const fn from_bits(u: u32) -> Self {
113 Self(u)
114 }
115
116 /// Return the bits of the color as a u32. These are in "`0xAABBGGRR`" format, that
117 /// is, little-endian RGBA with 8 bits per channel.
118 #[inline]
119 pub const fn to_bits(self) -> u32 {
120 self.0
121 }
122
123 // These are public in C++ ImGui, should they be public here?
124 /// The number of bits to shift the byte of the red channel. Always 0.
125 const R_SHIFT: u32 = 0;
126 /// The number of bits to shift the byte of the green channel. Always 8.
127 const G_SHIFT: u32 = 8;
128 /// The number of bits to shift the byte of the blue channel. Always 16.
129 const B_SHIFT: u32 = 16;
130 /// The number of bits to shift the byte of the alpha channel. Always 24.
131 const A_SHIFT: u32 = 24;
132}
133
134impl Default for ImColor32 {
135 #[inline]
136 fn default() -> Self {
137 Self::TRANSPARENT
138 }
139}
140
141impl std::fmt::Debug for ImColor32 {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.debug_struct("ImColor32")
144 .field("r", &self.r)
145 .field("g", &self.g)
146 .field("b", &self.b)
147 .field("a", &self.a)
148 .finish()
149 }
150}
151
152/// A struct that exists to allow field access to [`ImColor32`]. It essentially
153/// exists to be a `Deref`/`DerefMut` target and provide field access.
154///
155/// Note that while this is repr(C), be aware that on big-endian machines
156/// (`cfg(target_endian = "big")`) the order of the fields is reversed, as this
157/// is a view into a packed u32.
158///
159/// Generally should not be used, except as the target of the `Deref` impl of
160/// [`ImColor32`].
161#[derive(Copy, Clone, Debug)]
162#[repr(C, align(4))]
163// Should this be #[non_exhaustive] to discourage direct use?
164#[rustfmt::skip]
165pub struct ImColor32Fields {
166 #[cfg(target_endian = "little")] pub r: u8,
167 #[cfg(target_endian = "little")] pub g: u8,
168 #[cfg(target_endian = "little")] pub b: u8,
169 #[cfg(target_endian = "little")] pub a: u8,
170 // TODO(someday): i guess we should have BE tests, but for now I verified
171 // this locally.
172 #[cfg(target_endian = "big")] pub a: u8,
173 #[cfg(target_endian = "big")] pub b: u8,
174 #[cfg(target_endian = "big")] pub g: u8,
175 #[cfg(target_endian = "big")] pub r: u8,
176}
177
178// We assume that big and little are the only endiannesses, and that exactly one
179// is set. That is, PDP endian is not in use, and the we aren't using a
180// completely broken custom target json or something.
181#[cfg(any(
182 all(target_endian = "little", target_endian = "big"),
183 all(not(target_endian = "little"), not(target_endian = "big")),
184))]
185compile_error!("`cfg(target_endian = \"little\")` must be `cfg(not(target_endian = \"big\")`");
186
187// static assert sizes match
188const _: [(); core::mem::size_of::<ImColor32>()] = [(); core::mem::size_of::<ImColor32Fields>()];
189const _: [(); core::mem::align_of::<ImColor32>()] = [(); core::mem::align_of::<ImColor32Fields>()];
190
191impl core::ops::Deref for ImColor32 {
192 type Target = ImColor32Fields;
193 #[inline]
194 fn deref(&self) -> &ImColor32Fields {
195 // Safety: we statically assert the size and align match, and neither
196 // type has any special invariants.
197 unsafe { &*(self as *const Self as *const ImColor32Fields) }
198 }
199}
200impl core::ops::DerefMut for ImColor32 {
201 #[inline]
202 fn deref_mut(&mut self) -> &mut ImColor32Fields {
203 // Safety: we statically assert the size and align match, and neither
204 // type has any special invariants.
205 unsafe { &mut *(self as *mut Self as *mut ImColor32Fields) }
206 }
207}
208
209impl From<ImColor32> for u32 {
210 #[inline]
211 fn from(color: ImColor32) -> Self {
212 color.0
213 }
214}
215
216impl From<u32> for ImColor32 {
217 #[inline]
218 fn from(color: u32) -> Self {
219 ImColor32(color)
220 }
221}
222
223impl From<[f32; 4]> for ImColor32 {
224 #[inline]
225 fn from(v: [f32; 4]) -> Self {
226 Self::from_rgba_f32s(v[0], v[1], v[2], v[3])
227 }
228}
229
230impl From<(f32, f32, f32, f32)> for ImColor32 {
231 #[inline]
232 fn from(v: (f32, f32, f32, f32)) -> Self {
233 Self::from_rgba_f32s(v.0, v.1, v.2, v.3)
234 }
235}
236
237impl From<[f32; 3]> for ImColor32 {
238 #[inline]
239 fn from(v: [f32; 3]) -> Self {
240 Self::from_rgb_f32s(v[0], v[1], v[2])
241 }
242}
243
244impl From<(f32, f32, f32)> for ImColor32 {
245 fn from(v: (f32, f32, f32)) -> Self {
246 Self::from_rgb_f32s(v.0, v.1, v.2)
247 }
248}
249
250impl From<ImColor32> for [f32; 4] {
251 #[inline]
252 fn from(v: ImColor32) -> Self {
253 v.to_rgba_f32s()
254 }
255}
256impl From<ImColor32> for (f32, f32, f32, f32) {
257 #[inline]
258 fn from(color: ImColor32) -> Self {
259 let [r, g, b, a]: [f32; 4] = color.into();
260 (r, g, b, a)
261 }
262}
263
264// These utilities might be worth making `pub` as free functions in
265// `crate::color` so user code can ensure their numeric handling is
266// consistent...
267
268/// Clamp `v` to between 0.0 and 1.0, always returning a value between those.
269///
270/// Never returns NaN, or -0.0 — instead returns +0.0 for these (We differ from
271/// C++ Dear ImGUI here which probably is just ignoring values like these).
272#[inline]
273pub(crate) fn saturate(v: f32) -> f32 {
274 // Note: written strangely so that special values (NaN/-0.0) are handled
275 // automatically with no extra checks.
276 if v > 0.0 {
277 if v <= 1.0 {
278 v
279 } else {
280 1.0
281 }
282 } else {
283 0.0
284 }
285}
286
287/// Quantize a value in `0.0..=1.0` to `0..=u8::MAX`. Input outside 0.0..=1.0 is
288/// clamped. Uses a bias of 0.5, because we assume centered quantization is used
289/// (and because C++ imgui does it too). See:
290/// - https://github.com/ocornut/imgui/blob/e28b51786eae60f32c18214658c15952639085a2/imgui_internal.h#L218
291/// - https://cbloomrants.blogspot.com/2020/09/topics-in-quantization-for-games.html
292/// (see `quantize_centered`)
293#[inline]
294pub(crate) fn f32_to_u8_sat(f: f32) -> u8 {
295 let f = saturate(f) * 255.0 + 0.5;
296 // Safety: `saturate`'s result is between 0.0 and 1.0 (never NaN even for
297 // NaN input), and so for all inputs, `saturate(f) * 255.0 + 0.5` is inside
298 // `0.5 ..= 255.5`.
299 //
300 // This is verified for all f32 in `test_f32_to_u8_sat_exhaustive`.
301 //
302 // Also note that LLVM doesn't bother trying to figure this out so the
303 // unchecked does actually help. (That said, this likely doesn't matter
304 // for imgui-rs, but I had this code in another project and it felt
305 // silly to needlessly pessimize it).
306 unsafe { f.to_int_unchecked() }
307}
308
309/// Opposite of `f32_to_u8_sat`. Since we assume centered quantization, this is
310/// equivalent to dividing by 255 (or, multiplying by 1.0/255.0)
311#[inline]
312pub(crate) fn u8_to_f32_sat(u: u8) -> f32 {
313 (u as f32) * (1.0 / 255.0)
314}
315
316#[test]
317fn check_sat() {
318 assert_eq!(saturate(1.0), 1.0);
319 assert_eq!(saturate(0.5), 0.5);
320 assert_eq!(saturate(0.0), 0.0);
321 assert_eq!(saturate(-1.0), 0.0);
322 // next float from 1.0
323 assert_eq!(saturate(1.0 + f32::EPSILON), 1.0);
324 // prev float from 0.0 (Well, from -0.0)
325 assert_eq!(saturate(-f32::MIN_POSITIVE), 0.0);
326 // some NaNs.
327 assert_eq!(saturate(f32::NAN), 0.0);
328 assert_eq!(saturate(-f32::NAN), 0.0);
329 // neg zero comes through as +0
330 assert_eq!(saturate(-0.0).to_bits(), 0.0f32.to_bits());
331}
332
333// Check that the unsafe in `f32_to_u8_sat` is fine for all f32 (and that the
334// comments I wrote about `saturate` are actually true). This is way too slow in
335// debug mode, but finishes in ~15s on my machine for release (just this test).
336// This is tested in CI, but will only run if invoked manually with something
337// like: `cargo test -p imgui --release -- --ignored`.
338#[test]
339#[ignore]
340fn test_f32_to_u8_sat_exhaustive() {
341 for f in (0..=u32::MAX).map(f32::from_bits) {
342 let v = saturate(f);
343 assert!(
344 (0.0..=1.0).contains(&v) && (v.to_bits() != (-0.0f32).to_bits()),
345 "sat({} [e.g. {:#x}]) => {} [e.g {:#x}]",
346 f,
347 f.to_bits(),
348 v,
349 v.to_bits(),
350 );
351 let sat = v * 255.0 + 0.5;
352 // Note: This checks what's required by is the safety predicate for
353 // `f32::to_int_unchecked`:
354 // https://doc.rust-lang.org/std/primitive.f32.html#method.to_int_unchecked
355 assert!(
356 sat.trunc() >= 0.0 && sat.trunc() <= (u8::MAX as f32) && sat.is_finite(),
357 "f32_to_u8_sat({} [e.g. {:#x}]) would be UB!",
358 f,
359 f.to_bits(),
360 );
361 }
362}
363
364#[test]
365fn test_saturate_all_u8s() {
366 for u in 0..=u8::MAX {
367 let v = f32_to_u8_sat(u8_to_f32_sat(u));
368 assert_eq!(u, v);
369 }
370}