diff --git a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs index 90b1a9d8f..d10264c93 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs @@ -5,30 +5,35 @@ namespace ImageProcessor.Filters { + using System.Numerics; + /// /// Converts the colors of the image to greyscale applying the formula as specified by /// ITU-R Recommendation BT.601 . /// - public class GreyscaleBt601 : ColorMatrixFilter + public class GreyscaleBt601 : MatrixFilter { /// - /// The inversion matrix. + /// The greyscale matrix. /// - private static readonly ColorMatrix Matrix = new ColorMatrix( - new[] - { - new float[] { 0.299f, 0.299f, 0.299f, 0, 0 }, - new float[] { 0.587f, 0.587f, 0.587f, 0, 0 }, - new float[] { 0.114f, 0.114f, 0.114f, 0, 0 }, - new float[] { 0, 0, 0, 1, 0 }, - new float[] { 0, 0, 0, 0, 1 } - }); + private static readonly Matrix4x4 Matrix = new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; /// /// Initializes a new instance of the class. /// public GreyscaleBt601() - : base(Matrix, true) + : base(Matrix) { } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs index b975d33d7..8f59be7a2 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs @@ -5,30 +5,35 @@ namespace ImageProcessor.Filters { + using System.Numerics; + /// /// Converts the colors of the image to greyscale applying the formula as specified by /// ITU-R Recommendation BT.709 . /// - public class GreyscaleBt709 : ColorMatrixFilter + public class GreyscaleBt709 : MatrixFilter { /// - /// The inversion matrix. + /// The greyscale matrix. /// - private static readonly ColorMatrix Matrix = new ColorMatrix( - new[] - { - new float[] { 0.2126f, 0.2126f, 0.2126f, 0, 0 }, - new float[] { 0.7152f, 0.7152f, 0.7152f, 0, 0 }, - new float[] { 0.0722f, 0.0722f, 0.0722f, 0, 0 }, - new float[] { 0, 0, 0, 1, 0 }, - new float[] { 0, 0, 0, 0, 1 } - }); + private static readonly Matrix4x4 Matrix = new Matrix4x4() + { + M11 = .2126f, + M12 = .2126f, + M13 = .2126f, + M21 = .7152f, + M22 = .7152f, + M23 = .7152f, + M31 = .0722f, + M32 = .0722f, + M33 = .0722f + }; /// /// Initializes a new instance of the class. /// public GreyscaleBt709() - : base(Matrix, true) + : base(Matrix) { } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs b/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs index f0d4c139a..895a9f2f9 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs @@ -5,30 +5,41 @@ namespace ImageProcessor.Filters { + using System.Numerics; + /// /// Converts the colors of the image recreating an old Lomograph effect. /// - public class Lomograph : ColorMatrixFilter + public class Lomograph : MatrixFilter { /// /// The Lomograph matrix. Purely artistic in composition. /// TODO: Calculate a matrix that works in the linear color space. /// - private static readonly ColorMatrix Matrix = new ColorMatrix( - new[] - { - new[] { 1.50f, 0, 0, 0, 0 }, - new[] { 0, 1.45f, 0, 0, 0 }, - new[] { 0, 0, 1.09f, 0, 0 }, - new float[] { 0, 0, 0, 1, 0 }, - new[] { -0.10f, 0.05f, -0.08f, 0, 1 } - }); + private static readonly Matrix4x4 Matrix = new Matrix4x4() + { + M11 = 1.5f, + M22 = 1.45f, + M33 = 1.09f, + M41 = -.1f, + M42 = .0f, + M43 = -.08f + }; + //private static readonly ColorMatrix Matrix = new ColorMatrix( + // new[] + // { + // new[] { 1.50f, 0, 0, 0, 0 }, + // new[] { 0, 1.45f, 0, 0, 0 }, + // new[] { 0, 0, 1.09f, 0, 0 }, + // new float[] { 0, 0, 0, 1, 0 }, + // new[] { -0.10f, 0.05f, -0.08f, 0, 1 } + // }); /// /// Initializes a new instance of the class. /// public Lomograph() - : base(Matrix, false) + : base(Matrix) { } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/MatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/MatrixFilter.cs new file mode 100644 index 000000000..2306af112 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/MatrixFilter.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// The color matrix filter. + /// + public class MatrixFilter : ParallelImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The to apply. + public MatrixFilter(Matrix4x4 matrix) + { + this.Value = matrix; + } + + /// + /// Gets the matrix value. + /// + public Matrix4x4 Value { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Matrix4x4 matrix = this.Value; + + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + target[x, y] = ApplyMatrix(source[x, y], matrix); + } + } + }); + } + + /// + /// Applies the color matrix against the given color. + /// + /// The source color. + /// The matrix. + /// + /// The . + /// + private static Color ApplyMatrix(Color color, Matrix4x4 matrix) + { + color = PixelOperations.ToLinear(color); + + float sr = color.R; + float sg = color.G; + float sb = color.B; + + color.R = (sr * matrix.M11) + (sg * matrix.M21) + (sb * matrix.M31) + matrix.M41; + color.G = (sr * matrix.M12) + (sg * matrix.M22) + (sb * matrix.M32) + matrix.M42; + color.B = (sr * matrix.M13) + (sg * matrix.M23) + (sb * matrix.M33) + matrix.M43; + + return PixelOperations.ToSrgb(color); + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs index 1b014a533..8628b8e81 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs @@ -5,30 +5,34 @@ namespace ImageProcessor.Filters { + using System.Numerics; + /// /// Converts the colors of the image to their sepia equivalent recreating an old photo effect. /// - public class Sepia : ColorMatrixFilter + public class Sepia : MatrixFilter { /// /// The sepia matrix. - /// TODO: Calculate a matrix that works in the linear color space. /// - private static readonly ColorMatrix Matrix = new ColorMatrix( - new[] - { - new[] { .393f, .349f, .272f, 0, 0 }, - new[] { .769f, .686f, .534f, 0, 0 }, - new[] { .189f, .168f, .131f, 0, 0 }, - new float[] { 0, 0, 0, 1, 0 }, - new float[] { 0, 0, 0, 0, 1 } - }); + private static readonly Matrix4x4 Matrix = new Matrix4x4() + { + M11 = .393f, + M12 = .349f, + M13 = .272f, + M21 = .769f, + M22 = .686f, + M23 = .534f, + M31 = .189f, + M32 = .168f, + M33 = .131f + }; /// /// Initializes a new instance of the class. /// public Sepia() - : base(Matrix, false) + : base(Matrix) { } } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index fdb70a183..48cd59e1d 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -48,6 +48,7 @@ + diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs index ace61db0d..dfda1ef79 100644 --- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs @@ -16,13 +16,13 @@ namespace ImageProcessor.Tests //{ "Brightness--50", new Brightness(-50) }, //{ "Contrast-50", new Contrast(50) }, //{ "Contrast--50", new Contrast(-50) }, - { "Saturation-100", new Saturation(100) }, - { "Saturation--0", new Saturation(0) }, + //{ "Saturation-100", new Saturation(100) }, + //{ "Saturation--0", new Saturation(0) }, //{ "Alpha--50", new Alpha(50) }, //{ "Invert", new Invert() }, //{ "Sepia", new Sepia() }, //{ "BlackWhite", new BlackWhite() }, - //{ "Lomograph", new Lomograph() }, + { "Lomograph", new Lomograph() }, //{ "Polaroid", new Polaroid() }, //{ "GreyscaleBt709", new GreyscaleBt709() }, //{ "GreyscaleBt601", new GreyscaleBt601() }, diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs index 9ece3b02b..6c33ef4c4 100644 --- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs +++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs @@ -19,16 +19,16 @@ namespace ImageProcessor.Tests /// public static readonly List Files = new List { - "../../TestImages/Formats/Jpg/Backdrop.jpg", + //"../../TestImages/Formats/Jpg/Backdrop.jpg", "../../TestImages/Formats/Jpg/Calliphora.jpg", - "../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", - "../../TestImages/Formats/Jpg/greyscale.jpg", - "../../TestImages/Formats/Bmp/Car.bmp", - "../../TestImages/Formats/Png/cmyk.png", - "../../TestImages/Formats/Png/gamma-1.0-or-2.2.png", - "../../TestImages/Formats/Png/splash.png", - "../../TestImages/Formats/Gif/leaf.gif", - "../../TestImages/Formats/Gif/rings.gif", + //"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", + //"../../TestImages/Formats/Jpg/greyscale.jpg", + //"../../TestImages/Formats/Bmp/Car.bmp", + //"../../TestImages/Formats/Png/cmyk.png", + //"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png", + //"../../TestImages/Formats/Png/splash.png", + //"../../TestImages/Formats/Gif/leaf.gif", + //"../../TestImages/Formats/Gif/rings.gif", //"../../TestImages/Formats/Gif/ani2.gif", //"../../TestImages/Formats/Gif/giphy.gif" };