Сократить строку на несколько пикселей

Я рисую пользовательскую диаграмму бизнес-объектов, используя .NET GDI+. Помимо прочего, диаграмма состоит из нескольких линий, соединяющих объекты.

В конкретном сценарии мне нужно сократить линию на определенное количество пикселей, скажем, на 10 пикселей, то есть найти точку на линии, которая лежит на 10 пикселей раньше конечной точки линии.

Представьте круг с радиусом r = 10 пикселей и линию с начальной точкой (x1, y1) и конечной точкой (x2, y2). Круг центрируется в конечной точке линии, как показано на следующем рисунке.

Illustration

Как рассчитать точку, отмеченную красным кругом, то есть пересечение между кругом и линией? Это даст мне новую конечную точку линии, сократив ее на 10 пикселей.


Решение

Спасибо за ваши ответы, из которых мне удалось собрать следующую процедуру. Я назвал его "LengthenLine", так как считаю более естественным пропускать отрицательное число пикселей, если я хочу сократить линию.

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

public void LengthenLine(PointF startPoint, ref PointF endPoint, float pixelCount)
{
  if (startPoint.Equals(endPoint))
    return; // not a line

  double dx = endPoint.X - startPoint.X;
  double dy = endPoint.Y - startPoint.Y;
  if (dx == 0)
  {
    // vertical line:
    if (endPoint.Y < startPoint.Y)
      endPoint.Y -= pixelCount;
    else
      endPoint.Y += pixelCount;
  }
  else if (dy == 0)
  {
    // horizontal line:
    if (endPoint.X < startPoint.X)
      endPoint.X -= pixelCount;
    else
      endPoint.X += pixelCount;
  }
  else
  {
    // non-horizontal, non-vertical line:
    double length = Math.Sqrt(dx * dx + dy * dy);
    double scale = (length + pixelCount) / length;
    dx *= scale;
    dy *= scale;
    endPoint.X = startPoint.X + Convert.ToSingle(dx);
    endPoint.Y = startPoint.Y + Convert.ToSingle(dy);
  }
}

Ответ 1

Найдите вектор направления, т.е. пусть векторы положения (с использованием поплавков) B = (x2, y2) и A = (x1, y1), тогда AB = B - A. Нормализовать этот вектор, разделив его по длине ( Math.Sqrt(xx + yy)). Затем умножьте вектор направления AB на исходную длину минус радиус окружности и добавьте обратно в начальную позицию линий:

double dx = x2 - x1;
double dy = y2 - y1;
double length = Math.Sqrt(dx * dx + dy * dy);
if (length > 0)
{
    dx /= length;
    dy /= length;
}
dx *= length - radius;
dy *= length - radius;
int x3 = (int)(x1 + dx);
int y3 = (int)(y1 + dy);

Изменить: Исправлен код, aaand исправлено начальное объяснение (считалось, что вы хотите, чтобы линия выходила из центра круга в ее периметр: P)

Ответ 2

Вы можете использовать похожие треугольники. Для основного треугольника d - гипотенузы, а расширение r - вертикальная линия, которая соответствует прямому углу. Внутри круга у вас будет меньший треугольник с гипотенузами длиной r.

r/d = (x2-a0)/(x2-x1) = (y2-b0)/(y2-y1)

a0 = x2 + (x2-x1)r/d

b0 = y2 + (y2-y1)r/d

Ответ 3

Я не уверен, почему вам даже пришлось вводить круг. Для строки, растянувшейся от (x2,y2) до (x1,y1), вы можете вычислить любую точку на этой строке как:

(x2+p*(x1-x2),y2+p*(y1-y2))

где p - это процент по линии, которую вы хотите отправить.

Чтобы вычислить процент, вам просто нужно:

p = r/L

Итак, в вашем случае (x3,y3) можно рассчитать как:

(x2+(10/L)*(x1-x2),y2+(10/L)*(y1-y2))

Например, если у вас есть две точки (x2=1,y2=5) и (x1=-6,y1=22), они имеют длину sqrt (7 2 + 17 2 или 18.38477631 и 10 делится на 0,543928293. Введя все эти цифры в уравнение выше:

  (x2 + (10/l)      * (x1-x2) , y2 + (10/l)      * (y1-y2))
= (1  + 0.543928293 * (-6- 1) , 5  + 0.543928293 * (22- 5))
= (1  + 0.543928293 * -7      , 5  + 0.543928293 * 17     )
= (x3=-2.807498053,y3=14.24678098)

Расстояние между (x3,y3) и (x1,y1) составляет sqrt (3.192501947 2 + 7.753219015 2) или 8.384776311, разница 10 с точностью до одной части в тысячу миллион, и это только из-за ошибок округления на моем калькуляторе.