Browse Source

Begin add ColorMatrix filters.

Former-commit-id: 87e8abdf10ff5c2068354690817dd32a86d613c2
Former-commit-id: 9eeb9dd07422fded5300087200f781d7d7064b50
Former-commit-id: a35e9e5e97952e268bcb3310194d738282817052
pull/1/head
James Jackson-South 10 years ago
parent
commit
743e3309b3
  1. 91
      src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs
  2. 4
      src/ImageProcessorCore/Filters/ColorBlindness.cs
  3. 58
      src/ImageProcessorCore/Filters/Lomograph.cs
  4. 23
      src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs
  5. 58
      src/ImageProcessorCore/Filters/Polaroid.cs
  6. 2
      src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs
  7. 36
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs
  8. 21
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs
  9. 34
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs
  10. 88
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs
  11. 30
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs
  12. 38
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs
  13. 54
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs
  14. 78
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs
  15. 37
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs
  16. 80
      src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs
  17. 79
      src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs
  18. 2
      src/ImageProcessorCore/Samplers/Crop.cs
  19. 2
      src/ImageProcessorCore/Samplers/EntropyCrop.cs
  20. 2
      src/ImageProcessorCore/Samplers/Pad.cs
  21. 2
      src/ImageProcessorCore/Samplers/Rotate.cs
  22. 2
      src/ImageProcessorCore/Samplers/RotateFlip.cs
  23. 2
      src/ImageProcessorCore/Samplers/Skew.cs
  24. 2
      tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs
  25. 38
      tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs
  26. 38
      tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs

91
src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs

@ -0,0 +1,91 @@
// <copyright file="Vector4Extensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Extension methods for the <see cref="Vector4"/> struct.
/// </summary>
public static class Vector4Extensions
{
/// <summary>
/// Compresses a linear color signal to its sRGB equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="linear">The <see cref="Vector4"/> whose signal to compress.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(this Vector4 linear)
{
// TODO: Is there a faster way to do this?
float r = Compress(linear.X);
float g = Compress(linear.Y);
float b = Compress(linear.Z);
return new Vector4(r, g, b, linear.W);
}
/// <summary>
/// Expands an sRGB color signal to its linear equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="gamma">The <see cref="Color"/> whose signal to expand.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(this Vector4 gamma)
{
// TODO: Is there a faster way to do this?
float r = Expand(gamma.X);
float g = Expand(gamma.Y);
float b = Expand(gamma.Z);
return new Vector4(r, g, b, gamma.W);
}
/// <summary>
/// Gets the compressed sRGB value from an linear signal.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="signal">The signal value to compress.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Compress(float signal)
{
if (signal <= 0.0031308f)
{
return signal * 12.92f;
}
return (1.055f * (float)Math.Pow(signal, 0.41666666f)) - 0.055f;
}
/// <summary>
/// Gets the expanded linear value from an sRGB signal.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="signal">The signal value to expand.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Expand(float signal)
{
if (signal <= 0.04045f)
{
return signal / 12.92f;
}
return (float)Math.Pow((signal + 0.055f) / 1.055f, 2.4f);
}
}
}

4
src/ImageProcessorCore/Filters/ColorBlindness.cs

@ -1,14 +1,14 @@
// <copyright file="ColorBlindness.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>-------------------------------------------------------------------------------------------------------------------
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

58
src/ImageProcessorCore/Filters/Lomograph.cs

@ -0,0 +1,58 @@
// <copyright file="Lomograph.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Lomograph<T, TP>(this Image<T, TP> source, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return Lomograph(source, source.Bounds, progressHandler);
}
/// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Lomograph<T, TP>(this Image<T, TP> source, Rectangle rectangle, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
LomographProcessor<T, TP> processor = new LomographProcessor<T, TP>();
processor.OnProgress += progressHandler;
try
{
return source.Process(rectangle, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

23
src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs

@ -0,0 +1,23 @@
// <copyright file="GreyscaleMode.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
/// <summary>
/// Provides enumeration over the various greyscale methods available.
/// </summary>
public enum GreyscaleMode
{
/// <summary>
/// ITU-R Recommendation BT.709
/// </summary>
Bt709,
/// <summary>
/// ITU-R Recommendation BT.601
/// </summary>
Bt601
}
}

58
src/ImageProcessorCore/Filters/Polaroid.cs

@ -0,0 +1,58 @@
// <copyright file="Polaroid.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Polaroid<T, TP>(this Image<T, TP> source, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return Polaroid(source, source.Bounds, progressHandler);
}
/// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Polaroid<T, TP>(this Image<T, TP> source, Rectangle rectangle, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
PolaroidProcessor<T, TP> processor = new PolaroidProcessor<T, TP>();
processor.OnProgress += progressHandler;
try
{
return source.Process(rectangle, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

2
src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs

@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors
/// <see cref="Image"/>. The image will be converted to greyscale before thresholding
/// occurs.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class ThresholdProcessor<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct

36
src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs

@ -0,0 +1,36 @@
// <copyright file="BlackWhiteProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image to their black and white equivalent.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class BlackWhiteProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = 1.5f,
M12 = 1.5f,
M13 = 1.5f,
M21 = 1.5f,
M22 = 1.5f,
M23 = 1.5f,
M31 = 1.5f,
M32 = 1.5f,
M33 = 1.5f,
M41 = -1f,
M42 = -1f,
M43 = -1f,
};
}
}

21
src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs

@ -29,6 +29,7 @@ namespace ImageProcessorCore.Processors
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Matrix4x4 matrix = this.Matrix;
bool compand = this.Compand;
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
@ -40,7 +41,7 @@ namespace ImageProcessorCore.Processors
{
for (int x = startX; x < endX; x++)
{
targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix);
targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix, compand);
}
this.OnRowProcessed();
@ -53,23 +54,23 @@ namespace ImageProcessorCore.Processors
/// </summary>
/// <param name="color">The source color.</param>
/// <param name="matrix">The matrix.</param>
/// <param name="compand">Whether to compand the color during processing.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
private T ApplyMatrix(T color, Matrix4x4 matrix)
private T ApplyMatrix(T color, Matrix4x4 matrix, bool compand)
{
bool compand = this.Compand;
Vector4 vector = color.ToVector4();
//if (compand)
//{
// color = Color.Expand(color);
//}
if (compand)
{
vector = vector.Expand();
}
Vector4 vector = color.ToVector4();
Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix);
//return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A);
vector = new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W);
T packed = default(T);
packed.PackVector(new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W));
packed.PackVector(compand ? vector.Compress() : vector);
return packed;
}
}

34
src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs

@ -0,0 +1,34 @@
// <copyright file="GreyscaleBt601Processor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image to greyscale applying the formula as specified by
/// ITU-R Recommendation BT.601 <see href="https://en.wikipedia.org/wiki/Luma_%28video%29#Rec._601_luma_versus_Rec._709_luma_coefficients"/>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class GreyscaleBt601Processor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = .299f,
M12 = .299f,
M13 = .299f,
M21 = .587f,
M22 = .587f,
M23 = .587f,
M31 = .114f,
M32 = .114f,
M33 = .114f
};
}
}

88
src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs

@ -0,0 +1,88 @@
// <copyright file="HueProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
/// <summary>
/// An <see cref="IImageProcessor"/> to change the hue of an <see cref="Image"/>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class HueProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The <see cref="Matrix4x4"/> used to alter the image.
/// </summary>
private Matrix4x4 matrix;
/// <summary>
/// Initializes a new instance of the <see cref="HueProcessor"/> class.
/// </summary>
/// <param name="angle">The new brightness of the image. Must be between -100 and 100.</param>
public HueProcessor(float angle)
{
// Wrap the angle round at 360.
angle = angle % 360;
// Make sure it's not negative.
while (angle < 0)
{
angle += 360;
}
this.Angle = angle;
}
/// <summary>
/// Gets the rotation value.
/// </summary>
public float Angle { get; }
/// <inheritdoc/>
public override Matrix4x4 Matrix => this.matrix;
/// <inheritdoc/>
public override bool Compand => false;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
float radians = (float)ImageMaths.DegreesToRadians(this.Angle);
double cosradians = Math.Cos(radians);
double sinradians = Math.Sin(radians);
float lumR = .213f;
float lumG = .715f;
float lumB = .072f;
float oneMinusLumR = 1 - lumR;
float oneMinusLumG = 1 - lumG;
float oneMinusLumB = 1 - lumB;
// The matrix is set up to preserve the luminance of the image.
// See http://graficaobscura.com/matrix/index.html
// Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx
Matrix4x4 matrix4X4 = new Matrix4x4()
{
M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)),
M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)),
M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)),
M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)),
M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)),
M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)),
M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)),
M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)),
M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB))
};
this.matrix = matrix4X4;
}
}
}

30
src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs

@ -0,0 +1,30 @@
// <copyright file="KodachromeProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image recreating an old Kodachrome camera effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class KodachromeProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = 0.6997023f,
M22 = 0.4609577f,
M33 = 0.397218f,
M41 = 0.005f,
M42 = -0.005f,
M43 = 0.005f
};
}
}

38
src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs

@ -0,0 +1,38 @@
// <copyright file="LomographProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image recreating an old Lomograph effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class LomographProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = 1.5f,
M22 = 1.45f,
M33 = 1.11f,
M41 = -.1f,
M42 = .0f,
M43 = -.08f
};
/// <inheritdoc/>
protected override void AfterApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
T packed = default(T);
packed.PackBytes(0, 10, 0, 255);
new VignetteProcessor<T, TP> { VignetteColor = packed }.Apply(target, target, targetRectangle);
}
}
}

54
src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs

@ -0,0 +1,54 @@
// <copyright file="PolaroidProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image recreating an old Polaroid effect.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class PolaroidProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = 1.538f,
M12 = -0.062f,
M13 = -0.262f,
M21 = -0.022f,
M22 = 1.578f,
M23 = -0.022f,
M31 = .216f,
M32 = -.16f,
M33 = 1.5831f,
M41 = 0.02f,
M42 = -0.05f,
M43 = -0.05f
};
/// <inheritdoc/>
protected override void AfterApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
T packedV = default(T);
packedV.PackBytes(102, 34, 0, 255);
new VignetteProcessor<T, TP> { VignetteColor = packedV }.Apply(target, target, targetRectangle);
T packedG = default(T);
packedG.PackBytes(255, 153, 102, 178);
new GlowProcessor<T, TP>
{
GlowColor = packedG,
RadiusX = target.Width / 4f,
RadiusY = target.Width / 4f
}
.Apply(target, target, targetRectangle);
}
}
}

78
src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs

@ -0,0 +1,78 @@
// <copyright file="SaturationProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// An <see cref="IImageProcessor"/> to change the saturation of an <see cref="Image"/>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class SaturationProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The saturation to be applied to the image.
/// </summary>
private readonly int saturation;
/// <summary>
/// The <see cref="Matrix4x4"/> used to alter the image.
/// </summary>
private Matrix4x4 matrix;
/// <summary>
/// Initializes a new instance of the <see cref="SaturationProcessor"/> class.
/// </summary>
/// <param name="saturation">The new saturation of the image. Must be between -100 and 100.</param>
/// <exception cref="ArgumentException">
/// <paramref name="saturation"/> is less than -100 or is greater than 100.
/// </exception>
public SaturationProcessor(int saturation)
{
Guard.MustBeBetweenOrEqualTo(saturation, -100, 100, nameof(saturation));
this.saturation = saturation;
}
/// <inheritdoc/>
public override Matrix4x4 Matrix => this.matrix;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
float saturationFactor = this.saturation / 100f;
// Stop at -1 to prevent inversion.
saturationFactor++;
// The matrix is set up to "shear" the colour space using the following set of values.
// Note that each colour component has an effective luminance which contributes to the
// overall brightness of the pixel.
// See http://graficaobscura.com/matrix/index.html
float saturationComplement = 1.0f - saturationFactor;
float saturationComplementR = 0.3086f * saturationComplement;
float saturationComplementG = 0.6094f * saturationComplement;
float saturationComplementB = 0.0820f * saturationComplement;
Matrix4x4 matrix4X4 = new Matrix4x4()
{
M11 = saturationComplementR + saturationFactor,
M12 = saturationComplementR,
M13 = saturationComplementR,
M21 = saturationComplementG,
M22 = saturationComplementG + saturationFactor,
M23 = saturationComplementG,
M31 = saturationComplementB,
M32 = saturationComplementB,
M33 = saturationComplementB + saturationFactor,
};
this.matrix = matrix4X4;
}
}
}

37
src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs

@ -0,0 +1,37 @@
// <copyright file="SepiaProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image to their sepia equivalent.
/// The formula used matches the svg specification. <see href="http://www.w3.org/TR/filter-effects/#sepiaEquivalent"/>
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class SepiaProcessor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = .393f,
M12 = .349f,
M13 = .272f,
M21 = .769f,
M22 = .686f,
M23 = .534f,
M31 = .189f,
M32 = .168f,
M33 = .131f
};
/// <inheritdoc/>
public override bool Compand => false;
}
}

80
src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs

@ -0,0 +1,80 @@
// <copyright file="GlowProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Creates a glow effect on the image
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class GlowProcessor<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor"/> class.
/// </summary>
public GlowProcessor()
{
this.GlowColor.PackVector(Color.White.ToVector4());
}
/// <summary>
/// Gets or sets the glow color to apply.
/// </summary>
public T GlowColor { get; set; }
/// <summary>
/// Gets or sets the the x-radius.
/// </summary>
public float RadiusX { get; set; }
/// <summary>
/// Gets or sets the the y-radius.
/// </summary>
public float RadiusY { get; set; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
T glowColor = this.GlowColor;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
{
// TODO: Premultiply?
float distance = Vector2.Distance(centre, new Vector2(x, y));
Vector4 sourceColor = sourcePixels[x, y].ToVector4();
Vector4 result = Vector4.Lerp(glowColor.ToVector4(), sourceColor, .5f * (distance / maxDistance));
T packed = default(T);
packed.PackVector(result);
targetPixels[x, y] = packed;
}
this.OnRowProcessed();
});
}
}
}
}

79
src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs

@ -0,0 +1,79 @@
// <copyright file="VignetteProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Creates a vignette effect on the image
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class VignetteProcessor<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor"/> class.
/// </summary>
public VignetteProcessor()
{
this.VignetteColor.PackVector(Color.Black.ToVector4());
}
/// <summary>
/// Gets or sets the vignette color to apply.
/// </summary>
public T VignetteColor { get; set; }
/// <summary>
/// Gets or sets the the x-radius.
/// </summary>
public float RadiusX { get; set; }
/// <summary>
/// Gets or sets the the y-radius.
/// </summary>
public float RadiusY { get; set; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
T vignetteColor = this.VignetteColor;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Vector4 sourceColor = sourcePixels[x, y].ToVector4();
Vector4 result = Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - .9f * (distance / maxDistance));
T packed = default(T);
packed.PackVector(result);
targetPixels[x, y] = packed;
}
this.OnRowProcessed();
});
}
}
}
}

2
src/ImageProcessorCore/Samplers/Crop.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
src/ImageProcessorCore/Samplers/EntropyCrop.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
src/ImageProcessorCore/Samplers/Pad.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
src/ImageProcessorCore/Samplers/Rotate.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
src/ImageProcessorCore/Samplers/RotateFlip.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
src/ImageProcessorCore/Samplers/Skew.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{

2
tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs

@ -1,4 +1,4 @@
// <copyright file="SamplerTests.cs" company="James Jackson-South">
// <copyright file="ColorBlindnessTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>

38
tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs

@ -0,0 +1,38 @@
// <copyright file="LomographTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System.IO;
using Xunit;
public class LomographTest : FileTestBase
{
[Fact]
public void ImageShouldApplyLomographFilter()
{
const string path = "TestOutput/Lomograph";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileName(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.Lomograph()
.Save(output);
}
}
}
}
}
}

38
tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs

@ -0,0 +1,38 @@
// <copyright file="PolaroidTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System.IO;
using Xunit;
public class PolaroidTest : FileTestBase
{
[Fact]
public void ImageShouldApplyPolaroidFilter()
{
const string path = "TestOutput/Polaroid";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileName(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.Polaroid()
.Save(output);
}
}
}
}
}
}
Loading…
Cancel
Save