mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: e355d9dc0b3efaee0c8c54435090041dac447c45 Former-commit-id: 7ef69bb6c4f28b1e6ae704d84963c4aa7bd53073 Former-commit-id: 6a152f3985346d0b61a2f654f7eebbda8f46f265af/merge-core
11 changed files with 372 additions and 38 deletions
@ -0,0 +1,70 @@ |
|||
// <copyright file="Contrast.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessor.Filters |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// An <see cref="IImageFilter"/> to change the contrast of an <see cref="Image"/>.
|
|||
/// </summary>
|
|||
public class Contrast : ParallelImageFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Contrast"/> class.
|
|||
/// </summary>
|
|||
/// <param name="contrast">The new contrast of the image. Must be between -100 and 100.</param>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="contrast"/> is less than -100 is greater than 100.
|
|||
/// </exception>
|
|||
public Contrast(int contrast) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(contrast, -100, 100, nameof(contrast)); |
|||
this.Value = contrast; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the contrast value.
|
|||
/// </summary>
|
|||
public int Value { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY) |
|||
{ |
|||
double contrast = (100.0 + this.Value) / 100.0; |
|||
|
|||
for (int y = startY; y < endY; y++) |
|||
{ |
|||
for (int x = rectangle.X; x < rectangle.Right; x++) |
|||
{ |
|||
Bgra color = source[x, y]; |
|||
|
|||
double r = color.R / 255.0; |
|||
r -= 0.5; |
|||
r *= contrast; |
|||
r += 0.5; |
|||
r *= 255; |
|||
r = r.Clamp(0, 255); |
|||
|
|||
double g = color.G / 255.0; |
|||
g -= 0.5; |
|||
g *= contrast; |
|||
g += 0.5; |
|||
g *= 255; |
|||
g = g.Clamp(0, 255); |
|||
|
|||
double b = color.B / 255.0; |
|||
b -= 0.5; |
|||
b *= contrast; |
|||
b += 0.5; |
|||
b *= 255; |
|||
b = b.Clamp(0, 255); |
|||
|
|||
target[x, y] = new Bgra((byte)b, (byte)g, (byte)r, color.A); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
namespace ImageProcessor.Filters |
|||
{ |
|||
/// <summary>
|
|||
/// Image processing filter interface.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The interface defines the set of methods, which should be
|
|||
/// provided by all image processing filters. Methods of this interface
|
|||
/// manipulate the original image.
|
|||
/// </remarks>
|
|||
public interface IImageFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Apply filter to an image at the area of the specified rectangle.
|
|||
/// </summary>
|
|||
/// <param name="target">Target image to apply filter to.</param>
|
|||
/// <param name="source">The source image. Cannot be null.</param>
|
|||
/// <param name="rectangle">The rectangle, which defines the area of the
|
|||
/// image where the filter should be applied to.</param>
|
|||
/// <remarks>The method keeps the source image unchanged and returns the
|
|||
/// the result of image processing filter as new image.</remarks>
|
|||
/// <exception cref="System.ArgumentNullException">
|
|||
/// <paramref name="target"/>
|
|||
/// is null.
|
|||
/// - or -
|
|||
/// <paramref name="source"/>
|
|||
/// is null.
|
|||
/// </exception>
|
|||
/// <exception cref="System.ArgumentException">
|
|||
/// <paramref name="rectangle"/> doesnt fit the dimension of the image.
|
|||
/// </exception>
|
|||
void Apply(ImageBase target, ImageBase source, Rectangle rectangle); |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
// <copyright file="ImageFilterExtensions.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessor.Filters |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Exstension methods for performing filtering methods again an image.
|
|||
/// </summary>
|
|||
public static class ImageFilterExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the collection of filters to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="filters">Any filters to apply to the image.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
public static Image Filter(this Image source, params IImageFilter[] filters) => Filter(source, source.Bounds, filters); |
|||
|
|||
/// <summary>
|
|||
/// Applies the collection of filters to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="rectangle">
|
|||
/// The rectangle defining the bounds of the pixels the image filter with adjust.</param>
|
|||
/// <param name="filters">Any filters to apply to the image.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
public static Image Filter(this Image source, Rectangle rectangle, params IImageFilter[] filters) |
|||
{ |
|||
// ReSharper disable once LoopCanBeConvertedToQuery
|
|||
foreach (IImageFilter filter in filters) |
|||
{ |
|||
source = PerformAction(source, true, (sourceImage, targetImage) => filter.Apply(targetImage, sourceImage, rectangle)); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Alters the contrast component of the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="amount">The new contrast of the image. Must be between -100 and 100.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
public static Image Contrast(this Image source, int amount) => source.Filter(new Contrast(amount)); |
|||
|
|||
/// <summary>
|
|||
/// Performs the given action on the source image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to perform the action against.</param>
|
|||
/// <param name="clone">Whether to clone the image.</param>
|
|||
/// <param name="action">The <see cref="Action"/> to perform against the image.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
private static Image PerformAction(Image source, bool clone, Action<ImageBase, ImageBase> action) |
|||
{ |
|||
Image transformedImage = clone ? new Image(source) : new Image(source.Width, source.Height); |
|||
action(source, transformedImage); |
|||
|
|||
for (int i = 0; i < source.Frames.Count; i++) |
|||
{ |
|||
ImageFrame frame = source.Frames[i]; |
|||
ImageFrame tranformedFrame = new ImageFrame(frame); |
|||
action(frame, tranformedFrame); |
|||
|
|||
if (!clone) |
|||
{ |
|||
transformedImage.Frames.Add(tranformedFrame); |
|||
} |
|||
else |
|||
{ |
|||
transformedImage.Frames[i] = tranformedFrame; |
|||
} |
|||
} |
|||
|
|||
return transformedImage; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
namespace ImageProcessor.Filters |
|||
{ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Allows the application of filters using prallel processing.
|
|||
/// </summary>
|
|||
public abstract class ParallelImageFilter : IImageFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the count of workers to run the filter in parallel.
|
|||
/// </summary>
|
|||
public int Parallelism { get; set; } = Environment.ProcessorCount; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Apply(ImageBase target, ImageBase source, Rectangle rectangle) |
|||
{ |
|||
this.OnApply(); |
|||
|
|||
if (this.Parallelism > 1) |
|||
{ |
|||
int partitionCount = this.Parallelism; |
|||
|
|||
Task[] tasks = new Task[partitionCount]; |
|||
|
|||
for (int p = 0; p < partitionCount; p++) |
|||
{ |
|||
int current = p; |
|||
tasks[p] = Task.Run(() => |
|||
{ |
|||
int batchSize = rectangle.Height / partitionCount; |
|||
int yStart = rectangle.Y + (current * batchSize); |
|||
int yEnd = current == partitionCount - 1 ? rectangle.Bottom : yStart + batchSize; |
|||
|
|||
this.Apply(target, source, rectangle, yStart, yEnd); |
|||
}); |
|||
} |
|||
|
|||
Task.WaitAll(tasks); |
|||
} |
|||
else |
|||
{ |
|||
this.Apply(target, source, rectangle, rectangle.Y, rectangle.Bottom); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This method is called before the filter is applied to prepare the filter.
|
|||
/// </summary>
|
|||
protected virtual void OnApply() |
|||
{ |
|||
} |
|||
|
|||
protected abstract void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// <copyright file="IImageSampler.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessor.Samplers |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the methods required for all image sampling (resizing) algorithms.
|
|||
/// </summary>
|
|||
public interface IImageSampler |
|||
{ |
|||
/// <summary>
|
|||
/// Resizes the specified source image by creating a new image with
|
|||
/// the specified size which is a resized version of the passed image.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="height">The height.</param>
|
|||
void Sample(ImageBase source, ImageBase target, int width, int height); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
|
|||
namespace ImageProcessor.Tests.Filters |
|||
{ |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
|
|||
using ImageProcessor.Filters; |
|||
|
|||
using Xunit; |
|||
|
|||
public class FilterTests |
|||
{ |
|||
public static readonly List<string> Files = new List<string> |
|||
{ |
|||
{ "../../TestImages/Formats/Jpg/Backdrop.jpg"}, |
|||
{ "../../TestImages/Formats/Bmp/Car.bmp" }, |
|||
{ "../../TestImages/Formats/Png/cmyk.png" }, |
|||
//{ "../../TestImages/Formats/Gif/a.gif" },
|
|||
//{ "../../TestImages/Formats/Gif/leaf.gif" },
|
|||
//{ "../../TestImages/Formats/Gif/ani.gif" },
|
|||
//{ "../../TestImages/Formats/Gif/ani2.gif" },
|
|||
//{ "../../TestImages/Formats/Gif/giphy.gif" },
|
|||
}; |
|||
|
|||
public static readonly TheoryData<string, IImageFilter> Filters = new TheoryData<string, IImageFilter> |
|||
{ |
|||
{ "Contrast-50", new Contrast(50) }, |
|||
{ "Contrast--50", new Contrast(-50) }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData("Filters")] |
|||
public void FilterImage(string name, IImageFilter filter) |
|||
{ |
|||
if (!Directory.Exists("Filtered")) |
|||
{ |
|||
Directory.CreateDirectory("Filtered"); |
|||
} |
|||
|
|||
foreach (string file in Files) |
|||
{ |
|||
using (FileStream stream = File.OpenRead(file)) |
|||
{ |
|||
Stopwatch watch = Stopwatch.StartNew(); |
|||
Image image = new Image(stream); |
|||
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); |
|||
using (FileStream output = File.OpenWrite($"Filtered/{ Path.GetFileName(filename) }")) |
|||
{ |
|||
image.Filter(filter).Save(output); |
|||
} |
|||
|
|||
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue