5943


CGAffineTransform の影響で変更された CGRect の値を元に戻すにはどうすればいいでしょうか？

以下が検証として取り組んでいる変換例で、point(10,10), size(100, 100) の四角形を 45 度傾けています。

[code]

    // 初期状態
    CGRect rect = CGRectMake(10, 10, 100, 100);                          // {{10, 10}, {100, 100}}
    CGAffineTransform transform = CGAffineTransformIdentity;             // [1, 0, 0, 1, 0, 0]
    
    // 45度回転させる
    CGAffineTransform rotate = CGAffineTransformMakeRotation(45.0f * M_PI / 180.0f); // [0.70710677, 0.70710677, -0.70710677, 0.70710677, 0, 0]
    transform = CGAffineTransformConcat(transform, rotate);              // [0.70710677, 0.70710677, -0.70710677, 0.70710677, 0, 0]
    rect = CGRectApplyAffineTransform(rect, transform);                  // {{-70.710678, 14.142136}, {141.42136, 141.42136}}
    
    // rectを初期状態に復元したい
    // ...？
    
[/code]

以下のコードはダメでした。

[code]

    rect = CGRectApplyAffineTransform(rect, CGAffineTransformInvert(transform)); // {{-40, -40}, {200, 200}}
    
[/code]

ちなみに、

[code]

    CGAffineTransformConcat(transform, CGAffineTransformInvert(transform)) // [0.99999994, 0, 0, 0.99999994, 0, 0]
    
[/code]

であり、誤差はあるものの、行列自体は CGAffineTransformInvert() で戻せるようです。が、CGRect が期待どおりに戻りません。

実際に使用する際は、初期状態が CGAffineTransformIdentity
とは限らず、どのような行列が適用されているかはわかりません。また、CGAffineTransformIdentity なときの CGRect
も取得できるとは限りません。言い換えれば、ある行列が既に適用済みの CGRect から CGAffineTransformIdentity な状態の
CGRect を算出するのが目的です。

今回の例では、45度傾けることで `{{-70.710678, 14.142136}, {141.42136, 141.42136}}`
になってしまったものを `{{10, 10}, {100, 100}}` に変換できれば成功です。


CC BY-SA 3.0
2015-02-04T02:37:15.913
5910
2015-02-04T11:22:16.617
question
ios objective-c
CGRectApplyAffineTransformを適用されたCGRectを元に戻したい


質問者様の正確な回答かわかりませんが、`{{10, 10}, {100, 100}}`へ戻す方法です。  
swiftで失礼します。

[code]

    var button : UIButton = UIButton()
    override func viewDidLoad() {
        super.viewDidLoad()
        button = UIButton(frame: CGRectMake(10, 10, 100, 100))
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "didTapButton:", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(button)
        //SwiftだとM_PIがdouble型なので、CGFloatへ
        let endAngle:CGFloat =  CGFloat(M_PI)
        button.transform = CGAffineTransformMakeRotation(endAngle / 4);
        println(button.frame)
    }
    func didTapButton(sender : UIButton)
    {
        button.transform = CGAffineTransformIdentity
        println(button.frame)
    }
    
    
[/code]

log

[code]

    //回転した状態
    (-10.7106781186547,-10.7106781186548,141.42135623731,141.42135623731)
    //タップ後の状態
    (10.0,10.0,100.0,100.0)
    
[/code]


CC BY-SA 3.0
2015-02-04T03:32:35.063
5913
2015-02-04T03:32:35.063
4551
5910
answer


CGRectの仕様として原点の座標とサイズを保持するようになっているため、45度傾けた時点でCGRectのサイズが拡大されてしまいます。  
（長方形を紙に描いて、傾けてみればわかりやすいかと思います）

これを避ける場合、元のCGRectから四隅に該当するCGPointを生成して、それぞれ別々に変換させるのが楽かと思います。こうしておけば、CGAffineTransformInvert()でそれぞれのCGPointを元の位置に戻すことが出来ます。

以下Swiftですが、試しにやってみました。

[code]

    // CGRectの四隅をCGPointに変換
    func rectToPoints(rect: CGRect) -> [CGPoint] {
        var ar = [CGPoint]()
        ar.append(CGPoint(x: rect.minX, y: rect.minY))
        ar.append(CGPoint(x: rect.maxX, y: rect.minY))
        ar.append(CGPoint(x: rect.maxX, y: rect.maxY))
        ar.append(CGPoint(x: rect.minX, y: rect.maxY))
        return ar
    }
    
    // CGPointをCGRectに変換
    func pointsToRect(points: [CGPoint]) -> CGRect {
        var min_x = points[0].x
        var max_x = points[0].x
        var min_y = points[0].y
        var max_y = points[0].y
    
        for pos in points {
            min_x = min(min_x, pos.x)
            max_x = max(max_x, pos.x)
            min_y = min(min_y, pos.y)
            max_y = max(max_y, pos.y)
        }
        return CGRect(x: min_x, y: min_y, width: max_x-min_x, height: max_y-min_y)
    }
    
    // 初期状態
    let rect = CGRect(x: 10, y: 10, width: 100, height: 100)
    let transform = CGAffineTransformIdentity
    
    // 45度回転させる
    let rotate = CGAffineTransformMakeRotation(CGFloat(45.0 * M_PI / 180.0))
    let transform2 = CGAffineTransformConcat(transform, rotate)
    let rect2 = CGRectApplyAffineTransform(rect, transform2)
    
    // ここまでは、Objective-CのコードをSwiftに直しただけ
    
    // 四隅を別々にしてから45度回転
    let point = rectToPoints(rect)
    let point2 = point.map() { pos in CGPointApplyAffineTransform(pos, transform2) }
    let rect3 = pointsToRect(point2)    // {x -70.711 y 14.142 w 141.421 h 141.421}
    
    // 四隅を別々に逆変換
    let invert = CGAffineTransformInvert(transform2)
    let point3 = point2.map() { pos in CGPointApplyAffineTransform(pos, invert) }
    let rect4 = pointsToRect(point3)    // {x 10 y 10 w 100 h 100}
    
[/code]

**補足** : 回答内関数のObjective-C版を付記します

[code]

    // e.g.)
    // CGPoint points[4];
    // [self rectToPoints: rect points: points];
    - (void)rectToPoints:(CGRect)rect points:(CGPoint[4])points {
    
      points[0] = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
      points[1] = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
      points[2] = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
      points[3] = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
    }
    
    // e.g.)
    // CGRect rect = [self pointsToRect: points];
    - (CGRect)pointsToRect:(CGPoint[4])points {
    
      CGFloat minX = points[0].x;
      CGFloat maxX = points[0].x;
      CGFloat minY = points[0].y;
      CGFloat maxY = points[0].y;
    
      for (int i = 0; i < 4; i++) {
        minX = fminf(minX, points[i].x);
        maxX = fmaxf(maxX, points[i].x);
        minY = fminf(minY, points[i].y);
        maxY = fmaxf(maxY, points[i].y);
      }
    
      return CGRectMake(minX, minY, maxX - minX, maxY - minY);
    }
[/code]


CC BY-SA 3.0
2015-02-04T09:51:42.140
5943
2015-02-04T11:22:16.617
2015-02-04T11:22:16.617
7459
5910
answer