天天看點

一文弄懂CGAffineTransform和CTM

一文弄懂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););

縮放:

視圖空間 預設以中心點為原點 transform = CGAffineTransformMakeScale(1, -1); 沿着中心X軸線豎直翻轉

繪制空間 預設以左下角為原點 transform = CGAffineTransformMakeScale(1, -1); 沿着X軸橫線豎直翻轉

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

CGContextConcatCTM(ctx, CGAffineTransformMakeScale(1, -1));

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

方法一:

//移動到螢幕右邊

transform = CGAffineTransformTranslate(transform, imageHeight,0);

//逆時針旋轉90度

transform = CGAffineTransformRotate(transform, M_PI_2);

//将transform作用于context

CGContextConcatCTM(ctx, transform);

方法二:

//右移圖檔高度的距離

transform = CGAffineTransformTranslate(transform, 0,-imageHeight);

執行結果如下

CGAffineTransformRotate組合坐标系問題

注意:所有的變換都會影響到坐标系産生新的坐标系。比如:旋轉轉換,坐标系也跟着旋轉,按照X軸翻轉縮放,坐标系也會翻轉。

解釋一下逆時針旋轉圖檔 方法一 和 方法二

方法一比較容易了解,

1.右移圖檔寬度

2.左下角 為圓心,逆時針旋轉90度。

重點說明下方法二

1.逆時針旋轉90度

2.右移圖檔高度。

逆時針旋轉大家容易了解,但是,右移圖檔高度為什麼是transform = CGAffineTransformTranslate(transform, 0,-imageHeight);???

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

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,并不會出現上下颠倒的問題。

    //建立位圖上下文

    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

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));

//縮放

    //列印結果為 (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);

    //列印為(300, 100, 200, 400); 先計算了縮放,再計算平移得到此值

//平移+旋轉

    //列印為(100, -100, 200, 100);  先計算了旋轉,再計算平移得到此值

//平移 + 旋轉 + 縮放

    transform = CGAffineTransformRotate(transform, -M_PI_2);

    transform = CGAffineTransformScale(transform, 2, 2);

    //列印為 (100, -300, 400, 200); 先計算了旋轉/縮放,再計算平移得到此值

    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

總結:

    平移:CGRectApplyAffineTransform對于平移轉換是與實際變換結果一緻的。

    旋轉:以目前坐标系原點(0,0)進行旋轉計算後的值。

    縮放:得到的結果為,rect中的各個值乘以縮放比例。

    組合變換:先計算旋轉和縮放,最後計算平移