Вычисление координат ограничивающей рамки по повернутому прямоугольнику

StackOverflow https://stackoverflow.com/questions/622140

Вопрос

У меня есть координаты верхней левой точки прямоугольника, а также его ширина, высота и поворот от 0 до 180 и от -0 до -180.

Я пытаюсь получить ограничивающие координаты фактического прямоугольника вокруг прямоугольника.

Каков простой способ вычисления координат ограничивающей рамки

  • Минимальный y, максимальный y, минимальный x, максимальный x?

Точка A не всегда находится на минимальной границе y, она может быть где угодно.

При необходимости я могу использовать matrix the transform toolkit в as3.

Это было полезно?

Решение

  • Преобразуйте координаты всех четырех углов
  • Найдите наименьший из всех четырех крестиков как min_x
  • Найдите самый большой из всех четырех крестиков и назовите его max_x
  • То же самое с буквами "у"
  • Ваша ограничивающая рамка - это (min_x,min_y), (min_x,max_y), (max_x,max_y), (max_x,min_y)

AFAIK, нет никакой королевской дороги, которая доставит вас туда намного быстрее.

Если вам интересно, как преобразовать координаты, попробуйте:

x2 = x0+(x-x0)*cos(theta)+(y-y0)*sin(theta)
y2 = y0-(x-x0)*sin(theta)+(y-y0)*cos(theta)

где (x0, y0) - это центр, вокруг которого вы вращаетесь.Возможно, вам придется повозиться с этим в зависимости от ваших тригонометрических функций (ожидают ли они градусы или радианы), смысла / знака вашей системы координат икак вы указываете углы и т.д.

Другие советы

Я понимаю, что вы просите ActionScript, но, на всякий случай, если кто-нибудь придет сюда в поисках ответа на iOS или OS-X, это:

+ (CGRect) boundingRectAfterRotatingRect: (CGRect) rect toAngle: (float) radians
{
    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect result = CGRectApplyAffineTransform (rect, xfrm);

    return result;
}

Если ваша операционная система предлагает сделать всю тяжелую работу за вас, позвольте этому случиться!:)

Быстрый:

func boundingRectAfterRotatingRect(rect: CGRect, toAngle radians: CGFloat) -> CGRect {
    let xfrm = CGAffineTransformMakeRotation(radians)
    return CGRectApplyAffineTransform (rect, xfrm)
}

Метод, описанный MarkusQ, работает отлично, но имейте в виду, что вам не нужно преобразовывать остальные три угла, если у вас уже есть точка A.

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

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

ct = cos( theta );
st = sin( theta );

hct = h * ct;
wct = w * ct;
hst = h * st;
wst = w * st;

if ( theta > 0 )
{
    if ( theta < 90 )
    {
        // 0 < theta < 90
        y_min = A_y;
        y_max = A_y + hct + wst;
        x_min = A_x - hst;
        x_max = A_x + wct;
    }
    else
    {
        // 90 <= theta <= 180
        y_min = A_y + hct;
        y_max = A_y + wst;
        x_min = A_x - hst + wct;
        x_max = A_x;
    }
}
else
{
    if ( theta > -90 )
    {
        // -90 < theta <= 0
        y_min = A_y + wst;
        y_max = A_y + hct;
        x_min = A_x;
        x_max = A_x + wct - hst;
    }
    else
    {
        // -180 <= theta <= -90
        y_min = A_y + wst + hct;
        y_max = A_y;
        x_min = A_x + wct;
        x_max = A_x - hst;
    }
}

Этот подход предполагает, что у вас есть то, что вы говорите, что у вас есть, т. е.точка A и значение для theta, которое лежит в диапазоне [-180, 180].Я также предположил, что тета увеличивается по часовой стрелке, поскольку именно это, по-видимому, указывает прямоугольник, повернутый на 30 градусов на вашей диаграмме, который вы используете, я не был уверен, что пыталась обозначить часть справа.Если это неправильный способ обхода, то просто поменяйте местами симметричные предложения, а также знак st терминов.

    fitRect: function( rw,rh,radians ){
            var x1 = -rw/2,
                x2 = rw/2,
                x3 = rw/2,
                x4 = -rw/2,
                y1 = rh/2,
                y2 = rh/2,
                y3 = -rh/2,
                y4 = -rh/2;

            var x11 = x1 * Math.cos(radians) + y1 * Math.sin(radians),
                y11 = -x1 * Math.sin(radians) + y1 * Math.cos(radians),
                x21 = x2 * Math.cos(radians) + y2 * Math.sin(radians),
                y21 = -x2 * Math.sin(radians) + y2 * Math.cos(radians), 
                x31 = x3 * Math.cos(radians) + y3 * Math.sin(radians),
                y31 = -x3 * Math.sin(radians) + y3 * Math.cos(radians),
                x41 = x4 * Math.cos(radians) + y4 * Math.sin(radians),
                y41 = -x4 * Math.sin(radians) + y4 * Math.cos(radians);

            var x_min = Math.min(x11,x21,x31,x41),
                x_max = Math.max(x11,x21,x31,x41);

            var y_min = Math.min(y11,y21,y31,y41);
                y_max = Math.max(y11,y21,y31,y41);

            return [x_max-x_min,y_max-y_min];
        }

если вы используете GDI + , вы можете создать новый графический путь -> Добавить к нему любые точки или фигуры -> Применить преобразование поворота -> использовать GraphicsPath.getBounds() и это вернет прямоугольник, который ограничивает вашу повернутую фигуру.

(редактировать) VB.Net Пример

Public Shared Sub RotateImage(ByRef img As Bitmap, degrees As Integer)
' http://stackoverflow.com/questions/622140/calculate-bounding-box-coordinates-from-a-rotated-rectangle-picture-inside#680877
'
Using gp As New GraphicsPath
  gp.AddRectangle(New Rectangle(0, 0, img.Width, img.Height))

  Dim translateMatrix As New Matrix
  translateMatrix.RotateAt(degrees, New PointF(img.Width \ 2, img.Height \ 2))
  gp.Transform(translateMatrix)

  Dim gpb = gp.GetBounds

  Dim newwidth = CInt(gpb.Width)
  Dim newheight = CInt(gpb.Height)

  ' http://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations
  '
  Dim rotatedBmp As New Bitmap(newwidth, newheight)

  rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

  Using g As Graphics = Graphics.FromImage(rotatedBmp)
    g.Clear(Color.White)
    translateMatrix = New Matrix
    translateMatrix.Translate(newwidth \ 2, newheight \ 2)
    translateMatrix.Rotate(degrees)
    translateMatrix.Translate(-img.Width \ 2, -img.Height \ 2)
    g.Transform = translateMatrix
    g.DrawImage(img, New PointF(0, 0))
  End Using
  img.Dispose()
  img = rotatedBmp
End Using

Конечная Подлодка

Хотя Code Guru описал метод getBounds(), я заметил, что вопрос помечен как 3, flex , поэтому вот фрагмент as3, который иллюстрирует идею.

var box:Shape = new Shape();
box.graphics.beginFill(0,.5);
box.graphics.drawRect(0,0,100,50);
box.graphics.endFill();
box.rotation = 20;
box.x = box.y = 100;
addChild(box);

var bounds:Rectangle = box.getBounds(this);

var boundingBox:Shape = new Shape();
boundingBox.graphics.lineStyle(1);
boundingBox.graphics.drawRect(bounds.x,bounds.y,bounds.width,bounds.height);
addChild(boundingBox);

Я заметил, что есть два метода, которые, кажется, делают одно и то же:getBounds() и getRect()

/**
     * Applies the given transformation matrix to the rectangle and returns
     * a new bounding box to the transformed rectangle.
     */
    public static function getBoundsAfterTransformation(bounds:Rectangle, m:Matrix):Rectangle {
        if (m == null) return bounds;

        var topLeft:Point = m.transformPoint(bounds.topLeft);
        var topRight:Point = m.transformPoint(new Point(bounds.right, bounds.top));
        var bottomRight:Point = m.transformPoint(bounds.bottomRight);
        var bottomLeft:Point = m.transformPoint(new Point(bounds.left, bounds.bottom));

        var left:Number = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var top:Number = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        var right:Number = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var bottom:Number = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        return new Rectangle(left, top, right - left, bottom - top);
    }

Примените матрицу поворота к вашим угловым точкам.Затем используйте минимум / максимум соответственно полученных координат x, y для определения вашей новой ограничивающей рамки.

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

Подписи являются:

общедоступный статический элемент с плавающей точкой getAngleFromPoint (центральная точка конечной точки, точка касания конечной точки)

общедоступный статический float getTwoFingerDistance (float firstTouchX, float firstTouchY, float secondTouchX, float secondTouchY)

Точка getPointFromAngle (конечный двойной угол, конечный двойной радиус)

Это решение предполагает, что плотность пикселей распределена равномерно.Перед вращением объекта выполните следующие действия:

  1. Используйте getAngleFromPoint для вычисления угла от центра до верхнего правого угла (допустим, это возвращает 20 градусов), что означает, что верхний левый угол равен -20 градусам или 340 градусам.

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

  3. Теперь предположим, что мы поворачиваем объект по часовой стрелке на 30 градусов.Теперь мы знаем, что верхний правый угол должен быть под углом 50 градусов, а верхний левый угол - под углом 10 градусов.

  4. Теперь вы должны иметь возможность использовать функцию getPointFromAngle в верхнем левом и правом верхнем углу.используя радиус, возвращенный с шага 2.Позиция X, умноженная на 2 в правом верхнем углу, должна дать вам новую ширину, а позиция Y, умноженная на 2 в левом верхнем углу, должна дать новую высоту.

Эти вышеуказанные 4 шага должны быть приведены к условиям, основанным на том, как далеко вы повернули свой объект, в противном случае вы можете вернуть высоту как ширину, а ширину как высоту.

Имейте в виду, что угловые функции выражаются в коэффициентах 0-1 вместо 0-360 (просто умножьте или разделите на 360, где это уместно):

// Получает угол из двух точек, выраженный в виде коэффициента 0 -1 (0 равно 0/360, 0.25 равно 90 градусам и т.д.)

public float getAngleFromPoint(final Point centerPoint, final Point touchPoint) {

    float returnVal = 0;

    //+0 - 0.5
    if(touchPoint.x > centerPoint.x) {

        returnVal = (float) (Math.atan2((touchPoint.x - centerPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI);

    }
    //+0.5
    else if(touchPoint.x < centerPoint.x) {

        returnVal = (float) (1 - (Math.atan2((centerPoint.x - touchPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI));

    }//End if(touchPoint.x > centerPoint.x)

    return returnVal;

}

//Измеряет расстояние по диагонали между двумя точками

public float getTwoFingerDistance(final float firstTouchX, final float firstTouchY, final float secondTouchX, final float secondTouchY) {

    float pinchDistanceX = 0;
    float pinchDistanceY = 0;

    if(firstTouchX > secondTouchX) {

        pinchDistanceX = Math.abs(secondTouchX - firstTouchX);

    }
    else if(firstTouchX < secondTouchX) {

        pinchDistanceX = Math.abs(firstTouchX - secondTouchX);

    }//End if(firstTouchX > secondTouchX)

    if(firstTouchY > secondTouchY) {

        pinchDistanceY = Math.abs(secondTouchY - firstTouchY);

    }
    else if(firstTouchY < secondTouchY) {

        pinchDistanceY = Math.abs(firstTouchY - secondTouchY);

    }//End if(firstTouchY > secondTouchY)

    if(pinchDistanceX == 0 && pinchDistanceY == 0) {

        return 0;

    }
    else {

        pinchDistanceX = (pinchDistanceX * pinchDistanceX);
        pinchDistanceY = (pinchDistanceY * pinchDistanceY);
        return (float) Math.abs(Math.sqrt(pinchDistanceX + pinchDistanceY));

    }//End if(pinchDistanceX == 0 && pinchDistanceY == 0)

}

// Получить координаты XY под углом, заданным радиусом (угол выражается в коэффициенте 0-1, равном 0/360 градусам, а 0,75 - 270 и т.д.)

public Point getPointFromAngle(final double angle, final double radius) {

    final Point coords = new Point();
    coords.x = (int) (radius * Math.sin((angle) * 2 * Math.PI));
    coords.y = (int) -(radius * Math.cos((angle) * 2 * Math.PI));

    return coords;

}

Эти фрагменты кода взяты из моих библиотек с открытым исходным кодом: https://bitbucket.org/warwick/hgdialrepo и https://bitbucket.org/warwick/hacergestov2.Один из них - это библиотека жестов для Android, а другой - управление набором номера для Android.Существует также реализация управления набором номера в OpenGLES 2.0 по адресу: https://bitbucket.org/warwick/hggldial

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

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

Я использовал Region для Первого поворота прямоугольника, а затем использовал эту повернутую область для обнаружения этого прямоугольника

        r = new Rectangle(new Point(100, 200), new Size(200, 200));         
        Color BorderColor = Color.WhiteSmoke;
        Color FillColor = Color.FromArgb(66, 85, 67);
        int angle = 13;
        Point pt = new Point(r.X, r.Y);
        PointF rectPt = new PointF(r.Left + (r.Width / 2),
                               r.Top + (r.Height / 2));
       //declare myRegion globally 
        myRegion = new Region(r);

        // Create a transform matrix and set it to have a 13 degree

        // rotation.
        Matrix transformMatrix = new Matrix();
        transformMatrix.RotateAt(angle, pt);

        // Apply the transform to the region.
        myRegion.Transform(transformMatrix);
        g.FillRegion(Brushes.Green, myRegion);
        g.ResetTransform();

теперь перейдем к обнаружению этого прямоугольника

        private void panel_MouseMove(object sender, MouseEventArgs e)
    {


        Point point = e.Location;
        if (myRegion.IsVisible(point, _graphics))
        {
            // The point is in the region. Use an opaque brush.
            this.Cursor = Cursors.Hand;
        }
        else {
            this.Cursor = Cursors.Cross;
        }

    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top