Скругленный прямоугольник не точный - PullRequest
8 голосов
/ 19 мая 2011

Каждый пример кода, который я когда-либо нашел для рисования скругленных прямоугольников с использованием GDI +, выглядит примерно так (поднято и слегка изменено из BobPowell.net):

  Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Panel1.Paint
    e.Graphics.Clear(SystemColors.Window)
    e.Graphics.SmoothingMode = SmoothingMode.None

    Call DrawRoundRect(e.Graphics, Pens.Red, 10, 10, 48, 24, 6)
  End Sub

  Public Sub DrawRoundRect(ByVal g As Graphics, ByVal p As Pen, ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single, ByVal radius As Single)
    Using gp As New GraphicsPath()
      gp.StartFigure()
      gp.AddArc(x + width - radius, y, radius * 2, radius * 2, 270, 90)
      gp.AddArc(x + width - radius, y + height - radius, radius * 2, radius * 2, 0, 90)
      gp.AddArc(x, y + height - radius, radius * 2, radius * 2, 90, 90)
      gp.AddArc(x, y, radius * 2, radius * 2, 180, 90)
      gp.CloseFigure()
      g.DrawPath(p, gp)
    End Using
  End Sub

В результате получается прямоугольник с закругленными углами, в котором точен только верхний левый угол.

Антиалиасинг должен быть отключен, поскольку он проходит через подключение к удаленному рабочему столу, и я не могу зависеть от его доступности. Кроме того, я ищу четкий прямоугольник с закругленными углами.

enter image description here

Я пытался изменить размеры других углов и изменить выравнивание пера, но, кажется, ничто не дает простой, точный скругленный прямоугольник.

Есть ли способ нарисовать лучший закругленный прямоугольник, чем этот, в старых добрых winforms?

Ответы [ 4 ]

3 голосов
/ 21 июля 2011

1) Измените исходное изображение на двоичное, кратное его первоначальному размеру.Обычно я делаю выборку до ширины и высоты в 4 раза больше (или 8, или 16), чем оригинал.

2) Выполните все мои операции рисования GDI + (принимая во внимание, конечно, что мой сокоординаты должны быть умножены в 4 раза.Нет необходимости использовать какие-либо причудливые сглаживания.

3) Повторно сэмплируйте изображение обратно к исходным размерам.Сжатие изображения приводит к хорошему эффекту сглаживания и минимизирует любые ошибки округления в линиях, кривых и т. Д.

private Bitmap GenerateButton(int overSampling) {

    int overSampling = 8;
    int width=(48 + 10 + 10 + 6) * overSampling;
    int height=(24 + 10 + 10 + 6) * overSampling;

    // Draw the button with the rounded corners, but do
    // so at 8 times the normal size.
    Bitmap bitmap=new Bitmap(width,height);
    using (Graphics g = Graphics.FromImage(bitmap)) {
        g.Clear(Color.White);
        g.SmoothingMode = SmoothingMode.None;
        DrawRoundRect(overSampling, g, new Pen(Color.Red, overSampling), 10, 10, 48, 24, 6);
    }

    // Shrink the image down to its intended size
    Bitmap shrunkVersion=new Bitmap(bitmap.Width / overSampling, bitmap.Height / overSampling);
    using (Graphics g = Graphics.FromImage(shrunkVersion)) {
        // Use hi-quality resampling for a nice, smooth image.
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.DrawImage(bitmap, 0, 0, shrunkVersion.Width, shrunkVersion.Height);
    }

    return shrunkVersion;
}

private void DrawRoundRect(int overSampling, Graphics g, Pen p, float x, float y, float width, float height, float radius)
{
    using (GraphicsPath gp = new GraphicsPath())
    {
        gp.StartFigure();
        gp.AddArc((x + width - radius) * overSampling, y * overSampling, (radius * 2) * overSampling, (radius * 2) * overSampling, 270, 90);
        gp.AddArc((x + width - radius) * overSampling, (y + height - radius) * overSampling, (radius * 2) * overSampling, (radius * 2) * overSampling, 0, 90);
        gp.AddArc(x * overSampling, (y + height - radius) * overSampling, radius * 2 * overSampling, radius * 2 * overSampling, 90, 90);
        gp.AddArc(x * overSampling, y * overSampling, radius * 2 * overSampling, radius * 2 * overSampling, 180, 90);
        gp.CloseFigure();
        g.DrawPath(p, gp);
    }
}

Без передискретизации:

Without Smoothing

С8 раз передискретизация:

With Smoothing

2 голосов
/ 05 июля 2011

Я нашел лучшее решение - это просто Windows API старой школы:

Private Sub DrawRoundRect(ByVal g As Graphics, ByVal r As Rectangle)
  Dim hDC As IntPtr = g.GetHdc
  Dim hPen As IntPtr = CreatePen(PS_SOLID, 0, ColorTranslator.ToWin32(Color.Red))
  Dim hOldPen As IntPtr = SelectObject(hDC, hPen)
  SelectObject(hDC, GetStockObject(NULL_BRUSH))
  RoundRect(hDC, r.Left, r.Top, r.Right - 1, r.Bottom - 1, 12, 12)
  SelectObject(hDC, hOldPen)
  DeleteObject(hPen)
  g.ReleaseHdc(hDC)
End Sub

Это дает симметричный прямоугольник с закругленными углами, который я искал:

enter image description here

1 голос
/ 08 июня 2011

Иногда я использовал «низкотехнологичный» подход для устранения ошибок округления в GDI +

1) Изменение размера исходного изображения до двоичного значения, кратного его первоначальному размеру.Как правило, я делаю выборку до ширины и высоты в 4 раза больше (или 8, или 16), чем оригинал.

2) Выполните все мои операции рисования GDI + (принимая во внимание, конечно, что мой сокоординаты должны быть умножены в 4 раза.Нет необходимости использовать какие-либо необычные сглаживания.

3) Повторно сэмплируйте изображение обратно к исходным размерам.Сжатие изображения приводит к хорошему эффекту сглаживания и минимизирует любые ошибки округления в линиях, кривых и т. Д.

1 голос
/ 21 мая 2011

Потому что никто еще не ответил вам, вот уловка, которую я использовал в прошлом. Он работает достаточно хорошо и определенно выглядит лучше, чем классическая реализация с AddArc ().

Он использует круги и обрезки для достижения желаемого результата. Может отображаться незначительные артефакты при использовании перьев шириной более 1 пикселя, но в остальном он работает хорошо.

Надеюсь, этого будет достаточно для вашего проекта.

    private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
    {
        g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
        g.DrawLine(pen, rect.Right, rect.Top+radius, rect.Right, rect.Bottom - radius);
        g.DrawLine(pen, rect.Left + radius, rect.Bottom, rect.Right - radius, rect.Bottom);
        g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);

        g.SetClip(new Rectangle(rect.Left, rect.Top, radius, radius));
        g.DrawEllipse(pen, rect.Left, rect.Top, radius * 2, radius * 2);
        g.ResetClip();

        g.SetClip(new Rectangle(rect.Right-radius, rect.Top, radius+1, radius+1));
        g.DrawEllipse(pen, rect.Right - radius * 2, rect.Top, radius * 2, radius * 2);
        g.ResetClip();

        g.SetClip(new Rectangle(rect.Right - radius, rect.Bottom-radius, radius+1, radius+1));
        g.DrawEllipse(pen, rect.Right - radius * 2, rect.Bottom - (radius * 2), radius * 2, radius * 2);
        g.ResetClip();

        g.SetClip(new Rectangle(rect.Left, rect.Bottom - radius, radius+1, radius+1));
        g.DrawEllipse(pen, rect.Left, rect.Bottom - (radius * 2), radius * 2, radius * 2);
        g.ResetClip();
    }

Интерфейс метода прост, но оставьте комментарий, если вам нужна помощь.

Редактировать: Что-то еще, что должно сработать, - нарисовать одну и ту же дугу четыре раза, но перевернуть, используя TranslateTransform и TranslateScale. Это должно означать, что дуга выглядит идентичной в каждом углу.

    private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
    {
        g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
        g.DrawLine(pen, rect.Right-1, rect.Top+radius, rect.Right-1, rect.Bottom - radius);
        g.DrawLine(pen, rect.Left + radius, rect.Bottom-1, rect.Right - radius, rect.Bottom-1);
        g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);

        g.TranslateTransform(rect.Left, rect.Top);
        g.DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);
        g.ResetTransform();

        g.TranslateTransform(rect.Right, rect.Top);
        g.ScaleTransform(-1, 1);
        g.DrawArc(pen, 1, 0, radius * 2, radius * 2, 180, 90);
        g.ResetTransform();

        g.TranslateTransform(rect.Right, rect.Bottom);
        g.ScaleTransform(-1, -1);
        g.DrawArc(pen, 1, 1, radius * 2, radius * 2, 180, 90);
        g.ResetTransform();

        g.TranslateTransform(rect.Left, rect.Bottom);
        g.ScaleTransform(1, -1);
        g.DrawArc(pen, 0, 1, radius * 2, radius * 2, 180, 90);
        g.ResetTransform();
    }

Это похоже на старый метод компьютерной графики для рисования круга, в котором вы бы рисовали четверть круга четыре раза, чтобы избежать ошибок округления, таких как ошибка в GDI.

Другая альтернатива - нарисовать первую дугу на изображении, а затем нарисовать изображение четыре раза, переворачивая при необходимости. Ниже приведен вариант второго метода с использованием изображения для рисования дуг.

    private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
    {
        g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
        g.DrawLine(pen, rect.Right - 1, rect.Top + radius, rect.Right - 1, rect.Bottom - radius);
        g.DrawLine(pen, rect.Left + radius, rect.Bottom - 1, rect.Right - radius, rect.Bottom - 1);
        g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);

        Bitmap arc = new Bitmap(radius, radius, g);
        Graphics.FromImage(arc).DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);

        g.TranslateTransform(rect.Left, rect.Top);
        g.DrawImage(arc, 0, 0);
        g.ResetTransform();

        g.TranslateTransform(rect.Right, rect.Top);
        g.ScaleTransform(-1, 1);
        g.DrawImage(arc, 0, 0);
        g.ResetTransform();

        g.TranslateTransform(rect.Right, rect.Bottom);
        g.ScaleTransform(-1, -1);
        g.DrawImage(arc, 0, 0);
        g.ResetTransform();

        g.TranslateTransform(rect.Left, rect.Bottom);
        g.ScaleTransform(1, -1);
        g.DrawImage(arc, 0, 0);
        g.ResetTransform();

        arc.Dispose();
    }
...