Browse Source

Fix constructor and add cors support.

Former-commit-id: bb6daf831ba9b3efeeef392cf7b7eb53c7358030
Former-commit-id: 568e90c5570902dd0a2467960d91cf69d8036629
Former-commit-id: 24eaa11c52350199f263d023c763e9abb600bede
af/merge-core
James South 11 years ago
parent
commit
da37031cf8
  1. 2
      build/NuSpecs/ImageProcessor.nuspec
  2. 2
      build/build.xml
  3. 150
      src/ImageProcessor.Playground/Program.cs
  4. 3
      src/ImageProcessor.Playground/images/input/blue-balloon.jpg
  5. 1
      src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs
  6. 12
      src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs
  7. 24
      src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs
  8. 37
      src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs
  9. 102
      src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs
  10. 1
      src/ImageProcessor.sln.DotSettings
  11. 19
      src/ImageProcessor/ImageFactory.cs
  12. 1
      src/ImageProcessor/ImageProcessor.csproj
  13. 19
      src/ImageProcessor/Imaging/Colors/YCbCrColor.cs
  14. 43
      src/ImageProcessor/Imaging/Helpers/Adjustments.cs
  15. 35
      src/ImageProcessor/Imaging/Helpers/ImageMaths.cs
  16. 58
      src/ImageProcessor/Imaging/Helpers/PixelOperations.cs
  17. 16
      src/ImageProcessor/Imaging/TextLayer.cs
  18. 262
      src/ImageProcessor/Processors/Halftone - Copy.cs
  19. 32
      src/ImageProcessor/Processors/ReplaceColor.cs
  20. 60
      src/ImageProcessor/Processors/Watermark.cs
  21. 4
      src/ImageProcessor/Properties/AssemblyInfo.cs

2
build/NuSpecs/ImageProcessor.nuspec

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>ImageProcessor</id>
<version>2.2.1.0</version>
<version>2.2.2.0</version>
<title>ImageProcessor</title>
<authors>James South</authors>
<owners>James South</owners>

2
build/build.xml

@ -1,7 +1,7 @@
<projects>
<project>
<name>ImageProcessor</name>
<version>2.2.1.0</version>
<version>2.2.2.0</version>
<folder>..\src\ImageProcessor</folder>
<projfile>ImageProcessor.csproj</projfile>
<outputs>

150
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<FileInfo> files = GetFilesByExtensions(di, ".gif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png", ".jpg", ".jpeg");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png");
//IEnumerable<FileInfo> 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();
}

3
src/ImageProcessor.Playground/images/input/blue-balloon.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a83d4e0e313a749cb054c61b3c6f9dcd63546e2880e2221f23e39865ba3bed4b
size 59460

1
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);

12
src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs

@ -1,4 +1,14 @@
namespace ImageProcessor.UnitTests.Imaging.Helpers
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageMathsUnitTests.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Test harness for the image math unit tests
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.UnitTests.Imaging.Helpers
{
using System.Drawing;
using FluentAssertions;

24
src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs

@ -144,6 +144,15 @@ namespace ImageProcessor.Web.Configuration
});
}
/// <summary>
/// Retrieves the security configuration section from the current application configuration.
/// </summary>
/// <returns>The security configuration section from the current application configuration. </returns>
internal ImageSecuritySection GetImageSecuritySection()
{
return imageSecuritySection ?? (imageSecuritySection = ImageSecuritySection.GetConfiguration());
}
/// <summary>
/// Retrieves the processing configuration section from the current application configuration.
/// </summary>
@ -162,15 +171,6 @@ namespace ImageProcessor.Web.Configuration
return imageCacheSection ?? (imageCacheSection = ImageCacheSection.GetConfiguration());
}
/// <summary>
/// Retrieves the security configuration section from the current application configuration.
/// </summary>
/// <returns>The security configuration section from the current application configuration. </returns>
private static ImageSecuritySection GetImageSecuritySection()
{
return imageSecuritySection ?? (imageSecuritySection = ImageSecuritySection.GetConfiguration());
}
#region GraphicesProcessors
/// <summary>
/// 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
/// </returns>
private Dictionary<string, string> GetServiceSettings(string name)
{
ImageSecuritySection.ServiceElement serviceElement = GetImageSecuritySection()
ImageSecuritySection.ServiceElement serviceElement = this.GetImageSecuritySection()
.ImageServices
.Cast<ImageSecuritySection.ServiceElement>()
.FirstOrDefault(x => x.Name == name);
@ -400,7 +400,7 @@ namespace ImageProcessor.Web.Configuration
/// </returns>
private Uri[] GetServiceWhitelist(string name)
{
ImageSecuritySection.ServiceElement serviceElement = GetImageSecuritySection()
ImageSecuritySection.ServiceElement serviceElement = this.GetImageSecuritySection()
.ImageServices
.Cast<ImageSecuritySection.ServiceElement>()
.FirstOrDefault(x => x.Name == name);

37
src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs

@ -22,6 +22,20 @@ namespace ImageProcessor.Web.Configuration
/// </summary>
public sealed class ImageSecuritySection : ConfigurationSection
{
/// <summary>
/// Gets the <see cref="CORSOriginElement"/>
/// </summary>
/// <value>The <see cref="CORSOriginElement"/></value>
[ConfigurationProperty("cors", IsRequired = false)]
public CORSOriginElement CORSOrigin
{
get
{
object o = this["cors"];
return o as CORSOriginElement;
}
}
/// <summary>
/// Gets the <see cref="ServiceElementCollection"/>
/// </summary>
@ -213,6 +227,27 @@ namespace ImageProcessor.Web.Configuration
}
}
/// <summary>
/// Represents a CORSOriginsElement configuration element within the configuration.
/// </summary>
public class CORSOriginElement : ConfigurationElement
{
/// <summary>
/// Gets the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.WhiteListElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.WhiteListElementCollection"/>.
/// </value>
[ConfigurationProperty("whitelist", IsRequired = false)]
public WhiteListElementCollection WhiteList
{
get
{
return this["whitelist"] as WhiteListElementCollection;
}
}
}
/// <summary>
/// Represents a whitelist collection configuration element within the configuration.
/// </summary>
@ -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; }
}

102
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));
}
/// <summary>
/// Gets a value indicating whether the current origin request passes sanitizing rules.
/// </summary>
/// <param name="path">
/// The image path.
/// </param>
/// <returns>
/// <c>True</c> if the request is valid; otherwise, <c>False</c>.
/// </returns>
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
}
}

1
src/ImageProcessor.sln.DotSettings

@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BGRA/@EntryIndexedValue">BGRA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPP/@EntryIndexedValue">BPP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CORS/@EntryIndexedValue">CORS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DT/@EntryIndexedValue">DT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FPX/@EntryIndexedValue">FPX</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FR/@EntryIndexedValue">FR</s:String>

19
src/ImageProcessor/ImageFactory.cs

@ -59,6 +59,17 @@ namespace ImageProcessor
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ImageFactory"/> class.
/// </summary>
/// <param name="preserveExifData">
/// Whether to preserve exif metadata. Defaults to false.
/// </param>
public ImageFactory(bool preserveExifData = false)
: this(preserveExifData, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFactory"/> class.
/// </summary>
@ -68,7 +79,7 @@ namespace ImageProcessor
/// <param name="fixGamma">
/// Whether to fix the gamma component of the image. Defaults to true.
/// </param>
public ImageFactory(bool preserveExifData = false, bool fixGamma = true)
public ImageFactory(bool preserveExifData, bool fixGamma)
{
this.PreserveExifData = preserveExifData;
this.ExifPropertyItems = new ConcurrentDictionary<int, PropertyItem>();
@ -122,6 +133,11 @@ namespace ImageProcessor
/// </summary>
public bool FixGamma { get; set; }
/// <summary>
/// Gets or the current gamma value.
/// </summary>
public float CurrentGamma { get; private set; }
/// <summary>
/// Gets or sets the exif property items.
/// </summary>
@ -635,6 +651,7 @@ namespace ImageProcessor
value = 2.2F;
}
this.CurrentGamma = value;
Gamma gamma = new Gamma { DynamicParameter = value };
this.CurrentImageFormat.ApplyProcessor(gamma.ProcessImage, this);
}

1
src/ImageProcessor/ImageProcessor.csproj

@ -169,6 +169,7 @@
<Compile Include="Imaging\AnchorPosition.cs" />
<Compile Include="Imaging\Formats\IQuantizableImageFormat.cs" />
<Compile Include="Imaging\Helpers\ImageMaths.cs" />
<Compile Include="Imaging\Helpers\PixelOperations.cs" />
<Compile Include="Imaging\ImageLayer.cs" />
<Compile Include="Imaging\MetaData\ExifPropertyTagType.cs" />
<Compile Include="Imaging\Convolution.cs" />

19
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);
}
/// <summary>
@ -69,7 +69,7 @@ namespace ImageProcessor.Imaging.Colors
/// <summary>
/// Gets the U chroma component.
/// <remarks>A value ranging between -255 and 255.</remarks>
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cb
{
@ -81,7 +81,7 @@ namespace ImageProcessor.Imaging.Colors
/// <summary>
/// Gets the V chroma component.
/// <remarks>A value ranging between -255 and 255.</remarks>
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCrColor"/> to a
/// <see cref="CmykColor"/>.

43
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;
/// <summary>
/// Provides reusable adjustment methods to apply to images.
/// </summary>
@ -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;
}

35
src/ImageProcessor/Imaging/Helpers/ImageMaths.cs

@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Helpers
{
using System;
using System.Drawing;
using ImageProcessor.Imaging.Colors;
/// <summary>
@ -73,6 +74,40 @@ namespace ImageProcessor.Imaging.Helpers
return value;
}
/// <summary>
/// Returns value indicating whether the given number is with in the minimum and maximum
/// given range.
/// </summary>
/// <param name="value">
/// The The value to clamp.
/// </param>
/// <param name="min">
/// If <paramref name="include"/>
/// The minimum range value.
/// </param>
/// <param name="max">
/// The maximum range value.
/// </param>
/// <param name="include">
/// Whether to include the minimum and maximum values.
/// Defaults to true.
/// </param>
/// <typeparam name="T">
/// The <see cref="System.Type"/> to test.
/// </typeparam>
/// <returns>
/// True if the value falls within the maximum and minimum; otherwise, false.
/// </returns>
public static bool InRange<T>(T value, T min, T max, bool include = true) where T : IComparable<T>
{
if (include)
{
return (value.CompareTo(min) >= 0) && (value.CompareTo(max) <= 0);
}
return (value.CompareTo(min) > 0) && (value.CompareTo(max) < 0);
}
/// <summary>
/// Returns the given degrees converted to radians.
/// </summary>

58
src/ImageProcessor/Imaging/Helpers/PixelOperations.cs

@ -0,0 +1,58 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PixelOperations.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Performs per-pixel operations.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Helpers
{
using System;
using System.Drawing;
using ImageProcessor.Common.Extensions;
/// <summary>
/// Performs per-pixel operations.
/// </summary>
public static class PixelOperations
{
/// <summary>
/// Returns the given color adjusted by the given gamma value.
/// </summary>
/// <param name="color">
/// The <see cref="Color"/> to adjust.
/// </param>
/// <param name="value">
/// The gamma value - Between .1 and 5.
/// </param>
/// <returns>
/// The adjusted <see cref="Color"/>.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the given gamma value is out with the acceptable range.
/// </exception>
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);
}
}
}

16
src/ImageProcessor/Imaging/TextLayer.cs

@ -117,6 +117,16 @@ namespace ImageProcessor.Imaging
/// Gets or sets a value indicating whether a DropShadow should be drawn.
/// </summary>
public bool DropShadow { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the text should be rendered vertically.
/// </summary>
public bool Vertical { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the text should be rendered right to left.
/// </summary>
public bool RightToLeft { get; set; }
#endregion
/// <summary>
@ -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;
}
/// <summary>
@ -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;
}
}

262
src/ImageProcessor/Processors/Halftone - Copy.cs

@ -1,262 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Halftone.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// The halftone.
/// </summary>
class Halftone : IGraphicsProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Halftone"/> class.
/// </summary>
public Halftone()
{
this.Settings = new Dictionary<string, string>();
}
/// <summary>
/// Gets or sets the dynamic parameter.
/// </summary>
public dynamic DynamicParameter
{
get;
set;
}
/// <summary>
/// Gets or sets any additional settings required by the processor.
/// </summary>
public Dictionary<string, string> Settings
{
get;
set;
}
/// <summary>
/// The process image.
/// </summary>
/// <param name="factory">
/// The factory.
/// </param>
/// <returns>
/// The <see cref="Image"/>.
/// </returns>
/// <exception cref="ImageProcessingException">
/// </exception>
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;
}
}
}

32
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;
/// <summary>
/// Encapsulates methods allowing the replacement of a color within an image.
@ -69,11 +70,20 @@ namespace ImageProcessor.Processors
{
Tuple<Color, Color, int> 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));

60
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
}
}
}
/// <summary>
/// Returns the correct flags for the given text layer.
/// </summary>
/// <param name="textLayer">
/// The <see cref="TextLayer"/> to return the flags for.
/// </param>
/// <returns>
/// The <see cref="StringFormatFlags"/>.
/// </returns>
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;
}
}
}

4
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")]

Loading…
Cancel
Save