| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- using SkiaSharp;
- using System;
- using System.Collections.Generic;
- namespace Ryujinx.Ava.UI.Windows
- {
- static class IconColorPicker
- {
- private const int ColorsPerLine = 64;
- private const int TotalColors = ColorsPerLine * ColorsPerLine;
- private const int UvQuantBits = 3;
- private const int UvQuantShift = BitsPerComponent - UvQuantBits;
- private const int SatQuantBits = 5;
- private const int SatQuantShift = BitsPerComponent - SatQuantBits;
- private const int BitsPerComponent = 8;
- private const int CutOffLuminosity = 64;
- private readonly struct PaletteColor(int qck, byte r, byte g, byte b)
- {
- public int Qck => qck;
- public byte R => r;
- public byte G => g;
- public byte B => b;
- }
- public static SKColor GetFilteredColor(SKBitmap image)
- {
- SKColor color = GetColor(image);
- // We don't want colors that are too dark.
- // If the color is too dark, make it brighter by reducing the range
- // and adding a constant color.
- int luminosity = GetColorApproximateLuminosity(color.Red, color.Green, color.Blue);
- if (luminosity < CutOffLuminosity)
- {
- color = new SKColor(
- (byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue),
- (byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue),
- (byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue));
- }
- return color;
- }
- public static SKColor GetColor(SKBitmap image)
- {
- PaletteColor[] colors = new PaletteColor[TotalColors];
- Dictionary<int, int> dominantColorBin = new Dictionary<int, int>();
- SKColor[] buffer = GetBuffer(image);
- int i = 0;
- int maxHitCount = 0;
- for (int y = 0; y < image.Height; y++)
- {
- int yOffset = y * image.Width;
- for (int x = 0; x < image.Width && i < TotalColors; x++)
- {
- int offset = x + yOffset;
- SKColor pixel = buffer[offset];
- byte cr = pixel.Red;
- byte cg = pixel.Green;
- byte cb = pixel.Blue;
- int qck = GetQuantizedColorKey(cr, cg, cb);
- if (dominantColorBin.TryGetValue(qck, out int hitCount))
- {
- dominantColorBin[qck] = hitCount + 1;
- if (maxHitCount < hitCount)
- {
- maxHitCount = hitCount;
- }
- }
- else
- {
- dominantColorBin.Add(qck, 1);
- }
- colors[i++] = new PaletteColor(qck, cr, cg, cb);
- }
- }
- int highScore = -1;
- PaletteColor bestCandidate = default;
- for (i = 0; i < TotalColors; i++)
- {
- int score = GetColorScore(dominantColorBin, maxHitCount, colors[i]);
- if (highScore < score)
- {
- highScore = score;
- bestCandidate = colors[i];
- }
- }
- return new SKColor(bestCandidate.R, bestCandidate.G, bestCandidate.B);
- }
- public static SKColor[] GetBuffer(SKBitmap image)
- {
- SKColor[] pixels = new SKColor[image.Width * image.Height];
- for (int y = 0; y < image.Height; y++)
- {
- for (int x = 0; x < image.Width; x++)
- {
- pixels[x + y * image.Width] = image.GetPixel(x, y);
- }
- }
- return pixels;
- }
- private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
- {
- int hitCount = dominantColorBin[color.Qck];
- int balancedHitCount = BalanceHitCount(hitCount, maxHitCount);
- int quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift;
- int value = GetColorValue(color);
- // If the color is rarely used on the image,
- // then chances are that there's a better candidate, even if the saturation value
- // is high. By multiplying the saturation value with a weight, we can lower
- // it if the color is almost never used (hit count is low).
- int satWeighted = quantSat;
- int satWeight = balancedHitCount << 5;
- if (satWeight < 0x100)
- {
- satWeighted = (satWeighted * satWeight) >> 8;
- }
- // Compute score from saturation and dominance of the color.
- // We prefer more vivid colors over dominant ones, so give more weight to the saturation.
- int score = ((satWeighted << 1) + balancedHitCount) * value;
- return score;
- }
- private static int GetColorSaturation(PaletteColor color)
- {
- int cMax = Math.Max(Math.Max(color.R, color.G), color.B);
- if (cMax == 0)
- {
- return 0;
- }
- int cMin = Math.Min(Math.Min(color.R, color.G), color.B);
- int delta = cMax - cMin;
- return (delta << 8) / cMax;
- }
- private static int GetColorValue(PaletteColor color) => Math.Max(Math.Max(color.R, color.G), color.B);
- private static int BalanceHitCount(int hitCount, int maxHitCount) => (hitCount << 8) / maxHitCount;
- private static int GetColorApproximateLuminosity(byte r, byte g, byte b) => (r + g + b) / 3;
- private static int GetQuantizedColorKey(byte r, byte g, byte b)
- {
- int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
- int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
- return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits);
- }
- }
- }
|