转自:http://iblog.tencent.com/mac-retina-guide/
QQ for Mac V2.1版本支持了Retina,作为国内首个支持Retina高分屏的Mac OS X App,我们希望能够和大家分享一些在Retina支持方面的开发技术心得。欢迎Mac平台的开发朋友们与我们沟通交流。也希望可以通过iblog这个平台在今后和大家交流Mac OS X系统方面的技术经验。
基本概念和思想
和iOS的异同
在系统对高分屏的支持上,Mac OS和iOS基本上是一样的:都是两倍的Scale Factor,都是系统自动来完成这个Scaling。但Mac OS毕竟是电脑操作系统,与iOS相比,多了对以下几个方面的支持:
- window和screen的关系
- 多显示器
- 显示器分辨率的随时更改
两种运行模式:Framework-Scaled和Magnified
在高分屏的Mac中,App有两种运行模式: Framework-Scaled模式和Magnified模式。两种模式的差别,在于Backing Store层的大小。什么是Backing Store层?我们的App和显示器之间,其实有一层buffer层,这层就是Backing Store层。在Framework-Scaled模式下,Backing Store层的大小和显示器一样,而Magnified模式时是显示器的四分之一(长宽各为二分之一),相当于同尺寸普通屏的大小。
在Framework-Scaled模式下,因为Backing Store层的大小和显示器一样,所以我们的App可以更精细地做视觉处理。而与之相比,Magnified模式其实是一个纯粹为兼容而设计的模式。在Magnified模式下,因为Backing Store层的大小和普通屏一样,App还是以1x的形式运行,在显示时,即Backing Store层到显示器的转换时,系统再把其拉伸到2x。这样,在Magnified模式下的App会保持和普通屏一致的表现,当然,也会因拉伸而看上去有点模糊。
非Cocoa的App则只能以Magnified模式运行,而Cocoa App默认是以Framework-Scaled模式运行。 如果Cocoa App觉得自己在Framework-Scaled模式下运行有较大体验问题,也可以在程序中将自己设成默认以Magnified模式运行。 此外,用户也可以通过程序文件的“显示简介”窗口手动更改App的运行模式。
毫无疑问,要在高分屏上有细腻的视觉感受,我们的程序必须运行在Framework-Scaled模式下。
Point和Pixel
什么是Point?苹果给的定义是用户空间的一个单位。在普通屏上,Point和Pixel是一比一的关系,而在高分屏上,它们的关系是一比四。这点和iOS是一样的。
对开发者来说,大部分情况下,我们根本不需要去考虑Pixel,因为我们程序中用到的坐标基本上都是以Point为单位的。在编写代码时,我们可以这样假设:世界上根本就不存在高分屏的Mac,新的MacBook Pro其实还是原来的1440*900。这样,我们就会都是以Point的方式去考虑坐标,从而保持程序高分屏和普通屏表现的一致性。这也是苹果推荐的方式。
那我们什么时候才需要考虑到Pixel呢?当我们准备操纵像素的时候。主要有以下几个地方:
- Core Graphics。也就是说,当我们用到CG开头的API时,我们需要注意,它们的坐标大部分是以Pixel为单位的。
- OpenGL。既然是绘制,自然要精确到像素级。
DPI和PPI
DPI:Dots Per Inch,每英寸点数,一般用来表示打印机或显示设备的精度。而在图像中,则一般使用PPI。
PPI:Pixels Per Inch,每英寸像素数,一般用来表示图像的精细度。
高分屏的DPI是144,是普通屏的两倍。此外,用系统截图工具截取的图片,其PPI也是144,同样是普通屏时的两倍。
支持高分屏,我们需要做些什么?
确认我们App运行在Framework-Scaled模式下
Cocoa App默认是以Framework-Scaled模式运行,不用做任何额外的事情。对于非Cocoa的App,如果要支持高分屏,需要做些修改了。
增加两倍图资源
和iOS一样,要支持高分屏,对所有图片,我们都需要在其旁边增加一个名字为“一倍图名字+@2x”的两倍图资源。对原来用图数量就比较多的程序来说,如QQ,设计师的工作量毫无疑问是巨大的。不仅如此,因为Mac窗体大小不像iOS那么统一,为了防止问题发生,切图时还要注意以下事项:
- 两倍图的图片资源,其分辨率最好是一倍图严格的两倍;PPI和一倍图保持一样,还是72。如果不这样做,不仅在高分屏上有可能出现不符合我们预想的表现,普通屏也是会受影响的。这是因为这些错误有可能会导致系统判断两倍图关系失败,从而使普通屏下直接读取两倍图,导致界面展示异常。
- 如果程序中还涉及到图片的叠加,那么这些用来叠加的图片,内容上最好也是严格的两倍。举个例子,如果一倍图中有根线的位置是第三行,那么两倍图中这根线的位置应该是第五行和第六行。否则,会出现在普通屏下对齐的图片在高分屏下却没有对齐的情况。
确认代码中的资源图片读取方法
要想让系统帮我们自适应使用一倍图和两倍图,我们最好尽量使用文档推荐的资源图片读取方法:NSImage的imageNamed:方法和NSBundle的imageForResource:方法,分别对应main bundle和其他bundle中图片的读取。如果之前代码中有使用其他资源图片读取方法的地方,最好替换掉。
确认资源图片读取时不要带后缀名
和iOS一样,如果我们想让系统自适应来使用一倍图和两倍图,读取时传入的图片名字就不能带有后缀名,否则读取的是那张指定名字的一倍图。xib中也是一样的情况。所以,请搜索全程序确认这点。
确认之前涉及图片size的代码是否有问题
NSImage的size是以Point为单位的,所以无论一倍图还是两倍图,其size都是一样的。而前面讲过CG层又是以Pixel为单位的,所以请详细检查转换时的代码。
检查有图片拉伸的地方是否有异常
首先注意一点, 拉伸图片时,系统会自动使用两倍图。这是因为,NSImage读取图片时其实是两幅图同时读入的,使用时再根据目标区域的大小决定使用哪张图。如果目标区域的大小是介于一倍图和两倍图之间,那么系统会使用两倍图,然后将其缩到目标区域大小。所以要注意喽,如果之前程序中有目标区域比图片大,那么赶紧确认下是否还表现正常,因为这里会使用两倍图的。
当然,如果需要,我们可以不让系统不这么做。通过NSImage的类方法setMatchesOnlyOnBestFittingAxis: 设定后,一倍图和两倍图就严格根据普通屏和高分屏来使用了。不过需要注意,该方法是10.7.4才有的。
一般来说,最好使用NSDrawThreePartImage和NSDrawNinePartImage来拉伸图像。
增加Point与Pixel的转换代码
之前因为普通屏下Point与Pixel是一比一的关系,所以有些代码无意中会将这两种坐标弄混。所以,最好确认下这方面的代码,特别是和屏幕相关的地方。
需要Point与Pixel之间的坐标转换时,使用Backing Coordinate System。NSView、NSWindow和NSScreen都有与其坐标转换的方法,尽量不要直接使用Scale Factor进行计算。
增加分辨率变化时的响应代码
前面提过,与iOS相比,Mac是支持多显示器和随时切换分辨率。当一个窗体在两个不同显示模式的显示器里来回拖拽时,这个窗体的显示模式和分辨率同时也在发生着变化。不过别担心,一旦这些发生变化,系统会自动帮我们重绘窗体,一倍图和两倍图之类的也会自动帮我们切换。不过,我们最好还是确认下,在发生这些变化的情况下,我们的表现是否还是正常的。
如果需要在显示模式切换时做些处理时,我们可以监听NSWindowDidChangeBackingPropertiesNotification。如果是自定义NSView,可以重载viewDidChangeBackingProperties方法,在里面做处理。
注意高分屏时截取的图像其PPI是144
当我们通过系统截图工具或QQ在高分屏中进行截图时,就会发现截取后的图像,其PPI是144,是普通屏的两倍。这并不难理解,因为高分屏单位距离的像素数就是普通屏的两倍。所以程序中使用截图的地方,要注意这点。
替换API
1)Base Coordinate System相关
之前我们会经常使用Base Coordinate System来表示在window中的坐标,而这个坐标系因为高分屏将被抛弃。所以,和这个坐标系相关的API,我们最好都要替换掉。
NSView:
convertPointToBase:替换成convertPoint:toView:(view为nil)
convertSizeToBase:替换成convertSize:toView:
convertRectToBase:替换成convertRect:toView:
convertPointFromBase: 替换成convertPoint:fromView:
convertSizeFromBase: 替换成convertSize:fromView:
convertRectFromBase: 替换成convertRect:fromView:
NSWindow:
convertBaseToScreen:替换成convertRectToScreen:
convertScreenToBase:替换成convertRectFromScreen:
2)图片绘制相关
NSImage的两个方法compositeToPoint:和dissolveToPoint:因为涉及到Base Coordinate System,所以被建议替换成drawAtPoint:fromRect:operation:fraction:。
此外,lockFocus方法也建议用imageWithSize:flipped:drawingHandler:替换。前面提到过,NSImage其实是同时拥有一倍图和两倍图的,会根据目标区域来使用哪一张,而lockFocus其实只处理了一张。
还有,建议使用NSGraphicsContext来处理图像,因为它会自动帮我们处理Scale Factor。
3)Scale Factor相关
因为高分屏Scale Factor的介入,旧Scale Factor相关的需要修改:
a)NSScreen和NSWindow的userSpaceScaleFactor;
b)HIGetScaleFactor方法需用HIWindowGetBackingScaleFactor替换;
c)NSWindow的NSUnscaledWindowMask常量将被抛弃;
一些Trick
如何往NSTextView中添加高清gif图
为了支持高清表情,我们更改了在NSTextView中添加gif图的实现方法,采用了NSFileWrapper的方式,通过NSFileWrapper的setIcon:方法将NSImage设进去。但修改后运行我们发现表情都不会动了。怎么回事?我们跟踪了好一会,途中尝试了几种不同的方法,终于在做了以下处理后使表情运行正常:
- 将表情资源文件的.gif后缀改成.png;
- 高分屏和普通屏分开读取图片,普通屏只读取一倍图,高分屏则采用不使用后缀名的方式读取。
这种做法确实很让人费解,但这确实是我们经过多次尝试后找到的解决办法。之后我们还会继续研究这个问题,希望能找到更好的解决办法。
如何在制作dmg包时让其finder背景也支持retina
Apple没有在高分屏指南的文档中提到任何与dmg相关的内容,也没有为之增加任何新的方法来做这件事。但在其文档中有提到,使用tiffutil来制作复合型的tiff格式图片。在此格式的图片中,同时存储着普通与高清版本图像。事实上经过我们的测试,这种tiff格式图片是可以被用作dmg背景的。具体命令是:
tiffutil -cathidpicheck 普通图 高清图 -out 目标图片名称
需要注意的是:
- DropDMG暂时不支持将tiff作为背景,可以通过修改后缀名为png的方式绕过此限制。
- 用这种方法生成的dmg,在查看时会在左侧有一道奇怪的白边。可能是Apple的实现问题。
工具推荐
高分屏模拟工具
新的MacBook Pro刚出来,货源紧缺,实在不好买到。别担心,Xcode的Graphics Tools可以让我们模拟高分屏。Graphics Tools可以到developer.apple.com下载。 如何模拟高分屏:
- 将Graphics Tools中的Quartz Debug打开;
- 选择UI Resolution菜单;
- 在弹出来的窗口中选择“Enable HiDPI display modes”;
- 注销并重新登录;
- 在系统偏好设置的显示器面板中,选择后面带有HiDPI标志的分辨率。
这样,系统就会模拟高分屏的显示模式。略感不足的是,模拟后的分辨率只有原来的四分之一,操作各种不习惯。没办法,毕竟是一比四的关系啊。
两倍图检查工具
一下子换那么多图,哪些地方的两倍图显示正确,哪些地方还没换,我等肉眼凡胎,检查时难免纰漏。没关系,系统提供了自动标红的功能,帮助我们找出这些地方。
打开方式:在终端中,敲入以下命令
defaults write com.mycompany.myapp CGContextHighlight2xScaledImages YES
( com.mycompany.myapp就是我们需要检查的App名,如果是想检查所有的App,用-g替换 )
打开后,没有换成两倍图的地方,都会被红框标出来,非常方便。
UI实用工具
- Layer Cake,直接从psd生成切图文件,甚至可以从普通psd直接生成高清版本的切图,在为旧软件增加高清支持时尤其方便。
- IconKit,支持生成高清版本的icns文件。
- PaintCode,矢量绘图并直接生成Objective-C代码,自然支持高清,大大减少UI和开发的工作。