/src/Data/FileType.cs
C# | 540 lines | 365 code | 73 blank | 102 comment | 33 complexity | c4e16ee329d1ae224afeffce096b5c89 MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using PaintDotNet.Base;
- using PaintDotNet.Quantize;
- using PaintDotNet.SystemLayer;
- using System;
- using System.Drawing;
- using System.IO;
- using System.Reflection;
- using System.Runtime.Serialization;
- using System.Runtime.Serialization.Formatters.Binary;
- using System.Text;
-
- namespace PaintDotNet
- {
- /// <summary>
- /// Represents one type of file that PaintDotNet can load or save.
- /// </summary>
- public abstract class FileType
- {
- private readonly string[] _extensions;
- private readonly string _name;
- private readonly FileTypeFlags _flags;
-
- // should be of the format ".ext" ... like ".bmp" or ".jpg"
- // The first extension in this list is the default extension (".jpg" for JPEG,
- // for instance, as ".jfif" etc. are not seen very often)
- public string[] Extensions
- {
- get
- {
- return (string[])_extensions.Clone();
- }
- }
-
- /// <summary>
- /// Gets the default extension for the FileType.
- /// </summary>
- /// <remarks>
- /// This is always the first extension that is supported
- /// </remarks>
- public string DefaultExtension
- {
- get
- {
- return _extensions[0];
- }
- }
-
- /// <summary>
- /// Returns the friendly name of the file type, such as "Bitmap" or "JPEG".
- /// </summary>
- public string Name
- {
- get
- {
- return _name;
- }
- }
-
- public FileTypeFlags Flags
- {
- get
- {
- return _flags;
- }
- }
-
- /// <summary>
- /// Gets a flag indicating whether this FileType supports layers.
- /// </summary>
- /// <remarks>
- /// If a FileType is asked to save a Document that has more than one layer,
- /// it will flatten it before it saves it.
- /// </remarks>
- public bool SupportsLayers
- {
- get
- {
- return (_flags & FileTypeFlags.SupportsLayers) != 0;
- }
- }
-
- /// <summary>
- /// Gets a flag indicating whether this FileType supports custom headers.
- /// </summary>
- /// <remarks>
- /// If this returns false, then the Document's CustomHeaders will be discarded
- /// on saving.
- /// </remarks>
- public bool SupportsCustomHeaders
- {
- get
- {
- return (_flags & FileTypeFlags.SupportsCustomHeaders) != 0;
- }
- }
-
- /// <summary>
- /// Gets a flag indicating whether this FileType supports the Save() method.
- /// </summary>
- /// <remarks>
- /// If this property returns false, calling Save() will throw a NotSupportedException.
- /// </remarks>
- public bool SupportsSaving
- {
- get
- {
- return (_flags & FileTypeFlags.SupportsSaving) != 0;
- }
- }
-
- /// <summary>
- /// Gets a flag indicating whether this FileType supports the Load() method.
- /// </summary>
- /// <remarks>
- /// If this property returns false, calling Load() will throw a NotSupportedException.
- /// </remarks>
- public bool SupportsLoading
- {
- get
- {
- return (_flags & FileTypeFlags.SupportsLoading) != 0;
- }
- }
-
- /// <summary>
- /// Gets a flag indicating whether this FileType reports progress while saving.
- /// </summary>
- /// <remarks>
- /// If false, then the callback delegate passed to Save() will be ignored.
- /// </remarks>
- public bool SavesWithProgress
- {
- get
- {
- return (_flags & FileTypeFlags.SavesWithProgress) != 0;
- }
- }
-
- [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)]
- protected FileType(string name, bool supportsLayers, bool supportsCustomHeaders, string[] extensions)
- : this(name, supportsLayers, supportsCustomHeaders, true, true, false, extensions)
- {
- }
-
- [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)]
- protected FileType(string name, bool supportsLayers, bool supportsCustomHeaders, bool supportsSaving,
- bool supportsLoading, bool savesWithProgress, string[] extensions)
- : this(name,
- (supportsLayers ? FileTypeFlags.SupportsLayers : 0) |
- (supportsCustomHeaders ? FileTypeFlags.SupportsCustomHeaders : 0) |
- (supportsSaving ? FileTypeFlags.SupportsSaving : 0) |
- (supportsLoading ? FileTypeFlags.SupportsLoading : 0) |
- (savesWithProgress ? FileTypeFlags.SavesWithProgress : 0),
- extensions)
- {
- }
-
- protected FileType(string name, FileTypeFlags flags, string[] extensions)
- {
- _name = name;
- _flags = flags;
- _extensions = (string[])extensions.Clone();
- }
-
- public bool SupportsExtension(string ext)
- {
- foreach (string ext2 in _extensions)
- {
- if (0 == string.Compare(ext2, ext, StringComparison.InvariantCultureIgnoreCase))
- {
- return true;
- }
- }
-
- return false;
- }
-
- [Obsolete("This method is retained for compatibility with older plugins. Please use the other overload of Quantize().")]
- protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, ProgressEventHandler progressCallback)
- {
- // This is the old version of the function took maxColors=255 to mean 255 colors + 1 slot for transparency.
- // The new version expects maxColors=256 and enableTransparency=true for
-
- if (maxColors < 2 || maxColors > 255)
- {
- throw new ArgumentOutOfRangeException(
- "maxColors",
- maxColors,
- "Out of bounds. Must be in the range [2, 255]");
- }
-
- return Quantize(quantizeMe, ditherAmount, maxColors + 1, true, progressCallback);
- }
-
- /// <summary>
- /// Takes a Surface and quantizes it down to an 8-bit bitmap.
- /// </summary>
- /// <param name="quantizeMe">The Surface to quantize.</param>
- /// <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>
- /// <param name="maxColors">The maximum number of colors to use. This may range from 2 to 256.</param>
- /// <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>
- /// <param name="progressCallback">The progress callback delegate.</param>
- /// <returns>An 8-bit Bitmap that is the same size as quantizeMe.</returns>
- protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, bool enableTransparency, ProgressEventHandler progressCallback)
- {
- if (ditherAmount < 0 || ditherAmount > 8)
- {
- throw new ArgumentOutOfRangeException(
- "ditherAmount",
- ditherAmount,
- "Out of bounds. Must be in the range [0, 8]");
- }
-
- if (maxColors < 2 || maxColors > 256)
- {
- throw new ArgumentOutOfRangeException(
- "maxColors",
- maxColors,
- "Out of bounds. Must be in the range [2, 256]");
- }
-
- // TODO: detect if transparency is needed? or take another argument
-
- using (Bitmap bitmap = quantizeMe.CreateAliasedBitmap(quantizeMe.Bounds, true))
- {
- var quantizer = new OctreeQuantizer(maxColors, enableTransparency)
- {DitherLevel = ditherAmount};
- Bitmap quantized = quantizer.Quantize(bitmap, progressCallback);
- return quantized;
- }
- }
-
- [Obsolete("Use the other Save() overload instead", true)]
- public void Save(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback, bool rememberToken)
- {
- using (var scratch = new Surface(input.Width, input.Height))
- {
- Save(input, output, token, callback, rememberToken);
- }
- }
-
- public void Save(
- Document input,
- Stream output,
- SaveConfigToken token,
- Surface scratchSurface,
- ProgressEventHandler callback,
- bool rememberToken)
- {
- Tracing.LogFeature("Save(" + GetType().FullName + ")");
-
- if (!SupportsSaving)
- {
- throw new NotImplementedException("Saving is not supported by this FileType");
- }
- Surface disposeMe = null;
-
- if (scratchSurface == null)
- {
- disposeMe = new Surface(input.Size);
- scratchSurface = disposeMe;
- }
- else if (scratchSurface.Size != input.Size)
- {
- throw new ArgumentException("scratchSurface.Size must equal input.Size");
- }
-
- if (rememberToken)
- {
- Type ourType = GetType();
- string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter";
-
- var ms = new MemoryStream();
-
- var formatter = new BinaryFormatter();
- var deferredFormatter = new DeferredFormatter(false, null);
- var streamingContext = new StreamingContext(formatter.Context.State, deferredFormatter);
- formatter.Context = streamingContext;
-
- object tokenSubset = GetSerializablePortionOfSaveConfigToken(token);
-
- formatter.Serialize(ms, tokenSubset);
- deferredFormatter.FinishSerialization(ms);
-
- byte[] bytes = ms.GetBuffer();
- string base64Bytes = Convert.ToBase64String(bytes);
-
- Settings.CurrentUser.SetString(savedTokenName, base64Bytes);
- }
-
- try
- {
- OnSave(input, output, token, scratchSurface, callback);
- }
-
- catch (OnSaveNotImplementedException)
- {
- OldOnSaveTrampoline(input, output, token, callback);
- }
-
- if (disposeMe == null) return;
- disposeMe.Dispose();
- disposeMe = null;
- }
-
- protected virtual SaveConfigToken GetSaveConfigTokenFromSerializablePortion(object portion)
- {
- return (SaveConfigToken)portion;
- }
-
- protected virtual object GetSerializablePortionOfSaveConfigToken(SaveConfigToken token)
- {
- return token;
- }
-
- private sealed class OnSaveNotImplementedException
- : Exception
- {
- public OnSaveNotImplementedException(string message)
- : base(message)
- {
- }
- }
-
- /// <summary>
- /// Because the old OnSave() method is obsolete, we must use reflection to call it.
- /// This is important for legacy FileType plugins. It allows us to ensure that no
- /// new plugins can be compiled using the old OnSave() overload.
- /// </summary>
- private void OldOnSaveTrampoline(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback)
- {
- MethodInfo onSave = GetType().GetMethod(
- "OnSave",
- BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
- Type.DefaultBinder,
- new[]
- {
- typeof(Document),
- typeof(Stream),
- typeof(SaveConfigToken),
- typeof(ProgressEventHandler)
- },
- null);
-
- onSave.Invoke(
- this,
- new object[]
- {
- input,
- output,
- token,
- callback
- });
- }
-
- [Obsolete("Use the other OnSave() overload. It provides a scratch rendering surface that may enable your plugin to conserve memory usage.")]
- protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback)
- {
- }
-
- protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
- {
- throw new OnSaveNotImplementedException("Derived classes must implement this method. It is virtual instead of abstract in order to maintain compatibility with legacy plugins.");
- }
-
- /// <summary>
- /// Determines if saving with a given SaveConfigToken would alter the image
- /// in any way. Put another way, if the document is saved with these settings
- /// and then immediately loaded, would it have exactly the same pixel values?
- /// Any lossy codec should return 'false'.
- /// This value is used to optimizing preview rendering memory usage, and as such
- /// flattening should not be taken in to consideration. For example, the codec
- /// for PNG returns true, even though it flattens the image.
- /// </summary>
- /// <param name="token">The SaveConfigToken to determine reflexiveness for.</param>
- /// <returns>true if the save would be reflexive, false if not</returns>
- /// <remarks>If the SaveConfigToken is for another FileType, the result is undefined.</remarks>
- public virtual bool IsReflexive(SaveConfigToken token)
- {
- return false;
- }
-
- public virtual SaveConfigWidget CreateSaveConfigWidget()
- {
- return new NoSaveConfigWidget();
- }
-
- [Serializable]
- private sealed class NoSaveConfigToken
- : SaveConfigToken
- {
- }
-
- /// <summary>
- /// Gets a flag indicating whether or not the file type supports configuration
- /// via a SaveConfigToken and SaveConfigWidget.
- /// </summary>
- /// <remarks>
- /// Implementers of FileType derived classes don't need to do anything special
- /// for this property to be accurate. If your FileType implements
- /// CreateDefaultSaveConfigToken, this will correctly return true.
- /// </remarks>
- public bool SupportsConfiguration
- {
- get
- {
- SaveConfigToken token = CreateDefaultSaveConfigToken();
- return !(token is NoSaveConfigToken);
- }
- }
-
- public SaveConfigToken GetLastSaveConfigToken()
- {
- Type ourType = GetType();
- string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter";
- string savedToken = Settings.CurrentUser.GetString(savedTokenName, null);
- SaveConfigToken saveConfigToken = null;
-
- if (savedToken != null)
- {
- try
- {
- byte[] bytes = Convert.FromBase64String(savedToken);
-
- var ms = new MemoryStream(bytes);
-
- var formatter = new BinaryFormatter();
- var deferred = new DeferredFormatter();
- var streamingContext = new StreamingContext(formatter.Context.State, deferred);
- formatter.Context = streamingContext;
-
- var sfb = new SerializationFallbackBinder();
- sfb.AddAssembly(GetType().Assembly);
- sfb.AddAssembly(typeof(FileType).Assembly);
- formatter.Binder = sfb;
-
- object obj = formatter.Deserialize(ms);
- deferred.FinishDeserialization(ms);
-
- ms.Close();
- ms = null;
-
- //SaveConfigToken sct = new SaveConfigToken();
- //saveConfigToken = (SaveConfigToken)obj;
- saveConfigToken = GetSaveConfigTokenFromSerializablePortion(obj);
- }
-
- catch (Exception)
- {
- // Ignore erros and revert to default
- saveConfigToken = null;
- }
- }
-
- return saveConfigToken ?? (saveConfigToken = CreateDefaultSaveConfigToken());
- }
-
- public SaveConfigToken CreateDefaultSaveConfigToken()
- {
- return OnCreateDefaultSaveConfigToken();
- }
-
- /// <summary>
- /// Creates a SaveConfigToken for this FileType with the default values.
- /// </summary>
- protected virtual SaveConfigToken OnCreateDefaultSaveConfigToken()
- {
- return new NoSaveConfigToken();
- }
-
- public Document Load(Stream input)
- {
- Tracing.LogFeature("Load(" + GetType().FullName + ")");
-
- if (!SupportsLoading)
- {
- throw new NotSupportedException("Loading not supported for this FileType");
- }
- return OnLoad(input);
- }
-
- protected abstract Document OnLoad(Stream input);
-
- public override bool Equals(object obj)
- {
- if (obj == null || !(obj is FileType))
- {
- return false;
- }
-
- return _name.Equals(((FileType)obj).Name);
- }
-
- public override int GetHashCode()
- {
- return _name.GetHashCode();
- }
-
- /// <summary>
- /// Returns a string that can be used for populating a *FileDialog common dialog.
- /// </summary>
- public override string ToString()
- {
- var sb = new StringBuilder(_name);
- sb.Append(" (");
-
- for (int i = 0; i < _extensions.Length; ++i)
- {
- sb.Append("*");
- sb.Append(_extensions[i]);
-
- sb.Append(i != _extensions.Length - 1 ? "; " : ")");
- }
-
- sb.Append("|");
-
- for (int i = 0; i < _extensions.Length; ++i)
- {
- sb.Append("*");
- sb.Append(_extensions[i]);
-
- if (i != _extensions.Length - 1)
- {
- sb.Append(";");
- }
- }
-
- return sb.ToString();
- }
- }
- }