//
// 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;
}
}
}