Максимальная ограничивающая рамка фигуры из одной аффинной формы в другую

#java #math #image-processing #geometry #interpolation

#java #математика #обработка изображений #геометрия #интерполяция

Вопрос:

Краткие сведения

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

  • java.awt.Rectangle
  • java.awt.geom.AffineTransform
  • Привязка (пользовательский класс)

В настоящее время то, что у меня есть

Вот как определяется класс привязки:

 public class Anchor {
  public double deltaX = 0, deltaY = 0;
  public double scaleX = 1, scaleY = 1;
  public double degrees = 0;
}
  

В моем реальном приложении у меня есть набор якорей, которые привязаны ко времени, и по мере выполнения анимации фигура интерполируется между двумя соседними «якорями». Но для этого вопроса нам нужно беспокоиться только о 2 якорях.

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

 public static Rectangle getBounds(Anchor leftAnchor, Anchor rightAnchor, int shapeWidth, int shapeHeight) {
  Rectangle baseShape = new Rectangle(0, 0, shapeWidth, shapeHeight); // the shape we draw in the animation
  Rectangle globalBounds = null, localBounds; // global bounds is the bounds of the whole animation
  for(double time = 0;time <= 1;time  = 0.001) { // interpolate from one anchor to the next (1000 steps)
    // Create the transformation and find the finds of the resultant shape
    AffineTransform transformation = getInterpolatedTransformation(leftAnchor, rightAnchor, shapeWidth, shapeHeight, time);
    localBounds = transformation.createTransformedShape(baseShape).getBounds();
    if(globalBounds == null) // if it's the first step, create the initial bounds
      globalBounds = localBounds;
    else // otherwise continue adding bounds
      globalBounds.add(localBounds);
  }
  return globalBounds; // return the global bounds
}

public static AffineTransform getInterpolatedTransformation(Anchor left, Anchor right, int width, int height, double time) {
  // get the interpolated values from the two anchors
  double deltaX = linearInterpolation(left.deltaX, right.deltaX, time);
  double deltaY = linearInterpolation(left.deltaX, right.deltaX, time);
  double scaleX = linearInterpolation(left.scaleX, right.scaleX, time);
  double scaleY = linearInterpolation(left.scaleY, right.scaleY, time);
  double degrees = linearInterpolation(left.degrees, right.degrees, time);

  // Create the AffineTransformation based on the two interpolated acnhors
  AffineTransform transform = new AffineTransform();
  transform.translate(deltaX, deltaY);
  transform.scale(scaleX, scaleY);
  transform.rotate(Math.toRadians(degrees),
          scaleX*width/2.0 deltaX,
          scaleY*height/2.0 deltaY);

  return transform; // return it
}
  

Большой вопрос

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

Примечания

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

PS: Перед вами, программистами на C, я приношу извинения за мою многословность в моем коде…

Комментарии:

1. Я не совсем уверен, какова цель. Вы хотите найти максимальную ограничивающую рамку, которую может охватывать фигура? Или вам нужно найти точную ограничивающую рамку набора преобразований? Насколько точной она должна быть (допустимое превышение? / допустимое превышение?).

2. Да — я хочу найти максимальную ограничивающую рамку, которую может охватывать фигура при интерполяции между двумя аффинными преобразованиями. Точные границы предпочтительнее, но превышение не является слишком большой проблемой.

Ответ №1:

Из вашего итеративного решения вы могли бы получить одноэтапное решение (хотя оно может переоценить ограничивающую рамку, поскольку это позволит рассчитать максимально возможную площадь, если были выбраны самые экстремальные значения для поворота):

 public static Rectangle getBounds(Anchor leftAnchor, Anchor rightAnchor, int shapeWidth, int shapeHeight) {
   double size = Math.sqrt((shapeWidth * shapeWidth)   (shapeHeight * shapeHeight));
   // find the maximum scale
   double maxScale = Math.max(leftAnchor.scaleX, leftAnchor.scaleY);
   maxScale = Math.max(rightAnchor.scaleX);    
   maxScale = Math.max(rightAnchor.scaleY);    
   int maxSize = (int) Math.ceil(maxScale * size);
   return new Rectangle(0, 0, maxSize, maxSize);
}
  

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

Комментарии:

1. Это хорошая идея. Кажется, я понимаю, что вы делаете (но в вашем коде есть некоторые ошибки). Возможно, это тот путь, по которому я должен идти.

Ответ №2:

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

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

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

Строгий способ заключается в аналитическом нахождении экстремумов кривой.

Взглянув на преобразованную абсциссу, мы имеем X = X0 t.DX (Sx t.DSx) cos(A t.Da) - (Sy t.DSy) sin(A t.Da) .

Выведите один раз и найдите нули производной : 0 = DX DSx cos(A t.Da) - Da.(Sx t.DSx) sin(A t.Da) - DSy sin(A t.Da) - Da.(Sy t.DSy) cos(A t.Da) .

К сожалению, это трансцендентное уравнение, поскольку оно имеет члены вида t.sin(t) и аналогичные, поэтому аналитическое решение невозможно, и вам придется прибегнуть к численным подходам.

Моя рекомендация для компромисса между вычислительной стоимостью и точностью: вычислите угловые положения для разумного количества кадров и сохраните экстремальные значения Xmin, Xmax, Ymin, Ymax . Если вы наблюдаете изменение поведения на одной из границ, скажем Xmin , уменьшение, а затем увеличение, вы можете уточнить поиск, используя меньший шаг (пока не дойдете до шага одного кадра); в качестве альтернативы, используйте три значения вокруг минимального и выполните параболическую интерполяцию.

Обновить:

Случай с постоянным масштабом можно рассматривать аналитически. (Либо масштаб действительно постоянный, либо вы остаетесь на безопасной стороне, сохраняя максимальное значение масштаба).

Например, уравнение для абсциссы имеет вид, подобный A B.t C.cos(t) D.sin(t) . Вывод, 0 = B - C.sin(t) D.cos(t) . Это хорошо известное линейное тригонометрическое уравнение. Вы можете найти его корни во временном интервале. Требуется тщательное обсуждение.

Комментарии:

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

Ответ №3:

Я отправил это переполнение стека другу за советом. У него не было времени посмотреть на это тогда и там, но он прислал мне это: проблема X amp; Y. Возможно, удастся решить этот математический вопрос, но я нашел гораздо лучший способ решения проблемы.

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