diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 603aee091a..95f086331f 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -23,6 +23,7 @@ namespace ImageProcessor.PlayGround using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; using ImageProcessor.Imaging.Formats; + using ImageProcessor.Processors; /// /// The program. @@ -50,8 +51,9 @@ namespace ImageProcessor.PlayGround } // Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); - //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "circle.png")); - IEnumerable files = GetFilesByExtensions(di, ".png"); + Image overlay = Image.FromFile(Path.Combine(resolvedPath, "monster.png")); + //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cow_PNG2140.png")); + IEnumerable files = GetFilesByExtensions(di, ".jpg"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); foreach (FileInfo fileInfo in files) @@ -67,7 +69,7 @@ namespace ImageProcessor.PlayGround { using (ImageFactory imageFactory = new ImageFactory(true)) { - Size size = new Size(400, 400); + Size size = new Size(200, 200); ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false); //ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size) @@ -76,7 +78,13 @@ namespace ImageProcessor.PlayGround //}; // Load, resize, set the format and quality and save an image. imageFactory.Load(inStream) - //.Alpha(50) + //.Overlay(new ImageLayer + // { + // Image = overlay, + // Size = size, + // Opacity = 80 + // }) + .Alpha(50) //.BackgroundColor(Color.White) //.Resize(new Size((int)(size.Width * 1.1), 0)) //.ContentAwareResize(layer) @@ -99,7 +107,7 @@ namespace ImageProcessor.PlayGround //.Filter(MatrixFilters.HiSatch) //.Pixelate(8) //.GaussianSharpen(10) - .Format(new PngFormat() { IsIndexed = true }) + //.Format(new PngFormat() { IsIndexed = true }) .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); stopwatch.Stop(); diff --git a/src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootrapperTests.cs b/src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootstrapperTests.cs similarity index 71% rename from src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootrapperTests.cs rename to src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootstrapperTests.cs index 77f498d509..ab7df4e575 100644 --- a/src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootrapperTests.cs +++ b/src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootstrapperTests.cs @@ -1,27 +1,35 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // +// +// Test harness for the ImageProcessor bootstrapper tests +// // -------------------------------------------------------------------------------------------------------------------- + namespace ImageProcessor.UnitTests.Configuration { using System; using System.Linq; using FluentAssertions; - using NUnit.Framework; using ImageProcessor.Configuration; + using NUnit.Framework; + /// /// Test harness for the ImageProcessor bootstrapper tests /// [TestFixture] - public class ImageProcessorBootrapperTests + public class ImageProcessorBootstrapperTests { + /// + /// Test to see that the bootstrapper singleton is instantiated. + /// [Test] - public void BoostrapperSingletonIsInstantiated() + public void BootstrapperSingletonIsInstantiated() { ImageProcessorBootstrapper.Instance.SupportedImageFormats.Count().Should().BeGreaterThan(0, "because there should be supported image formats"); diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 14ca2e062a..065cd86c01 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -17,6 +17,7 @@ namespace ImageProcessor.UnitTests using ImageProcessor.Imaging; using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; + using ImageProcessor.Imaging.Formats; using NUnit.Framework; @@ -126,7 +127,25 @@ namespace ImageProcessor.UnitTests { Image original = (Image)imageFactory.Image.Clone(); imageFactory.Alpha(50); - AssertionHelpers.AssertImagesAreDifferent(original, imageFactory.Image, "because the alpha operation should have been applied on {0}", imageFactory.ImagePath); + + ISupportedImageFormat format = imageFactory.CurrentImageFormat; + + if (format.GetType() == typeof(BitmapFormat)) + { + AssertionHelpers.AssertImagesAreIdentical( + original, + imageFactory.Image, + "because the alpha operation should not have been applied on {0}", + imageFactory.ImagePath); + } + else + { + AssertionHelpers.AssertImagesAreDifferent( + original, + imageFactory.Image, + "because the alpha operation should have been applied on {0}", + imageFactory.ImagePath); + } } } diff --git a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj index d106c6d949..b1ae1daaf5 100644 --- a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj +++ b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj @@ -55,7 +55,7 @@ - + PreserveNewest diff --git a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs index 5f77561723..76f9fd68f7 100644 --- a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs @@ -12,9 +12,12 @@ namespace ImageProcessor.UnitTests.Imaging { using System.Diagnostics.CodeAnalysis; using System.Drawing; + + using FluentAssertions; + using ImageProcessor.Imaging.Colors; + using NUnit.Framework; - using FluentAssertions; /// /// Test harness for the color classes. diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index e222c52af6..f8f8f4bf5a 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -64,14 +64,14 @@ namespace ImageProcessor.Web.HttpModules private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); /// - /// The locker for preventing duplicate requests. + /// Whether to preserve exif meta data. /// - private static readonly AsyncDuplicateLock Locker = new AsyncDuplicateLock(); + private static bool? preserveExifMetaData; /// - /// Whether to preserve exif meta data. + /// The locker for preventing duplicate requests. /// - private static bool? preserveExifMetaData; + private readonly AsyncDuplicateLock locker = new AsyncDuplicateLock(); /// /// A value indicating whether this instance of the given entity has been disposed. @@ -366,7 +366,7 @@ namespace ImageProcessor.Web.HttpModules // Process the image. using (ImageFactory imageFactory = new ImageFactory(preserveExifMetaData != null && preserveExifMetaData.Value)) { - using (await Locker.LockAsync(cachedPath)) + using (await this.locker.LockAsync(cachedPath)) { byte[] imageBuffer; diff --git a/src/ImageProcessor.Web/Processors/Watermark.cs b/src/ImageProcessor.Web/Processors/Watermark.cs index b5842ea7ee..b61bf13b0c 100644 --- a/src/ImageProcessor.Web/Processors/Watermark.cs +++ b/src/ImageProcessor.Web/Processors/Watermark.cs @@ -178,7 +178,7 @@ namespace ImageProcessor.Web.Processors /// /// The correct /// - private Point ParsePosition(string input) + private Point? ParsePosition(string input) { foreach (Match match in PositionRegex.Matches(input)) { @@ -193,7 +193,7 @@ namespace ImageProcessor.Web.Processors } } - return Point.Empty; + return null; } /// @@ -217,8 +217,6 @@ namespace ImageProcessor.Web.Processors } } - - return Color.Black; } diff --git a/src/ImageProcessor/Common/Extensions/ImageExtensions.cs b/src/ImageProcessor/Common/Extensions/ImageExtensions.cs deleted file mode 100644 index b0ffd301d2..0000000000 --- a/src/ImageProcessor/Common/Extensions/ImageExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ - - -namespace ImageProcessor.Common.Extensions -{ - using System.Drawing; - using System.Drawing.Imaging; - - public static class ImageExtensions - { - public static Image ChangePixelFormat(this Image image, PixelFormat format) - { - Bitmap clone = new Bitmap(image.Width, image.Height, format); - clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); - - using (Graphics graphics = Graphics.FromImage(clone)) - { - graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height)); - } - - image = new Bitmap(clone); - return image; - } - } -} diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 02d277bad4..c5ce4f630f 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -705,6 +705,27 @@ namespace ImageProcessor return this; } + /// + /// Adds a image overlay to the current image. + /// + /// + /// The containing the properties necessary to add + /// the image overlay to the image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Overlay(ImageLayer imageLayer) + { + if (this.ShouldProcess) + { + Overlay watermark = new Overlay { DynamicParameter = imageLayer }; + this.CurrentImageFormat.ApplyProcessor(watermark.ProcessImage, this); + } + + return this; + } + /// /// Pixelates an image with the given size. /// diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 8b855632f5..e750cf552f 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -126,7 +126,6 @@ - diff --git a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs index 31634e7b5c..bef3e864ce 100644 --- a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs +++ b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs @@ -15,8 +15,6 @@ namespace ImageProcessor.Imaging.Helpers using System.Drawing.Imaging; using System.Threading.Tasks; - using ImageProcessor.Imaging.Colors; - /// /// Provides reusable adjustment methods to apply to images. /// diff --git a/src/ImageProcessor/Imaging/ImageLayer.cs b/src/ImageProcessor/Imaging/ImageLayer.cs index d33e8fca24..23bd4a75fb 100644 --- a/src/ImageProcessor/Imaging/ImageLayer.cs +++ b/src/ImageProcessor/Imaging/ImageLayer.cs @@ -22,11 +22,6 @@ namespace ImageProcessor.Imaging /// private int opacity = 100; - /// - /// The position to start creating the text from. - /// - private Point position = Point.Empty; - /// /// Gets or sets the image. /// @@ -49,11 +44,7 @@ namespace ImageProcessor.Imaging /// /// Gets or sets the Position of the text layer. /// - public Point Position - { - get { return this.position; } - set { this.position = value; } - } + public Point? Position { get; set; } /// /// Returns a value that indicates whether the specified object is an diff --git a/src/ImageProcessor/Imaging/Resizer.cs b/src/ImageProcessor/Imaging/Resizer.cs index 39a0af8e30..eaaedbb16f 100644 --- a/src/ImageProcessor/Imaging/Resizer.cs +++ b/src/ImageProcessor/Imaging/Resizer.cs @@ -60,7 +60,7 @@ namespace ImageProcessor.Imaging /// /// The resized . /// - public Image ResizeImage(Image source) + public Bitmap ResizeImage(Image source) { int width = this.ResizeLayer.Size.Width; int height = this.ResizeLayer.Size.Height; @@ -111,7 +111,7 @@ namespace ImageProcessor.Imaging /// /// The resized . /// - private Image ResizeImage( + private Bitmap ResizeImage( Image source, int width, int height, @@ -275,7 +275,7 @@ namespace ImageProcessor.Imaging if (width == 0) { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); width = destinationWidth; } @@ -300,7 +300,7 @@ namespace ImageProcessor.Imaging if (reject) { - return source; + return (Bitmap)source; } } @@ -309,7 +309,7 @@ namespace ImageProcessor.Imaging // Exit if upscaling is not allowed. if ((width > sourceWidth || height > sourceHeight) && upscale == false && resizeMode != ResizeMode.Stretch) { - return source; + return (Bitmap)source; } newImage = new Bitmap(width, height); @@ -340,8 +340,8 @@ namespace ImageProcessor.Imaging using (ImageAttributes wrapMode = new ImageAttributes()) { wrapMode.SetWrapMode(WrapMode.TileFlipXY); - Rectangle destRect = new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - graphics.DrawImage(source, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); + Rectangle destinationRectangle = new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + graphics.DrawImage(source, destinationRectangle, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); } // Reassign the image. @@ -360,7 +360,7 @@ namespace ImageProcessor.Imaging throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); } - return source; + return (Bitmap)source; } } } diff --git a/src/ImageProcessor/Imaging/TextLayer.cs b/src/ImageProcessor/Imaging/TextLayer.cs index 22c854ec24..c934791d71 100644 --- a/src/ImageProcessor/Imaging/TextLayer.cs +++ b/src/ImageProcessor/Imaging/TextLayer.cs @@ -43,11 +43,6 @@ namespace ImageProcessor.Imaging /// The font size to render the text. /// private int fontSize = 48; - - /// - /// The position to start creating the text from. - /// - private Point position = Point.Empty; #endregion #region Properties @@ -116,11 +111,7 @@ namespace ImageProcessor.Imaging /// /// Gets or sets the Position of the text layer. /// - public Point Position - { - get { return this.position; } - set { this.position = value; } - } + public Point? Position { get; set; } /// /// Gets or sets a value indicating whether a DropShadow should be drawn. diff --git a/src/ImageProcessor/Processors/Overlay.cs b/src/ImageProcessor/Processors/Overlay.cs index da499710e4..bbcd703493 100644 --- a/src/ImageProcessor/Processors/Overlay.cs +++ b/src/ImageProcessor/Processors/Overlay.cs @@ -13,9 +13,11 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; + using System.Drawing.Drawing2D; using ImageProcessor.Common.Exceptions; using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Helpers; /// /// Adds an image overlay to the current image. @@ -67,15 +69,60 @@ namespace ImageProcessor.Processors { newImage = new Bitmap(image); ImageLayer imageLayer = this.DynamicParameter; - Image overlay = imageLayer.Image; + Bitmap overlay = new Bitmap(imageLayer.Image); + + // Set the resolution of the overlay and the image to match. + overlay.SetResolution(newImage.HorizontalResolution, newImage.VerticalResolution); + Size size = imageLayer.Size; - int opacity = Math.Min((int)Math.Ceiling((imageLayer.Opacity / 100f) * 255), 255); + int width = image.Width; + int height = image.Height; + int overlayWidth = Math.Min(image.Size.Width, size.Width); + int overlayHeight = Math.Min(image.Size.Height, size.Height); + Point? position = imageLayer.Position; + int opacity = imageLayer.Opacity; + if (image.Size != overlay.Size) + { + // Find the maximum possible dimensions and resize the image. + ResizeLayer layer = new ResizeLayer(new Size(overlayWidth, overlayHeight), ResizeMode.Max); + overlay = new Resizer(layer).ResizeImage(overlay); + overlayWidth = overlay.Width; + overlayHeight = overlay.Height; + } + + // Figure out bounds. + Rectangle parent = new Rectangle(0, 0, width, height); + Rectangle child = new Rectangle(0, 0, overlayWidth, overlayHeight); + + // Apply opacity. + if (opacity < 100) + { + overlay = Adjustments.Alpha(overlay, opacity); + } + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + if (position != null) + { + // Draw the image in position catering for overflow. + graphics.DrawImage(overlay, new Point(Math.Min(position.Value.X, width - overlayWidth), Math.Min(position.Value.Y, height - overlayHeight))); + } + else + { + RectangleF centered = ImageMaths.CenteredRectangle(parent, child); + graphics.DrawImage(overlay, new PointF(centered.X, centered.Y)); + } + } image.Dispose(); image = newImage; - } catch (Exception ex) { diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index bfab0f5d74..ace278a20c 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -89,7 +89,7 @@ namespace ImageProcessor.Processors resizeLayer.MaxSize = maxSize; Resizer resizer = new Resizer(resizeLayer); - newImage = (Bitmap)resizer.ResizeImage(image); + newImage = resizer.ResizeImage(image); // Check that the original image has not been returned. if (newImage != image) diff --git a/src/ImageProcessor/Processors/Watermark.cs b/src/ImageProcessor/Processors/Watermark.cs index e14ed7cb53..5d0da759ec 100644 --- a/src/ImageProcessor/Processors/Watermark.cs +++ b/src/ImageProcessor/Processors/Watermark.cs @@ -81,13 +81,13 @@ namespace ImageProcessor.Processors { using (Brush brush = new SolidBrush(Color.FromArgb(opacity, textLayer.FontColor))) { - Point origin = textLayer.Position; + Point? origin = textLayer.Position; // Work out the size of the text. SizeF textSize = graphics.MeasureString(text, font, new SizeF(image.Width, image.Height), drawFormat); // We need to ensure that there is a position set for the watermark - if (origin == Point.Empty) + if (origin == null) { int x = (int)(image.Width - textSize.Width) / 2; int y = (int)(image.Height - textSize.Height) / 2; @@ -111,7 +111,7 @@ namespace ImageProcessor.Processors // Scale the shadow position to match the font size. // Magic number but it's based on artistic preference. int shadowDiff = (int)Math.Ceiling(fontSize / 24f); - Point shadowPoint = new Point(origin.X + shadowDiff, origin.Y + shadowDiff); + 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)); @@ -121,7 +121,7 @@ namespace ImageProcessor.Processors } // Set the bounds so any overlapping text will wrap. - bounds = new RectangleF(origin, new SizeF(image.Width - origin.X, image.Height - origin.Y)); + bounds = new RectangleF(origin.Value, new SizeF(image.Width - origin.Value.X, image.Height - origin.Value.Y)); graphics.DrawString(text, font, brush, bounds, drawFormat); }