diff --git a/src/ImageProcessor.Web/Caching/CachedImage.cs b/src/ImageProcessor.Web/Caching/CachedImage.cs
new file mode 100644
index 000000000..500331d55
--- /dev/null
+++ b/src/ImageProcessor.Web/Caching/CachedImage.cs
@@ -0,0 +1,66 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Dual licensed under the MIT or GPL Version 2 licenses.
+//
+// -----------------------------------------------------------------------
+
+namespace ImageProcessor.Web.Caching
+{
+ #region Using
+ using System;
+ #endregion
+
+ ///
+ /// Describes a cached image
+ ///
+ public class CachedImage
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The value of the cached item.
+ ///
+ ///
+ /// The last write time of the cached item.
+ ///
+ public CachedImage(string value, DateTime lastWriteTimeUtc)
+ {
+ this.Value = value;
+ this.LastWriteTimeUtc = lastWriteTimeUtc;
+ }
+
+ ///
+ /// Gets the value date time delimiter.
+ ///
+ public static string ValueLastWriteTimeDelimiter
+ {
+ get
+ {
+ return "|*|";
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the cached item
+ ///
+ public string Value { get; set; }
+
+ ///
+ /// Gets or sets the last write time of the cached item
+ ///
+ public DateTime LastWriteTimeUtc { get; set; }
+
+ ///
+ /// The value and last write time as a string.
+ ///
+ ///
+ /// The .
+ ///
+ public string ValueAndLastWriteTimeUtcToString()
+ {
+ return string.Format("{0}{1}{2}", this.Value, ValueLastWriteTimeDelimiter, this.LastWriteTimeUtc);
+ }
+ }
+}
diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs
index e4abdee97..b5f828825 100644
--- a/src/ImageProcessor.Web/Caching/DiskCache.cs
+++ b/src/ImageProcessor.Web/Caching/DiskCache.cs
@@ -131,7 +131,7 @@ namespace ImageProcessor.Web.Caching
}
///
- /// Purges any files from the filesystem cache in a background thread.
+ /// Purges any files from the file-system cache in a background thread.
///
internal static void PurgeCachedFolders()
{
@@ -184,7 +184,7 @@ namespace ImageProcessor.Web.Caching
}
///
- /// Purges any files from the filesystem cache in the given folders.
+ /// Purges any files from the file-system cache in the given folders.
///
private static void PurgeFolders()
{
@@ -231,7 +231,6 @@ namespace ImageProcessor.Web.Caching
}
}
}
-
});
});
}
diff --git a/src/ImageProcessor.Web/Caching/LockedDictionary.cs b/src/ImageProcessor.Web/Caching/LockedDictionary.cs
new file mode 100644
index 000000000..de8e214a3
--- /dev/null
+++ b/src/ImageProcessor.Web/Caching/LockedDictionary.cs
@@ -0,0 +1,321 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Dual licensed under the MIT or GPL Version 2 licenses.
+//
+// -----------------------------------------------------------------------
+
+namespace ImageProcessor.Web.Caching
+{
+ #region Using
+ using System.Collections.Generic;
+ using System.Linq;
+ #endregion
+
+ ///
+ /// Represents a collection of keys and values that are thread safe.
+ ///
+ ///
+ /// The type of the keys in the dictionary.
+ ///
+ ///
+ /// The type of the values in the dictionary.
+ ///
+ public class LockedDictionary : IDictionary
+ {
+ ///
+ /// The _inner.
+ ///
+ private readonly Dictionary innerDictionary = new Dictionary();
+
+ #region Constructors
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The value to initialize the LockedDictionary with.
+ ///
+ public LockedDictionary(IDictionary val = null)
+ {
+ if (val != null)
+ {
+ this.innerDictionary = val.ToDictionary(x => x.Key, x => x.Value);
+ }
+ }
+ #endregion
+
+ #region Properties
+ ///
+ /// Gets a collection containing the keys in the .
+ ///
+ public ICollection Keys
+ {
+ get
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.Keys.ToArray();
+ }
+ }
+ }
+
+ ///
+ /// Gets a collection containing the values in the .
+ ///
+ public ICollection Values
+ {
+ get
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.Values.ToArray();
+ }
+ }
+ }
+
+ ///
+ /// Gets the number of key/value pairs contained in the .
+ ///
+ public int Count
+ {
+ get
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.Count;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the is read only.
+ ///
+ public bool IsReadOnly
+ {
+ get { return false; }
+ }
+
+ ///
+ /// Gets or sets the value associated with the specified key.
+ ///
+ ///
+ /// The key of the value to get or set.
+ ///
+ ///
+ /// TThe value associated with the specified key. If the specified key is not found,
+ /// a get operation throws a ,
+ /// and a set operation creates a new element with the specified key.
+ ///
+ public TVal this[TKey key]
+ {
+ get
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary[key];
+ }
+ }
+
+ set
+ {
+ lock (this.innerDictionary)
+ {
+ this.innerDictionary[key] = value;
+ }
+ }
+ }
+ #endregion
+
+ #region Methods
+ ///
+ /// Adds the specified key and value to the dictionary.
+ ///
+ ///
+ /// The key of the element to add.
+ ///
+ ///
+ /// The value of the element to add. The value can be null for reference types.
+ ///
+ public void Add(TKey key, TVal value)
+ {
+ lock (this.innerDictionary)
+ {
+ this.innerDictionary.Add(key, value);
+ }
+ }
+
+ ///
+ /// Determines whether the LockedDictionary contains the specified key.
+ ///
+ ///
+ /// The key to locate in the LockedDictionary.
+ ///
+ ///
+ /// true if the LockedDictionary contains the key; otherwise, false.
+ ///
+ public bool ContainsKey(TKey key)
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.ContainsKey(key);
+ }
+ }
+
+ ///
+ /// Removes the value with the specified key from the .
+ ///
+ ///
+ /// The key of the element to remove.
+ ///
+ ///
+ /// true if the element is successfully found and removed; otherwise, false.
+ /// This method returns false if key is not found in the .
+ ///
+ public bool Remove(TKey key)
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.Remove(key);
+ }
+ }
+
+ ///
+ /// Gets the value associated with the specified key.
+ ///
+ ///
+ /// The key of the value to get.
+ ///
+ ///
+ /// When this method returns, contains the value associated with the specified key, if the key is found;
+ /// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.
+ ///
+ ///
+ /// true if the contains an element with
+ /// the specified key; otherwise, false.
+ ///
+ public bool TryGetValue(TKey key, out TVal value)
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.TryGetValue(key, out value);
+ }
+ }
+
+ ///
+ /// Adds the specified key and value to the dictionary.
+ ///
+ ///
+ /// The representing
+ /// the item to add.
+ ///
+ public void Add(KeyValuePair item)
+ {
+ lock (this.innerDictionary)
+ {
+ this.innerDictionary.Add(item.Key, item.Value);
+ }
+ }
+
+ ///
+ /// Removes all keys and values from the .
+ ///
+ public void Clear()
+ {
+ lock (this.innerDictionary)
+ {
+ this.innerDictionary.Clear();
+ }
+ }
+
+ ///
+ /// Determines whether the contains the specified key.
+ ///
+ ///
+ /// The representing
+ /// the item to locate.
+ ///
+ ///
+ /// true if the contains an element
+ /// with the specified key; otherwise, false.
+ ///
+ public bool Contains(KeyValuePair item)
+ {
+ lock (this.innerDictionary)
+ {
+ var inner = this.innerDictionary as IDictionary;
+ return inner.Contains(item);
+ }
+ }
+
+ ///
+ /// Copies the elements of an to a one-dimensional
+ /// starting at a particular index.
+ ///
+ ///
+ /// The one-dimensional that is the destination of the elements copied
+ /// from .KeyCollection.
+ /// The must have zero-based indexing.
+ ///
+ ///
+ /// The zero-based index in array at which copying begins.
+ ///
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ lock (this.innerDictionary)
+ {
+ var inner = this.innerDictionary as IDictionary;
+ inner.CopyTo(array, arrayIndex);
+ }
+ }
+
+ ///
+ /// Removes the item with the specified
+ /// from the
+ ///
+ ///
+ /// The representing the item to remove.
+ ///
+ ///
+ /// This method returns false if item is not found in the .
+ ///
+ public bool Remove(KeyValuePair item)
+ {
+ lock (this.innerDictionary)
+ {
+ var inner = this.innerDictionary as IDictionary;
+ return inner.Remove(item);
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the .KeyCollection.
+ ///
+ ///
+ /// A
+ /// for the .
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.ToList().GetEnumerator();
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the .KeyCollection.
+ ///
+ ///
+ /// A
+ /// for the .
+ ///
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ lock (this.innerDictionary)
+ {
+ return this.innerDictionary.ToArray().GetEnumerator();
+ }
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor.Web/Caching/PersistantDictionary.cs b/src/ImageProcessor.Web/Caching/PersistantDictionary.cs
new file mode 100644
index 000000000..4f0f1f134
--- /dev/null
+++ b/src/ImageProcessor.Web/Caching/PersistantDictionary.cs
@@ -0,0 +1,193 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Dual licensed under the MIT or GPL Version 2 licenses.
+//
+// -----------------------------------------------------------------------
+
+namespace ImageProcessor.Web.Caching
+{
+ #region Using
+ using System;
+ using System.IO;
+ using System.Web.Hosting;
+ using ImageProcessor.Web.Config;
+ #endregion
+
+ ///
+ /// Represents a collection of keys and values whose operations are concurrent.
+ ///
+ public class PersistantDictionary : LockedDictionary
+ {
+ ///
+ /// A new instance Initializes a new instance of the class.
+ /// initialized lazily.
+ ///
+ private static readonly Lazy Lazy =
+ new Lazy(() => new PersistantDictionary());
+
+ ///
+ /// The default path for cached folders on the server.
+ ///
+ private static readonly string CachePath = ImageProcessorConfig.Instance.VirtualCachePath;
+
+ ///
+ /// The object to lock against.
+ ///
+ private static readonly object SyncRoot = new object();
+
+ ///
+ /// The cached index location.
+ ///
+ private readonly string cachedIndexFile = Path.Combine(HostingEnvironment.MapPath(CachePath), "imagecache.bin");
+
+ #region Constructors
+ ///
+ /// Prevents a default instance of the class
+ /// from being created.
+ ///
+ private PersistantDictionary()
+ {
+ this.LoadCache();
+ }
+ #endregion
+
+ ///
+ /// Gets the current instance of the class.
+ ///
+ public static PersistantDictionary Instance
+ {
+ get
+ {
+ return Lazy.Value;
+ }
+ }
+
+ #region Public
+ ///
+ /// Tries to remove the value associated with the specified key.
+ ///
+ ///
+ /// The key of the item to remove.
+ ///
+ ///
+ /// The value to assign the returned value to.
+ ///
+ ///
+ /// true if the removes an element with
+ /// the specified key; otherwise, false.
+ ///
+ public bool TryRemove(string key, out CachedImage value)
+ {
+ // No CachedImage to remove.
+ if (!this.ContainsKey(key))
+ {
+ value = default(CachedImage);
+ return false;
+ }
+
+ // Remove the CachedImage.
+ lock (SyncRoot)
+ {
+ value = this[key];
+ this.Remove(key);
+
+ this.SaveCache();
+
+ return true;
+ }
+ }
+
+ ///
+ /// Adds the specified key and value to the dictionary or returns the value if it exists.
+ ///
+ ///
+ /// The key.
+ ///
+ ///
+ /// The delegate method that returns the value.
+ ///
+ ///
+ /// The value of the item to add or get.
+ ///
+ public CachedImage GetOrAdd(string key, Func factory)
+ {
+ // Get the CachedImage.
+ if (this.ContainsKey(key))
+ {
+ return this[key];
+ }
+
+ lock (SyncRoot)
+ {
+ // Add the CachedImage.
+ CachedImage ret = factory(key);
+ this[key] = ret;
+
+ this.SaveCache();
+
+ return ret;
+ }
+ }
+ #endregion
+
+ ///
+ /// Saves the in memory cache to the file-system.
+ ///
+ private void SaveCache()
+ {
+ using (FileStream fileStream = File.Create(this.cachedIndexFile))
+ {
+ using (BinaryWriter binaryWriter = new BinaryWriter(fileStream))
+ {
+ // Put the count.
+ binaryWriter.Write(this.Count);
+
+ // Put the values.
+ foreach (var pair in this)
+ {
+ binaryWriter.Write(pair.Key);
+ binaryWriter.Write(pair.Value.ValueAndLastWriteTimeUtcToString());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Loads the cache file to populate the in memory cache.
+ ///
+ private void LoadCache()
+ {
+ lock (SyncRoot)
+ {
+ if (File.Exists(this.cachedIndexFile))
+ {
+ using (FileStream fileStream = File.OpenRead(this.cachedIndexFile))
+ {
+ using (BinaryReader binaryReader = new BinaryReader(fileStream))
+ {
+ // Get the count.
+ int count = binaryReader.ReadInt32();
+
+ // Read in all pairs.
+ for (int i = 0; i < count; i++)
+ {
+ // Read the key/value strings
+ string key = binaryReader.ReadString();
+ string value = binaryReader.ReadString();
+
+ // Create a CachedImage
+ string[] valueAndLastWriteTime = value.Split(new[] { CachedImage.ValueLastWriteTimeDelimiter }, StringSplitOptions.None);
+ DateTime lastWriteTime = DateTime.Parse(valueAndLastWriteTime[1]);
+ CachedImage cachedImage = new CachedImage(valueAndLastWriteTime[0], lastWriteTime);
+
+ // Assign the value
+ this[key] = cachedImage;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs
index 1c35ff9bc..7332b2d1b 100644
--- a/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs
+++ b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs
@@ -10,14 +10,16 @@ namespace ImageProcessor.Web.Config
#region Using
using System;
using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ImageProcessor.Processors;
#endregion
///
- /// Encapsulates methods to allow the retrieval of imageprocessor settings.
+ /// Encapsulates methods to allow the retrieval of ImageProcessor settings.
/// http://csharpindepth.com/Articles/General/Singleton.aspx
///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
public class ImageProcessorConfig
{
#region Fields
@@ -107,6 +109,7 @@ namespace ImageProcessor.Web.Config
///
/// Gets a list of whitelisted urls that images can be downloaded from.
///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
public Uri[] RemoteFileWhiteList
{
get
diff --git a/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs
index 3aa938b8f..a99eb520d 100644
--- a/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs
+++ b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs
@@ -49,7 +49,7 @@ namespace ImageProcessor.Web.Helpers
///
/// Returns a hash code for the specified .
///
- /// The FileInfo to return the hashcode for.
+ /// The FileInfo to return the hash code for.
/// A hash code for the specified .
public int GetHashCode(System.IO.FileInfo fi)
{
diff --git a/src/ImageProcessor.Web/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/ImageFactoryExtensions.cs
index 2feff79fd..1b05bb669 100644
--- a/src/ImageProcessor.Web/ImageFactoryExtensions.cs
+++ b/src/ImageProcessor.Web/ImageFactoryExtensions.cs
@@ -15,14 +15,17 @@ namespace ImageProcessor.Web
#endregion
///
- /// Extends the ImageFactory class to provide a fluent api.
+ /// Extends the ImageFactory class to provide a fluent API.
///
public static class ImageFactoryExtensions
{
+ ///
+ /// The object to lock against.
+ ///
private static readonly object SyncRoot = new object();
///
- /// Auto processes image files based on any querystring parameters added to the image path.
+ /// Auto processes image files based on any query string parameters added to the image path.
///
///
/// The current instance of the class
@@ -31,32 +34,29 @@ namespace ImageProcessor.Web
///
/// The current instance of the class.
///
-public static ImageFactory AutoProcess(this ImageFactory factory)
-{
- if (factory.ShouldProcess)
- {
- // TODO: This is going to be a bottleneck for speed. Find a faster way.
- lock (SyncRoot)
+ public static ImageFactory AutoProcess(this ImageFactory factory)
{
- // Get a list of all graphics processors that have parsed and matched the querystring.
- List list =
- ImageProcessorConfig.Instance.GraphicsProcessors
- .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue)
- .OrderBy(y => y.SortOrder)
- .ToList();
-
- // Loop through and process the image.
- foreach (IGraphicsProcessor graphicsProcessor in list)
+ if (factory.ShouldProcess)
{
- factory.Image = graphicsProcessor.ProcessImage(factory);
+ // TODO: This is going to be a bottleneck for speed. Find a faster way.
+ lock (SyncRoot)
+ {
+ // Get a list of all graphics processors that have parsed and matched the querystring.
+ List list =
+ ImageProcessorConfig.Instance.GraphicsProcessors
+ .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue)
+ .OrderBy(y => y.SortOrder)
+ .ToList();
+
+ // Loop through and process the image.
+ foreach (IGraphicsProcessor graphicsProcessor in list)
+ {
+ factory.Image = graphicsProcessor.ProcessImage(factory);
+ }
+ }
}
- }
- }
-
- return factory;
-
-}
-
+ return factory;
+ }
}
}
diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj
index 6f5589cf3..0ef489007 100644
--- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj
+++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj
@@ -51,7 +51,10 @@
+
+
+
diff --git a/src/Test/Test/Content/style.css b/src/Test/Test/Content/style.css
index 6848b86e7..b94de3ac8 100644
--- a/src/Test/Test/Content/style.css
+++ b/src/Test/Test/Content/style.css
@@ -19,25 +19,14 @@ section section {
}
section section:nth-child(2n) {
- color: #fff;
- }
-
-
- section section:nth-child(2) {
background-color: #00a9ec;
+ color: #fff;
}
- section section:nth-child(4) {
+ section section:nth-child(4n) {
background-color: #ED1C24;
}
-img {
- /*background: #fff;
- padding: 3px;
- -webkit-box-shadow: 0 0 5px rgb(0, 0, 0, 0.4);
- box-shadow: 0 0 5px rgb(0, 0, 0, 0.4);*/
-}
-
.no-bullets {
padding-left: 0;
}
diff --git a/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id b/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id
new file mode 100644
index 000000000..74f69293c
--- /dev/null
+++ b/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id
@@ -0,0 +1 @@
+8150b46ab27c62ba51aaba551eef3f1a30f08de9
\ No newline at end of file
diff --git a/src/Test/Test/Images/Penguins.gif.REMOVED.git-id b/src/Test/Test/Images/Penguins.gif.REMOVED.git-id
new file mode 100644
index 000000000..ce873d473
--- /dev/null
+++ b/src/Test/Test/Images/Penguins.gif.REMOVED.git-id
@@ -0,0 +1 @@
+6ad3b846d4697584ff601ac481b14a4d3bbb5736
\ No newline at end of file
diff --git a/src/Test/Test/Images/Penguins.png.REMOVED.git-id b/src/Test/Test/Images/Penguins.png.REMOVED.git-id
new file mode 100644
index 000000000..78062a0e7
--- /dev/null
+++ b/src/Test/Test/Images/Penguins.png.REMOVED.git-id
@@ -0,0 +1 @@
+a2c796fbb7de948230a22982ab74892891dd5198
\ No newline at end of file
diff --git a/src/Test/Test/Test.csproj b/src/Test/Test/Test.csproj
index b59d56d3d..7198d738b 100644
--- a/src/Test/Test/Test.csproj
+++ b/src/Test/Test/Test.csproj
@@ -89,8 +89,12 @@
+
+
+
+
diff --git a/src/Test/Test/Views/Home/Index.cshtml b/src/Test/Test/Views/Home/Index.cshtml
index 981fb6aaf..05b7a8885 100644
--- a/src/Test/Test/Views/Home/Index.cshtml
+++ b/src/Test/Test/Views/Home/Index.cshtml
@@ -1,94 +1,367 @@
@{
ViewBag.Title = "Home Page";
}
-
-
-
-
Resized
-

-
-
-
Cropped
-

-
-
-
-
-
-
-
-
Watermark
-

-
-
-
Format
-

-
-
-
-
-
-
-
Rotate
-

-
-
-
Quality
-

-
-
-
-
-
-
-
Alpha
-

-
-
-
Remote
-

-
-
-
+
+ Jpg
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
Remote
+

+
+
+
+
+
+ Gif
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
+
+
+ Png
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
+
+
+ Bmp
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
+