cichlid/math/
mod.rs

1//! Collection of math Traits and functions for manipulating integers.
2//!
3//! Some including functions:
4//! - Scaling Functions (from one integer to another)
5//! - In place and batch scaling (`nscale16x3` for example).
6//! - Dimming and Brightening Functions
7//! - Fast u8 and u16 trigonometric functions
8//! - Other useful operations, such as blending integers.
9//!
10//! This module offers a couple different ways to access the m
11//! These are the raw functions for both `u8` and `u16`. Most of these methods
12//! are implemented through the [`Scaling`] trait interface, see that for better
13//! documentation of these functions.
14//!
15//! If `const` functions are desired, use the re-exported functions rather than
16//! the trait impls.
17//!
18//! [`Scaling`]: ./trait.ScalingInt.html
19
20// Credit for most of these functions goes to the authoers of the FastLED library.
21
22
23#![allow(clippy::cast_lossless)]
24
25// TODO: SIMD this stuff https://doc.rust-lang.org/core/arch/arm/index.html
26// https://doc.rust-lang.org/edition-guide/rust-2018/simd-for-faster-computing.html
27
28pub(crate) mod ext;
29pub(crate) mod lerp;
30pub(crate) mod trig;
31
32pub use math_u16_impls::blend as blend_u16;
33pub use math_u16_impls::brighten_lin as brighten_u16_lin;
34pub use math_u16_impls::brighten_raw as brighten_u16_raw;
35pub use math_u16_impls::brighten_video as brighten_u16_video;
36pub use math_u16_impls::dim_lin as dim_u16_lin;
37pub use math_u16_impls::dim_raw as dim_u16_raw;
38pub use math_u16_impls::dim_video as dim_u16_video;
39pub use math_u16_impls::nscale as nscale_u16;
40pub use math_u16_impls::nscale_x2 as nscale_u16x2;
41pub use math_u16_impls::nscale_x3 as nscale_u16x3;
42pub use math_u16_impls::nscale_x4 as nscale_u16x4;
43pub use math_u16_impls::scale as scale_u16;
44pub use math_u16_impls::scale_video as scale_u16_video;
45pub use math_u8_impls::blend as blend_u8;
46pub use math_u8_impls::brighten_lin as brighten_u8_lin;
47pub use math_u8_impls::brighten_raw as brighten_u8_raw;
48pub use math_u8_impls::brighten_video as brighten_u8_video;
49pub use math_u8_impls::dim_lin as dim_u8_lin;
50pub use math_u8_impls::dim_raw as dim_u8_raw;
51pub use math_u8_impls::dim_video as dim_u8_video;
52pub use math_u8_impls::nscale as nscale_u8;
53pub use math_u8_impls::nscale_x2 as nscale_u8x2;
54pub use math_u8_impls::nscale_x3 as nscale_u8x3;
55pub use math_u8_impls::nscale_x4 as nscale_u8x4;
56pub use math_u8_impls::scale as scale_u8;
57pub use math_u8_impls::scale_video as scale_u8_video;
58
59pub use trig::{sin_u8,cos_u8,sin_u16,cos_u16};
60
61/// Basic trigonometric functions for integers.
62pub trait Trig<Signed> {
63    fn sin(self) -> Signed;
64
65    fn cos(self) -> Signed;
66}
67
68/// Scaling, Dimming, Brightening, and other misc functions functions for integers
69/// representing scalar components.
70///
71/// These functions are extremely useful for operating on integer color components,
72/// such as the red/blue/green values seen in `RGB` color encoding.
73///
74/// # Notes on Fractional Components
75///
76/// These methods are used primarily for representing integers as fractions, rather than
77/// whole numbers. They can also be treated as fractions, percents, or some otherwise
78/// range-bounded scalar to a dimension. A more accurate way to represent this information
79/// would be to use a `f32`/`f64` and clamping the result to a pre-defined range.
80/// Integers are used as the math is significantly faster to compute, and floating values
81/// aren't always available on all target platforms.
82///
83/// For example, a `u8` takes on the range `[0:255]`. The maximum value
84/// of 255 doesn't represent the existence of 255 items, but rather being the maximum
85/// possible scalar for a dimension. Respectively, the value of 0 is the minimum value
86/// for a dimension.
87///
88/// As a by-product, these functions are saturating, hitting a ceiling at the maximum
89/// possible value, and hitting a floor at the minimum possible value (usually 0,
90/// except for `_video` functions).
91///
92/// # Terminology
93///
94/// - `_video`: The output is guaranteed to only be zero if at least one of the
95///   inputs is zero.
96/// - `_lin`: Used only in brightening and dimming functions. If the input is below
97///   half of the maximum value, the value is brightened / dimmed linearly instead of
98///   scaled.
99///
100pub trait ScalingInt {
101    /// Scales self by a second one (`scale`), which is treated as the numerator
102    /// of a fraction whose denominator is `Self::MAX`.
103    ///
104    /// In other words, it computes `i * (scale / Self::MAX)`
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// use cichlid::prelude::ScalingInt;
110    ///
111    /// assert_eq!(100u8.scale(255), 100); // 100 * 1.0
112    /// assert_eq!(100u8.scale(0), 0); // 100 * 0.0
113    /// assert_eq!(100u8.scale(255 / 2), 50); // 100 * 0.5
114    /// ```
115    fn scale(self, other: Self) -> Self;
116
117    /// The "video" version of scale.
118    ///
119    /// This version guarantees that the output will be only be zero if one
120    /// or both of the inputs are zero.  If both inputs are non-zero, the output is guaranteed
121    /// to be non-zero.
122    ///
123    /// This makes for better 'video'/LED dimming, at the cost of several additional cycles.
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use cichlid::prelude::ScalingInt;
129    ///
130    /// assert_eq!(100u8.scale_video(255), 100u8.scale(255)); // same as scale8...
131    /// assert_ne!(1u8.scale_video(1),  1u8.scale(1));  // Except scale8() == 0
132    /// ```
133    fn scale_video(self, other: Self) -> Self;
134
135    /// Dims an integer.
136    ///
137    /// The eye does not respond in a linear way to light. High speed PWM'd LEDs at 50% duty cycle
138    /// appear far brighter then the 'half as bright' you might expect.
139    ///
140    /// If you want your midpoint brightness level (for `u8`, that'd be 128) to appear half as
141    /// bright as 'full' brightness (255 for `u8`), you have to apply a dimming function.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// use cichlid::prelude::ScalingInt;
147    ///
148    /// let full_brightness: u8 = 255;
149    /// assert_eq!(255, full_brightness.dim_raw());
150    ///
151    /// let half_brightness: u8 = full_brightness / 2;
152    /// assert_eq!(63, half_brightness.dim_raw());
153    /// ```
154    fn dim_raw(self) -> Self;
155
156    /// Dims in video mode.
157    ///
158    /// This is the same as `dim_raw`, but the output of this function will only be zero if the
159    /// input is zero.
160    ///
161    /// # Example
162    ///
163    /// ```
164    /// use cichlid::prelude::ScalingInt;
165    ///
166    /// assert_eq!(255u8.dim_raw(), 255u8.dim_video());
167    /// assert_ne!(30u8.dim_raw(), 30u8.dim_video());
168    /// ```
169    fn dim_video(self) -> Self;
170
171    /// Dims an integer linearly.
172    ///
173    /// This is the same as `dim_raw`, but when `x < (Self::MAX / 2)`, the value is simply halved.
174    /// The output will only be zero if the input is zero.
175    fn dim_lin(self) -> Self;
176
177    /// Inverse of the `dim_raw` function, brightens a value.
178    fn brighten_raw(self) -> Self;
179
180    /// Inverse of the `dim_video` function, brightens a value.
181    fn brighten_video(self) -> Self;
182
183    /// Linear version of the `brighten8_raw`, that halves for values < `Self::MAX / 2`.
184    ///
185    /// Notably, this is the relative inverse of `dim_lin`.
186    fn brighten_lin(self) -> Self;
187
188    /// Blends self with another integer by the fraction `amount_of_b`.
189    fn blend(self, b: Self, amount_of_b: Self) -> Self;
190}
191
192macro_rules! doc_comment {
193    ($x:expr, $($tt:tt)*) => {
194        #[doc = $x]
195        $($tt)*
196    };
197}
198
199// nscaling macro
200macro_rules! impl_nscale_ops {
201    ($t:tt, $up:tt, $shift:expr, $mscaler:expr, $($element:tt),*) => {
202         let scaler: $up = 1 as $up + $up::from($mscaler);
203         $( *$element = (((*$element as $up) * scaler) >> $shift) as $t; )*
204    };
205}
206
207macro_rules! impl_scale_ops { ($t:tt, $up:tt, $shift:expr, $max:expr) => (
208    doc_comment!{concat!(
209        "Scale a `", stringify!($t), "` by another."),
210        #[inline(always)]
211        pub const fn scale(i: $t, scale: $t) -> $t {
212            (((i as $up) * (1 as $up + scale as $up)) >> $shift) as $t
213        }
214    }
215
216    doc_comment!{concat!(
217        "Scale a `", stringify!($t), "` by another, but in video mode.",
218        "\n\n",
219        "Video scaling guarantees the output of this function will only be zero",
220        "if-and-only-if at least one of the inputs are zero."),
221        #[inline]
222        pub const fn scale_video(i: $t, scale: $t) -> $t {
223            let x: $t = (((i as $up) * (scale as $up)) >> $shift) as $t;
224            let correction_int: $t = (i != 0) as $t;
225            let correction_scale: $t = (scale != 0) as $t;
226            let correction: $t = correction_int & correction_scale;
227            x + correction as $t
228        }}
229
230    doc_comment!{concat!("Dims a `", stringify!($t), "`."),
231        #[inline(always)]
232        pub const fn dim_raw(x: $t) -> $t {
233            scale(x, x)
234        }}
235
236    doc_comment!{concat!(
237        "Dims a `", stringify!($t), "` in video mode.",
238        "\n\n",
239        "Similar to `scale_video`, the output will only be zero if the input",
240        "is also zero."),
241        #[inline(always)]
242        pub const fn dim_video(x: $t) -> $t {
243            scale_video(x, x)
244        }}
245
246    doc_comment!{concat!(
247        "Dims a `", stringify!($t), "` similar to `dim_raw`, but linearly below a threshold.",
248        "\n\n",
249        "When the input is less than equal to`", stringify!($max / 2), "`, the output is dimmed ",
250        "by halving."),
251        #[inline]
252        pub const fn dim_lin(x: $t) -> $t {
253            const UPPER_BITS: $t = (1 << ($shift - 1));
254            let use_lin = (x & UPPER_BITS) != 0;
255            let scale_x_reg = (use_lin as $t) * scale(x, x);
256            let scale_x_lin = (!use_lin as $t) * (x.wrapping_add(1) / 2);
257            // This is just a hack to be able to use const fns.
258            scale_x_reg.wrapping_add(scale_x_lin)
259        }}
260
261    doc_comment!{concat!(
262        "Brightens a `", stringify!($t), "`.",
263        "\n\n",
264        "This is the inverse of `dim_raw`."),
265        #[inline]
266        pub const fn brighten_raw(x: $t) -> $t {
267            let ix = $max - x;
268            $max - dim_raw(ix)
269        }}
270
271    doc_comment!{concat!(
272        "Brightens a `", stringify!($t), "` but in video mode.",
273        "\n\n",
274        "This is the inverse of `dim_video`."),
275        #[inline]
276        pub const fn brighten_video(x: $t) -> $t {
277            let ix = $max - x;
278            $max - dim_video(ix)
279        }}
280
281    doc_comment!{concat!(
282        "Brightens a `", stringify!($t), "`, but linearly below a threshold.",
283        "\n\n",
284        "This is the inverse of `dim_lin`."),
285        #[inline]
286        pub const fn brighten_lin(x: $t) -> $t {
287            let ix = $max - x;
288            $max - dim_lin(ix)
289        }}
290
291    doc_comment!{concat!(
292        "Scales a single `", stringify!($t), "` in place."),
293        #[inline(always)]
294        pub fn nscale(int: &mut $t, scaler: $t) {
295            *int = scale(*int, scaler);
296        }}
297
298    doc_comment!{concat!(
299        "Inplace scaling for two `", stringify!($t), "`'s by the same value."),
300        #[inline(always)]
301        pub fn nscale_x2(int_1: &mut $t, int_2: &mut $t, scaler: $t) {
302            impl_nscale_ops!($t, $up, $shift, scaler, int_1, int_2);
303        }}
304
305    doc_comment!{concat!(
306        "Inplace scaling for three `", stringify!($t), "`'s by the same value."),
307        #[inline]
308        pub fn nscale_x3(int_1: &mut $t, int_2: &mut $t, int_3: &mut $t, scaler: $t) {
309            impl_nscale_ops!($t, $up, $shift, scaler, int_1, int_2, int_3);
310        }}
311
312    doc_comment!{concat!(
313        "Inplace scaling for four `", stringify!($t), "`'s by the same value."),
314        #[inline]
315        pub fn nscale_x4(int_1: &mut $t, int_2: &mut $t, int_3: &mut $t, int_4: &mut $t, scaler: $t) {
316            impl_nscale_ops!($t, $up, $shift, scaler, int_1, int_2, int_3, int_4);
317        }}
318
319
320    doc_comment!{concat!(
321        "Blends a `", stringify!($t), "`another integer by the fraction `amount_of_b`."),
322        #[inline]
323        pub const fn blend(a: $t, b: $t, amount_of_b: $t) -> $t {
324            let amount_of_a: $up = ($max - amount_of_b) as $up;
325            let mut partial: $up = 0;
326            partial += a as $up * amount_of_a as $up;
327            partial += a as $up;
328            partial += b as $up * amount_of_b as $up;
329            partial += b as $up;
330            (partial >> $shift) as $t
331        }}
332    )
333}
334
335// Re exports a function name to be used through another.
336//
337// Great for creating shimmy traits with already made functions underneath.
338macro_rules! impl_scaling_trait_rename {
339    ($t:tt, $fname:ident) => (
340        #[inline(always)]
341        fn $fname(self) -> $t {
342            $fname(self)
343        }
344    );
345    ($t:tt, $param:ident, $fname:ident) => (
346        #[inline(always)]
347        fn $fname(self, $param: $t) -> $t {
348            $fname(self, $param)
349        }
350    );
351
352    ($t:tt, $param_1:ident, $param_2:ident, $fname:ident) => (
353        #[inline(always)]
354        fn $fname(self, $param_1: $t, $param_2: $t) -> $t {
355            $fname(self, $param_1, $param_2)
356        }
357    );
358}
359
360macro_rules! impl_scaling_trait {
361    ($t:tt) => {
362        impl crate::math::ScalingInt for $t {
363            impl_scaling_trait_rename!($t, other, scale);
364            impl_scaling_trait_rename!($t, other, scale_video);
365            impl_scaling_trait_rename!($t, dim_raw);
366            impl_scaling_trait_rename!($t, dim_video);
367            impl_scaling_trait_rename!($t, dim_lin);
368            impl_scaling_trait_rename!($t, brighten_raw);
369            impl_scaling_trait_rename!($t, brighten_video);
370            impl_scaling_trait_rename!($t, brighten_lin);
371            impl_scaling_trait_rename!($t, b, amount_of_b, blend);
372        }
373    };
374}
375
376mod math_u8_impls {
377    //! Math functions for `u8`s. Includes scaling, dimming, brightening.
378    //!
379    //! Better documentation for these functions can be found under [`ScalingInt`].
380    //!
381    //! [`ScalingInt`]: ../trait.ScalingInt.html
382    impl_scale_ops!(u8, u16, 8, 255);
383    impl_scaling_trait!(u8);
384}
385
386mod math_u16_impls {
387    //! Math functions for `u16`s. Includes scaling, dimming, brightening.
388    //!
389    //! Better documentation for these functions can be found under [`ScalingInt`].
390    //!
391    //! [`ScalingInt`]: ../trait.ScalingInt.html
392    impl_scale_ops!(u16, u32, 16, 65535);
393    impl_scaling_trait!(u16);
394}