一文弄懂CGAffineTransform和CTM


  • 一些概念

坐標空間(系): 視圖(View) 坐標空間與繪制(draw)坐標空間
CTM:全稱 current transformation matrix,看名稱 “當前變換矩陣” 也就是矩陣。
CGAffineTransform: 是一個具體的矩陣數據值。 CGAffineTransform是CTM的具體值。
  • 關於矩陣變換

相同 CGAffineTransform作用於不同的坐標空間,其結果不一樣。

移動:

視圖空間 中心為原點,向右為x遞增,向下y遞增,CGAffineTransformMakeTranslation(-75, 25);  左移75,下移25
繪制空間 左下點為原點,向右為x遞增,向上y遞增,CGAffineTransformMakeTranslation(-75, 25);  左移75,上移25

視圖空間示例:_demoView.transform = CGAffineTransformMakeTranslation(-75, 25);

繪制空間示例:
CGContextConcatCTM(ctx, CGAffineTransformMakeTranslation(-75, 25));
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);


旋轉:
視圖空間 中心為原點,向右為x遞增,向下y遞增, transform = CGAffineTransformRotate(transform, -M_PI_2); 圍繞中心點,逆時針旋轉90度
繪制空間 左下點為原點,向右為x遞增,向上y遞增 transform = CGAffineTransformRotate(transform, -M_PI_2); 圍繞左下角點,順時針旋轉90度

視圖空間示例:_demoView.transform = CGAffineTransformRotate(transform, -M_PI_2);

繪制空間示例:
CGContextConcatCTM(ctx, CGAffineTransformRotate(transform, -M_PI_2););
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

縮放:
視圖空間 默認以中心點為原點 transform = CGAffineTransformMakeScale(1, -1); 沿着中心X軸線豎直翻轉
繪制空間 默認以左下角為原點 transform = CGAffineTransformMakeScale(1, -1); 沿着X軸橫線豎直翻轉

視圖空間示例:_demoView.transform = CGAffineTransformMakeScale(1, -1);

繪制空間示例:
CGContextConcatCTM(ctx, CGAffineTransformMakeScale(1, -1));
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

如果我們現在想要把上面的圖片逆時針旋轉90度,可以使用 CGAffineTransform組合的方式。這里提供兩種轉換方式,都是采用的平移、旋轉,執行順序不同導致,給的參數也不同。后續說明原因。
方法一:

//移動到屏幕右邊
transform = CGAffineTransformTranslate(transform, imageHeight,0);
//逆時針旋轉90度
transform = CGAffineTransformRotate(transform, M_PI_2);
//將transform作用於context
CGContextConcatCTM(ctx, transform);
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);


方法二:
//逆時針旋轉90度
transform = CGAffineTransformRotate(transform, M_PI_2);
//右移圖片高度的距離
transform = CGAffineTransformTranslate(transform, 0,-imageHeight);
CGContextConcatCTM(ctx, transform);
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

執行結果如下
CGAffineTransformRotate組合坐標系問題
注意:所有的變換都會影響到坐標系產生新的坐標系。比如:旋轉轉換,坐標系也跟着旋轉,按照X軸翻轉縮放,坐標系也會翻轉。
解釋一下逆時針旋轉圖片 方法一 和 方法二
方法一比較容易理解,
1.右移圖片寬度
2.左下角 為圓心,逆時針旋轉90度。


重點說明下方法二
1.逆時針旋轉90度
2.右移圖片高度。

逆時針旋轉大家容易理解,但是,右移圖片高度為什么是transform = CGAffineTransformTranslate(transform, 0,-imageHeight);???
謎底是,逆時針旋轉的時候,坐標系也跟着逆時針旋轉90度,變成了右下角為原點,y左邊遞增,右邊遞減。x上方向遞增,下方向遞減。所以此時想要把圖片向右邊移動-imageHeight的距離,按照新的坐標系,就是往y軸的遞減方向走。也就有了transform = CGAffineTransformTranslate(transform, 0,-imageHeight);

  • 關於CGContextDrawImage

DrawImage繪制什么情況是顛倒的,什么情況不是顛倒的,坐標系又是什么樣的?

使用 UIGraphicsGetCurrentContext()獲取的上下文, CGContextDrawImage是 顛倒的。想要正向的圖片需要做CTM變換。
-(void)drawImage{
    
    CGFloat imageWidth = CGImageGetWidth(self.image.CGImage);
    CGFloat imageHeight = CGImageGetHeight(self.image.CGImage);
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageWidth, imageHeight), 0, [UIScreen mainScreen].scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

//,為了保證正向顯示圖片,需要先上移圖片高度,再沿X軸翻轉。
//    CGContextTranslateCTM(context, 0, imageHeight);
//    CGContextScaleCTM(context, 1, -1);
// 使用轉換之后的坐標系繪制圖片
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);
    
    UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    self.imageView.image = newImg;
}

  自己創建位圖,再調用 CGContextDrawImage,並不會出現上下顛倒的問題。
-(void)drawImage{
    
    CGFloat imageWidth = CGImageGetWidth(self.image.CGImage);
    CGFloat imageHeight = CGImageGetHeight(self.image.CGImage);
    
    //創建位圖上下文
    CGContextRef ctx = CGBitmapContextCreate(NULL, imageHeight,imageWidth,
                                             CGImageGetBitsPerComponent(self.image.CGImage), 0,
                                             CGImageGetColorSpace(self.image.CGImage),
                                             CGImageGetBitmapInfo(self.image.CGImage));
    //這里drawImage是正的。
    CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    self.imageView.image = img;
    return ;
}

CGContextDrawImage 的坐標系
默認是以左下角為坐標原點開始繪制,但是,But,通過一系列的CTM轉換之后,最終的繪制坐標CGRectMake(0, 0, imageWidth, imageHeight)是根據新的坐標系計算的。比如逆時針旋轉90度的例子,新坐標系 原點為右下角,y左邊遞增,右邊遞減。x上方向遞增

CGContextDrawImage(ctx, CGRectMake(100, 100, imageWidth, imageHeight), self.image.CGImage);

關於 CGRectApplyAffineTransform轉換CGRect

  • 關於CGRectApplyAffineTransform轉換CGRect

CGrectApplyAffineTrans對於平移、縮放、旋轉的表現情況


CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t) 在當前坐標系,對rect做仿射變換之后,得到的新rect。
//平移
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印結果為(200, 100, 100, 200);
    CGRect newRC = CGRectApplyAffineTransform(rc, CGAffineTransformMakeTranslation(100, 100));

//縮放
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印結果為 (200, 0, 200, 400); 所有值都乘了2
    CGRect newRC = CGRectApplyAffineTransform(rc, CGAffineTransformMakeScale(2, 2));


//旋轉
    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, 0);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印結果為(0, -200, 200, 100); 得到的是相對於(0,0)旋轉90度的值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);
   

//平移 + 縮放
    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformScale(transform, 2, 2);
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印為(300, 100, 200, 400); 先計算了縮放,再計算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

//平移+旋轉
    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印為(100, -100, 200, 100);  先計算了旋轉,再計算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

//平移 + 旋轉 + 縮放

    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    transform = CGAffineTransformScale(transform, 2, 2);
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印為 (100, -300, 400, 200); 先計算了旋轉/縮放,再計算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

總結:
    平移:CGRectApplyAffineTransform對於平移轉換是與實際變換結果一致的。
    旋轉:以當前坐標系原點(0,0)進行旋轉計算后的值。
    縮放:得到的結果為,rect中的各個值乘以縮放比例。
    組合變換:先計算旋轉和縮放,最后計算平移


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM