PageRenderTime 39ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Data/FileType.cs

https://bitbucket.org/tuldok89/openpdn
C# | 540 lines | 365 code | 73 blank | 102 comment | 33 complexity | c4e16ee329d1ae224afeffce096b5c89 MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.Base;
  10. using PaintDotNet.Quantize;
  11. using PaintDotNet.SystemLayer;
  12. using System;
  13. using System.Drawing;
  14. using System.IO;
  15. using System.Reflection;
  16. using System.Runtime.Serialization;
  17. using System.Runtime.Serialization.Formatters.Binary;
  18. using System.Text;
  19. namespace PaintDotNet
  20. {
  21. /// <summary>
  22. /// Represents one type of file that PaintDotNet can load or save.
  23. /// </summary>
  24. public abstract class FileType
  25. {
  26. private readonly string[] _extensions;
  27. private readonly string _name;
  28. private readonly FileTypeFlags _flags;
  29. // should be of the format ".ext" ... like ".bmp" or ".jpg"
  30. // The first extension in this list is the default extension (".jpg" for JPEG,
  31. // for instance, as ".jfif" etc. are not seen very often)
  32. public string[] Extensions
  33. {
  34. get
  35. {
  36. return (string[])_extensions.Clone();
  37. }
  38. }
  39. /// <summary>
  40. /// Gets the default extension for the FileType.
  41. /// </summary>
  42. /// <remarks>
  43. /// This is always the first extension that is supported
  44. /// </remarks>
  45. public string DefaultExtension
  46. {
  47. get
  48. {
  49. return _extensions[0];
  50. }
  51. }
  52. /// <summary>
  53. /// Returns the friendly name of the file type, such as "Bitmap" or "JPEG".
  54. /// </summary>
  55. public string Name
  56. {
  57. get
  58. {
  59. return _name;
  60. }
  61. }
  62. public FileTypeFlags Flags
  63. {
  64. get
  65. {
  66. return _flags;
  67. }
  68. }
  69. /// <summary>
  70. /// Gets a flag indicating whether this FileType supports layers.
  71. /// </summary>
  72. /// <remarks>
  73. /// If a FileType is asked to save a Document that has more than one layer,
  74. /// it will flatten it before it saves it.
  75. /// </remarks>
  76. public bool SupportsLayers
  77. {
  78. get
  79. {
  80. return (_flags & FileTypeFlags.SupportsLayers) != 0;
  81. }
  82. }
  83. /// <summary>
  84. /// Gets a flag indicating whether this FileType supports custom headers.
  85. /// </summary>
  86. /// <remarks>
  87. /// If this returns false, then the Document's CustomHeaders will be discarded
  88. /// on saving.
  89. /// </remarks>
  90. public bool SupportsCustomHeaders
  91. {
  92. get
  93. {
  94. return (_flags & FileTypeFlags.SupportsCustomHeaders) != 0;
  95. }
  96. }
  97. /// <summary>
  98. /// Gets a flag indicating whether this FileType supports the Save() method.
  99. /// </summary>
  100. /// <remarks>
  101. /// If this property returns false, calling Save() will throw a NotSupportedException.
  102. /// </remarks>
  103. public bool SupportsSaving
  104. {
  105. get
  106. {
  107. return (_flags & FileTypeFlags.SupportsSaving) != 0;
  108. }
  109. }
  110. /// <summary>
  111. /// Gets a flag indicating whether this FileType supports the Load() method.
  112. /// </summary>
  113. /// <remarks>
  114. /// If this property returns false, calling Load() will throw a NotSupportedException.
  115. /// </remarks>
  116. public bool SupportsLoading
  117. {
  118. get
  119. {
  120. return (_flags & FileTypeFlags.SupportsLoading) != 0;
  121. }
  122. }
  123. /// <summary>
  124. /// Gets a flag indicating whether this FileType reports progress while saving.
  125. /// </summary>
  126. /// <remarks>
  127. /// If false, then the callback delegate passed to Save() will be ignored.
  128. /// </remarks>
  129. public bool SavesWithProgress
  130. {
  131. get
  132. {
  133. return (_flags & FileTypeFlags.SavesWithProgress) != 0;
  134. }
  135. }
  136. [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)]
  137. protected FileType(string name, bool supportsLayers, bool supportsCustomHeaders, string[] extensions)
  138. : this(name, supportsLayers, supportsCustomHeaders, true, true, false, extensions)
  139. {
  140. }
  141. [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)]
  142. protected FileType(string name, bool supportsLayers, bool supportsCustomHeaders, bool supportsSaving,
  143. bool supportsLoading, bool savesWithProgress, string[] extensions)
  144. : this(name,
  145. (supportsLayers ? FileTypeFlags.SupportsLayers : 0) |
  146. (supportsCustomHeaders ? FileTypeFlags.SupportsCustomHeaders : 0) |
  147. (supportsSaving ? FileTypeFlags.SupportsSaving : 0) |
  148. (supportsLoading ? FileTypeFlags.SupportsLoading : 0) |
  149. (savesWithProgress ? FileTypeFlags.SavesWithProgress : 0),
  150. extensions)
  151. {
  152. }
  153. protected FileType(string name, FileTypeFlags flags, string[] extensions)
  154. {
  155. _name = name;
  156. _flags = flags;
  157. _extensions = (string[])extensions.Clone();
  158. }
  159. public bool SupportsExtension(string ext)
  160. {
  161. foreach (string ext2 in _extensions)
  162. {
  163. if (0 == string.Compare(ext2, ext, StringComparison.InvariantCultureIgnoreCase))
  164. {
  165. return true;
  166. }
  167. }
  168. return false;
  169. }
  170. [Obsolete("This method is retained for compatibility with older plugins. Please use the other overload of Quantize().")]
  171. protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, ProgressEventHandler progressCallback)
  172. {
  173. // This is the old version of the function took maxColors=255 to mean 255 colors + 1 slot for transparency.
  174. // The new version expects maxColors=256 and enableTransparency=true for
  175. if (maxColors < 2 || maxColors > 255)
  176. {
  177. throw new ArgumentOutOfRangeException(
  178. "maxColors",
  179. maxColors,
  180. "Out of bounds. Must be in the range [2, 255]");
  181. }
  182. return Quantize(quantizeMe, ditherAmount, maxColors + 1, true, progressCallback);
  183. }
  184. /// <summary>
  185. /// Takes a Surface and quantizes it down to an 8-bit bitmap.
  186. /// </summary>
  187. /// <param name="quantizeMe">The Surface to quantize.</param>
  188. /// <param name="ditherAmount">How strong should dithering be applied. 0 for no dithering, 8 for full dithering. 7 is generally a good default to use.</param>
  189. /// <param name="maxColors">The maximum number of colors to use. This may range from 2 to 256.</param>
  190. /// <param name="enableTransparency">If true, then one color slot will be reserved for transparency. Any color with an alpha value less than 255 will be transparent in the output.</param>
  191. /// <param name="progressCallback">The progress callback delegate.</param>
  192. /// <returns>An 8-bit Bitmap that is the same size as quantizeMe.</returns>
  193. protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, bool enableTransparency, ProgressEventHandler progressCallback)
  194. {
  195. if (ditherAmount < 0 || ditherAmount > 8)
  196. {
  197. throw new ArgumentOutOfRangeException(
  198. "ditherAmount",
  199. ditherAmount,
  200. "Out of bounds. Must be in the range [0, 8]");
  201. }
  202. if (maxColors < 2 || maxColors > 256)
  203. {
  204. throw new ArgumentOutOfRangeException(
  205. "maxColors",
  206. maxColors,
  207. "Out of bounds. Must be in the range [2, 256]");
  208. }
  209. // TODO: detect if transparency is needed? or take another argument
  210. using (Bitmap bitmap = quantizeMe.CreateAliasedBitmap(quantizeMe.Bounds, true))
  211. {
  212. var quantizer = new OctreeQuantizer(maxColors, enableTransparency)
  213. {DitherLevel = ditherAmount};
  214. Bitmap quantized = quantizer.Quantize(bitmap, progressCallback);
  215. return quantized;
  216. }
  217. }
  218. [Obsolete("Use the other Save() overload instead", true)]
  219. public void Save(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback, bool rememberToken)
  220. {
  221. using (var scratch = new Surface(input.Width, input.Height))
  222. {
  223. Save(input, output, token, callback, rememberToken);
  224. }
  225. }
  226. public void Save(
  227. Document input,
  228. Stream output,
  229. SaveConfigToken token,
  230. Surface scratchSurface,
  231. ProgressEventHandler callback,
  232. bool rememberToken)
  233. {
  234. Tracing.LogFeature("Save(" + GetType().FullName + ")");
  235. if (!SupportsSaving)
  236. {
  237. throw new NotImplementedException("Saving is not supported by this FileType");
  238. }
  239. Surface disposeMe = null;
  240. if (scratchSurface == null)
  241. {
  242. disposeMe = new Surface(input.Size);
  243. scratchSurface = disposeMe;
  244. }
  245. else if (scratchSurface.Size != input.Size)
  246. {
  247. throw new ArgumentException("scratchSurface.Size must equal input.Size");
  248. }
  249. if (rememberToken)
  250. {
  251. Type ourType = GetType();
  252. string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter";
  253. var ms = new MemoryStream();
  254. var formatter = new BinaryFormatter();
  255. var deferredFormatter = new DeferredFormatter(false, null);
  256. var streamingContext = new StreamingContext(formatter.Context.State, deferredFormatter);
  257. formatter.Context = streamingContext;
  258. object tokenSubset = GetSerializablePortionOfSaveConfigToken(token);
  259. formatter.Serialize(ms, tokenSubset);
  260. deferredFormatter.FinishSerialization(ms);
  261. byte[] bytes = ms.GetBuffer();
  262. string base64Bytes = Convert.ToBase64String(bytes);
  263. Settings.CurrentUser.SetString(savedTokenName, base64Bytes);
  264. }
  265. try
  266. {
  267. OnSave(input, output, token, scratchSurface, callback);
  268. }
  269. catch (OnSaveNotImplementedException)
  270. {
  271. OldOnSaveTrampoline(input, output, token, callback);
  272. }
  273. if (disposeMe == null) return;
  274. disposeMe.Dispose();
  275. disposeMe = null;
  276. }
  277. protected virtual SaveConfigToken GetSaveConfigTokenFromSerializablePortion(object portion)
  278. {
  279. return (SaveConfigToken)portion;
  280. }
  281. protected virtual object GetSerializablePortionOfSaveConfigToken(SaveConfigToken token)
  282. {
  283. return token;
  284. }
  285. private sealed class OnSaveNotImplementedException
  286. : Exception
  287. {
  288. public OnSaveNotImplementedException(string message)
  289. : base(message)
  290. {
  291. }
  292. }
  293. /// <summary>
  294. /// Because the old OnSave() method is obsolete, we must use reflection to call it.
  295. /// This is important for legacy FileType plugins. It allows us to ensure that no
  296. /// new plugins can be compiled using the old OnSave() overload.
  297. /// </summary>
  298. private void OldOnSaveTrampoline(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback)
  299. {
  300. MethodInfo onSave = GetType().GetMethod(
  301. "OnSave",
  302. BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
  303. Type.DefaultBinder,
  304. new[]
  305. {
  306. typeof(Document),
  307. typeof(Stream),
  308. typeof(SaveConfigToken),
  309. typeof(ProgressEventHandler)
  310. },
  311. null);
  312. onSave.Invoke(
  313. this,
  314. new object[]
  315. {
  316. input,
  317. output,
  318. token,
  319. callback
  320. });
  321. }
  322. [Obsolete("Use the other OnSave() overload. It provides a scratch rendering surface that may enable your plugin to conserve memory usage.")]
  323. protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback)
  324. {
  325. }
  326. protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
  327. {
  328. throw new OnSaveNotImplementedException("Derived classes must implement this method. It is virtual instead of abstract in order to maintain compatibility with legacy plugins.");
  329. }
  330. /// <summary>
  331. /// Determines if saving with a given SaveConfigToken would alter the image
  332. /// in any way. Put another way, if the document is saved with these settings
  333. /// and then immediately loaded, would it have exactly the same pixel values?
  334. /// Any lossy codec should return 'false'.
  335. /// This value is used to optimizing preview rendering memory usage, and as such
  336. /// flattening should not be taken in to consideration. For example, the codec
  337. /// for PNG returns true, even though it flattens the image.
  338. /// </summary>
  339. /// <param name="token">The SaveConfigToken to determine reflexiveness for.</param>
  340. /// <returns>true if the save would be reflexive, false if not</returns>
  341. /// <remarks>If the SaveConfigToken is for another FileType, the result is undefined.</remarks>
  342. public virtual bool IsReflexive(SaveConfigToken token)
  343. {
  344. return false;
  345. }
  346. public virtual SaveConfigWidget CreateSaveConfigWidget()
  347. {
  348. return new NoSaveConfigWidget();
  349. }
  350. [Serializable]
  351. private sealed class NoSaveConfigToken
  352. : SaveConfigToken
  353. {
  354. }
  355. /// <summary>
  356. /// Gets a flag indicating whether or not the file type supports configuration
  357. /// via a SaveConfigToken and SaveConfigWidget.
  358. /// </summary>
  359. /// <remarks>
  360. /// Implementers of FileType derived classes don't need to do anything special
  361. /// for this property to be accurate. If your FileType implements
  362. /// CreateDefaultSaveConfigToken, this will correctly return true.
  363. /// </remarks>
  364. public bool SupportsConfiguration
  365. {
  366. get
  367. {
  368. SaveConfigToken token = CreateDefaultSaveConfigToken();
  369. return !(token is NoSaveConfigToken);
  370. }
  371. }
  372. public SaveConfigToken GetLastSaveConfigToken()
  373. {
  374. Type ourType = GetType();
  375. string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter";
  376. string savedToken = Settings.CurrentUser.GetString(savedTokenName, null);
  377. SaveConfigToken saveConfigToken = null;
  378. if (savedToken != null)
  379. {
  380. try
  381. {
  382. byte[] bytes = Convert.FromBase64String(savedToken);
  383. var ms = new MemoryStream(bytes);
  384. var formatter = new BinaryFormatter();
  385. var deferred = new DeferredFormatter();
  386. var streamingContext = new StreamingContext(formatter.Context.State, deferred);
  387. formatter.Context = streamingContext;
  388. var sfb = new SerializationFallbackBinder();
  389. sfb.AddAssembly(GetType().Assembly);
  390. sfb.AddAssembly(typeof(FileType).Assembly);
  391. formatter.Binder = sfb;
  392. object obj = formatter.Deserialize(ms);
  393. deferred.FinishDeserialization(ms);
  394. ms.Close();
  395. ms = null;
  396. //SaveConfigToken sct = new SaveConfigToken();
  397. //saveConfigToken = (SaveConfigToken)obj;
  398. saveConfigToken = GetSaveConfigTokenFromSerializablePortion(obj);
  399. }
  400. catch (Exception)
  401. {
  402. // Ignore erros and revert to default
  403. saveConfigToken = null;
  404. }
  405. }
  406. return saveConfigToken ?? (saveConfigToken = CreateDefaultSaveConfigToken());
  407. }
  408. public SaveConfigToken CreateDefaultSaveConfigToken()
  409. {
  410. return OnCreateDefaultSaveConfigToken();
  411. }
  412. /// <summary>
  413. /// Creates a SaveConfigToken for this FileType with the default values.
  414. /// </summary>
  415. protected virtual SaveConfigToken OnCreateDefaultSaveConfigToken()
  416. {
  417. return new NoSaveConfigToken();
  418. }
  419. public Document Load(Stream input)
  420. {
  421. Tracing.LogFeature("Load(" + GetType().FullName + ")");
  422. if (!SupportsLoading)
  423. {
  424. throw new NotSupportedException("Loading not supported for this FileType");
  425. }
  426. return OnLoad(input);
  427. }
  428. protected abstract Document OnLoad(Stream input);
  429. public override bool Equals(object obj)
  430. {
  431. if (obj == null || !(obj is FileType))
  432. {
  433. return false;
  434. }
  435. return _name.Equals(((FileType)obj).Name);
  436. }
  437. public override int GetHashCode()
  438. {
  439. return _name.GetHashCode();
  440. }
  441. /// <summary>
  442. /// Returns a string that can be used for populating a *FileDialog common dialog.
  443. /// </summary>
  444. public override string ToString()
  445. {
  446. var sb = new StringBuilder(_name);
  447. sb.Append(" (");
  448. for (int i = 0; i < _extensions.Length; ++i)
  449. {
  450. sb.Append("*");
  451. sb.Append(_extensions[i]);
  452. sb.Append(i != _extensions.Length - 1 ? "; " : ")");
  453. }
  454. sb.Append("|");
  455. for (int i = 0; i < _extensions.Length; ++i)
  456. {
  457. sb.Append("*");
  458. sb.Append(_extensions[i]);
  459. if (i != _extensions.Length - 1)
  460. {
  461. sb.Append(";");
  462. }
  463. }
  464. return sb.ToString();
  465. }
  466. }
  467. }