Skip to content

Commit

Permalink
Merge #729
Browse files Browse the repository at this point in the history
729: Implemented some additional API functions related to crate::core_type… r=toasteater a=jacobsky

…s::Color

I needed some additional functionality from the Color class in godot and noticed that there were a lot of APIs that weren't accessible from rust. 

This PR plumbs in the functions from godot and adds some tests that can be used to sanity check them.

This commit adds the API functionality and some basic sanity tests that I verified are passing in the engine. If additional tests are required, please let me know.
Signed-off-by: Jacobsky <cael.jacobsen@gmail.com>

Co-authored-by: Jacobsky <cael.jacobsen@gmail.com>
  • Loading branch information
bors[bot] and jacobsky committed Apr 30, 2021
2 parents cb0f4fd + c608e1a commit d95b8fa
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 17 deletions.
2 changes: 1 addition & 1 deletion examples/spinning_cube/src/lib.rs
Expand Up @@ -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::<SpatialMaterial>().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));
}
}
}
Expand Down
212 changes: 212 additions & 0 deletions gdnative-core/src/core_types/color.rs
Expand Up @@ -2,6 +2,7 @@ use crate::private::get_api;
use crate::sys;
use std::mem::transmute;

use crate::core_types::GodotString;
/// RGBA color with 32 bits floating point components.
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
Expand All @@ -13,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()) }
Expand All @@ -48,6 +77,132 @@ impl Color {
}
}

#[inline]
pub fn blend(&self, other: &Color) -> Color {
Color::from_sys(unsafe { (get_api().godot_color_blend)(self.sys(), other.sys()) })
}

#[inline]
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 gray(&self) -> f32 {
// Implemented as described in godot docs
(self.r + self.b + self.g) / 3.0
}

#[inline]
pub fn inverted(&self) -> Color {
// Implementation as described in godot docs.
Color {
r: 1.0 - self.r,
g: 1.0 - self.g,
b: 1.0 - self.b,
a: self.a,
}
}

#[inline]
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 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
}

/// 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)
}

/// 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
}

/// 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)
}

/// 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
}

/// 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)]
#[inline]
pub fn sys(&self) -> &sys::godot_color {
Expand All @@ -72,3 +227,60 @@ fn color_repr() {
use std::mem::size_of;
assert_eq!(size_of::<Color>(), size_of::<sys::godot_color>());
}

#[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::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::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::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::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::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 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));
});
30 changes: 15 additions & 15 deletions gdnative-core/src/core_types/color_array.rs
Expand Up @@ -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()
};
Expand All @@ -34,25 +34,25 @@ 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());
}
);

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 }]");
Expand Down
2 changes: 1 addition & 1 deletion test/src/lib.rs
Expand Up @@ -22,7 +22,7 @@ pub extern "C" fn run_tests(

status &= gdnative::core_types::dictionary::test_dictionary();
// status &= gdnative::test_dictionary_clone_clear();

status &= gdnative::core_types::test_color();
status &= gdnative::core_types::test_array();
status &= gdnative::core_types::test_array_debug();
// status &= gdnative::test_array_clone_clear();
Expand Down

0 comments on commit d95b8fa

Please sign in to comment.