【问题标题】:Rotate CGPath without changing its position旋转CGPath而不改变它的位置
【发布时间】:2012-12-06 06:48:57
【问题描述】:

我想旋转一个CGPath,我正在使用以下代码:

CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
CGPathRef rotatedPath = CGPathCreateCopyByTransformingPath(myPath, &transform);

这段代码工作正常,但它改变了路径的位置!我希望路径保持与旋转前相同的位置。

【问题讨论】:

  • 路径将围绕某个点旋转。它必须。你认为它应该围绕什么点旋转?从你的问题看不清楚。
  • 我相信路径会围绕 (0, 0) 旋转。您可以让您的变换将中心转换为 (0,0),然后旋转,然后再变换回来。

标签: iphone objective-c ios core-graphics


【解决方案1】:

路径没有“位置”。路径是一组点(由线段和曲线段定义)。每个点都有自己的位置。

也许您想围绕特定点而不是原点旋转路径。诀窍是创建一个组合了三个单独变换的复合变换:

  1. 将原点平移到旋转点。
  2. 旋转。
  3. 从第 1 步反转翻译。

例如,这里有一个函数,它接受一个路径并返回一个新路径,该路径是围绕其边界框中心旋转的原始路径:

static CGPathRef createPathRotatedAroundBoundingBoxCenter(CGPathRef path, CGFloat radians) {
    CGRect bounds = CGPathGetBoundingBox(path); // might want to use CGPathGetPathBoundingBox
    CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformTranslate(transform, center.x, center.y);
    transform = CGAffineTransformRotate(transform, radians);
    transform = CGAffineTransformTranslate(transform, -center.x, -center.y);
    return CGPathCreateCopyByTransformingPath(path, &transform);
}

请注意,此函数返回一个具有 +1 保留计数的新路径,您在完成该路径后负责释放该路径。例如,如果您尝试旋转形状图层的路径:

- (IBAction)rotateButtonWasTapped:(id)sender {
    CGPathRef path = createPathRotatedAroundBoundingBoxCenter(shapeLayer_.path, M_PI / 8);
    shapeLayer_.path  = path;
    CGPathRelease(path);
}

更新

这是一个使用 Swift Playground 的演示。我们将从一个显示路径并用十字准线标记原点的辅助函数开始:

import UIKit
import XCPlayground

func showPath(label: String, path: UIBezierPath) {
    let graph = UIBezierPath()
    let r = 40
    graph.moveToPoint(CGPoint(x:0,y:r))
    graph.addLineToPoint(CGPoint(x:0,y:-r))
    graph.moveToPoint(CGPoint(x:-r,y:0))
    graph.addLineToPoint(CGPoint(x:r,y:0))
    graph.appendPath(path)
    XCPCaptureValue(label, graph)
}

接下来,这是我们的测试路径:

var path = UIBezierPath()
path.moveToPoint(CGPoint(x:1000,y:1000))
path.addLineToPoint(CGPoint(x:1000,y:1200))
path.addLineToPoint(CGPoint(x:1100,y:1200))
showPath("original", path)

(请记住,十字准线是原点,不是我们正在转换的路径的一部分。)

我们获取中心并转换路径,使其以原点为中心:

let bounds = CGPathGetBoundingBox(path.CGPath)
let center = CGPoint(x:CGRectGetMidX(bounds), y:CGRectGetMidY(bounds))

let toOrigin = CGAffineTransformMakeTranslation(-center.x, -center.y)
path.applyTransform(toOrigin)
showPath("translated center to origin", path)

然后我们旋转它。所有旋转都围绕原点发生:

let rotation = CGAffineTransformMakeRotation(CGFloat(M_PI / 3.0))
path.applyTransform(rotation)
showPath("rotated", path)

最后,我们把它翻译回来,完全颠倒了原来的翻译:

let fromOrigin = CGAffineTransformMakeTranslation(center.x, center.y)
path.applyTransform(fromOrigin)
showPath("translated back to original center", path)

请注意,我们必须反转原始翻译。我们不按其新边界框的中心进行平移。回想一下(在此示例中),原始中心位于 (1050,1100)。但在我们将其平移到原点并旋转后,新边界框的中心位于 (-25,0)。将路径平移 (-25,0) 不会使其接近其原始位置!

【讨论】:

  • 边界框会在旋转过程中发生变化,比如说 45 度,那么你最终会得到不同尺寸的贝塞尔路径。因此,要撤消翻译,您可能必须获得新的边界框并使用新的原点进行反转。使用旧中心反转将不起作用,因为路径边界在旋转后可能会增长。
  • 撤消翻译意味着与原始翻译完全相反。您不想查看新的边界框。您必须通过原始平移变换的逆来变换路径。
  • @Andy 我已经更新了我的答案以逐步显示该过程。
  • @robmayoff 很棒的帖子,非常感谢 Playground 的这次更新。我可能在这里混合了一些东西。我试图旋转贝塞尔路径并将其渲染为图像。为此,我必须重置翻译以确保贝塞尔路径适合图像边界。所以我基本上使用了 bezierPath.bounds 中的 (x, y) 来撤消翻译。
  • 真的很有帮助!!!谢啦 !我花了 5-6 个小时才明白我需要旋转更复杂的路径而不是整个层
【解决方案2】:

Swift 5 版本:

func rotate(path: UIBezierPath, degree: CGFloat) {
    let bounds: CGRect = path.cgPath.boundingBox
    let center = CGPoint(x: bounds.midX, y: bounds.midY)

    let radians = degree / 180.0 * .pi
    var transform: CGAffineTransform = .identity
    transform = transform.translatedBy(x: center.x, y: center.y)
    transform = transform.rotated(by: radians)
    transform = transform.translatedBy(x: -center.x, y: -center.y)
    path.apply(transform)
}

【讨论】:

    【解决方案3】:

    一个 Swift 3 方法来围绕它的中心旋转一个矩形:

    func createRotatedCGRect(rect: CGRect, radians: CGFloat) -> CGPath {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let path = UIBezierPath(rect: rect)
        path.apply(CGAffineTransform(translationX: center.x, y: center.y).inverted())
        path.apply(CGAffineTransform(rotationAngle: radians))
        path.apply(CGAffineTransform(translationX: center.x, y: center.y))
        return path.cgPath
    }
    

    所有 rob mayoff 将其应用于路径及其边界框的代码仍然适用,因此我没有理由重复它们。

    【讨论】:

      【解决方案4】:

      如果您想旋转和/或缩放 UIBezierPath,或缩放 CAShapeLayer,同时保持 UIBezierPath 的中心。这里是 Swift 4/5 的函数。它基于 rob mayoff 的回答。

      extension UIBezierPath {
       
      /// perform scale on UIBezierPath and keep the position in the path's center
        func scale(scale:CGFloat) {
          let bounds = self.cgPath.boundingBox
          
          let center = CGPoint(x:bounds.midX,y:bounds.midY)
          var transform = CGAffineTransform.identity.translatedBy(x: center.x, y: center.y)
          transform = transform.scaledBy(x: scale, y: scale)
          transform = transform.translatedBy(x: -center.x, y: -center.y);
          
          self.apply(transform)
        }
      
      
        /// perform rotate on UIBezierPath and keep the center unchanged
        func rotate(radians:CGFloat) {
          let bounds = self.cgPath.boundingBox
          
          let center = CGPoint(x:bounds.midX,y:bounds.midY)
          var transform = CGAffineTransform.identity.translatedBy(x: center.x, y: center.y)
          transform = transform.rotated(by: radians)
          transform = transform.translatedBy(x: -center.x, y: -center.y);
          
          self.apply(transform)
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多