如何避免缩放用户控件带有圆角的边框的视觉伪影?

4

我有一个Form,其中包含:

  1. TrackBar(最小值为1,最大值为200,表示缩放百分比);
  2. UserControl,其BorderStyle = BorderStyle.None

相关代码

Form1

来自设计器的代码

trackBar1.Value = 100;
BackColor = Color.Gray;

通过代码后台

private void trackBar1_Scroll(object sender, EventArgs e)
{
    userControl11.SetZoomFactor(trackBar1.Value / 100F);
}

UserControl1

internal float MyBaseWidth;

public UserControl1()
{
    InitializeComponent();

    MyBaseWidth = Width;

    SetZoomFactor(1);
}

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
    e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
    e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

    Pen p = new Pen(Color.Yellow);
    e.Graphics.DrawPath(p, GraphicsPathWithBorder);
}

internal GraphicsPath GraphicsPathWithBorder;

internal void SetZoomFactor(float z)
{
    Width = (int)(MyBaseWidth * z);

    GraphicsPathWithBorder = RoundedCornerRectangle(ClientRectangle);
    Region = new Region(GraphicsPathWithBorder);
}

internal static GraphicsPath RoundedCornerRectangle(Rectangle r)
{
    GraphicsPath path = new GraphicsPath();
    float size = 10 * 2F;

    path.StartFigure();

    path.AddArc(r.X, r.Y,
        size, size, 180, 90);
    path.AddArc((r.X + (r.Width - size)), r.Y,
        size, size, 270, 90);
    path.AddArc((r.X + (r.Width - size)), (r.Y + (r.Height - size)),
        size, size, 0, 90);
    path.AddArc(r.X, (r.Y + (r.Height - size)),
        size, size, 90, 90);

    path.CloseFigure();

    return path;
}

初始截图

initially

使用滑动条后的截图

after zoom

缩小后黄色边框的右侧变得不可见,放大时右侧会出现多个黄色边框。

更新:

答案是正确的,但控件的一部分超出了边界。以下是curveSize = 20的右上角截图:

curve 1

以及curveSize = 24的截图:

curve 2


1
在更改大小后,您需要通过覆盖 OnSizeChanged 并调用 Invalidate 手动使控件无效,或者通过设置 ResizeRedraw=true 自动使其无效。 - Reza Aghaei
你是否在寻找类似于这个的东西? - Reza Aghaei
@RezaAghaei ResizeRedraw=true 能够正常工作,但边框不会进行抗锯齿处理。 - silviubogan
这是另一回事。实际上,为了获得平滑的边缘,您应该忘记更改区域。相反,您需要创建一个支持透明背景颜色的控件。 - Reza Aghaei
1个回答

7
我建议使用略微不同的方法来绘制边框和用户控件的内容,这也应该解决了重新绘制控件时产生的伪像问题。
当您为控件创建区域,然后将区域绘制出来时,绘制的外部边框没有反锯齿效果:锯齿状像素落在区域之外。当在区域周围绘制边框时,当然也会应用相同的效果。
这里,我应用缩放矩阵平移矩阵,将区域的边界缩放和移动到定义控件边界的外部区域内部。
缩放和平移变换的大小由画笔大小确定。
有关矩阵用法的更多信息,请参见此处:翻转GraphicsPath
在这种情况下,当边框被绘制时,边框的外部抗锯齿部分位于区域边界内,并且抗锯齿效果得到保留。
控件的背景颜色设置为Color.Transparent(用户控件本身支持颜色透明度)。
我还添加了一些(非装饰性)属性,允许定义内部颜色(控件的BackColor)和边框的大小和颜色。其余部分基本上与以前相同。
示例结果:

Rounded Zoomable UserControl


using System.Drawing;
using System.Drawing.Drawing2D;

public partial class RoundControl : UserControl
{
    private GraphicsPath GraphicsPathWithBorder;
    private float MyBaseWidth;
    private float m_PenSize = 2f;
    private Color m_BorderColor = Color.Yellow;
    private Color m_FillColor = Color.Green;

    public RoundControl()
    {
        ResizeRedraw = true;
        InitializeComponent();
        MyBaseWidth = Width;
    }

    public float BorderSize
    {
        get => m_PenSize;
        set {
            m_PenSize = value;
            Invalidate();
        }
    }

    public Color BorderColor
    {
        get => m_BorderColor;
        set {
            m_BorderColor = value;
            Invalidate();
        }
    }

    public Color FillColor
    {
        get => m_FillColor;
        set {
            m_FillColor = value;
            Invalidate();
        }
    }

    protected override void OnLayout(LayoutEventArgs e) {
        UpdateRegion();
        base.OnLayout(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        RectangleF rect = GraphicsPathWithBorder.GetBounds();
        float scaleX = 1 - ((m_PenSize + 1) / rect.Width);
        float scaleY = 1 - ((m_PenSize + 1) / rect.Height);
        using (Pen pen = new Pen(m_BorderColor, m_PenSize))
        using (Brush brush = new SolidBrush(m_FillColor))
        using (Matrix mx = new Matrix(scaleX, 0, 0, scaleY, pen.Width / 2, pen.Width / 2))
        {
            e.Graphics.Transform = mx;
            e.Graphics.FillPath(brush, GraphicsPathWithBorder);
            e.Graphics.DrawPath(pen, GraphicsPathWithBorder);
        }
        base.OnPaint(e);
    }

    internal void SetZoomFactor(float z) {
        int newWidth = (int)(MyBaseWidth * z);
        if (newWidth <= (30 + m_PenSize * 2)) return;
        Width = newWidth;
        UpdateRegion();
    }


    private void UpdateRegion() {
        GraphicsPathWithBorder = RoundedCornerRectangle(ClientRectangle);
        Region = new Region(GraphicsPathWithBorder);
        Invalidate();
    }

    private GraphicsPath RoundedCornerRectangle(Rectangle r)
    {
        GraphicsPath path = new GraphicsPath();
        // Fixed curve size since we only scale on X-dimension
        // Otherwise, adjust also considering the height
        float curveSize = 10 * 2.4F;

        path.StartFigure();
        path.AddArc(r.X, r.Y, curveSize, curveSize, 180, 90);
        path.AddArc(r.Right - curveSize, r.Y, curveSize, curveSize, 270, 90);
        path.AddArc(r.Right - curveSize, r.Bottom - curveSize, curveSize, curveSize, 0, 90);
        path.AddArc(r.X, r.Bottom - curveSize, curveSize, curveSize, 90, 90);
        path.CloseFigure();
        return path;
    }
}

你是否按照描述设置了 BackColor = Color.Transparent?在编写生产代码时,应覆盖/消除所有用户不得篡改的属性。如果是为自己编写,可以保留属性可见并在设计器中设置它。或者使用 OnPaint 方法并自行绘制背景。或者覆盖 OnPaintBackground。或者使用 SetStyle(ControlStyles.Opaque, true);,这样就没有背景了,再次可以自行绘制任何您喜欢的背景。 - Jimi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接