2013-10-03 7 views
2

Я написал код, который создает округленный прямоугольник GraphicsPath, основанный на пользовательской структуре, BorderRadius (что позволяет мне определять левый верхний правый, правый нижний и нижний правый радиус прямоугольник), а сам начальная Rectangle:Странно рисованный GraphicsPath с Graphics.FillPath

public static GraphicsPath CreateRoundRectanglePath(BorderRadius radius, Rectangle rectangle) 
{ 
    GraphicsPath result = new GraphicsPath(); 

    if (radius.TopLeft > 0) 
    { 
     result.AddArc(rectangle.X, rectangle.Y, radius.TopLeft, radius.TopLeft, 180, 90); 
    } 
    else 
    { 
     result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y), new System.Drawing.Point(rectangle.X, rectangle.Y)); 
    } 
    if (radius.TopRight > 0) 
    { 
     result.AddArc(rectangle.X + rectangle.Width - radius.TopRight, rectangle.Y, radius.TopRight, radius.TopRight, 270, 90); 
    } 
    else 
    { 
     result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y)); 
    } 
    if (radius.BottomRight > 0) 
    { 
     result.AddArc(rectangle.X + rectangle.Width - radius.BottomRight, rectangle.Y + rectangle.Height - radius.BottomRight, radius.BottomRight, radius.BottomRight, 0, 90); 
    } 
    else 
    { 
     result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height)); 
    } 
    if (radius.BottomLeft > 0) 
    { 
     result.AddArc(rectangle.X, rectangle.Y + rectangle.Height - radius.BottomLeft, radius.BottomLeft, radius.BottomLeft, 90, 90); 
    } 
    else 
    { 
     result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height)); 
    } 

    return result; 
} 

Теперь, если я использую это наряду с FillPath и DrawPath, я заметил некоторые странные результаты:

GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), new Rectangle(10, 10, 100, 100)); 
e.Graphics.DrawPath(new Pen(Color.Black, 1), path); 
e.Graphics.FillPath(new SolidBrush(Color.Black), path); 

Я увеличенный в каждый полученного Rectangle (правая сторона), так что вы можете ясно видеть, проблему:

rectangles

То, что я хотел бы знать: Почему все из дуг на нарисованном прямоугольнике равны, и все дуги на заполненный прямоугольник, нечетный?

Лучше все еще, можно ли зафиксировать его, чтобы правильно заполнить прямоугольник?

EDIT: Возможно ли заполнить внутреннюю часть GraphicsPath без использования FillPath?

EDIT: В соответствии с комментариями .... вот пример BorderRadius структуры

public struct BorderRadius 
{ 
    public Int32 TopLeft { get; set; } 
    public Int32 TopRight { get; set; } 
    public Int32 BottomLeft { get; set; } 
    public Int32 BottomRight { get; set; } 

    public BorderRadius(int all) : this() 
    { 
     this.TopLeft = this.TopRight = this.BottomLeft = this.BottomRight = all; 
    } 
} 
+0

Можете ли вы разместить свой BorderRadius(), чтобы мы могли играть с примером? –

+0

@Idle_Mind, см. Обновление – series0ne

ответ

3

я испытал те же проблемы и найти решение. Может быть, слишком поздно для вас @seriesOne, но это может быть полезно для других людей, если у них есть эта проблема. В основном при использовании методов заливки (а также при настройке закругленного прямоугольника в качестве обтравочного контура с помощью Graphics.SetClip) мы должны перемещать на один пиксель правую и нижнюю строки. Поэтому я придумал метод, который принимает параметр для исправления прямоугольника, используя заливку или нет. Вот оно:

private static GraphicsPath CreateRoundedRectangle(Rectangle b, int r, bool fill = false) 
{ 
    var path = new GraphicsPath(); 
    var r2 = (int)r/2; 
    var fix = fill ? 1 : 0; 

    b.Location = new Point(b.X - 1, b.Y - 1); 
    if (!fill) 
     b.Size = new Size(b.Width - 1, b.Height - 1); 

    path.AddArc(b.Left, b.Top, r, r, 180, 90); 
    path.AddLine(b.Left + r2, b.Top, b.Right - r2 - fix, b.Top); 

    path.AddArc(b.Right - r - fix, b.Top, r, r, 270, 90); 
    path.AddLine(b.Right, b.Top + r2, b.Right, b.Bottom - r2); 

    path.AddArc(b.Right - r - fix, b.Bottom - r - fix, r, r, 0, 90); 
    path.AddLine(b.Right - r2, b.Bottom, b.Left + r2, b.Bottom); 

    path.AddArc(b.Left, b.Bottom - r - fix, r, r, 90, 90); 
    path.AddLine(b.Left, b.Bottom - r2, b.Left, b.Top + r2); 

    return path; 
} 

Так вот как вы его используете:

g.DrawPath(new Pen(Color.Red), CreateRoundedRectangle(rect, 24, false)); 
g.FillPath(new SolidBrush(Color.Red), CreateRoundedRectangle(rect, 24, true)); 
+0

Никогда не поздно, хотя у меня есть повернул мою руку к WPF, но все же заинтересовался, увидев, что это работает в Windows Forms :-) – series0ne

1

Я предлагаю явно добавив линию от конца каждой дуги до начала следующего.

Вы также можете попробовать использовать метод Flatten для приближения всех кривых на вашем пути с помощью линий. Это должно устранить любую двусмысленность.

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

Вы можете исследовать точки на своем пути, используя функцию GetPathData: http://msdn.microsoft.com/en-us/library/ms535534%28v=vs.85%29.aspx

кривой Безье (которые GDI + используют приблизительные дуги) представлены на 4 пункта. Первая - конечная точка и может быть любым типом. Вторая и третья - контрольные точки и имеют тип PathPointBezier. Последняя является другой конечной точкой и имеет тип PathPointBezier. Другими словами, когда GDI + видит PathPointBezier, он использует текущую позицию пути и точки 3 Безье для рисования кривой. Кривые Безье могут быть нанизаны друг на друга, но число точек безье должно всегда делиться на 3.

Что вы делаете, это немного странно, поскольку вы рисуете кривые в разных местах без явных линий, чтобы присоединиться к ним. Я предполагаю, что это создает такой шаблон:

PathPointStart - end point of first arc 
PathPointBezier - control point of first arc 
PathPointBezier - control point of first arc 
PathPointBezier - end point of first arc 
PathPointLine - end point of second arc 
PathPointBezier - control point of second arc 
PathPointBezier - control point of second arc 
PathPointBezier - end point of second arc 
PathPointLine - end point of third arc 
PathPointBezier - control point of third arc 
PathPointBezier - control point of third arc 
PathPointBezier - end point of third arc 

Это выглядит разумно. GDI + должен рисовать линию от последней конечной точки каждой кривой до первой конечной точки следующего. DrawPath явно делает это, но я думаю, что FillPath интерпретирует точки по-разному. Некоторые конечные точки рассматриваются как контрольные точки и наоборот.

+0

Спасибо за это, это очень подробное объяснение! Как вы думаете, мне нужно переписать весь мой метод CreateRoundRectanglePath или просто добавить к нему строки? – series0ne

+0

Я думаю, что у него есть разумные шансы работать, если вы добавляете строки, но это всего лишь образованное предположение, что типы точек имеют какое-либо отношение к вашей проблеме. Я не знаю, что произойдет, если вы добавите строки, что именно FillPath делает с точками, или то, что путь действительно выглядит. –

+0

Я попытался добавить строки и использовать кривые безье, а не дуги, а также попытался ответить Idle_Mind ... ничего не работает! :-( – series0ne

0

«Возможно ли заполнить внутреннюю часть GraphicsPath без использования FillPath?»

Да ...но я думаю, что это скорее трюк в гостиной (и он может не работать так, как вы ожидаете, для более сложных форм). Вы можете подрезать графику на пути, а затем просто заполнить весь прямоугольник, охватывающий:

 Rectangle rc = new Rectangle(10, 10, 100, 100); 
     GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), rc); 
     e.Graphics.SetClip(path); 
     e.Graphics.FillRectangle(Brushes.Black, rc); 
     e.Graphics.ResetClip(); 
+0

Отлично, спасибо за этот ответ ... Я дам ему попробовать! – series0ne

+0

К сожалению, это имеет тот же эффект, что и FillPath: прямоугольник нечетный, как на рисунке – gigi

0

Реальная причина поведения объяснена в Pixel behaviour of FillRectangle and DrawRectangle.

Это связано с округлением пикселей по пикселям и тем фактом, что FillRectangle/FillPath с целыми координатами заканчивается рисованием в середине пикселя и округляется (согласно Graphics.PixelOffsetMode).

С другой стороны, DrawRectangle/DrawPath рисуем ручкой 1px, которая отлично округляется на границах пикселей.

В зависимости от вашего использования решение может состоять в том, чтобы раздуть/сдуть прямоугольник для FillRectangle/FillPath на .5px.