PaletteCollection.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. using Resources;
  2. using SmartCoalApplication.Core;
  3. using SmartCoalApplication.Resources;
  4. using SmartCoalApplication.SystemLayer;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11. namespace SmartCoalApplication.PluginAssemblys
  12. {
  13. internal sealed class PaletteCollection
  14. {
  15. /// <summary>
  16. /// The required number of colors for a palette.
  17. /// </summary>
  18. /// <remarks>
  19. /// If a palette is loaded with fewer colors than this, then it will be padded with entries
  20. /// that are equal to DefaultColor. If a palette is loaded with more colors than this, then
  21. /// the 97th through the last color will be discarded.
  22. /// </remarks>
  23. public const int PaletteColorCount = 96;
  24. private const char lineCommentChar = ';';
  25. private static readonly Encoding paletteFileEncoding = Encoding.UTF8;
  26. private Dictionary<string, ColorBgra[]> palettes; // maps from Name -> Palette
  27. public static bool ValidatePaletteName(string paletteName)
  28. {
  29. if (string.IsNullOrEmpty(paletteName))
  30. {
  31. return false;
  32. }
  33. try
  34. {
  35. string fileName = Path.ChangeExtension(paletteName, PalettesFileExtension);
  36. string pathName = Path.Combine(PalettesPath, fileName);
  37. char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
  38. char[] invalidPathNameChars = Path.GetInvalidPathChars();
  39. if (pathName.IndexOfAny(invalidPathNameChars) != -1)
  40. {
  41. return false;
  42. }
  43. if (fileName.IndexOfAny(invalidFileNameChars) != -1)
  44. {
  45. return false;
  46. }
  47. return true;
  48. }
  49. catch (ArgumentNullException)
  50. {
  51. return false;
  52. }
  53. catch (ArgumentException)
  54. {
  55. return false;
  56. }
  57. }
  58. public static ColorBgra DefaultColor
  59. {
  60. get
  61. {
  62. return ColorBgra.White;
  63. }
  64. }
  65. public static ColorBgra[] DefaultPalette
  66. {
  67. get
  68. {
  69. return
  70. new ColorBgra[PaletteColorCount]
  71. {
  72. ColorBgra.FromUInt32(0xff000000),
  73. ColorBgra.FromUInt32(0xff404040),
  74. ColorBgra.FromUInt32(0xffff0000),
  75. ColorBgra.FromUInt32(0xffff6a00),
  76. ColorBgra.FromUInt32(0xffffd800),
  77. ColorBgra.FromUInt32(0xffb6ff00),
  78. ColorBgra.FromUInt32(0xff4cff00),
  79. ColorBgra.FromUInt32(0xff00ff21),
  80. ColorBgra.FromUInt32(0xff00ff90),
  81. ColorBgra.FromUInt32(0xff00ffff),
  82. ColorBgra.FromUInt32(0xff0094ff),
  83. ColorBgra.FromUInt32(0xff0026ff),
  84. ColorBgra.FromUInt32(0xff4800ff),
  85. ColorBgra.FromUInt32(0xffb200ff),
  86. ColorBgra.FromUInt32(0xffff00dc),
  87. ColorBgra.FromUInt32(0xffff006e),
  88. ColorBgra.FromUInt32(0xffffffff),
  89. ColorBgra.FromUInt32(0xff808080),
  90. ColorBgra.FromUInt32(0xff7f0000),
  91. ColorBgra.FromUInt32(0xff7f3300),
  92. ColorBgra.FromUInt32(0xff7f6a00),
  93. ColorBgra.FromUInt32(0xff5b7f00),
  94. ColorBgra.FromUInt32(0xff267f00),
  95. ColorBgra.FromUInt32(0xff007f0e),
  96. ColorBgra.FromUInt32(0xff007f46),
  97. ColorBgra.FromUInt32(0xff007f7f),
  98. ColorBgra.FromUInt32(0xff004a7f),
  99. ColorBgra.FromUInt32(0xff00137f),
  100. ColorBgra.FromUInt32(0xff21007f),
  101. ColorBgra.FromUInt32(0xff57007f),
  102. ColorBgra.FromUInt32(0xff7f006e),
  103. ColorBgra.FromUInt32(0xff7f0037),
  104. ColorBgra.FromUInt32(0xffa0a0a0),
  105. ColorBgra.FromUInt32(0xff303030),
  106. ColorBgra.FromUInt32(0xffff7f7f),
  107. ColorBgra.FromUInt32(0xffffb27f),
  108. ColorBgra.FromUInt32(0xffffe97f),
  109. ColorBgra.FromUInt32(0xffdaff7f),
  110. ColorBgra.FromUInt32(0xffa5ff7f),
  111. ColorBgra.FromUInt32(0xff7fff8e),
  112. ColorBgra.FromUInt32(0xff7fffc5),
  113. ColorBgra.FromUInt32(0xff7fffff),
  114. ColorBgra.FromUInt32(0xff7fc9ff),
  115. ColorBgra.FromUInt32(0xff7f92ff),
  116. ColorBgra.FromUInt32(0xffa17fff),
  117. ColorBgra.FromUInt32(0xffd67fff),
  118. ColorBgra.FromUInt32(0xffff7fed),
  119. ColorBgra.FromUInt32(0xffff7fb6),
  120. ColorBgra.FromUInt32(0xffc0c0c0),
  121. ColorBgra.FromUInt32(0xff606060),
  122. ColorBgra.FromUInt32(0xff7f3f3f),
  123. ColorBgra.FromUInt32(0xff7f593f),
  124. ColorBgra.FromUInt32(0xff7f743f),
  125. ColorBgra.FromUInt32(0xff6d7f3f),
  126. ColorBgra.FromUInt32(0xff527f3f),
  127. ColorBgra.FromUInt32(0xff3f7f47),
  128. ColorBgra.FromUInt32(0xff3f7f62),
  129. ColorBgra.FromUInt32(0xff3f7f7f),
  130. ColorBgra.FromUInt32(0xff3f647f),
  131. ColorBgra.FromUInt32(0xff3f497f),
  132. ColorBgra.FromUInt32(0xff503f7f),
  133. ColorBgra.FromUInt32(0xff6b3f7f),
  134. ColorBgra.FromUInt32(0xff7f3f76),
  135. ColorBgra.FromUInt32(0xff7f3f5b),
  136. ColorBgra.FromUInt32(0x80000000),
  137. ColorBgra.FromUInt32(0x80404040),
  138. ColorBgra.FromUInt32(0x80ff0000),
  139. ColorBgra.FromUInt32(0x80ff6a00),
  140. ColorBgra.FromUInt32(0x80ffd800),
  141. ColorBgra.FromUInt32(0x80b6ff00),
  142. ColorBgra.FromUInt32(0x804cff00),
  143. ColorBgra.FromUInt32(0x8000ff21),
  144. ColorBgra.FromUInt32(0x8000ff90),
  145. ColorBgra.FromUInt32(0x8000ffff),
  146. ColorBgra.FromUInt32(0x800094ff),
  147. ColorBgra.FromUInt32(0x800026ff),
  148. ColorBgra.FromUInt32(0x804800ff),
  149. ColorBgra.FromUInt32(0x80b200ff),
  150. ColorBgra.FromUInt32(0x80ff00dc),
  151. ColorBgra.FromUInt32(0x80ff006e),
  152. ColorBgra.FromUInt32(0x80ffffff),
  153. ColorBgra.FromUInt32(0x80808080),
  154. ColorBgra.FromUInt32(0x807f0000),
  155. ColorBgra.FromUInt32(0x807f3300),
  156. ColorBgra.FromUInt32(0x807f6a00),
  157. ColorBgra.FromUInt32(0x805b7f00),
  158. ColorBgra.FromUInt32(0x80267f00),
  159. ColorBgra.FromUInt32(0x80007f0e),
  160. ColorBgra.FromUInt32(0x80007f46),
  161. ColorBgra.FromUInt32(0x80007f7f),
  162. ColorBgra.FromUInt32(0x80004a7f),
  163. ColorBgra.FromUInt32(0x8000137f),
  164. ColorBgra.FromUInt32(0x8021007f),
  165. ColorBgra.FromUInt32(0x8057007f),
  166. ColorBgra.FromUInt32(0x807f006e),
  167. ColorBgra.FromUInt32(0x807f0037)
  168. };
  169. }
  170. }
  171. public string[] PaletteNames
  172. {
  173. get
  174. {
  175. Dictionary<string, ColorBgra[]>.KeyCollection keyCollection = this.palettes.Keys;
  176. string[] keys = new string[keyCollection.Count];
  177. int index = 0;
  178. foreach (string key in keyCollection)
  179. {
  180. keys[index] = key;
  181. ++index;
  182. }
  183. return keys;
  184. }
  185. }
  186. public PaletteCollection()
  187. {
  188. this.palettes = new Dictionary<string, ColorBgra[]>();
  189. }
  190. public static string PalettesFileExtension
  191. {
  192. get
  193. {
  194. // seems like using .txt is just simpler: makes it obvious that it is human readable/writable, and not xml (which has high perf costs @ startup)
  195. return ".txt";
  196. }
  197. }
  198. public static string PalettesPath
  199. {
  200. get
  201. {
  202. string userDataPath = PdnInfo.UserDataPath;
  203. string palettesDirName = PdnResources.GetString("ColorPalettes.UserDataSubDirName");
  204. string palettesPath = Path.Combine(userDataPath, palettesDirName);
  205. return palettesPath;
  206. }
  207. }
  208. private static bool ParseColor(string colorString, out ColorBgra color)
  209. {
  210. bool returnVal;
  211. if (string.IsNullOrEmpty(colorString))
  212. {
  213. color = DefaultColor;
  214. returnVal = false;
  215. return returnVal;
  216. }
  217. /*int v = 0;
  218. if (!int.TryParse(colorString, out v))
  219. {
  220. color = DefaultColor;
  221. returnVal = false;
  222. return returnVal;
  223. }*/
  224. try
  225. {
  226. color = ColorBgra.ParseHexString(colorString);
  227. returnVal = true;
  228. }
  229. catch (Exception ex)
  230. {
  231. Tracing.Ping("Exception while parsing color string '" + colorString + "' :" + ex.ToString());
  232. color = DefaultColor;
  233. returnVal = false;
  234. }
  235. return returnVal;
  236. }
  237. public static string RemoveComments(string line)
  238. {
  239. int commentIndex = line.IndexOf(lineCommentChar);
  240. if (commentIndex != -1)
  241. {
  242. return line.Substring(0, commentIndex);
  243. }
  244. else
  245. {
  246. return line;
  247. }
  248. }
  249. public static bool ParsePaletteLine(string line, out ColorBgra color)
  250. {
  251. color = DefaultColor;
  252. if (line == null)
  253. {
  254. return false;
  255. }
  256. string trimmed1 = RemoveComments(line);
  257. string trimmed = trimmed1.Trim();
  258. if (trimmed.Length < 4)
  259. {
  260. return false;
  261. }
  262. bool gotColor = ParseColor(trimmed, out color);
  263. return gotColor;
  264. }
  265. public static ColorBgra[] ParsePaletteString(string paletteString)
  266. {
  267. List<ColorBgra> palette = new List<ColorBgra>();
  268. StringReader sr = new StringReader(paletteString);
  269. while (true)
  270. {
  271. string line = sr.ReadLine();
  272. if (line == null)
  273. {
  274. break;
  275. }
  276. ColorBgra color;
  277. bool gotColor = ParsePaletteLine(line, out color);
  278. if (gotColor && palette.Count < PaletteColorCount)
  279. {
  280. palette.Add(color);
  281. }
  282. }
  283. return palette.ToArray();
  284. }
  285. public static ColorBgra[] LoadPalette(string palettePath)
  286. {
  287. ColorBgra[] palette = null;
  288. FileStream paletteFile = new FileStream(palettePath, FileMode.Open, FileAccess.Read, FileShare.Read);
  289. try
  290. {
  291. StreamReader sr = new StreamReader(paletteFile, paletteFileEncoding);
  292. try
  293. {
  294. string paletteString = sr.ReadToEnd();
  295. palette = ParsePaletteString(paletteString);
  296. }
  297. finally
  298. {
  299. sr.Close(); // as per docs, this also closes paletteFile
  300. sr = null;
  301. paletteFile = null;
  302. }
  303. }
  304. finally
  305. {
  306. if (paletteFile != null)
  307. {
  308. paletteFile.Close();
  309. paletteFile = null;
  310. }
  311. }
  312. if (palette == null)
  313. {
  314. return new ColorBgra[0];
  315. }
  316. else
  317. {
  318. return palette;
  319. }
  320. }
  321. private static string FormatColor(ColorBgra color)
  322. {
  323. return color.ToHexString();
  324. }
  325. public static string GetPaletteSaveString(ColorBgra[] palette)
  326. {
  327. StringWriter sw = new StringWriter();
  328. string header = PdnResources.GetString("ColorPalette.SaveHeader");
  329. sw.WriteLine(header);
  330. foreach (ColorBgra color in palette)
  331. {
  332. string colorString = FormatColor(color);
  333. sw.WriteLine(colorString);
  334. }
  335. return sw.ToString();
  336. }
  337. public static void SavePalette(string palettePath, ColorBgra[] palette)
  338. {
  339. FileStream paletteFile = new FileStream(palettePath, FileMode.Create, FileAccess.Write, FileShare.Read);
  340. try
  341. {
  342. StreamWriter sw = new StreamWriter(paletteFile, paletteFileEncoding);
  343. try
  344. {
  345. string paletteString = GetPaletteSaveString(palette);
  346. sw.WriteLine(paletteString);
  347. }
  348. finally
  349. {
  350. sw.Close(); // as per documentation, this closes paletteFile as well
  351. sw = null;
  352. paletteFile = null;
  353. }
  354. }
  355. finally
  356. {
  357. if (paletteFile != null)
  358. {
  359. paletteFile.Close();
  360. paletteFile = null;
  361. }
  362. }
  363. }
  364. private bool DoesPalettesPathExist()
  365. {
  366. string palettesPath = PalettesPath;
  367. bool returnVal;
  368. try
  369. {
  370. returnVal = Directory.Exists(palettesPath);
  371. }
  372. catch (Exception ex)
  373. {
  374. Tracing.Ping("Exception while querying whether palettes path, '" + palettesPath + "' exists: " + ex.ToString());
  375. returnVal = false;
  376. }
  377. return returnVal;
  378. }
  379. public static void EnsurePalettesPathExists()
  380. {
  381. string palettesPath = PalettesPath;
  382. try
  383. {
  384. if (!Directory.Exists(palettesPath))
  385. {
  386. Directory.CreateDirectory(palettesPath);
  387. }
  388. }
  389. catch (Exception ex)
  390. {
  391. // Fail silently
  392. Tracing.Ping("Exception while ensuring that " + palettesPath + " exists: " + ex.ToString());
  393. }
  394. }
  395. public void Load()
  396. {
  397. if (!DoesPalettesPathExist())
  398. {
  399. // can't load anything! no custom palettes exist.
  400. // we really don't want to create this directory unless they've
  401. // saved anything in to it. this is especially important if they
  402. // install and then set their language. the path name is localized
  403. // so we only want the final name to be there.
  404. this.palettes = new Dictionary<string, ColorBgra[]>();
  405. return;
  406. }
  407. string[] pathNames = new string[0];
  408. try
  409. {
  410. pathNames = Directory.GetFiles(PalettesPath, "*" + PalettesFileExtension);
  411. }
  412. catch (Exception ex)
  413. {
  414. // Trace the error, but otherwise fail silently
  415. Tracing.Ping("Exception while retrieving list of palette filenames: " + ex.ToString());
  416. }
  417. // Now, load the palettes
  418. Dictionary<string, ColorBgra[]> newPalettes = new Dictionary<string, ColorBgra[]>();
  419. foreach (string pathName in pathNames)
  420. {
  421. ColorBgra[] palette = LoadPalette(pathName);
  422. ColorBgra[] goodPalette = EnsureValidPaletteSize(palette);
  423. string fileName = Path.GetFileName(pathName);
  424. string paletteName = Path.ChangeExtension(fileName, null);
  425. newPalettes.Add(paletteName, goodPalette);
  426. }
  427. this.palettes = newPalettes;
  428. }
  429. public void Save()
  430. {
  431. EnsurePalettesPathExists();
  432. string palettesPath = PalettesPath;
  433. foreach (string paletteName in this.palettes.Keys)
  434. {
  435. ColorBgra[] palette = this.palettes[paletteName];
  436. ColorBgra[] goodPalette = EnsureValidPaletteSize(palette);
  437. string fileName = Path.ChangeExtension(paletteName, PalettesFileExtension);
  438. string pathName = Path.Combine(palettesPath, fileName);
  439. SavePalette(pathName, goodPalette);
  440. }
  441. }
  442. public static ColorBgra[] EnsureValidPaletteSize(ColorBgra[] colors)
  443. {
  444. ColorBgra[] validPalette = new ColorBgra[PaletteColorCount];
  445. for (int i = 0; i < PaletteColorCount; ++i)
  446. {
  447. if (i >= colors.Length)
  448. {
  449. validPalette[i] = DefaultColor;
  450. }
  451. else
  452. {
  453. validPalette[i] = colors[i];
  454. }
  455. }
  456. return validPalette;
  457. }
  458. public ColorBgra[] Get(string name)
  459. {
  460. string existingKeyName;
  461. bool contains = Contains(name, out existingKeyName);
  462. if (contains)
  463. {
  464. ColorBgra[] colors = this.palettes[existingKeyName];
  465. return (ColorBgra[])colors.Clone();
  466. }
  467. else
  468. {
  469. return null;
  470. }
  471. }
  472. public bool Contains(string name, out string existingKeyName)
  473. {
  474. foreach (string key in this.palettes.Keys)
  475. {
  476. if (string.Compare(key, name, StringComparison.InvariantCultureIgnoreCase) == 0)
  477. {
  478. existingKeyName = key;
  479. return true;
  480. }
  481. }
  482. existingKeyName = null;
  483. return false;
  484. }
  485. public void AddOrUpdate(string name, ColorBgra[] colors)
  486. {
  487. if (colors.Length != PaletteColorCount)
  488. {
  489. throw new ArgumentException("palette must have exactly " + PaletteColorCount.ToString() + " colors (actual: " + colors.Length.ToString() + ")");
  490. }
  491. Delete(name);
  492. this.palettes.Add(name, colors);
  493. }
  494. public bool Delete(string name)
  495. {
  496. string existingKeyName;
  497. bool contains = Contains(name, out existingKeyName);
  498. if (contains)
  499. {
  500. this.palettes.Remove(existingKeyName);
  501. return true;
  502. }
  503. return false;
  504. }
  505. }
  506. }