From 01bd3e9ffec6df4d0620168bc3f8423b7641aadb Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 5 Aug 2014 00:28:03 +0100 Subject: [PATCH] WebP is now a separate plugin Former-commit-id: dc2bf6c33ec42faa0b2e935a7bca10d9adf85b10 --- .../HttpModules/ImageProcessingModule.cs | 41 +++++-- src/ImageProcessor.sln | 17 +++ .../ImageProcessorBootstrapper.cs | 85 ++++++++++++++- src/ImageProcessor/ImageProcessor.csproj | 6 -- .../ImageProcessorConsole.csproj | 4 + .../images/output/4.sm.webp | 3 + .../images/output/test.webp | 3 + .../ImageProcessor.Plugins.WebP.csproj | 67 ++++++++++++ .../Imaging/Formats/NativeMethods.cs | 6 +- .../Imaging/Formats/WebPFormat.cs | 3 +- .../Properties/AssemblyInfo.cs | 36 +++++++ .../Unmanaged/x64/libwebp.dll.REMOVED.git-id | 0 .../Unmanaged/x86/libwebp.dll.REMOVED.git-id | 0 .../Controllers/HomeController.cs | 5 + .../Test_Website_MVC_NET45.csproj | 7 ++ .../Test_Website_NET45/Views/Home/WebP.cshtml | 100 ++++++++++++++++++ .../Views/Shared/_Layout.cshtml | 1 + 17 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 src/ImageProcessorConsole/images/output/4.sm.webp create mode 100644 src/ImageProcessorConsole/images/output/test.webp create mode 100644 src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/ImageProcessor.Plugins.WebP.csproj rename src/{ImageProcessor => Plugins/ImageProcessor/ImageProcessor.Plugins.WebP}/Imaging/Formats/NativeMethods.cs (96%) rename src/{ImageProcessor => Plugins/ImageProcessor/ImageProcessor.Plugins.WebP}/Imaging/Formats/WebPFormat.cs (98%) create mode 100644 src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs rename src/{ImageProcessor => Plugins/ImageProcessor/ImageProcessor.Plugins.WebP}/Resources/Unmanaged/x64/libwebp.dll.REMOVED.git-id (100%) rename src/{ImageProcessor => Plugins/ImageProcessor/ImageProcessor.Plugins.WebP}/Resources/Unmanaged/x86/libwebp.dll.REMOVED.git-id (100%) create mode 100644 src/TestWebsites/NET45/Test_Website_NET45/Views/Home/WebP.cshtml diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 2fe0a275d..d7b15e7e1 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -43,6 +43,11 @@ namespace ImageProcessor.Web.HttpModules /// private const string CachedResponseTypeKey = "CACHED_IMAGE_RESPONSE_TYPE_054F217C-11CF-49FF-8D2F-698E8E6EB58F"; + /// + /// The key for storing the file dependency of the current image. + /// + private const string CachedResponseFileDependency = "CACHED_IMAGE_DEPENDENCY_054F217C-11CF-49FF-8D2F-698E8E6EB58F"; + /// /// The regular expression to search strings for. /// @@ -215,15 +220,18 @@ namespace ImageProcessor.Web.HttpModules HttpContext context = ((HttpApplication)sender).Context; object responseTypeObject = context.Items[CachedResponseTypeKey]; + object dependencyFileObject = context.Items[CachedResponseFileDependency]; - if (responseTypeObject != null) + if (responseTypeObject != null && dependencyFileObject != null) { string responseType = (string)responseTypeObject; + string dependencyFile = (string)dependencyFileObject; // Set the headers - this.SetHeaders(context, responseType); + this.SetHeaders(context, responseType, dependencyFile); context.Items[CachedResponseTypeKey] = null; + context.Items[CachedResponseFileDependency] = null; } } @@ -440,7 +448,15 @@ namespace ImageProcessor.Web.HttpModules } } - string incomingEtag = context.Request.Headers["If-None-Match"]; + // Image is from the cache so the mime-type will need to be set. + if (context.Items[CachedResponseTypeKey] == null) + { + context.Items[CachedResponseTypeKey] = ImageHelpers.GetExtension(cachedPath).Replace(".", "image/"); + } + + context.Items[CachedResponseFileDependency] = cachedPath; + + string incomingEtag = context.Request.Headers["If" + "-None-Match"]; if (incomingEtag != null && !isNewOrUpdated) { @@ -449,8 +465,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(virtualCachedPath)); - this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey]); + this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey], cachedPath); if (!isRemote) { @@ -482,18 +497,28 @@ namespace ImageProcessor.Web.HttpModules /// the HttpContext object that provides /// references to the intrinsic server objects /// - /// The HTTP MIME type to to send. - private void SetHeaders(HttpContext context, string responseType) + /// + /// The HTTP MIME type to to send. + /// + /// + /// The dependency path for the cache dependency. + /// + private void SetHeaders(HttpContext context, string responseType, string dependencyPath) { HttpResponse response = context.Response; response.ContentType = responseType; - response.AddHeader("Image-Served-By", "ImageProcessor.Web/" + AssemblyVersion); + if (response.Headers["Image-Served-By"] == null) + { + response.AddHeader("Image-Served-By", "ImageProcessor.Web/" + AssemblyVersion); + } HttpCachePolicy cache = response.Cache; cache.SetCacheability(HttpCacheability.Public); cache.VaryByHeaders["Accept-Encoding"] = true; + + context.Response.AddFileDependency(dependencyPath); cache.SetLastModifiedFromFileDependencies(); int maxDays = DiskCache.MaxFileCachedDuration; diff --git a/src/ImageProcessor.sln b/src/ImageProcessor.sln index a12d17c68..8ad0ed4e9 100644 --- a/src/ImageProcessor.sln +++ b/src/ImageProcessor.sln @@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web.UnitTest EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.UnitTests", "ImageProcessor.UnitTests\ImageProcessor.UnitTests.csproj", "{633B1C4C-4823-47BE-9A01-A665F3118C8C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Plugins.WebP", "Plugins\ImageProcessor\ImageProcessor.Plugins.WebP\ImageProcessor.Plugins.WebP.csproj", "{2CF69699-959A-44DC-A281-4E2596C25043}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution All|Any CPU = All|Any CPU @@ -201,6 +203,21 @@ Global {633B1C4C-4823-47BE-9A01-A665F3118C8C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {633B1C4C-4823-47BE-9A01-A665F3118C8C}.Release|Mixed Platforms.Build.0 = Release|Any CPU {633B1C4C-4823-47BE-9A01-A665F3118C8C}.Release|x86.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.All|Any CPU.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.All|Any CPU.Build.0 = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.All|Mixed Platforms.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.All|Mixed Platforms.Build.0 = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.All|x86.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Debug|x86.ActiveCfg = Debug|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Release|Any CPU.Build.0 = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2CF69699-959A-44DC-A281-4E2596C25043}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs index 09b772455..59f975fb3 100644 --- a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs +++ b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs @@ -4,22 +4,25 @@ // Licensed under the Apache License, Version 2.0. // // -// The image processor bootstrapper. +// The ImageProcessor bootstrapper. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Configuration { using System; + using System.Collections; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Reflection; using ImageProcessor.Common.Exceptions; using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging.Formats; /// - /// The image processor bootstrapper. + /// The ImageProcessor bootstrapper. /// public class ImageProcessorBootstrapper { @@ -35,8 +38,8 @@ namespace ImageProcessor.Configuration /// private ImageProcessorBootstrapper() { - this.LoadSupportedImageFormats(); this.NativeBinaryFactory = new NativeBinaryFactory(); + this.LoadSupportedImageFormats(); } /// @@ -70,14 +73,42 @@ namespace ImageProcessor.Configuration try { Type type = typeof(ISupportedImageFormat); + HashSet found = new HashSet(); + + // Get any references and used assemblies. + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + this.LoadReferencedAssemblies(found, assembly); + } + + // Get any referenced but not used assemblies. + Assembly executingAssembly = Assembly.GetExecutingAssembly(); + string targetBasePath = Path.GetDirectoryName(new Uri(executingAssembly.Location).LocalPath); + + // ReSharper disable once AssignNullToNotNullAttribute + FileInfo[] files = new DirectoryInfo(targetBasePath).GetFiles("*.dll", SearchOption.AllDirectories); + + foreach (FileInfo fileInfo in files) + { + AssemblyName assemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName); + + if (!AppDomain.CurrentDomain.GetAssemblies() + .Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName()))) + { + // In a web app, this assembly will automatically be bound from the + // Asp.Net Temporary folder from where the site actually runs. + this.LoadReferencedAssemblies(found, Assembly.Load(assemblyName)); + } + } + List availableTypes = AppDomain.CurrentDomain .GetAssemblies() - .SelectMany(s => s.GetLoadableTypes()) + .SelectMany(a => a.GetLoadableTypes()) .Where(t => type.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract) .ToList(); this.SupportedImageFormats = availableTypes - .Select(x => (Activator.CreateInstance(x) as ISupportedImageFormat)).ToList(); + .Select(f => (Activator.CreateInstance(f) as ISupportedImageFormat)).ToList(); } catch (Exception ex) { @@ -85,5 +116,49 @@ namespace ImageProcessor.Configuration } } } + + /// + /// Loads any referenced assemblies into the current application domain. + /// + /// + /// The collection containing the name of already found assemblies. + /// + /// + /// The assembly to load from. + /// + private void LoadReferencedAssemblies(HashSet found, Assembly assembly) + { + // Used to avoid duplicates + ArrayList results = new ArrayList(); + + // Resulting info + Stack stack = new Stack(); + + // Stack of names + // Store root assembly (level 0) directly into results list + stack.Push(assembly.ToString()); + + // Do a preorder, non-recursive traversal + while (stack.Count > 0) + { + string info = (string)stack.Pop(); + + // Get next assembly + if (!found.Contains(info)) + { + found.Add(info); + results.Add(info); + + // Store it to results ArrayList + Assembly child = Assembly.Load(info); + AssemblyName[] subchild = child.GetReferencedAssemblies(); + + for (int i = subchild.Length - 1; i >= 0; --i) + { + stack.Push(subchild[i].ToString()); + } + } + } + } } } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index ad69d127b..ed1aa9c64 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -79,7 +79,6 @@ - @@ -87,7 +86,6 @@ - @@ -131,10 +129,6 @@ - - - - diff --git a/src/ImageProcessorConsole/ImageProcessorConsole.csproj b/src/ImageProcessorConsole/ImageProcessorConsole.csproj index fba366b0c..1afd8ef0e 100644 --- a/src/ImageProcessorConsole/ImageProcessorConsole.csproj +++ b/src/ImageProcessorConsole/ImageProcessorConsole.csproj @@ -56,6 +56,10 @@ {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} ImageProcessor + + {2cf69699-959a-44dc-a281-4e2596c25043} + ImageProcessor.Plugins.WebP + + \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Formats/NativeMethods.cs b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/NativeMethods.cs similarity index 96% rename from src/ImageProcessor/Imaging/Formats/NativeMethods.cs rename to src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/NativeMethods.cs index 0c42710b0..936b58256 100644 --- a/src/ImageProcessor/Imaging/Formats/NativeMethods.cs +++ b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/NativeMethods.cs @@ -8,7 +8,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace ImageProcessor.Imaging.Formats +namespace ImageProcessor.Plugins.WebP.Imaging.Formats { using System; using System.IO; @@ -23,7 +23,7 @@ namespace ImageProcessor.Imaging.Formats internal static class NativeMethods { /// - /// Whether the process is running in 64bit mode. Used for calling the correct dllimport method. + /// Whether the process is running in 64bit mode. Used for calling the correct method. /// Clunky I know but I couldn't get dynamic methods to work. /// private static readonly bool Is64Bit = Environment.Is64BitProcess; @@ -34,7 +34,7 @@ namespace ImageProcessor.Imaging.Formats static NativeMethods() { string folder = Is64Bit ? "x64" : "x86"; - string name = string.Format("ImageProcessor.Resources.Unmanaged.{0}.libwebp.dll", folder); + string name = string.Format("ImageProcessor.Plugins.WebP.Resources.Unmanaged.{0}.libwebp.dll", folder); Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name); using (MemoryStream memoryStream = new MemoryStream()) diff --git a/src/ImageProcessor/Imaging/Formats/WebPFormat.cs b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/WebPFormat.cs similarity index 98% rename from src/ImageProcessor/Imaging/Formats/WebPFormat.cs rename to src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/WebPFormat.cs index 9f350c5c0..0bc2e2b7b 100644 --- a/src/ImageProcessor/Imaging/Formats/WebPFormat.cs +++ b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/WebPFormat.cs @@ -10,7 +10,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace ImageProcessor.Imaging.Formats +namespace ImageProcessor.Plugins.WebP.Imaging.Formats { using System; using System.Collections.Generic; @@ -22,6 +22,7 @@ namespace ImageProcessor.Imaging.Formats using System.Text; using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging.Formats; /// /// Provides the necessary information to support webp images. diff --git a/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d9aa6f985 --- /dev/null +++ b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageProcessor.Plugins.WebP")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("James South")] +[assembly: AssemblyProduct("ImageProcessor.Plugins.WebP")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bf160db5-2ea7-4c85-9b0e-f1ddf2595e37")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ImageProcessor/Resources/Unmanaged/x64/libwebp.dll.REMOVED.git-id b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x64/libwebp.dll.REMOVED.git-id similarity index 100% rename from src/ImageProcessor/Resources/Unmanaged/x64/libwebp.dll.REMOVED.git-id rename to src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x64/libwebp.dll.REMOVED.git-id diff --git a/src/ImageProcessor/Resources/Unmanaged/x86/libwebp.dll.REMOVED.git-id b/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x86/libwebp.dll.REMOVED.git-id similarity index 100% rename from src/ImageProcessor/Resources/Unmanaged/x86/libwebp.dll.REMOVED.git-id rename to src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x86/libwebp.dll.REMOVED.git-id diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Controllers/HomeController.cs b/src/TestWebsites/NET45/Test_Website_NET45/Controllers/HomeController.cs index 02fc35575..1eae92d44 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Controllers/HomeController.cs +++ b/src/TestWebsites/NET45/Test_Website_NET45/Controllers/HomeController.cs @@ -41,6 +41,11 @@ namespace Test_Website_NET45.Controllers return View(); } + public ActionResult WebP() + { + return View(); + } + public ActionResult External() { return this.View(); diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_MVC_NET45.csproj b/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_MVC_NET45.csproj index 96f09d69b..c2118914f 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_MVC_NET45.csproj +++ b/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_MVC_NET45.csproj @@ -159,6 +159,10 @@ {3b5dd734-fb7a-487d-8ce6-55e7af9aea7e} ImageProcessor + + {2cf69699-959a-44dc-a281-4e2596c25043} + ImageProcessor.Plugins.WebP + @@ -191,6 +195,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/WebP.cshtml b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/WebP.cshtml new file mode 100644 index 000000000..ff03d1240 --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/WebP.cshtml @@ -0,0 +1,100 @@ +@{ + ViewBag.Title = "WebP"; +} + +
+

WebP

+
+
+
+

Resized

+ +
+
+

Cropped

+ +
+
+
+
+

Filter

+
+
+

blackwhite

+ +
+
+

comic

+ +
+
+
+
+

lomograph

+ +
+
+

greyscale

+ +
+
+
+
+

polaroid

+ +
+
+

sepia

+ +
+
+
+
+

gotham

+ +
+
+

hisatch

+ +
+
+
+
+

losatch

+ +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+
+
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Views/Shared/_Layout.cshtml b/src/TestWebsites/NET45/Test_Website_NET45/Views/Shared/_Layout.cshtml index 74af019a2..5f8255ea4 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Views/Shared/_Layout.cshtml +++ b/src/TestWebsites/NET45/Test_Website_NET45/Views/Shared/_Layout.cshtml @@ -29,6 +29,7 @@
  • @Html.ActionLink("Png8", "Png8")
  • @Html.ActionLink("Bmp", "Bmp")
  • @Html.ActionLink("Tiff", "Tiff")
  • +
  • @Html.ActionLink("WebP", "WebP")
  • @Html.ActionLink("External", "External")