diff --git a/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs b/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs
index 4fe7d22c4..f61b70dd5 100644
--- a/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs
+++ b/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs
@@ -8,21 +8,6 @@ namespace Artemis.Core.Services
///
internal class ColorQuantizerService : IColorQuantizerService
{
- private static float GetComparisonValue(float sat, float targetSaturation, float luma, float targetLuma)
- {
- static float InvertDiff(float value, float target)
- {
- return 1 - Math.Abs(value - target);
- }
-
- const float totalWeight = weightSaturation + weightLuma;
-
- float totalValue = InvertDiff(sat, targetSaturation) * weightSaturation +
- InvertDiff(luma, targetLuma) * weightLuma;
-
- return totalValue / totalWeight;
- }
-
///
public SKColor[] Quantize(IEnumerable colors, int amount)
{
@@ -48,29 +33,12 @@ namespace Artemis.Core.Services
///
public SKColor FindColorVariation(IEnumerable colors, ColorType type, bool ignoreLimits = false)
{
- (float targetLuma, float minLuma, float maxLuma, float targetSaturation, float minSaturation, float maxSaturation) = type switch
- {
- ColorType.Vibrant => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetVibrantSaturation, minVibrantSaturation, 1f),
- ColorType.LightVibrant => (targetLightLuma, minLightLuma, 1f, targetVibrantSaturation, minVibrantSaturation, 1f),
- ColorType.DarkVibrant => (targetDarkLuma, 0f, maxDarkLuma, targetVibrantSaturation, minVibrantSaturation, 1f),
- ColorType.Muted => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetMutesSaturation, 0, maxMutesSaturation),
- ColorType.LightMuted => (targetLightLuma, minLightLuma, 1f, targetMutesSaturation, 0, maxMutesSaturation),
- ColorType.DarkMuted => (targetDarkLuma, 0, maxDarkLuma, targetMutesSaturation, 0, maxMutesSaturation),
- _ => (0.5f, 0f, 1f, 0.5f, 0f, 1f)
- };
-
- float bestColorScore = float.MinValue;
+ float bestColorScore = 0;
SKColor bestColor = SKColor.Empty;
+
foreach (SKColor clr in colors)
{
- clr.ToHsl(out float _, out float sat, out float luma);
- sat /= 100f;
- luma /= 100f;
-
- if (!ignoreLimits && (sat <= minSaturation || sat >= maxSaturation || luma <= minLuma || luma >= maxLuma))
- continue;
-
- float score = GetComparisonValue(sat, targetSaturation, luma, targetLuma);
+ float score = GetScore(clr, type, ignoreLimits);
if (score > bestColorScore)
{
bestColorScore = score;
@@ -81,6 +49,82 @@ namespace Artemis.Core.Services
return bestColor;
}
+ ///
+ public ColorSwatch FindAllColorVariations(IEnumerable colors, bool ignoreLimits = false)
+ {
+ SKColor bestVibrantColor = SKColor.Empty;
+ SKColor bestLightVibrantColor = SKColor.Empty;
+ SKColor bestDarkVibrantColor = SKColor.Empty;
+ SKColor bestMutedColor = SKColor.Empty;
+ SKColor bestLightMutedColor = SKColor.Empty;
+ SKColor bestDarkMutedColor = SKColor.Empty;
+ float bestVibrantScore = 0;
+ float bestLightVibrantScore = 0;
+ float bestDarkVibrantScore = 0;
+ float bestMutedScore = 0;
+ float bestLightMutedScore = 0;
+ float bestDarkMutedScore = 0;
+
+ //ugly but at least we only loop through the enumerable once ¯\_(ツ)_/¯
+ foreach (var color in colors)
+ {
+ static void SetIfBetterScore(ref float bestScore, ref SKColor bestColor, SKColor newColor, ColorType type, bool ignoreLimits)
+ {
+ float newScore = GetScore(newColor, type, ignoreLimits);
+ if (newScore > bestScore)
+ {
+ bestScore = newScore;
+ bestColor = newColor;
+ }
+ }
+
+ SetIfBetterScore(ref bestVibrantScore, ref bestVibrantColor, color, ColorType.Vibrant, ignoreLimits);
+ SetIfBetterScore(ref bestLightVibrantScore, ref bestLightVibrantColor, color, ColorType.LightVibrant, ignoreLimits);
+ SetIfBetterScore(ref bestDarkVibrantScore, ref bestDarkVibrantColor, color, ColorType.DarkVibrant, ignoreLimits);
+ SetIfBetterScore(ref bestMutedScore, ref bestMutedColor, color, ColorType.Muted, ignoreLimits);
+ SetIfBetterScore(ref bestLightMutedScore, ref bestLightMutedColor, color, ColorType.LightMuted, ignoreLimits);
+ SetIfBetterScore(ref bestDarkMutedScore, ref bestDarkMutedColor, color, ColorType.DarkMuted, ignoreLimits);
+ }
+
+ return new()
+ {
+ Vibrant = bestVibrantColor,
+ LightVibrant = bestLightVibrantColor,
+ DarkVibrant = bestDarkVibrantColor,
+ Muted = bestMutedColor,
+ LightMuted = bestLightMutedColor,
+ DarkMuted = bestDarkMutedColor,
+ };
+ }
+
+ private static float GetScore(SKColor color, ColorType type, bool ignoreLimits = false)
+ {
+ static float InvertDiff(float value, float target)
+ {
+ return 1 - Math.Abs(value - target);
+ }
+
+ color.ToHsl(out float _, out float saturation, out float luma);
+ saturation /= 100f;
+ luma /= 100f;
+
+ if (!ignoreLimits &&
+ (saturation <= GetMinSaturation(type) || saturation >= GetMaxSaturation(type)
+ || luma <= GetMinLuma(type) || luma >= GetMaxLuma(type)))
+ {
+ //if either saturation or luma falls outside the min-max, return the
+ //lowest score possible unless we're ignoring these limits.
+ return float.MinValue;
+ }
+
+ float totalValue = (InvertDiff(saturation, GetTargetSaturation(type)) * weightSaturation) +
+ (InvertDiff(luma, GetTargetLuma(type)) * weightLuma);
+
+ const float totalWeight = weightSaturation + weightLuma;
+
+ return totalValue / totalWeight;
+ }
+
#region Constants
private const float targetDarkLuma = 0.26f;
@@ -97,6 +141,72 @@ namespace Artemis.Core.Services
private const float weightSaturation = 3f;
private const float weightLuma = 5f;
+ private static float GetTargetLuma(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => targetNormalLuma,
+ ColorType.LightVibrant => targetLightLuma,
+ ColorType.DarkVibrant => targetDarkLuma,
+ ColorType.Muted => targetNormalLuma,
+ ColorType.LightMuted => targetLightLuma,
+ ColorType.DarkMuted => targetDarkLuma,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
+ private static float GetMinLuma(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => minNormalLuma,
+ ColorType.LightVibrant => minLightLuma,
+ ColorType.DarkVibrant => 0f,
+ ColorType.Muted => minNormalLuma,
+ ColorType.LightMuted => minLightLuma,
+ ColorType.DarkMuted => 0,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
+ private static float GetMaxLuma(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => maxNormalLuma,
+ ColorType.LightVibrant => 1f,
+ ColorType.DarkVibrant => maxDarkLuma,
+ ColorType.Muted => maxNormalLuma,
+ ColorType.LightMuted => 1f,
+ ColorType.DarkMuted => maxDarkLuma,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
+ private static float GetTargetSaturation(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => targetVibrantSaturation,
+ ColorType.LightVibrant => targetVibrantSaturation,
+ ColorType.DarkVibrant => targetVibrantSaturation,
+ ColorType.Muted => targetMutesSaturation,
+ ColorType.LightMuted => targetMutesSaturation,
+ ColorType.DarkMuted => targetMutesSaturation,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
+ private static float GetMinSaturation(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => minVibrantSaturation,
+ ColorType.LightVibrant => minVibrantSaturation,
+ ColorType.DarkVibrant => minVibrantSaturation,
+ ColorType.Muted => 0,
+ ColorType.LightMuted => 0,
+ ColorType.DarkMuted => 0,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
+ private static float GetMaxSaturation(ColorType colorType) => colorType switch
+ {
+ ColorType.Vibrant => 1f,
+ ColorType.LightVibrant => 1f,
+ ColorType.DarkVibrant => 1f,
+ ColorType.Muted => maxMutesSaturation,
+ ColorType.LightMuted => maxMutesSaturation,
+ ColorType.DarkMuted => maxMutesSaturation,
+ _ => throw new ArgumentException(nameof(colorType))
+ };
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs b/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs
new file mode 100644
index 000000000..81abb7cc2
--- /dev/null
+++ b/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs
@@ -0,0 +1,40 @@
+using SkiaSharp;
+
+namespace Artemis.Core.Services
+{
+ ///
+ /// Swatch containing the known useful color variations.
+ ///
+ public struct ColorSwatch
+ {
+ ///
+ /// The component.
+ ///
+ public SKColor Vibrant { get; init; }
+
+ ///
+ /// The component.
+ ///
+ public SKColor LightVibrant { get; init; }
+
+ ///
+ /// The component.
+ ///
+ public SKColor DarkVibrant { get; init; }
+
+ ///
+ /// The component.
+ ///
+ public SKColor Muted { get; init; }
+
+ ///
+ /// The component.
+ ///
+ public SKColor LightMuted { get; init; }
+
+ ///
+ /// The component.
+ ///
+ public SKColor DarkMuted { get; init; }
+ }
+}
diff --git a/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs b/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs
index 6b0971684..82d70715a 100644
--- a/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs
+++ b/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs
@@ -26,5 +26,13 @@ namespace Artemis.Core.Services
/// Ignore hard limits on whether a color is considered for each category. Result may be if this is false
/// The color found
public SKColor FindColorVariation(IEnumerable colors, ColorType type, bool ignoreLimits = false);
+
+ ///
+ /// Finds all the color variations available and returns a struct containing them all.
+ ///
+ /// The colors to find the variations in
+ /// Ignore hard limits on whether a color is considered for each category. Some colors may be if this is false
+ /// A swatch containing all color variations
+ public ColorSwatch FindAllColorVariations(IEnumerable colors, bool ignoreLimits = false);
}
}