From 79a0201003ee6cc7d4da15cc22585f4db275f76a Mon Sep 17 00:00:00 2001 From: James South Date: Sun, 27 Jul 2014 23:07:11 +0100 Subject: [PATCH 1/7] Adding Linux native dll loading Former-commit-id: bf4c36486c3e3db4883d766162c4cf5c3c32da74 --- .../Configuration/NativeBinaryFactory.cs | 11 +++++++- .../Configuration/NativeMethods.cs | 28 +++++++++++++++++++ .../images/output/rotate.jpg | 3 ++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/ImageProcessorConsole/images/output/rotate.jpg diff --git a/src/ImageProcessor/Configuration/NativeBinaryFactory.cs b/src/ImageProcessor/Configuration/NativeBinaryFactory.cs index 741da61e2..e210aa036 100644 --- a/src/ImageProcessor/Configuration/NativeBinaryFactory.cs +++ b/src/ImageProcessor/Configuration/NativeBinaryFactory.cs @@ -94,7 +94,7 @@ namespace ImageProcessor.Configuration Assembly assembly = Assembly.GetExecutingAssembly(); string targetBasePath = new Uri(assembly.Location).LocalPath; string targetPath = Path.GetFullPath(Path.Combine(targetBasePath, "..\\" + folder + "\\" + name)); - + // Copy the file across if necessary. FileInfo fileInfo = new FileInfo(targetPath); bool rewrite = true; @@ -122,8 +122,13 @@ namespace ImageProcessor.Configuration try { +#if !__MonoCS__ // Load the binary into memory. pointer = NativeMethods.LoadLibrary(targetPath); +#else + // Load the binary into memory. The second parameter forces it to load immediately. + pointer = NativeMethods.dlopen(targetPath, 2); +#endif } catch (Exception ex) { @@ -187,9 +192,13 @@ namespace ImageProcessor.Configuration { IntPtr pointer = nativeBinary.Value; +#if !__MonoCS__ // According to http://stackoverflow.com/a/2445558/427899 you need to call this twice. NativeMethods.FreeLibrary(pointer); NativeMethods.FreeLibrary(pointer); +#else + NativeMethods.dlclose(pointer); +#endif } } } diff --git a/src/ImageProcessor/Configuration/NativeMethods.cs b/src/ImageProcessor/Configuration/NativeMethods.cs index a20b5a86f..6c1ab9805 100644 --- a/src/ImageProcessor/Configuration/NativeMethods.cs +++ b/src/ImageProcessor/Configuration/NativeMethods.cs @@ -40,5 +40,33 @@ namespace ImageProcessor.Configuration /// If the function succeeds, the return value is nonzero; otherwise zero. [DllImport("kernel32", CharSet = CharSet.Auto)] public static extern bool FreeLibrary(IntPtr hModule); + + /// + /// Loads the specified module into the address space of the calling process. + /// The specified module may cause other modules to be loaded. + /// + /// + /// The name of the module. This can be either a library module or + /// an executable module. + /// + /// + /// The flag indicating whether to load the library immediately or lazily. + /// + /// + /// If the function succeeds, the return value is a handle to the module; otherwise null. + /// + [System.Runtime.InteropServices.DllImport("libdl")] + public static extern IntPtr dlopen(string libname, int flags); + + /// + /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. + /// When the reference count reaches zero, the module is unloaded from the address space of the calling + /// process and the handle is no longer valid. + /// + /// A handle to the loaded library module. + /// The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle. + /// If the function succeeds, the return value is nonzero; otherwise zero. + [System.Runtime.InteropServices.DllImport("libdl")] + public static extern int dlclose(IntPtr hModule); } } diff --git a/src/ImageProcessorConsole/images/output/rotate.jpg b/src/ImageProcessorConsole/images/output/rotate.jpg new file mode 100644 index 000000000..a9b8b74dc --- /dev/null +++ b/src/ImageProcessorConsole/images/output/rotate.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b7215a59e681f4100078aeb74b5d423dcea54b9d2ea924a2bb7c74b2db3ee3a +size 26227 From 11b045dec9f6f9e45d135f5f3699a197a336fc03 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Mon, 28 Jul 2014 17:40:38 +0100 Subject: [PATCH 2/7] Fix for #70 - Height and width ratio should round up to nearest pixel Former-commit-id: 9d7ccd05efaed9b5d4b15196d8062672b26a2d6e --- src/ImageProcessor.Web/NET45/Processors/Resize.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/Processors/Resize.cs b/src/ImageProcessor.Web/NET45/Processors/Resize.cs index 09ce9bc3c..3c1772911 100644 --- a/src/ImageProcessor.Web/NET45/Processors/Resize.cs +++ b/src/ImageProcessor.Web/NET45/Processors/Resize.cs @@ -205,13 +205,13 @@ namespace ImageProcessor.Web.Processors // Replace 0 width if (size.Width == 0 && size.Height > 0 && input.Contains(WidthRatio) && !input.Contains(HeightRatio)) { - size.Width = (int)(value.ToPositiveFloatArray()[0] * size.Height); + size.Width = (int)Math.Ceiling(value.ToPositiveFloatArray()[0] * size.Height); } // Replace 0 height if (size.Height == 0 && size.Width > 0 && input.Contains(HeightRatio) && !input.Contains(WidthRatio)) { - size.Height = (int)(value.ToPositiveFloatArray()[0] * size.Width); + size.Height = (int)Math.Ceiling(value.ToPositiveFloatArray()[0] * size.Width); } } From 9067062aafa6946b9f3e053061d65c1209590f0f Mon Sep 17 00:00:00 2001 From: James South Date: Mon, 4 Aug 2014 15:51:44 +0100 Subject: [PATCH 3/7] Adding AsyncDeDuper Former-commit-id: a5e1f827c161642d6de2b164dcd3224195975ac9 --- .../NET4/ImageProcessor.Web_NET4.csproj | 3 + .../NET45/Helpers/AsyncDeDuperLock.cs | 134 ++++++++++++++++++ .../HttpModules/ImageProcessingModule.cs | 40 +----- .../NET45/ImageProcessor.Web_NET45.csproj | 1 + 4 files changed, 144 insertions(+), 34 deletions(-) create mode 100644 src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs diff --git a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj index 82d3986a7..601953a5e 100644 --- a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj +++ b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj @@ -101,6 +101,9 @@ StringExtensions.cs + + AsyncDeDuperLock.cs + CommonParameterParserUtility.cs diff --git a/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs new file mode 100644 index 000000000..7d6fbf0cd --- /dev/null +++ b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs @@ -0,0 +1,134 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Throttles duplicate requests. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Throttles duplicate requests. + /// Based loosely on + /// + public sealed class AsyncDeDuperLock + { + /// + /// The semaphore slims. + /// + private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); + + /// + /// The lock. + /// + /// + /// The hash. + /// + /// + /// The . + /// + public IDisposable Lock(string key) + { + DisposableScope releaser = new DisposableScope( + key, + s => + { + SemaphoreSlim locker; + if (SemaphoreSlims.TryRemove(s, out locker)) + { + locker.Release(); + locker.Dispose(); + } + }); + + SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); + semaphore.Wait(); + return releaser; + } + +#if NET45 && !__MonoCS__ + /// + /// The lock async. + /// + /// + /// The key. + /// + /// + /// The . + /// + public Task LockAsync(string key) + { + DisposableScope releaser = new DisposableScope( + key, + s => + { + SemaphoreSlim locker; + if (SemaphoreSlims.TryRemove(s, out locker)) + { + locker.Release(); + locker.Dispose(); + } + }); + + Task releaserTask = Task.FromResult(releaser as IDisposable); + SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); + + Task waitTask = semaphore.WaitAsync(); + + return waitTask.IsCompleted + ? releaserTask + : waitTask.ContinueWith( + (_, r) => (IDisposable)r, + releaser, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } +#endif + /// + /// The disposable scope. + /// + internal sealed class DisposableScope : IDisposable + { + /// + /// The key + /// + private readonly string key; + + /// + /// The close scope action. + /// + private readonly Action closeScopeAction; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The key. + /// + /// + /// The close scope action. + /// + public DisposableScope(string key, Action closeScopeAction) + { + this.key = key; + this.closeScopeAction = closeScopeAction; + } + + /// + /// The dispose. + /// + public void Dispose() + { + this.closeScopeAction(this.key); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 0f4db86f3..2fe0a275d 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -12,7 +12,6 @@ namespace ImageProcessor.Web.HttpModules { #region Using using System; - using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -22,7 +21,6 @@ namespace ImageProcessor.Web.HttpModules using System.Security.Permissions; using System.Security.Principal; using System.Text.RegularExpressions; - using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; @@ -56,9 +54,9 @@ namespace ImageProcessor.Web.HttpModules private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); /// - /// The collection of SemaphoreSlims for identifying given locking individual queries. + /// The locker for preventing duplicate requests. /// - private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); + private static readonly AsyncDeDuperLock Locker = new AsyncDeDuperLock(); /// /// The value to prefix any remote image requests with to ensure they get captured. @@ -149,20 +147,6 @@ namespace ImageProcessor.Web.HttpModules GC.SuppressFinalize(this); } - /// - /// Gets the specific for the given id. - /// - /// - /// The id representing the . - /// - /// - /// The for the given id. - /// - private static SemaphoreSlim GetSemaphoreSlim(string id) - { - return SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1)); - } - /// /// Disposes the object and frees resources for the Garbage Collector. /// @@ -377,13 +361,11 @@ namespace ImageProcessor.Web.HttpModules { if (isRemote) { - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); #if NET45 && !__MonoCS__ - await semaphore.WaitAsync(); + using (await Locker.LockAsync(cachedPath)) #else - semaphore.Wait(); + using (Locker.Lock(cachedPath)) #endif - try { Uri uri = new Uri(requestPath + "?" + urlParameters); RemoteFile remoteFile = new RemoteFile(uri, false); @@ -422,20 +404,14 @@ namespace ImageProcessor.Web.HttpModules } } } - finally - { - semaphore.Release(); - } } else { - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); #if NET45 && !__MonoCS__ - await semaphore.WaitAsync(); + using (await Locker.LockAsync(cachedPath)) #else - semaphore.Wait(); + using (Locker.Lock(cachedPath)) #endif - try { // Check to see if the file exists. // ReSharper disable once AssignNullToNotNullAttribute @@ -460,10 +436,6 @@ namespace ImageProcessor.Web.HttpModules // Trim the cache. await cache.TrimCachedFolderAsync(cachedPath); } - finally - { - semaphore.Release(); - } } } } diff --git a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj index 40bd16bc7..6d4694400 100644 --- a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj +++ b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj @@ -56,6 +56,7 @@ + From 18b7787c8b70d99a2c0e6e63d3b31477be459d76 Mon Sep 17 00:00:00 2001 From: James South Date: Mon, 4 Aug 2014 20:40:41 +0100 Subject: [PATCH 4/7] Make DisposalScope private Former-commit-id: b0d20c38ada344b0d457db7b78a7680366b3e696 --- src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs index 7d6fbf0cd..c6896c3a0 100644 --- a/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs +++ b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs @@ -23,7 +23,8 @@ namespace ImageProcessor.Web.Helpers /// /// The semaphore slims. /// - private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary SemaphoreSlims + = new ConcurrentDictionary(); /// /// The lock. @@ -79,7 +80,7 @@ namespace ImageProcessor.Web.Helpers Task releaserTask = Task.FromResult(releaser as IDisposable); SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); - + Task waitTask = semaphore.WaitAsync(); return waitTask.IsCompleted @@ -95,7 +96,7 @@ namespace ImageProcessor.Web.Helpers /// /// The disposable scope. /// - internal sealed class DisposableScope : IDisposable + private sealed class DisposableScope : IDisposable { /// /// The key From b6cf1e07e8d962e296b23c20b644349ddebd7697 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 5 Aug 2014 00:28:03 +0100 Subject: [PATCH 5/7] WebP is now a separate plugin Former-commit-id: 072872be821a4ac73038dec5a99de6ae2552a713 --- .../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")
  • From 158ae434d55c4f2ad39a821be4e33df1a571f0ab Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 5 Aug 2014 13:32:14 +0100 Subject: [PATCH 6/7] Fix dll load plus regex tests Former-commit-id: 2778a682ce325c1739e49f1e88ab7c5a8fa9f431 --- .../Extensions/DoubleExtensionsUnitTests.cs | 36 +--- .../ImageProcessor.Web.UnitTests.csproj | 4 + .../RegularExpressionUnitTests.cs | 201 +++++++++++++----- .../Helpers/CommonParameterParserUtility.cs | 2 +- .../ImageProcessorBootstrapper.cs | 8 +- 5 files changed, 161 insertions(+), 90 deletions(-) diff --git a/src/ImageProcessor.UnitTests/Extensions/DoubleExtensionsUnitTests.cs b/src/ImageProcessor.UnitTests/Extensions/DoubleExtensionsUnitTests.cs index cba1c07bc..91b62d74f 100644 --- a/src/ImageProcessor.UnitTests/Extensions/DoubleExtensionsUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Extensions/DoubleExtensionsUnitTests.cs @@ -10,7 +10,6 @@ namespace ImageProcessor.UnitTests.Extensions { - using System.Collections.Generic; using Common.Extensions; using NUnit.Framework; @@ -20,37 +19,20 @@ namespace ImageProcessor.UnitTests.Extensions [TestFixture] public class DoubleExtensionsUnitTests { - /// - /// Stores the values to test for the ToByte() extension method - /// - private Dictionary doubleToByteTests; - - /// - /// Sets up the values for the tests - /// - [TestFixtureSetUp] - public void Init() - { - this.doubleToByteTests = new Dictionary - { - { -10, 0x0 }, - { 1.5, 0x1 }, - { 25.7, 0x19 }, - { 1289047, 0xFF } - }; - } - /// /// Tests the double to byte conversion /// + /// Double input + /// Expected result [Test] - public void TestDoubleToByte() + [TestCase(-10, 0x0)] + [TestCase(1.5, 0x1)] + [TestCase(25.7, 0x19)] + [TestCase(1289047, 0xFF)] + public void TestDoubleToByte(double input, byte expected) { - foreach (var item in this.doubleToByteTests) - { - var result = item.Key.ToByte(); - Assert.AreEqual(item.Value, result); - } + byte result = input.ToByte(); + Assert.AreEqual(expected, result); } } } \ No newline at end of file diff --git a/src/ImageProcessor.Web.UnitTests/ImageProcessor.Web.UnitTests.csproj b/src/ImageProcessor.Web.UnitTests/ImageProcessor.Web.UnitTests.csproj index 460e329b9..357f3b11d 100644 --- a/src/ImageProcessor.Web.UnitTests/ImageProcessor.Web.UnitTests.csproj +++ b/src/ImageProcessor.Web.UnitTests/ImageProcessor.Web.UnitTests.csproj @@ -69,6 +69,10 @@ {3b5dd734-fb7a-487d-8ce6-55e7af9aea7e} ImageProcessor + + {2cf69699-959a-44dc-a281-4e2596c25043} + ImageProcessor.Plugins.WebP +
    diff --git a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs index c41454563..86c346e38 100644 --- a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs +++ b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs @@ -10,10 +10,14 @@ namespace ImageProcessor.Web.UnitTests { + using System; + using System.Collections.Generic; using System.Drawing; using ImageProcessor.Imaging; using ImageProcessor.Imaging.Filters; using ImageProcessor.Imaging.Formats; + using ImageProcessor.Plugins.WebP.Imaging.Formats; + using NUnit.Framework; /// @@ -40,37 +44,55 @@ namespace ImageProcessor.Web.UnitTests } /// - /// The brightness regex unit test. + /// The contrast regex unit test. /// + /// + /// The input string. + /// + /// + /// The expected result. + /// [Test] - public void TestBrightnessRegex() + [TestCase("brightness=56", 56)] + [TestCase("brightness=84", 84)] + [TestCase("brightness=66", 66)] + [TestCase("brightness=101", 1)] + [TestCase("brightness=00001", 1)] + [TestCase("brightness=-50", -50)] + [TestCase("brightness=0", 0)] + public void TestBrightnesstRegex(string input, int expected) { - const string Querystring = "brightness=56"; - const int Expected = 56; - Processors.Brightness brightness = new Processors.Brightness(); - brightness.MatchRegexIndex(Querystring); + brightness.MatchRegexIndex(input); + int result = brightness.Processor.DynamicParameter; - int actual = brightness.Processor.DynamicParameter; - - Assert.AreEqual(Expected, actual); + Assert.AreEqual(expected, result); } /// /// The contrast regex unit test. /// + /// + /// The input string. + /// + /// + /// The expected result. + /// [Test] - public void TestContrastRegex() + [TestCase("contrast=56", 56)] + [TestCase("contrast=84", 84)] + [TestCase("contrast=66", 66)] + [TestCase("contrast=101", 1)] + [TestCase("contrast=00001", 1)] + [TestCase("contrast=-50", -50)] + [TestCase("contrast=0", 0)] + public void TestContrastRegex(string input, int expected) { - const string Querystring = "contrast=56"; - const int Expected = 56; - Processors.Contrast contrast = new Processors.Contrast(); - contrast.MatchRegexIndex(Querystring); - - int actual = contrast.Processor.DynamicParameter; + contrast.MatchRegexIndex(input); + int result = contrast.Processor.DynamicParameter; - Assert.AreEqual(Expected, actual); + Assert.AreEqual(expected, result); } /// @@ -93,52 +115,102 @@ namespace ImageProcessor.Web.UnitTests /// The filter regex unit test. /// [Test] + public void TestFilterRegex() { - // Should really write more for the other filters. - const string Querystring = "filter=lomograph"; - IMatrixFilter expected = MatrixFilters.Lomograph; + Dictionary data = new Dictionary + { + { + "filter=lomograph", MatrixFilters.Lomograph + }, + { + "filter=polaroid", MatrixFilters.Polaroid + }, + { + "filter=comic", MatrixFilters.Comic + }, + { + "filter=greyscale", MatrixFilters.GreyScale + }, + { + "filter=blackwhite", MatrixFilters.BlackWhite + }, + { + "filter=invert", MatrixFilters.Invert + }, + { + "filter=gotham", MatrixFilters.Gotham + }, + { + "filter=hisatch", MatrixFilters.HiSatch + }, + { + "filter=losatch", MatrixFilters.LoSatch + }, + { + "filter=sepia", MatrixFilters.Sepia + } + }; Processors.Filter filter = new Processors.Filter(); - filter.MatchRegexIndex(Querystring); - - IMatrixFilter actual = filter.Processor.DynamicParameter; - - Assert.AreEqual(expected, actual); + foreach (KeyValuePair item in data) + { + filter.MatchRegexIndex(item.Key); + IMatrixFilter result = filter.Processor.DynamicParameter; + Assert.AreEqual(item.Value, result); + } } /// /// The format regex unit test. /// + /// + /// The input querystring. + /// + /// + /// The expected type. + /// [Test] - public void TestFormatRegex() + [TestCase("format=bmp", typeof(BitmapFormat))] + [TestCase("format=png", typeof(PngFormat))] + [TestCase("format=png8", typeof(PngFormat))] + [TestCase("format=jpeg", typeof(JpegFormat))] + [TestCase("format=jpg", typeof(JpegFormat))] + [TestCase("format=gif", typeof(GifFormat))] + [TestCase("format=webp", typeof(WebPFormat))] + public void TestFormatRegex(string input, Type expected) { - const string Querystring = "format=gif"; - ISupportedImageFormat expected = new GifFormat(); - Processors.Format format = new Processors.Format(); - format.MatchRegexIndex(Querystring); - - ISupportedImageFormat actual = format.Processor.DynamicParameter; + format.MatchRegexIndex(input); + Type result = format.Processor.DynamicParameter.GetType(); - Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, result); } /// /// The quality regex unit test. /// + /// + /// The input. + /// + /// + /// The expected result. + /// [Test] - public void TestQualityRegex() + [TestCase("quality=56", 56)] + [TestCase("quality=84", 84)] + [TestCase("quality=66", 66)] + [TestCase("quality=101", 1)] + [TestCase("quality=00001", 1)] + [TestCase("quality=-50", 50)] + [TestCase("quality=0", 0)] + public void TestQualityRegex(string input, int expected) { - const string Querystring = "quality=56"; - const int Expected = 56; - Processors.Quality quality = new Processors.Quality(); - quality.MatchRegexIndex(Querystring); - - int actual = quality.Processor.DynamicParameter; + quality.MatchRegexIndex(input); + int result = quality.Processor.DynamicParameter; - Assert.AreEqual(Expected, actual); + Assert.AreEqual(expected, result); } /// @@ -161,18 +233,25 @@ namespace ImageProcessor.Web.UnitTests /// /// The rotate regex unit test. /// + /// + /// The input string. + /// + /// + /// The expected result. + /// [Test] - public void TestRotateRegex() + [TestCase("rotate=0", 0)] + [TestCase("rotate=270", 270)] + [TestCase("rotate=-270", 0)] + [TestCase("rotate=angle-28", 28)] + public void TestRotateRegex(string input, int expected) { - const string Querystring = "rotate=270"; - const int Expected = 270; - Processors.Rotate rotate = new Processors.Rotate(); - rotate.MatchRegexIndex(Querystring); + rotate.MatchRegexIndex(input); - int actual = rotate.Processor.DynamicParameter; + int result = rotate.Processor.DynamicParameter; - Assert.AreEqual(Expected, actual); + Assert.AreEqual(expected, result); } /// @@ -181,14 +260,26 @@ namespace ImageProcessor.Web.UnitTests [Test] public void TestRoundedCornersRegex() { - const string Querystring = "roundedcorners=30"; - RoundedCornerLayer expected = new RoundedCornerLayer(30, true, true, true, true); - Processors.RoundedCorners roundedCorners = new Processors.RoundedCorners(); - roundedCorners.MatchRegexIndex(Querystring); + Dictionary data = new Dictionary + { + { + "roundedcorners=30", new RoundedCornerLayer(30, true, true, true, true) + }, + { + "roundedcorners=radius-26|tl-true|tr-false|bl-true|br-false", new RoundedCornerLayer(26, true, false, true, false) + }, + { + "roundedcorners=26,tl=true,tr=false,bl=true,br=false", new RoundedCornerLayer(26, true, false, true, false) + } + }; - RoundedCornerLayer actual = roundedCorners.Processor.DynamicParameter; - - Assert.AreEqual(expected, actual); + Processors.RoundedCorners roundedCorners = new Processors.RoundedCorners(); + foreach (KeyValuePair item in data) + { + roundedCorners.MatchRegexIndex(item.Key); + RoundedCornerLayer result = roundedCorners.Processor.DynamicParameter; + Assert.AreEqual(item.Value, result); + } } /// diff --git a/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs b/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs index 505d80d99..22c266a85 100644 --- a/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs +++ b/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs @@ -37,7 +37,7 @@ namespace ImageProcessor.Web.Helpers /// /// The regular expression to search strings for values between 1 and 100. /// - private static readonly Regex In100RangeRegex = new Regex(@"(-?(?:100)|-?([1-9]?[0-9]))", RegexOptions.Compiled); + private static readonly Regex In100RangeRegex = new Regex(@"(-?(0*(?:[1-9][0-9]?|100)))", RegexOptions.Compiled); /// /// The sharpen regex. diff --git a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs index 59f975fb3..4cffe51eb 100644 --- a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs +++ b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs @@ -73,13 +73,6 @@ 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(); @@ -88,6 +81,7 @@ namespace ImageProcessor.Configuration // ReSharper disable once AssignNullToNotNullAttribute FileInfo[] files = new DirectoryInfo(targetBasePath).GetFiles("*.dll", SearchOption.AllDirectories); + HashSet found = new HashSet(); foreach (FileInfo fileInfo in files) { AssemblyName assemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName); From 30a7099229f954a493a892bcc9b41cc376cc9263 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 5 Aug 2014 14:43:07 +0100 Subject: [PATCH 7/7] Moar tests plus known colours! Former-commit-id: 38f02083c8cae1bf549c0863c4842e196a0b90f5 --- .../RegularExpressionUnitTests.cs | 82 ++++++++++++++----- .../Helpers/CommonParameterParserUtility.cs | 42 +++++++++- .../NET45/Processors/Format.cs | 1 - 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs index 86c346e38..db81f4533 100644 --- a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs +++ b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs @@ -29,18 +29,25 @@ namespace ImageProcessor.Web.UnitTests /// /// The alpha regex unit test. /// + /// + /// The input string. + /// + /// + /// The expected result. + /// [Test] - public void TestAlphaRegex() + [TestCase("alpha=66", 66)] + [TestCase("alpha=-66", 66)] + [TestCase("alpha=101", 1)] + [TestCase("alpha=-101", 1)] + [TestCase("alpha=000053", 53)] + public void TestAlphaRegex(string input, int expected) { - const string Querystring = "alpha=56"; - const int Expected = 56; - Processors.Alpha alpha = new Processors.Alpha(); - alpha.MatchRegexIndex(Querystring); - - int actual = alpha.Processor.DynamicParameter; + alpha.MatchRegexIndex(input); + int result = alpha.Processor.DynamicParameter; - Assert.AreEqual(Expected, actual); + Assert.AreEqual(expected, result); } /// @@ -95,6 +102,32 @@ namespace ImageProcessor.Web.UnitTests Assert.AreEqual(expected, result); } + /// + /// The saturation regex unit test. + /// + /// + /// The input string. + /// + /// + /// The expected result. + /// + [Test] + [TestCase("saturation=56", 56)] + [TestCase("saturation=84", 84)] + [TestCase("saturation=66", 66)] + [TestCase("saturation=101", 1)] + [TestCase("saturation=00001", 1)] + [TestCase("saturation=-50", -50)] + [TestCase("saturation=0", 0)] + public void TestSaturationRegex(string input, int expected) + { + Processors.Saturation saturation = new Processors.Saturation(); + saturation.MatchRegexIndex(input); + int result = saturation.Processor.DynamicParameter; + + Assert.AreEqual(expected, result); + } + /// /// The rotate regex unit test. /// @@ -288,20 +321,29 @@ namespace ImageProcessor.Web.UnitTests [Test] public void TestTintRegex() { - const string HexQuerystring = "tint=6aa6cc"; - const string RgbaQuerystring = "tint=106,166,204,255"; - Color expectedHex = ColorTranslator.FromHtml("#" + "6aa6cc"); - Color expectedRgba = Color.FromArgb(255, 106, 166, 204); + Dictionary data = new Dictionary + { + { + "tint=6aa6cc", ColorTranslator.FromHtml("#" + "6aa6cc") + }, + { + "tint=106,166,204,255", Color.FromArgb(255, 106, 166, 204) + }, + { + "tint=fff", Color.FromArgb(255, 255, 255, 255) + }, + { + "tint=white", Color.White + } + }; Processors.Tint tint = new Processors.Tint(); - tint.MatchRegexIndex(HexQuerystring); - Color actualHex = tint.Processor.DynamicParameter; - Assert.AreEqual(expectedHex, actualHex); - - tint = new Processors.Tint(); - tint.MatchRegexIndex(RgbaQuerystring); - Color actualRgba = tint.Processor.DynamicParameter; - Assert.AreEqual(expectedRgba, actualRgba); + foreach (KeyValuePair item in data) + { + tint.MatchRegexIndex(item.Key); + Color result = tint.Processor.DynamicParameter; + Assert.AreEqual(item.Value, result); + } } } } \ No newline at end of file diff --git a/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs b/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs index 22c266a85..3e8362d08 100644 --- a/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs +++ b/src/ImageProcessor.Web/NET45/Helpers/CommonParameterParserUtility.cs @@ -11,8 +11,10 @@ namespace ImageProcessor.Web.Helpers { using System; + using System.Collections.Generic; using System.Drawing; using System.Globalization; + using System.Text; using System.Text.RegularExpressions; using ImageProcessor.Common.Extensions; @@ -24,10 +26,15 @@ namespace ImageProcessor.Web.Helpers /// public static class CommonParameterParserUtility { + /// + /// The collection of known colors. + /// + private static readonly Dictionary KnownColors = new Dictionary(); + /// /// The regular expression to search strings for colors. /// - private static readonly Regex ColorRegex = new Regex(@"(bgcolor|color|tint|vignette)(=|-)(\d+,\d+,\d+,\d+|([0-9a-fA-F]{3}){1,2})", RegexOptions.Compiled); + private static readonly Regex ColorRegex = BuildColorRegex(); /// /// The regular expression to search strings for angles. @@ -93,6 +100,11 @@ namespace ImageProcessor.Web.Helpers { string value = match.Value.Split(new[] { '=', '-' })[1]; + if (KnownColors.ContainsKey(value)) + { + return Color.FromKnownColor(KnownColors[value]); + } + if (value.Contains(",")) { int[] split = value.ToPositiveIntegerArray(); @@ -224,5 +236,33 @@ namespace ImageProcessor.Web.Helpers return 0; } + + /// + /// Builds a regular expression for the three main colour types. + /// + /// + /// The to match colors. + /// + private static Regex BuildColorRegex() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(@"(bgcolor|color|tint|vignette)(=|-)(\d+,\d+,\d+,\d+|([0-9a-fA-F]{3}){1,2}|("); + + KnownColor[] knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor)); + + for (int i = 0; i < knownColors.Length; i++) + { + KnownColor knownColor = knownColors[i]; + string name = knownColor.ToString().ToLowerInvariant(); + + KnownColors.Add(name, knownColor); + + stringBuilder.Append(i > 0 ? "|" + name : name); + } + + stringBuilder.Append("))"); + + return new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase); + } } } diff --git a/src/ImageProcessor.Web/NET45/Processors/Format.cs b/src/ImageProcessor.Web/NET45/Processors/Format.cs index b419f211f..7b57d7ddd 100644 --- a/src/ImageProcessor.Web/NET45/Processors/Format.cs +++ b/src/ImageProcessor.Web/NET45/Processors/Format.cs @@ -15,7 +15,6 @@ namespace ImageProcessor.Web.Processors using System.Linq; using System.Text; using System.Text.RegularExpressions; - using System.Web; using ImageProcessor.Configuration; using ImageProcessor.Imaging.Formats;