Browse Source

Adding overlay to Imageprocessor plus tidy up

touches #89


Former-commit-id: 730e210c4daeffe003c86faba3d62e17fe437cff
Former-commit-id: eec47cf2178eeff36597c1980b72f834410af164
pull/17/head
James South 11 years ago
parent
commit
9df91b15df
  1. 18
      src/ImageProcessor.Playground/Program.cs
  2. 16
      src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootstrapperTests.cs
  3. 21
      src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs
  4. 2
      src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj
  5. 5
      src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs
  6. 10
      src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs
  7. 6
      src/ImageProcessor.Web/Processors/Watermark.cs
  8. 24
      src/ImageProcessor/Common/Extensions/ImageExtensions.cs
  9. 21
      src/ImageProcessor/ImageFactory.cs
  10. 1
      src/ImageProcessor/ImageProcessor.csproj
  11. 2
      src/ImageProcessor/Imaging/Helpers/Adjustments.cs
  12. 11
      src/ImageProcessor/Imaging/ImageLayer.cs
  13. 16
      src/ImageProcessor/Imaging/Resizer.cs
  14. 11
      src/ImageProcessor/Imaging/TextLayer.cs
  15. 53
      src/ImageProcessor/Processors/Overlay.cs
  16. 2
      src/ImageProcessor/Processors/Resize.cs
  17. 8
      src/ImageProcessor/Processors/Watermark.cs

18
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;
/// <summary>
/// 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<FileInfo> files = GetFilesByExtensions(di, ".png");
Image overlay = Image.FromFile(Path.Combine(resolvedPath, "monster.png"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cow_PNG2140.png"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg");
//IEnumerable<FileInfo> 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();

16
src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootrapperTests.cs → src/ImageProcessor.UnitTests/Configuration/ImageProcessorBootstrapperTests.cs

@ -1,27 +1,35 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageProcessorBootstrapperTests.cs" company="James South">
// <copyright file="ImageProcessorBootrapperTests.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Test harness for the ImageProcessor bootstrapper tests
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.UnitTests.Configuration
{
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using ImageProcessor.Configuration;
using NUnit.Framework;
/// <summary>
/// Test harness for the ImageProcessor bootstrapper tests
/// </summary>
[TestFixture]
public class ImageProcessorBootrapperTests
public class ImageProcessorBootstrapperTests
{
/// <summary>
/// Test to see that the bootstrapper singleton is instantiated.
/// </summary>
[Test]
public void BoostrapperSingletonIsInstantiated()
public void BootstrapperSingletonIsInstantiated()
{
ImageProcessorBootstrapper.Instance.SupportedImageFormats.Count().Should().BeGreaterThan(0, "because there should be supported image formats");

21
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);
}
}
}

2
src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj

@ -55,7 +55,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AssertionHelpers.cs" />
<Compile Include="Configuration\ImageProcessorBootrapperTests.cs" />
<Compile Include="Configuration\ImageProcessorBootstrapperTests.cs" />
<Compile Include="ImageFactoryUnitTests.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Compile>

5
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;
/// <summary>
/// Test harness for the color classes.

10
src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs

@ -64,14 +64,14 @@ namespace ImageProcessor.Web.HttpModules
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
/// <summary>
/// The locker for preventing duplicate requests.
/// Whether to preserve exif meta data.
/// </summary>
private static readonly AsyncDuplicateLock Locker = new AsyncDuplicateLock();
private static bool? preserveExifMetaData;
/// <summary>
/// Whether to preserve exif meta data.
/// The locker for preventing duplicate requests.
/// </summary>
private static bool? preserveExifMetaData;
private readonly AsyncDuplicateLock locker = new AsyncDuplicateLock();
/// <summary>
/// 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;

6
src/ImageProcessor.Web/Processors/Watermark.cs

@ -178,7 +178,7 @@ namespace ImageProcessor.Web.Processors
/// <returns>
/// The correct <see cref="T:System.Drawing.Point"/>
/// </returns>
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;
}
/// <summary>
@ -217,8 +217,6 @@ namespace ImageProcessor.Web.Processors
}
}
return Color.Black;
}

24
src/ImageProcessor/Common/Extensions/ImageExtensions.cs

@ -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;
}
}
}

21
src/ImageProcessor/ImageFactory.cs

@ -705,6 +705,27 @@ namespace ImageProcessor
return this;
}
/// <summary>
/// Adds a image overlay to the current image.
/// </summary>
/// <param name="imageLayer">
/// The <see cref="T:ImageProcessor.Imaging.ImageLayer"/> containing the properties necessary to add
/// the image overlay to the image.
/// </param>
/// <returns>
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
public ImageFactory Overlay(ImageLayer imageLayer)
{
if (this.ShouldProcess)
{
Overlay watermark = new Overlay { DynamicParameter = imageLayer };
this.CurrentImageFormat.ApplyProcessor(watermark.ProcessImage, this);
}
return this;
}
/// <summary>
/// Pixelates an image with the given size.
/// </summary>

1
src/ImageProcessor/ImageProcessor.csproj

@ -126,7 +126,6 @@
<ItemGroup>
<Compile Include="Common\Extensions\AssemblyExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Extensions\ImageExtensions.cs" />
<Compile Include="Configuration\ImageProcessorBootstrapper.cs" />
<Compile Include="Common\Exceptions\ImageProcessingException.cs" />
<Compile Include="Common\Extensions\DoubleExtensions.cs" />

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

11
src/ImageProcessor/Imaging/ImageLayer.cs

@ -22,11 +22,6 @@ namespace ImageProcessor.Imaging
/// </summary>
private int opacity = 100;
/// <summary>
/// The position to start creating the text from.
/// </summary>
private Point position = Point.Empty;
/// <summary>
/// Gets or sets the image.
/// </summary>
@ -49,11 +44,7 @@ namespace ImageProcessor.Imaging
/// <summary>
/// Gets or sets the Position of the text layer.
/// </summary>
public Point Position
{
get { return this.position; }
set { this.position = value; }
}
public Point? Position { get; set; }
/// <summary>
/// Returns a value that indicates whether the specified object is an

16
src/ImageProcessor/Imaging/Resizer.cs

@ -60,7 +60,7 @@ namespace ImageProcessor.Imaging
/// <returns>
/// The resized <see cref="Image"/>.
/// </returns>
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
/// <returns>
/// The resized <see cref="Image"/>.
/// </returns>
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;
}
}
}

11
src/ImageProcessor/Imaging/TextLayer.cs

@ -43,11 +43,6 @@ namespace ImageProcessor.Imaging
/// The font size to render the text.
/// </summary>
private int fontSize = 48;
/// <summary>
/// The position to start creating the text from.
/// </summary>
private Point position = Point.Empty;
#endregion
#region Properties
@ -116,11 +111,7 @@ namespace ImageProcessor.Imaging
/// <summary>
/// Gets or sets the Position of the text layer.
/// </summary>
public Point Position
{
get { return this.position; }
set { this.position = value; }
}
public Point? Position { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a DropShadow should be drawn.

53
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;
/// <summary>
/// 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)
{

2
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)

8
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);
}

Loading…
Cancel
Save