using System.Reflection; using Godot; using Polonium; using Polonium.Attributes; using Polonium.SkinManagers.Generic; using System; using System.Collections.Generic; using System.Linq; using Skin = Polonium.SkinManagers.Skin; [AutoRegister] // ReSharper disable once CheckNamespace public static class SkinPaletteRegister { public static void Register() { Type gr = typeof(PoloniumRegistry.SkinRegistry<>); Assembly assembly = Assembly.GetExecutingAssembly(); IEnumerable skinTypes = assembly .GetTypes() .Where(t => t.IsSubclassOf(typeof(Skin)) && !t.IsAbstract) .Where(t => t.GetCustomAttributes(typeof(SkinInfo), true).Any()); foreach (Type skinType in skinTypes) { Type ist = gr.MakeGenericType(skinType); string pth = skinType.GetCustomAttribute()!.Path; int k = skinType.GetCustomAttribute()!.Roots; PropertyInfo paletteRoots = ist.GetProperty("PaletteRoots"); PropertyInfo paletteMap = ist.GetProperty("PaletteMap"); Color[] roots; Dictionary map; if (IsAutoPalette(skinType)) (roots, map) = KMean(ExtractColors(pth), k); else (roots, map) = GetPalette(pth); paletteRoots!.SetValue(null, roots); paletteMap!.SetValue(null, map); } } private static bool IsAutoPalette(Type type) { Type x = type; while (x != typeof(object) && x != null) { if (x.IsGenericType && x.GetGenericTypeDefinition() == typeof(AutoPaletteSkin<>)) return true; x = x.BaseType; } return false; } private static HashSet ExtractColors(string path) { HashSet colors = new(); CompressedTexture2D texture = ResourceLoader.Load(path); Image image = texture.GetImage(); for (int x = 0; x < image.GetWidth(); x++) for (int y = 0; y < image.GetHeight(); y++) { Color px = image.GetPixel(x, y); if (px.A8 > 0) colors.Add(px); } return colors; } private static (Color[], Dictionary) GetPalette(string path) { //HashSet colors = ExtractColors(path); HashSet colors = new(); Dictionary> map = new(); CompressedTexture2D texture = ResourceLoader.Load(path); Image image = texture.GetImage(); int height = image.GetHeight(); int width = image.GetWidth(); for (int y = height - 32; y < height; y++) { Color px0 = image.GetPixel(width-32, y); if (px0.A8 == 0) break; colors.Add(px0); map[px0] = new HashSet { px0 }; for (int x = width - 32; x < width; x++) { Color px = image.GetPixel(x, y); if (px.A8 == 0) break; map[px0].Add(px); } } return (colors.ToArray(), map.ToDictionary(p => p.Key, p => p.Value.ToArray())); } private static (Color[], Dictionary) KMean(HashSet colors, int k) { var colorPoints = colors.ToList(); var random = new Random(); Color[] centroids = colorPoints.OrderBy(x => random.Next()).Take(k).ToArray(); bool hasConverged = false; Dictionary> clusters = new(); while (!hasConverged) { clusters = centroids.ToDictionary(c => c, c => new List()); foreach (Color color in colorPoints) { Color nearestCentroid = centroids.OrderBy(c => c.DistanceTo(color)).First(); clusters[nearestCentroid].Add(color); } hasConverged = true; for (int i = 0; i < centroids.Length; i++) { if (clusters[centroids[i]].Count == 0) continue; Color newCentroid = clusters[centroids[i]].AverageColor(); if (!newCentroid.Equals(centroids[i])) { centroids[i] = newCentroid; hasConverged = false; } } } Dictionary resultMap = clusters.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.ToArray() ); return (centroids, resultMap); } private static Vector4 ToVector(this Color a) => new(a.R, a.G, a.B, a.A); private static double DistanceTo(this Color a, Color b) => a.ToVector().DistanceTo(b.ToVector()); private static Color ToColor(this Vector4 a) => new Color(a.X, a.Y, a.Z, a.W); private static Color AverageColor(this IEnumerable a) { Vector4[] aa = a.Select(x => x.ToVector()).ToArray(); Vector4 res = Vector4.Zero; foreach (Vector4 c in aa) res += c; return (res / aa.Length).ToColor(); } }