Skip to content

Commit

Permalink
Make all color channels f64.
Browse files Browse the repository at this point in the history
A color channel can now be NaN or infinite, so represent them as f64
rather than as rationals.  This also makes the color structs smaller,
as the rational numbers were 16 bytes each while a f64 is 8.
  • Loading branch information
kaj committed Sep 20, 2024
1 parent 70ed713 commit 292298e
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 334 deletions.
25 changes: 12 additions & 13 deletions rsass/src/sass/functions/color/hsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ use super::{
use crate::css::{CallArgs, Value};
use crate::output::Format;
use crate::sass::{ArgsError, FormalArgs, Name};
use crate::value::{Color, Hsla, Numeric, Rational, Unit};
use crate::value::{Color, Hsla, Numeric, Unit};
use crate::Scope;
use num_traits::{one, zero};

pub fn register(f: &mut Scope) {
def_va!(f, _hsl(kwargs), |s| do_hsla(&name!(hsl), s));
Expand All @@ -32,10 +31,8 @@ pub fn register(f: &mut Scope) {
Value::Color(col, _) => {
let is_rgb = col.is_rgb();
let col = col.to_hsla();
Ok(
Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), !is_rgb)
.into(),
)
Ok(Hsla::new(col.hue(), 0., col.lum(), col.alpha(), !is_rgb)
.into())
}
v @ Value::Numeric(..) => Ok(Value::call("grayscale", [v])),
v => Err(is_not(&v, "a color")).named(name!(color)),
Expand Down Expand Up @@ -77,8 +74,10 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) {
def!(f, grayscale(color), |args| match args.get(name!(color))? {
Value::Color(col, _) => {
let col = col.to_hsla();
Ok(Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), false)
.into())
Ok(
Hsla::new(col.hue(), 0., col.lum(), col.alpha(), false)
.into(),
)
}
v => NumOrSpecial::try_from(v)
.map_err(|e| is_not(e.value(), "a color"))
Expand All @@ -94,7 +93,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) {
let col = s.get::<Color>(name!(color))?;
let sat = s.get_map(name!(amount), check_amount)?;
let col = col.to_hsla();
let sat = (col.sat() + sat).clamp(zero(), one());
let sat = (col.sat() + sat).clamp(0., 1.);
Ok(Hsla::new(col.hue(), sat, col.lum(), col.alpha(), false)
.into())
}
Expand Down Expand Up @@ -186,7 +185,7 @@ fn hsla_from_values(
} else {
Ok(Hsla::new(
check_hue(h).named(name!(hue))?,
check_pct_opt(s).named(name!(saturation))?,
f64::max(0., check_pct_opt(s).named(name!(saturation))?),
check_pct_opt(l).named(name!(lightness))?,
check_alpha(a).named(name!(alpha))?,
true, // ??
Expand All @@ -195,13 +194,13 @@ fn hsla_from_values(
}
}

pub fn percentage(v: Rational) -> Value {
pub fn percentage(v: f64) -> Value {
Numeric::percentage(v).into()
}

/// Gets a percentage as a fraction 0 .. 1.
/// If v is not a percentage, keep it as it is.
fn check_pct_opt(v: Value) -> Result<Rational, String> {
fn check_pct_opt(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
if !v.unit.is_percent() {
// Note: The deprecation warning should include the parameter name
Expand All @@ -214,7 +213,7 @@ fn check_pct_opt(v: Value) -> Result<Rational, String> {
v.unit
);
}
Ok(v.as_ratio()? / 100)
Ok(f64::from(v.value) / 100.)
}

#[test]
Expand Down
19 changes: 8 additions & 11 deletions rsass/src/sass/functions/color/hwb.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use super::super::FunctionMap;
use super::hsl::percentage;
use super::{
check_alpha, check_expl_pct, eval_inner, is_not, relative_color,
check_alpha, check_expl_pct_norange, eval_inner, is_not, relative_color,
CallError, CheckedArg, ResolvedArgs,
};
use crate::css::{CallArgs, Value};
use crate::output::Format;
use crate::sass::FormalArgs;
use crate::value::{
Color, Hwba, ListSeparator, Numeric, Rational, Rgba, Unit,
};
use crate::value::{Color, Hwba, ListSeparator, Numeric, Rgba, Unit};
use crate::Scope;
use num_traits::{one, zero};

pub fn register(f: &mut Scope) {
def_va!(f, hwb(kwargs), hwb);
Expand Down Expand Up @@ -57,14 +54,14 @@ fn hwb(s: &ResolvedArgs) -> Result<Value, CallError> {
})?
};
let hue = check_hue(hue).named(name!(hue))?;
let w = check_expl_pct(w).named(name!(whiteness))?;
let b = check_expl_pct(b).named(name!(blackness))?;
let w = check_expl_pct_norange(w).named(name!(whiteness))?;
let b = check_expl_pct_norange(b).named(name!(blackness))?;
let a = check_alpha(a).named(name!(alpha))?;
// I don't really agree with this, but it makes tests pass.
let hue = if w + b >= one() { zero() } else { hue };
let hue = if w + b >= 1. { 0. } else { hue };
let hwba = Hwba::new(hue, w, b, a);
let rgba = Rgba::from(&hwba);
if rgba.is_integer() {
if rgba.is_integer() && w >= 0. {
Ok(rgba.into())
} else {
Ok(hwba.into())
Expand Down Expand Up @@ -129,10 +126,10 @@ fn badchannels(v: &Value) -> CallError {
))
}

fn check_hue(v: Value) -> Result<Rational, String> {
fn check_hue(v: Value) -> Result<f64, String> {
let vv = Numeric::try_from(v)?;
if let Some(scaled) = vv.as_unit_def(Unit::Deg) {
Ok(scaled.as_ratio()?)
Ok(scaled.into())
} else {
Err(is_not(&vv, "an angle"))
}
Expand Down
73 changes: 41 additions & 32 deletions rsass/src/sass/functions/color/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::css::{CallArgs, CssString, Value};
use crate::input::SourcePos;
use crate::output::Format;
use crate::sass::{FormalArgs, Name};
use crate::value::{ListSeparator, Number, Numeric, Quotes, Rational, Unit};
use crate::value::{ListSeparator, Numeric, Quotes, Unit};
use crate::Scope;
use num_traits::{one, zero, Signed};
use num_traits::{one, zero};
mod channels;
mod hsl;
mod hwb;
Expand Down Expand Up @@ -61,7 +61,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) {

/// For the alpha parameter of rgba, hsla, hwba functions
/// Special perk: Defaults to 1.0 if the value is null.
fn check_alpha(v: Value) -> Result<Rational, String> {
fn check_alpha(v: Value) -> Result<f64, String> {
Ok(match v {
Value::Null => one(),
v => {
Expand All @@ -70,31 +70,29 @@ fn check_alpha(v: Value) -> Result<Rational, String> {
.ok_or_else(|| {
expected_to(num, "have unit \"%\" or no units")
})?
.as_ratio()?
.into()
}
})
}
/// Get a rational number in the 0..1 range.
fn check_alpha_range(v: Value) -> Result<Rational, String> {
fn check_alpha_range(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
let r = v.as_ratio()?;
if r < zero() || r > one() {
if v.value < zero() || v.value > one() {
Err(expected_to(v, "be within 0 and 1"))
} else {
Ok(r)
Ok(v.value.into())
}
}
fn check_alpha_pm(v: Value) -> Result<Rational, String> {
fn check_alpha_pm(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
let r = v.as_ratio()?;
if r.abs() > one() {
if v.value.abs() > one() {
Err(expected_to(v, "be within -1 and 1"))
} else {
Ok(r)
Ok(v.value.into())
}
}

fn check_hue(v: Value) -> Result<Rational, String> {
fn check_hue(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
let v = match v.as_unit_def(Unit::Deg) {
Some(v) => v,
Expand All @@ -109,13 +107,13 @@ fn check_hue(v: Value) -> Result<Rational, String> {
v.value
}
};
v.as_ratio().map_err(|e| e.to_string())
Ok(v.into())
}

fn check_pct(v: Value) -> Result<Number, String> {
fn check_pct(v: Value) -> Result<f64, String> {
let val = Numeric::try_from(v)?;
match val.as_unit_def(Unit::Percent) {
Some(v) => Ok(v / 100),
Some(v) => Ok(f64::from(v) / 100.),
None => {
dep_warn!(
"Passing a number without unit % ({}) is deprecated.\
Expand All @@ -124,69 +122,80 @@ fn check_pct(v: Value) -> Result<Number, String> {
val.format(Format::introspect()),
val.unit
);
Ok(val.value / 100)
Ok(f64::from(val.value) / 100.)
}
}
}

fn check_expl_pct(v: Value) -> Result<Rational, String> {
fn check_expl_pct_norange(v: Value) -> Result<f64, String> {
let val = Numeric::try_from(v)?;
if !val.unit.is_percent() {
Err(expected_to(val, "have unit \"%\""))
} else {
Ok(f64::from(val.value) / 100.)
}
}
fn check_expl_pct(v: Value) -> Result<f64, String> {
let val = Numeric::try_from(v)?;
if !val.unit.is_percent() {
return Err(expected_to(val, "have unit \"%\""));
}
if val.value < zero() || val.value > 100.into() {
Err(expected_to(val, "be within 0% and 100%"))
} else {
Ok(val.as_ratio()? / 100)
Ok(f64::from(val.value) / 100.)
}
}

fn check_pct_range(v: Value) -> Result<Rational, String> {
fn check_pct_range(v: Value) -> Result<f64, String> {
let val = check_pct(v)?;
if val < zero() || val > one() {
Err(expected_to(
Numeric::percentage(val),
"be within 0% and 100%",
))
} else {
Ok(val.as_ratio()?)
Ok(val)
}
}

fn check_amount(v: Value) -> Result<Rational, String> {
fn check_amount(v: Value) -> Result<f64, String> {
let val = check_pct(v)?;
if val < zero() || val > one() {
Err(expected_to(Value::scalar(val * 100), "be within 0 and 100"))
Err(expected_to(
Value::scalar(val * 100.),
"be within 0 and 100",
))
} else {
Ok(val.as_ratio()?)
Ok(val)
}
}

fn check_channel(v: Value) -> Result<Rational, String> {
fn check_channel(v: Value) -> Result<f64, String> {
num2chan(&Numeric::try_from(v)?)
}
fn check_channel_range(v: Value) -> Result<Rational, String> {
fn check_channel_range(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
let r = num2chan(&v)?;
if r > Rational::from_integer(255) || r < zero() {
if r > 255. || r < zero() {
Err(expected_to(v, "be within 0 and 255"))
} else {
Ok(r)
}
}
fn check_channel_pm(v: Value) -> Result<Rational, String> {
fn check_channel_pm(v: Value) -> Result<f64, String> {
let v = Numeric::try_from(v)?;
let r = num2chan(&v)?;
if r.abs() > Rational::from_integer(255) {
if r.abs() > 255. {
Err(expected_to(v, "be within -255 and 255"))
} else {
Ok(r)
}
}
fn num2chan(v: &Numeric) -> Result<Rational, String> {
let r = v.as_ratio()?;
fn num2chan(v: &Numeric) -> Result<f64, String> {
let r: f64 = v.value.clone().into();
if v.unit.is_percent() {
Ok(r * 255 / 100)
Ok(r * 255. / 100.)
} else {
Ok(r)
}
Expand Down
Loading

0 comments on commit 292298e

Please sign in to comment.