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}