diff --git a/examples/spinning_cube/src/lib.rs b/examples/spinning_cube/src/lib.rs index dfb96c09d..aefa1a579 100644 --- a/examples/spinning_cube/src/lib.rs +++ b/examples/spinning_cube/src/lib.rs @@ -64,7 +64,7 @@ impl RustTest { if let Some(mat) = owner.get_surface_material(0) { let mat = unsafe { mat.assume_safe() }; let mat = mat.cast::().expect("Incorrect material"); - mat.set_albedo(Color::rgba(self.time.cos().abs(), 0.0, 0.0, 1.0)); + mat.set_albedo(Color::from_rgba(self.time.cos().abs(), 0.0, 0.0, 1.0)); } } } diff --git a/gdnative-core/src/core_types/color.rs b/gdnative-core/src/core_types/color.rs index 613add50e..c482f98e9 100644 --- a/gdnative-core/src/core_types/color.rs +++ b/gdnative-core/src/core_types/color.rs @@ -14,16 +14,44 @@ pub struct Color { } impl Color { + #[deprecated] #[inline] pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { Color { r, g, b, a } } + #[deprecated] #[inline] pub fn rgb(r: f32, g: f32, b: f32) -> Color { Color { r, g, b, a: 1.0 } } + #[inline] + pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color { + Color { r, g, b, a } + } + + #[inline] + pub fn from_rgb(r: f32, g: f32, b: f32) -> Color { + Color { r, g, b, a: 1.0 } + } + + #[inline] + pub fn from_hsv(h: f32, s: f32, v: f32) -> Color { + Color::from_hsva(h, s, v, 1.0) + } + + #[inline] + pub fn from_hsva(h: f32, s: f32, v: f32, a: f32) -> Color { + let color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }; + Color::from_sys(unsafe { (get_api().godot_color_from_hsv)(color.sys(), h, s, v, a) }) + } + #[inline] pub fn h(&self) -> f32 { unsafe { (get_api().godot_color_get_h)(self.sys()) } @@ -48,6 +76,7 @@ impl Color { a: self.a + (weight * (other.a - self.a)), } } + #[inline] pub fn blend(&self, other: &Color) -> Color { Color::from_sys(unsafe { (get_api().godot_color_blend)(self.sys(), other.sys()) }) @@ -57,15 +86,12 @@ impl Color { pub fn contrasted(&self) -> Color { Color::from_sys(unsafe { (get_api().godot_color_contrasted)(self.sys()) }) } + #[inline] pub fn darkened(&self, amount: f32) -> Color { Color::from_sys(unsafe { (get_api().godot_color_darkened)(self.sys(), amount) }) } - #[inline] - pub fn from_hsv(h: f32, s: f32, v: f32, a: f32) -> Color { - let color = Color::rgba(0.0, 0.0, 0.0, 0.0); - Color::from_sys(unsafe { (get_api().godot_color_from_hsv)(color.sys(), h, s, v, a) }) - } + #[inline] pub fn gray(&self) -> f32 { // Implemented as described in godot docs @@ -76,39 +102,105 @@ impl Color { pub fn inverted(&self) -> Color { // Implementation as described in godot docs. Color { - r: 1.0f32 - self.r, - g: 1.0f32 - self.g, - b: 1.0f32 - self.b, + r: 1.0 - self.r, + g: 1.0 - self.g, + b: 1.0 - self.b, a: self.a, } } - pub fn to_abgr32(&self) -> i32 { - unsafe { (get_api().godot_color_to_abgr32)(self.sys()) } + #[inline] + pub fn to_html(&self, with_alpha: bool) -> GodotString { + GodotString::from_sys(unsafe { (get_api().godot_color_to_html)(self.sys(), with_alpha) }) } - pub fn to_abgr64(&self) -> i32 { - unsafe { (get_api().godot_color_to_abgr64)(self.sys()) } + /// Returns the reverse of the RGBA32 byte representation for this color where each byte represents a component of the ABGR profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in ABGR byte order + /// On little endian machines this is stored in RGBA byte order + /// # Example + /// `0x00FF7FFF` would be the equivalent to `Color::from_rgba(1.0, 0.5, 1.0, 0.0)` + #[inline] + pub fn to_abgr32(&self) -> u32 { + ((self.a * 255.0) as u32) << 24 + | ((self.b * 255.0) as u32) << 16 + | ((self.g * 255.0) as u32) << 8 + | (self.r * 255.0) as u32 } - pub fn to_argb32(&self) -> i32 { - unsafe { (get_api().godot_color_to_argb32)(self.sys()) } + /// Returns the reverse of the RGBA64 byte representation for this color where each word represents represents a component of the ABGR profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in ABGR word order + /// On little endian machines this is stored in RGBA word order + /// # Example + /// `0x0000FFFF7FFFFFFF` would be the equivalent to `Color::from_rgba(0.0, 1.0, 0.5, 1.0)` + #[inline] + pub fn to_abgr64(&self) -> u64 { + ((self.a * 65535.0) as u64) << 48 + | ((self.b * 65535.0) as u64) << 32 + | ((self.g * 65535.0) as u64) << 16 + | ((self.r * 65535.0) as u64) } - pub fn to_argb64(&self) -> i32 { - unsafe { (get_api().godot_color_to_argb64)(self.sys()) } + /// Returns the ARGB32 format representation representation for this color where each byte represents a component of the ARGB profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in the order ARGB byte order + /// On little endian machines this is stored in the order BGRA byte order + /// `0x0000FFFF7FFFFFFF` would be the equivalent to `Color::from_rgba(1.0, 0.5, 1.0, 0.0)` + #[inline] + pub fn to_argb32(&self) -> u32 { + ((self.a * 255.0) as u32) << 24 + | ((self.r * 255.0) as u32) << 16 + | ((self.g * 255.0) as u32) << 8 + | (self.b * 255.0) as u32 } - pub fn to_html(&self, with_alpha: bool) -> GodotString { - GodotString::from_sys(unsafe { (get_api().godot_color_to_html)(self.sys(), with_alpha) }) + /// Returns the ARGB64 format representation for this color where each word represents a component of the ARGB profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in the order ARGB word order + /// On little endian machines this is stored in the order BGRA word order + /// # Example + /// `0x0000FFFF7FFFFFFF` would be the equivalent to `Color::from_rgba(1.0, 0.5, 1.0, 0.0)` + #[inline] + pub fn to_argb64(&self) -> u64 { + ((self.a * 65535.0) as u64) << 48 + | ((self.r * 65535.0) as u64) << 32 + | ((self.g * 65535.0) as u64) << 16 + | ((self.b * 65535.0) as u64) } - pub fn to_rgba32(&self) -> i32 { - unsafe { (get_api().godot_color_to_rgba32)(self.sys()) } + /// Returns the OpenGL Texture format byte representation for this color where each byte represents a component of the RGBA profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in RGBA byte order + /// On little endian machines this is stored in ABGR byte order + /// # Example + /// `0x00FF7FFF` would be the equivalent to `Color::from_rgba(0.0, 1.0, 0.5, 1.0)` + #[inline] + pub fn to_rgba32(&self) -> u32 { + ((self.r * 255.0) as u32) << 24 + | ((self.g * 255.0) as u32) << 16 + | ((self.b * 255.0) as u32) << 8 + | (self.a * 255.0) as u32 } - pub fn to_rgba64(&self) -> i32 { - unsafe { (get_api().godot_color_to_rgba64)(self.sys()) } + /// Returns the OpenGL Texture format byte representation for this color where each byte represents a component of the RGBA profile. + /// This is the byte information used when storing this color as a part of a texture. + /// # Endianness + /// On big endian architecture this is stored in RGBA word order + /// On little endian machines this is stored in ABGR word order + /// # Example + /// `0x0000FFFF7FFFFFFF` would be the equivalent to `Color::from_rgba(0.0, 1.0, 0.5, 1.0)` + #[inline] + pub fn to_rgba64(&self) -> u64 { + ((self.r * 65535.0) as u64) << 48 + | ((self.g * 65535.0) as u64) << 32 + | ((self.b * 65535.0) as u64) << 16 + | ((self.a * 65535.0) as u64) } #[doc(hidden)] @@ -136,48 +228,59 @@ fn color_repr() { assert_eq!(size_of::(), size_of::()); } +#[test] +fn color_to_pixel_color_formats() { + let color = Color::from_rgba(1.0, 0.5, 1.0, 0.0); + assert_eq!(0xFF7FFF00, color.to_rgba32()); + assert_eq!(0xFFFF7FFFFFFF0000, color.to_rgba64()); + assert_eq!(0x00FF7FFF, color.to_abgr32()); + assert_eq!(0x0000FFFF7FFFFFFF, color.to_abgr64()); + assert_eq!(0x00FF7FFF, color.to_argb32()); + assert_eq!(0x0000FFFF7FFFFFFF, color.to_argb64()); +} + godot_test!(test_color { // Test to_html - assert_eq!("ffffffff", Color::rgba(1.0, 1.0, 1.0, 1.0).to_html(true).to_string()); - assert_eq!("ffffff", Color::rgba(1.0, 1.0, 1.0, 1.0).to_html(false).to_string()); - assert_eq!("80ffffff", Color::rgba(1.0, 1.0, 1.0, 0.5).to_html(true).to_string()); - assert_eq!("ffffff", Color::rgba(1.0, 1.0, 1.0, 0.5).to_html(false).to_string()); - assert_eq!("ff8000", Color::rgb(1.0, 0.5, 0.0).to_html(false).to_string()); - assert_eq!("ff0080ff", Color::rgb(0.0, 0.5, 1.0).to_html(true).to_string()); + assert_eq!("ffffffff", Color::from_rgba(1.0, 1.0, 1.0, 1.0).to_html(true).to_string()); + assert_eq!("ffffff", Color::from_rgba(1.0, 1.0, 1.0, 1.0).to_html(false).to_string()); + assert_eq!("80ffffff", Color::from_rgba(1.0, 1.0, 1.0, 0.5).to_html(true).to_string()); + assert_eq!("ffffff", Color::from_rgba(1.0, 1.0, 1.0, 0.5).to_html(false).to_string()); + assert_eq!("ff8000", Color::from_rgb(1.0, 0.5, 0.0).to_html(false).to_string()); + assert_eq!("ff0080ff", Color::from_rgb(0.0, 0.5, 1.0).to_html(true).to_string()); // Test Gray // String comparison due to non-trivial way to truncate floats use crate::core_types::IsEqualApprox; - assert!(0.4f32.is_equal_approx(Color::rgb(0.2, 0.4, 0.6).gray())); - assert!(0.5f32.is_equal_approx(Color::rgb(0.1, 0.5, 0.9).gray())); - assert!(0.9f32.is_equal_approx(Color::rgb(1.0, 1.0, 0.7).gray())); - assert!(0.42f32.is_equal_approx(Color::rgb(0.6, 0.6, 0.06).gray())); + assert!(0.4f32.is_equal_approx(Color::from_rgb(0.2, 0.4, 0.6).gray())); + assert!(0.5f32.is_equal_approx(Color::from_rgb(0.1, 0.5, 0.9).gray())); + assert!(0.9f32.is_equal_approx(Color::from_rgb(1.0, 1.0, 0.7).gray())); + assert!(0.42f32.is_equal_approx(Color::from_rgb(0.6, 0.6, 0.06).gray())); // Test invert - let inverted = Color::rgb(1.0, 1.0,1.0).inverted(); + let inverted = Color::from_rgb(1.0, 1.0,1.0).inverted(); assert!(0f32.is_equal_approx(inverted.r)); assert!(0f32.is_equal_approx(inverted.g)); assert!(0f32.is_equal_approx(inverted.b)); - let inverted = Color::rgb(0.95, 0.95,0.95).inverted(); + let inverted = Color::from_rgb(0.95, 0.95,0.95).inverted(); assert!(0.05f32.is_equal_approx(inverted.r)); assert!(0.05f32.is_equal_approx(inverted.g)); assert!(0.05f32.is_equal_approx(inverted.b)); - let inverted = Color::rgb(0.05, 0.95,0.55).inverted(); + let inverted = Color::from_rgb(0.05, 0.95,0.55).inverted(); assert!(0.95f32.is_equal_approx(inverted.r)); assert!(0.05f32.is_equal_approx(inverted.g)); assert!(0.45f32.is_equal_approx(inverted.b)); // This is a series of sanity checks to test that the API bounds work properly. - let color = Color::from_hsv(1.0, 1.0, 1.0, 1.0); - color.darkened(0.20); - color.contrasted(); - color.inverted(); - color.to_rgba32(); - color.to_rgba64(); - color.to_abgr32(); - color.to_abgr64(); - color.to_argb32(); - color.to_argb64(); - let other_color = Color::rgba(1.0, 1.0, 1.0, 1.0); - color.blend(&other_color); + let hsv_color = Color::from_hsv(0.75, 0.5, 0.25); + let color = Color::from_hsva(0.75, 0.5, 0.25, 1.0); + assert_eq!(hsv_color, color); + let color = Color::from_rgb(0.75, 0.5, 0.25); + assert_eq!(Color::from_rgb(0.25, 0.5, 0.75), color.inverted()); + // Following results were derived from the godot engine code based on the RGB values of 0.75, 0.5, 0.25 respectively. + assert_eq!(Color::from_rgb(0.25, 0.00, 0.75), color.contrasted()); + assert_eq!(Color::from_rgba(0.60, 0.40, 0.20, 1.0), color.darkened(0.20)); + // Check that the blend values are correct. + let color = Color::from_rgba(0.0, 1.0, 0.5, 1.0); + let other_color = Color::from_rgba(1.0, 0.0, 0.5, 1.0); + assert_eq!(Color::from_rgba(1.0, 0.0, 0.5, 1.0), color.blend(&other_color)); }); diff --git a/gdnative-core/src/core_types/color_array.rs b/gdnative-core/src/core_types/color_array.rs index 2e069be68..959395927 100644 --- a/gdnative-core/src/core_types/color_array.rs +++ b/gdnative-core/src/core_types/color_array.rs @@ -9,17 +9,17 @@ godot_test!( use crate::NewRef as _; let arr = ColorArray::from_vec(vec![ - Color::rgb(1.0, 0.0, 0.0), - Color::rgb(0.0, 1.0, 0.0), - Color::rgb(0.0, 0.0, 1.0), + Color::from_rgb(1.0, 0.0, 0.0), + Color::from_rgb(0.0, 1.0, 0.0), + Color::from_rgb(0.0, 0.0, 1.0), ]); let original_read = { let read = arr.read(); assert_eq!(&[ - Color::rgb(1.0, 0.0, 0.0), - Color::rgb(0.0, 1.0, 0.0), - Color::rgb(0.0, 0.0, 1.0), + Color::from_rgb(1.0, 0.0, 0.0), + Color::from_rgb(0.0, 1.0, 0.0), + Color::from_rgb(0.0, 0.0, 1.0), ], read.as_slice()); read.clone() }; @@ -34,15 +34,15 @@ godot_test!( } } - assert_eq!(Color::rgb(1.0, 0.0, 1.0), cow_arr.get(0)); - assert_eq!(Color::rgb(0.0, 1.0, 1.0), cow_arr.get(1)); - assert_eq!(Color::rgb(0.0, 0.0, 1.0), cow_arr.get(2)); + assert_eq!(Color::from_rgb(1.0, 0.0, 1.0), cow_arr.get(0)); + assert_eq!(Color::from_rgb(0.0, 1.0, 1.0), cow_arr.get(1)); + assert_eq!(Color::from_rgb(0.0, 0.0, 1.0), cow_arr.get(2)); // the write shouldn't have affected the original array assert_eq!(&[ - Color::rgb(1.0, 0.0, 0.0), - Color::rgb(0.0, 1.0, 0.0), - Color::rgb(0.0, 0.0, 1.0), + Color::from_rgb(1.0, 0.0, 0.0), + Color::from_rgb(0.0, 1.0, 0.0), + Color::from_rgb(0.0, 0.0, 1.0), ], original_read.as_slice()); } ); @@ -50,9 +50,9 @@ godot_test!( godot_test!( test_color_array_debug { let arr = ColorArray::from_vec(vec![ - Color::rgb(1.0, 0.0, 0.0), - Color::rgb(0.0, 1.0, 0.0), - Color::rgb(0.0, 0.0, 1.0), + Color::from_rgb(1.0, 0.0, 0.0), + Color::from_rgb(0.0, 1.0, 0.0), + Color::from_rgb(0.0, 0.0, 1.0), ]); assert_eq!(format!("{:?}", arr), "[Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }]");