c# - solo - redondear esquinas photoshop cc
Algoritmo para crear esquinas redondeadas en un polĂgono (5)
Algo de geometría con Paint:
0. Tienes una esquina:
1. Conoces las coordenadas de los puntos de esquina, deja que sean P 1 , P 2 y P:
2. Ahora puedes obtener vectores de puntos y ángulos entre vectores:
angle = atan(PY - P1Y, PX - P1X) - atan(PY - P2Y, PX - P2X)
3. Obtenga la longitud del segmento entre el punto angular y los puntos de intersección con el círculo.
segment = PC1 = PC2 = radius / |tan(angle / 2)|
4. Aquí debe verificar la longitud del segmento y la longitud mínima de PP 1 y PP 2 :
Longitud de PP 1 :
PP1 = sqrt((PX - P1X)2 + (PY - P1Y)2)
Longitud de PP 2 :
PP2 = sqrt((PX - P2X)2 + (PY - P2Y)2)
Si segmento> PP 1 o segmento> PP 2, entonces necesita disminuir el radio:
min = Min(PP1, PP2) (for polygon is better to divide this value by 2) segment > min ? segment = min radius = segment * |tan(angle / 2)|
5. Obtenga la longitud de PO:
PO = sqrt(radius2 + segment2)
6. Obtenga el C 1 X y C 1 Y en la proporción entre las coordenadas del vector, la longitud del vector y la longitud del segmento:
Proporción:
(PX - C1X) / (PX - P1X) = PC1 / PP1
Asi que:
C1X = PX - (PX - P1X) * PC1 / PP1
Lo mismo para C 1 Y :
C1Y = PY - (PY - P1Y) * PC1 / PP1
7. Obtenga el C 2 X y C 2 Y de la misma manera:
C2X = PX - (PX - P2X) * PC2 / PP2 C2Y = PY - (PY - P2Y) * PC2 / PP2
8. Ahora puede usar la adición de vectores PC 1 y PC 2 para encontrar el centro del círculo de la misma manera por proporción:
(PX - OX) / (PX - CX) = PO / PC (PY - OY) / (PY - CY) = PO / PC
Aquí:
CX = C1X + C2X - PX CY = C1Y + C2Y - PY PC = sqrt((PX - CX)2 + (PY - CY)2)
Dejar:
dx = PX - CX = PX * 2 - C1X - C2X dy = PY - CY = PY * 2 - C1Y - C2Y
Asi que:
PC = sqrt(dx2 + dy2) OX = PX - dx * PO / PC OY = PY - dy * PO / PC
9. Aquí puedes dibujar un arco. Para esto, necesitas obtener el ángulo de inicio y el ángulo de arco final:
Lo encontré aquí :
startAngle = atan((C1Y - OY) / (C1X - OX)) endAngle = atan((C2Y - OY) / (C2X - OX))
10. Por último, necesitas obtener un ángulo de barrido y hacer algunos controles para ello:
sweepAngle = endAngle - startAngle
Si sweepAngle <0 entonces intercambia startAngle y endAngle, e invierte sweepAngle:
sweepAngle < 0 ?
sweepAngle = - sweepAngle
startAngle = endAngle
Comprueba si barrer en ángulo> 180 grados:
sweepAngle > 180 ?
sweepAngle = 180 - sweepAngle
11. Y ahora puedes dibujar una esquina redondeada:
Alguna geometría con c #:
private void DrawRoundedCorner(Graphics graphics, PointF angularPoint,
PointF p1, PointF p2, float radius)
{
//Vector 1
double dx1 = angularPoint.X - p1.X;
double dy1 = angularPoint.Y - p1.Y;
//Vector 2
double dx2 = angularPoint.X - p2.X;
double dy2 = angularPoint.Y - p2.Y;
//Angle between vector 1 and vector 2 divided by 2
double angle = (Math.Atan2(dy1, dx1) - Math.Atan2(dy2, dx2)) / 2;
// The length of segment between angular point and the
// points of intersection with the circle of a given radius
double tan = Math.Abs(Math.Tan(angle));
double segment = radius / tan;
//Check the segment
double length1 = GetLength(dx1, dy1);
double length2 = GetLength(dx2, dy2);
double length = Math.Min(length1, length2);
if (segment > length)
{
segment = length;
radius = (float)(length * tan);
}
// Points of intersection are calculated by the proportion between
// the coordinates of the vector, length of vector and the length of the segment.
var p1Cross = GetProportionPoint(angularPoint, segment, length1, dx1, dy1);
var p2Cross = GetProportionPoint(angularPoint, segment, length2, dx2, dy2);
// Calculation of the coordinates of the circle
// center by the addition of angular vectors.
double dx = angularPoint.X * 2 - p1Cross.X - p2Cross.X;
double dy = angularPoint.Y * 2 - p1Cross.Y - p2Cross.Y;
double L = GetLength(dx, dy);
double d = GetLength(segment, radius);
var circlePoint = GetProportionPoint(angularPoint, d, L, dx, dy);
//StartAngle and EndAngle of arc
var startAngle = Math.Atan2(p1Cross.Y - circlePoint.Y, p1Cross.X - circlePoint.X);
var endAngle = Math.Atan2(p2Cross.Y - circlePoint.Y, p2Cross.X - circlePoint.X);
//Sweep angle
var sweepAngle = endAngle - startAngle;
//Some additional checks
if (sweepAngle < 0)
{
startAngle = endAngle;
sweepAngle = -sweepAngle;
}
if (sweepAngle > Math.PI)
sweepAngle = Math.PI - sweepAngle;
//Draw result using graphics
var pen = new Pen(Color.Black);
graphics.Clear(Color.White);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawLine(pen, p1, p1Cross);
graphics.DrawLine(pen, p2, p2Cross);
var left = circlePoint.X - radius;
var top = circlePoint.Y - radius;
var diameter = 2 * radius;
var degreeFactor = 180 / Math.PI;
graphics.DrawArc(pen, left, top, diameter, diameter,
(float)(startAngle * degreeFactor),
(float)(sweepAngle * degreeFactor));
}
private double GetLength(double dx, double dy)
{
return Math.Sqrt(dx * dx + dy * dy);
}
private PointF GetProportionPoint(PointF point, double segment,
double length, double dx, double dy)
{
double factor = segment / length;
return new PointF((float)(point.X - dx * factor),
(float)(point.Y - dy * factor));
}
Para obtener puntos de arco puede usar esto:
//One point for each degree. But in some cases it will be necessary
// to use more points. Just change a degreeFactor.
int pointsCount = (int)Math.Abs(sweepAngle * degreeFactor);
int sign = Math.Sign(sweepAngle);
PointF[] points = new PointF[pointsCount];
for (int i = 0; i < pointsCount; ++i)
{
var pointX =
(float)(circlePoint.X
+ Math.Cos(startAngle + sign * (double)i / degreeFactor)
* radius);
var pointY =
(float)(circlePoint.Y
+ Math.Sin(startAngle + sign * (double)i / degreeFactor)
* radius);
points[i] = new PointF(pointX, pointY);
}
Estoy buscando un algoritmo que me permita crear esquinas redondeadas desde un polígono. En Entrada, obtengo una matriz de puntos que representa el polígono (línea roja) y en salida, una matriz de puntos que representa el polígono con esquina redondeada (línea negra).
También me gustaría tener una forma de controlar el radio de cada esquina. Ya traté de usar Bezier y Subdivision, pero no es lo que estoy buscando. Bezier y Subdivision suavizan todo el polígono. Lo que quiero, es solo redondear las esquinas.
Alguien sabe algún buen algoritmo para hacer eso? Estoy trabajando en C # pero el código tiene que ser independiente de cualquier biblioteca .NET.
Adaptación de Objective-C de la respuesta de nempoBu4 :
typedef enum {
path_move_to,
path_line_to
} Path_command;
static inline CGFloat sqr (CGFloat a)
{
return a * a;
}
static inline CGFloat positive_angle (CGFloat angle)
{
return angle < 0 ? angle + 2 * (CGFloat) M_PI : angle;
}
static void add_corner (UIBezierPath* path, CGPoint p1, CGPoint p, CGPoint p2, CGFloat radius, Path_command first_add)
{
// 2
CGFloat angle = positive_angle (atan2f (p.y - p1.y, p.x - p1.x) - atan2f (p.y - p2.y, p.x - p2.x));
// 3
CGFloat segment = radius / fabsf (tanf (angle / 2));
CGFloat p_c1 = segment;
CGFloat p_c2 = segment;
// 4
CGFloat p_p1 = sqrtf (sqr (p.x - p1.x) + sqr (p.y - p1.y));
CGFloat p_p2 = sqrtf (sqr (p.x - p2.x) + sqr (p.y - p2.y));
CGFloat min = MIN(p_p1, p_p2);
if (segment > min) {
segment = min;
radius = segment * fabsf (tanf (angle / 2));
}
// 5
CGFloat p_o = sqrtf (sqr (radius) + sqr (segment));
// 6
CGPoint c1;
c1.x = (CGFloat) (p.x - (p.x - p1.x) * p_c1 / p_p1);
c1.y = (CGFloat) (p.y - (p.y - p1.y) * p_c1 / p_p1);
// 7
CGPoint c2;
c2.x = (CGFloat) (p.x - (p.x - p2.x) * p_c2 / p_p2);
c2.y = (CGFloat) (p.y - (p.y - p2.y) * p_c2 / p_p2);
// 8
CGFloat dx = p.x * 2 - c1.x - c2.x;
CGFloat dy = p.y * 2 - c1.y - c2.y;
CGFloat p_c = sqrtf (sqr (dx) + sqr (dy));
CGPoint o;
o.x = p.x - dx * p_o / p_c;
o.y = p.y - dy * p_o / p_c;
// 9
CGFloat start_angle = positive_angle (atan2f ((c1.y - o.y), (c1.x - o.x)));
CGFloat end_angle = positive_angle (atan2f ((c2.y - o.y), (c2.x - o.x)));
if (first_add == path_move_to) {
[path moveToPoint: c1];
}
else {
[path addLineToPoint: c1];
}
[path addArcWithCenter: o radius: radius startAngle: start_angle endAngle: end_angle clockwise: angle < M_PI];
}
UIBezierPath* path_with_rounded_corners (NSArray<NSValue*>* points, CGFloat corner_radius)
{
UIBezierPath* path = [UIBezierPath bezierPath];
NSUInteger count = points.count;
for (NSUInteger i = 0; i < count; ++i) {
CGPoint prev = points[i > 0 ? i - 1 : count - 1].CGPointValue;
CGPoint p = points[i].CGPointValue;
CGPoint next = points[i + 1 < count ? i + 1 : 0].CGPointValue;
add_corner (path, prev, p, next, corner_radius, i == 0 ? path_move_to : path_line_to);
}
[path closePath];
return path;
}
Aquí está mi realización de la idea de dbc en c #:
/// <summary>
/// Round polygon corners
/// </summary>
/// <param name="points">Vertices array</param>
/// <param name="radius">Round radius</param>
/// <returns></returns>
static public GraphicsPath RoundCorners(PointF[] points, float radius) {
GraphicsPath retval = new GraphicsPath();
if (points.Length < 3) {
throw new ArgumentException();
}
rects = new RectangleF[points.Length];
PointF pt1, pt2;
//Vectors for polygon sides and normal vectors
Vector v1, v2, n1 = new Vector(), n2 = new Vector();
//Rectangle that bounds arc
SizeF size = new SizeF(2 * radius, 2 * radius);
//Arc center
PointF center = new PointF();
for (int i = 0; i < points.Length; i++) {
pt1 = points[i];//First vertex
pt2 = points[i == points.Length - 1 ? 0 : i + 1];//Second vertex
v1 = new Vector(pt2.X, pt2.Y) - new Vector(pt1.X, pt1.Y);//One vector
pt2 = points[i == 0 ? points.Length - 1 : i - 1];//Third vertex
v2 = new Vector(pt2.X, pt2.Y) - new Vector(pt1.X, pt1.Y);//Second vector
//Angle between vectors
float sweepangle = (float)Vector.AngleBetween(v1, v2);
//Direction for normal vectors
if (sweepangle < 0) {
n1 = new Vector(v1.Y, -v1.X);
n2 = new Vector(-v2.Y, v2.X);
}
else {
n1 = new Vector(-v1.Y, v1.X);
n2 = new Vector(v2.Y, -v2.X);
}
n1.Normalize(); n2.Normalize();
n1 *= radius; n2 *= radius;
/// Points for lines which intersect in the arc center
PointF pt = points[i];
pt1 = new PointF((float)(pt.X + n1.X), (float)(pt.Y + n1.Y));
pt2 = new PointF((float)(pt.X + n2.X), (float)(pt.Y + n2.Y));
double m1 = v1.Y / v1.X, m2 = v2.Y / v2.X;
//Arc center
if (v1.X == 0) {// first line is parallel OY
center.X = pt1.X;
center.Y = (float)(m2 * (pt1.X - pt2.X) + pt2.Y);
}
else if (v1.Y == 0) {// first line is parallel OX
center.X = (float)((pt1.Y - pt2.Y) / m2 + pt2.X);
center.Y = pt1.Y;
}
else if (v2.X == 0) {// second line is parallel OY
center.X = pt2.X;
center.Y = (float)(m1 * (pt2.X - pt1.X) + pt1.Y);
}
else if (v2.Y == 0) {//second line is parallel OX
center.X = (float)((pt2.Y - pt1.Y) / m1 + pt1.X);
center.Y = pt2.Y;
}
else {
center.X = (float)((pt2.Y - pt1.Y + m1 * pt1.X - m2 * pt2.X) / (m1 - m2));
center.Y = (float)(pt1.Y + m1 * (center.X - pt1.X));
}
rects[i] = new RectangleF(center.X - 2, center.Y - 2, 4, 4);
//Tangent points on polygon sides
n1.Negate(); n2.Negate();
pt1 = new PointF((float)(center.X + n1.X), (float)(center.Y + n1.Y));
pt2 = new PointF((float)(center.X + n2.X), (float)(center.Y + n2.Y));
//Rectangle that bounds tangent arc
RectangleF rect = new RectangleF(new PointF(center.X - radius, center.Y - radius), size);
sweepangle = (float)Vector.AngleBetween(n2, n1);
retval.AddArc(rect, (float)Vector.AngleBetween(new Vector(1, 0), n2), sweepangle);
}
retval.CloseAllFigures();
return retval;
}
Aquí hay una forma de usar algo de geometría:
- las dos líneas son tangentes al círculo inscrito
- La normal a la tangente se encuentra en el centro del círculo.
- Deje que el ángulo entre líneas sea X
- El ángulo subtendido en el centro del círculo será K = 360-90 * 2-X = 180-X
- Vamos a decidir los dos puntos de tangentes como (x1, y) y (x2, y)
- El acorde que une los puntos tiene longitud l = (x2-x1)
- Dentro del círculo, el acorde y dos normales de longitud r (radio) forman un triángulo isósceles
- El pendicular divide el traingle en igual a la mitad de triángulos en ángulo recto.
- Uno de ángulo es K / 2 y el lado es l / 2
- usando propiedades de triángulo rectángulo sin sen (K / 2) = (l / 2) / r
- r = (l / 2) / sin (K / 2)
- pero K = 180-X entonces r = (l / 2) / sin (90-X / 2) = (l / 2) / cos (X / 2)
- de ahí que r = (x2-x1) / (2 * cos (X / 2))
- Ahora simplemente dibuja un arco de (x1, y) a (x2, y) usando el radio r
Nota:-
Lo anterior se explica solo para líneas que se encuentran en el origen y el eje Y divide el ángulo entre ellas en la mitad. Pero es igualmente aplicable para todos los rincones solo necesita aplicar una rotación y una traducción antes de aplicar lo anterior. Además, debe seleccionar algunos valores x de intersección desde donde desea dibujar el arco. Los valores no deben ser demasiado lejos o cercanos al origen
Está buscando una tangente de arco a dos segmentos de línea conectados, de un radio dado, dado por una serie de puntos secuenciales. El algoritmo para encontrar este arco es el siguiente:
Para cada segmento, construye un vector normal.
Si está trabajando en 2d, puede restar los dos puntos finales para obtener un vector tangente (X, Y). En ese caso, los vectores normales serán más o menos (-Y, X). Normalice el vector normal a la longitud uno. Finalmente, elija la dirección con un producto punto positivo con el vector tangente del siguiente segmento. ( Ver la actualización a continuación ).
Si está trabajando en 3d no 2D, para obtener lo normal, cruce los vectores tangentes de los dos segmentos en el vértice que desea redondear para obtener un vector perpendicular al plano de las líneas. Si la perpendicular tiene una longitud cero, los segmentos son paralelos y no se puede requerir ninguna ronda. De lo contrario, normalízalo, luego cruza la perpendicular con la tangente para obtener lo normal).
Usando los vectores normales, desplace cada segmento de línea hacia el interior del polígono según su radio deseado. Para compensar un segmento, desplace sus puntos finales utilizando el vector normal N que acaba de calcular, como ese: P ''= P + r * N (una combinación lineal).
Intersecta las dos líneas de desplazamiento para encontrar el centro. (Esto funciona porque un vector de radio de un círculo es siempre perpendicular a su tangente).
Para encontrar el punto en el cual el círculo intersecta cada segmento, desplace el centro del círculo hacia atrás para cada segmento original. Estos serán los puntos finales de su arco.
Asegúrese de que los puntos finales del arco estén dentro de cada segmento, de lo contrario creará un polígono autointersecante.
Crea un arco a través de ambos puntos finales con el centro y el radio que determinaste.
No tengo a mano ningún software de dibujo adecuado, pero este diagrama muestra la idea:
En este punto, necesitará introducir clases para representar una figura que consta de segmentos de línea y arco, o poligonalizar el arco con la precisión adecuada y agregar todos los segmentos al polígono.
Actualización: he actualizado la imagen, etiquetando los puntos P1, P2 y P3, y los vectores normales Norm12 y Norm23. Las normales normalizadas son únicas solo hasta la dirección de volteo, y debe elegir las volteos de la siguiente manera:
El producto escalar de Norm12 con (P3 - P2) debe ser positivo. Si es negativo, múltiples Norm12 por -1.0. Si es cero, los puntos son colineales y no es necesario crear una esquina redondeada. Esto se debe a que desea compensar hacia P3.
El producto escalar de Norm23 con (P1 - P2) también debe ser positivo, ya que se está desplazando hacia P1.