Mac平台开发Retina高分屏app的技术心得

转自QQ的官方blog,看了一下蛮有启发的,怕他们哪天突然给删掉了,再次保存一下.

基本概念和思想

和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和开发的工作。

转自这里

发表评论

电子邮件地址不会被公开。 必填项已用*标注