Bug 1332633 - Part 1: Implement ComputeDistance trait.

Introduce ComputeDistance trait, which implement compute_distance and
compute_squared_distance.

For vector, compute_squared_distance is necessary because we use Euclidean
distance as the distance between two values. The easier way to implement
compute_squared_distance is to square the result from compute_distance, but
for some property values, they may have many components, e.g. (v1, v2, v3).
If we just square the result from compute_distance, the computation is
(sqrt(v1^2 + v2^2 + v3^2))^2. There are two redundant operators:
"square-root" and then "square". In order to avoid this, we should
implement compute_squared_distance separately for these types.

MozReview-Commit-ID: LmmrUXYlDb6
This commit is contained in:
Boris Chiou 2017-03-18 19:47:04 +08:00
parent 17dc598d99
commit 57f87007f2
7 changed files with 733 additions and 11 deletions

View file

@ -10,7 +10,7 @@ use euclid::{Point2D, Size2D};
#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
use properties::{CSSWideKeyword, PropertyDeclaration};
use properties::longhands;
use properties::longhands::background_size::computed_value::T as BackgroundSize;
use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
use properties::longhands::font_weight::computed_value::T as FontWeight;
use properties::longhands::line_height::computed_value::T as LineHeight;
use properties::longhands::text_shadow::computed_value::T as TextShadowList;
@ -696,14 +696,14 @@ impl Interpolate for VerticalAlign {
}
}
}
impl Interpolate for BackgroundSize {
impl Interpolate for BackgroundSizeList {
#[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
self.0.interpolate(&other.0, progress).map(BackgroundSize)
self.0.interpolate(&other.0, progress).map(BackgroundSizeList)
}
}
/// https://drafts.csswg.org/css-transitions/#animtype-color
impl Interpolate for RGBA {
#[inline]
@ -2032,3 +2032,614 @@ impl<T, U> Interpolate for Either<T, U>
}
}
}
/// We support ComputeDistance for an API in gecko to test the transition per property.
impl ComputeDistance for AnimationValue {
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (self, other) {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animation_type == "normal":
(&AnimationValue::${prop.camel_case}(ref from),
&AnimationValue::${prop.camel_case}(ref to)) => {
from.compute_distance(to)
},
% else:
(&AnimationValue::${prop.camel_case}(ref _from),
&AnimationValue::${prop.camel_case}(ref _to)) => {
Err(())
},
% endif
% endif
% endfor
_ => {
panic!("Expected compute_distance of computed values of the same \
property, got: {:?}, {:?}", self, other);
}
}
}
}
/// A trait used to implement [compute_distance].
/// In order to compute the Euclidean distance of a list, we need to compute squared distance
/// for each element, so the vector can sum it and then get its squared root as the distance.
pub trait ComputeDistance: Sized {
/// Compute distance between a value and another for a given property.
fn compute_distance(&self, other: &Self) -> Result<f64, ()>;
/// Compute squared distance between a value and another for a given property.
/// This is used for list or if there are many components in a property value.
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_distance(other).map(|d| d * d)
}
}
impl<T: ComputeDistance> ComputeDistance for Vec<T> {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
if self.len() != other.len() {
return Err(());
}
let mut squared_dist = 0.0f64;
for (this, other) in self.iter().zip(other) {
let diff = try!(this.compute_squared_distance(other));
squared_dist += diff;
}
Ok(squared_dist)
}
}
impl ComputeDistance for Au {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
}
impl ComputeDistance for Auto {
#[inline]
fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
Err(())
}
}
impl ComputeDistance for Normal {
#[inline]
fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
Err(())
}
}
impl <T> ComputeDistance for Option<T>
where T: ComputeDistance,
{
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (self, other) {
(&Some(ref this), &Some(ref other)) => {
this.compute_distance(other)
},
_ => Err(()),
}
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
match (self, other) {
(&Some(ref this), &Some(ref other)) => {
this.compute_squared_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for f32 {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
Ok((*self - *other).abs() as f64)
}
}
impl ComputeDistance for f64 {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
Ok((*self - *other).abs())
}
}
impl ComputeDistance for i32 {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
Ok((*self - *other).abs() as f64)
}
}
impl ComputeDistance for Visibility {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
if *self == *other {
Ok(0.0)
} else {
Ok(1.0)
}
}
}
/// https://www.w3.org/TR/smil-animation/#animateColorElement says we should use Euclidean RGB-cube distance.
impl ComputeDistance for RGBA {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
fn clamp(val: f32) -> f32 {
val.max(0.).min(1.)
}
let start_a = clamp(self.alpha_f32());
let end_a = clamp(other.alpha_f32());
let start = [ start_a,
self.red_f32() * start_a,
self.green_f32() * start_a,
self.blue_f32() * start_a ];
let end = [ end_a,
other.red_f32() * end_a,
other.green_f32() * end_a,
other.blue_f32() * end_a ];
let diff = start.iter().zip(&end)
.fold(0.0f64, |n, (&a, &b)| {
let diff = (a - b) as f64;
n + diff * diff
});
Ok(diff)
}
}
impl ComputeDistance for CSSParserColor {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sq| sq.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => {
this.compute_squared_distance(other)
},
_ => Ok(0.0),
}
}
}
impl ComputeDistance for CalcLengthOrPercentage {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sq| sq.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
let length_diff = (self.length().0 - other.length().0) as f64;
let percentage_diff = (self.percentage() - other.percentage()) as f64;
Ok(length_diff * length_diff + percentage_diff * percentage_diff)
}
}
impl ComputeDistance for LengthOrPercentage {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LengthOrPercentage::Length(ref this),
LengthOrPercentage::Length(ref other)) => {
this.compute_distance(other)
},
(LengthOrPercentage::Percentage(ref this),
LengthOrPercentage::Percentage(ref other)) => {
this.compute_distance(other)
},
(this, other) => {
let this: CalcLengthOrPercentage = From::from(this);
let other: CalcLengthOrPercentage = From::from(other);
this.compute_distance(&other)
}
}
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LengthOrPercentage::Length(ref this),
LengthOrPercentage::Length(ref other)) => {
let diff = (this.0 - other.0) as f64;
Ok(diff * diff)
},
(LengthOrPercentage::Percentage(ref this),
LengthOrPercentage::Percentage(ref other)) => {
let diff = (this - other) as f64;
Ok(diff * diff)
},
(this, other) => {
let this: CalcLengthOrPercentage = From::from(this);
let other: CalcLengthOrPercentage = From::from(other);
let length_diff = (this.length().0 - other.length().0) as f64;
let percentage_diff = (this.percentage() - other.percentage()) as f64;
Ok(length_diff * length_diff + percentage_diff * percentage_diff)
}
}
}
}
impl ComputeDistance for LengthOrPercentageOrAuto {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LengthOrPercentageOrAuto::Length(ref this),
LengthOrPercentageOrAuto::Length(ref other)) => {
this.compute_distance(other)
},
(LengthOrPercentageOrAuto::Percentage(ref this),
LengthOrPercentageOrAuto::Percentage(ref other)) => {
this.compute_distance(other)
},
(this, other) => {
// If one of the element is Auto, Option<> will be None, and the returned distance is Err(())
let this: Option<CalcLengthOrPercentage> = From::from(this);
let other: Option<CalcLengthOrPercentage> = From::from(other);
this.compute_distance(&other)
}
}
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LengthOrPercentageOrAuto::Length(ref this),
LengthOrPercentageOrAuto::Length(ref other)) => {
let diff = (this.0 - other.0) as f64;
Ok(diff * diff)
},
(LengthOrPercentageOrAuto::Percentage(ref this),
LengthOrPercentageOrAuto::Percentage(ref other)) => {
let diff = (this - other) as f64;
Ok(diff * diff)
},
(this, other) => {
let this: Option<CalcLengthOrPercentage> = From::from(this);
let other: Option<CalcLengthOrPercentage> = From::from(other);
if this.is_none() || other.is_none() {
Err(())
} else {
let length_diff = (this.unwrap().length().0 - other.unwrap().length().0) as f64;
let percentage_diff = (this.unwrap().percentage() - other.unwrap().percentage()) as f64;
Ok(length_diff * length_diff + percentage_diff * percentage_diff)
}
}
}
}
}
impl ComputeDistance for LengthOrPercentageOrNone {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LengthOrPercentageOrNone::Length(ref this),
LengthOrPercentageOrNone::Length(ref other)) => {
this.compute_distance(other)
},
(LengthOrPercentageOrNone::Percentage(ref this),
LengthOrPercentageOrNone::Percentage(ref other)) => {
this.compute_distance(other)
},
_ => Err(())
}
}
}
impl ComputeDistance for LengthOrNone {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(Either::First(ref length), Either::First(ref other)) => {
length.compute_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for MinLength {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(MinLength::LengthOrPercentage(ref this),
MinLength::LengthOrPercentage(ref other)) => {
this.compute_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for MaxLength {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(MaxLength::LengthOrPercentage(ref this),
MaxLength::LengthOrPercentage(ref other)) => {
this.compute_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for VerticalAlign {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(VerticalAlign::LengthOrPercentage(ref this),
VerticalAlign::LengthOrPercentage(ref other)) => {
this.compute_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for BorderRadiusSize {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
Ok(try!(self.0.width.compute_squared_distance(&other.0.width)) +
try!(self.0.height.compute_squared_distance(&other.0.height)))
}
}
impl ComputeDistance for BackgroundSizeList {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_squared_distance(&other.0)
}
}
impl ComputeDistance for LineHeight {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (*self, *other) {
(LineHeight::Length(ref this),
LineHeight::Length(ref other)) => {
this.compute_distance(other)
},
(LineHeight::Number(ref this),
LineHeight::Number(ref other)) => {
this.compute_distance(other)
},
_ => Err(()),
}
}
}
impl ComputeDistance for FontWeight {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
let a = (*self as u32) as f64;
let b = (*other as u32) as f64;
a.compute_distance(&b)
}
}
impl ComputeDistance for Position {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
Ok(try!(self.horizontal.compute_squared_distance(&other.horizontal)) +
try!(self.vertical.compute_squared_distance(&other.vertical)))
}
}
impl ComputeDistance for HorizontalPosition {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_squared_distance(&other.0)
}
}
impl ComputeDistance for VerticalPosition {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_squared_distance(&other.0)
}
}
impl ComputeDistance for ClipRect {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
let list = [ try!(self.top.compute_distance(&other.top)),
try!(self.right.compute_distance(&other.right)),
try!(self.bottom.compute_distance(&other.bottom)),
try!(self.left.compute_distance(&other.left)) ];
Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
}
}
impl ComputeDistance for TextShadow {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
let list = [ try!(self.offset_x.compute_distance(&other.offset_x)),
try!(self.offset_y.compute_distance(&other.offset_y)),
try!(self.blur_radius.compute_distance(&other.blur_radius)),
try!(self.color.compute_distance(&other.color)) ];
Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
}
}
impl ComputeDistance for TextShadowList {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
let zero = TextShadow {
offset_x: Au(0),
offset_y: Au(0),
blur_radius: Au(0),
color: CSSParserColor::RGBA(RGBA::transparent()),
};
let max_len = cmp::max(self.0.len(), other.0.len());
let mut diff_squared = 0.0f64;
for i in 0..max_len {
diff_squared += match (self.0.get(i), other.0.get(i)) {
(Some(shadow), Some(other)) => {
try!(shadow.compute_squared_distance(other))
},
(Some(shadow), None) |
(None, Some(shadow)) => {
try!(shadow.compute_squared_distance(&zero))
},
(None, None) => unreachable!(),
};
}
Ok(diff_squared)
}
}
impl ComputeDistance for BoxShadow {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
if self.inset != other.inset {
return Err(());
}
let list = [ try!(self.offset_x.compute_distance(&other.offset_x)),
try!(self.offset_y.compute_distance(&other.offset_y)),
try!(self.color.compute_distance(&other.color)),
try!(self.spread_radius.compute_distance(&other.spread_radius)),
try!(self.blur_radius.compute_distance(&other.blur_radius)) ];
Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
}
}
impl ComputeDistance for BoxShadowList {
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.compute_squared_distance(other).map(|sd| sd.sqrt())
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
// The inset value must change
let mut zero = BoxShadow {
offset_x: Au(0),
offset_y: Au(0),
spread_radius: Au(0),
blur_radius: Au(0),
color: CSSParserColor::RGBA(RGBA::transparent()),
inset: false,
};
let max_len = cmp::max(self.0.len(), other.0.len());
let mut diff_squared = 0.0f64;
for i in 0..max_len {
diff_squared += match (self.0.get(i), other.0.get(i)) {
(Some(shadow), Some(other)) => {
try!(shadow.compute_squared_distance(other))
},
(Some(shadow), None) |
(None, Some(shadow)) => {
zero.inset = shadow.inset;
try!(shadow.compute_squared_distance(&zero))
}
(None, None) => unreachable!(),
};
}
Ok(diff_squared)
}
}
impl ComputeDistance for TransformList {
#[inline]
fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
Err(())
}
}
impl<T, U> ComputeDistance for Either<T, U>
where T: ComputeDistance, U: ComputeDistance
{
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
match (self, other) {
(&Either::First(ref this), &Either::First(ref other)) => {
this.compute_distance(other)
},
(&Either::Second(ref this), &Either::Second(ref other)) => {
this.compute_distance(other)
},
_ => Err(())
}
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
match (self, other) {
(&Either::First(ref this), &Either::First(ref other)) => {
this.compute_squared_distance(other)
},
(&Either::Second(ref this), &Either::Second(ref other)) => {
this.compute_squared_distance(other)
},
_ => Err(())
}
}
}