PaletteCollection.cs 19 KB

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