From bc06684111d0b80294ea885ae477eb4f96323f3a Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 24 Jun 2014 16:28:17 +0100 Subject: [PATCH] WebP support :) Former-commit-id: a5eadb739308ab7e2b63ab936f0c069430f91154 --- .../NET45/Caching/CachedImage.cs | 2 - .../NET45/Caching/DiskCache.cs | 52 +-- .../HttpModules/ImageProcessingModule.cs | 19 +- src/ImageProcessor/ImageFactory.cs | 22 +- src/ImageProcessor/ImageProcessor.csproj | 8 +- .../Imaging/Formats/WebPFormat.cs | 316 ++++++++++++++++++ src/ImageProcessor/Settings.StyleCop | 1 + src/ImageProcessor/libwebp.dll.REMOVED.git-id | 1 + src/ImageProcessorConsole/Program.cs | 6 +- .../images/input/4.sm.webp | 3 + .../images/input/test.webp | 3 + .../images/output/4.sm.webp | 3 + .../images/output/test.webp | 3 + 13 files changed, 392 insertions(+), 47 deletions(-) create mode 100644 src/ImageProcessor/Imaging/Formats/WebPFormat.cs create mode 100644 src/ImageProcessor/libwebp.dll.REMOVED.git-id create mode 100644 src/ImageProcessorConsole/images/input/4.sm.webp create mode 100644 src/ImageProcessorConsole/images/input/test.webp create mode 100644 src/ImageProcessorConsole/images/output/4.sm.webp create mode 100644 src/ImageProcessorConsole/images/output/test.webp diff --git a/src/ImageProcessor.Web/NET45/Caching/CachedImage.cs b/src/ImageProcessor.Web/NET45/Caching/CachedImage.cs index f775ac1c8..511c2b318 100644 --- a/src/ImageProcessor.Web/NET45/Caching/CachedImage.cs +++ b/src/ImageProcessor.Web/NET45/Caching/CachedImage.cs @@ -10,9 +10,7 @@ namespace ImageProcessor.Web.Caching { - #region Using using System; - #endregion /// /// Describes a cached image diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs index 1d8e954aa..f37bf15fb 100644 --- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs @@ -13,7 +13,6 @@ namespace ImageProcessor.Web.Caching #region Using using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -96,24 +95,33 @@ namespace ImageProcessor.Web.Caching this.requestPath = requestPath; this.fullPath = fullPath; this.imageName = imageName; - this.CachedPath = this.GetCachePath(); } #endregion - #region Properties + #region Methods + #region Internal /// - /// Gets the cached path. + /// Gets the full transformed cached path for the image. + /// The images are stored in paths that are based upon the SHA1 of their full request path + /// taking the individual characters of the hash to determine their location. + /// This allows us to store millions of images. /// - internal string CachedPath { get; private set; } - #endregion + /// The full cached path for the image. + internal Task GetCachePathAsync() + { + return TaskHelpers.Run(this.GetCachePath); + } - #region Methods - #region Internal /// /// Gets the virtual path to the cached processed image. /// - /// The virtual path to the cached processed image. - internal string GetVirtualCachedPath() + /// + /// The path to the cached image. + /// + /// + /// The virtual path to the cached processed image. + /// + internal string GetVirtualCachedPath(string cachedPath) { string applicationPath = this.request.PhysicalApplicationPath; string virtualDir = this.request.ApplicationPath; @@ -121,7 +129,7 @@ namespace ImageProcessor.Web.Caching if (applicationPath != null) { - return this.CachedPath.Replace(applicationPath, virtualDir).Replace(@"\", "/"); + return cachedPath.Replace(applicationPath, virtualDir).Replace(@"\", "/"); } throw new InvalidOperationException( @@ -131,13 +139,16 @@ namespace ImageProcessor.Web.Caching /// /// Adds an image to the cache. /// - internal void AddImageToCache() + /// + /// The path to the cached image. + /// + internal void AddImageToCache(string cachedPath) { - string key = Path.GetFileNameWithoutExtension(this.CachedPath); + string key = Path.GetFileNameWithoutExtension(cachedPath); CachedImage cachedImage = new CachedImage { Key = key, - Path = this.CachedPath, + Path = cachedPath, CreationTimeUtc = DateTime.UtcNow }; @@ -147,14 +158,16 @@ namespace ImageProcessor.Web.Caching /// /// Returns a value indicating whether the original file is new or has been updated. /// + /// + /// The path to the cached image. + /// /// /// True if the the original file is new or has been updated; otherwise, false. /// - internal async Task IsNewOrUpdatedFileAsync() + internal async Task IsNewOrUpdatedFileAsync(string cachedPath) { - string path = this.CachedPath; bool isUpdated = false; - CachedImage cachedImage = await CacheIndexer.GetValueAsync(path); + CachedImage cachedImage = await CacheIndexer.GetValueAsync(cachedPath); if (cachedImage == null) { @@ -166,7 +179,7 @@ namespace ImageProcessor.Web.Caching // Check to see if the cached image is set to expire. if (this.IsExpired(cachedImage.CreationTimeUtc)) { - CacheIndexer.Remove(path); + CacheIndexer.Remove(cachedPath); isUpdated = true; } } @@ -236,12 +249,11 @@ namespace ImageProcessor.Web.Caching /// /// Gets the full transformed cached path for the image. - /// The images are stored in paths that are based upon the sha1 of their full request path + /// The images are stored in paths that are based upon the SHA1 of their full request path /// taking the individual characters of the hash to determine their location. /// This allows us to store millions of images. /// /// The full cached path for the image. - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] private string GetCachePath() { string cachedPath = string.Empty; diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 56e70d797..91ec40aab 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -161,7 +161,6 @@ namespace ImageProcessor.Web.HttpModules /// private static SemaphoreSlim GetSemaphoreSlim(string id) { - id = id.ToMD5Fingerprint(); SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1)); return semaphore; } @@ -348,13 +347,14 @@ namespace ImageProcessor.Web.HttpModules // Create a new cache to help process and cache the request. DiskCache cache = new DiskCache(request, requestPath, fullPath, imageName); + string cachedPath = await cache.GetCachePathAsync(); // Since we are now rewriting the path we need to check again that the current user has access // to the rewritten path. // Get the user for the current request // If the user is anonymous or authentication doesn't work for this suffix avoid a NullReferenceException // in the UrlAuthorizationModule by creating a generic identity. - string virtualCachedPath = cache.GetVirtualCachedPath(); + string virtualCachedPath = cache.GetVirtualCachedPath(cachedPath); IPrincipal user = context.User ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]); @@ -362,6 +362,7 @@ namespace ImageProcessor.Web.HttpModules PermissionSet permission = new PermissionSet(PermissionState.None); permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted)); bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet); + bool isAllowed = true; // Run the rewritten path past the auth system again, using the result as the default "AllowAccess" value @@ -373,13 +374,11 @@ namespace ImageProcessor.Web.HttpModules if (isAllowed) { // Is the file new or updated? - bool isNewOrUpdated = await cache.IsNewOrUpdatedFileAsync(); + bool isNewOrUpdated = await cache.IsNewOrUpdatedFileAsync(cachedPath); // Only process if the file has been updated. if (isNewOrUpdated) { - string cachedPath = cache.CachedPath; - // Process the image. using (ImageFactory imageFactory = new ImageFactory(preserveExifMetaData != null && preserveExifMetaData.Value)) { @@ -419,7 +418,7 @@ namespace ImageProcessor.Web.HttpModules context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType; // Add to the cache. - cache.AddImageToCache(); + cache.AddImageToCache(cachedPath); // Trim the cache. await cache.TrimCachedFolderAsync(cachedPath); @@ -458,7 +457,7 @@ namespace ImageProcessor.Web.HttpModules context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType; // Add to the cache. - cache.AddImageToCache(); + cache.AddImageToCache(cachedPath); // Trim the cache. await cache.TrimCachedFolderAsync(cachedPath); @@ -480,7 +479,7 @@ namespace ImageProcessor.Web.HttpModules context.Response.AddHeader("Content-Length", "0"); context.Response.StatusCode = (int)HttpStatusCode.NotModified; context.Response.SuppressContent = true; - context.Response.AddFileDependency(context.Server.MapPath(cache.GetVirtualCachedPath())); + context.Response.AddFileDependency(context.Server.MapPath(virtualCachedPath)); this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey]); if (!isRemote) @@ -489,10 +488,8 @@ namespace ImageProcessor.Web.HttpModules } } - string virtualPath = cache.GetVirtualCachedPath(); - // The cached file is valid so just rewrite the path. - context.RewritePath(virtualPath, false); + context.RewritePath(virtualCachedPath, false); } else { diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 4d623c246..97e749cbc 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -120,24 +120,24 @@ namespace ImageProcessor internal Image Image { get; set; } /// - /// Gets or sets the memory stream for storing any input stream to prevent disposal. + /// Gets or sets the stream for storing any input stream to prevent disposal. /// - internal MemoryStream InputStream { get; set; } + internal Stream InputStream { get; set; } #endregion #region Methods /// /// Loads the image to process. Always call this method first. /// - /// - /// The containing the image information. + /// + /// The containing the image information. /// /// /// The current instance of the class. /// - public ImageFactory Load(MemoryStream memoryStream) + public ImageFactory Load(Stream stream) { - ISupportedImageFormat format = FormatUtilities.GetFormat(memoryStream); + ISupportedImageFormat format = FormatUtilities.GetFormat(stream); if (format == null) { @@ -145,10 +145,10 @@ namespace ImageProcessor } // Set our image as the memory stream value. - this.Image = format.Load(memoryStream); + this.Image = format.Load(stream); // Store the stream so we can dispose of it later. - this.InputStream = memoryStream; + this.InputStream = stream; // Set the other properties. format.Quality = DefaultQuality; @@ -831,17 +831,17 @@ namespace ImageProcessor /// /// Saves the current image to the specified output stream. /// - /// + /// /// The to save the image information to. /// /// /// The current instance of the class. /// - public ImageFactory Save(MemoryStream memoryStream) + public ImageFactory Save(Stream stream) { if (this.ShouldProcess) { - this.Image = this.CurrentImageFormat.Save(memoryStream, this.Image); + this.Image = this.CurrentImageFormat.Save(stream, this.Image); } return this; diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index cd884e30c..7e0c0b4f4 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -52,7 +52,6 @@ - @@ -84,6 +83,7 @@ + @@ -128,7 +128,11 @@ - + + + PreserveNewest + +