use num_traits::Float;
use std::f64::consts::PI;
use std::cmp::PartialEq;
use std::ops::{Add, Sub};
use cast;
macro_rules! make_hues {
($($(#[$doc:meta])+ struct $name:ident;)+) => ($(
$(#[$doc])+
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct $name<T: Float = f32>(T);
impl<T: Float> $name<T> {
#[inline]
pub fn from_degrees(degrees: T) -> $name<T> {
$name(degrees)
}
#[inline]
pub fn from_radians(radians: T) -> $name<T> {
$name(radians * cast(180.0) / cast(PI))
}
#[inline]
pub fn to_degrees(self) -> T {
normalize_angle(self.0)
}
#[inline]
pub fn to_radians(self) -> T {
normalize_angle(self.0) * cast(PI) / cast(180.0)
}
#[inline]
pub fn to_positive_degrees(self) -> T {
normalize_angle_positive(self.0)
}
#[inline]
pub fn to_positive_radians(self) -> T {
normalize_angle_positive(self.0) * cast(PI) / cast(180.0)
}
#[inline]
pub fn to_raw_degrees(self) -> T {
self.0
}
#[inline]
pub fn to_raw_radians(self) -> T {
self.0 * cast(PI) / cast(180.0)
}
}
impl<T: Float> From<T> for $name<T> {
#[inline]
fn from(degrees: T) -> $name<T> {
$name(degrees)
}
}
impl Into<f64> for $name<f64> {
#[inline]
fn into(self) -> f64 {
normalize_angle(self.0)
}
}
impl Into<f32> for $name<f32> {
#[inline]
fn into(self) -> f32 {
normalize_angle(self.0)
}
}
impl Into<f32> for $name<f64> {
#[inline]
fn into(self) -> f32 {
normalize_angle(self.0) as f32
}
}
impl<T: Float> PartialEq for $name<T> {
#[inline]
fn eq(&self, other: &$name<T>) -> bool {
let hue_s: T = (*self).to_degrees();
let hue_o: T = (*other).to_degrees();
hue_s.eq(&hue_o)
}
}
impl<T: Float> PartialEq<T> for $name<T> {
#[inline]
fn eq(&self, other: &T) -> bool {
let hue: T = (*self).to_degrees();
hue.eq(&normalize_angle(*other))
}
}
impl<T: Float> Add<$name<T>> for $name<T> {
type Output = $name<T>;
#[inline]
fn add(self, other: $name<T>) -> $name<T> {
$name(self.0 + other.0)
}
}
impl<T: Float> Add<T> for $name<T> {
type Output = $name<T>;
#[inline]
fn add(self, other: T) -> $name<T> {
$name(self.0 + other)
}
}
impl Add<$name<f32>> for f32 {
type Output = $name<f32>;
#[inline]
fn add(self, other: $name<f32>) -> $name<f32> {
$name(self + other.0)
}
}
impl Add<$name<f64>> for f64 {
type Output = $name<f64>;
#[inline]
fn add(self, other: $name<f64>) -> $name<f64> {
$name(self + other.0)
}
}
impl<T: Float> Sub<$name<T>> for $name<T> {
type Output = $name<T>;
#[inline]
fn sub(self, other: $name<T>) -> $name<T> {
$name(self.0 - other.0)
}
}
impl<T: Float> Sub<T> for $name<T> {
type Output = $name<T>;
#[inline]
fn sub(self, other: T) -> $name<T> {
$name(self.0 - other)
}
}
impl Sub<$name<f32>> for f32 {
type Output = $name<f32>;
#[inline]
fn sub(self, other: $name<f32>) -> $name<f32> {
$name(self - other.0)
}
}
impl Sub<$name<f64>> for f64 {
type Output = $name<f64>;
#[inline]
fn sub(self, other: $name<f64>) -> $name<f64> {
$name(self - other.0)
}
}
)+)
}
make_hues! {
struct LabHue;
struct RgbHue;
}
#[inline]
fn normalize_angle<T: Float>(deg: T) -> T {
let c360 = cast(360.0);
let c180 = cast(180.0);
deg - (((deg + c180) / c360) - T::one()).ceil() * c360
}
#[inline]
fn normalize_angle_positive<T: Float>(deg: T) -> T {
let c360 = cast(360.0);
deg - ((deg / c360).floor() * c360)
}
#[cfg(test)]
mod test {
use RgbHue;
use super::{normalize_angle, normalize_angle_positive};
#[test]
fn normalize_angle_0_360() {
let inp = [
-1000.0_f32,
-900.0,
-360.5,
-360.0,
-359.5,
-240.0,
-180.5,
-180.0,
-179.5,
-90.0,
-0.5,
0.0,
0.5,
90.0,
179.5,
180.0,
180.5,
240.0,
359.5,
360.0,
360.5,
900.0,
1000.0,
];
let expected = [
80.0_f32, 180.0, 359.5, 0.0, 0.5, 120.0, 179.5, 180.0, 180.5, 270.0, 359.5, 0.0, 0.5,
90.0, 179.5, 180.0, 180.5, 240.0, 359.5, 0.0, 0.5, 180.0, 280.0,
];
let result: Vec<f32> = inp.iter().map(|x| normalize_angle_positive(*x)).collect();
for (res, exp) in result.iter().zip(expected.iter()) {
assert_eq!(res, exp);
}
}
#[test]
fn normalize_angle_180_180() {
let inp = [
-1000.0_f32,
-900.0,
-360.5,
-360.0,
-359.5,
-240.0,
-180.5,
-180.0,
-179.5,
-90.0,
-0.5,
0.0,
0.5,
90.0,
179.5,
180.0,
180.5,
240.0,
359.5,
360.0,
360.5,
900.0,
1000.0,
];
let expected = [
80.0, 180.0, -0.5, 0.0, 0.5, 120.0, 179.5, 180.0, -179.5, -90.0, -0.5, 0.0, 0.5, 90.0,
179.5, 180.0, -179.5, -120.0, -0.5, 0.0, 0.5, 180.0, -80.0,
];
let result: Vec<f32> = inp.iter().map(|x| normalize_angle(*x)).collect();
for (res, exp) in result.iter().zip(expected.iter()) {
assert_eq!(res, exp);
}
}
#[test]
fn float_conversion() {
for i in -180..180 {
let hue = RgbHue::from(4.0 * i as f32);
let degs = hue.to_degrees();
assert!(degs > -180.0 && degs <= 180.0);
let pos_degs = hue.to_positive_degrees();
assert!(pos_degs >= 0.0 && pos_degs < 360.0);
assert_eq!(RgbHue::from(degs), RgbHue::from(pos_degs));
}
}
#[cfg(feature = "serde")]
#[test]
fn serialize() {
let serialized = ::serde_json::to_string(&RgbHue::from_degrees(10.2)).unwrap();
assert_eq!(serialized, "10.2");
}
#[cfg(feature = "serde")]
#[test]
fn deserialize() {
let deserialized: RgbHue = ::serde_json::from_str("10.2").unwrap();
assert_eq!(deserialized, RgbHue::from_degrees(10.2));
}
}