//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageProcessorCore
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
///
/// Provides common mathematical methods.
///
internal static class ImageMaths
{
///
/// 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 * Math.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 *= (float)Math.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 * (float)(Math.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);
}
///
/// Gets the bounding from the given matrix.
///
/// The source rectangle.
/// The transformation matrix.
///
/// The .
///
public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{
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[] allCorners = { leftTop, rightTop, leftBottom, rightBottom };
float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min();
float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min();
return new Rectangle(0, 0, (int)extentX, (int)extentY);
}
///
/// 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 = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon;
break;
case RgbaComponent.G:
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon;
break;
case RgbaComponent.A:
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon;
break;
default:
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon;
break;
}
Func getMinY = pixels =>
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(pixels, x, y, componentValue))
{
return y;
}
}
}
return 0;
};
Func getMaxY = pixels =>
{
for (int y = height - 1; y > -1; y--)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(pixels, x, y, componentValue))
{
return y;
}
}
}
return height;
};
Func getMinX = pixels =>
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(pixels, x, y, componentValue))
{
return x;
}
}
}
return 0;
};
Func getMaxX = pixels =>
{
for (int x = width - 1; x > -1; x--)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(pixels, x, y, componentValue))
{
return x;
}
}
}
return height;
};
using (PixelAccessor bitmapPixels = bitmap.Lock())
{
topLeft.Y = getMinY(bitmapPixels);
topLeft.X = getMinX(bitmapPixels);
bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height);
bottomRight.X = (getMaxX(bitmapPixels) + 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;
}
}
}