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