// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
//
//
// Provides reusable mathematical methods to apply to images.
//
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Helpers
{
using System;
using System.Drawing;
using ImageProcessor.Imaging.Colors;
///
/// Provides reusable mathematical methods to apply to images.
///
public static class ImageMaths
{
///
/// Gets a representing the child centered relative to the parent.
///
///
/// The parent .
///
///
/// The child .
///
///
/// The centered .
///
public static RectangleF CenteredRectangle(Rectangle parent, Rectangle child)
{
float x = (parent.Width - child.Width) / 2.0F;
float y = (parent.Height - child.Height) / 2.0F;
int width = child.Width;
int height = child.Height;
return new RectangleF(x, y, width, height);
}
///
/// Restricts a value to be within a specified range.
///
///
/// The The value to clamp.
///
///
/// The minimum value. If value is less than min, min will be returned.
///
///
/// The maximum value. If value is greater than max, max will be returned.
///
///
/// The to clamp.
///
///
/// The representing the clamped value.
///
public static T Clamp(T value, T min, T max) where T : IComparable
{
if (value.CompareTo(min) < 0)
{
return min;
}
if (value.CompareTo(max) > 0)
{
return max;
}
return value;
}
///
/// Returns the given degrees converted to radians.
///
///
/// The angle in degrees.
///
///
/// The representing the degree as radians.
///
public static double DegreesToRadians(double angleInDegrees)
{
return angleInDegrees * (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);
}
///
/// 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(Image bitmap, byte componentValue, RgbaComponent channel = RgbaComponent.B)
{
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 = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).R != b;
break;
case RgbaComponent.G:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).G != b;
break;
case RgbaComponent.A:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).A != b;
break;
default:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).B != b;
break;
}
Func getMinY = fastBitmap =>
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(fastBitmap, x, y, componentValue))
{
return y;
}
}
}
return 0;
};
Func getMaxY = fastBitmap =>
{
for (int y = height - 1; y > -1; y--)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(fastBitmap, x, y, componentValue))
{
return y;
}
}
}
return height;
};
Func getMinX = fastBitmap =>
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(fastBitmap, x, y, componentValue))
{
return x;
}
}
}
return 0;
};
Func getMaxX = fastBitmap =>
{
for (int x = width - 1; x > -1; x--)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(fastBitmap, x, y, componentValue))
{
return x;
}
}
}
return height;
};
using (FastBitmap fastBitmap = new FastBitmap(bitmap))
{
topLeft.Y = getMinY(fastBitmap);
topLeft.X = getMinX(fastBitmap);
bottomRight.Y = getMaxY(fastBitmap) + 1;
bottomRight.X = getMaxX(fastBitmap) + 1;
}
return GetBoundingRectangle(topLeft, bottomRight);
}
///
/// Rotates one point around another
///
///
/// The point to rotate.
/// The rotation angle in degrees.
/// The centre point of rotation. If not set the point will equal
///
///
/// Rotated point
public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null)
{
Point center = centerPoint ?? Point.Empty;
double angleInRadians = DegreesToRadians(angleInDegrees);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)((cosTheta * (pointToRotate.X - center.X)) -
((sinTheta * (pointToRotate.Y - center.Y)) + center.X)),
Y =
(int)((sinTheta * (pointToRotate.X - center.X)) +
((cosTheta * (pointToRotate.Y - center.Y)) + center.Y))
};
}
///
/// Returns the array of matching the bounds of the given rectangle.
///
///
/// The to return the points from.
///
///
/// The array.
///
public static Point[] ToPoints(Rectangle rectangle)
{
return new[]
{
new Point(rectangle.Left, rectangle.Top),
new Point(rectangle.Right, rectangle.Top),
new Point(rectangle.Right, rectangle.Bottom),
new Point(rectangle.Left, rectangle.Bottom)
};
}
///
/// Calculates the zoom needed after the rotation to ensure the canvas is covered
/// by the rotated image.
///
/// Width of the image.
/// Height of the image.
/// The angle in degrees.
///
/// Based on
///
/// The zoom needed
public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angleInDegrees)
{
Rectangle rectangle = GetBoundingRotatedRectangle(imageWidth, imageHeight, angleInDegrees);
return Math.Max((float)rectangle.Width / imageWidth, (float)rectangle.Height / imageHeight);
}
}
}