Core Graphics 二: CGAffineTransfo

2020-09-24  本文已影响0人  Trigger_o

Core Graphics 一: CGContext基本绘制

图形学变换需要了解矩阵
1.向量和矩阵
2.变换

变换的过程是图形的每一个点都根据一个关系变换成另一个点
在齐次坐标中,点可以被描述成矩阵
变换的过程就是点的矩阵乘上一个变换矩阵(变换关系),最后得到另给一个点矩阵

CGAffineTransform

struct CGAffineTransform {
  CGFloat a, b, c, d;
  CGFloat tx, ty;
};

CGAffineTransform提供了用于创建、连接和应用仿射转换的函数.
因为第三列总是(0,0,1),所以CGAffineTransform数据结构只包含前两列的值.
注意文档中的写法,矩阵写成一排,是从左到右从上到下的,比如一个3x3的单位矩阵,单位矩阵是对角线为1,其他全是0的矩阵,在iOS的文档中写作[1 0 0 0 1 0 0 0 1]
所以3x2的CGAffineTransform写作[a b c d tx ty]

在上面的文章中,点被描述成单列矩阵,而在Quartz中,点描述成单行矩阵因此iOS的变换矩阵和上面文章里的变换矩阵不太一样.


开头链接里的
Quartz里的

因此在Quartz中[X Y 1] [a b 0 c d 0 tx ty 1] = [aX+cY+tx bX+dY+ty 1]

综上所述,在Quartz中,变换的过程就是调用CGAffineTransform的一系列方法,生成一个CGAffineTransform结构体,这个结构体就是一个变换矩阵,然后设置UIView的UIViewGeometry分类里的transform属性,或者调用变换函数CTM,给定上下文和CGAffineTransform,来执行变换

1.CGAffineTransform的函数

    CGAffineTransform transform = CGAffineTransformIdentity;
    [UIView animateWithDuration:.5 animations:^{
        [view setTransform:CGAffineTransformScale(transform, .5, .5)];
    }];

CGAffineTransform提供了几个生成特定形式变换矩阵的API

2.变换的拆分与合并
变换就是矩阵,两次变换就是两个矩阵,那么,两个矩阵相乘,就是将两个变换合并起来,一次完成,
同理,复杂的变化也可以拆分
例如,一个平移矩阵[1 0 0 1 2 2] ,乘上第二个平移矩阵[1 0 0 1 2 2] 等于[1 0 0 1 4 4].
因此,上面的Translate和Scale和Rotate三个函数,都提供了两个函数,一个是需要一个CGAffineTransform,修改并返回,另一个不需要传CGAffineTransform,它会修改CGAffineTransformIdentity并返回,因此一个是叠加,一个是从初始状态计算.
当分解一个变换的时候,就需要操作同一个CGAffineTransform结构体

    DrawView *view = [[DrawView alloc]initWithFrame:CGRectMake(0, 0, 300, 400)];
    view.center = self.view.center;
    view.backgroundColor = UIColor.yellowColor;
    [self.view addSubview:view];

    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformScale(transform, .5, .5);
    transform = CGAffineTransformTranslate(transform, 250, 400);
    transform = CGAffineTransformRotate(transform, M_PI/2);
    [UIView animateWithDuration:.5 animations:^{
        [view setTransform:transform];
    }];

3.变换对UIKit坐标系的影响
拆分变换需要注意坐标系的变化,当使用旋转变换后,坐标系也会发生旋转.

    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformTranslate(transform, 200, -200);
    transform = CGAffineTransformRotate(transform, M_PI/2);
    transform = CGAffineTransformTranslate(transform, 200, 200);
    NSLog(@"dv=%@",NSStringFromCGRect(self.dview.frame));
    [UIView animateWithDuration:.5 animations:^{
        [view setTransform:transform];
    }completion:^(BOOL finished) {
        NSLog(@"dv=%@",NSStringFromCGRect(self.dview.frame));
    }];

在这个例子里面,view先平移,再旋转,再平移会来,其中:
1.旋转始终是以view的中心为中心进行
2.view的fram在变换前是{{362, 483}, {300, 400}},旋转90度后,frame变成{{312, 533}, {400, 300}},宽高颠倒了.
那么如果旋转45度呢,CGRec在坐标系内是一个平行于坐标轴的矩形,旋转45度就不平行了,改成M_PI_4打印一下,发现是{{264.51262658470836, 435.51262658470836}, {494.97474683058323, 494.97474683058323}},
这不太容易看出来,把宽高都改成300,再试一次,从{{362, 483}, {300, 400}}变成了{{299.86796564403573, 470.86796564403573}, {424.26406871192853, 424.26406871192853}},一个正方形旋转45度之后,对角线垂直于y轴,300x300+300x300 = 424x424,因此view的fram变成了一个包裹变换后的区域的新的矩形.
为了验证这一点,可以再创建一个viewA,始终让A的fram和View的frame相同,A也添加在VC上,给A一个透明度方便观察,然后执行动画.

 [UIView animateWithDuration:.5 animations:^{
        [view setTransform:transform];
        self.viewA.frame = self.dview.frame;
    }completion:^(BOOL finished) {
        NSLog(@"dv=%@",NSStringFromCGRect(self.dview.frame));
    }];
frame的变化.gif

3.第二次平移不是-200,200,因为坐标系的方向变了,一开始左上角是原点,朝右是x,朝下是y,旋转后,右上角是原点,朝左是y,朝下是x.
transform结构体在调用CGAffineTransform函数之后,会将transform进行修改并返回,而CGAffineTransformmake函数不需要transform参数,也就不会修改,它只是返回一个变换矩阵而已,这两种的结果是不一样的,一个是在变换的基础上继续变换,另一个是在初始状态的基础上变换.

//        transform = CGAffineTransformTranslate(transform, 200, 0);
        [UIView animateWithDuration:.5 animations:^{
//            [self.dview setTransform:transform];
            [self.dview setTransform:CGAffineTransformMakeTranslation(200, 0)];
        }];
第一次的旋转变换被丢掉了
在第一次变换的基础上第二次变换

当然,还可以合并两次变换

    transform = CGAffineTransformRotate(transform, M_PI_4);
    transform = CGAffineTransformTranslate(transform, 200, 0);
合并变换矩阵.gif

4.由于view旋转了,因此view在superView中的frame也发生了变化,origin和size可能都变了,但是view自身的坐标系变了,因此view中的subView的frame没有变,同样view的bounds是基于自身坐标系的,因此bounds的size不变.

4.CTM坐标系
Core Graphics提供了几个变换坐标系的函数,也就是CTM函数

这三个函数的功能就是将绘制时使用的坐标系(CTM)进行变换,与UIKit的坐标系不同,CTM发生变化,对UIKit坐标没有影响.视图的子视图没有任何变化,frame也不会变化,但是context绘制的图形会变化.由于是坐标系的变化,所以图形的位置和大小都可能会变

///View内
- (void)drawRect:(CGRect)rect{
    if(self.shouldTransform){
//         CGContextRotateCTM(context, M_PI/8);
//        CGContextTranslateCTM(context, 400, 0);
        CGContextScaleCTM(context, .5, .5);
    }

    CGContextDrawImage(context, CGRectMake(rect.size.width/2, rect.size.height/2, 80, 80), [UIImage imageNamed:@"img"].CGImage);
}

///VC内
- (void)transform{
    self.dview.shouldTransform = YES;
    [self.dview setNeedsDisplay];
    NSLog(@"%@",NSStringFromCGRect(self.subv.frame));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = UIColor.whiteColor;
    self.dview = [[DrawView alloc]initWithFrame:self.view.bounds];
    self.dview.backgroundColor = UIColor.lightGrayColor;
    [self.view addSubview:self.dview];
    
    self.subv = [[UIView alloc]initWithFrame:CGRectMake(300, 300, 90, 90)];
    self.subv.backgroundColor = UIColor.whiteColor;
    [self.dview addSubview:self.subv];
    NSLog(@"%@",NSStringFromCGRect(self.subv.frame));
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setTitle:@"transform" forState:UIControlStateNormal];
    btn.frame = CGRectMake(20, 20, 100, 40);
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(transform) forControlEvents:UIControlEventTouchUpInside];
}

上面的例子是给视图V定义一个shouldTransform属性,在VC创建一个按钮,按钮点击会设置shouldTransform为yes,然后重新绘制,并且再打印一次subV的frame
结果是subV的frame和视觉都没有变换,但是绘制在dview上的图片偏移了
在调用CTM函数前后,绘制图片的rect都是CGRectMake(rect.size.width/2, rect.size.height/2, 80, 80),因此图片的坐标是没变的,变的是坐标系

- (void)showanimation{
    CGAffineTransform transform = self.dview.transform;
    transform = CGAffineTransformTranslate(transform, 100, 100);
    [UIView animateWithDuration:.5 animations:^{
        [self.dview setTransform:transform];
    }];
}

再加一个按钮,对dview进行变换,发现CTM不会影响变换的结果

我们看到,CTM的作用在context上的,transform是作用在UIView上的,但是CTM只提供了简单的坐标系变换函数,做不到像CGAffineTransform那样的组合和分解.
对此Core Graphics提供了将Transform转换到CTM的函数,这个转换也包含了执行

//变换坐标系
        CGAffineTransform transform = CGContextGetCTM(context);
        transform = CGAffineTransformRotate(transform, M_PI_4);
        transform = CGAffineTransformTranslate(transform, 200, 0);
        CGContextConcatCTM(context, transform);
//绘制图形
        CGContextDrawImage(context, CGRectMake(0, 0, 80, 80), [UIImage imageNamed:@"img"].CGImage);

箭头就是图片img

5.3D变换

3D变换是在Core Animation里面的,再iOS12以上才支持,3D变换在齐次坐标概念中,是4x4的矩阵
Z轴是朝向屏幕的里和外,当然,屏幕是二维的,3d的效果通过光栅化变换到二维的屏幕上.
提供了类似2d变换的一套API

    CATransform3D tran3d = CATransform3DRotate(self.dview.transform3D, M_PI, 100, 100, 100);
    [UIView animateWithDuration:.5 animations:^{
        [self.dview setTransform3D:tran3d];
     }];
3D变换
动画
上一篇 下一篇

猜你喜欢

热点阅读