Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/xamarin/Xamarin.PropertyEditing.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBertrand Le Roy <beleroy@microsoft.com>2018-06-26 06:54:52 +0300
committerBertrand Le Roy <beleroy@microsoft.com>2018-06-26 06:54:52 +0300
commitbfa7cf328708e5f0599ee1422af99051df4264a1 (patch)
tree2585bc063982d3fa020dcbbb663dae1231f1c084 /Xamarin.PropertyEditing
parent43d1eefca7331dc32405d5170d89ccdb025f03c1 (diff)
Store RGB components of `CommonColor`internally as double instead of byte, to improve accuracy. Fixes #325.
Diffstat (limited to 'Xamarin.PropertyEditing')
-rw-r--r--Xamarin.PropertyEditing/Drawing/CommonColor.cs271
-rw-r--r--Xamarin.PropertyEditing/ViewModels/MaterialDesignColorViewModel.cs14
2 files changed, 145 insertions, 140 deletions
diff --git a/Xamarin.PropertyEditing/Drawing/CommonColor.cs b/Xamarin.PropertyEditing/Drawing/CommonColor.cs
index 632b033..73e563a 100644
--- a/Xamarin.PropertyEditing/Drawing/CommonColor.cs
+++ b/Xamarin.PropertyEditing/Drawing/CommonColor.cs
@@ -9,87 +9,72 @@ namespace Xamarin.PropertyEditing.Drawing
public struct CommonColor : IEquatable<CommonColor>
{
public CommonColor (byte r, byte g, byte b, byte a = 255, string label = null)
+ : this ((double)r, g, b, a, label) { }
+ private CommonColor (double r, double g, double b, byte a = 255, string label = null)
{
+ this.r = r;
+ this.g = g;
+ this.b = b;
A = a;
- R = r;
- G = g;
- B = b;
Label = label;
- c = null;
- m = null;
- y = null;
- k = null;
- hueR = null;
- hueG = 0;
- hueB = 0;
- hue = null;
- lightness = null;
- brightness = null;
- saturation = null;
+ this.c = null;
+ this.m = null;
+ this.y = null;
+ this.k = null;
+ this.hueR = null;
+ this.hueG = 0;
+ this.hueB = 0;
+ this.hue = null;
+ this.lightness = null;
+ this.brightness = null;
+ this.saturation = null;
}
/// <summary>
- /// Alpha channel
- /// </summary>
- public byte A { get; }
-
- /// <summary>
/// Red component
/// </summary>
- public byte R { get; }
+ public byte R => DoubleToByte (this.r);
/// <summary>
/// Green component
/// </summary>
- public byte G { get; }
+ public byte G => DoubleToByte (this.g);
/// <summary>
/// Blue component
/// </summary>
- public byte B { get; }
+ public byte B => DoubleToByte (this.b);
+
+ /// <summary>
+ /// Alpha channel
+ /// </summary>
+ public byte A { get; }
/// <summary>
/// An optional label for the color, that does not affect equality or anything else.
/// </summary>
public string Label { get; set; }
- double? k;
/// <summary>
/// Black component
/// </summary>
- public double K => k ?? (k = (double)(255 - (R >= G ? (R >= B ? R : B) : (G >= B ? G : B))) / 255).Value;
-
- double Complement(byte component, double k)
- {
- if (k == 1) return 0;
- var val = (1 - k - (double)component / 255) / (1 - k);
- if (val < 0) return 0;
- if (val > 1) return 1;
- return val;
- }
-
- double? c;
+ public double K => this.k ?? (this.k =
+ (255 - (this.r >= this.g ? (this.r >= this.b ? this.r : this.b) : (this.g >= this.b ? this.g : this.b))) / 255).Value;
/// <summary>
/// Cyan component
/// </summary>
- public double C => c ?? (c = Complement(R, K)).Value;
+ public double C => this.c ?? (this.c = Complement(this.r, K)).Value;
- double? m;
/// <summary>
/// Magenta component
/// </summary>
- public double M => m ?? (m = Complement(G, K)).Value;
+ public double M => this.m ?? (this.m = Complement(this.g, K)).Value;
- double? y;
/// <summary>
/// Yellow component
/// </summary>
- public double Y => y ?? (y = Complement(B, K)).Value;
+ public double Y => this.y ?? (this.y = Complement(this.b, K)).Value;
- static readonly CommonColor Red = new CommonColor (255, 0, 0);
-
- byte? hueR;
- byte hueG, hueB;
/// <summary>
/// Gets a hue from this color.
/// A hue has the highest component of the passed-in color at 255,
@@ -101,103 +86,103 @@ namespace Xamarin.PropertyEditing.Drawing
/// </summary>
public CommonColor HueColor {
get {
- if (hueR.HasValue) return new CommonColor(hueR.Value, hueG, hueB);
+ if (this.hueR.HasValue)
+ return new CommonColor(this.hueR.Value, this.hueG, this.hueB);
// Map grey to red
if (IsGrey) {
- hueR = 255;
- hueG = 0;
- hueB = 0;
- hue = 0;
- return Red;
+ this.hueR = 255;
+ this.hueG = 0;
+ this.hueB = 0;
+ this.hue = 0;
+ return new CommonColor(255, 0, 0);
}
- var isRedMax = R >= G && R >= B;
- var isGreenMax = G >= R && G >= B;
- var isRedMin = R <= G && R <= B;
- var isGreenMin = G <= R && G <= B;
+ var isRedMax = this.r >= this.g && this.r >= this.b;
+ var isGreenMax = this.g >= this.r && this.g >= this.b;
+ var isRedMin = this.r <= this.g && this.r <= this.b;
+ var isGreenMin = this.g <= this.r && this.g <= this.b;
CommonColor hueColor =
isRedMax ?
// Red is max
isGreenMin ?
// Green is min
- new CommonColor (255, 0, InterpolateComponent (B, G, R)) :
+ new CommonColor (255, 0, InterpolateComponent (this.b, this.g, this.r)) :
// Blue is min
- new CommonColor (255, InterpolateComponent (G, B, R), 0) :
+ new CommonColor (255, InterpolateComponent (this.g, this.b, this.r), 0) :
isGreenMax ?
// Green is max
isRedMin ?
// Red is min
- new CommonColor (0, 255, InterpolateComponent (B, R, G)) :
+ new CommonColor (0, 255, InterpolateComponent (this.b, this.r, this.g)) :
// Blue is min
- new CommonColor (InterpolateComponent (R, B, G), 255, 0) :
+ new CommonColor (InterpolateComponent (this.r, this.b, this.g), 255, 0) :
// Blue is max
isRedMin ?
// Red is min
- new CommonColor (0, InterpolateComponent (G, R, B), 255) :
+ new CommonColor (0, InterpolateComponent (this.g, this.r, this.b), 255) :
// Green is min
- new CommonColor (InterpolateComponent (R, G, B), 0, 255);
+ new CommonColor (InterpolateComponent (this.r, this.g, this.b), 0, 255);
- hueR = hueColor.R;
- hueG = hueColor.G;
- hueB = hueColor.B;
+ this.hueR = hueColor.r;
+ this.hueG = hueColor.g;
+ this.hueB = hueColor.b;
return hueColor;
}
}
- double? hue;
/// <summary>
/// The hue for this color, as an angle between 0 and 360 degrees.
/// </summary>
public double Hue {
get {
- if (hue.HasValue) return hue.Value;
+ if (this.hue.HasValue) return this.hue.Value;
// Map grey to 0 degrees
if (IsGrey) {
- hueR = 255;
- hueG = 0;
- hueB = 0;
- hue = 0;
+ this.hueR = 255;
+ this.hueG = 0;
+ this.hueB = 0;
+ this.hue = 0;
return 0;
}
- var isRedMax = R >= G && R >= B;
- var isGreenMax = G >= R && G >= B;
- var isRedMin = R <= G && R <= B;
- var isGreenMin = G <= R && G < B;
- var d = ((double)(isRedMax ? R : isGreenMax ? G : B) - (isRedMin ? R : isGreenMin ? G : B));
+ var isRedMax = this.r >= this.g && this.r >= this.b;
+ var isGreenMax = this.g >= this.r && this.g >= this.b;
+ var isRedMin = this.r <= this.g && this.r <= this.b;
+ var isGreenMin = this.g <= this.r && this.g <= this.b;
+
+ var d = (isRedMax ? this.r : isGreenMax ? this.g : this.b) - (isRedMin ? this.r : isGreenMin ? this.g : this.b);
- hue =
- isRedMax ? (Mod((G - B) / d, 6)) * 60 :
- isGreenMax ? (((B - R)/ d) + 2) * 60 :
- (((R - G) / d) + 4) * 60;
+ this.hue =
+ isRedMax ? (Mod((this.g - this.b) / d, 6)) * 60 :
+ isGreenMax ? (((this.b - this.r)/ d) + 2) * 60 :
+ (((this.r - this.g) / d) + 4) * 60;
- return hue.Value;
+ return this.hue.Value;
}
}
- double? lightness;
/// <summary>
/// The lightness of the color, where white is 1, black is 0, and primary colors are 0.5.
/// </summary>
public double Lightness {
get {
- if (lightness.HasValue) return lightness.Value;
+ if (this.lightness.HasValue) return this.lightness.Value;
- var isRedMax = R >= G && R >= B;
- var isGreenMax = G >= R && G >= B;
- var isRedMin = R <= G && R <= B;
- var isGreenMin = G <= R && G <= B;
+ var isRedMax = this.r >= this.g && this.r >= this.b;
+ var isGreenMax = this.g >= this.r && this.g >= this.b;
+ var isRedMin = this.r <= this.g && this.r <= this.b;
+ var isGreenMin = this.g <= this.r && this.g <= this.b;
- lightness = ((double)(isRedMax ? R : isGreenMax ? G : B) + (isRedMin ? R : isGreenMin ? G : B)) / 510;
+ this.lightness =
+ ((isRedMax ? this.r : isGreenMax ? this.g : this.b) + (isRedMin ? this.r : isGreenMin ? this.g : this.b)) / 510;
- return lightness.Value;
+ return this.lightness.Value;
}
}
- double? brightness;
/// <summary>
/// A brightness between 0 and 1, 1 being the measure for the hue of the color
/// (the hue being a fully-saturated version of the same color), and 0 being
@@ -205,20 +190,19 @@ namespace Xamarin.PropertyEditing.Drawing
/// </summary>
public double Brightness {
get {
- if (brightness.HasValue) return brightness.Value;
+ if (this.brightness.HasValue) return this.brightness.Value;
CommonColor hue = HueColor;
var isRedMaxed = hue.R == 255;
var isGreenMaxed = hue.G == 255;
- return (brightness =
- isRedMaxed ? (double)R / 255 :
- isGreenMaxed ? (double)G / 255 :
- (double)B / 255
+ return (this.brightness =
+ isRedMaxed ? this.r / 255 :
+ isGreenMaxed ? this.g / 255 :
+ this.b / 255
).Value;
}
}
- double? saturation;
/// <summary>
/// A saturation between 0 and 1, 1 being the measure for the hue of the color
/// (the hue being a fully saturated version of the color), and 0 being the
@@ -226,27 +210,48 @@ namespace Xamarin.PropertyEditing.Drawing
/// </summary>
public double Saturation {
get {
- if (saturation.HasValue) return saturation.Value;
+ if (this.saturation.HasValue) return this.saturation.Value;
- if (IsGrey) return (saturation = 0).Value;
+ if (IsGrey) return (this.saturation = 0).Value;
- var isRedMax = R >= G && R >= B;
- var isGreenMax = G >= R && G >= B;
- var isRedMin = R <= G && R <= B;
- var isGreenMin = G <= R && G <= B;
+ var isRedMax = this.r >= this.g && this.r >= this.b;
+ var isGreenMax = this.g >= this.r && this.g >= this.b;
+ var isRedMin = this.r <= this.g && this.r <= this.b;
+ var isGreenMin = this.g <= this.r && this.g <= this.b;
- var max = (double)(isRedMax ? R : isGreenMax ? G : B);
- var d = (max - (isRedMin ? R : isGreenMin ? G : B));
+ var max = isRedMax ? this.r : isGreenMax ? this.g : this.b;
+ var d = (max - (isRedMin ? this.r : isGreenMin ? this.g : this.b));
- saturation = d / max;
+ this.saturation = d / max;
- return saturation.Value;
+ return this.saturation.Value;
}
}
- static readonly int[][] redRanges = new[] { new[] { 0, 60 }, new[] { 300, 360 } };
- static readonly int[][] greenRanges = new[] { new[] { 60, 180 } };
- static readonly int[][] blueRanges = new[] { new[] { 180, 300 } };
+ private readonly double r;
+ private readonly double g;
+ private readonly double b;
+ private double? k;
+ private double? c;
+ private double? m;
+ private double? y;
+ private double? hue;
+ private double? hueR;
+ private double hueG, hueB;
+ private double? lightness;
+ private double? brightness;
+ private double? saturation;
+
+ private double Complement (double component, double k)
+ {
+ if (k == 1) return 0;
+ var val = (1 - k - component / 255) / (1 - k);
+ return val < 0 ? 0 : val > 1 ? 1 : val;
+ }
+
+ private static readonly int[][] redRanges = new[] { new[] { 0, 60 }, new[] { 300, 360 } };
+ private static readonly int[][] greenRanges = new[] { new[] { 60, 180 } };
+ private static readonly int[][] blueRanges = new[] { new[] { 180, 300 } };
/// <summary>
/// Finds the hue color from a hue angle between 0 and 360 degrees.
@@ -281,20 +286,20 @@ namespace Xamarin.PropertyEditing.Drawing
/// <param name="hue">The hue, between 0 and 360</param>
/// <param name="intervals">A set of intervals where the component is 255.</param>
/// <returns>The value of the component.</returns>
- static byte GetHueComponent (double hue, int[][] intervals)
+ private static double GetHueComponent (double hue, int[][] intervals)
{
if (hue < 0 || hue > 360)
throw new ArgumentOutOfRangeException (nameof (hue), "Position must be between 0 and 6.");
- foreach (int[] interval in intervals) {
+ foreach (var interval in intervals) {
// Component is 255 inside the interval
if (hue >= interval[0] && hue <= interval[1])
return 255;
// Component linearly grows from 0 to 255 60 degrees left of the interval
if (hue >= interval[0] - 60 && hue < interval[0])
- return (byte)((hue - interval[0] + 60) * 255 / 60);
+ return (hue - interval[0] + 60) * 255 / 60;
// Component linearly falls from 255 to 0 60 degrees right of the interval
if (hue > interval[1] && hue <= interval[1] + 60)
- return (byte)(255 - (hue - interval[1]) * 255 / 60);
+ return 255 - (hue - interval[1]) * 255 / 60;
}
// Otherwise, it's zero
return 0;
@@ -311,27 +316,27 @@ namespace Xamarin.PropertyEditing.Drawing
// We're between 0 and 120
if (hueColor.R == 255) {
// We're between 0 and 60, and green is on the rising phase
- return (double)hueColor.G * 60 / 255;
+ return hueColor.g * 60 / 255;
}
// We're between 60 and 120, and red is on the declining phase
- return ((double)(255 - hueColor.R) * 60 / 255 + 60);
+ return ((255 - hueColor.r) * 60 / 255 + 60);
}
else if (hueColor.R == 0) {
// We're between 120 and 240
if (hueColor.G == 255) {
// We're between 120 and 180, and blue is on the rise
- return ((double)hueColor.B * 60 / 255 + 120);
+ return (hueColor.b * 60 / 255 + 120);
}
// We're between 180 and 240, and green is declining
- return ((double)(255 - hueColor.G) * 60 / 255 + 180);
+ return ((255 - hueColor.g) * 60 / 255 + 180);
}
// We're between 240 and 360
if (hueColor.B == 255) {
// We're between 240 and 300, and red is on the rise
- return ((double)hueColor.R * 60 / 255 + 240);
+ return (hueColor.r * 60 / 255 + 240);
}
// We're between 300 and 360, and blue is declining
- return ((double)(255 - hueColor.B) * 60 / 255 + 300);
+ return ((255 - hueColor.b) * 60 / 255 + 300);
}
/// <summary>
@@ -347,11 +352,10 @@ namespace Xamarin.PropertyEditing.Drawing
/// <param name="lowest">The lowest component value</param>
/// <param name="highest">The highest component value</param>
/// <returns>The interpolated third component</returns>
- static byte InterpolateComponent (byte component, byte lowest, byte highest)
+ static double InterpolateComponent (double component, double lowest, double highest)
{
var delta = highest - lowest;
- if (delta == 0) return highest;
- return (byte)((component - lowest) * 255 / delta);
+ return delta == 0 ? highest : (component - lowest) * 255 / delta;
}
/// <summary>
@@ -378,9 +382,9 @@ namespace Xamarin.PropertyEditing.Drawing
throw new ArgumentOutOfRangeException (nameof (k));
}
var color = new CommonColor (
- (byte)(255 * (1 - c) * (1 - k)),
- (byte)(255 * (1 - m) * (1 - k)),
- (byte)(255 * (1 - y) * (1 - k)),
+ 255 * (1 - c) * (1 - k),
+ 255 * (1 - m) * (1 - k),
+ 255 * (1 - y) * (1 - k),
alpha) {
// pre-cache CMYK components, since we know them, and it will improve round-tripping
c = c,
@@ -428,7 +432,7 @@ namespace Xamarin.PropertyEditing.Drawing
hue < 180 || hue >= 300 ? x :
c;
- var color = new CommonColor ((byte)(255 * (r + m)), (byte)(255 * (g + m)), (byte)(255 * (b + m)), alpha) {
+ var color = new CommonColor (255 * (r + m), 255 * (g + m), 255 * (b + m), alpha) {
// Pre-cache HLS components, since we know them, and it will improve round-tripping
hue = hue,
lightness = lightness,
@@ -474,7 +478,7 @@ namespace Xamarin.PropertyEditing.Drawing
hue < 180 || hue >= 300 ? x :
c;
- var color = new CommonColor ((byte)(255 * (r + m)), (byte)(255 * (g + m)), (byte)(255 * (b + m)), alpha) {
+ var color = new CommonColor (255 * (r + m), 255 * (g + m), 255 * (b + m), alpha) {
// Pre-cache HLS components, since we know them, and it will improve round-tripping
hue = hue,
brightness = brightness,
@@ -488,13 +492,16 @@ namespace Xamarin.PropertyEditing.Drawing
private static double Mod (double a, double b) => a - b * Math.Floor (a / b);
- public override bool Equals (object obj)
+ private static byte DoubleToByte(double? d)
{
- if (obj == null) return false;
- if (!(obj is CommonColor otherColor)) return false;
- return Equals (otherColor, false);
+ if (!d.HasValue) return 0;
+ var round = Math.Round (d.Value);
+ return (byte)(round < 0 ? 0 : round > 255 ? 255 : round);
}
+ public override bool Equals (object obj)
+ => obj == null ? false : !(obj is CommonColor otherColor) ? false : Equals (otherColor, false);
+
public bool Equals (CommonColor other) => Equals (other, false);
public bool Equals (CommonColor other, bool ignoreAlpha)
@@ -509,9 +516,9 @@ namespace Xamarin.PropertyEditing.Drawing
public static bool operator != (CommonColor left, CommonColor right) => !Equals (left, right);
public static double SquaredDistance (CommonColor left, CommonColor right)
- => (left.R - right.R) * (left.R - right.R)
- + (left.G - right.G) * (left.G - right.G)
- + (left.B - right.B) * (left.B - right.B);
+ => (left.r - right.r) * (left.r - right.r)
+ + (left.g - right.g) * (left.g - right.g)
+ + (left.b - right.b) * (left.b - right.b);
public override int GetHashCode ()
{
diff --git a/Xamarin.PropertyEditing/ViewModels/MaterialDesignColorViewModel.cs b/Xamarin.PropertyEditing/ViewModels/MaterialDesignColorViewModel.cs
index 1c0453f..c88c328 100644
--- a/Xamarin.PropertyEditing/ViewModels/MaterialDesignColorViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/MaterialDesignColorViewModel.cs
@@ -41,11 +41,9 @@ namespace Xamarin.PropertyEditing.ViewModels
{
get {
// Debug.WriteLine ($"Getting ColorName ({this.colorName})");
- if (this.colorName != null) return this.colorName;
- if (Parent.Value is CommonSolidBrush solidBrush) {
- return (this.colorName = Palette.Name);
- }
- return (this.colorName = Strings.MaterialColorGrey);
+ return this.colorName ??
+ (Parent.Value is CommonSolidBrush solidBrush ? (this.colorName = Palette.Name)
+ : (this.colorName = Strings.MaterialColorGrey));
}
set {
// Debug.WriteLine ($"Setting ColorName to {value}");
@@ -251,9 +249,9 @@ namespace Xamarin.PropertyEditing.ViewModels
Color = new CommonColor (newColor.R, newColor.G, newColor.B, alpha);
}
- private static string[] NormalNames = new[] { "50", "100", "200", "300", "400", "500", "600", "700", "800", "900" };
- private static string[] AccentNames = new[] { "A100", "A200", "A400", "A700" };
- private static string[] BlackAndWhiteNames = new[] { Strings.White, Strings.Black };
+ private static readonly string[] NormalNames = new[] { "50", "100", "200", "300", "400", "500", "600", "700", "800", "900" };
+ private static readonly string[] AccentNames = new[] { "A100", "A200", "A400", "A700" };
+ private static readonly string[] BlackAndWhiteNames = new[] { Strings.White, Strings.Black };
private static MaterialColorScale FindPalette (string colorName, bool isAccent = false)
{