【C#】在一个任意旋转的矩形(由四个顶点定义)内绘制一个内切椭圆

核心点:在一个任意旋转的矩形(由四个顶点定义)内绘制一个内切椭圆

实现步骤

  1. 计算矩形中心:作为旋转中心点

  2. 创建椭圆路径:在未旋转状态下定义椭圆

  3. 应用旋转变换:使用矩阵绕中心点旋转路径

  4. 绘制变换后的路径

实现方法

cs 复制代码
public void DrawRotatedEllipse(Graphics g, PointF[] rotatedCorners, float rotationAngle)
{
    // 确保输入是四个点
    if (rotatedCorners.Length != 4)
        throw new ArgumentException("必须提供矩形的四个顶点");

    // 初始化画笔(根据您的原有逻辑)
    if (g != m_LastGraphic)
    {
        m_ControlPen = new Pen(Color.FromKnownColor(AnnotationSysINI.GetSetToolsColor()), 
                              AnnotationSysINI.GetSetToolsLineWide());
        m_BackPen = new Pen(Color.FromKnownColor(KnownColor.Black), 5);
        m_LastGraphic = g;
    }
    
    // 设置高质量绘图
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
    g.CompositingQuality = CompositingQuality.HighQuality;

    // 1. 计算旋转矩形的中心点
    PointF center = new PointF(
        (rotatedCorners[0].X + rotatedCorners[1].X + rotatedCorners[2].X + rotatedCorners[3].X) / 4,
        (rotatedCorners[0].Y + rotatedCorners[1].Y + rotatedCorners[2].Y + rotatedCorners[3].Y) / 4
    );

    // 2. 计算矩形的实际宽度和高度
    float width = (float)Math.Sqrt(
        Math.Pow(rotatedCorners[1].X - rotatedCorners[0].X, 2) +
        Math.Pow(rotatedCorners[1].Y - rotatedCorners[0].Y, 2)
    );
    
    float height = (float)Math.Sqrt(
        Math.Pow(rotatedCorners[3].X - rotatedCorners[0].X, 2) +
        Math.Pow(rotatedCorners[3].Y - rotatedCorners[0].Y, 2)
    );

    // 3. 创建椭圆路径(在原点处创建)
    using (GraphicsPath path = new GraphicsPath())
    {
        // 创建以原点为中心的椭圆
        RectangleF baseRect = new RectangleF(-width / 2, -height / 2, width, height);
        path.AddEllipse(baseRect);

        // 4. 应用变换:先旋转后平移
        using (Matrix transform = new Matrix())
        {
            // 注意顺序:先旋转后平移
            transform.Rotate(rotationAngle);
            transform.Translate(center.X, center.Y, MatrixOrder.Append);
            path.Transform(transform);
        }

        // 5. 绘制椭圆
        if (AnnotationSysINI.GetSetToolsLineBackPen())
            g.DrawPath(m_BackPen, path);
        g.DrawPath(m_ControlPen, path);
    }
    
    // 6. (可选) 绘制旋转矩形边界用于验证
    using (Pen debugPen = new Pen(Color.Red, 1))
    {
        g.DrawPolygon(debugPen, rotatedCorners);
    }
}

使用示例

cs 复制代码
// 创建旋转后的矩形四个顶点
PointF[] rotatedCorners = new PointF[4]
{
    new PointF(100, 50),   // 旋转后的左上
    new PointF(200, 70),   // 旋转后的右上
    new PointF(180, 170),  // 旋转后的右下
    new PointF(80, 150)    // 旋转后的左下
};

// 计算旋转角度(如果需要)
float angle = 30; // 已知的旋转角度

// 在Paint事件中调用
DrawRotatedEllipse(e.Graphics, rotatedCorners, angle);

关键点说明

  1. 直接使用旋转后的顶点

    • 方法接受旋转后矩形的四个顶点作为输入

    • 不再依赖原始未旋转的左上/右下坐标

  2. 正确计算尺寸

    cs 复制代码
    // 宽度 = 左上到右上的距离
    float width = Distance(rotatedCorners[0], rotatedCorners[1]);
    
    // 高度 = 左上到左下的距离
    float height = Distance(rotatedCorners[0], rotatedCorners[3]);
  3. 变换顺序至关重要

    cs 复制代码
    // 正确顺序:先旋转后平移
    transform.Rotate(rotationAngle);
    transform.Translate(center.X, center.Y, MatrixOrder.Append);
  4. 原点中心法

    • 先在原点(0,0)创建椭圆

    • 然后通过变换移动到实际位置

如果只有原始矩形和旋转角度

cs 复制代码
public PointF[] CalculateRotatedCorners(PointF topLeft, PointF bottomRight, float angle)
{
    float width = bottomRight.X - topLeft.X;
    float height = bottomRight.Y - topLeft.Y;
    PointF center = new PointF(topLeft.X + width/2, topLeft.Y + height/2);
    
    PointF[] corners = new PointF[4]
    {
        topLeft, // 原始左上
        new PointF(topLeft.X + width, topLeft.Y), // 原始右上
        bottomRight, // 原始右下
        new PointF(topLeft.X, topLeft.Y + height) // 原始左下
    };
    
    using (Matrix matrix = new Matrix())
    {
        matrix.RotateAt(angle, center);
        matrix.TransformPoints(corners);
    }
    
    return corners;
}

// 使用:
PointF topLeft = new PointF(50, 50);
PointF bottomRight = new PointF(250, 150);
float angle = 30f;

PointF[] rotatedCorners = CalculateRotatedCorners(topLeft, bottomRight, angle);
DrawRotatedEllipse(g, rotatedCorners, angle);

常见问题解决

  1. 椭圆位置偏移

    • 检查中心点计算是否正确

    • 验证顶点顺序是否一致(顺时针/逆时针)

    • 使用调试边界框可视化

  2. 椭圆变形

    • 确保宽度/高度计算正确

    • 检查旋转角度单位(度/弧度)

    • 确认矩形顶点构成直角

  3. 性能优化:这种方法保证了椭圆始终精确适配旋转后的矩形边界,无论旋转角度如何变化。

cs 复制代码
// 类级别缓存
private PointF[] _lastCorners;
private GraphicsPath _cachedPath;

// 在方法中:
if (_lastCorners != rotatedCorners)
{
    // 重新计算路径
    _cachedPath?.Dispose();
    _cachedPath = CreateEllipsePath(rotatedCorners, angle);
    _lastCorners = (PointF[])rotatedCorners.Clone();
}
g.DrawPath(pen, _cachedPath);

在旋转矩形内绘制椭圆并实现鼠标碰撞检测

cs 复制代码
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class RotatedEllipseTool
{
    // 存储椭圆路径用于碰撞检测
    private GraphicsPath _ellipsePath;
    
    // 画笔设置
    private Pen _controlPen;
    private Pen _backPen;
    private Graphics _lastGraphic;
    
    // 椭圆属性
    public PointF[] RotatedCorners { get; private set; }
    public float RotationAngle { get; private set; }
    
    public RotatedEllipseTool()
    {
        // 初始化画笔
        _controlPen = new Pen(Color.Blue, 2);
        _backPen = new Pen(Color.Black, 5);
    }
    
    // 创建旋转椭圆路径(用于绘制和碰撞检测)
    public void CreateRotatedEllipse(PointF[] rotatedCorners, float rotationAngle)
    {
        // 确保输入是四个点
        if (rotatedCorners.Length != 4)
            throw new ArgumentException("必须提供矩形的四个顶点");
        
        // 保存参数
        RotatedCorners = rotatedCorners;
        RotationAngle = rotationAngle;
        
        // 计算旋转矩形的中心点
        PointF center = new PointF(
            (rotatedCorners[0].X + rotatedCorners[1].X + rotatedCorners[2].X + rotatedCorners[3].X) / 4,
            (rotatedCorners[0].Y + rotatedCorners[1].Y + rotatedCorners[2].Y + rotatedCorners[3].Y) / 4
        );

        // 计算矩形的实际宽度和高度
        float width = Distance(rotatedCorners[0], rotatedCorners[1]);
        float height = Distance(rotatedCorners[0], rotatedCorners[3]);

        // 创建椭圆路径
        _ellipsePath?.Dispose(); // 释放旧路径
        _ellipsePath = new GraphicsPath();
        
        // 创建以原点为中心的椭圆
        RectangleF baseRect = new RectangleF(-width / 2, -height / 2, width, height);
        _ellipsePath.AddEllipse(baseRect);

        // 应用变换:先旋转后平移
        using (Matrix transform = new Matrix())
        {
            // 注意顺序:先旋转后平移
            transform.Rotate(rotationAngle);
            transform.Translate(center.X, center.Y, MatrixOrder.Append);
            _ellipsePath.Transform(transform);
        }
    }
    
    // 绘制椭圆
    public void Draw(Graphics g)
    {
        if (_ellipsePath == null) return;
        
        // 初始化画笔(如果需要)
        if (g != _lastGraphic)
        {
            // 这里使用您原有的配置逻辑
            _controlPen = new Pen(AnnotationSysINI.GetSetToolsColor(), 
                                 AnnotationSysINI.GetSetToolsLineWide());
            _backPen = new Pen(Color.Black, 5);
            _lastGraphic = g;
        }
        
        // 设置高质量绘图
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.PixelOffsetMode = PixelOffsetMode.HighQuality;
        g.CompositingQuality = CompositingQuality.HighQuality;

        // 绘制椭圆
        if (AnnotationSysINI.GetSetToolsLineBackPen())
            g.DrawPath(_backPen, _ellipsePath);
        g.DrawPath(_controlPen, _ellipsePath);
        
        // 绘制边界框(调试用)
        using (Pen debugPen = new Pen(Color.Red, 1))
        {
            g.DrawPolygon(debugPen, RotatedCorners);
        }
    }
    
    // 鼠标碰撞检测
    public bool HitTest(PointF point)
    {
        if (_ellipsePath == null) return false;
        
        // 使用路径的IsVisible方法进行碰撞检测
        return _ellipsePath.IsVisible(point);
    }
    
    // 辅助方法:计算两点距离
    private float Distance(PointF p1, PointF p2)
    {
        float dx = p2.X - p1.X;
        float dy = p2.Y - p1.Y;
        return (float)Math.Sqrt(dx * dx + dy * dy);
    }
    
    // 计算旋转后的矩形顶点
    public static PointF[] CalculateRotatedCorners(PointF topLeft, PointF bottomRight, float angle)
    {
        float width = bottomRight.X - topLeft.X;
        float height = bottomRight.Y - topLeft.Y;
        PointF center = new PointF(topLeft.X + width/2, topLeft.Y + height/2);
        
        PointF[] corners = new PointF[4]
        {
            topLeft, // 原始左上
            new PointF(topLeft.X + width, topLeft.Y), // 原始右上
            bottomRight, // 原始右下
            new PointF(topLeft.X, topLeft.Y + height) // 原始左下
        };
        
        using (Matrix matrix = new Matrix())
        {
            matrix.RotateAt(angle, center);
            matrix.TransformPoints(corners);
        }
        
        return corners;
    }
    
    // 释放资源
    public void Dispose()
    {
        _ellipsePath?.Dispose();
        _controlPen?.Dispose();
        _backPen?.Dispose();
    }
}

// 使用示例
public class DrawingForm : Form
{
    private RotatedEllipseTool _ellipseTool = new RotatedEllipseTool();
    
    public DrawingForm()
    {
        this.DoubleBuffered = true;
        this.Size = new Size(800, 600);
        
        // 创建旋转矩形
        PointF topLeft = new PointF(100, 100);
        PointF bottomRight = new PointF(300, 200);
        float rotationAngle = 30f;
        
        // 计算旋转后的顶点
        PointF[] rotatedCorners = RotatedEllipseTool.CalculateRotatedCorners(
            topLeft, bottomRight, rotationAngle);
        
        // 创建椭圆路径
        _ellipseTool.CreateRotatedEllipse(rotatedCorners, rotationAngle);
        
        // 设置鼠标事件
        this.MouseClick += DrawingForm_MouseClick;
        this.MouseMove += DrawingForm_MouseMove;
    }
    
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        _ellipseTool.Draw(e.Graphics);
    }
    
    private void DrawingForm_MouseClick(object sender, MouseEventArgs e)
    {
        if (_ellipseTool.HitTest(e.Location))
        {
            MessageBox.Show("点击了椭圆内部!");
        }
    }
    
    private void DrawingForm_MouseMove(object sender, MouseEventArgs e)
    {
        // 改变鼠标指针当在椭圆内部时
        this.Cursor = _ellipseTool.HitTest(e.Location) 
            ? Cursors.Hand 
            : Cursors.Default;
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _ellipseTool.Dispose();
        }
        base.Dispose(disposing);
    }
}

关键实现说明

1. 路径创建与保存

  • CreateRotatedEllipse方法计算并保存椭圆路径

  • 路径存储在_ellipsePath字段中,用于绘制和碰撞检测

  • 路径创建过程:

    1. 计算旋转矩形的中心点

    2. 计算矩形的实际宽度和高度

    3. 创建以原点为中心的椭圆

    4. 应用旋转变换矩阵

2. 鼠标碰撞检测

  • HitTest方法使用GraphicsPath.IsVisible()检测点是否在路径内

  • 该方法利用GDI+的内置功能进行精确碰撞检测

  • 在鼠标事件中调用该方法实现交互效果

3. 使用示例

  • 在窗体中创建旋转椭圆工具实例

  • 计算旋转后的矩形顶点

  • 创建椭圆路径

  • 在Paint事件中绘制椭圆

  • 在鼠标事件中检测碰撞

4. 辅助功能

  • CalculateRotatedCorners:从原始矩形计算旋转后的顶点

  • Distance:计算两点间距离

  • Dispose:正确释放GDI资源

实现效果

  1. 在旋转矩形内绘制完美适配的椭圆

  2. 椭圆随矩形旋转角度变化

  3. 鼠标在椭圆内部时指针变为手形

  4. 点击椭圆内部显示提示信息

  5. 使用高质量抗锯齿渲染

注意事项

  1. 资源管理

    • 使用后务必调用Dispose()释放资源

    • 路径在重新创建前释放旧路径

  2. 碰撞检测精度

    • IsVisible方法考虑了路径的填充区域

    • 对于空心椭圆,可能需要调整检测方式

  3. 性能优化

    • 路径创建后缓存起来,避免重复计算

    • 只在必要时重新创建路径(如矩形改变时)

URL

C# 图像旋转一定角度后,对应坐标怎么计算?_c# 求点旋转之后的坐标-CSDN博客文章浏览阅读1.8k次,点赞16次,收藏10次。本文详细解释了如何在二维空间中计算图像内坐标在旋转特定角度后的新坐标,包括中心点、角度转换、旋转公式以及使用C#代码示例展示如何应用这些概念。同时提到了处理边界问题和使用GDI+或OpenCV库进行旋转的功能。https://blog.csdn.net/wangnaisheng/article/details/137919395?spm=1011.2415.3001.5331 C# Bitmap实现角度旋转_c# bitmap 旋转-CSDN博客文章浏览阅读2.2k次,点赞10次,收藏8次。本文介绍了在C#中对Bitmap对象进行旋转的三种方法:使用Bitmap.RotateFlip方法进行90度旋转,使用System.Drawing.Drawing2D.Matrix类进行任意角度旋转,以及利用第三方库如ImageSharp进行高级图像处理。https://blog.csdn.net/wangnaisheng/article/details/138160242?spm=1011.2415.3001.5331

相关推荐
Kookoos7 小时前
ABP + ClickHouse 实时 OLAP:物化视图与写入聚合
clickhouse·c#·linq·abp vnext·实时olap
ghie909011 小时前
C#实现OPC客户端
c#
甄天12 小时前
VisionPro联合编程控件导入WinFrom以及VS卡死问题
c#·visual studio·visionpro
oioihoii12 小时前
VS Code C#调试完全指南
开发语言·c#·策略模式
咕白m62515 小时前
使用 C# 实现 PDF 转 HTML 并自定义转换选项
c#·html
唐青枫19 小时前
从 Skip Take 到 Keyset:C# 分页原理与实践
c#·.net
钢铁男儿21 小时前
C# 一个投资跟踪程序的设计与实现:面向对象与设计模式的深度解析
java·设计模式·c#
技术支持者python,php1 天前
C#-mqtt通讯,服务端和客户端,以及esp32-mqtt
服务器·windows·c#
菌王2 天前
EXCEL 2 word 的一些案例。excel通过一些策略将内容写入word中。
开发语言·c#