购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.4 Quartz坐标变换

图形的另一种操作就是变换,主要包括平移、缩放和旋转等形式变换。变换离不开坐标,不同的绘图系统对于坐标系的定义也有所区别。

2.4.1 坐标系

在苹果的2D图形技术是Quartz 2D和UIKit,Quartz 2D是Mac OS X和iOS环境下的2D绘图引擎。涉及内容包括:基于路径的绘图,透明度绘图,遮盖,阴影,透明层,颜色管理,防锯齿渲染,生成PDF,以及PDF元数据相关处理。在iOS中还可以通过UIKit进行图形绘制,但是Quartz 2D和UIKit坐标系不同。

图2-14 Quartz 2D的坐标系

Quartz 2D的坐标系,原点在左下角, x 方向向右为正方向, y 方向向上为正方向,如图2-14所示。

图2-15 UIKit的坐标系

UIKit的坐标系,原点在左上角, x 方向向右为正方向, y 方向向下为正方向,如图2-15所示。

图2-16 原始图片

下面通过实例介绍他们的区别,图2-16所示是一张可爱的招财猫图片。

如果绘制这样的图片在不同坐标系下会有什么不同呢?绘制图片可以通过2.2节介绍的UIImage类中几个绘制图像的方法实现,这种方式就不再介绍了。下面看看实现方式。

- (void)drawRect:(CGRect)rect
{        
    NSString *path = [[NSBundle mainBundle] 
                                            pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSaveGState(context);
    
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);

}

图2-17 绘制图片

上面的实现代码方式全部通过Quartz 2D函数实现绘制,也可以实现绘制图像的目的,其中CGContextDrawImage函数相当于UIImage类中的绘制图像方法。但是绘制出来的结果令人沮丧,图像倒过来了(如图2-17所示),这是因为Quartz 2D坐标系和UIKit坐标系不同所导致的,我们使用的绘制函数都是基于Quartz 2D坐标系的。

为了能够正确的显示在UIKit坐标系下,需要进行坐标变换,需要修改代码如下。

- (void)drawRect:(CGRect)rect
{    
    
    NSString *path = [[NSBundle mainBundle] 
'                                            pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSaveGState(context);
    
    CGContextTranslateCTM(context, 0, img.size.height);                     ①
    CGContextScaleCTM(context, 1, -1);                                      ②

    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);

}

图2-18 变换之后

我们添加了第①~②行代码,具体的含义我们先不用理解,这关于坐标变换的问题,会在2.4节介绍。添加之后的运行结果如图2-18所示。

提示: 读者可能已经发现,如果使用UIImage类提供的绘制图像方法不会出现这个问题,UIImage采用的坐标是UIKit坐标,已经进行了转换,不需要再进行转换了。

2.4.2 2D图形的基本变换

在图形变换过程中需要大量使用矢量、矩阵及其运算,学习图形变换需要了解这些基本矩阵知识,关于这些知识本书不再介绍。2D图形的基本变换包括:平移、缩放和旋转三种变换。

1.平移变换

图2-19 平移变换

平移是一物体从一个位置到另一位置所做的直线移动。如果要把一个位于 P x y )的点移到新位置 P ′( x ′, y ′)时,只要在原坐标上加上平移距离Tx及Ty即可,如图2-19所示。

2.缩放变换

图2-20 缩放变换

用来改变一物体大小的变换称为缩放变换。如果要对一个多边形进行比例变换,那么可把各顶点的坐标( x y )均乘以比例因子 S x S y ,以产生变换后的坐标( x ′, y ′)。其中, S x S y 可以是任意正数, S x S y 可以相等或不等。如果比例因子数值小于1,则物体尺寸减小;大于1,则使物体放大;Sx及Sy都等于1,则物体大小形状不变。需要注意的是图2-20表示的比例变换是针对坐标原点的。

3.旋转变换

图2-21 旋转变换

物体上的各点绕一固定点沿圆周路径做转动称为旋转变换。我们可用旋转角表示旋转量的大小。一个点由位置( x y )旋转到( x ′, y ′)的角度为自水平轴算起的角度, θ 为旋转角,如图2-21所示。

有的图形系统还提供另外几种很有用的变换,如反射变换及错切变换等。我们重点介绍一下反射变换。反射是用来产生物体的镜像的一种变换。物体的镜像一般是相对于一个对称轴生成的,因此反射变换可以分为 x 轴对称变换、 y 轴对称变换和坐标原点的对称变换。

4. x 轴对称变换

图2-22 x 轴对称变换

x 轴的对称变换是一种特殊形式的缩放变换,其中 S x =1, S y =-1,如图2-22所示。

5. y 轴对称变换

图2-23 y 轴对称变换

y 轴的对称变换是一种特殊形式的缩放变换,其中 S x =-1, S y =1,如图2-23所示。

6.坐标原点的对称变换

图2-24 坐标原点的对称变换

关于坐标原点的对称变换是一种特殊形式的缩放变换,其中 S x =-1, S y =-1,如图2-24所示。

2.4.3 CTM变换矩阵

Quartz 2D提供了多种形式的变换,其中主要是当前变换矩阵变换(current transformation matrix,CTM)和仿射(affine)变换。CTM变换,这种变换比较简单,主要的函数有:

1.平移变换

平移变换根据指定的 T x T y 值移动坐标系统的原点。我们通过调用CGContextTranslateCTM函数来改变每个点的 x y 坐标值。如图2-25右所示显示了一幅图片沿 x 轴移动了100个单位,沿y轴移动了50个单位。具体代码如下:

- (void)drawRect:(CGRect)rect
{    
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();    
    CGContextSaveGState(context);

    CGContextTranslateCTM (context, 100, 50);
    
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);
}

@end

图2-25 平移变换

2.缩放变换

缩放操作根据指定的 S x S y 因子来改变图像的大小,从而放大或缩小图像。 S x S y 因子的大小决定了新的坐标系是否比原始坐标系大或者小。图2-26显示了指定 S x 因子为0.5, S y 因子为0.75后的缩放效果。具体代码如下:

- (void)drawRect:(CGRect)rect
{    
    
    NSString *path = [[NSBundle mainBundle] 
                                        pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();    
    CGContextSaveGState(context);

    CGContextScaleCTM (context, .5, .75);

    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);
}

@end

图2-26 缩放变换

另外,通过指定 S x 因子为负数是 x 轴对称变换,同样可以指定 S y 因子为负数是 y 轴对称变换。通过调用CGContextScaleCTM函数来指定 S x S y 缩放因子。

3.旋转变换

S x S y 旋转变换根据指定的角度来旋转坐标。我们可以通过CGContextRotateCTM函数来指定旋转角度(以弧度为单位)。图2-27右图所示,显示了图片以原点为中心顺时针旋转45°,代码如下所示。

- (void)drawRect:(CGRect)rect
{    
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();    
    CGContextSaveGState(context);

    CGContextRotateCTM(context, radians(45.));
    
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);
}

@end

图2-27 旋转变换

其中,radians是我们定义的宏,用来将弧度转化成为度。我们需要h文件中添加如下代码。

#define radians(x) (x*M_PI/180)

由于旋转操作使图片的部分区域置于上下文之外,所以区域外的部分被裁减。如果旋转的弧度为负数,则图形是逆时针旋转。

CGContextRotateCTM(context, radians(-45.));

有些情况下需要组合变换,得到累加效果。还记得2.4.1一节Quartz 2D和UIKit坐标系不同导致的图片倒置吗?如图2-28右所示的效果,需要组合变化。具体变化代码如下。

- (void)drawRect:(CGRect)rect
{        
    NSString *path = [[NSBundle mainBundle] 
                                        pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSaveGState(context);
    
    CGContextTranslateCTM(context, 0, img.size.height);
    CGContextScaleCTM(context, 1, -1);

    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);

}

其中,先进行了平移变换,再进行了缩放变换,CGContextScaleCTM(context,1,-1)缩放的结果是进行了 y 轴对称变换。要想达到这个效果上面的组合方式不是唯一的。

图2-28 组合变换

提示: 当相同的绘制程序在一个UIView对象和Quartz图形上下文上进行绘制时候,需要做一个变换,使Quartz图形上下文与UIView具有相同的坐标系。要达到这一目的,需要将Quartz图形上下文的原点平移到左上角,再乘以-1对( y 轴对称变换),如图2-29显示了这种转换过程。

图2-29 坐标转换

2.4.4 仿射(affine)变换

仿射(affine)变换也是一种2D坐标变换,它可以重用变换,经过多次变换(即多次的矩阵相乘),每一种变换都可以用矩阵表示,通过多次矩阵相乘得到最后结果。仿射变换函数:

如图2-28右所示的效果,可以使用仿射变换,具体变化代码如下。

- (void)drawRect:(CGRect)rect
{    
    
    NSString *path = [[NSBundle mainBundle] 
                                        pathForResource:@"cat" ofType:@"png"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];    
    CGImageRef image = img.CGImage;
    
    CGContextRef context = UIGraphicsGetCurrentContext();    
    CGContextSaveGState(context);    
    
    CGAffineTransform myAffine 
                = CGAffineTransformMakeTranslation(0, img.size.height);                ①
    myAffine = CGAffineTransformScale(myAffine, 1, -1);                                ②
    CGContextConcatCTM(context, myAffine);                                             ③
    
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    
    CGContextRestoreGState(context);

}

首先,通过第①行所示CGAffineTransformMakeTranslation函数创建新的平移变换矩阵。第②行代码是通过CGAffineTransformScale(myAffine,1,-1)语句在平移变换矩阵上乘以缩放变换矩阵,然后再通过第③行所示CGContextConcatCTM函数连接到CTM矩阵并输出结果。 1YaVZmiMGMH9hfB1UR5kolPNl714OS+B5w3NH5qh+juI/2o+40i2lWZD7pe4Vvs6

点击中间区域
呼出菜单
上一章
目录
下一章
×