diff --git a/src/ImageProcessor.Web.PostProcessor/ApplicationEvents.cs b/src/ImageProcessor.Web.PostProcessor/ApplicationEvents.cs new file mode 100644 index 000000000..27162b32e --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/ApplicationEvents.cs @@ -0,0 +1,36 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// PostProcesses any image requests within the web application. +// Many thanks to Azure Image Optimizer +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Web; + +[assembly: PreApplicationStartMethod(typeof(ImageProcessor.Web.PostProcessor.ApplicationEvents), "Start")] +namespace ImageProcessor.Web.PostProcessor +{ + using ImageProcessor.Web.Helpers; + using ImageProcessor.Web.HttpModules; + + /// + /// PostProcesses any image requests within the web application. + /// Many thanks to Azure Image Optimizer + /// + public static class ApplicationEvents + { + public static void Start() + { + ImageProcessingModule.OnPostProcessing += PostProcess; + } + + private static async void PostProcess(object sender, PostProcessingEventArgs e) + { + await PostProcessor.PostProcessImage(e.CachedImagePath); + } + } +} diff --git a/src/ImageProcessor.Web.PostProcessor/ImageProcessor.Web.PostProcessor.csproj b/src/ImageProcessor.Web.PostProcessor/ImageProcessor.Web.PostProcessor.csproj new file mode 100644 index 000000000..fcfad149a --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/ImageProcessor.Web.PostProcessor.csproj @@ -0,0 +1,78 @@ + + + + + Debug + AnyCPU + {55D08737-7D7E-4995-8892-BD9F944329E6} + Library + Properties + ImageProcessor.Web.PostProcessor + ImageProcessor.Web.PostProcessor + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {D011A778-59C8-4BFA-A770-C350216BF161} + ImageProcessor.Web + + + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} + ImageProcessor + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/PostProcessingResultEventArgs.cs b/src/ImageProcessor.Web.PostProcessor/PostProcessingResultEventArgs.cs new file mode 100644 index 000000000..eb019872e --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/PostProcessingResultEventArgs.cs @@ -0,0 +1,103 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The post processing result event arguments. +// Many thanks to Azure Image Optimizer +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.PostProcessor +{ + using System; + using System.IO; + using System.Text; + + /// + /// The post processing result event arguments. + /// Many thanks to Azure Image Optimizer + /// + public class PostProcessingResultEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The original file name. + /// The result file name. + public PostProcessingResultEventArgs(string originalFileName, string resultFileName) + { + FileInfo original = new FileInfo(originalFileName); + FileInfo result = new FileInfo(resultFileName); + + if (original.Exists) + { + this.OriginalFileName = original.FullName; + this.OriginalFileSize = original.Length; + } + + if (result.Exists) + { + this.ResultFileName = result.FullName; + this.ResultFileSize = result.Length; + } + } + + /// + /// Gets the original file size in bytes. + /// + public long OriginalFileSize { get; set; } + + /// + /// Gets the original file name. + /// + public string OriginalFileName { get; set; } + + /// + /// Gets the result file size in bytes. + /// + public long ResultFileSize { get; set; } + + /// + /// + /// + public string ResultFileName { get; set; } + + /// + /// Gets the difference in filesize in bytes. + /// + public long Saving + { + get { return this.OriginalFileSize - this.ResultFileSize; } + } + + /// + /// Gets the difference in filesize as a percentage. + /// + public double Percent + { + get + { + return Math.Round(100 - this.ResultFileSize / (double)this.OriginalFileSize * 100, 1); + } + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Optimized " + Path.GetFileName(this.OriginalFileName)); + stringBuilder.AppendLine("Before: " + this.OriginalFileSize + " bytes"); + stringBuilder.AppendLine("After: " + this.ResultFileSize + " bytes"); + stringBuilder.AppendLine("Saving: " + this.Saving + " bytes / " + Percent + "%"); + + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs b/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs new file mode 100644 index 000000000..7508c3dcf --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs @@ -0,0 +1,111 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The image postprocessor. +// Many thanks to Azure Image Optimizer +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.PostProcessor +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Threading.Tasks; + + /// + /// The image postprocessor. + /// Many thanks to Azure Image Optimizer + /// + internal static class PostProcessor + { + public async static Task PostProcessImage(string sourceFile) + { + string targetFile = Path.GetTempFileName(); + PostProcessingResultEventArgs result = await RunProcessAsync(sourceFile, targetFile); + + if (result != null && result.Saving > 0 && result.ResultFileSize > 0) + { + File.Copy(result.ResultFileName, result.OriginalFileName, true); + File.Delete(result.ResultFileName); + } + else + { + File.Delete(targetFile); + } + } + + private static Task RunProcessAsync(string sourceFile, string targetFile) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + ProcessStartInfo start = new ProcessStartInfo("cmd") + { + WindowStyle = ProcessWindowStyle.Hidden, + WorkingDirectory = PostProcessorBootstrapper.WorkingPath, + Arguments = GetArguments(sourceFile, targetFile), + UseShellExecute = false, + CreateNoWindow = true, + }; + + if (string.IsNullOrWhiteSpace(start.Arguments)) + { + tcs.SetResult(null); + return tcs.Task; + } + + Process process = new Process + { + StartInfo = start, + EnableRaisingEvents = true + }; + + process.Exited += (sender, args) => + { + tcs.SetResult(new PostProcessingResultEventArgs(sourceFile, targetFile)); + process.Dispose(); + }; + + process.Start(); + + return tcs.Task; + } + + private static string GetArguments(string sourceFile, string targetFile) + { + if (!Uri.IsWellFormedUriString(sourceFile, UriKind.RelativeOrAbsolute) && !File.Exists(sourceFile)) + { + return null; + } + + string ext; + + string extension = Path.GetExtension(sourceFile); + if (extension != null) + { + ext = extension.ToLowerInvariant(); + } + else + { + return null; + } + + switch (ext) + { + case ".png": + return string.Format(CultureInfo.CurrentCulture, "/c png.cmd \"{0}\" \"{1}\"", sourceFile, targetFile); + + case ".jpg": + case ".jpeg": + return string.Format(CultureInfo.CurrentCulture, "/c jpegtran -copy all -optimize -progressive \"{0}\" \"{1}\"", sourceFile, targetFile); + + case ".gif": + return string.Format(CultureInfo.CurrentCulture, "/c gifsicle --crop-transparency --no-comments --no-extensions --no-names --optimize=3 --batch \"{0}\" --output=\"{1}\"", sourceFile, targetFile); + } + return null; + } + } +} diff --git a/src/ImageProcessor.Web.PostProcessor/PostProcessorBootstrapper.cs b/src/ImageProcessor.Web.PostProcessor/PostProcessorBootstrapper.cs new file mode 100644 index 000000000..929799504 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/PostProcessorBootstrapper.cs @@ -0,0 +1,84 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The postprocessor bootstrapper. +// Many thanks to Azure Image Optimizer +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.PostProcessor +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + + using ImageProcessor.Configuration; + + /// + /// The postprocessor bootstrapper. + /// Many thanks to Azure Image Optimizer + /// + internal static class PostProcessorBootstrapper + { + /// + /// Initializes static members of the class. + /// + static PostProcessorBootstrapper() + { + RegisterExecutables(); + } + + /// + /// Gets the working directory path. + /// + public static string WorkingPath { get; private set; } + + /// + /// Registers the embedded executables. + /// + public static void RegisterExecutables() + { + // None of the tools used here are called using dllimport so we don't go through the normal registration channel. + string folder = ImageProcessorBootstrapper.Instance.NativeBinaryFactory.Is64BitEnvironment ? "x64" : "x86"; + Assembly assembly = Assembly.GetExecutingAssembly(); + WorkingPath = Path.GetFullPath(Path.Combine(new Uri(assembly.Location).LocalPath, "..\\imageprocessor.postprocessor\\")); + + // Create the folder for storing temporary images. + // ReSharper disable once AssignNullToNotNullAttribute + DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(WorkingPath)); + if (!directoryInfo.Exists) + { + directoryInfo.Create(); + } + + // Get the resources and copy them across. + Dictionary resources = new Dictionary + { + {"gifsicle.exe","ImageProcessor.Web.PostProcessor.Resources.Unmanaged." + folder + ".gifsicle.exe"}, + {"jpegtran.exe","ImageProcessor.Web.PostProcessor.Resources.Unmanaged.x86.jpegtran.exe"}, + {"optipng.exe","ImageProcessor.Web.PostProcessor.Resources.Unmanaged.x86.optipng.exe"}, + {"pngout.exe","ImageProcessor.Web.PostProcessor.Resources.Unmanaged.x86.pngout.exe"}, + {"png.cmd","ImageProcessor.Web.PostProcessor.Resources.Unmanaged.x86.png.cmd" } + }; + + // Write the files out to the bin folder. + foreach (KeyValuePair resource in resources) + { + using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource.Value)) + { + if (resourceStream != null) + { + using (FileStream fileStream = File.OpenWrite(Path.Combine(WorkingPath, resource.Key))) + { + resourceStream.CopyTo(fileStream); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web.PostProcessor/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cd5035c52 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/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.Web.PostProcessor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageProcessor.Web.PostProcessor")] +[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("089f0bac-b115-4c40-a955-64da08aa0989")] + +// 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.Web.PostProcessor/README.md b/src/ImageProcessor.Web.PostProcessor/README.md new file mode 100644 index 000000000..05c331aeb --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/README.md @@ -0,0 +1,13 @@ +#Resource locations + +###gifsicle +http://www.lcdf.org/gifsicle/ + +###jpegtran +http://jpegclub.org/jpegtran/ + +###optipng +http://optipng.sourceforge.net/ + +###pngout +http://advsys.net/ken/utils.htm \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x64/gifsicle.exe.REMOVED.git-id b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x64/gifsicle.exe.REMOVED.git-id new file mode 100644 index 000000000..1e5525042 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x64/gifsicle.exe.REMOVED.git-id @@ -0,0 +1 @@ +8fdba772314bc58249bc68118054cadfabf200ba \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/gifsicle.exe.REMOVED.git-id b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/gifsicle.exe.REMOVED.git-id new file mode 100644 index 000000000..ffc8d42a9 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/gifsicle.exe.REMOVED.git-id @@ -0,0 +1 @@ +58c7376ac5c2b3f95a36c286d35d4151abc8fafc \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/jpegtran.exe.REMOVED.git-id b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/jpegtran.exe.REMOVED.git-id new file mode 100644 index 000000000..a934efad1 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/jpegtran.exe.REMOVED.git-id @@ -0,0 +1 @@ +c4b33334d3a5ce903fee18d9573571d93ac81bd4 \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/optipng.exe.REMOVED.git-id b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/optipng.exe.REMOVED.git-id new file mode 100644 index 000000000..3e52481dd --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/optipng.exe.REMOVED.git-id @@ -0,0 +1 @@ +81cff3c5fe3cf669ef6e158240cc0034ed3711fd \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/png.cmd b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/png.cmd new file mode 100644 index 000000000..3b515d8b5 --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/png.cmd @@ -0,0 +1,2 @@ +optipng %1 -out %2 -o3 -i0 -quiet +pngout %2 %2 /s1 /y /v /q \ No newline at end of file diff --git a/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/pngout.exe b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/pngout.exe new file mode 100644 index 000000000..0a0d7d3fa Binary files /dev/null and b/src/ImageProcessor.Web.PostProcessor/Resources/Unmanaged/x86/pngout.exe differ diff --git a/src/ImageProcessor.Web.PostProcessor/Settings.StyleCop b/src/ImageProcessor.Web.PostProcessor/Settings.StyleCop new file mode 100644 index 000000000..bb05f99bc --- /dev/null +++ b/src/ImageProcessor.Web.PostProcessor/Settings.StyleCop @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ImageProcessor.Web/Helpers/ImageHelpers.cs b/src/ImageProcessor.Web/Helpers/ImageHelpers.cs index fee71b606..6b2ea9eb5 100644 --- a/src/ImageProcessor.Web/Helpers/ImageHelpers.cs +++ b/src/ImageProcessor.Web/Helpers/ImageHelpers.cs @@ -63,7 +63,7 @@ namespace ImageProcessor.Web.Helpers /// public static string GetExtension(string fullPath, string queryString) { - Match match; + Match match = null; // First check to see if the format processor is being used and test against that. IWebGraphicsProcessor format = ImageProcessorConfiguration.Instance.GraphicsProcessors @@ -73,7 +73,8 @@ namespace ImageProcessor.Web.Helpers { match = format.RegexPattern.Match(queryString); } - else + + if (match == null || !match.Success) { // Test against the path minus the querystring so any other // processors don't interere. diff --git a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs index c344110de..3024be284 100644 --- a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs @@ -40,5 +40,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.1.3.0")] -[assembly: AssemblyFileVersion("4.1.3.0")] +[assembly: AssemblyVersion("4.1.4.0")] +[assembly: AssemblyFileVersion("4.1.4.0")] diff --git a/src/ImageProcessor.sln b/src/ImageProcessor.sln index adac94cc4..8caaeabc7 100644 --- a/src/ImageProcessor.sln +++ b/src/ImageProcessor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C427A497-74DC-49B1-8420-D6E68354F29B}" ProjectSection(SolutionItems) = preProject @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Plugins.Cair EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessor.Playground\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web.PostProcessor", "ImageProcessor.Web.PostProcessor\ImageProcessor.Web.PostProcessor.csproj", "{55D08737-7D7E-4995-8892-BD9F944329E6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution All|Any CPU = All|Any CPU @@ -181,6 +183,21 @@ Global {7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}.Release|Mixed Platforms.Build.0 = Release|Any CPU {7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}.Release|x86.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.All|Any CPU.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.All|Any CPU.Build.0 = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.All|Mixed Platforms.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.All|Mixed Platforms.Build.0 = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.All|x86.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Debug|x86.ActiveCfg = Debug|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Release|Any CPU.Build.0 = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {55D08737-7D7E-4995-8892-BD9F944329E6}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/TestWebsites/MVC/Test_Website_MVC.csproj b/src/TestWebsites/MVC/Test_Website_MVC.csproj index 9075ef587..6c6b551c4 100644 --- a/src/TestWebsites/MVC/Test_Website_MVC.csproj +++ b/src/TestWebsites/MVC/Test_Website_MVC.csproj @@ -152,6 +152,10 @@ + + {55d08737-7d7e-4995-8892-bd9f944329e6} + ImageProcessor.Web.PostProcessor + {d011a778-59c8-4bfa-a770-c350216bf161} ImageProcessor.Web