From d5c93c79f3d5c3a5c9d3cb8764af21b794abdabe Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 21 Apr 2015 21:35:18 +0100 Subject: [PATCH] Fix constructor and add cors support. Former-commit-id: bb6daf831ba9b3efeeef392cf7b7eb53c7358030 Former-commit-id: 568e90c5570902dd0a2467960d91cf69d8036629 Former-commit-id: 24eaa11c52350199f263d023c763e9abb600bede --- build/NuSpecs/ImageProcessor.nuspec | 2 +- build/build.xml | 2 +- src/ImageProcessor.Playground/Program.cs | 150 +++++----- .../images/input/blue-balloon.jpg | Bin 0 -> 59460 bytes .../Imaging/ColorUnitTests.cs | 1 - .../Imaging/Helpers/ImageMathsUnitTests.cs | 12 +- .../ImageProcessorConfiguration.cs | 24 +- .../Configuration/ImageSecuritySection.cs | 37 ++- .../HttpModules/ImageProcessingModule.cs | 102 +++++-- src/ImageProcessor.sln.DotSettings | 1 + src/ImageProcessor/ImageFactory.cs | 19 +- src/ImageProcessor/ImageProcessor.csproj | 1 + .../Imaging/Colors/YCbCrColor.cs | 19 +- .../Imaging/Helpers/Adjustments.cs | 43 ++- .../Imaging/Helpers/ImageMaths.cs | 35 +++ .../Imaging/Helpers/PixelOperations.cs | 58 ++++ src/ImageProcessor/Imaging/TextLayer.cs | 16 +- .../Processors/Halftone - Copy.cs | 262 ------------------ src/ImageProcessor/Processors/ReplaceColor.cs | 32 ++- src/ImageProcessor/Processors/Watermark.cs | 60 +++- src/ImageProcessor/Properties/AssemblyInfo.cs | 4 +- 21 files changed, 485 insertions(+), 395 deletions(-) create mode 100644 src/ImageProcessor.Playground/images/input/blue-balloon.jpg create mode 100644 src/ImageProcessor/Imaging/Helpers/PixelOperations.cs delete mode 100644 src/ImageProcessor/Processors/Halftone - Copy.cs diff --git a/build/NuSpecs/ImageProcessor.nuspec b/build/NuSpecs/ImageProcessor.nuspec index f73d4178d4..f5eb45e288 100644 --- a/build/NuSpecs/ImageProcessor.nuspec +++ b/build/NuSpecs/ImageProcessor.nuspec @@ -2,7 +2,7 @@ ImageProcessor - 2.2.1.0 + 2.2.2.0 ImageProcessor James South James South diff --git a/build/build.xml b/build/build.xml index b6f8e05df1..b1b3027acc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,7 +1,7 @@ ImageProcessor - 2.2.1.0 + 2.2.2.0 ..\src\ImageProcessor ImageProcessor.csproj diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 4975a3f038..590ad5989b 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -61,7 +61,9 @@ namespace ImageProcessor.PlayGround //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "gamma_dalai_lama_gray.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Arc-de-Triomphe-France.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Martin-Schoeller-Jack-Nicholson-Portrait.jpeg")); + //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "night-bridge.png")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "tree.jpg")); + //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "blue-balloon.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "test2.png")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "120430.gif")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "rickroll.original.gif")); @@ -70,9 +72,9 @@ namespace ImageProcessor.PlayGround //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png")); //IEnumerable files = GetFilesByExtensions(di, ".gif"); //IEnumerable files = GetFilesByExtensions(di, ".png", ".jpg", ".jpeg"); - //IEnumerable files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); + IEnumerable files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); //IEnumerable files = GetFilesByExtensions(di, ".png"); - IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png"); + //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png"); foreach (FileInfo fileInfo in files) { @@ -81,79 +83,91 @@ namespace ImageProcessor.PlayGround continue; } - byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName); - Console.WriteLine("Processing: " + fileInfo.Name); + byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName); + Console.WriteLine("Processing: " + fileInfo.Name); - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); - // ImageProcessor - using (MemoryStream inStream = new MemoryStream(photoBytes)) + // ImageProcessor + using (MemoryStream inStream = new MemoryStream(photoBytes)) + { + using (ImageFactory imageFactory = new ImageFactory(true, true)) { - using (ImageFactory imageFactory = new ImageFactory(true, true)) - { - Size size = new Size(600, 0); - //CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage); - //ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false); - - //ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size) - //{ - // ConvolutionType = ConvolutionType.Sobel - //}; - // Load, resize, set the format and quality and save an image. - imageFactory.Load(inStream) - //.DetectObjects(EmbeddedHaarCascades.FrontFaceDefault) - //.Overlay(new ImageLayer - // { - // Image = overlay, - // Opacity = 50 - // }) - //.Alpha(50) - //.BackgroundColor(Color.White) - //.Resize(new Size((int)(size.Width * 1.1), 0)) - //.ContentAwareResize(layer) - //.Constrain(size) - //.Mask(mask) - //.Format(new PngFormat()) - //.BackgroundColor(Color.Cyan) - //.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128) - //.GaussianSharpen(3) - //.Saturation(20) - //.Resize(size) - //.Resize(new ResizeLayer(size, ResizeMode.Max)) - // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) - //.DetectEdges(new SobelEdgeFilter(), true) - //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) - //.GaussianBlur(new GaussianLayer(10, 11)) - //.EntropyCrop() - .Halftone() - //.RotateBounded(150, false) - //.Crop(cropLayer) - //.Rotate(140) - //.Filter(MatrixFilters.Invert) - //.Brightness(-5) - //.Contrast(50) - //.Filter(MatrixFilters.Comic) - //.Flip() - //.Filter(MatrixFilters.HiSatch) - //.Pixelate(8) - //.GaussianSharpen(10) - //.Format(new PngFormat() { IsIndexed = true }) - //.Format(new PngFormat() ) - .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); - //.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png"))); - - stopwatch.Stop(); - } + Size size = new Size(600, 0); + //CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage); + //ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false); + // TextLayer textLayer = new TextLayer() + //{ + // Text = "هناك حقيقة مثبتة منذ زمن", + // FontColor = Color.White, + // DropShadow = true, + // Vertical = true, + // //RightToLeft = true, + // //Position = new Point(5, 5) + + //}; + + //ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size) + //{ + // ConvolutionType = ConvolutionType.Sobel + //}; + // Load, resize, set the format and quality and save an image. + imageFactory.Load(inStream) + //.DetectObjects(EmbeddedHaarCascades.FrontFaceDefault) + //.Overlay(new ImageLayer + // { + // Image = overlay, + // Opacity = 50 + // }) + //.Alpha(50) + //.BackgroundColor(Color.White) + //.Resize(new Size((int)(size.Width * 1.1), 0)) + //.ContentAwareResize(layer) + //.Constrain(size) + //.Mask(mask) + //.Format(new PngFormat()) + //.BackgroundColor(Color.Cyan) + //.Watermark(textLayer) + //.ReplaceColor(Color.FromArgb(93, 136, 231), Color.FromArgb(94, 134, 78), 50) + //.GaussianSharpen(3) + //.Saturation(20) + //.Resize(size) + //.Resize(new ResizeLayer(size, ResizeMode.Max)) + // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) + //.DetectEdges(new SobelEdgeFilter(), true) + //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) + //.GaussianBlur(new GaussianLayer(10, 11)) + //.EntropyCrop() + //.Gamma(2.2F) + //.Halftone() + //.RotateBounded(150, false) + //.Crop(cropLayer) + //.Rotate(140) + //.Filter(MatrixFilters.Invert) + //.Brightness(-5) + //.Contrast(50) + .Filter(MatrixFilters.Comic) + //.Flip() + //.Filter(MatrixFilters.HiSatch) + //.Pixelate(8) + //.GaussianSharpen(10) + //.Format(new PngFormat() { IsIndexed = true }) + //.Format(new PngFormat() ) + .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); + //.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png"))); + + stopwatch.Stop(); } + } - long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64; - float mB = peakWorkingSet64 / (float)1024 / 1024; + long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64; + float mB = peakWorkingSet64 / (float)1024 / 1024; - Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB); + Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB); - //Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms"); - } + //Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms"); + } Console.ReadLine(); } diff --git a/src/ImageProcessor.Playground/images/input/blue-balloon.jpg b/src/ImageProcessor.Playground/images/input/blue-balloon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70c5339be863a23b17bfdc9e0187d8713db8ff62 GIT binary patch literal 59460 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 K`V^7ZGyek~qEqVt literal 0 HcmV?d00001 diff --git a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs index 04b5e3d123..9370142a83 100644 --- a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs @@ -132,7 +132,6 @@ namespace ImageProcessor.UnitTests.Imaging Debug.Print(cmykColor.ToString()); - string result = ColorTranslator.ToHtml(cmykColor); result.Should().Be(expected); diff --git a/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs index aa20d9b51a..18e592edee 100644 --- a/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs @@ -1,4 +1,14 @@ -namespace ImageProcessor.UnitTests.Imaging.Helpers +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Test harness for the image math unit tests +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.UnitTests.Imaging.Helpers { using System.Drawing; using FluentAssertions; diff --git a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs index 4d80291370..06ef9434db 100644 --- a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs +++ b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs @@ -144,6 +144,15 @@ namespace ImageProcessor.Web.Configuration }); } + /// + /// Retrieves the security configuration section from the current application configuration. + /// + /// The security configuration section from the current application configuration. + internal ImageSecuritySection GetImageSecuritySection() + { + return imageSecuritySection ?? (imageSecuritySection = ImageSecuritySection.GetConfiguration()); + } + /// /// Retrieves the processing configuration section from the current application configuration. /// @@ -162,15 +171,6 @@ namespace ImageProcessor.Web.Configuration return imageCacheSection ?? (imageCacheSection = ImageCacheSection.GetConfiguration()); } - /// - /// Retrieves the security configuration section from the current application configuration. - /// - /// The security configuration section from the current application configuration. - private static ImageSecuritySection GetImageSecuritySection() - { - return imageSecuritySection ?? (imageSecuritySection = ImageSecuritySection.GetConfiguration()); - } - #region GraphicesProcessors /// /// Gets the list of available GraphicsProcessors. @@ -282,7 +282,7 @@ namespace ImageProcessor.Web.Configuration { if (this.ImageServices == null) { - if (GetImageSecuritySection().AutoLoadServices) + if (this.GetImageSecuritySection().AutoLoadServices) { Type type = typeof(IImageService); try @@ -368,7 +368,7 @@ namespace ImageProcessor.Web.Configuration /// private Dictionary GetServiceSettings(string name) { - ImageSecuritySection.ServiceElement serviceElement = GetImageSecuritySection() + ImageSecuritySection.ServiceElement serviceElement = this.GetImageSecuritySection() .ImageServices .Cast() .FirstOrDefault(x => x.Name == name); @@ -400,7 +400,7 @@ namespace ImageProcessor.Web.Configuration /// private Uri[] GetServiceWhitelist(string name) { - ImageSecuritySection.ServiceElement serviceElement = GetImageSecuritySection() + ImageSecuritySection.ServiceElement serviceElement = this.GetImageSecuritySection() .ImageServices .Cast() .FirstOrDefault(x => x.Name == name); diff --git a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs index 28644ee655..9cceb317af 100644 --- a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs @@ -22,6 +22,20 @@ namespace ImageProcessor.Web.Configuration /// public sealed class ImageSecuritySection : ConfigurationSection { + /// + /// Gets the + /// + /// The + [ConfigurationProperty("cors", IsRequired = false)] + public CORSOriginElement CORSOrigin + { + get + { + object o = this["cors"]; + return o as CORSOriginElement; + } + } + /// /// Gets the /// @@ -213,6 +227,27 @@ namespace ImageProcessor.Web.Configuration } } + /// + /// Represents a CORSOriginsElement configuration element within the configuration. + /// + public class CORSOriginElement : ConfigurationElement + { + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("whitelist", IsRequired = false)] + public WhiteListElementCollection WhiteList + { + get + { + return this["whitelist"] as WhiteListElementCollection; + } + } + } + /// /// Represents a whitelist collection configuration element within the configuration. /// @@ -275,7 +310,7 @@ namespace ImageProcessor.Web.Configuration [ConfigurationProperty("url", DefaultValue = "", IsRequired = true)] public Uri Url { - get { return (Uri)this["url"]; } + get { return new Uri(this["url"].ToString(), UriKind.RelativeOrAbsolute); } set { this["url"] = value; } } diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index 990ab2eb5b..231cd7b236 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -307,7 +307,7 @@ namespace ImageProcessor.Web.HttpModules string url = request.Url.ToString(); bool isLegacy = ProtocolRegex.Matches(url).Count > 1; bool hasMultiParams = url.Count(f => f == '?') > 1; - string requestPath = string.Empty; + string requestPath; string queryString = string.Empty; string urlParameters = string.Empty; @@ -316,28 +316,18 @@ namespace ImageProcessor.Web.HttpModules { // We need to split the querystring to get the actual values we want. string[] paths = url.Split('?'); + requestPath = paths[1]; - //if (!string.IsNullOrWhiteSpace(multiQuery)) - //{ - //// UrlDecode seems to mess up in some circumstance. - //if (multiQuery.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) - //{ - // multiQuery = multiQuery.Replace(":/", "://"); - //} - - requestPath = paths[1]; - - // Handle extension-less urls. - if (paths.Length > 3) - { - queryString = paths[3]; - urlParameters = paths[2]; - } - else if (paths.Length > 1) - { - queryString = paths[2]; - } - //} + // Handle extension-less urls. + if (paths.Length > 3) + { + queryString = paths[3]; + urlParameters = paths[2]; + } + else if (paths.Length > 1) + { + queryString = paths[2]; + } } else { @@ -383,6 +373,11 @@ namespace ImageProcessor.Web.HttpModules return; } + if (string.IsNullOrWhiteSpace(requestPath)) + { + return; + } + string parts = !string.IsNullOrWhiteSpace(urlParameters) ? "?" + urlParameters : string.Empty; string fullPath = string.Format("{0}{1}?{2}", requestPath, parts, queryString); object resourcePath; @@ -400,7 +395,7 @@ namespace ImageProcessor.Web.HttpModules } // Check whether the path is valid for other requests. - if (resourcePath == null || !currentService.IsValidRequest(resourcePath.ToString())) + if (!currentService.IsValidRequest(resourcePath.ToString())) { return; } @@ -522,6 +517,16 @@ namespace ImageProcessor.Web.HttpModules cache.SetRevalidation(HttpCacheRevalidation.AllCaches); this.imageCache = null; + + if (!string.IsNullOrEmpty(context.Request.Headers["Origin"])) + { + string origin = context.Request.Headers["Origin"]; + + if (this.IsValidOriginRequest(origin)) + { + response.AddHeader("Access-Control-Allow-Origin", origin); + } + } } } @@ -611,6 +616,57 @@ namespace ImageProcessor.Web.HttpModules // Return the file based service return services.FirstOrDefault(s => string.IsNullOrWhiteSpace(s.Prefix) && s.IsValidRequest(path)); } + + /// + /// Gets a value indicating whether the current origin request passes sanitizing rules. + /// + /// + /// The image path. + /// + /// + /// True if the request is valid; otherwise, False. + /// + private bool IsValidOriginRequest(string path) + { + ImageSecuritySection.CORSOriginElement origins = + ImageProcessorConfiguration.Instance.GetImageSecuritySection().CORSOrigin; + + if (origins == null || origins.WhiteList == null) + { + return false; + } + + // Check the url is from a whitelisted location. + Uri url = new Uri(path); + string upper = url.Host.ToUpperInvariant(); + + // Check for root or sub domain. + bool validUrl = false; + foreach (Uri uri in origins.WhiteList) + { + if (uri.ToString() == "*") + { + return true; + } + + if (!uri.IsAbsoluteUri) + { + Uri rebaseUri = new Uri("http://" + uri.ToString().TrimStart('.', '/')); + validUrl = upper.StartsWith(rebaseUri.Host.ToUpperInvariant()) || upper.EndsWith(rebaseUri.Host.ToUpperInvariant()); + } + else + { + validUrl = upper.StartsWith(uri.Host.ToUpperInvariant()) || upper.EndsWith(uri.Host.ToUpperInvariant()); + } + + if (validUrl) + { + break; + } + } + + return validUrl; + } #endregion } } \ No newline at end of file diff --git a/src/ImageProcessor.sln.DotSettings b/src/ImageProcessor.sln.DotSettings index d8fb7528a5..6ab368df98 100644 --- a/src/ImageProcessor.sln.DotSettings +++ b/src/ImageProcessor.sln.DotSettings @@ -1,6 +1,7 @@  BGRA BPP + CORS DT FPX FR diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 73ef61e747..4c18e0e3da 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -59,6 +59,17 @@ namespace ImageProcessor #endregion #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// + /// Whether to preserve exif metadata. Defaults to false. + /// + public ImageFactory(bool preserveExifData = false) + : this(preserveExifData, true) + { + } + /// /// Initializes a new instance of the class. /// @@ -68,7 +79,7 @@ namespace ImageProcessor /// /// Whether to fix the gamma component of the image. Defaults to true. /// - public ImageFactory(bool preserveExifData = false, bool fixGamma = true) + public ImageFactory(bool preserveExifData, bool fixGamma) { this.PreserveExifData = preserveExifData; this.ExifPropertyItems = new ConcurrentDictionary(); @@ -122,6 +133,11 @@ namespace ImageProcessor /// public bool FixGamma { get; set; } + /// + /// Gets or the current gamma value. + /// + public float CurrentGamma { get; private set; } + /// /// Gets or sets the exif property items. /// @@ -635,6 +651,7 @@ namespace ImageProcessor value = 2.2F; } + this.CurrentGamma = value; Gamma gamma = new Gamma { DynamicParameter = value }; this.CurrentImageFormat.ApplyProcessor(gamma.ProcessImage, this); } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index e1a7814a96..998a82514c 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -169,6 +169,7 @@ + diff --git a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs index 7cb2c236f1..0035ea755c 100644 --- a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs +++ b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs @@ -51,8 +51,8 @@ namespace ImageProcessor.Imaging.Colors private YCbCrColor(float y, float cb, float cr) { this.y = ImageMaths.Clamp(y, 0, 255); - this.cb = ImageMaths.Clamp(cb, -255, 255); - this.cr = ImageMaths.Clamp(cr, -255, 255); + this.cb = ImageMaths.Clamp(cb, 0, 255); + this.cr = ImageMaths.Clamp(cr, 0, 255); } /// @@ -69,7 +69,7 @@ namespace ImageProcessor.Imaging.Colors /// /// Gets the U chroma component. - /// A value ranging between -255 and 255. + /// A value ranging between 0 and 255. /// public float Cb { @@ -81,7 +81,7 @@ namespace ImageProcessor.Imaging.Colors /// /// Gets the V chroma component. - /// A value ranging between -255 and 255. + /// A value ranging between 0 and 255. /// public float Cr { @@ -189,9 +189,13 @@ namespace ImageProcessor.Imaging.Colors float cb = ycbcrColor.Cb - 128; float cr = ycbcrColor.Cr - 128; - byte r = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y + (1.402 * cr)))); - byte g = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y - (0.34414 * cb) - (0.71414 * cr)))); - byte b = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y + (1.772 * cb)))); + //byte r = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y + (1.402 * cr)))); + //byte g = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y - (0.34414 * cb) - (0.71414 * cr)))); + //byte b = Convert.ToByte(Math.Max(0.0f, Math.Min(255f, y + (1.772 * cb)))); + + byte r = Convert.ToByte(ImageMaths.Clamp(y + (1.402 * cr), 0, 255)); + byte g = Convert.ToByte(ImageMaths.Clamp(y - (0.34414 * cb) - (0.71414 * cr), 0, 255)); + byte b = Convert.ToByte(ImageMaths.Clamp(y + (1.772 * cb), 0, 255)); return Color.FromArgb(255, r, g, b); } @@ -226,7 +230,6 @@ namespace ImageProcessor.Imaging.Colors return HslaColor.FromColor(ycbcrColor); } - /// /// Allows the implicit conversion of an instance of to a /// . diff --git a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs index fd4812bcd7..cb583bd478 100644 --- a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs +++ b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs @@ -15,6 +15,8 @@ namespace ImageProcessor.Imaging.Helpers using System.Drawing.Imaging; using System.Threading.Tasks; + using ImageProcessor.Common.Extensions; + /// /// Provides reusable adjustment methods to apply to images. /// @@ -202,16 +204,47 @@ namespace ImageProcessor.Imaging.Helpers Bitmap destination = new Bitmap(width, height); destination.SetResolution(source.HorizontalResolution, source.VerticalResolution); - Rectangle rectangle = new Rectangle(0, 0, width, height); - using (Graphics graphics = Graphics.FromImage(destination)) + byte[] ramp = new byte[256]; + for (int x = 0; x < 256; ++x) + { + byte val = ((255.0 * Math.Pow(x / 255.0, value)) + 0.5).ToByte(); + ramp[x] = val; + } + + using (FastBitmap fastSource = new FastBitmap(source)) { - using (ImageAttributes attributes = new ImageAttributes()) + using (FastBitmap fastDestination = new FastBitmap(destination)) { - attributes.SetGamma(value); - graphics.DrawImage(source, rectangle, 0, 0, width, height, GraphicsUnit.Pixel, attributes); + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + // ReSharper disable once AccessToDisposedClosure + Color color = fastSource.GetPixel(x, y); + byte r = ramp[color.R]; + byte g = ramp[color.G]; + byte b = ramp[color.B]; + + // ReSharper disable once AccessToDisposedClosure + fastDestination.SetPixel(x, y, Color.FromArgb(color.A, r, g, b)); + } + }); } } + //Rectangle rectangle = new Rectangle(0, 0, width, height); + //using (Graphics graphics = Graphics.FromImage(destination)) + //{ + // using (ImageAttributes attributes = new ImageAttributes()) + // { + // attributes.SetGamma(value); + // graphics.DrawImage(source, rectangle, 0, 0, width, height, GraphicsUnit.Pixel, attributes); + // } + //} + source.Dispose(); return destination; } diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index 050c5e8293..97338bfa32 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Helpers { using System; using System.Drawing; + using ImageProcessor.Imaging.Colors; /// @@ -73,6 +74,40 @@ namespace ImageProcessor.Imaging.Helpers return value; } + /// + /// Returns value indicating whether the given number is with in the minimum and maximum + /// given range. + /// + /// + /// The The value to clamp. + /// + /// + /// If + /// The minimum range value. + /// + /// + /// The maximum range value. + /// + /// + /// Whether to include the minimum and maximum values. + /// Defaults to true. + /// + /// + /// The to test. + /// + /// + /// True if the value falls within the maximum and minimum; otherwise, false. + /// + public static bool InRange(T value, T min, T max, bool include = true) where T : IComparable + { + if (include) + { + return (value.CompareTo(min) >= 0) && (value.CompareTo(max) <= 0); + } + + return (value.CompareTo(min) > 0) && (value.CompareTo(max) < 0); + } + /// /// Returns the given degrees converted to radians. /// diff --git a/src/ImageProcessor/Imaging/Helpers/PixelOperations.cs b/src/ImageProcessor/Imaging/Helpers/PixelOperations.cs new file mode 100644 index 0000000000..2393fcace5 --- /dev/null +++ b/src/ImageProcessor/Imaging/Helpers/PixelOperations.cs @@ -0,0 +1,58 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Performs per-pixel operations. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging.Helpers +{ + using System; + using System.Drawing; + using ImageProcessor.Common.Extensions; + + /// + /// Performs per-pixel operations. + /// + public static class PixelOperations + { + /// + /// Returns the given color adjusted by the given gamma value. + /// + /// + /// The to adjust. + /// + /// + /// The gamma value - Between .1 and 5. + /// + /// + /// The adjusted . + /// + /// + /// Thrown if the given gamma value is out with the acceptable range. + /// + public static Color Gamma(Color color, float value) + { + if (value > 5 || value < .1) + { + throw new ArgumentOutOfRangeException("value", "Value should be between .1 and 5."); + } + + byte[] ramp = new byte[256]; + for (int x = 0; x < 256; ++x) + { + byte val = ((255.0 * Math.Pow(x / 255.0, value)) + 0.5).ToByte(); + ramp[x] = val; + } + + byte r = ramp[color.R]; + byte g = ramp[color.G]; + byte b = ramp[color.B]; + + return Color.FromArgb(color.A, r, g, b); + } + } +} diff --git a/src/ImageProcessor/Imaging/TextLayer.cs b/src/ImageProcessor/Imaging/TextLayer.cs index c934791d71..12b049e422 100644 --- a/src/ImageProcessor/Imaging/TextLayer.cs +++ b/src/ImageProcessor/Imaging/TextLayer.cs @@ -117,6 +117,16 @@ namespace ImageProcessor.Imaging /// Gets or sets a value indicating whether a DropShadow should be drawn. /// public bool DropShadow { get; set; } + + /// + /// Gets or sets a value indicating whether the text should be rendered vertically. + /// + public bool Vertical { get; set; } + + /// + /// Gets or sets a value indicating whether the text should be rendered right to left. + /// + public bool RightToLeft { get; set; } #endregion /// @@ -147,7 +157,9 @@ namespace ImageProcessor.Imaging && this.Style == textLayer.Style && this.DropShadow == textLayer.DropShadow && this.Opacity == textLayer.Opacity - && this.Position == textLayer.Position; + && this.Position == textLayer.Position + && this.Vertical == textLayer.Vertical + && this.RightToLeft == textLayer.RightToLeft; } /// @@ -168,6 +180,8 @@ namespace ImageProcessor.Imaging hashCode = (hashCode * 397) ^ this.Opacity; hashCode = (hashCode * 397) ^ this.FontSize; hashCode = (hashCode * 397) ^ this.Position.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Vertical.GetHashCode(); + hashCode = (hashCode * 397) ^ this.RightToLeft.GetHashCode(); return hashCode; } } diff --git a/src/ImageProcessor/Processors/Halftone - Copy.cs b/src/ImageProcessor/Processors/Halftone - Copy.cs deleted file mode 100644 index e43ce19dee..0000000000 --- a/src/ImageProcessor/Processors/Halftone - Copy.cs +++ /dev/null @@ -1,262 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessor.Processors -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Threading.Tasks; - - using ImageProcessor.Common.Exceptions; - using ImageProcessor.Common.Extensions; - using ImageProcessor.Imaging; - using ImageProcessor.Imaging.Colors; - using ImageProcessor.Imaging.Helpers; - - /// - /// The halftone. - /// - class Halftone : IGraphicsProcessor - { - /// - /// Initializes a new instance of the class. - /// - public Halftone() - { - this.Settings = new Dictionary(); - } - - /// - /// Gets or sets the dynamic parameter. - /// - public dynamic DynamicParameter - { - get; - set; - } - - /// - /// Gets or sets any additional settings required by the processor. - /// - public Dictionary Settings - { - get; - set; - } - - /// - /// The process image. - /// - /// - /// The factory. - /// - /// - /// The . - /// - /// - /// - public Image ProcessImage(ImageFactory factory) - { - Bitmap cyan = null; - Bitmap magenta = null; - Bitmap yellow = null; - Bitmap keyline = null; - Bitmap newImage = null; - Image image = factory.Image; - - try - { - int width = image.Width; - int height = image.Height; - - // Angles taken from Wikipedia page. - float cyanAngle = 15f; - float magentaAngle = 75f; - float yellowAngle = 0f; - float keylineAngle = 45f; - - int diameter = 4; - float multiplier = 4 * (float)Math.Sqrt(2); - - // Cyan color sampled from Wikipedia page. - Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239)); - Brush magentaBrush = Brushes.Magenta; - Brush yellowBrush = Brushes.Yellow; - Brush keylineBrush; - - // Create our images. - cyan = new Bitmap(width, height); - magenta = new Bitmap(width, height); - yellow = new Bitmap(width, height); - keyline = new Bitmap(width, height); - newImage = new Bitmap(width, height); - - // Ensure the correct resolution is set. - cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution); - magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution); - yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution); - keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution); - newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); - - // Check bounds against this. - Rectangle rectangle = new Rectangle(0, 0, width, height); - - using (Graphics graphicsCyan = Graphics.FromImage(cyan)) - using (Graphics graphicsMagenta = Graphics.FromImage(magenta)) - using (Graphics graphicsYellow = Graphics.FromImage(yellow)) - using (Graphics graphicsKeyline = Graphics.FromImage(keyline)) - { - // Ensure cleared out. - graphicsCyan.Clear(Color.Transparent); - graphicsMagenta.Clear(Color.Transparent); - graphicsYellow.Clear(Color.Transparent); - graphicsKeyline.Clear(Color.Transparent); - - // This is too slow. The graphics object can't be called within a parallel - // loop so we have to do it old school. :( - using (FastBitmap sourceBitmap = new FastBitmap(image)) - { - for (int y = -height * 2; y < height * 2; y += diameter) - { - for (int x = -width * 2; x < width * 2; x += diameter) - { - Color color; - CmykColor cmykColor; - float brushWidth; - - // Cyan - Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), cyanAngle); - int angledX = rotatedPoint.X; - int angledY = rotatedPoint.Y; - if (rectangle.Contains(new Point(angledX, angledY))) - { - color = sourceBitmap.GetPixel(angledX, angledY); - cmykColor = color; - brushWidth = diameter * (cmykColor.C / 255f) * multiplier; - graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); - } - - // Magenta - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), magentaAngle); - angledX = rotatedPoint.X; - angledY = rotatedPoint.Y; - if (rectangle.Contains(new Point(angledX, angledY))) - { - color = sourceBitmap.GetPixel(angledX, angledY); - cmykColor = color; - brushWidth = diameter * (cmykColor.M / 255f) * multiplier; - graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); - } - - // Yellow - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), yellowAngle); - angledX = rotatedPoint.X; - angledY = rotatedPoint.Y; - if (rectangle.Contains(new Point(angledX, angledY))) - { - color = sourceBitmap.GetPixel(angledX, angledY); - cmykColor = color; - brushWidth = diameter * (cmykColor.Y / 255f) * multiplier; - graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); - } - - // Keyline - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), keylineAngle); - angledX = rotatedPoint.X; - angledY = rotatedPoint.Y; - if (rectangle.Contains(new Point(angledX, angledY))) - { - color = sourceBitmap.GetPixel(angledX, angledY); - cmykColor = color; - brushWidth = diameter * (cmykColor.K / 255f) * multiplier; - - // Just using blck is too dark. - keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); - graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth); - } - } - } - } - - // Set our white background. - using (Graphics graphics = Graphics.FromImage(newImage)) - { - graphics.Clear(Color.White); - } - - // Blend the colors now to mimic adaptive blending. - using (FastBitmap cyanBitmap = new FastBitmap(cyan)) - using (FastBitmap magentaBitmap = new FastBitmap(magenta)) - using (FastBitmap yellowBitmap = new FastBitmap(yellow)) - using (FastBitmap keylineBitmap = new FastBitmap(keyline)) - using (FastBitmap destinationBitmap = new FastBitmap(newImage)) - { - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < width; x++) - { - // ReSharper disable AccessToDisposedClosure - Color cyanPixel = cyanBitmap.GetPixel(x, y); - Color magentaPixel = magentaBitmap.GetPixel(x, y); - Color yellowPixel = yellowBitmap.GetPixel(x, y); - Color keylinePixel = keylineBitmap.GetPixel(x, y); - - CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel); - destinationBitmap.SetPixel(x, y, blended); - // ReSharper restore AccessToDisposedClosure - } - }); - } - } - - cyan.Dispose(); - magenta.Dispose(); - yellow.Dispose(); - keyline.Dispose(); - image.Dispose(); - image = newImage; - } - catch (Exception ex) - { - if (cyan != null) - { - cyan.Dispose(); - } - - if (magenta != null) - { - magenta.Dispose(); - } - - if (yellow != null) - { - yellow.Dispose(); - } - - if (keyline != null) - { - keyline.Dispose(); - } - - if (newImage != null) - { - newImage.Dispose(); - } - - throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); - } - - return image; - } - } -} diff --git a/src/ImageProcessor/Processors/ReplaceColor.cs b/src/ImageProcessor/Processors/ReplaceColor.cs index dad33192b8..4ce3577806 100644 --- a/src/ImageProcessor/Processors/ReplaceColor.cs +++ b/src/ImageProcessor/Processors/ReplaceColor.cs @@ -18,6 +18,7 @@ namespace ImageProcessor.Processors using ImageProcessor.Common.Exceptions; using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Helpers; /// /// Encapsulates methods allowing the replacement of a color within an image. @@ -69,11 +70,20 @@ namespace ImageProcessor.Processors { Tuple parameters = this.DynamicParameter; Color original = parameters.Item1; + Color replacement = parameters.Item2; + + // Ensure that color comparison takes any gamma adjustments into consideration. + if (factory.FixGamma || Math.Abs(factory.CurrentGamma) > 0) + { + original = PixelOperations.Gamma(original, factory.CurrentGamma); + replacement = PixelOperations.Gamma(replacement, factory.CurrentGamma); + } + byte originalR = original.R; byte originalG = original.G; byte originalB = original.B; + byte originalA = original.A; - Color replacement = parameters.Item2; byte replacementR = replacement.R; byte replacementG = replacement.G; byte replacementB = replacement.B; @@ -81,6 +91,14 @@ namespace ImageProcessor.Processors int fuzziness = parameters.Item3; + byte minR = (originalR - fuzziness).ToByte(); + byte minG = (originalG - fuzziness).ToByte(); + byte minB = (originalB - fuzziness).ToByte(); + + byte maxR = (originalR + fuzziness).ToByte(); + byte maxG = (originalG + fuzziness).ToByte(); + byte maxB = (originalB + fuzziness).ToByte(); + newImage = new Bitmap(image); int width = image.Width; int height = image.Height; @@ -103,11 +121,11 @@ namespace ImageProcessor.Processors byte currentA = currentColor.A; // Test whether it is in the expected range. - if (currentR <= originalR + fuzziness && currentR >= originalR - fuzziness) + if (ImageMaths.InRange(currentR, minR, maxR)) { - if (currentG <= originalG + fuzziness && currentG >= originalG - fuzziness) + if (ImageMaths.InRange(currentG, minG, maxG)) { - if (currentB <= originalB + fuzziness && currentB >= originalB - fuzziness) + if (ImageMaths.InRange(currentB, minB, maxB)) { // Ensure the values are within an acceptable byte range // and set the new value. @@ -116,7 +134,11 @@ namespace ImageProcessor.Processors byte b = (originalB - currentB + replacementB).ToByte(); // Allow replacement with transparent color. - byte a = currentA != replacementA ? replacementA : currentA; + byte a = currentA; + if (originalA != replacementA) + { + a = replacementA; + } // ReSharper disable once AccessToDisposedClosure fastBitmap.SetPixel(x, y, Color.FromArgb(a, r, g, b)); diff --git a/src/ImageProcessor/Processors/Watermark.cs b/src/ImageProcessor/Processors/Watermark.cs index 5d0da759ec..d40a2b6199 100644 --- a/src/ImageProcessor/Processors/Watermark.cs +++ b/src/ImageProcessor/Processors/Watermark.cs @@ -72,6 +72,7 @@ namespace ImageProcessor.Processors int opacity = Math.Min((int)Math.Ceiling((textLayer.Opacity / 100f) * 255), 255); int fontSize = textLayer.FontSize; FontStyle fontStyle = textLayer.Style; + bool fallbackUsed = false; using (Graphics graphics = Graphics.FromImage(newImage)) { @@ -79,6 +80,12 @@ namespace ImageProcessor.Processors { using (StringFormat drawFormat = new StringFormat()) { + StringFormatFlags? formatFlags = this.GetFlags(textLayer); + if (formatFlags != null) + { + drawFormat.FormatFlags = formatFlags.Value; + } + using (Brush brush = new SolidBrush(Color.FromArgb(opacity, textLayer.FontColor))) { Point? origin = textLayer.Position; @@ -89,9 +96,13 @@ namespace ImageProcessor.Processors // We need to ensure that there is a position set for the watermark if (origin == null) { - int x = (int)(image.Width - textSize.Width) / 2; + int x = textLayer.RightToLeft + ? 0 + : (int)(image.Width - textSize.Width) / 2; int y = (int)(image.Height - textSize.Height) / 2; origin = new Point(x, y); + + fallbackUsed = true; } // Set the hinting and draw the text. @@ -114,14 +125,28 @@ namespace ImageProcessor.Processors Point shadowPoint = new Point(origin.Value.X + shadowDiff, origin.Value.Y + shadowDiff); // Set the bounds so any overlapping text will wrap. - bounds = new RectangleF(shadowPoint, new SizeF(image.Width - shadowPoint.X, image.Height - shadowPoint.Y)); + if (textLayer.RightToLeft && fallbackUsed) + { + bounds = new RectangleF(shadowPoint, new SizeF(image.Width - ((int)(image.Width - textSize.Width) / 2) - shadowPoint.X, image.Height - shadowPoint.Y)); + } + else + { + bounds = new RectangleF(shadowPoint, new SizeF(image.Width - shadowPoint.X, image.Height - shadowPoint.Y)); + } graphics.DrawString(text, font, shadowBrush, bounds, drawFormat); } } // Set the bounds so any overlapping text will wrap. - bounds = new RectangleF(origin.Value, new SizeF(image.Width - origin.Value.X, image.Height - origin.Value.Y)); + if (textLayer.RightToLeft && fallbackUsed) + { + bounds = new RectangleF(origin.Value, new SizeF(image.Width - ((int)(image.Width - textSize.Width) / 2), image.Height - origin.Value.Y)); + } + else + { + bounds = new RectangleF(origin.Value, new SizeF(image.Width - origin.Value.X, image.Height - origin.Value.Y)); + } graphics.DrawString(text, font, brush, bounds, drawFormat); } @@ -177,5 +202,34 @@ namespace ImageProcessor.Processors } } } + + /// + /// Returns the correct flags for the given text layer. + /// + /// + /// The to return the flags for. + /// + /// + /// The . + /// + private StringFormatFlags? GetFlags(TextLayer textLayer) + { + if (textLayer.Vertical && textLayer.RightToLeft) + { + return StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft; + } + + if (textLayer.Vertical) + { + return StringFormatFlags.DirectionVertical; + } + + if (textLayer.RightToLeft) + { + return StringFormatFlags.DirectionRightToLeft; + } + + return null; + } } } \ No newline at end of file diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs index f287baae4d..520d28502a 100644 --- a/src/ImageProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -41,8 +41,8 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")] [assembly: InternalsVisibleTo("ImageProcessor.UnitTests")] [assembly: InternalsVisibleTo("ImageProcessor.Web")]