1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Drawing;
24 using System.Globalization;
25 using System.Text;
26 
27 using KeePass.UI;
28 
29 namespace KeePass.App
30 {
31 	public enum AppIconType
32 	{
33 		None = 0,
34 		Main,
35 		QuadNormal,
36 		QuadLocked
37 	}
38 
39 	public static class AppIcons
40 	{
41 		private static Dictionary<string, Icon> g_dCache = new Dictionary<string, Icon>();
42 		private static readonly object g_oCacheSync = new object();
43 
44 		// The average hue of the main icon is about 225 degrees
45 		private static readonly Color g_clrMain = UIUtil.ColorFromHsv(225, 1, 1);
46 
47 		private static Color[] g_vColors = null;
48 		internal static Color[] Colors
49 		{
50 			get
51 			{
52 				if(g_vColors == null)
53 				{
54 					List<Color> l = new List<Color>();
55 
56 					for(int h = 0; h < 360; h += 15)
57 						l.Add(UIUtil.ColorFromHsv((h + 225) % 360, 1, 1));
58 
59 					g_vColors = l.ToArray();
60 
61 #if DEBUG
62 					Color[] vInvariant = new Color[] { g_clrMain,
63 						Color.Red, Color.Lime, Color.Blue, Color.Yellow };
64 					foreach(Color clr in vInvariant)
65 					{
66 						Debug.Assert(UIUtil.ColorsEqual(RoundColor(clr), clr));
67 					}
68 #endif
69 				}
70 
71 				return g_vColors;
72 			}
73 		}
74 
75 		public static Icon Default
76 		{
77 			get { return Get(AppIconType.Main, Size.Empty, Color.Empty); }
78 		}
79 
Get(AppIconType t, Size sz, Color clr)80 		public static Icon Get(AppIconType t, Size sz, Color clr)
81 		{
82 			int w = Math.Min(Math.Max(sz.Width, 0), 256);
83 			int h = Math.Min(Math.Max(sz.Height, 0), 256);
84 			if((w == 0) || (h == 0))
85 			{
86 				Size szDefault = UIUtil.GetIconSize();
87 				w = szDefault.Width;
88 				h = szDefault.Height;
89 			}
90 
91 			Color c = clr;
92 			if(!UIUtil.ColorsEqual(c, Color.Empty)) c = RoundColor(c);
93 
94 			NumberFormatInfo nf = NumberFormatInfo.InvariantInfo;
95 			string strID = ((long)t).ToString(nf) + ":" + w.ToString(nf) + ":" +
96 				h.ToString(nf) + ":" + c.ToArgb().ToString(nf);
97 
98 			Icon ico = null;
99 			lock(g_oCacheSync)
100 			{
101 				if(g_dCache.TryGetValue(strID, out ico)) return ico;
102 			}
103 
104 			if(t == AppIconType.Main)
105 				ico = Properties.Resources.KeePass;
106 			else if(t == AppIconType.QuadNormal)
107 				ico = Properties.Resources.QuadNormal;
108 			else if(t == AppIconType.QuadLocked)
109 			{
110 				ico = Properties.Resources.QuadLocked;
111 
112 				Debug.Assert(UIUtil.ColorsEqual(c, Color.Empty));
113 				c = Color.Empty; // This icon should not be recolored
114 			}
115 			else { Debug.Assert(false); }
116 
117 			if((ico != null) && !UIUtil.ColorsEqual(c, Color.Empty))
118 				ico = IconColorizer.Recolor(ico, c);
119 
120 			// Select requested resolution
121 			if(ico != null) ico = new Icon(ico, w, h); // Preserves icon data
122 
123 			Debug.Assert(ico != null);
124 			lock(g_oCacheSync) { g_dCache[strID] = ico; }
125 			return ico;
126 		}
127 
ColorDist(Color c1, Color c2)128 		private static int ColorDist(Color c1, Color c2)
129 		{
130 			int dR = (int)c1.R - (int)c2.R;
131 			int dG = (int)c1.G - (int)c2.G;
132 			int dB = (int)c1.B - (int)c2.B;
133 			return ((dR * dR) + (dG * dG) + (dB * dB));
134 		}
135 
136 		/// <summary>
137 		/// Round <paramref name="clr" /> to the nearest supported color.
138 		/// </summary>
RoundColor(Color clr)139 		public static Color RoundColor(Color clr)
140 		{
141 			Debug.Assert(!UIUtil.ColorsEqual(clr, Color.Empty));
142 			if((clr.R == clr.B) && (clr.G == clr.B))
143 				return g_clrMain; // Gray => default
144 
145 			Color[] v = AppIcons.Colors;
146 
147 			int c = clr.ToArgb();
148 			for(int i = 0; i < v.Length; ++i)
149 			{
150 				if(v[i].ToArgb() == c) return clr;
151 			}
152 
153 			int iMin = 0, dMin = int.MaxValue;
154 			for(int i = 0; i < v.Length; ++i)
155 			{
156 				int d = ColorDist(clr, v[i]);
157 				if(d < dMin)
158 				{
159 					iMin = i;
160 					dMin = d;
161 				}
162 			}
163 			return v[iMin];
164 		}
165 	}
166 }
167