高级绘图要领-几何语言

目录
  • 视图坐标
一 视图坐标
1.1 Frames 和 Bounds

一个视图的frame是通过bounds,center和其他transform计算来的。他描述了可以包括整个视图的最小矩形。

1.2 坐标系转换

iOS SDK提供了很多坐标系转换的方法。比如说,你也许会希望吧一个点从视图坐标系转换到它的父视图的坐标系中,来确定在它的父视图的哪个位置进行绘制。你可以如下这么做:

CGPoint convertedPoint = [outerView convertPoint:samplePoint fromView:grayView];

你可以使用任何视图实例调用这个方法。尤其是你想要转换一个点到另一个坐标系上时(toView:)或事从另一个坐标系转移过来(fromView:),

像例子中那样。 这些视图必须存在于同一个UIWindow,否则这个方法不会奏效。这些视图不需要有任何其他的相关关系了。然而,他们可以是兄弟姐妹,父子,祖先,或其他关系。

这个方法会返回一个你想要得到的点坐标。 转换矩形的方法和转换点的方法也是类似的。把一个矩形从一个视图转换到另一个坐标系,使用convertRect:fromView:。转换回来,使用convertRect:toView:

二 关键结构体

iOS绘图使用四个关键的结构体来定义几何基元:尺寸矩形转换。这些结构体都使用同一的单位:逻辑点。点通过CGFloat值来定义。在iOS中用float,而OS X使用double。 不像像素,固定整数点且与设备硬件无关。他们的值和亚像素精度提供的数学坐标有关。iOS绘图系统使用你习惯的数学。

你会用到的四个基元如下:

  • CGPoint: 点结构由x和y组成,他们定义了逻辑位置。
  • CGSize : 尺寸结构由width和height组成,他们定义了横纵轴上的延展
  • CGRect : 矩形由使用点定义的origin属性和一个size属性组成。
  • CGAffineTransform : 放射变换结构描述了几何项的改变——特别是,一个项目如何放置,放缩,旋转,他储存了a,b,c,d,tx和ty六个值的矩阵来定义变换。
2.1 转换

转换是iOS几何学中最强大一个部分。他允许点从一个坐标系转移到另一个坐标系。也允许你放缩旋转,镜像,位移等等当你绘制的时候,通过保存线性和相关比例。转换提供了复杂的架构来解决几何问题。核心图像的版本(CGAffineTransform)使用3x3的矩阵,来解决2D问题。而在3D中,使用Core Animation中的图层定义的4x4的矩阵。Quartz转换允许你进行几何的位移放缩旋转。 所有的转换都可以用如下所示的底层转换矩阵表示:

image.png

用C语言机构提表示为:

struct CGAffineTransform{
       CGFloat a;
       CGFloat b;
       CGFloat c;
       CGFloat d;
       CGFloat tx;
       CGFloat ty;
}
2.1.1 创建转换

不像其他核心图像结构体,你几乎很少直接使用放射变换。大部分人甚至都不需要使用CGAffineTransformMake()(需要六个参数)来创建结构体。 取而代之的是,CGAffineTransformMakeScale()CGAffineTransformMakeRotate()或是CGAffineTransformMakeTranslation()。这些方法分别通过参数创建了放缩,旋转,位移矩阵。这些方法可以允许你根据你想完成的变换执行对应的操作。需要旋转一个对象?直接输入特定的选择程度即可。需要移动对象?直接输入移动位移即可。每一个方法创建了一个transform来完成指定操作。

2.1.2 分层转换

相关方法允许你把一个转换覆盖在另一个转换之上,完成一个复杂的变换。不像第一个函数,他们只取一部分变换作为参数。他们定义一个变换会层叠另一个变换在顶上。不像make方法,他们总是按一定序列摆放的,且每一个变换的结果都会传递到下一个变换。比方说,你也许想创建一个围绕他的中心选择和放缩的事物,你可以如下列代码这样处理:

CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, -center.x, -center.y);
t = CGAffineTransformRotate(t, M_PI_4);
t = CGAffineTransformScale(t, 1.5, 1.5);
t = CGAffineTransformTranslate(t ,center.x, center.y);

开始是一个默认的变换。默认的transfrom是个基础,就像加法中的0或者乘法中的1一样。在使用几何对象时,一般以此开始。是用来也确保一个固定的起始点以服务于之后的操作。 因为转换应用于对象的起始点,并不是中心点。你需要把中心点移动到起始位置。放缩和选择总是与坐标为(0,0)的点关联。如果你想让他们关联到另一个点,比如说一个视图或是路径的中心点,你需要把你想要的点移动到(0,0)的位置。这一连串的变换都会存储在唯一的变换t中。

2.1.3 曝露转换

UIKit库提供了大量的辅助图像和绘制操作的方法。还包括一些转为放射变换特定的方法。你可以通过UIKit的NSStringFromCGAffineTransform()方法打印出一个视图的变换。

这里没有做中心在定位,所以只有两个操作。 这些数字并不是那么直观。特别是,他没有直接告诉你放缩了多少选择了多少。不过幸运的是,有很简单方法可以处理,可直接展示更加直观的值。下面展示如何计算X,Y方向上的放缩,以及旋转角度。 当然,没有必要去计算translation(位移)的值,因为这些值直观地储存在txty中。

//Extract the x scale from transform
CGFloat TransformGetXScale(CGAffineTransform t)
{
     retrun sqrt(t.a * t.a + t.c * t.c);
}

//Extract the y scale from transform
CGFloat TransformGetYScale(CGAffineTransform t)
{
      retrun sqrt(t.b * t.b + t.d * t.d);
}

//Extract the rotation in radians
CGFloat TransformGetRotation(CGAffineTransform t)
{
      return atan2f(t.b, t.a);
}
2.1.4 其他基础方法
  • CGRectInset(rect, xinset, yinset) :这个方法会得到一个与原矩形同中心但更大或者更小的矩形。inset为正则变小,反之变大。这个方法在移动绘制图形和子图像离开边界来提供一个空白空间是会显得很有用。

  • CGRectOffset (rect, xoffset, yoffset):这个方法会得到一个与原矩形偏离x,y距离的矩形。Offset可以很好的应用于拖动矩形在屏幕上移动,或是创建一个阴影效果。

  • CGRectGetMidX(rect)CGRectGetMidY(rect) : 这两个方法返回矩形x方向和y方向上的中心坐标。这个方法可以很容易的获取bounds或者frames的中心点。相关方法还有minX,maxX,minY,maxY,width和height。中心点可以很好的在绘图中心项中使用。

  • CGRectUnion(rect1,rect2): 这个方法返回一个可以所有源矩形都包括进去的最小矩形。这个方法可以帮你确定你所绘制的元素最小的边界盒子。这个方法可以把你所有的绘制路径和元素结合起来并且作为他们的模板。

  • CGRectIntersection(rect1,rect2) : 这个方法返回两个矩形的交叉部分,若无交叉则返回CGRectNull。交叉可以在使用AutoLayout计算矩形插入时起到很好的作用。路径边界和图像边界之间的交叉,可以得到两者间固有的内容。

  • CGRectIntegral(rect) 这个方法把源矩形转换为整数。所有的orign值都会四舍五入到整数。而size值则会向上舍入。如此来确保新的矩形会把原始的矩形完整的包括进去。整数话矩形可以为绘制提速。视图绘制通过像素边界,这样操作可以更好的减少锯齿和模糊。

  • CGRectStandardize(rect) 这个方法会返回一个正数宽高值的基础矩形。标准化可以帮助你更好地简化数学运算当操作一些交叉绘制,特别当用户交叉在左上中而非右下时。

  • CGRectDivide(rect, &sliceRect, &remainderRect, amount, edge)这个方法是核心图像这些方法中最复杂的一个了,但是它也是举足轻重的。除法可以交叉切割一个矩形成一个部分,以便于你可以细化绘制区域。

2.1.4 使用CGRectDivide

CGRectDivide方法是极其便利的。它提供了非常简单的方法来分割和细化矩形成几部分。每一步,你都会定义需要分割多少,分割哪一部分。你可以用从任何边界进行分割,包括CGRectMinXEdgeCGRectMinYEdgeCGRectMaxXEdgeCGRectMaxYEdge。 一系列的代码之后会得到如图下图所示的图案。下面代码展示了具体如何实现。这段代码先是分割出矩形左边的一小部分,然后纵轴对半分割剩下的部分。然后再在剩下的部分左右两侧再分出一小块。

  • 核心代码如下
  • 运行结果如下
2.1.5 矩形公用自定义方法

你使用CGRectMake()来创建frames,bounds或者其他矩形参数。它接收4个浮点数参数x,y,width和height。这是Quartz绘图中最为重要的一个方法。 但常常,你总是想通过你经常使用的对象,如points和size,来直接创建矩形。但你可以通过组合域来重写方法。你会发现有一个实用的简化方法是多么有用,通过使用结构体为参数。代码如下

出奇意料地,Quartz没有一个内建方法来获取矩形的中心点。虽然你可以很容易地通过核心图像方法取到x,y的中点。直接通过rect返回一个点坐标会更加方便一些。

围绕着一个中心点来创建一个矩形也是常常会遇到的挑战。比方说,你想让一个文本中心对齐某点,或事围绕一个点来摆放某个形状。下面代码即为实现方法。你提供一个中心点和一个尺寸,返回的矩形会描述你的目标位置。

将上述代码结合来给指定矩形绘制一个中心对齐的字符串。它计算了字符串的尺寸(使用iOS7的API),然后和矩形的中心一起来创建了一个中心围绕的对象。

  • 运行结果如下

另一个解决中心对齐的方法,它使用一个已存在的矩形和它的中心来得到另一个矩形,而非通过尺寸和目标点。你可以使用这个方法通过Bezier曲线得到的bounds矩形来计算。通过下列方法来让路径位于一个矩形中心处。

三 适配填充

往往,你需要重新改变绘制的尺寸来适配更小或更大的空间,而非使用他的原始尺寸。为了完成这些,你需要计算绘制的目标,不论你是在绘制路径,图像或是上下文。下面有四个基础程序你需要完成:中心对齐适配填充压缩。 如果你曾经使用过view content mode,这些代码看起来会比较相似。当绘制的时候,你会使用和UIKit一样的填充视图和上下文的策略。他们对应的填充策略分别是:centerscale aspect fitscale aspect fillscale to fill

3.1 中心对齐

中心对齐会按照矩形的原始比例摆放视图,直接摆放在目标的中心点位置。素材比展示区域大会被裁剪。素材比区域小则会修边(在四边都会留下多余的区域,在绘制中叫做windowboxing)。 在每个情况中,图像都是用RectAroundCenter()来完成中心对齐的。在外部的大图会被裁剪,而内部小图则会留下很多额外的空间。

3.2 适配

当你需要适配一个项目到某个目标时,你需要保持它原有的比例并让它的所有部分都可见。由于需要维持原有比例,结果也并不会是全覆盖的,也往往会留下一些额外的空间。 在物理世界中,修边是指在一个框架图中使用的背景或边界。这个背景或边界在画和物理尺寸中留下空间。这里,我使用一个matting(修边)来表示绘图区域和目标矩形之间的额外区域。

灰色背景为目标矩形。淡紫色背景是需要适配的图案通过下述代码的计算得到的结果。除非两者比例恰好切合,绘制区域需要做中心对齐操作,把额外的区域留在上下或左右两端。

3.3 填充

填充,如下图所示,确保你的目标区域每一个像素都有源图像等一部分。不要被绘图的fill(填充,用特定颜色或图案填充路径)操作迷惑,。 为了完成这个操作,填充或扩充或缩放源矩形来精确地覆盖在目标上。他会裁剪掉所有在目标以外的部分,不论是在上下或是左右。结果矩形会是中心对齐的,但只有一个维度会完整的展示出来。 下面代码计算出来目标目标矩形的过程。

3.4 压缩

压缩会调整源图像的比例来适配到整个目标区域。目标区域的每一个像素都会有源图像里的内容。如下图所示。素材会在一个方向上重新设置尺寸,以便可以容纳得下。这里,兔子图像压缩了横向来匹配目标矩形。 不像之前的适配风格,压缩不需要额外的计算。直接绘制即可,Quartz会做其他剩下的工作。

四 总结

本章介绍了一些基本的术语和一些操作核心绘图的方法。你已经了解了像素和点的区别,探索了常见的数据类型,学习了如何计算绘图位置。这里有一些最后需要注意的地方,在你离开本章之前可以进行思考:

  • 在编写代码是总是要把屏幕比例考虑进去。相关的设备如iPad2和第一代iPad mini没有使用Retina屏幕。专注于逻辑空间(点)而不是设备空间(像素)可以让你更灵活地支持更新和新的设备几何。

  • 大部分绘制都是相互关联的,尤其是当你需要触摸操作时。学习如何把点从一个坐标系转移到另一个坐标系能更好的按照你的想法来定位,而不是定位到继承中。

  • 放射变换总是被看成是其他的核心图像结构体,但是它和其他更常见的兄弟(点,尺寸和矩形)一样重要。变换是十分强大的可以很大程度上简化绘制操作。如果你有多余的时间,很值得花费一些经历来学习变换和矩阵数学(线性代数)。Khan Academy(www.khanacademy.org) 在这个课题上提供了很好的教程。

  • 一个强健的几何方法代码库可以在很多项目中都使用到。基础几何是永远不会过时的,相似的任务总是会反复出现。学会如何中心对齐矩阵和计算适配和填充目标会在很多时候节约你的时间,并且保证代码长久高效运行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355