From 2c92c89b0b079bf244914194842b7bb380f7f27a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 9 May 2024 13:17:21 -0700 Subject: [PATCH] [Color 4] Update `color.same()` and color equality (#2232) See sass/sass#3852 See sass/sass#3858 --- lib/src/functions/color.dart | 38 ++++++++++++++++++++++++++++++------ lib/src/util/number.dart | 11 +++++++++++ lib/src/value/color.dart | 28 +++++++++++++++++--------- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/src/functions/color.dart b/lib/src/functions/color.dart index 3ad370e50..206bfb67e 100644 --- a/lib/src/functions/color.dart +++ b/lib/src/functions/color.dart @@ -504,12 +504,38 @@ final module = BuiltInModule("color", functions: [ var color1 = arguments[0].assertColor('color1'); var color2 = arguments[1].assertColor('color2'); - // Convert both colors into the same space to compare them. Usually we - // just use color1's space, but since HSL and HWB can't represent - // out-of-gamut colors we use RGB for all legacy color spaces. - var targetSpace = color1.isLegacy ? ColorSpace.rgb : color1.space; - return SassBoolean( - color1.toSpace(targetSpace) == color2.toSpace(targetSpace)); + /// Converts [color] to the xyz-d65 space without any mising channels. + SassColor toXyzNoMissing(SassColor color) => switch (color) { + SassColor(space: ColorSpace.xyzD65, hasMissingChannel: false) => + color, + SassColor( + space: ColorSpace.xyzD65, + :var channel0, + :var channel1, + :var channel2, + :var alpha + ) => + SassColor.xyzD65(channel0, channel1, channel2, alpha), + SassColor( + :var space, + :var channel0, + :var channel1, + :var channel2, + :var alpha + ) => + // Use [ColorSpace.convert] manually so that we can convert missing + // channels to 0 without having to create new intermediate color + // objects. + space.convert( + ColorSpace.xyzD65, channel0, channel1, channel2, alpha) + }; + + return SassBoolean(color1.space == color2.space + ? fuzzyEquals(color1.channel0, color2.channel0) && + fuzzyEquals(color1.channel1, color2.channel1) && + fuzzyEquals(color1.channel2, color2.channel2) && + fuzzyEquals(color1.alpha, color2.alpha) + : toXyzNoMissing(color1) == toXyzNoMissing(color2)); }), _function( diff --git a/lib/src/util/number.dart b/lib/src/util/number.dart index 0cd82fb70..9eca12928 100644 --- a/lib/src/util/number.dart +++ b/lib/src/util/number.dart @@ -30,6 +30,17 @@ bool fuzzyEquals(num number1, num number2) { (number2 * _inverseEpsilon).round(); } +/// Like [fuzzyEquals], but allows null values for [number1] and [number2]. +/// +/// null values are only equal to one another. +bool fuzzyEqualsNullable(num? number1, num? number2) { + if (number1 == number2) return true; + if (number1 == null || number2 == null) return false; + return (number1 - number2).abs() <= _epsilon && + (number1 * _inverseEpsilon).round() == + (number2 * _inverseEpsilon).round(); +} + /// Returns a hash code for [number] that matches [fuzzyEquals]. int fuzzyHashCode(double number) { if (!number.isFinite) return number.hashCode; diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index 088900483..9a61f91eb 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -213,6 +213,16 @@ class SassColor extends Value { _ => true }; + /// Whether this color has any missing channels. + /// + /// @nodoc + @internal + bool get hasMissingChannel => + isChannel0Missing || + isChannel1Missing || + isChannel2Missing || + isAlphaMissing; + /// This color's red channel, between `0` and `255`. /// /// **Note:** This is rounded to the nearest integer, which may be lossy. Use @@ -953,21 +963,21 @@ class SassColor extends Value { if (isLegacy) { if (!other.isLegacy) return false; - if (!fuzzyEquals(alpha, other.alpha)) return false; - if (space == ColorSpace.rgb && other.space == ColorSpace.rgb) { - return fuzzyEquals(channel0, other.channel0) && - fuzzyEquals(channel1, other.channel1) && - fuzzyEquals(channel2, other.channel2); + if (!fuzzyEqualsNullable(alphaOrNull, other.alphaOrNull)) return false; + if (space == other.space) { + return fuzzyEqualsNullable(channel0OrNull, other.channel0OrNull) && + fuzzyEqualsNullable(channel1OrNull, other.channel1OrNull) && + fuzzyEqualsNullable(channel2OrNull, other.channel2OrNull); } else { return toSpace(ColorSpace.rgb) == other.toSpace(ColorSpace.rgb); } } return space == other.space && - fuzzyEquals(channel0, other.channel0) && - fuzzyEquals(channel1, other.channel1) && - fuzzyEquals(channel2, other.channel2) && - fuzzyEquals(alpha, other.alpha); + fuzzyEqualsNullable(channel0OrNull, other.channel0OrNull) && + fuzzyEqualsNullable(channel1OrNull, other.channel1OrNull) && + fuzzyEqualsNullable(channel2OrNull, other.channel2OrNull) && + fuzzyEqualsNullable(alphaOrNull, other.alphaOrNull); } int get hashCode {