diff --git a/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs b/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs new file mode 100644 index 0000000000..97d2b66d70 --- /dev/null +++ b/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Extension methods for the struct. + /// + public static class Vector4Extensions + { + /// + /// Compresses a linear color signal to its sRGB equivalent. + /// + /// + /// + /// The whose signal to compress. + /// The . + 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); + } + + /// + /// Expands an sRGB color signal to its linear equivalent. + /// + /// + /// + /// The whose signal to expand. + /// The . + 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); + } + + /// + /// Gets the compressed sRGB value from an linear signal. + /// + /// + /// + /// The signal value to compress. + /// + /// The . + /// + [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; + } + + /// + /// Gets the expanded linear value from an sRGB signal. + /// + /// + /// + /// The signal value to expand. + /// + /// The . + /// + [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); + } + } +} diff --git a/src/ImageProcessorCore/Filters/ColorBlindness.cs b/src/ImageProcessorCore/Filters/ColorBlindness.cs index b5848a5140..6ab553ba2d 100644 --- a/src/ImageProcessorCore/Filters/ColorBlindness.cs +++ b/src/ImageProcessorCore/Filters/ColorBlindness.cs @@ -1,14 +1,14 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Filters/Lomograph.cs b/src/ImageProcessorCore/Filters/Lomograph.cs new file mode 100644 index 0000000000..5a484bcc87 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Lomograph.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Lomograph(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Lomograph(source, source.Bounds, progressHandler); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + LomographProcessor processor = new LomographProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs new file mode 100644 index 0000000000..269c1179ef --- /dev/null +++ b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Provides enumeration over the various greyscale methods available. + /// + public enum GreyscaleMode + { + /// + /// ITU-R Recommendation BT.709 + /// + Bt709, + + /// + /// ITU-R Recommendation BT.601 + /// + Bt601 + } +} diff --git a/src/ImageProcessorCore/Filters/Polaroid.cs b/src/ImageProcessorCore/Filters/Polaroid.cs new file mode 100644 index 0000000000..e56496322c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Polaroid.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Polaroid(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Polaroid(source, source.Bounds, progressHandler); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + PolaroidProcessor processor = new PolaroidProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs index 856ae6d615..9315c0b16a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs @@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors /// . The image will be converted to greyscale before thresholding /// occurs. /// + /// The pixel format. + /// The packed format. long, float. public class ThresholdProcessor : ImageProcessor where T : IPackedVector where TP : struct diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs new file mode 100644 index 0000000000..6893d2a8a0 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their black and white equivalent. + /// + /// The pixel format. + /// The packed format. long, float. + public class BlackWhiteProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + 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, + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index cbab86a1c2..dc5811133b 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/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 sourcePixels = source.Lock()) using (IPixelAccessor 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 /// /// The source color. /// The matrix. + /// Whether to compand the color during processing. /// /// The . /// - 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; } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs new file mode 100644 index 0000000000..9e6b25ad68 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to greyscale applying the formula as specified by + /// ITU-R Recommendation BT.601 . + /// + /// The pixel format. + /// The packed format. long, float. + public class GreyscaleBt601Processor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs new file mode 100644 index 0000000000..771abeab50 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + + /// + /// An to change the hue of an . + /// + /// The pixel format. + /// The packed format. long, float. + public class HueProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + /// The used to alter the image. + /// + private Matrix4x4 matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The new brightness of the image. Must be between -100 and 100. + 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; + } + + /// + /// Gets the rotation value. + /// + public float Angle { get; } + + /// + public override Matrix4x4 Matrix => this.matrix; + + /// + public override bool Compand => false; + + /// + protected override void OnApply(ImageBase target, ImageBase 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; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs new file mode 100644 index 0000000000..f7a84fc082 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class KodachromeProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.6997023f, + M22 = 0.4609577f, + M33 = 0.397218f, + M41 = 0.005f, + M42 = -0.005f, + M43 = 0.005f + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs new file mode 100644 index 0000000000..cfdc628935 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Lomograph effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class LomographProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.5f, + M22 = 1.45f, + M33 = 1.11f, + M41 = -.1f, + M42 = .0f, + M43 = -.08f + }; + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + T packed = default(T); + packed.PackBytes(0, 10, 0, 255); + new VignetteProcessor { VignetteColor = packed }.Apply(target, target, targetRectangle); + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs new file mode 100644 index 0000000000..7a0263bd05 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Polaroid effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class PolaroidProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + 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 + }; + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + T packedV = default(T); + packedV.PackBytes(102, 34, 0, 255); + new VignetteProcessor { VignetteColor = packedV }.Apply(target, target, targetRectangle); + + T packedG = default(T); + packedG.PackBytes(255, 153, 102, 178); + new GlowProcessor + { + GlowColor = packedG, + RadiusX = target.Width / 4f, + RadiusY = target.Width / 4f + } + .Apply(target, target, targetRectangle); + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs new file mode 100644 index 0000000000..39e7b0bd46 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// An to change the saturation of an . + /// + /// The pixel format. + /// The packed format. long, float. + public class SaturationProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + /// The saturation to be applied to the image. + /// + private readonly int saturation; + + /// + /// The used to alter the image. + /// + private Matrix4x4 matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The new saturation of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public SaturationProcessor(int saturation) + { + Guard.MustBeBetweenOrEqualTo(saturation, -100, 100, nameof(saturation)); + this.saturation = saturation; + } + + /// + public override Matrix4x4 Matrix => this.matrix; + + /// + protected override void OnApply(ImageBase target, ImageBase 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; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs new file mode 100644 index 0000000000..60ba27c0e6 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their sepia equivalent. + /// The formula used matches the svg specification. + /// + /// The pixel format. + /// The packed format. long, float. + public class SepiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .393f, + M12 = .349f, + M13 = .272f, + M21 = .769f, + M22 = .686f, + M23 = .534f, + M31 = .189f, + M32 = .168f, + M33 = .131f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs new file mode 100644 index 0000000000..fdffcd7809 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Creates a glow effect on the image + /// + /// The pixel format. + /// The packed format. long, float. + public class GlowProcessor : ImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// Initializes a new instance of the class. + /// + public GlowProcessor() + { + this.GlowColor.PackVector(Color.White.ToVector4()); + } + + /// + /// Gets or sets the glow color to apply. + /// + public T GlowColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public float RadiusX { get; set; } + + /// + /// Gets or sets the the y-radius. + /// + public float RadiusY { get; set; } + + /// + protected override void Apply(ImageBase target, ImageBase 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 sourcePixels = source.Lock()) + using (IPixelAccessor 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(); + }); + } + } + } +} + diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs new file mode 100644 index 0000000000..2afd9e00cd --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Creates a vignette effect on the image + /// + /// The pixel format. + /// The packed format. long, float. + public class VignetteProcessor : ImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// Initializes a new instance of the class. + /// + public VignetteProcessor() + { + this.VignetteColor.PackVector(Color.Black.ToVector4()); + } + + /// + /// Gets or sets the vignette color to apply. + /// + public T VignetteColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public float RadiusX { get; set; } + + /// + /// Gets or sets the the y-radius. + /// + public float RadiusY { get; set; } + + /// + protected override void Apply(ImageBase target, ImageBase 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 sourcePixels = source.Lock()) + using (IPixelAccessor 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(); + }); + } + } + } +} + diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs index ff3196c430..04c96dbd1a 100644 --- a/src/ImageProcessorCore/Samplers/Crop.cs +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs index 123378173c..631662d800 100644 --- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs index 0b1b959b08..0a7277144e 100644 --- a/src/ImageProcessorCore/Samplers/Pad.cs +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index 8a53b64981..ad00518f14 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs index 407737dd37..093af8503f 100644 --- a/src/ImageProcessorCore/Samplers/RotateFlip.cs +++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 08e07ffd35..ab188ceeee 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs index 5b2300885f..9e6c9c1cb7 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs new file mode 100644 index 0000000000..e125bd8efd --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs new file mode 100644 index 0000000000..b44cbd6d5d --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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); + } + } + } + } + } +} \ No newline at end of file