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