diff --git a/README.md b/README.md index abd098148..a9ef0f0cc 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Entropy Crop - Rotation - [ ] Flip (90, 270, FlipType etc. Need help) [#261](https://github.com/JimBobSquarePants/ImageProcessor/issues/261) - - [] Rotate by angle (Need help with Paeth approach) [#258](https://github.com/JimBobSquarePants/ImageProcessor/issues/258) + - [ ] Rotate by angle (Need help with Paeth approach) [#258](https://github.com/JimBobSquarePants/ImageProcessor/issues/258) - ColorMatrix operations (Uses Matrix4x4) - [x] BlackWhite - [x] Greyscale BT709 @@ -111,7 +111,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Brightness - [x] Pixelate - [x] Saturation - - [ ] Hue [#262](https://github.com/JimBobSquarePants/ImageProcessor/issues/262) + - [x] Hue - [x] Blend - [ ] Mask - Effects diff --git a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs index 2e4d598ae..9fc56e72b 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs @@ -16,6 +16,9 @@ namespace ImageProcessor.Filters /// public abstract Matrix4x4 Matrix { get; } + /// + public virtual bool Compand => true; + /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { @@ -34,7 +37,7 @@ namespace ImageProcessor.Filters { for (int x = startX; x < endX; x++) { - target[x, y] = ApplyMatrix(source[x, y], matrix); + target[x, y] = this.ApplyMatrix(source[x, y], matrix); } } }); @@ -48,9 +51,14 @@ namespace ImageProcessor.Filters /// /// The . /// - private static Color ApplyMatrix(Color color, Matrix4x4 matrix) + private Color ApplyMatrix(Color color, Matrix4x4 matrix) { - color = Color.InverseCompand(color); + bool compand = this.Compand; + + if (compand) + { + color = Color.InverseCompand(color); + } float sr = color.R; float sg = color.G; @@ -60,7 +68,7 @@ namespace ImageProcessor.Filters 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 Color.Compand(color); + return compand ? Color.Compand(color) : color; } } } diff --git a/src/ImageProcessor/Filters/Hue.cs b/src/ImageProcessor/Filters/ColorMatrix/Hue.cs similarity index 58% rename from src/ImageProcessor/Filters/Hue.cs rename to src/ImageProcessor/Filters/ColorMatrix/Hue.cs index b7fafec5a..3a70f5b49 100644 --- a/src/ImageProcessor/Filters/Hue.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Hue.cs @@ -36,31 +36,38 @@ /// public override Matrix4x4 Matrix => this.matrix; + /// + public override bool Compand => false; + /// protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { - float degrees = this.Angle; - double costheta = Math.Cos(degrees); - double sintheta = Math.Sin(degrees); + 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 - // TODO: This isn't correct. Need to double check MS numbers against maths. Matrix4x4 matrix4X4 = new Matrix4x4() { - M11 = (float)(.213 + (costheta * .787) - (sintheta * .213)), - M12 = (float)(.715 - (costheta * .715) - (sintheta * .715)), - M13 = (float)(.072 - (costheta * .072) + (sintheta * .928)), - M21 = (float)(.213 - (costheta * .213) + (sintheta * .143)), - M22 = (float)(.715 + (costheta * .285) + (sintheta * .140)), - M23 = (float)(.072 - (costheta * .072) - (sintheta * .283)), - M31 = (float)(.213 - (costheta * .213) - (sintheta * .787)), - M32 = (float)(.715 - (costheta * .715) + (sintheta * .715)), - M33 = (float)(.072 + (costheta * .928) + (sintheta * .072)) + M11 = (float)(+lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)), + M12 = (float)(+lumR - (cosradians * lumR) - (sinradians * lumR)), + M13 = (float)(+lumR - (cosradians * lumR) - (sinradians * 0.787)), + 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/ImageProcessor/Filters/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/IColorMatrixFilter.cs index fb231c5a8..3ac0bedc7 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/IColorMatrixFilter.cs @@ -17,5 +17,10 @@ namespace ImageProcessor.Filters /// Gets the used to alter the image. /// Matrix4x4 Matrix { get; } + + /// + /// Gets a value indicating whether to compand the value on processing. + /// + bool Compand { get; } } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs index d0cfe6aaf..cab932b25 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs @@ -8,7 +8,8 @@ namespace ImageProcessor.Filters using System.Numerics; /// - /// Converts the colors of the image to their sepia equivalent recreating an old photo effect. + /// Converts the colors of the image to their sepia equivalent. + /// The formula used matches the svg specification. /// public class Sepia : ColorMatrixFilter { @@ -25,5 +26,8 @@ namespace ImageProcessor.Filters M32 = .168f, M33 = .131f }; + + /// + public override bool Compand => false; } } diff --git a/src/ImageProcessor/Filters/ImageFilterExtensions.cs b/src/ImageProcessor/Filters/ImageFilterExtensions.cs index ceaf47a34..5651934b5 100644 --- a/src/ImageProcessor/Filters/ImageFilterExtensions.cs +++ b/src/ImageProcessor/Filters/ImageFilterExtensions.cs @@ -209,6 +209,31 @@ namespace ImageProcessor.Filters : source.Process(rectangle, new GreyscaleBt601()); } + /// + /// Alters the hue component of the image. + /// + /// The image this method extends. + /// The angle in degrees to adjust the image. + /// The . + public static Image Hue(this Image source, float degrees) + { + return Hue(source, degrees, source.Bounds); + } + + /// + /// Alters the hue component of the image. + /// + /// The image this method extends. + /// The angle in degrees to adjust the image. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Hue(this Image source, float degrees, Rectangle rectangle) + { + return source.Process(rectangle, new Hue(degrees)); + } + /// /// Inverts the colors of the image. /// diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs index 9c1f76125..e5a0c1a07 100644 --- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs @@ -41,7 +41,9 @@ namespace ImageProcessor.Tests { "Sobel", new Sobel {Greyscale = true} }, { "Pixelate", new Pixelate(8) }, { "GuassianBlur", new GuassianBlur(10) }, - { "GuassianSharpen", new GuassianSharpen(10) } + { "GuassianSharpen", new GuassianSharpen(10) }, + { "Hue-180", new Hue(180) }, + { "Hue--180", new Hue(-180) } }; [Theory]