cacao/appkit/color.rs

126 lines
3.6 KiB
Rust

//! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked
//! for (what I believe) is a friendlier API.
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, CGFloat};
/// A color with red, green, blue, and alpha components, in a byte each.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Color {
/// The red component.
pub red: u8,
/// The green component.
pub green: u8,
/// The blue component.
pub blue: u8,
/// The alpha component.
pub alpha: u8,
}
impl Default for Color {
fn default() -> Color {
Color { red: 0, green: 0, blue: 0, alpha: 0 }
}
}
impl Color {
/// Constructs a new Color value from float components. It expects the red,
/// green, blue and alpha channels in that order, and all values will be
/// clamped to the 0.0 ... 1.0 range.
#[inline]
pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self::new(
clamp_unit_f32(red),
clamp_unit_f32(green),
clamp_unit_f32(blue),
clamp_unit_f32(alpha),
)
}
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self::new(0, 0, 0, 0)
}
/// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
#[inline]
pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
Color {
red: red,
green: green,
blue: blue,
alpha: alpha,
}
}
/// Maps to NS/UIColor.
pub fn into_platform_specific_color(&self) -> id {
let red = self.red as CGFloat / 255.0;
let green = self.green as CGFloat / 255.0;
let blue = self.blue as CGFloat / 255.0;
let alpha = self.alpha as CGFloat / 255.0;
unsafe {
msg_send![class!(NSColor), colorWithRed:red green:green blue:blue alpha:alpha]
}
}
/// Returns the red channel in a floating point number form, from 0 to 1.
#[inline]
pub fn red_f32(&self) -> f32 {
self.red as f32 / 255.0
}
/// Returns the green channel in a floating point number form, from 0 to 1.
#[inline]
pub fn green_f32(&self) -> f32 {
self.green as f32 / 255.0
}
/// Returns the blue channel in a floating point number form, from 0 to 1.
#[inline]
pub fn blue_f32(&self) -> f32 {
self.blue as f32 / 255.0
}
/// Returns the alpha channel in a floating point number form, from 0 to 1.
#[inline]
pub fn alpha_f32(&self) -> f32 {
self.alpha as f32 / 255.0
}
}
#[inline]
pub fn rgb(red: u8, green: u8, blue: u8) -> Color {
rgba(red, green, blue, 255)
}
#[inline]
pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
Color::new(red, green, blue, alpha)
}
fn clamp_unit_f32(val: f32) -> u8 {
// Whilst scaling by 256 and flooring would provide
// an equal distribution of integers to percentage inputs,
// this is not what Gecko does so we instead multiply by 255
// and round (adding 0.5 and flooring is equivalent to rounding)
//
// Chrome does something similar for the alpha value, but not
// the rgb values.
//
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
//
// Clamping to 256 and rounding after would let 1.0 map to 256, and
// `256.0_f32 as u8` is undefined behavior:
//
// https://github.com/rust-lang/rust/issues/10184
clamp_floor_256_f32(val * 255.)
}
fn clamp_floor_256_f32(val: f32) -> u8 {
val.round().max(0.).min(255.) as u8
}