diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..0392d3e02 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,93 @@ +[*.cs] +# Use soft tabs (spaces) for indentation +indent_style = space + +# Indent switch case contents +csharp_indent_case_contents = true + +# Indent switch labels +csharp_indent_switch_labels = true + +# Place catch statements on a new line +csharp_new_line_before_catch = true + +# Place else statements on a new line +csharp_new_line_before_else = true + +# Require finally statements to be on a new line after the closing brace +csharp_new_line_before_finally = true + +# Require braces to be on a new line for types, properties, lambdas, object_collection_array_initializers, methods, control_blocks, and accessors (also known as "Allman" style) +csharp_new_line_before_open_brace = types, properties, lambdas, object_collection_array_initializers, methods, control_blocks, accessors + +# Sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +# Require NO space between a cast and the value +csharp_space_after_cast = false + +# Require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true + +# Require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true + +# Require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true + +# Remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false + +# Do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false + +# Remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false + +# Place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false + +# Leave code block on single line +csharp_preserve_single_line_blocks = true + +# Prefer expression-bodied members for accessors +csharp_style_expression_bodied_accessors = true:suggestion + +# Prefer expression-bodied members for constructors +csharp_style_expression_bodied_constructors = true:suggestion + +# Prefer expression-bodied members for indexers +csharp_style_expression_bodied_indexers = true:suggestion + +# Prefer expression-bodied members for methods +csharp_style_expression_bodied_methods = true:suggestion + +# Prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +# Prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion + +# Prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Prefer explicit type over var to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = false:suggestion + +# Prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +# Prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +# Prefer fields to be prefaced with this. in C# or Me. in Visual Basic +dotnet_style_qualification_for_field = true:suggestion + +# Prefer methods to be prefaced with this. in C# or Me. in Visual Basic +dotnet_style_qualification_for_method = true:suggestion + +# Prefer properties to be prefaced with this. in C# or Me. in Visual Basic +dotnet_style_qualification_for_property = true:suggestion diff --git a/ImageProcessor.sln b/ImageProcessor.sln index 8fc6482a2..e49199a71 100644 --- a/ImageProcessor.sln +++ b/ImageProcessor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2026 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{5368AF57-74ED-4499-BCCD-38BCB18C4629}" EndProject @@ -34,13 +34,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{4E1AB050 build\build.ps1 = build\build.ps1 EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Readme", "Readme", "{1F68939E-8BBE-4D09-98B5-68B06BBDE68E}" - ProjectSection(SolutionItems) = preProject - LICENSE = LICENSE - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuspecs", "Nuspecs", "{E0B81C77-6E29-4500-9E9F-F0B72EBE8241}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{E0B81C77-6E29-4500-9E9F-F0B72EBE8241}" ProjectSection(SolutionItems) = preProject build\NuSpecs\ImageProcessor.nuspec = build\NuSpecs\ImageProcessor.nuspec build\NuSpecs\ImageProcessor.Plugins.Cair.nuspec = build\NuSpecs\ImageProcessor.Plugins.Cair.nuspec @@ -81,6 +75,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ImageProcessor.Web.Plugins. build\content\ImageProcessor.Web.Plugins.AmazonS3Cache\config\imageprocessor\cache.config.transform = build\content\ImageProcessor.Web.Plugins.AmazonS3Cache\config\imageprocessor\cache.config.transform EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57BBD0FE-E8BB-45EF-B7EE-EB7669192D0D}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + LICENSE = LICENSE + NuGet.config = NuGet.config + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution All|Any CPU = All|Any CPU @@ -167,6 +171,7 @@ Global {633B1C4C-4823-47BE-9A01-A665F3118C8C} = {0E113662-D6E2-473E-92BA-6005EB412C00} {ADAF0E0D-0CE9-45C2-AF95-70443273C468} = {0E113662-D6E2-473E-92BA-6005EB412C00} {7BF5274B-56A7-4B62-8105-E9BDF25BAFE7} = {0E113662-D6E2-473E-92BA-6005EB412C00} + {4E1AB050-8CC4-4099-80AF-7FF2BFC000E9} = {57BBD0FE-E8BB-45EF-B7EE-EB7669192D0D} {E0B81C77-6E29-4500-9E9F-F0B72EBE8241} = {4E1AB050-8CC4-4099-80AF-7FF2BFC000E9} {A74AE40C-5374-4B15-BEBF-94D74C16AC3A} = {0E113662-D6E2-473E-92BA-6005EB412C00} {19517DBA-58AA-4294-97BF-0D02902749B4} = {4E1AB050-8CC4-4099-80AF-7FF2BFC000E9} diff --git a/ImageProcessor.sln.DotSettings b/ImageProcessor.sln.DotSettings deleted file mode 100644 index a2833a2b2..000000000 --- a/ImageProcessor.sln.DotSettings +++ /dev/null @@ -1,6 +0,0 @@ - - ASCII - CORS - IO - SHA - SRGB \ No newline at end of file diff --git a/Nuget.config b/NuGet.config similarity index 60% rename from Nuget.config rename to NuGet.config index 00e302a4d..554c2f634 100644 --- a/Nuget.config +++ b/NuGet.config @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/build/NuSpecs/ImageProcessor.Web.PostProcessor.nuspec b/build/NuSpecs/ImageProcessor.Web.PostProcessor.nuspec index e7fe1260f..92948a9f2 100644 --- a/build/NuSpecs/ImageProcessor.Web.PostProcessor.nuspec +++ b/build/NuSpecs/ImageProcessor.Web.PostProcessor.nuspec @@ -22,7 +22,7 @@ en-GB Image Resize Crop Rotate Quality Watermark Gif Jpg Jpeg Bitmap Png Tiff ASP Cache EXIF - + diff --git a/build/NuSpecs/ImageProcessor.Web.nuspec b/build/NuSpecs/ImageProcessor.Web.nuspec index 835a7e0c3..ef29e2489 100644 --- a/build/NuSpecs/ImageProcessor.Web.nuspec +++ b/build/NuSpecs/ImageProcessor.Web.nuspec @@ -25,7 +25,7 @@ Image Resize Crop Rotate Quality Watermark Gif Jpg Jpeg Bitmap Png Tiff ASP Cache EXIF - + diff --git a/build/NuSpecs/ImageProcessor.nuspec b/build/NuSpecs/ImageProcessor.nuspec index 02531031d..4e4ea9247 100644 --- a/build/NuSpecs/ImageProcessor.nuspec +++ b/build/NuSpecs/ImageProcessor.nuspec @@ -23,6 +23,11 @@ James Jackson-South en-GB Image Resize Crop Rotate Quality Watermark Gif Jpg Jpeg Bitmap Png Tiff Fluent Animated EXIF + + + + + diff --git a/build/build.ps1 b/build/build.ps1 index b0d49529b..14e886689 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -8,7 +8,7 @@ $nugetOutput = Join-Path $binPath "NuGets"; # Projects (NuGet dependencies are handled in the nuspec files themselves) $imageprocessor = @{ name = "ImageProcessor" - version = "2.8.0" + version = "2.9.0" folder = Join-Path $buildPath "src\ImageProcessor" output = Join-Path $binPath "ImageProcessor\lib\net452" csproj = "ImageProcessor.csproj" @@ -17,7 +17,7 @@ $imageprocessor = @{ $imageProcessorPluginsCair = @{ name = "ImageProcessor.Plugins.Cair" - version = "1.3.0" + version = "1.3.1" folder = Join-Path $buildPath "src\ImageProcessor.Plugins.Cair" output = Join-Path $binPath "ImageProcessor.Plugins.Cair\lib\net452" csproj = "ImageProcessor.Plugins.Cair.csproj" @@ -35,7 +35,7 @@ $imageProcessorPluginsWebP = @{ $imageprocessorWeb = @{ name = "ImageProcessor.Web" - version = "4.11.0" + version = "4.12.0" folder = Join-Path $buildPath "src\ImageProcessor.Web" output = Join-Path $binPath "ImageProcessor.Web\lib\net452" csproj = "ImageProcessor.Web.csproj" @@ -49,7 +49,7 @@ $imageprocessorWebConfig = @{ $imageProcessorWebPluginsAzureBlobCache = @{ name = "ImageProcessor.Web.Plugins.AzureBlobCache" - version = "1.6.0" + version = "1.7.0" folder = Join-Path $buildPath "src\ImageProcessor.Web.Plugins.AzureBlobCache" output = Join-Path $binPath "ImageProcessor.Web.Plugins.AzureBlobCache\lib\net452" csproj = "ImageProcessor.Web.Plugins.AzureBlobCache.csproj" @@ -58,7 +58,7 @@ $imageProcessorWebPluginsAzureBlobCache = @{ $imageProcessorWebPluginsAmazonS3Cache = @{ name = "ImageProcessor.Web.Plugins.AmazonS3Cache" - version = "1.1.0" + version = "1.2.0" folder = Join-Path $buildPath "src\ImageProcessor.Web.Plugins.AmazonS3Cache" output = Join-Path $binPath "ImageProcessor.Web.Plugins.AmazonS3Cache\lib\net452" csproj = "ImageProcessor.Web.Plugins.AmazonS3Cache.csproj" @@ -67,7 +67,7 @@ $imageProcessorWebPluginsAmazonS3Cache = @{ $imageProcessorWebPluginsPostProcessor = @{ name = "ImageProcessor.Web.Plugins.PostProcessor" - version = "1.5.0" + version = "2.0.0" folder = Join-Path $buildPath "src\ImageProcessor.Web.Plugins.PostProcessor" output = Join-Path $binPath "ImageProcessor.Web.Plugins.PostProcessor\lib\net452" csproj = "ImageProcessor.Web.Plugins.PostProcessor.csproj" diff --git a/build/content/ImageProcessor.Plugins.WebP/web.config.transform b/build/content/ImageProcessor.Plugins.WebP/web.config.transform index aaa15b857..70b5d667f 100644 --- a/build/content/ImageProcessor.Plugins.WebP/web.config.transform +++ b/build/content/ImageProcessor.Plugins.WebP/web.config.transform @@ -2,7 +2,7 @@ - + diff --git a/build/content/ImageProcessor.Web.Config/web.config.transform b/build/content/ImageProcessor.Web.Config/web.config.transform index 6acc1eafd..75bf362f0 100644 --- a/build/content/ImageProcessor.Web.Config/web.config.transform +++ b/build/content/ImageProcessor.Web.Config/web.config.transform @@ -2,12 +2,11 @@ -
-
-
+
+
+
- diff --git a/build/content/ImageProcessor.Web.Plugins.AmazonS3Cache/config/imageprocessor/cache.config.transform b/build/content/ImageProcessor.Web.Plugins.AmazonS3Cache/config/imageprocessor/cache.config.transform index 6a6cb898f..c0315678f 100644 --- a/build/content/ImageProcessor.Web.Plugins.AmazonS3Cache/config/imageprocessor/cache.config.transform +++ b/build/content/ImageProcessor.Web.Plugins.AmazonS3Cache/config/imageprocessor/cache.config.transform @@ -4,7 +4,7 @@ - + @@ -16,4 +16,3 @@ - diff --git a/build/content/ImageProcessor.Web.Plugins.AzureBlobCache/config/imageprocessor/cache.config.transform b/build/content/ImageProcessor.Web.Plugins.AzureBlobCache/config/imageprocessor/cache.config.transform index ea3d82bc4..e0e2c4758 100644 --- a/build/content/ImageProcessor.Web.Plugins.AzureBlobCache/config/imageprocessor/cache.config.transform +++ b/build/content/ImageProcessor.Web.Plugins.AzureBlobCache/config/imageprocessor/cache.config.transform @@ -2,16 +2,15 @@ - - - - - - - - + + + + + + + + - diff --git a/build/content/ImageProcessor.Web/web.config.transform b/build/content/ImageProcessor.Web/web.config.transform index bdee7b510..872fe9124 100644 --- a/build/content/ImageProcessor.Web/web.config.transform +++ b/build/content/ImageProcessor.Web/web.config.transform @@ -3,13 +3,13 @@ - + - + diff --git a/src/ImageProcessor.Plugins.Cair/ImageProcessor.Plugins.Cair.csproj b/src/ImageProcessor.Plugins.Cair/ImageProcessor.Plugins.Cair.csproj index 7ff8c49dd..377af60a4 100644 --- a/src/ImageProcessor.Plugins.Cair/ImageProcessor.Plugins.Cair.csproj +++ b/src/ImageProcessor.Plugins.Cair/ImageProcessor.Plugins.Cair.csproj @@ -38,6 +38,9 @@ + + ..\..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + @@ -62,6 +65,7 @@ + diff --git a/src/ImageProcessor.Plugins.Cair/Imaging/ContentAwareResizeLayer.cs b/src/ImageProcessor.Plugins.Cair/Imaging/ContentAwareResizeLayer.cs index 4fd5f4f1b..1b8e3f613 100644 --- a/src/ImageProcessor.Plugins.Cair/Imaging/ContentAwareResizeLayer.cs +++ b/src/ImageProcessor.Plugins.Cair/Imaging/ContentAwareResizeLayer.cs @@ -10,18 +10,14 @@ namespace ImageProcessor.Plugins.Cair.Imaging { + using System; using System.Drawing; /// /// Encapsulates the properties required to resize an image using content aware resizing. /// - public class ContentAwareResizeLayer + public class ContentAwareResizeLayer : IEquatable { - /// - /// The expected output type. - /// - private OutputType outputType = OutputType.Cair; - /// /// Initializes a new instance of the class. /// @@ -33,6 +29,11 @@ public ContentAwareResizeLayer(Size size) this.Size = size; } + /// + /// Gets or sets the size. + /// + public Size Size { get; set; } + /// /// Gets or sets the content aware resize convolution type (Default ContentAwareResizeConvolutionType.Prewitt). /// @@ -46,23 +47,7 @@ public ContentAwareResizeLayer(Size size) /// /// Gets or sets the expected output type. /// - public OutputType OutputType - { - get - { - return this.outputType; - } - - set - { - this.outputType = value; - } - } - - /// - /// Gets or sets the size. - /// - public Size Size { get; set; } + public OutputType OutputType { get; set; } = OutputType.Cair; /// /// Gets or sets the the file path to a bitmap file that provides weight indicators specified using @@ -88,54 +73,36 @@ public OutputType OutputType public int Timeout { get; set; } = 60000; /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - ContentAwareResizeLayer resizeLayer = obj as ContentAwareResizeLayer; - - if (resizeLayer == null) - { - return false; - } + public override bool Equals(object obj) => obj is ContentAwareResizeLayer contentAwareResizeLayer && this.Equals(contentAwareResizeLayer); - return this.Size == resizeLayer.Size - && this.ConvolutionType == resizeLayer.ConvolutionType - && this.EnergyFunction == resizeLayer.EnergyFunction - && this.OutputType == resizeLayer.OutputType - && this.Parallelize == resizeLayer.Parallelize - && this.Timeout == resizeLayer.Timeout - && this.WeightPath == resizeLayer.WeightPath; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(ContentAwareResizeLayer other) => other != null + && this.Size == other.Size + && this.ConvolutionType == other.ConvolutionType + && this.EnergyFunction == other.EnergyFunction + && this.OutputType == other.OutputType + && this.WeightPath == other.WeightPath + && this.Parallelize == other.Parallelize + && this.Timeout == other.Timeout; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = (int)this.ConvolutionType; - hashCode = (hashCode * 397) ^ (int)this.EnergyFunction; - hashCode = (hashCode * 397) ^ this.Parallelize.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)this.OutputType; - hashCode = (hashCode * 397) ^ this.Timeout; - hashCode = (hashCode * 397) ^ this.Size.GetHashCode(); - hashCode = (hashCode * 397) ^ (this.WeightPath != null ? this.WeightPath.GetHashCode() : 0); - return hashCode; - } - } + public override int GetHashCode() => (this.Size, this.ConvolutionType, this.EnergyFunction, this.OutputType, this.WeightPath, this.Parallelize, this.Timeout).GetHashCode(); } } diff --git a/src/ImageProcessor.Plugins.Cair/Properties/AssemblyInfo.cs b/src/ImageProcessor.Plugins.Cair/Properties/AssemblyInfo.cs index 9a550feaa..49070aabc 100644 --- a/src/ImageProcessor.Plugins.Cair/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Plugins.Cair/Properties/AssemblyInfo.cs @@ -40,5 +40,5 @@ // // 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.2.0")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.3.1.00000")] +[assembly: AssemblyFileVersion("1.3.1.00000")] diff --git a/src/ImageProcessor.Plugins.Cair/packages.config b/src/ImageProcessor.Plugins.Cair/packages.config new file mode 100644 index 000000000..8653b8e4c --- /dev/null +++ b/src/ImageProcessor.Plugins.Cair/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs b/src/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs index 3c931f96f..464306624 100644 --- a/src/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Plugins.WebP/Properties/AssemblyInfo.cs @@ -40,5 +40,5 @@ // // 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.6.0")] -[assembly: AssemblyFileVersion("1.0.6.0")] +[assembly: AssemblyVersion("1.3.0.00000")] +[assembly: AssemblyFileVersion("1.3.0.00000")] diff --git a/src/ImageProcessor.Web.Plugins.AmazonS3Cache/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web.Plugins.AmazonS3Cache/Properties/AssemblyInfo.cs index b93dd013a..327867e5e 100644 --- a/src/ImageProcessor.Web.Plugins.AmazonS3Cache/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web.Plugins.AmazonS3Cache/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James Jackson-South. // Licensed under the Apache License, Version 2.0. @@ -18,7 +18,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ImageProcessor.Web.Plugins.AmazonS3Cache")] -[assembly: AssemblyCopyright("Copyright © James Jackson-South")] +[assembly: AssemblyCopyright("Copyright © James Jackson-South")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -39,6 +39,6 @@ // // 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")] +// [assembly: AssemblyVersion("1.7.0.00000")] +[assembly: AssemblyVersion("1.7.0.00000")] +[assembly: AssemblyFileVersion("1.7.0.00000")] diff --git a/src/ImageProcessor.Web.Plugins.AzureBlobCache/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web.Plugins.AzureBlobCache/Properties/AssemblyInfo.cs index 07f67664d..11a70398c 100644 --- a/src/ImageProcessor.Web.Plugins.AzureBlobCache/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web.Plugins.AzureBlobCache/Properties/AssemblyInfo.cs @@ -40,5 +40,5 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.3.1.0")] -[assembly: AssemblyFileVersion("1.3.1.0")] +[assembly: AssemblyVersion("1.7.0.00000")] +[assembly: AssemblyFileVersion("1.7.0.00000")] diff --git a/src/ImageProcessor.Web.Plugins.PostProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web.Plugins.PostProcessor/Properties/AssemblyInfo.cs index ec35b4441..a57980769 100644 --- a/src/ImageProcessor.Web.Plugins.PostProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web.Plugins.PostProcessor/Properties/AssemblyInfo.cs @@ -41,7 +41,7 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.2.3.0")] -[assembly: AssemblyFileVersion("1.2.3.0")] +[assembly: AssemblyVersion("2.0.0.00000")] +[assembly: AssemblyFileVersion("2.0.0.00000")] -[assembly: InternalsVisibleTo("ImageProcessor.Web.UnitTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("ImageProcessor.Web.UnitTests")] diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs index 2488037fb..fb201ace9 100644 --- a/src/ImageProcessor.Web/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/Caching/DiskCache.cs @@ -211,64 +211,62 @@ public override Task TrimCacheAsync() this.ScheduleCacheTrimmer(token => { - string rootDirectory = Path.GetDirectoryName(this.CachedPath); - + var rootDirectory = Path.GetDirectoryName(this.CachedPath); if (rootDirectory != null) { - // Jump up to the parent branch to clean through the cache. - // UNC folders can throw exceptions if the file doesn't exist. - IEnumerable directories = SafeEnumerateDirectories(validatedAbsoluteCachePath).Reverse(); - + // Jump up to the parent branch to clean through the cache + // UNC folders can throw exceptions if the file doesn't exist + var directories = SafeEnumerateDirectories(validatedAbsoluteCachePath).Reverse().ToList(); foreach (string directory in directories) { - if (!Directory.Exists(directory)) - { - continue; - } - if (token.IsCancellationRequested) { break; } - IEnumerable files = Directory.EnumerateFiles(directory) - .Select(f => new FileInfo(f)) - .OrderBy(f => f.CreationTimeUtc); - int count = files.Count(); - - foreach (FileInfo fileInfo in files) + try { - if (token.IsCancellationRequested) - { - break; - } - - try + var files = Directory.EnumerateFiles(directory).Select(f => new FileInfo(f)).OrderBy(f => f.CreationTimeUtc).ToList(); + var count = files.Count; + foreach (var fileInfo in files) { - // If the group count is equal to the max count minus 1 then we know we - // have reduced the number of items below the maximum allowed. - // We'll cleanup any orphaned expired files though. - if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) + if (token.IsCancellationRequested) { break; } - // Remove from the cache and delete each CachedImage. - CacheIndexer.Remove(fileInfo.Name); - fileInfo.Delete(); - count--; - } - catch (Exception ex) - { - // Log it but skip to the next file. - ImageProcessorBootstrapper.Instance.Logger.Log($"Unable to clean cached file: {fileInfo.FullName}, {ex.Message}"); + try + { + // If the group count is equal to the max count minus 1 then we know we have reduced the number of items below the maximum allowed + // We'll cleanup any orphaned expired files though + if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) + { + break; + } + + // Remove from the cache and delete each CachedImage + CacheIndexer.Remove(fileInfo.Name); + fileInfo.Delete(); + count--; + } + catch (Exception ex) + { + // Log it but skip to the next file + ImageProcessorBootstrapper.Instance.Logger.Log($"Unable to clean cached file: {fileInfo.FullName}, {ex.Message}"); + } } } + catch (Exception ex) + { + // Log it but skip to the next directory + ImageProcessorBootstrapper.Instance.Logger.Log($"Unable to clean cached directory: {directory}, {ex.Message}"); + } - // If the directory is empty of files delete it to remove the FCN. + // If the directory is empty of files delete it to remove the FCN this.RecursivelyDeleteEmptyDirectories(directory, validatedAbsoluteCachePath, token); } } + return Task.FromResult(0); }); diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs index 4c5d5d717..6bd8f7a60 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope { object result = base.ConvertFrom(culture, value, propertyType); - return result is int[] list ? new Point(list[0], list[1]) : result; + return result is int[] list && list.Length == 2 ? new Point(list[0], list[1]) : result; } } } diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs new file mode 100644 index 000000000..7cb1dca28 --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South. +// Licensed under the Apache License, Version 2.0. +// +// +// The point converter. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Drawing; + using System.Globalization; + + /// + /// The PointF converter. + /// + public class PointFConverter : GenericArrayTypeConverter + { + /// + /// Converts the given object to the type of this converter, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// The to use as the current culture. + /// + /// The to convert. + /// The property type that the converter will convert to. + /// The conversion cannot be performed. + public override object ConvertFrom(CultureInfo culture, object value, Type propertyType) + { + object result = base.ConvertFrom(culture, value, propertyType); + + return result is float[] list && list.Length == 2 ? new PointF(list[0], list[1]) : result; + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs index 98d38c23b..8e579f49e 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope { object result = base.ConvertFrom(culture, value, propertyType); - return result is int[] list ? new Size(list[0], list[1]) : result; + return result is int[] list && list.Length == 2 ? new Size(list[0], list[1]) : result; } } } diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs index 9e5bbb665..272d463f7 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs @@ -22,13 +22,13 @@ namespace ImageProcessor.Web.Helpers public sealed class QueryParamParser { /// - /// A new instance of the class. + /// A new instance of the class. /// with lazy initialization. /// private static readonly Lazy Lazy = new Lazy(() => new QueryParamParser()); /// - /// Prevents a default instance of the class from being created. + /// Prevents a default instance of the class from being created. /// private QueryParamParser() { @@ -42,25 +42,22 @@ private QueryParamParser() } /// - /// Gets the current instance. + /// Gets the current instance. /// + /// + /// The instance. + /// public static QueryParamParser Instance => Lazy.Value; /// /// Parses the given string value converting it to the given type. /// - /// - /// The value to parse. - /// - /// - /// The to use as the current culture. - /// If not set will parse using - /// - /// - /// The to convert the string to. - /// + /// The to convert the string to. + /// The value to parse. + /// The to use as the current culture. + /// If not set will parse using /// - /// The . + /// The . /// public T ParseValue(string value, CultureInfo culture = null) { @@ -72,6 +69,12 @@ public T ParseValue(string value, CultureInfo culture = null) Type type = typeof(T); IQueryParamConverter converter = QueryTypeDescriptor.GetConverter(type); + if (converter == null && Nullable.GetUnderlyingType(type) is Type underlyingType) + { + type = underlyingType; + converter = QueryTypeDescriptor.GetConverter(type); + } + try { // ReSharper disable once AssignNullToNotNullAttribute @@ -87,12 +90,8 @@ public T ParseValue(string value, CultureInfo culture = null) /// /// Adds a type converter to the parser. /// - /// - /// The to add a converter for. - /// - /// - /// The type of to add. - /// + /// The to add a converter for. + /// The type of to add. public void AddTypeConverter(Type type, Type converterType) => QueryTypeDescriptor.AddConverter(type, converterType); /// @@ -108,15 +107,19 @@ public T ParseValue(string value, CultureInfo culture = null) /// /// Adds point converters. /// - private void AddPointConverters() => this.AddTypeConverter(typeof(Point), typeof(PointConverter)); + private void AddPointConverters() + { + this.AddTypeConverter(typeof(Point), typeof(PointConverter)); + this.AddTypeConverter(typeof(PointF), typeof(PointFConverter)); + } /// - /// Adds point converters. + /// Adds size converters. /// private void AddSizeConverters() => this.AddTypeConverter(typeof(Size), typeof(SizeConverter)); /// - /// Add the generic converters + /// Add the generic converters. /// private void AddGenericConverters() { diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 720e0c83c..897a404f8 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -72,6 +72,7 @@ + diff --git a/src/ImageProcessor.Web/Processors/Resize.cs b/src/ImageProcessor.Web/Processors/Resize.cs index 36d4f6208..10fe2a464 100644 --- a/src/ImageProcessor.Web/Processors/Resize.cs +++ b/src/ImageProcessor.Web/Processors/Resize.cs @@ -75,16 +75,19 @@ public int MatchRegexIndex(string queryString) ResizeMode mode = QueryParamParser.Instance.ParseValue(queryCollection["mode"]); AnchorPosition position = QueryParamParser.Instance.ParseValue(queryCollection["anchor"]); bool upscale = queryCollection["upscale"] == null || QueryParamParser.Instance.ParseValue(queryCollection["upscale"]); - float[] center = queryCollection["center"] != null - ? QueryParamParser.Instance.ParseValue(queryCollection["center"]) : - new float[] { }; + PointF? center = QueryParamParser.Instance.ParseValue(queryCollection["center"]); + if (center.HasValue) + { + // Swap X/Y for backwards compatibility + center = new PointF(center.Value.Y, center.Value.X); + } this.Processor.DynamicParameter = new ResizeLayer(size) { ResizeMode = mode, AnchorPosition = position, Upscale = upscale, - CenterCoordinates = center + Center = center }; // Correctly parse any restrictions. diff --git a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs index c6ea053e4..5f3bd5a65 100644 --- a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs @@ -41,7 +41,7 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.8.7.0")] -[assembly: AssemblyFileVersion("4.8.7.0")] +[assembly: AssemblyVersion("4.12.0.00000")] +[assembly: AssemblyFileVersion("4.12.0.00000")] [assembly: InternalsVisibleTo("ImageProcessor.Web.UnitTests")] diff --git a/src/ImageProcessor/Common/Extensions/FloatExtensions.cs b/src/ImageProcessor/Common/Extensions/FloatExtensions.cs index ab0a80ecc..b7067046f 100644 --- a/src/ImageProcessor/Common/Extensions/FloatExtensions.cs +++ b/src/ImageProcessor/Common/Extensions/FloatExtensions.cs @@ -32,6 +32,6 @@ public static class FloatExtensions /// /// The . /// - public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, 0, 255)); + public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, byte.MinValue, byte.MaxValue)); } } diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index b5c0f14d8..8837c9381 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -75,7 +75,7 @@ public class ImageFactory : IDisposable /// Whether to preserve exif metadata. Defaults to false. /// public ImageFactory(bool preserveExifData = false) - : this(preserveExifData, true) + : this(preserveExifData, false) { } @@ -119,7 +119,7 @@ public ImageFactory(MetaDataMode metaDataMode, bool fixGamma) } /// - /// Finalizes an instance of the ImageFactory class. + /// Finalizes an instance of the class. /// /// /// Use C# destructor syntax for finalization code. diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 1ad521cdb..aad9be49c 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -42,6 +42,9 @@ + + ..\..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + @@ -148,6 +151,7 @@ + @@ -202,5 +206,8 @@ + + + \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Colors/CmykColor.cs b/src/ImageProcessor/Imaging/Colors/CmykColor.cs index 1dbf47b4b..5a1f3d8d1 100644 --- a/src/ImageProcessor/Imaging/Colors/CmykColor.cs +++ b/src/ImageProcessor/Imaging/Colors/CmykColor.cs @@ -247,54 +247,49 @@ public override string ToString() } /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// Another object to compare to. public override bool Equals(object obj) => obj is CmykColor cmykColor && this.Equals(cmykColor); /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(CmykColor other) - { - Color thisColor = this; - Color otherColor = other; - return thisColor.Equals(otherColor); - } + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(CmykColor other) => + this.C == other.C + && this.M == other.M + && this.Y == other.Y + && this.K == other.K; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - Color thisColor = this; - return thisColor.GetHashCode(); - } + public override int GetHashCode() => (this.C, this.M, this.Y, this.K).GetHashCode(); /// /// Checks the range of the given value to ensure that it remains within the acceptable boundaries. /// - /// - /// The value to check. - /// + /// The value to check. /// - /// The sanitized . + /// The sanitized . /// private static float Clamp(float value) => ImageMaths.Clamp(value, 0, 100); /// - /// Returns a value indicating whether the current instance is empty. + /// Determines whether this instance is empty. /// /// - /// The true if this instance is empty; otherwise, false. + /// true if this instance is empty; otherwise, false. /// private bool IsEmpty() { diff --git a/src/ImageProcessor/Imaging/Colors/Color32.cs b/src/ImageProcessor/Imaging/Colors/Color32.cs index 4e01e031e..0e989c481 100644 --- a/src/ImageProcessor/Imaging/Colors/Color32.cs +++ b/src/ImageProcessor/Imaging/Colors/Color32.cs @@ -86,47 +86,29 @@ public Color32(int argb) public Color Color => Color.FromArgb(this.A, this.R, this.G, this.B); /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// Another object to compare to. public override bool Equals(object obj) => obj is Color32 color32 && this.Equals(color32); /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(Color32 other) => this.Argb.Equals(other.Argb); - - /// - /// Returns the hash code for this instance. - /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// true if the current object is equal to the parameter; otherwise, false. /// - public override int GetHashCode() => this.GetHashCode(this); + public bool Equals(Color32 other) => this.Argb == other.Argb; /// - /// Returns the hash code for the given instance. + /// Returns a hash code for this instance. /// - /// - /// The instance of to return the hash code for. - /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - private int GetHashCode(Color32 obj) - { - unchecked - { - int hashCode = obj.B.GetHashCode(); - hashCode = (hashCode * 397) ^ obj.G.GetHashCode(); - hashCode = (hashCode * 397) ^ obj.R.GetHashCode(); - return (hashCode * 397) ^ obj.A.GetHashCode(); - } - } + public override int GetHashCode() => this.Argb.GetHashCode(); } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs index 23e9483ff..d47f8eb79 100644 --- a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs +++ b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs @@ -242,37 +242,34 @@ public override string ToString() } /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// Another object to compare to. public override bool Equals(object obj) => obj is HslaColor hslaColor && this.Equals(hslaColor); /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(HslaColor other) - { - Color thisColor = this; - Color otherColor = other; - return thisColor.Equals(otherColor); - } + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(HslaColor other) => + this.H == other.H + && this.S == other.S + && this.L == other.L + && this.A == other.A; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - Color thisColor = this; - return thisColor.GetHashCode(); - } + public override int GetHashCode() => (this.H, this.S, this.L, this.A).GetHashCode(); /// /// Gets the color component from the given hue values. diff --git a/src/ImageProcessor/Imaging/Colors/RGBAColor.cs b/src/ImageProcessor/Imaging/Colors/RGBAColor.cs index 3a4bd6e3a..defa82056 100644 --- a/src/ImageProcessor/Imaging/Colors/RGBAColor.cs +++ b/src/ImageProcessor/Imaging/Colors/RGBAColor.cs @@ -190,36 +190,33 @@ public override string ToString() } /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// Another object to compare to. public override bool Equals(object obj) => obj is RgbaColor rgbaColor && this.Equals(rgbaColor); /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(RgbaColor other) - { - Color thisColor = this; - Color otherColor = other; - return thisColor.Equals(otherColor); - } + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(RgbaColor other) => + this.R == other.R + && this.G == other.G + && this.B == other.B + && this.A == other.A; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - Color thisColor = this; - return thisColor.GetHashCode(); - } + public override int GetHashCode() => (this.R, this.G, this.B, this.A).GetHashCode(); } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs index 7c969c001..957e6cede 100644 --- a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs +++ b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs @@ -168,37 +168,33 @@ public override string ToString() } /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// Another object to compare to. public override bool Equals(object obj) => obj is YCbCrColor yCbCrColor && this.Equals(yCbCrColor); /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(YCbCrColor other) - { - Color thisColor = this; - Color otherColor = other; - return thisColor.Equals(otherColor); - } + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(YCbCrColor other) => + this.Y == other.Y + && this.Cb == other.Cb + && this.Cr == other.Cr; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - Color thisColor = this; - return thisColor.GetHashCode(); - } + public override int GetHashCode() => (this.Y, this.Cb, this.Cr).GetHashCode(); /// /// Returns a value indicating whether the current instance is empty. diff --git a/src/ImageProcessor/Imaging/CropLayer.cs b/src/ImageProcessor/Imaging/CropLayer.cs index f1fdee181..7582201e8 100644 --- a/src/ImageProcessor/Imaging/CropLayer.cs +++ b/src/ImageProcessor/Imaging/CropLayer.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging /// /// Encapsulates the properties required to crop an image. /// - public class CropLayer + public class CropLayer : IEquatable { /// /// Initializes a new instance of the class. @@ -31,9 +31,24 @@ public class CropLayer /// public CropLayer(float left, float top, float right, float bottom, CropMode cropMode = CropMode.Percentage) { - if (left < 0 || top < 0 || right < 0 || bottom < 0) + if (left < 0) { - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(left)); + } + + if (top < 0) + { + throw new ArgumentOutOfRangeException(nameof(top)); + } + + if (right < 0) + { + throw new ArgumentOutOfRangeException(nameof(right)); + } + + if (bottom < 0) + { + throw new ArgumentOutOfRangeException(nameof(bottom)); } this.Left = left; @@ -69,47 +84,34 @@ public CropLayer(float left, float top, float right, float bottom, CropMode crop public CropMode CropMode { get; set; } /// - /// Determines whether the specified , is - /// equal to this instance. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The to compare with this instance. - /// + /// The to compare with this instance. /// - /// true if the specified is equal to - /// this instance; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is CropLayer cropLayer)) - { - return false; - } + public override bool Equals(object obj) => obj is CropLayer cropLayer && this.Equals(cropLayer); - // Define the tolerance for variation in their values - return Math.Abs(this.Top - cropLayer.Top) <= Math.Abs(this.Top * .0001) - && Math.Abs(this.Right - cropLayer.Right) <= Math.Abs(this.Right * .0001) - && Math.Abs(this.Bottom - cropLayer.Bottom) <= Math.Abs(this.Bottom * .0001) - && Math.Abs(this.Left - cropLayer.Left) <= Math.Abs(this.Left * .0001) - && this.CropMode.Equals(cropLayer.CropMode); - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(CropLayer other) => other != null + && this.Left == other.Left + && this.Top == other.Top + && this.Right == other.Right + && this.Bottom == other.Bottom + && this.CropMode == other.CropMode; /// - /// Serves as a hash function for a particular type. + /// Returns a hash code for this instance. /// /// - /// A hash code for the current . + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Left.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Top.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Right.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Bottom.GetHashCode(); - return (hashCode * 397) ^ (int)this.CropMode; - } - } + public override int GetHashCode() => (this.Left, this.Top, this.Right, this.Bottom, this.CropMode).GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/FastBitmap.cs b/src/ImageProcessor/Imaging/FastBitmap.cs index 02b0bd2b4..d9c2080fc 100644 --- a/src/ImageProcessor/Imaging/FastBitmap.cs +++ b/src/ImageProcessor/Imaging/FastBitmap.cs @@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging /// /// Allows fast access to 's pixel data. /// - public unsafe class FastBitmap : IDisposable + public unsafe class FastBitmap : IDisposable, IEquatable { /// /// The integral representation of the 8bppIndexed pixel format. @@ -400,27 +400,29 @@ public void Dispose() } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if the specified object is equal to the current object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// The object to compare with the current object. - public override bool Equals(object obj) - { - if (!(obj is FastBitmap fastBitmap)) - { - return false; - } + public override bool Equals(object obj) => obj is FastBitmap fastBitmap && this.Equals(fastBitmap); - return this.bitmap == fastBitmap.bitmap; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(FastBitmap other) => other != null + && this.bitmap == other.bitmap; /// - /// Serves as a hash function for a particular type. + /// Returns a hash code for this instance. /// /// - /// A hash code for the current . + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() => this.bitmap.GetHashCode(); diff --git a/src/ImageProcessor/Imaging/Filters/Photo/MatrixFilterBase.cs b/src/ImageProcessor/Imaging/Filters/Photo/MatrixFilterBase.cs index 9bb760f83..7c37cb4f4 100644 --- a/src/ImageProcessor/Imaging/Filters/Photo/MatrixFilterBase.cs +++ b/src/ImageProcessor/Imaging/Filters/Photo/MatrixFilterBase.cs @@ -10,13 +10,14 @@ namespace ImageProcessor.Imaging.Filters.Photo { + using System; using System.Drawing; using System.Drawing.Imaging; /// /// The matrix filter base contains equality methods. /// - public abstract class MatrixFilterBase : IMatrixFilter + public abstract class MatrixFilterBase : IMatrixFilter, IEquatable { /// /// Gets the for this filter instance. @@ -29,41 +30,88 @@ public abstract class MatrixFilterBase : IMatrixFilter /// The current image to process /// The new image to return /// - /// The processed . + /// The processed . /// public abstract Bitmap TransformImage(Image source, Image destination); /// - /// Determines whether the specified , is equal to this instance. + /// Determines whether the specified , is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// - /// true if the specified is equal to this instance; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is IMatrixFilter filter)) - { - return false; - } + public override bool Equals(object obj) => obj is IMatrixFilter matrixFilter && this.Equals(matrixFilter); - return this.GetType().Name == filter.GetType().Name - && this.Matrix.Equals(filter.Matrix); - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(IMatrixFilter other) => other != null + && this.GetType() == other.GetType() + && (this.Matrix == null || other.Matrix == null ? this.Matrix == other.Matrix : ( + this.Matrix.Matrix00 == other.Matrix.Matrix00 + && this.Matrix.Matrix01 == other.Matrix.Matrix01 + && this.Matrix.Matrix02 == other.Matrix.Matrix02 + && this.Matrix.Matrix03 == other.Matrix.Matrix03 + && this.Matrix.Matrix04 == other.Matrix.Matrix04 + && this.Matrix.Matrix10 == other.Matrix.Matrix10 + && this.Matrix.Matrix11 == other.Matrix.Matrix11 + && this.Matrix.Matrix12 == other.Matrix.Matrix12 + && this.Matrix.Matrix13 == other.Matrix.Matrix13 + && this.Matrix.Matrix14 == other.Matrix.Matrix14 + && this.Matrix.Matrix20 == other.Matrix.Matrix20 + && this.Matrix.Matrix21 == other.Matrix.Matrix21 + && this.Matrix.Matrix22 == other.Matrix.Matrix22 + && this.Matrix.Matrix23 == other.Matrix.Matrix23 + && this.Matrix.Matrix24 == other.Matrix.Matrix24 + && this.Matrix.Matrix30 == other.Matrix.Matrix30 + && this.Matrix.Matrix31 == other.Matrix.Matrix31 + && this.Matrix.Matrix32 == other.Matrix.Matrix32 + && this.Matrix.Matrix33 == other.Matrix.Matrix33 + && this.Matrix.Matrix34 == other.Matrix.Matrix34 + && this.Matrix.Matrix40 == other.Matrix.Matrix40 + && this.Matrix.Matrix41 == other.Matrix.Matrix41 + && this.Matrix.Matrix42 == other.Matrix.Matrix42 + && this.Matrix.Matrix43 == other.Matrix.Matrix43 + && this.Matrix.Matrix44 == other.Matrix.Matrix44 + )); /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.GetType().Name.GetHashCode(); - return (hashCode * 397) ^ this.Matrix.GetHashCode(); - } - } + public override int GetHashCode() => ( + this.GetType(), + this.Matrix?.Matrix00, + this.Matrix?.Matrix01, + this.Matrix?.Matrix02, + this.Matrix?.Matrix03, + this.Matrix?.Matrix04, + this.Matrix?.Matrix10, + this.Matrix?.Matrix11, + this.Matrix?.Matrix12, + this.Matrix?.Matrix13, + this.Matrix?.Matrix14, + this.Matrix?.Matrix20, + this.Matrix?.Matrix21, + this.Matrix?.Matrix22, + this.Matrix?.Matrix23, + this.Matrix?.Matrix24, + this.Matrix?.Matrix30, + this.Matrix?.Matrix31, + this.Matrix?.Matrix32, + this.Matrix?.Matrix33, + this.Matrix?.Matrix34, + this.Matrix?.Matrix40, + this.Matrix?.Matrix41, + this.Matrix?.Matrix42, + this.Matrix?.Matrix43, + this.Matrix?.Matrix44).GetHashCode(); } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Formats/FormatBase.cs b/src/ImageProcessor/Imaging/Formats/FormatBase.cs index d5e582243..4651b865c 100644 --- a/src/ImageProcessor/Imaging/Formats/FormatBase.cs +++ b/src/ImageProcessor/Imaging/Formats/FormatBase.cs @@ -18,7 +18,7 @@ namespace ImageProcessor.Imaging.Formats /// /// The supported format base. Implement this class when building a supported format. /// - public abstract class FormatBase : ISupportedImageFormat + public abstract class FormatBase : ISupportedImageFormat, IEquatable { /// /// Initializes a new instance of the class. @@ -123,36 +123,31 @@ public virtual Image Save(string path, Image image, long bitDepth) } /// - /// Determines whether the specified , is equal to this instance. + /// Determines whether the specified , is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// - /// true if the specified is equal to this instance; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is ISupportedImageFormat format)) - { - return false; - } + public override bool Equals(object obj) => obj is ISupportedImageFormat format && this.Equals(format); - return this.MimeType.Equals(format.MimeType) && this.IsIndexed.Equals(format.IsIndexed); - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(ISupportedImageFormat other) => other != null + && this.MimeType == other.MimeType + && this.IsIndexed == other.IsIndexed; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.MimeType.GetHashCode(); - hashCode = (hashCode * 397) ^ this.IsIndexed.GetHashCode(); - return (hashCode * 397) ^ this.Quality; - } - } + public override int GetHashCode() => (this.MimeType, this.IsIndexed).GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/GaussianLayer.cs b/src/ImageProcessor/Imaging/GaussianLayer.cs index 15d45b15a..55668d397 100644 --- a/src/ImageProcessor/Imaging/GaussianLayer.cs +++ b/src/ImageProcessor/Imaging/GaussianLayer.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging /// /// A Gaussian layer for applying sharpening and blurring methods to an image. /// - public class GaussianLayer + public class GaussianLayer : IEquatable { /// /// The size. @@ -131,43 +131,32 @@ public int Threshold } /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is GaussianLayer gaussianLayer)) - { - return false; - } + public override bool Equals(object obj) => obj is GaussianLayer gaussianLayer && this.Equals(gaussianLayer); - return this.Size == gaussianLayer.Size - && Math.Abs(this.Sigma - gaussianLayer.Sigma) < 0.0001 - && this.Threshold == gaussianLayer.Threshold; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(GaussianLayer other) => other != null + && this.Size == other.Size + && this.Sigma == other.Sigma + && this.Threshold == other.Threshold; /// - /// Serves as a hash function for a particular type. + /// Returns a hash code for this instance. /// /// - /// A hash code for the current . + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Size; - hashCode = (hashCode * 397) ^ this.Size.GetHashCode(); - return (hashCode * 397) ^ this.Threshold; - } - } + public override int GetHashCode() => (this.Size, this.Sigma, this.Threshold).GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs index bf60cd9ab..1a42df3b5 100644 --- a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs +++ b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs @@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging.Helpers using System; using System.Drawing; using System.Drawing.Imaging; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageProcessor.Common.Extensions; @@ -329,7 +330,7 @@ private static byte[] GetLinearBytes() byte[] ramp = new byte[256]; for (int x = 0; x < 256; ++x) { - ramp[x] = (255f * SRGBToLinear(x / 255f)).ToByte(); + ramp[x] = ((255 * SRGBToLinear(x / 255D)) + .5).ToByte(); } return ramp; @@ -347,7 +348,7 @@ private static byte[] GetSRGBBytes() byte[] ramp = new byte[256]; for (int x = 0; x < 256; ++x) { - ramp[x] = (255f * LinearToSRGB(x / 255f)).ToByte(); + ramp[x] = ((255 * LinearToSRGB(x / 255D)) + .5).ToByte(); } return ramp; @@ -362,16 +363,15 @@ private static byte[] GetSRGBBytes() /// /// The . /// - private static float SRGBToLinear(float signal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double SRGBToLinear(double signal) { - const float a = 0.055f; - if (signal <= 0.04045) { - return signal / 12.92f; + return signal / 12.92; } - return (float)Math.Pow((signal + a) / (1 + a), 2.4); + return Math.Pow((signal + 0.055) / 1.055, 2.4); } /// @@ -381,18 +381,17 @@ private static float SRGBToLinear(float signal) /// /// The signal value to convert. /// - /// The . + /// The . /// - private static float LinearToSRGB(float signal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double LinearToSRGB(double signal) { - const float a = 0.055f; - - if (signal <= 0.0031308) + if (signal <= (0.04045 / 12.92)) { - return signal * 12.92f; + return signal * 12.92; } - return ((float)((1 + a) * Math.Pow(signal, 0.4166667F))) - a; + return (1.055 * Math.Pow(signal, 1.0 / 2.4)) - 0.055; } } } diff --git a/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs b/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs index 8c1360fb9..bc8eac7c6 100644 --- a/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs +++ b/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs @@ -28,15 +28,15 @@ public static void SetGraphicsOptions(Graphics graphics, bool blending = false, { // Highest quality resampling algorithm. graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - - // Ensure pixel offset is set. - graphics.PixelOffsetMode = PixelOffsetMode.Half; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; if (smoothing) { // We want smooth edges when drawing. graphics.SmoothingMode = SmoothingMode.AntiAlias; } + if (blending || smoothing) { // Best combination for blending pixels. diff --git a/src/ImageProcessor/Imaging/ImageLayer.cs b/src/ImageProcessor/Imaging/ImageLayer.cs index 99bd9e57a..578dc04f7 100644 --- a/src/ImageProcessor/Imaging/ImageLayer.cs +++ b/src/ImageProcessor/Imaging/ImageLayer.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging /// /// Encapsulates the properties required to add an image layer to an image. /// - public class ImageLayer : IDisposable + public class ImageLayer : IDisposable, IEquatable { /// /// A value indicating whether this instance of the given entity has been disposed. @@ -51,46 +51,34 @@ public class ImageLayer : IDisposable public Point? Position { get; set; } /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is ImageLayer imageLayer)) - { - return false; - } + public override bool Equals(object obj) => obj is ImageLayer imageLayer && this.Equals(imageLayer); - return this.Image == imageLayer.Image - && this.Size == imageLayer.Size - && this.Opacity == imageLayer.Opacity - && this.Position == imageLayer.Position; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(ImageLayer other) => other != null + && this.Image == other.Image + && this.Size == other.Size + && this.Opacity == other.Opacity + && this.Position == other.Position; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Image.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Size.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Opacity; - return (hashCode * 397) ^ this.Position.GetHashCode(); - } - } + public override int GetHashCode() => (this.Image, this.Size, this.Opacity, this.Position).GetHashCode(); /// /// Disposes the object and frees resources for the Garbage Collector. diff --git a/src/ImageProcessor/Imaging/MetaData/Rational.cs b/src/ImageProcessor/Imaging/MetaData/Rational.cs index 32bd97adf..499842290 100644 --- a/src/ImageProcessor/Imaging/MetaData/Rational.cs +++ b/src/ImageProcessor/Imaging/MetaData/Rational.cs @@ -1000,27 +1000,20 @@ public int CompareTo(object obj) public override string ToString() => Convert.ToString(this, CultureInfo.InvariantCulture); /// - /// Indicates whether this instance and a specified object are equal. + /// Determines whether the specified , is equal to this instance. /// + /// The to compare with this instance. /// - /// true if and this instance are the same type and represent the same value; - /// otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - /// The object to compare with the current instance. public override bool Equals(object obj) => this.CompareTo(obj) == 0; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - // Adapted from Anonymous Type: { uint Numerator, uint Denominator } - int num = 0x1fb8d67d; - num = (-1521134295 * num) + EqualityComparer.Default.GetHashCode(this.numerator); - return (-1521134295 * num) + EqualityComparer.Default.GetHashCode(this.denominator); - } + public override int GetHashCode() => (this.Numerator, this.Denominator).GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/ResizeHelper.cs b/src/ImageProcessor/Imaging/ResizeHelper.cs new file mode 100644 index 000000000..1a726a1fe --- /dev/null +++ b/src/ImageProcessor/Imaging/ResizeHelper.cs @@ -0,0 +1,394 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South. +// Licensed under the Apache License, Version 2.0. +// +// +// Resizes an image to the given dimensions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Drawing; + +namespace ImageProcessor.Imaging +{ + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The source image size. + /// The resize options. + /// The target width. + /// The target height. + /// + /// The tuple representing the location and the bounds. + /// + public static (Size, Rectangle) CalculateTargetLocationAndBounds( + Size sourceSize, + ResizeLayer options, + int width, + int height) + { + if (width <= 0 && height <= 0) + { + ThrowInvalid($"Target width {width} and height {height} must be greater than zero."); + } + + switch (options.ResizeMode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(sourceSize, options, width, height); + case ResizeMode.Pad: + return CalculatePadRectangle(sourceSize, options, width, height); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(sourceSize, options, width, height); + case ResizeMode.Max: + return CalculateMaxRectangle(sourceSize, width, height); + case ResizeMode.Min: + return CalculateMinRectangle(sourceSize, width, height); + + // Last case ResizeMode.Stretch: + default: + return (new Size(width, height), new Rectangle(0, 0, width, height)); + } + } + + private static (Size, Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeLayer options, + int width, + int height) + { + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } + + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + int boxPadHeight = height > 0 ? height : (int)Math.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)Math.Round(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.AnchorPosition) + { + case AnchorPosition.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPosition.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPosition.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPosition.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.BottomLeft: + destinationY = height - sourceHeight; + destinationX = 0; + break; + default: + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; + break; + } + + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); + } + + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options, width, height); + } + + private static (Size, Rectangle) CalculateCropRectangle( + Size source, + ResizeLayer options, + int width, + int height) + { + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.Center is var center && center.HasValue) + { + float centerRatio = -(ratio * sourceHeight) * center.Value.Y; + targetY = (int)Math.Round(centerRatio + (height / 2F)); + + if (targetY > 0) + { + targetY = 0; + } + + if (targetY < (int)Math.Round(height - (sourceHeight * ratio))) + { + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.AnchorPosition) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + targetY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + break; + default: + targetY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } + + targetHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.Center is var center && center.HasValue) + { + float centerRatio = -(ratio * sourceWidth) * center.Value.X; + targetX = (int)Math.Round(centerRatio + (width / 2F)); + + if (targetX > 0) + { + targetX = 0; + } + + if (targetX < (int)Math.Round(width - (sourceWidth * ratio))) + { + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.AnchorPosition) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + targetX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + break; + default: + targetX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + + targetWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + + // Target image width and height can be different to the rectangle width and height. + return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculateMaxRectangle( + Size source, + int width, + int height) + { + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)source.Height); + float percentWidth = Math.Abs(width / (float)source.Width); + + // Integers must be cast to floats to get needed precision + float ratio = height / (float)width; + float sourceRatio = source.Height / (float)source.Width; + + if (sourceRatio < ratio) + { + targetHeight = (int)Math.Round(source.Height * percentWidth); + } + else + { + targetWidth = (int)Math.Round(source.Width * percentHeight); + } + + // Replace the size to match the rectangle. + return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int targetWidth = width; + int targetHeight = height; + + // Don't upscale + if (width > sourceWidth || height > sourceHeight) + { + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); + } + + // Find the shortest distance to go. + int widthDiff = sourceWidth - width; + int heightDiff = sourceHeight - height; + + if (widthDiff < heightDiff) + { + float sourceRatio = (float)sourceHeight / sourceWidth; + targetHeight = (int)Math.Round(width * sourceRatio); + } + else if (widthDiff > heightDiff) + { + float sourceRatioInverse = (float)sourceWidth / sourceHeight; + targetWidth = (int)Math.Round(height * sourceRatioInverse); + } + else + { + if (height > width) + { + float percentWidth = Math.Abs(width / (float)sourceWidth); + targetHeight = (int)Math.Round(sourceHeight * percentWidth); + } + else + { + float percentHeight = Math.Abs(height / (float)sourceHeight); + targetWidth = (int)Math.Round(sourceWidth * percentHeight); + } + } + + // Replace the size to match the rectangle. + return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculatePadRectangle( + Size sourceSize, + ResizeLayer options, + int width, + int height) + { + float ratio; + int sourceWidth = sourceSize.Width; + int sourceHeight = sourceSize.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + targetWidth = (int)Math.Round(sourceWidth * percentHeight); + + switch (options.AnchorPosition) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + targetX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + break; + default: + targetX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + else + { + ratio = percentWidth; + targetHeight = (int)Math.Round(sourceHeight * percentWidth); + + switch (options.AnchorPosition) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + targetY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + break; + default: + targetY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } + + // Target image width and height can be different to the rectangle width and height. + return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight)); + } + + private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + } +} diff --git a/src/ImageProcessor/Imaging/ResizeLayer.cs b/src/ImageProcessor/Imaging/ResizeLayer.cs index 1c3b81271..94c01d7e2 100644 --- a/src/ImageProcessor/Imaging/ResizeLayer.cs +++ b/src/ImageProcessor/Imaging/ResizeLayer.cs @@ -10,6 +10,7 @@ namespace ImageProcessor.Imaging { + using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -17,37 +18,20 @@ namespace ImageProcessor.Imaging /// /// Encapsulates the properties required to resize an image. /// - public class ResizeLayer + /// + public class ResizeLayer : IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// The containing the width and height to set the image to. - /// - /// - /// The resize mode to apply to resized image. (Default ResizeMode.Pad) - /// - /// - /// The to apply to resized image. (Default AnchorPosition.Center) - /// - /// - /// Whether to allow up-scaling of images. (Default true) - /// - /// - /// The center coordinates (Default null) - /// - /// - /// The maximum size to resize an image to. - /// Used to restrict resizing based on calculated resizing - /// - /// - /// The range of sizes to restrict resizing an image to. - /// Used to restrict resizing based on calculated resizing - /// - /// - /// The anchor point (Default null) - /// + /// The containing the width and height to resize the image to. + /// The resize mode to apply to the resized image. + /// The anchor position to apply to the resized image. + /// Whether to allow up-scaling of images. + /// The center coordinates (Y,X). + /// The maximum size to resize an image to. Used to restrict resizing based on calculated resizing. + /// The range of sizes to restrict resizing an image to. Used to restrict resizing based on calculated resizing. + /// The anchor point. public ResizeLayer( Size size, ResizeMode resizeMode = ResizeMode.Pad, @@ -62,107 +46,138 @@ public ResizeLayer( this.Upscale = upscale; this.ResizeMode = resizeMode; this.AnchorPosition = anchorPosition; - this.CenterCoordinates = centerCoordinates ?? new float[] { }; + if (centerCoordinates != null && centerCoordinates.Length == 2) + { + this.Center = new PointF(centerCoordinates[1], centerCoordinates[0]); + } this.MaxSize = maxSize; - this.RestrictedSizes = restrictedSizes ?? new List(); + this.RestrictedSizes = restrictedSizes; this.AnchorPoint = anchorPoint; } /// /// Gets or sets the size. /// + /// + /// The size. + /// public Size Size { get; set; } /// - /// Gets or sets the max size. + /// Gets or sets the maximum size. /// + /// + /// The maximum size. + /// public Size? MaxSize { get; set; } /// - /// Gets or sets the restricted range of sizes. to restrict resizing methods to. + /// Gets or sets the restricted range of sizes to restrict resizing methods to. /// + /// + /// The restricted sizes. + /// public List RestrictedSizes { get; set; } /// /// Gets or sets the resize mode. /// + /// + /// The resize mode. + /// public ResizeMode ResizeMode { get; set; } /// /// Gets or sets the anchor position. /// + /// + /// The anchor position. + /// public AnchorPosition AnchorPosition { get; set; } /// /// Gets or sets a value indicating whether to allow up-scaling of images. - /// For this is always true. + /// For this is always true. /// + /// + /// true if up-scaling is allowed; otherwise, false. + /// public bool Upscale { get; set; } /// - /// Gets or sets the center coordinates. + /// Gets or sets the center coordinates (Y,X). + /// + /// + /// The center coordinates (Y,X). + /// + [Obsolete("Use the Center property instead.")] + public float[] CenterCoordinates + { + get + { + return this.Center is PointF center ? new float[] { center.Y, center.X } : null; + } + set + { + if (value != null && value.Length == 2) + { + this.Center = new PointF(value[1], value[0]); + } + else + { + this.Center = null; + } + } + } + + /// + /// Gets the center coordinates as . /// - public float[] CenterCoordinates { get; set; } + /// + /// The center coordinates as . + /// + public PointF? Center { get; set; } /// /// Gets or sets the anchor point. /// + /// + /// The anchor point. + /// public Point? AnchorPoint { get; set; } /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is ResizeLayer resizeLayer)) - { - return false; - } + public override bool Equals(object obj) => obj is ResizeLayer resizeLayer && this.Equals(resizeLayer); - return this.Size == resizeLayer.Size - && this.ResizeMode == resizeLayer.ResizeMode - && this.AnchorPosition == resizeLayer.AnchorPosition - && this.Upscale == resizeLayer.Upscale - && ((this.CenterCoordinates != null - && resizeLayer.CenterCoordinates != null - && this.CenterCoordinates.SequenceEqual(resizeLayer.CenterCoordinates)) - || (this.CenterCoordinates == resizeLayer.CenterCoordinates)) - && this.MaxSize == resizeLayer.MaxSize - && ((this.RestrictedSizes != null - && resizeLayer.RestrictedSizes != null - && this.RestrictedSizes.SequenceEqual(resizeLayer.RestrictedSizes)) - || (this.RestrictedSizes == resizeLayer.RestrictedSizes)) - && this.AnchorPoint == resizeLayer.AnchorPoint; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(ResizeLayer other) => other != null + && this.Size == other.Size + && this.MaxSize == other.MaxSize + && (this.RestrictedSizes == null || other.RestrictedSizes == null ? this.RestrictedSizes == other.RestrictedSizes : this.RestrictedSizes.SequenceEqual(other.RestrictedSizes)) + && this.ResizeMode == other.ResizeMode + && this.AnchorPosition == other.AnchorPosition + && this.Upscale == other.Upscale + && this.Center == other.Center + && this.AnchorPoint == other.AnchorPoint; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Size.GetHashCode(); - hashCode = (hashCode * 397) ^ this.MaxSize.GetHashCode(); - hashCode = (hashCode * 397) ^ (this.RestrictedSizes?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (int)this.ResizeMode; - hashCode = (hashCode * 397) ^ (int)this.AnchorPosition; - hashCode = (hashCode * 397) ^ this.Upscale.GetHashCode(); - hashCode = (hashCode * 397) ^ (this.CenterCoordinates?.GetHashCode() ?? 0); - return (hashCode * 397) ^ this.AnchorPoint.GetHashCode(); - } - } + public override int GetHashCode() => (this.Size, this.MaxSize, this.ResizeMode, this.AnchorPosition, this.Upscale, this.Center, this.AnchorPoint).GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/ImageProcessor/Imaging/Resizer.cs b/src/ImageProcessor/Imaging/Resizer.cs index cdfef07e7..1b53f89d5 100644 --- a/src/ImageProcessor/Imaging/Resizer.cs +++ b/src/ImageProcessor/Imaging/Resizer.cs @@ -10,17 +10,16 @@ namespace ImageProcessor.Imaging { + using ImageProcessor.Common.Exceptions; + using ImageProcessor.Common.Extensions; + using ImageProcessor.Imaging.Formats; + using ImageProcessor.Imaging.Helpers; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; - using ImageProcessor.Common.Exceptions; - using ImageProcessor.Common.Extensions; - using ImageProcessor.Imaging.Formats; - using ImageProcessor.Imaging.Helpers; - /// /// Provides methods to resize images. /// @@ -65,472 +64,54 @@ public class Resizer /// /// The resized . /// - public Bitmap ResizeImage(Image source, bool linear) - { - int width = this.ResizeLayer.Size.Width; - int height = this.ResizeLayer.Size.Height; - ResizeMode mode = this.ResizeLayer.ResizeMode; - AnchorPosition anchor = this.ResizeLayer.AnchorPosition; - bool upscale = this.ResizeLayer.Upscale; - float[] centerCoordinates = this.ResizeLayer.CenterCoordinates; - int maxWidth = this.ResizeLayer.MaxSize?.Width ?? int.MaxValue; - int maxHeight = this.ResizeLayer.MaxSize?.Height ?? int.MaxValue; - List restrictedSizes = this.ResizeLayer.RestrictedSizes; - Point? anchorPoint = this.ResizeLayer.AnchorPoint; - - return this.ResizeImage(source, width, height, maxWidth, maxHeight, restrictedSizes, mode, anchor, upscale, centerCoordinates, linear, anchorPoint); - } - - /// - /// Gets an image resized using the composite color space without any gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// - /// The . - /// - protected virtual Bitmap ResizeComposite(Image source, int width, int height, Rectangle destination) - { - var resized = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); - - using (var graphics = Graphics.FromImage(resized)) - { - GraphicsHelper.SetGraphicsOptions(graphics); - using (var attributes = new ImageAttributes()) - { - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(source, destination, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes); - } - } - - return resized; - } - - /// - /// Gets an image resized using the linear color space with gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// - /// The . - /// - protected virtual Bitmap ResizeLinear(Image source, int width, int height, Rectangle destination) => this.ResizeLinear(source, width, height, destination, this.AnimationProcessMode); - - /// - /// Gets an image resized using the linear color space with gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// The process mode for frames in animated images. - /// - /// The . - /// - protected virtual Bitmap ResizeLinear(Image source, int width, int height, Rectangle destination, AnimationProcessMode animationProcessMode) - { - // Adjust the gamma value so that the image is in the linear color space. - Bitmap linear = Adjustments.ToLinear(source.Copy(animationProcessMode)); - - var resized = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); - - using (var graphics = Graphics.FromImage(resized)) - { - GraphicsHelper.SetGraphicsOptions(graphics); - using (var attributes = new ImageAttributes()) - { - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(linear, destination, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes); - } - } - - // Return to composite color space. - resized = Adjustments.ToSRGB(resized); - - linear.Dispose(); - return resized; - } - - /// - /// Resizes the given image. - /// - /// The source to resize - /// The width to resize the image to. - /// The height to resize the image to. - /// The default max width to resize the image to. - /// The default max height to resize the image to. - /// A containing image resizing restrictions. - /// The mode with which to resize the image. - /// The anchor position to place the image at. - /// Whether to allow up-scaling of images. (Default true) - /// - /// If the resize mode is crop, you can set a specific center coordinate, use as alternative to anchorPosition - /// - /// Whether to resize the image using the linear color space. - /// - /// If resize mode is box pad, you can set a specific anchor coordinate, use as alternative to anchorPosition. - /// - /// - /// The resized . - /// - private Bitmap ResizeImage( + public Bitmap ResizeImage( Image source, - int width, - int height, - int maxWidth, - int maxHeight, - List restrictedSizes, - ResizeMode resizeMode = ResizeMode.Pad, - AnchorPosition anchorPosition = AnchorPosition.Center, - bool upscale = true, - float[] centerCoordinates = null, - bool linear = false, - Point? anchorPoint = null) + bool linear) { Bitmap newImage = null; try { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationWidth = width; - int destinationHeight = height; - - maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue; - maxHeight = maxHeight > 0 ? maxHeight : int.MaxValue; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - int destinationX = 0; - int destinationY = 0; - - // Change the destination rectangle coordinates if box padding. - if (resizeMode == ResizeMode.BoxPad) + Size sourceSize = source.Size; + int targetWidth = this.ResizeLayer.Size.Width; + int targetHeight = this.ResizeLayer.Size.Height; + int maxWidth = this.ResizeLayer.MaxSize?.Width ?? int.MaxValue; + int maxHeight = this.ResizeLayer.MaxSize?.Height ?? int.MaxValue; + + // Ensure size is populated across both dimensions. + // These dimensions are used to calculate the final dimensions determined by the mode algorithm. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int Min = 1; + if (targetWidth == 0 && targetHeight > 0) { - int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - destinationWidth = sourceWidth; - destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - upscale = true; - - if (anchorPoint.HasValue) - { - if (anchorPoint.Value.Y < 0) - { - destinationY = 0; - } - else if (anchorPoint.Value.Y + sourceHeight > boxPadHeight) - { - destinationY = boxPadHeight - sourceHeight; - } - else - { - destinationY = anchorPoint.Value.Y; - } - - if (anchorPoint.Value.X < 0) - { - destinationX = 0; - } - else if (anchorPoint.Value.X + sourceWidth > boxPadWidth) - { - destinationX = boxPadWidth - sourceWidth; - } - else - { - destinationX = anchorPoint.Value.X; - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Left: - destinationY = (height - sourceHeight) / 2; - destinationX = 0; - break; - case AnchorPosition.Right: - destinationY = (height - sourceHeight) / 2; - destinationX = width - sourceWidth; - break; - case AnchorPosition.TopRight: - destinationY = 0; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Top: - destinationY = 0; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.TopLeft: - destinationY = 0; - destinationX = 0; - break; - case AnchorPosition.BottomRight: - destinationY = height - sourceHeight; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Bottom: - destinationY = height - sourceHeight; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.BottomLeft: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - } - } - else - { - // Switch to pad mode to downscale and calculate from there. - resizeMode = ResizeMode.Pad; - } + targetWidth = (int)Math.Max(Min, Math.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height)); } - // Change the destination rectangle coordinates if padding and - // there has been a set width and height. - if (resizeMode == ResizeMode.Pad && width > 0 && height > 0) + if (targetHeight == 0 && targetWidth > 0) { - double ratio; - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - - switch (anchorPosition) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); - break; - } - } - else - { - ratio = percentWidth; - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - - switch (anchorPosition) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } + targetHeight = (int)Math.Max(Min, Math.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); } - // Change the destination rectangle coordinates if cropping and - // there has been a set width and height. - if (resizeMode == ResizeMode.Crop && width > 0 && height > 0) - { - double ratio; - - if (percentHeight < percentWidth) - { - ratio = percentWidth; - - if (centerCoordinates?.Length > 0) - { - double center = -(ratio * sourceHeight) * centerCoordinates[0]; - destinationY = (int)center + (height / 2); - - if (destinationY > 0) - { - destinationY = 0; - } - - if (destinationY < (int)(height - (sourceHeight * ratio))) - { - destinationY = (int)(height - (sourceHeight * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); - } - else - { - ratio = percentHeight; - - if (centerCoordinates?.Length > 0) - { - double center = -(ratio * sourceWidth) * centerCoordinates[1]; - destinationX = (int)center + (width / 2); - - if (destinationX > 0) - { - destinationX = 0; - } - - if (destinationX < (int)(width - (sourceWidth * ratio))) - { - destinationX = (int)(width - (sourceWidth * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); - break; - } - } - - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); - } - } + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(source.Size, this.ResizeLayer, targetWidth, targetHeight); - // Constrain the image to fit the maximum possible height or width. - if (resizeMode == ResizeMode.Max) - { - // If either is 0, we don't need to figure out orientation - if (width > 0 && height > 0) - { - // Integers must be cast to doubles to get needed precision - double ratio = (double)height / width; - double sourceRatio = (double)sourceHeight / sourceWidth; - - if (sourceRatio < ratio) - { - height = 0; - } - else - { - width = 0; - } - } - } + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int width = size.Width; + int height = size.Height; + bool upscale = this.ResizeLayer.Upscale; - // Resize the image until the shortest side reaches the set given dimension. - if (resizeMode == ResizeMode.Min) + if (ResizeLayer.ResizeMode == ResizeMode.Min) { - height = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - width = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - double sourceRatio = (double)sourceHeight / sourceWidth; - // Ensure we can't upscale. maxHeight = sourceHeight; maxWidth = sourceWidth; upscale = false; - - // Find the shortest distance to go. - int widthDiff = sourceWidth - width; - int heightDiff = sourceHeight - height; - - if (widthDiff < heightDiff) - { - destinationHeight = Convert.ToInt32(width * sourceRatio); - height = destinationHeight; - destinationWidth = width; - } - else if (widthDiff > heightDiff) - { - destinationHeight = height; - destinationWidth = Convert.ToInt32(height / sourceRatio); - width = destinationWidth; - } - else - { - if (height > width) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - height = destinationHeight; - } - else if (width > height) - { - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - width = destinationWidth; - } - else - { - destinationWidth = width; - destinationHeight = height; - } - } } - // If height or width is not passed we assume that the standard ratio is to be kept. - if (height == 0) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - height = destinationHeight; - } - - if (width == 0) - { - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - width = destinationWidth; - } + maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue; + maxHeight = maxHeight > 0 ? maxHeight : int.MaxValue; + List restrictedSizes = this.ResizeLayer.RestrictedSizes; // Restrict sizes if (restrictedSizes?.Count > 0) @@ -560,15 +141,15 @@ private Bitmap ResizeImage( if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) { // Exit if upscaling is not allowed. - if ((width > sourceWidth || height > sourceHeight) && !upscale && resizeMode != ResizeMode.Stretch) + if ((width > sourceWidth || height > sourceHeight) + && !upscale + && this.ResizeLayer.ResizeMode != ResizeMode.Stretch) { return (Bitmap)source; } - // Do the resize. - var destination = new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - - newImage = linear ? this.ResizeLinear(source, width, height, destination, this.AnimationProcessMode) : this.ResizeComposite(source, width, height, destination); + newImage = linear ? this.ResizeLinear(source, width, height, rectangle, this.AnimationProcessMode) + : this.ResizeComposite(source, width, height, rectangle); // Reassign the image. source.Dispose(); @@ -584,5 +165,103 @@ private Bitmap ResizeImage( return (Bitmap)source; } + + /// + /// Gets an image resized using the composite color space without any gamma correction adjustments. + /// + /// The source image. + /// The target width to resize to. + /// The target height to resize to. + /// The destination rectangle. + /// + /// The . + /// + protected virtual Bitmap ResizeComposite(Image source, int width, int height, Rectangle destination) + { + var resized = new Bitmap(width, height, source.PixelFormat); + resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (var graphics = Graphics.FromImage(resized)) + { + GraphicsHelper.SetGraphicsOptions(graphics); + using (var attributes = new ImageAttributes()) + { + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage( + source, + destination, + 0, + 0, + source.Width, + source.Height, + GraphicsUnit.Pixel, + attributes); + } + } + + return resized; + } + + /// + /// Gets an image resized using the linear color space with gamma correction adjustments. + /// + /// The source image. + /// The width to resize to. + /// The height to resize to. + /// The destination rectangle. + /// + /// The . + /// + protected virtual Bitmap ResizeLinear(Image source, int targetWidth, int targetHeight, Rectangle destination) + => this.ResizeLinear(source, targetWidth, targetHeight, destination, this.AnimationProcessMode); + + /// + /// Gets an image resized using the linear color space with gamma correction adjustments. + /// + /// The source image. + /// The width to resize to. + /// The height to resize to. + /// The destination rectangle. + /// The process mode for frames in animated images. + /// + /// The . + /// + protected virtual Bitmap ResizeLinear( + Image source, + int width, + int height, + Rectangle destination, + AnimationProcessMode animationProcessMode) + { + // Adjust the gamma value so that the image is in the linear color space. + Bitmap linear = Adjustments.ToLinear(source.Copy(animationProcessMode)); + + var resized = new Bitmap(width, height, source.PixelFormat); + resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (var graphics = Graphics.FromImage(resized)) + { + GraphicsHelper.SetGraphicsOptions(graphics); + using (var attributes = new ImageAttributes()) + { + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage( + linear, + destination, + 0, + 0, + source.Width, + source.Height, + GraphicsUnit.Pixel, + attributes); + } + } + + // Return to composite color space. + resized = Adjustments.ToSRGB(resized); + + linear.Dispose(); + return resized; + } } } diff --git a/src/ImageProcessor/Imaging/RoundedCornerLayer.cs b/src/ImageProcessor/Imaging/RoundedCornerLayer.cs index 3e4e3017c..69442befc 100644 --- a/src/ImageProcessor/Imaging/RoundedCornerLayer.cs +++ b/src/ImageProcessor/Imaging/RoundedCornerLayer.cs @@ -10,10 +10,12 @@ namespace ImageProcessor.Imaging { + using System; + /// /// Encapsulates the properties required to add rounded corners to an image. /// - public class RoundedCornerLayer + public class RoundedCornerLayer : IEquatable { /// /// Initializes a new instance of the class. @@ -68,45 +70,34 @@ public RoundedCornerLayer(int radius, bool topLeft = true, bool topRight = true, public bool BottomRight { get; set; } /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is RoundedCornerLayer rounded)) - { - return false; - } + public override bool Equals(object obj) => obj is RoundedCornerLayer roundedCornerLayer && this.Equals(roundedCornerLayer); - return this.Radius == rounded.Radius - && this.TopLeft == rounded.TopLeft && this.TopRight == rounded.TopRight - && this.BottomLeft == rounded.BottomLeft && this.BottomRight == rounded.BottomRight; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(RoundedCornerLayer other) => other != null + && this.Radius == other.Radius + && this.TopLeft == other.TopLeft + && this.TopRight == other.TopRight + && this.BottomLeft == other.BottomLeft + && this.BottomRight == other.BottomRight; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Radius; - hashCode = (hashCode * 397) ^ this.TopLeft.GetHashCode(); - hashCode = (hashCode * 397) ^ this.TopRight.GetHashCode(); - hashCode = (hashCode * 397) ^ this.BottomLeft.GetHashCode(); - return (hashCode * 397) ^ this.BottomRight.GetHashCode(); - } - } + public override int GetHashCode() => (this.Radius, this.TopLeft, this.TopRight, this.BottomLeft, this.BottomRight).GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/TextLayer.cs b/src/ImageProcessor/Imaging/TextLayer.cs index 049fa92af..837299c18 100644 --- a/src/ImageProcessor/Imaging/TextLayer.cs +++ b/src/ImageProcessor/Imaging/TextLayer.cs @@ -18,7 +18,7 @@ namespace ImageProcessor.Imaging /// /// Encapsulates the properties required to add a layer of text to an image. /// - public class TextLayer : IDisposable + public class TextLayer : IDisposable, IEquatable { /// /// A value indicating whether this instance of the given entity has been disposed. @@ -96,58 +96,40 @@ public class TextLayer : IDisposable public bool RightToLeft { get; set; } /// - /// Returns a value that indicates whether the specified object is an - /// object that is equivalent to - /// this object. + /// Determines whether the specified , is equal to this instance. /// - /// - /// The object to test. - /// + /// The to compare with this instance. /// - /// True if the given object is an object that is equivalent to - /// this object; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) - { - if (!(obj is TextLayer textLayer)) - { - return false; - } + public override bool Equals(object obj) => obj is TextLayer textLayer && this.Equals(textLayer); - return this.Text == textLayer.Text - && this.FontColor == textLayer.FontColor - && this.FontFamily.Equals(textLayer.FontFamily) - && this.FontSize == textLayer.FontSize - && this.Style == textLayer.Style - && this.DropShadow == textLayer.DropShadow - && this.Opacity == textLayer.Opacity - && this.Position == textLayer.Position - && this.Vertical == textLayer.Vertical - && this.RightToLeft == textLayer.RightToLeft; - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(TextLayer other) => other != null + && this.Text == other.Text + && this.FontColor == other.FontColor + && (this.FontFamily == null || other.FontFamily == null ? this.FontFamily == other.FontFamily : this.FontFamily.Equals(other.FontFamily)) + && this.FontSize == other.FontSize + && this.Style == other.Style + && this.Opacity == other.Opacity + && this.Position == other.Position + && this.DropShadow == other.DropShadow + && this.Vertical == other.Vertical + && this.RightToLeft == other.RightToLeft; /// - /// Returns the hash code for this instance. + /// Returns a hash code for this instance. /// /// - /// A 32-bit signed integer that is the hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Text?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ this.DropShadow.GetHashCode(); - hashCode = (hashCode * 397) ^ (this.FontFamily?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (int)this.Style; - hashCode = (hashCode * 397) ^ this.FontColor.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Opacity; - hashCode = (hashCode * 397) ^ this.FontSize; - hashCode = (hashCode * 397) ^ this.Position.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Vertical.GetHashCode(); - return (hashCode * 397) ^ this.RightToLeft.GetHashCode(); - } - } + public override int GetHashCode() => (this.Text, this.FontColor, this.FontFamily, this.FontSize, this.Style, this.Opacity, this.Position, this.DropShadow, this.Vertical, this.RightToLeft).GetHashCode(); /// /// Disposes the object and frees resources for the Garbage Collector. diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index ae53745fc..6f896c314 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -77,7 +77,7 @@ public Image ProcessImage(ImageFactory factory) // Augment the layer with the extra information. resizeLayer.RestrictedSizes = this.RestrictedSizes; - var maxSize = new Size(); + Size maxSize = default; int.TryParse(this.Settings["MaxWidth"], NumberStyles.Any, CultureInfo.InvariantCulture, out int maxWidth); int.TryParse(this.Settings["MaxHeight"], NumberStyles.Any, CultureInfo.InvariantCulture, out int maxHeight); diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs index 3feaec74d..46c2ad6e0 100644 --- a/src/ImageProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -41,8 +41,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.5.6.0")] -[assembly: AssemblyFileVersion("2.5.6.0")] +[assembly: AssemblyVersion("2.9.0.00000")] +[assembly: AssemblyFileVersion("2.9.0.00000")] [assembly: InternalsVisibleTo("ImageProcessor.UnitTests")] [assembly: InternalsVisibleTo("ImageProcessor.Web")] diff --git a/src/ImageProcessor/packages.config b/src/ImageProcessor/packages.config new file mode 100644 index 000000000..8653b8e4c --- /dev/null +++ b/src/ImageProcessor/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/ImageProcessor.Playground/Program.cs b/tests/ImageProcessor.Playground/Program.cs index 225f798cd..74e59d8eb 100644 --- a/tests/ImageProcessor.Playground/Program.cs +++ b/tests/ImageProcessor.Playground/Program.cs @@ -68,7 +68,8 @@ public static void Main(string[] args) try { imageFactory.Load(inStream) - .Crop(new Rectangle(0, 0, imageFactory.Image.Width / 2, imageFactory.Image.Height / 2)) + .Resize(new Size(426, 0)) + //.Crop(new Rectangle(0, 0, imageFactory.Image.Width / 2, imageFactory.Image.Height / 2)) .Save(Path.GetFullPath(Path.Combine(outPath, fileInfo.Name))); } catch (Exception ex) diff --git a/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 31e5fc607..049a87497 100644 --- a/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -1008,7 +1008,7 @@ public void ResizeIsApplied() // Check we min correctly using the shortest size. imageFactory.Resize(minLayer); Assert.AreEqual(imageFactory.Image.Size, new Size(400, 300)); - imageFactory.Save(OutputPath + "resize-crop-" + i + ".jpg"); + imageFactory.Save(OutputPath + "resize-min-" + i + ".jpg"); // Check padding with only a single dimension specified (width) imageFactory.Resize(paddedSingleDimensionWidthLayer); diff --git a/tests/ImageProcessor.UnitTests/Images/792.jpeg b/tests/ImageProcessor.UnitTests/Images/792.jpeg new file mode 100644 index 000000000..4e0112eb9 Binary files /dev/null and b/tests/ImageProcessor.UnitTests/Images/792.jpeg differ diff --git a/tests/ImageProcessor.UnitTests/Images/format-Penguins.tif b/tests/ImageProcessor.UnitTests/Images/format-Penguins.tif index c789aaec2..8a79fd09a 100644 Binary files a/tests/ImageProcessor.UnitTests/Images/format-Penguins.tif and b/tests/ImageProcessor.UnitTests/Images/format-Penguins.tif differ diff --git a/tests/ImageProcessor.UnitTests/Imaging/Filters/Photo/MatrixFilterBaseTests.cs b/tests/ImageProcessor.UnitTests/Imaging/Filters/Photo/MatrixFilterBaseTests.cs index d5b76bb3f..c87f8afdf 100644 --- a/tests/ImageProcessor.UnitTests/Imaging/Filters/Photo/MatrixFilterBaseTests.cs +++ b/tests/ImageProcessor.UnitTests/Imaging/Filters/Photo/MatrixFilterBaseTests.cs @@ -29,7 +29,7 @@ public static void MatrixFilterBaseImplementsEqualsBasedOnMatrixPropertyVariant( VariantFilterBase first = new VariantFilterBase(); VariantFilterBase second = new VariantFilterBase(); - first.Equals(second).Should().BeFalse(); + first.Equals(second).Should().BeTrue(); } internal static ColorMatrix InvariantColorMatrix = new ColorMatrix(new[] diff --git a/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs b/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs index ca47f7782..2a0f83e5e 100644 --- a/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs +++ b/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs @@ -273,30 +273,18 @@ public void TestResizeRegex() { Dictionary data = new Dictionary { - { - "width=300", new ResizeLayer(new Size(300, 0)) - }, - { - "height=300", new ResizeLayer(new Size(0, 300)) - }, - { - "height=300.6", new ResizeLayer(new Size(0, 301)) - }, - { - "height=300&mode=crop", new ResizeLayer(new Size(0, 300), ResizeMode.Crop) - }, - { - "width=300&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) - }, - { - "width=300.2&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) - }, - { - "width=600&heightratio=0.416", new ResizeLayer(new Size(600, 250)) - }, - { - "width=600&height=250&mode=max", new ResizeLayer(new Size(600, 250), ResizeMode.Max) - } + { "width=300", new ResizeLayer(new Size(300, 0)) }, + { "height=300", new ResizeLayer(new Size(0, 300)) }, + { "height=300.6", new ResizeLayer(new Size(0, 301)) }, // Round size units + { "height=300&mode=crop", new ResizeLayer(new Size(0, 300), ResizeMode.Crop) }, + { "width=300&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) }, + { "width=300.2&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) }, // Round size units + { "width=600&heightratio=0.416", new ResizeLayer(new Size(600, 250)) }, + { "width=600&height=250&mode=max", new ResizeLayer(new Size(600, 250), ResizeMode.Max) }, + { "width=600&height=250¢er=1,0.5", new ResizeLayer(new Size(600, 250), centerCoordinates: new [] { 1f, 0.5f }) }, // Center coordinates (Y,X) + { "width=600&height=250¢er=0.5,0.25", new ResizeLayer(new Size(600, 250)) { Center = new PointF(0.25f, 0.5f) } }, // Center coordinates (Y,X) to PointF + { "width=600&height=250¢er=0.5", new ResizeLayer(new Size(600, 250)) }, // Invalid center coordinates should not result in 0,0 + { "width=600&height=250¢er=y,x", new ResizeLayer(new Size(600, 250)) } // Invalid center coordinates should not result in 0,0 }; Processors.Resize resize = new Processors.Resize();