如何在外旋矩形的边界内夹住一个矩形

时间:2014-11-03 22:20:12

标签: javascript math

我正在使用WebGL(使用2d画布后备照片编辑器)。

我决定将旋转直接合并到裁剪工具中,其方式类似于iOS 8照片裁剪器。即,在旋转照片时,照片的大小和位置会动态变化,以便裁剪区域始终包含在照片中。

然而,我正在努力解决一些数学问题。

我有两个矩形,照片和裁剪区域。

两者都被定义为:

var rect = {
     x : x,
     y : y,
     w : width,
     h : height
}

定义照片本身的矩形也有弧度的rotation属性,当然也描述了照片的角度(以弧度表示)。

应该注意的是,照片矩形的xy坐标(裁剪矩形)实际上定义了中心照片,而不是左上角。虽然这看起来很奇怪,但它使我们在其他地方的计算更容易,并且不应该影响这个问题。为了完整起见,我提到它。

照片的变换原点始终设置为croparea的 center

当照片的角度为0且裁剪区域与照片的大小相同时,矩形对齐如下:

enter image description here

当我旋转照片时,比例因子应用于照片矩形以确保croparea保持在照片边界内:

enter image description here

这是通过计算croparea的边界框来实现的,并从中计算出所需的比例因子以应用于photo矩形:

TC.UI.PhotoCropper.prototype._GetBoundingBox = function(w,h,rads){
    var c = Math.abs(Math.cos(rads));
    var s = Math.abs(Math.sin(rads));
    return({  w: h * s + w * c,  h: h * c + w * s });
}

var bbox = this._GetBoundingBox(crop.w, crop.h, photo.rotation);
var scale = Math.max(1, Math.max(bbox.w/photo.w, bbox.h/photo.h));

这当前与预期完全一致,并且无论在croparea(以及产生的旋转原点)恰好是哪里,照片矩形的宽度都会正确缩放。

此外,由于这是一个照片裁剪工具,当然可以修改croparea的位置。请注意,在修改croparea位置时,我们正在移动photo矩形本身。 (croparea始终保持居中,因为我们认为这对于基于触摸的设备来说更加自然,并且当与鼠标一起使用时仍然可以接受......这在iOS和Windows 8中都是相同的。)

因此,我们目前将x矩形的yphoto坐标限制在croparea的边缘,如下所示:

var maxX = crop.x + (crop.w/2) - (photo.w/2);
var maxY = crop.y + (crop.h/2) - (photo.w/2);
var minX = crop.x - (crop.w/2) + (photo.w/2);
var minY = crop.y - (crop.h/2) + (photo.h/2);

photo.x = Math.min(minX, Math.max(maxX, photo.x));
photo.y = Math.min(minY, Math.max(maxY, photo.y));

这就是我遇到麻烦的地方。

当croparea不在照片中居中时,我们最终将croparea移动到照片的边界之外,如下所示:

enter image description here

请记住,旋转的原点始终是croparea的中心

在上面的照片中,您可以看到正确计算了所需的宽度,并且照片可以正确缩放以包含croparea。

然而,由于我们的夹紧功能仅检查croparea的边缘,我们最终得到了照片边界外的croparea。

我们想修改我们的夹紧功能,以便考虑photo矩形的旋转。因此,应正确钳制xy坐标(在上面的屏幕截图中),以确保最终结果如下所示:

enter image description here

不幸的是,我不知道从哪里开始数学,可以真正使用一些帮助。

我们目前使用相同的钳位功能,同时旋转和移动photo矩形,并希望在可能的情况下继续这样做。

3 个答案:

答案 0 :(得分:4)

正如DARK_DUCK所说,有一些方法(非平凡的方式)来编写解决问题的clamp函数。但是,当您说“限制”时,我会自动认为您的解决方案必须限制用户无法达到某些无效状态,这对IMHO来说不是一个好的解决方案。也许用户将旋转图像通过一些无效状态只是为了达到有效状态。

我提出了另一种解决方案:

  1. 存储每个更改(轮换,翻译,比例)的最后一个有效状态
  2. 当用户完成更改时,如果当前状态有效或您恢复上一个有效状态,则不执行任何操作。
  3. 状态将由图像原点(或位置),旋转和比例组成。
    您可以设置从无效状态到上一个有效状态的转换动画。 处于无效状态时,您可以通过将裁剪区域边框设置为红色来通知用户: example

    有效状态是指裁剪区域的所有4个角都在图像矩形内。 以下功能将帮助您测试这种情况:

    //rotate a point around a origin
    function RotatePoint(point, angle, origin)
    {
        var a = angle * Math.PI / 180.0;
        var sina = Math.sin(a);
        var cosa = Math.cos(a);
    
        return
        {
            x : origin.x + cosa * (point.x - origin.x) - sina * (point.y - origin.y),
            y : origin.y + sina * (point.x - origin.x) + cosa * (point.y - origin.y)
        }
    
    }
    
    //position of point relative to line defined by a and b
    function PointPos(a, b, point)
    {
        return Math.sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
    }
    

    你必须:

    1. 应用旋转和翻译后计算图像矩形角的位置
    2. 使用上面提供的第二个功能检查裁剪矩形角是否在图像矩形内。如果

      x 矩形

      PointPos(a,b,x)== -1&& PointPos(b,c,x)== -1&& PointPos(c,d,x)== -1&& PointPos(d,a,x)== -1;

    3. 编辑: 以下是一个示例:JSFiddle

      这两个矩形有不同的起源。外部(pictureArea)矩形的旋转原点是内部(cropArea)的原点。

      祝你好运!

答案 1 :(得分:2)

怎么了?

不是将照片坐标夹在原始坐标系中,而是需要将它们夹在旋转的坐标系中。在旋转坐标系中使用裁剪区角落的坐标(在计算minXmaxXminYmaxY时)是没有意义的。

夹住照片坐标

您需要在旋转坐标系中构建裁剪区域的轴对齐边界框,您可以使用该边界框进行裁剪。像这样:

Bounding box in the rotated coordinate system

由于我们围绕裁剪区域的中心旋转,因此中心坐标保持不变。但是宽度和高度会增加。

请注意,您已经计算了这些数量以适当缩放照片。简而言之,您只需将计算边界框({​​{1}}和crop.w时获得的宽度crop.h和高度bbox.w替换为缩放时使用的宽度bbox.h和高度var maxX = crop.x + (bbox.w/2) - (photo.w/2); var maxY = crop.y + (bbox.h/2) - (photo.w/2); var minX = crop.x - (bbox.w/2) + (photo.w/2); var minY = crop.y - (bbox.h/2) + (photo.h/2); center = { x: Math.min(minX, Math.max(maxX, photo.x)), y: Math.min(minY, Math.max(maxY, photo.y)) };

var sin = Math.sin(-rotation);
var cos = Math.cos(-rotation);

photo.x = crop.x + cos*(center.x - crop.x) - sin*(center.y - crop.y);
photo.y = crop.y + sin*(center.x - crop.x) + cos*(center.y - crop.y);

请记住,在最后两行上获得的坐标仅适用于旋转坐标系。如果您在原始坐标系中需要它们,您仍然需要将它们旋转回来:

{{1}}

答案 2 :(得分:1)

实际上你不能“钳制”x和y值,因为x和y是相互依赖的。我想说的是你可以通过改变图像的x值或y值或x和y值的无限线性组合来解决问题(图像超出范围)。

因此钳位函数将返回一组无限正确的值(如果没有解,则不返回任何值)。如果您想要解决问题,您应该在钳位功能的规格中添加更多条件。例如,您可以说钳位函数必须最小化max(displacementX, displacementY)。如果添加此条件,则clamp函数将返回单个或无解。