#c# #winforms #drawing
#c# #winforms #рисование
Вопрос:
Я пытаюсь нарисовать пунктирные линии, используя этот DrawLine
метод. Однако это добавляет небольшое смещение к концам, что делает линии уродливыми. Как можно предотвратить добавление смещения к концам?
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (70, 50), new Point (70, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
Вывод
Предполагаемый результат
Комментарии:
1. Вот увеличенный результат рисования . Что не так с чертежом и чего вы ожидаете? (Пунктирная линия — это исходный пунктирный рисунок, а красная линия показывает поле, которое вы использовали для рисования.
2. @RezaAghaei Почему существует смещение? Технически линии должны совпадать, поскольку конечная точка первой строки и начальная точка второй строки одинаковы.
3. Вы можете использовать связанное изображение и показать неожиданную часть на изображении. Также покажите, каков ожидаемый результат. Я не мог понять, какое смещение вы упомянули. Это пунктирная линия шириной 2, как она может быть другой.
4. Это не столько смещение, сколько остаток, когда точки не соответствуют точности пикселей в заданной длине (длинах). Вы можете успешно манипулировать линиями, добавляя смещение или используя пользовательскую ширину точки или комбинацию..
5. Я думаю, что я, наконец, справился с этим, пожалуйста, попробуйте улучшенную версию!
Ответ №1:
Цель проста и правдоподобна:
- Мы хотим, чтобы наши строки начинались с точки.
И правила игры также довольно просты:
- И точки, и пробелы по умолчанию являются квадратами.
- Все линии рисуются с
PenAlignment.Center
помощью .
К сожалению, последствия комбинации довольно сложны, так что потерпите меня…
Давайте сначала посмотрим на 1-е правило; давайте проигнорируем другое DashStyles
и останемся DashStyle.Dot
. По умолчанию каждая точка и каждый пробел имеют Pen.Width
пиксели для обеих сторон. Это приводит прямо к первым проблемам:
- Если ширина наших линий не может быть разделена на, у
Pen.Width
нас проблемы. - Чтобы начинаться и заканчиваться точкой, мы хотим иметь
n
точки иn-1
пробелы.
Это еще не все, но давайте рассмотрим 2-е правило; чтобы проиллюстрировать это, я нарисовал это увеличенное в 10 раз изображение:
Это код, который создал цветные части:
g.FillRectangle(Brushes.Yellow, 15, 15, 10, 10);
g.DrawRectangle(Pens.Orange, 10, 10, 10, 10);
g.DrawLine(Pens.OrangeRed, 10, 5, 40, 5);
using (Pen pen = new Pen(Color.Red, 2f) { DashStyle = DashStyle.Dot })
g.DrawLine(pen, 10, 30, 48, 30);
using (Pen pen = new Pen(Color.Crimson, 2f))
g.DrawLine(pen, 10, 40, 48, 40);
using (Pen pen = new Pen(Color.DarkMagenta, 3f))
g.DrawLine(pen, 10, 50, 48, 50);
Посмотрите внимательно, чтобы увидеть, как рисуются линии!
(В сторону: вы также можете посмотреть разницу DrawRectangle
и FillRectangle
!)
- Горизонтальные линии начинаются и заканчиваются в правильных координатах, но они расширяются либо вниз (если их перо.Ширина = 1) или выше и ниже y-координаты.
- Конечно, вертикальные линии будут делать то же самое.
Проблема в том, что они просто не подходят друг к другу по (внешним) краям.
Итак, что мы можем сделать? Я не думаю, что a DashOffset
может помочь. Но есть еще один вариант настройки a Pen
: мы можем настроить его DashPattern
на использование пользовательских значений.
Нам нужны два значения floats
, содержащие масштабирование для точек и пробелов. По умолчанию оба значения равны 1f
. Я решил сохранить квадратные точки и изменить только пробелы. Вот функция, которая решает проблему с помощью
- увеличение ширины линии на половину ширины пера с обеих сторон, чтобы внешние края встретились
- расширение пробелов по мере необходимости, чтобы соответствовать длине линии
Вот функция рисования линии; она принимает Graphics
объект, a Pen
, два конца Points
и a byte
, которые сообщают нам, предназначена ли линия отдельно или будет соединяться с другими линиями, как в наших примерах.
Для создания хорошего соединения, которое будет хорошо работать с полупрозрачными кистями, нам нужна возможность пропускать точку a в начале или в конце или даже в обоих, например, когда мы хотим вставить диагональную линию, как в моем тесте ниже.
Значения пропуска — 0
пропустить none, 1 or 2
пропустить 1-ю или последнюю точку и 3
пропустить оба. Конечно, вы можете enumeration
вместо этого использовать.
void DrawDottedLine(Graphics g, Pen pen_, Point pa_, Point pb, byte skipDots)
{
float pw = pen_.Width;
float pw2 = pen_.Width / 2f;
Pen pen = (Pen)pen_.Clone();
// find out directions:
int sigX = Math.Sign(pb_.X - pa_.X);
int sigY = Math.Sign(pb_.Y - pa_.Y);
// move the end points out a bit:
PointF pa = new PointF(pa_.X - pw2 * sigX, pa_.Y - pw2 * sigY);
PointF pb = new PointF(pb_.X pw2 * sigX, pb_.Y pw2 * sigY);
// find line new length:
float lw = (float)(Math.Abs(pb.X - pa.X));
float lh = (float)(Math.Abs(pb.Y - pa.Y));
float ll = (float)(Math.Sqrt(lw * lw lh * lh));
// dot length:
float dl = ll / pw;
// dot gap count: round to nearest odd int:
int dc = (int)(2 * Math.Round((dl 1) / 2) - 1);
// gap count:
int gc = dc / 2 ;
// dot count:
dc = gc 1;
// gap scaling
float gs = (ll - dc * pw) / (pw * gc);
// our custom dashpattern
pen.DashPattern = new float[] { 1, gs };
// maybe skip 1st and/or last dots:
if (skipDots % 2 == 1) pa = new PointF(pa_.X pw * sigX, pa_.Y pw * sigY);
if (skipDots > 1) pb = new PointF(pb_.X - pw * sigX, pb_.Y - pw * sigY);
// finally we can draw the line:
g.DrawLine(pen, pa, pb);
// dispose of pen clone
pen.Dispose();
}
После некоторых очевидных приготовлений я немного перемещаю точки, а затем вычисляю количество точек и пробелов для вертикальных или горизонтальных линий. Затем я вычисляю измененный масштаб разрыва.
Вот результат, увеличенный в 4 раза, рисования четырех линий в форме прямоугольника с различной шириной пера, начиная с 1/3 - 10/3
:
Это тестовый стенд, который я использовал; обратите внимание на использование полупрозрачного черного цвета, чтобы проиллюстрировать, как правильно прорисовываются углы, т. Е. Не перекрываются:
Pen Dot = new Pen(Color.Black, 1f);
Point pa = new Point(10, 50);
Point pb = new Point(70, 50);
Point pc = new Point(70, 100);
Point pd = new Point(10, 100);
for (int i = 1; i < 10; i )
{
Dot = new Pen(Color.FromArgb(128, Color.Black), i / 3f){ DashStyle = DashStyle.Dot };
g.TranslateTransform(10, 10);
DrawDottedLine(g, Dot, pa, pb, 2);
DrawDottedLine(g, Dot, pb, pc, 2);
DrawDottedLine(g, Dot, pc, pd, 2);
DrawDottedLine(g, Dot, pd, pa, 2);
DrawDottedLine(g, Dot, pd, pb, 3);
}
Я действительно хотел бы, чтобы можно было избежать проблем с подключением, просто используя DrawLines
, но это не сработало, и после выяснения этого решения я не очень удивлен, что этого не произошло..
Ответ №2:
Предоставленные вами координаты указывают на верхние левые позиции результирующей линии. Поэтому, когда вы рисуете линию шириной в два пикселя, вы должны рассчитать начальную и конечную точки, чтобы включить ширину этой линии.
В этом случае вы хотели бы немного сместить вертикальную линию влево и вверх ( borderwith
если быть точным).
Итак, путем добавления (или вычитания) смещения, равного ширине линии, результат таков:
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (69, 49), new Point (69, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
Комментарии:
1. Я не мог понять, что вы говорите. Может помочь немного кода.
2. 67,49 и 67,100 выглядят идеально
3. @kakkarot, в этом случае вам нужно будет учесть тот факт, что точки имеют пробелы, поэтому смещения могут быть больше или меньше в зависимости от точки, где точка будет начинаться или заканчиваться.
4. На самом деле все нарисованные линии нарисованы пером. Выравнивание по центру, что усложняет дело. Я думаю, что я, наконец, прибил это..