Укоротить строку на количество пикселей

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

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

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

http://i45.tinypic.com/140b5w5.gif

Как рассчитать точку, отмеченную красным кругом, то есть пересечение между кругом и линией? Это даст мне новую конечную точку линии, сократив ее на 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);
  }
}

3 ответа

Решение

Найдите вектор направления, т.е. пусть векторы положения (используя поплавки) B = (x2, y2) и A = (x1, y1), затем AB = B - A. Нормализуйте этот вектор путем деления на его длину ( Math.Sqrt). (хх + у у)). Затем умножьте вектор направления 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);

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

Я не уверен, почему вы даже должны были представить круг. Для линии, простирающейся от (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 (72 + 172 или 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.1925019472 + 7.7532190152) или 8.384776311, разница в 10 с точностью до одной части на тысячу миллионов, и это только из-за ошибок округления в моем калькуляторе.

http://i50.tinypic.com/nz165z.png

Вы можете использовать похожие треугольники. Для основного треугольника, 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
Другие вопросы по тегам