// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore { using System; using System.Numerics; /// /// Provides common mathematical methods. /// internal static class ImageMaths { /// /// Represents PI, the ratio of a circle's circumference to its diameter. /// // ReSharper disable once InconsistentNaming public const float PI = 3.1415926535897931f; /// /// Returns how many bits are required to store the specified number of colors. /// Performs a Log2() on the value. /// /// The number of colors. /// /// The /// public static int GetBitsNeededForColorDepth(int colors) { return (int)Math.Ceiling(Math.Log(colors, 2)); } /// /// Implementation of 1D Gaussian G(x) function /// /// The x provided to G(x). /// The spread of the blur. /// The Gaussian G(x) public static float Gaussian(float x, float sigma) { const float Numerator = 1.0f; float denominator = (float)(Math.Sqrt(2 * PI) * sigma); float exponentNumerator = -x * x; float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); float left = Numerator / denominator; float right = (float)Math.Exp(exponentNumerator / exponentDenominator); return left * right; } /// /// Returns the result of a B-C filter against the given value. /// /// /// The value to process. /// The B-Spline curve variable. /// The Cardinal curve variable. /// /// The . /// public static float GetBcValue(float x, float b, float c) { float temp; if (x < 0) { x = -x; } temp = x * x; if (x < 1) { x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); return x / 6; } if (x < 2) { x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); return x / 6; } return 0; } /// /// Gets the result of a sine cardinal function for the given value. /// /// /// The value to calculate the result for. /// /// /// The . /// public static float SinC(float x) { const float Epsilon = .00001f; if (Math.Abs(x) > Epsilon) { x *= PI; return Clean((float)Math.Sin(x) / x); } return 1.0f; } /// /// Returns the given degrees converted to radians. /// /// The angle in degrees. /// /// The representing the degree as radians. /// public static float DegreesToRadians(float degrees) { return degrees * (PI / 180); } /// /// Gets the bounding from the given points. /// /// /// The designating the top left position. /// /// /// The designating the bottom right position. /// /// /// The bounding . /// public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) { return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } // http://gamedev.stackexchange.com/questions/22840/create-a-rectangle-struct-to-be-rotated-and-have-a-intersects-function public static Rectangle GetBoundingRotatedRectangle(Rectangle rectangle, float degrees, Point center) { float radians = DegreesToRadians(degrees); Matrix3x2 matrix = Matrix3x2.CreateRotation(radians, center.ToVector2()); Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom)); Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom)); // TODO: minY is wrong - negative return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y)); } /// /// Calculates the new size after rotation. /// /// The width of the image. /// The height of the image. /// The angle of rotation. /// The new size of the image public static Rectangle GetBoundingRotatedRectangle(int width, int height, float angleInDegrees) { // Check first clockwise. double radians = DegreesToRadians(angleInDegrees); double radiansSin = Math.Sin(radians); double radiansCos = Math.Cos(radians); double width1 = (height * radiansSin) + (width * radiansCos); double height1 = (width * radiansSin) + (height * radiansCos); // Find dimensions in the other direction radiansSin = Math.Sin(-radians); radiansCos = Math.Cos(-radians); double width2 = (height * radiansSin) + (width * radiansCos); double height2 = (width * radiansSin) + (height * radiansCos); // Get the external vertex for the rotation Rectangle result = new Rectangle( 0, 0, Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2))), Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)))); return result; } /// /// Finds the bounding rectangle based on the first instance of any color component other /// than the given one. /// /// /// The to search within. /// /// /// The color component value to remove. /// /// /// The channel to test against. /// /// /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) { const float Epsilon = .00001f; int width = bitmap.Width; int height = bitmap.Height; Point topLeft = new Point(); Point bottomRight = new Point(); Func delegateFunc; // Determine which channel to check against switch (channel) { case RgbaComponent.R: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].R - b) > Epsilon; break; case RgbaComponent.G: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].G - b) > Epsilon; break; case RgbaComponent.A: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].A - b) > Epsilon; break; default: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].B - b) > Epsilon; break; } Func getMinY = imageBase => { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (delegateFunc(imageBase, x, y, componentValue)) { return y; } } } return 0; }; Func getMaxY = imageBase => { for (int y = height - 1; y > -1; y--) { for (int x = 0; x < width; x++) { if (delegateFunc(imageBase, x, y, componentValue)) { return y; } } } return height; }; Func getMinX = imageBase => { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (delegateFunc(imageBase, x, y, componentValue)) { return x; } } } return 0; }; Func getMaxX = imageBase => { for (int x = width - 1; x > -1; x--) { for (int y = 0; y < height; y++) { if (delegateFunc(imageBase, x, y, componentValue)) { return x; } } } return height; }; topLeft.Y = getMinY(bitmap); topLeft.X = getMinX(bitmap); bottomRight.Y = (getMaxY(bitmap) + 1).Clamp(0, height); bottomRight.X = (getMaxX(bitmap) + 1).Clamp(0, width); return GetBoundingRectangle(topLeft, bottomRight); } /// /// Ensures that any passed double is correctly rounded to zero /// /// The value to clean. /// /// The /// . private static float Clean(float x) { const float Epsilon = .00001f; if (Math.Abs(x) < Epsilon) { return 0f; } return x; } } }