了解和使用Xcode

1.了解IDE窗口

(1)窗口布局一览

窗口包括:工具栏,导航器面板,编辑器面板,调试器面板,检查器面板和库面板。
可以按住Command键+一个数字键(从1到7)或点击导航器面板的顶端的图标来切换导航器面板的视图。
在窗口的右上角有一组工具栏按钮

(2)更改公司名称的方法

新建Objective-C源文件时,Xcode会自动帮你生成注释文字。如下:
  1. //
  2. // ViewController.h
  3. // LearnXcode
  4. //
  5. // Created by SharonHu on 15/7/14.
  6. // Copyright (c) 2015年Sharon. All rights reserved.
  7. //
Xcode在注释块中生成了文件名称、项目名称以及创建者和创建日期。
我们可以自己更改公司名称。
方法是:在导航器面板选中项目,在检查器面板的Project Document栏目下的Organization文本框中输入你的公司名称,在Class Prefix文本框中可以输入文件的前缀。如下图所示:

(3)一些使用技巧

A.过滤列表文件

使用导航器面板底部的搜索框可以过滤源列表文件,例如下图搜索出了名称中带有guide的文件。你可以在任意的导航器视图中使用这个搜索框。

B.窗口显示文件:
双击导航器面板中的某一个文件,可以用独立的全屏窗口显示这个源文件,这在比较两个不同文件时很有用。也可以用两个不同的窗口显示同一个文件,但是要注意有时候这两个窗口的内容会出现不同步的情况,因此需要点击其中某个窗口来使他们同步。
C.标签显示文件:
也可以用标签的显示方式(就像Safari一样)。显示标签的方法:View →Show Tab Bar选项,如下图:

2.Xcode偏好设置

通过“Command+逗号”快捷键或”Xcode|Preferences”菜单呼出偏好设置。

(1)主题及字体(Preferences->Fonts & Colors)

选中一种主题theme),例如“Midnight”,然后shift选择Source Editor/Console中的所有项,点击Fonts设置字体。Xcode默认字体为menlo,可选Consolas、Monaco等其他等宽字体。

(2)文本编辑配置(Preferences->Text Editing)

Editing:

  • Show Line Numbers:在编辑面板中显示行号。
  • Code folding ribbon:显示折叠ribbon。
  • Page guide at column:显示一行最多支持80个字符的提示分割线。

Indentation:

  • Prefer indent using:Spaces(为保持一致的视觉呈现和行末注释对齐,建议使用空格)
  • Tab width:4 spaces(tab expand,1个tab=4个空格)
  • Indent width:4 spaces(自动缩进步长=4个空格)

(3)SCM(Preferences->Source Control)

Enable Source Control:启用/禁用XCode自带Source Control Manager(SCM)。

(4)SDK/Simulator(Preferences->Downloads)

Downloads->Components:可下载SDK和Simulator。

(5)构建输出目录(Preferences->Locations->Locations)

  • 当选择为Default时,Derived Data的目录为~/Library/Developer/Xcode/DerivedData。
  • 当选择为Relative时,Derived Data的目录为当前.xcodeproj所在的目录。
  • 当选择为Custom时,Derived Data的目录需自定义。
    不建议使用绝对路径,因为写死之后,换环境或换平台,又要重新修改路径,建议使用相对路径(Relative

3. 在Xcode的帮助下编写代码

(1)首行缩进(美观排版)

选中文本后,按住Control键点击(或直接右击),在弹出来的上下文菜单中选择Structure → Re-Indent选项,Xcode会将代码重新排版。
快捷键Control + i 可以达到同样的效果。
通过Structure菜单,或者按下Command+ 【 键和 Command + 】键可以将选定的代码进行左移或者右移。
Xcode →Preferences → Text Editing → Indentation , 自定义Xcode内的代码风格。

(2)代码自动完成

编写程序时,Xcode会为所有内容生成索引,包括项目中的比变量名和方法名以及导入的框架。当输入代码时,Xcode会不断地比较你输入的代码和它生成的符号索引,如果匹配,Xcode就会给出建议。下图是我输入N时出现的自动完成列表:
E:枚举符号
f:表示函数
#:表示#define 指令
m:表示方法
C:表示类
等等

(3)批量编辑

   创建快照 :选择File  → Create Snapshot 选项(或者 Command + Control + S 快捷键)(如下图),Xcode会记住项目目前的状态,然后就可以随便编辑源文件而不用担心把项目“玩坏了”。
     如果意识到自己犯了一个严重的错误,可以通过File → RestoreSnapshot选项,打开快照窗口,这样就可以选择一个快照恢复项目了。如下图:
说明:快照文件存储在~Library/Developer/Xcode/Snapshots/目录中。

(4)查找替换功能

在导航器面板中选择搜索选项,或者快捷键Command + Shift + F,就可以对整个项目中的所有文件进行内容搜索和替换。可以点击Replace All对整个项目应用这个替换。

(5)重命名某一个符号的名称

选中一个符号,如局部变量或者参数,然后点击它,出现一个向下的箭头:
点击这个箭头,出现一个菜单,选择Edit all in Scope选项。
你会发现文件中所有使用了这个符号名称的地方都出现了一个虚线框,这时,在任意一个虚线框里修改符号名称,其他虚线框里的符号也会同时被修改。这个功能太棒了!只要单击编辑面板中的其他地方,就能退出Edit all in Scope模式了。
注意,如果发现Edit all in Scope菜单被禁用,这是因为这个功能与Xcode中的语法着色功能密切相关,如果你对语法着色功能改动过多或者关闭了它,Edit all in Scope功能也就停止工作。要解决这个问题,需要回到设置菜单,对语法着色功能进行调整,直到他能恢复工作。
(6)通过Xcode内置的重构工具修改类的名字
把光标放在类名上,右击,出现一个菜单列表,选择Refactor → Rename
在弹出的对话框中输入替代的类名:
可以点击Preview按钮,查看将要修改的地方:
确定修改没有问题后就点击Save。如果是第一次在项目中重构代码,Xcode会询问你是否要启用自动快照备份,选择yes是一个明智的决定。
注意:重构并不能重命名注释中的文字。可以用查找替换功能简化这一操作。

4.代码导航

在代码的生命周期中浏览代码的不同方法。

(1)emacs快捷键组合

指的是一些不用把手从键盘上拿开就能在文字中移动光标的快捷键。
emacs是一个很早就有的文本编辑器,它诞生在20世纪70年代,可以在现代的Mac操作系统上运行。
下面是这些按键及其功能:
Control-F :光标前(Forward)移效果(效果同右方向键)
Control-B:光标后(Backward)退(效果同左方向键)
Control-P:光标移动到上(Previous)一行(效果同上方向键)
Control-N:光标移动到下(Next)一行(效果同下方向键)
Control-A:光标移动到行首位置(效果同Command + 左方向键)
Control-E:光标移动到行末位置(效果同Command + 右方向键)
Control-T:交换(Transpose)光标两边的字符
Control-D:删除(Delete)光标右边的字符
Control-K:把当前光标以后的所有字符全部删除(Kill),便于重写行尾的代码。
Control-L:将光标置于窗口的正中央。如果你找不到光标的位置或者想要移动窗口使光标快速移动到正中央,这个快捷键会非常好用。

(2)快速打开的窍门

窍门一:快速查找并打开一个文件
File → Open Quickly(或者快捷键Shift + Command + O)
打开了一个对话框,在搜索框中搜索关键词,就可以查找与这个关键词有关的文件:
诀窍二:同时显示头文件和它对应的实现文件
View → Assistant → Show Assistant Editor(或者快捷键 Option + Command + 回车键)。默认情况下如果一个面板显示的是头文件,那么另一个面板显示的就一定是实现文件,不过可以根据自己的喜好更改这一设定。点击工具栏的Counterparts菜单,就可以看到其他选项。

(3)代码折叠(code folding)

紧挨着源代码左侧有两个空栏:分别是边栏和聚焦栏。
注意聚焦栏的灰度,代码嵌套的越深,它的灰度就越深。这中颜色编码能够使代码的复杂程度一目了然。
在不同灰色区域悬停鼠标可以高亮显示响应的代码片段。
(4)编辑面板上方的导航条
点击菜单按钮,可以看到快速打开最近访问的历史文件或执行其他高级的操作。
点击前进后退按钮,可以打开曾经编辑过的文件
功能菜单显示当前光标所在的方法,点击功能菜单可以看到文件中所有的符号。如下图所示:
蓝色高亮的地方,说明光标当前位于-tableView:heightForHeaderInSection:这个方法中。
按住Command键,并且点击功能菜单,就可以将文件中的方法按照字母顺序排列,如下图:
可以向菜单中加入其他内容。
有两种方法:一是使用#pragma mark whatever, 后面的whatever可以填写任何的文字,它会出现在菜单中。
#pragma -(减号),会在菜单中插入分割线。Xcode也会在注释里查看那些诸如“MARK:”(与#pragma mark 的功能相同)、“TODO”、“FIXME”、
“ !!!:”和“ ??? :”之类符号开头的文本,并将这些文本放入功能菜单中。最程序发布之前,最好先看看这些标记。

5.获取信息

(1)获得帮助

检查器面板顶端有两个图标
点击第一个图标,检查器显示的是当前文件的各种属性。
点击第二个图标,检查器中启用快速帮助(Quick Help)功能。在代码中点击任意位置,快速帮助面板会更新。

(2)文档管理程序

之间访问苹果公司的官方API文档的方法:按住Option键并且双击某个符号,就跳转到与该符号相关的文档中。如果按住Option键并且双击NSString,将会打开文档浏览器并搜索NSString,如图示:

6.调试运行

(1)Console & Scheme

shift+command+Y:显示控制台(Show/Hide the debug area)

shift + command + K:清除控制台(Clean)

option+command+R:编辑配置(Edit Scheme

(2)Build

Product -> Edit Scheme(option+command+R)->Info->Build Configuration:选择生成版本(Debug or Release)

command + B:构建(Buid)

(3)Target

一个定义好构建过程的Target成为一个Scheme,可在Scheme中定义Target的六种构建过程:Build/Run/Test/Profile/Analyze/Archive。

一个Target是指在一个Project中构建的一个产品,它包含了构建该产品的所有文件,以及如何构建该产品的配置。

Product -> Edit Scheme(option+command+R)->Manage Schemes可对Scheme的六种构建过程进行配置(可配置项包括Info、Arguments、Options)。

在Project Navigator中选中某个xcodeproj(例如QQ.xcodeproj),将进入Project Setting页面,可点击左侧图标show/hide project and targets list:

点击targets项可分别设置各target的Build Settings;右击可对target进行delete。

(4)Issue & Errors

编译错误(error)和警告(warning)过多时,只显示编译错误

点击底端的感叹号,即可只显示编译错误,忽略编译警告:

(5)Run

command + R:运行(Run),可能会先编译。若按下control直接运行上次build的product(Run Without Building)。

command + .:停止运行(Stop)

(6)Breakpoint

command + \:当前行设置/取消断点;通过鼠标点击蓝色断点来启用/禁用当前行断点。

command + Y:全局激活或禁用所有的断点,激活进入调试模式(此时断点蓝色可见)。

边列(Gutter)中的断点/警告可右键呼出Reveal in Breakpoint/Issue Navigator。

trick:编辑断点(Edit Breakpoint):

Condition:设置断点的触发条件,例如“i==3”(注意不能有空格)表示当i等于3时该断点才会被触发。

Ignore:设置断点需要被忽略多少次才会中断,若设置成5则表示第6次遇到该断点时才触发。

Action:设置断点触发时的动作,可以为Debugger Command、Log Message、Shell Command或Sound。

例如可设置以下Debugger Command:

(1)读取std::string sig的内存buffer值:mem read sig.c_str() -c sig.size();

(2)打印NSData实例sig:po sig

(7)Debug

F6:下一步(Step Over),逐过程单步调试,不进入函数体。

(fn+)F7:进入(Step Into)函数体。可能与多媒体键有冲突,故需要fn辅助。

(fn+)F8:跳出(Step Out)函数体。可能与多媒体键有冲突,例如呼叫iTunes,故需要fn辅助。

control+command+Y:逐断点(continue)继续执行。

trick:移动指令指针Move the instruction pointer):

断点调试运行时,可以将绿色指针箭头(Line 47)移动到其他行(Line 49)或其他断点(Line 51)实现跳转执行。

(8)Watch

shift+command+M:Debug Workflow->View Memory

command+K:Debug Workflow->Clear Console

Debug Workflow->Show Disassembly When Debugging,可进行汇编指令级调试。

trick:修改变量内存值change memory value while debugging):

调试运行时,可以在底部的调试窗口(Debug Area,可通过Shift+Command+Y呼出)右键某个变量,除了可以进行View Memory/View Value As之外,还可以选择Edit Value运行时编辑内存变量的值。

这种手动设置指定值,在调试某些难以复现的bug或进行边界测试非常有用,可以避免在验证某个问题时反复改值重新编译。

(9)lldb调试命令

  • n/next:step over;
  • s/step:step into;
  • finish:step out;
  • c/continue:goto next breakpoint;
  • expr/expression:Evaluate a C/ObjC/C++ expression(动态执行C/ObjC/C++表达式);
  • p/print/expr/expression:print as a C/C++ basic variable;

// 打印SYSTEM_VERSION(可能要加UIDevice*转换)
(lldb)p [[[UIDevice currentDevice] systemVersion] doubleValue]

  • po/expr -O/expression -O:Print as an Objective-C object;

// 打印屏幕bounds(可能要加UIScreen*转换)
(lldb)po NSStringFromCGRect([[UIScreen mainScreen] bounds])
// 打印状态栏frame(可能要加UIApplication*转换)
(lldb)po NSStringFromCGRect([UIApplication sharedApplication].statusBarFrame)

  • call:调用。其实上述p/po后接表达式(expression)也有调用的功能,一般只在不需要显式输出,或是无返回值时使用call,用于动态调试插入调用代码。
  • 例如可以在viewDidLoad:里面设置断点,然后在程序中断的时候输入以下命令:

// 调用后,继续运行程序,view的背景色将变成红色
(lldb) call [self.view setBackgroundColor:[UIColor redColor]]

  • bt(backtrace),打印当前调用堆栈(crash堆栈),“bt all”可打印所有thread的堆栈(相当于command+6的Debug Session Navigation)。
  • image:可用于寻址,有多个组合命令,比较实用的一种用法是寻找栈地址对应的代码(行)位置。
  • 例如某个UITableView总共有2个section,当其引用的currentSection.index≥2时将会引起[UITableView rectForHeaderInSection:]调用异常,可使用expr动态改值制造crash场景模拟调试。
  • 此时crash时的控制台bt显示异常出现在应用层代码“0x00d055b8 – [FACategoryTableView FACategorySectionHeaderDidTouched:] + 744”处(其中0x00d055b8为当前栈(代码段)偏移量,744为栈帧偏移量——PC指针相对函数入口的偏移)。
  • 那么具体是FACategoryTableView.m文件哪一行代码调用引起的异常呢?此时通过“image lookup –address”后接bt的call stack中的代码段偏移地址(0x00d055b8)即可定位出异常调用的代码行位置。
  • memory write:改写指定地址的内存(Write to the memory of the process being debugged)。可help mem write查看帮助:
  • x/memory read:dump指定地址的内存(Read from the memory of the process being debugged),后接起止地址或-c指定count加起始地址。可help mem read查看帮助:

Syntax:

memory read <cmd-options> <address-expression> [<address-expression>]

Command Options Usage:

size指定内存块(block/item)的大小,默认为1byte。

    –size <byte-size> ):The size in bytes to use when displaying with the selected format.

count指定内存块(block/item)的个数,可配合起始地址使用。

    -c <count> ( –count <count> ):The number of total items to display.

format指定内容显示格式,格式符同print:c-char,s-string,d-decimal,x-hex。

    -f <format> ( –format <format> ):Specify a format to be used for display.

Command Samples:

(a)起止地址

(lldb)mem read 0x10b88f0c 0x10b88f0c+9

0x10b88f0c: 39 38 37 36 35 34 33 32 31                       987654321

(b)起始地址+内存块count

(lldb)mem read 0x10b88f0c -c 9

0x10b88f0c: 39 38 37 36 35 34 33 32 31                       987654321

(c)起始地址+内存块size+内存块count(dump hex format)

(lldb)memory read -s 1 -f x -c 9 0x10b88f0c

0x10b88f0c: 0x39 0x38 0x37 0x36 0x35 0x34 0x33 0x32

0x10b88f14: 0x31

(d)起始地址+内存块size+内存块count(dump char format)

(lldb)memory read -s 1 -f c -c 9 0x10b88f0c

0x10b88f0c: 987654321

(e)起始地址+内存块size+内存块count(dump string format)

(lldb)mem read 0x10b5cf2c -f s -c 1

0x10b88f0c: “987654321”

(f)起始地址+内存块size+内存块count(dump int format)

(lldb)memory read -s 4 -f x -c 3 0x10b88f0c

0x10b88f0c: 0x36373839 0x32333435 0x109f0031

Syntax: memory write <cmd-options> <address> <value> [<value> […]]

(10)启用NSZombieEnabled调试EXC_BAD_ACCESS

当你对已释放的对象发送消息(90%的可能是对引用计数为0的对象再release)或release那些autorelease对象时,就会出现报EXC_BAD_ACCESS这样的错误。
默认设置下 Xcode不会给你定位具体是哪一行代码不该去使用已释放的对象,或者release用错了。
Product -> Edit Scheme(option+command+R) -> Diagnostics ,勾选“Objective-C”之后的“Enable Zombie Objects”。
设置NSZombieEnabled环境变量后,一个对象销毁时会被转化为_NSZombie;设置NSZombieEnabled后,当你向一个已经释放的对象发送消息,这个对象就不只是报EXC_BAD_ACCESS Crash,还会放出一个错误消息,然后以一种可预测的可以产生debug断点的方式消失, 因此我们可以找到具体或者大概是哪个对象被错误的释放或引用了。
注意NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存,一直开启后果自负!

How can I enable Xcode refactor options for an external build project?

I want to use XCode as an IDE for developing some AVR code. I did the following

1) create new project

2) select external build system

3) indicated i want it to be make, and told it NOT to “Pass build settings in environment”

4) copied my .c/.h/Makefile into the same directory i created the project in

5) added the .c/.h files to the project/target

6) verified that i can build (make) from both command line and Xcode

But I cannot use any of the refactor menu options, or jump to/reveal options. There appears to be no symbol information. Is there no way to use Xcode as more than just a simple C editor that uses an external build system????

If not, it’s a bust for embedded development. Please tell me I’m wrong 🙁

I found a workaround/solution. Create a second target called “Dummy” or something like that, that is based on the Command Line Tool template of type C. Select all the .c files and in the “Targets” pane on the left side bar, check the new Dummy project (or if the options change, as they seem to often, do whatever gestures necessary to make Dummy depend on your C files).

It will create a Dummy directory and a couple of other Dummy files which you can remove if you want. You never bother building ‘Dummy’, in fact, if it’s an embedded project with separate toolchain, likelihood is nearly 100% that it won’t compile as a Command Line Tool for OSX anyway. 🙂

Apparently XCode needs this indirect hint to know that your .c files are indeed real C files and can be parsed/introspected as such.

As you add new .c files which your external build system likely see automatically, make sure you include them as a dependent for the Dummy target.

I was able to get this to work using XCode 6.1. In my case I’m using C++ but it should also work with C as some of my C++ files make C function calls. Here’s what I did:

  1. Create a dummy target using: File->New->Target
  2. For the template choose: OS X->Application->Command Line Tool
  3. Click Next
  4. Fill out the Product Name, etc.
  5. Select C++ for the Language field.
  6. Click Finish.

Back in the Project Navigator pane:

  1. Right click on the new ‘dummy’ target
  2. Select: Add files to ‘your project name’
  3. Browse to select your directories/files to add.

Once the files were added to the Dummy target, I could then open a file under my normal target, right click on a method and Refactor, Jump To Definition, etc.

C++11 新特性:显式 override 和 final

参考文章:https://blogs.oracle.com/pcarlini/entry/c_11_tidbits_explicit_overrides

2012 年 3 月 22 日,GCC 4.7.0 正式发布。从这个版本开始,GCC 增加了许多新的 C++ 11 的特性。今天我们要介绍的是其中的一个特性:显式的使用 finaloverride关键字。先来看下面的例子:

C/C++
struct B1 final { };
struct D1 : B1 { }; // final

上面的代码是错误的,因为 D1 试图继承 B1,而 B1 则声明为 final。很像 Java,不是吗?当然!还有另外的用法:

C/C++
struct B2
{
virtual void f() final {} // final
};
struct D2 : B2
{
virtual void f() {}
};

这段代码又会出错,因为D2::f重写了B2::f,但是B2::f却被声明为 final 的!

下面再看另外一段代码:

C/C++
struct B3
{
virtual void f() {}
};
struct D3 : B3
{
void f() {}
};

开发 D3 的程序员真的想重写B3::f函数吗?还是说,他只是不小心写了个与父类同名的函数,却在不经意间导致了覆盖?为了避免这种错误,C++ 11 引入了override关键字(多么像 C# 啊!)。于是,我们会发现,下面的一段代码是会出错的:

C/C++
struct B4
{
virtual void g(int) {}
};
struct D4 : B4
{
virtual void g(int) override {} // OK
virtual void g(double) override {} // Error
};

多亏了override关键字,我们可以让编译器帮我们检测到这个很难发现的程序错误。这段代码的错误在于,override关键字表明,g(double)虽然想要进行override的操作,但实际父类并没有这么个函数。

值得注意的是,这些并不是一些语法糖,而是能确确实实地避免很多程序错误,并且暗示编译器可以作出一些优化。调用标记了finalvirtual函数,例如上面的B2::f,GNU C++ 前端会识别出,这个函数不能被覆盖,因此会将其从类的虚表中删除。而标记为final的类,例如上面的 B1,编译器则根本不会生成虚表。这样的代码显然更有效率。

为什么会出现High DPI问题

首先解释什么是DPI。DPI是Dots Per Inch缩写,表示每英寸点数,这里的点(Dot)指什么?对面向PC、手机、Pad编程的程序员来说,它等同像素(Pixel),于是DPI就等同PPI。
当前流行的DPI是多少,让以两款电脑为例有个直观认识。一商务笔记本,13.3×8.3英寸屏,最大分辨率1920×1080,由它可算出水平、垂直DPI大概是144(1920/13.3)、130(1080/8.3)。苹果的一款MacBook Air,13.3×8.3英寸屏,最大分辨率1440×900,它水平、垂直DPI分别是108、108。
接下说苹果营销出的一个概念:Retina屏(视网膜屏)。单从DPI这个参数来说,Retina屏是指它的DPI超过人眼能识别极限的最高DPI,我们不关心这个DPI是不是真的超过人眼分辨极限,但不得不承认的是,Retina屏的DPI的确是基本做到了普通屏的2倍。举个例子,当前一款MacBook Pro,13.3×8.3英寸屏,最大分辨率2560×1600,那它的水平、垂直DPI到达了192,基本是同期Air的两倍。
现在来讨论同一数量像素下在Air和Pro分别是多少英寸。假设有一个按钮,程序设定它是360×360像素,那它在Air的实际尺寸是3.33×3.33英寸/8.46cm,在Pro则是1.87×1.87英寸/4.76cm。——到此对High DPI问题会有初步认识了:由于不同DPI,使得同一App在不同显示器上显示时会出现很不一样的物理尺寸。就拿这两款苹果笔记本,Air上看去正常的按钮,换Retina上就小很多。
以上讨论是电脑屏,手机、Pad屏也一样存在DPI。例如iPhone6,对角线是4.7英寸(屏尺寸大概是4.09×2.3),它的水平、垂直DPI都是326。仍然以360×360按钮为例,在iPhone6上则变成1.1×1.1英寸(2.8cm)。以下表格小结了同一像素数在不同设备上的物理尺寸。

High DPI问题根源是用户在意的是物理单位表示的尺寸,程序员关注的是像素表示的尺寸,而关联物理单位和像素的DPI在各设备不同,使得要能让同一App在这些设备都能正常使用的话须进行额外处理。

解决High DPI方法:放大倍数(hdpi_scale)
让思考这样一个问题,对同一个按钮,如何让在MacBook Air、MacBook Pro看去是同一物理尺寸?很快能想到办法,按钮的像素数在Pro时是Air像素数乘上2。办法虽然简单,但的确是各操作系统在使用的解决High DPI方法。
那放大倍数到底是多少,有些是操作系统决定,像iOS,它规定iPhone 5/6是2。Android、Windows则由程序员自个定,不过为缩放图像简单,真正到显示时倍数要取整数。以下是Android定义的density(基本等同hdpi_scale)、DPI值之间关系,它用了一个等式:DPI = density * 160。

density是浮点数,转换到hdpi_scale可用四舍五入法,即小数部分>=0.4时向上取整,否则向下取整。

对Windows系统一般取2,一来是考虑到它的DPI相比手机来说不是很大,二来Windows有个High DPI-unaware概念,对属于High DPI-unaware类型App,它会默认用2去放大。接下就让说说High DPI-unaware。Windows把App分为两类:High DPI-aware和High DPI-unaware,App可调用SetProcessDpiAwareness(Win8.1开始出现)这个API让自个归属到哪种类型。

High DPI-unaware是告诉系统,我这App不支持高清屏,如遇上高清屏请通过DWM(Desktop Window Manager)虚拟化帮我去实现。此种方式的高DPI支持具体过程是这样的,比如当前系统的DPI是192(200%),程序运行时,系统会告诉你当前DPI仍然是96(100%),所以程序会仍然按照96的方式进行绘画,而且调用系统API时,得到的坐标、窗口尺寸都是按96来的。当我们画完之后,DWM再对整个窗口进行200%放大后画到屏幕上,这样看起来程序就自动支持高DPI了。 这种方式对那些非高清屏上开发却依旧要在清高清屏运行的App很有用,像在XP、Win7开发的App,正因为如此,App默认的工作方式是High DPI-unaware。
这种方式看起来很美妙,但有严重缺点,经过放大后的内容看起来会变模糊,比如文字会有明显锯齿。既然DWM虚拟化用户效果不怎么好,那么很多时候就得向操作系统宣称自个能支持高DPI,请直接告诉我真实DPI,至于遇到的屏是192还是108,我自个去处理,这种类型的App就是High DPI-aware。
虽然不叫DWM,iOS也有类似这样功能的模块,它也可把App分为那两类。不过由于DWM会导致图像变模糊这个致命缺点,往后程序必将都是High DPI-aware。
回到方法的源头,这会引入两个概念,配置尺寸和显示尺寸。

  • 配置尺寸(Config Size):定义控件时使用的尺寸。设定值时参考一条规则:能满足最小屏幕分辨率的DPI。
  • 显示尺寸(Display Size):控件最终显示出来的尺寸。是配置尺寸乘上放大倍数,在值上,放大倍数等于1时就是配置尺寸。

QT 如何支持高清屏,视网膜屏幕

原文链接:Morten Johan S?rvig – Retina display support for Mac OS, iOS and X11

Qt 5.0中添加了对于retina显示的基本支持。即将到来的Qt 5.1中提供了新的API和缺陷修复,对于这一问题进行了改进。Qt 4.8也获得了良好的支持,我们反向移植了一些Qt 5的补丁。

尽管这些实现的努力和Mac以及iOS程序员最为相关,但是来看一看其它平台是如何处理高DPI显示这一问题,也是很有趣的。这里主要有两种方式:

  • 基于DPI缩放——Win32 GDIKDE。在这种方式中,应用程序在全物理设备分辨率下工作,使用系统提供的一个DPI设定或者缩放因子,用于缩放布局。字体通常会被操作系统自动缩放(只要您使用点数(point)而不是像素(pixel)来指定字体大小)
  • 另一种意义的像素。在这种方式中,应用程序并不知道物理解析度(在任何程度上)。物理像素被逻辑像素替代:
平台/API 逻辑的 物理的
HTML CSS像素 设备像素
Apple 像素
Android 密度无关像素(dp) (屏幕)像素
Direct2D 设备无关像素(DIP) 物理像素
Qt(过去) 像素 像素
Qt(现在) 设备无关像素 设备像素

在历史上,Qt已经支持基于DPI缩放的物理像素这一方式。在2009年时,对于Windows上的高DPI值的支持已经有所改进。Qt布局对于增加的DPI并没有考虑。现在Qt 5添加了对于“新增像素”这一缩放类型的支持。

(还有其它的高DPI实现么?欢迎大家在评论中进行指正。)

Mac OS X的高DPI支持

OS X上高DPI模式的关键是,以前绝大多数几何信息都是通过物理像素给定的,现在却是设备无关点。这包括桌面几何信息(例如15英寸的Retina MacBook Pro是1440×900而不是全分辨率2880×1800)、窗口几何信息和事件坐标。CoreGraphics绘制引擎知道全分辨率并且针对这一解析度生成输出。例如,对于普通屏幕和高DPI屏幕(其它参数都相同的情况下),一个100×100的窗口在屏幕上占用的区域是一样的。在高DPI屏幕的窗口后端存储包含了200×200像素。

这种模式的主要收益是向后兼容性以及自由的高DPI矢量图形。对于底层情况不了解的应用程序可以简单地像以前一样工作在相同的几何情况下,并且保留写死的像素值。同时,他们还可以使用如文本这样的矢量图形,而不用做任何修改。光栅图形引擎不能获得自动改进,但这是可以实现的。不好的一点是在代码中使用点和像素的时候,有不可避免的坐标系统混淆。

点到像素的缩放因子总是2x。在改变屏幕分辨率的时候,这一情况也是真的——点和像素总是被一个值同时缩放。当使用“More Space”进行缩放的时候,应用程序将会被渲染到一个大的后端存储,这个后端存储会被再缩小到物理屏幕解析度上。

在Mac OS上缩放用户界面解析度

如果您手里没有Retina硬件,在使用外部显示器的时候,有一种仿真模式还是很有用的。打开显示器(Displays)属性并且选择一种HiDPI模式。(如果没有,请查看stackoverflow上的这个问题。)

为OS X应用程序启用高DPI

高DPI模式是通过Info.Plist文件中的这些键值控制的:

<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>

qmake将会为您添加这些内容。(严格意义上说,它将会只添加NSPrincipalClass,NSHighResolutionCapable是可选的并且默认值为true)。

如果NSHighResolutionCapable被设置为false,或者缺少这些键值,那么应用程序将会被按“普通”解析度渲染然后放大。这样的结果看起来很糟糕并且应该避免,特别是因为高DPI模式是非常向后兼容的,并且应用程序可以获得很多高DPI支持而不用做任何修改。

缩放的Qt Creator

高DPI的Qt Creator

(除了一个更新“模式”图标的补丁之外,没有其它修改了。)

Qt的实现细节

Mac OS 10.8(10.7是非正式的?)添加了对高DPI的Retina显示的支持。Qt 4免费获得这一支持,因为它使用的是CoreGraphics绘制引擎。

Qt 5使用的是光栅绘制引擎并且Qt通过缩放绘图器变换(transform)实现了高DPI矢量的绘制。HITheme同时为Qt 4和5提供了高DPI的Mac风格。在Qt 5的Fusion风格中,对于高DPI模式的支持也已经修改好了。

OpenGL是一种基于设备像素的API并且对于高DPI模式也仍然如此。在NSView中有一个flag可以用来开启或者禁用2x缩放——Qt在所有情况下都可以设置它。Shaders运行在设备像素中。

Qt Quick 1是构建于QGraphicsView之上的,它是一个QWidget并且通过QPainter获得对于高DPI的支持。

Qt Quick 2是基于Scene Graph(和OpenGL),已经更新了高DPI的支持。Qt Quick控件(也就是以前的Desktop Component)也已经更新了在高DPI模式下的渲染,其中包括距离场(distance field)文本渲染。(译者注:关于距离场,可以参考Yoann Lopes – Text Rendering in the QML Scene Graph以及iKDE上的译文。)

这里的卖点是应用程序开发人员不需要关心这些,您只需要在设备无关像素的空间里舒适地开发,Qt和/或OS会为您处理那些复杂的事情。但有一个例外,光栅内容(raster content)——需要提供高DPI光栅内容,并且应用程序代码需要正确处理这些内容。

窗口部件和绘图器

QPainter代码绝大多数情况下都和原来一样。我们来看看绘制渐变(gradient)的代码:

QRect destinationRect = ...
QGradient gradient = ...
painter.fillRect(rect, QBrush(gradient));

在高DPI显示器上,这个渐变在屏幕上的大小还是一样的,但是被填充了更多的(设备)像素。

绘制一个像素映射(pixmap)也是类似的:

QRect destinationRect = ...
QPixmap pixmap = ...
painter.drawPixmap(destinationRect, pixmap);

为了避免在高DPI显示器上出现缩放失真,像素映射必须包含足够的像素:两倍于destinationRect的宽和高。应用程序可以直接提供它们,也可以使用QIcon来管理不同的解析度:

QRect destinationRect = ...
QIcon icon = ...
painter.drawPixmap(destinationRect, icon.pixmap(destinationRect.size()));

QIcon::pixmap()已经被修改了,可以在高DPI系统中返回一个更大的像素映射。这种行为的改变会破坏现有的代码,所以它是由AA_UseHighDpiPixmaps这个应用程序属性来控制的:

qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);

在Qt 5.1中这个属性默认值是关闭的,但在未来的Qt发布中它很有可能默认为打开。

极端情况和devicePixelRatio

Qt的窗口部件有一些极端情况。在理想情况下,它一直使用QIcon,并且在绘制的时候会使用正确的像素映射,但是实际情况是Qt API经常直接生成和使用像素映射。当像素映射的大小被用来计算布局的几何信息时,会发生错误——如果一个像素映射已经是高分辨率的,那么在屏幕上它就不应该再占用更多的空间。

通过使用QPixmap::devicePixelRatio(),就能让200×200的像素映射实际占据100×100的设备无关像素。由QIcon::pixmap()返回的像素映射中devicePixelRatio已经设置好了。

例如QLabel就是一个“像素映射消费者”:

QPixmap pixmap2x = ...
pixmap2x.setDevicePixelRatio(2.0);
QLabel *label = ...
label->setPixmap(pixmap2x);

然后QLabel会除以devicePixelRatio来获得布局的大小:

QSize layoutSize = pixmap.size() / pixmap.devicePixelRatio();

与此类似的几种情况在Qt中都已经修复,并且应用程序代码在启用AA_UseHighDpiPixmaps之前也需要做类似处理。

下面几个Qt类中都提供了devicePixelRatio()的读写函数:

注释
QWindow::devicePixelRatio() 推荐使用的读写函数
QScreen::devicePixelRatio()
QGuiApplication::devicePixelRatio() 如果没有QWindow指针,请使用这个
QImage::[set]devicePixelRatio()
QPixmap::[set]devicePixelRatio()

文本

字体大小还可以和原来一样,会在高DPI显示中产生类似的大小(但会有一点小问题)。字体的像素大小是设备无关的像素大小。您在高DPI显示中永远不会得到太小的文本。

QGLWidget

OpenGL是在设备像素空间中工作的。例如,传递给glViewport的宽和高应该是设备像素。QGLWidget::resizeGL()中的宽和高也是设备像素的。

不管怎样,QGLWidget::width()实际上就是QWidget::width(),它返回的是设备无关像素的值。如果需要,用它来乘以widget->windowHandle()->devicePixelRatio()可以解决很多问题。

Qt Quick 2和控件

Qt Quick 2和Qt Quick控件可以直接使用。因为窗口部件的坐标是设备无关像素的。Qt Quick也有几个和光栅相关的极端情况,因为QML的Image元素是通过URL来指定图像源的,这样就避免了像素映射的传递。

Qt Quick控件

还有一个例外是OpenGL着色器(shader),它运行在设备像素空间中并且可以看到全分辨率。在通常情况下这没有什么问题,我们应该知道的一件重要的事情是,鼠标坐标是设备无关像素的,也许需要被转换成设备像素。

运行中的着色器效果实例

管理高解析度的光栅内容

正如我们所看到的,在缩放的情况下,光栅内容看起来会不够好,所以必须提供高解析度的内容。作为应用程序开发人员,您有两个选项:(请忽略“什么都不做”选项)

  • 使用高解析度版本替换现有光栅内容
  • 另外提供一份高解析度内容

第一个选项很简单,因为每个资源只有一个版本。可是您也许会发现(或者您的设计师会告诉您)像图标这样的资源只有在它被创建的那个特定解析度下看起来才最好。为了解决这个问题,Qt沿用了“@2x”这种图像文件名的方案:

foo.png
foo@2x.png

这样高解析度的内容和原来的一一对应。在需要的时候,“@2x”的版本会被QML的Image元素以及QIcon自动加载。

Image { source = “foo.png” }
QIcon icon(“foo.png”)

(对于QIcon请记住使用AA_UseHighDpiPixmaps)

试验性的跨平台的高解析度支持

QPA允许我们相对容易的完成跨平台的实现。Qt现在把这一问题分为三层:

  • 应用程序层(应用程序代码和使用QPA类的Qt代码)
  • QPA层(QWindow、QScreen、QBackingStore)
  • 平台插件层(QPlatform*子类)

简化一下,应用程序层是在设备无关像素空间中工作的,并不知道设备像素。平台插件是在设备像素空间中工作的,并不知道设备无关像素。QPA层在两者之间,基于一个由环境变量QT_HIGHDPI_SCALE_FACTOR指定的缩放因子进行转换。

实际上,这个情况还会更复杂一些,各层之间会有泄露的事情发生,并且在Mac和iOS下还会有一些例外情况。

代码在github上。最后是XCB下的Qt Creator的截屏:

DPI缩放的Qt Creator

QT_HIGDPI_SCALE_FACTOR=2缩放的Qt Creator

git format-patch 用法

git format-patch相对于git diff更便于操作,是更新的打包方式,应该采取这种打包方式。git diff打包的patch只能使用git apply处理。而git format-patch的补丁,可以应当使用git am命令。

基本用法

git format-patch xxxx.patch

第一种

format-patch可以基于分支进行打包,也可以基于上几次更新内容打包。

基于上几次内容打包

git format-patch HEAD^  有几个^就会打几个patch,从最近一次打起

git format-patch HEAD^^ 最近的二个patch内容

以下代码作用同上

git format-patch -1

git format-patch -2

git format-patch -1 -4    //可以打包版本2,3的patch。但是发现有时候会把最近4个包都打包出来,具体原因未知。

参考:http://leave001.blog.163.com/blog/static/16269129320126944238969/

关于分支,可以参考:http://www.cnblogs.com/y041039/articles/2411600.html

第二种

git format-patch -M origin/master

format-patch 命令依次创建补丁文件,并输出文件名。上面的 -M 选项允许 Git 检查是
否有对文件重命名的提交。我们来看看补丁文件的内容:

  1. $ cat 0001-add-limit-to-log-function.patch
  2. From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
  3. From: Jessica Smith <jessica@example.com>
  4. Date: Sun, 6 Apr 2008 10:17:23 -0700
  5. Subject: [PATCH 1/2] add limit to log function
  6. Limit log functionality to the first 20
  7. lib/simplegit.rb |
  8. 2 +-
  9. 1 files changed, 1 insertions(+), 1 deletions(-)
  10. diff –git a/lib/simplegit.rb b/lib/simplegit.rb
  11. index 76f47bc..f9815f1 100644
  12. — a/lib/simplegit.rb
  13. +++ b/lib/simplegit.rb
  14. @@ -14,7 +14,7 @@ class SimpleGit
  15. end
  16. def log(treeish = ‘master’)
  17. +
  18. command(“git log #{treeish}”)
  19. command(“git log -n 20 #{treeish}”)
  20. end
  21. def ls_tree(treeish = ‘master’)
  22. 1.6.2.rc1.20.g8c5b.dirty

如果有额外信息需要补充,但又不想放在提交消息中说明,可以编辑这些补丁文件,
在第一个 — 行之前添加说明,但不要修改下面的补丁正文,比如例子中的 Limit log
functionality to the first 20 部分。这样,其它开发者能阅读,但在采纳补丁时不会将
此合并进来。

应用patch

  1. $ git am 0001-limit-log-function.patch
  2. Applying: add limit to log function

你会看到它被干净地应用到本地分支,并自动创建了新的提交对象。

有时,我们也会遇到打不上补丁的情况。这多半是因为主干分支和补丁的基础分支相差太远,但也可能是因为某些依赖补丁还未应用。这种情况下,git am 会报错并询问该怎么
做:

  1. $ git am 0001-seeing-ifthis-helps-the-gem.patch
  2. Applying: seeing if this helps the gem
  3. error: patch failed: ticgit.gemspec:1
  4. error: ticgit.gemspec: patch does not apply
  5. Patch failed at 0001.
  6. When you have resolved this problem run “git am –resolved”.
  7. If you would prefer to skip this patch, instead run “git am –skip”.
  8. To restore the original branch and stop patching run “git am –abort”.

Git 会在有冲突的文件里加入冲突解决标记,这同合并或衍合操作一样。解决的办法也一样,先编辑文件消除冲突,然后暂存文件,最后运行 git am –resolved 提交修正结果

  1. $ (fix the file)
  2. $ git add ticgit.gemspec
  3. $ git am –resolved
  4. Applying: seeing if this helps the gem

如果想让 Git 更智能地处理冲突,可以用 -3 选项进行三方合并。如果当前分支未包含该补丁的基础代码或其祖先,那么三方合并就会失败,所以该选项默认为关闭状态。一般来
说,如果该补丁是基于某个公开的提交制作而成的话,你总是可以通过同步来获取这个共同祖先,所以用三方合并选项可以解决很多麻烦:

  1. $ git am -3 0001-seeing-ifthis-helps-the-gem.patch
  2. Applying: seeing if this helps the gem
  3. error: patch failed: ticgit.gemspec:1
  4. error: ticgit.gemspec: patch does not apply
  5. Using index info to reconstruct a base tree…
  6. Falling back to patching base and 3-way merge…
  7. No changes — Patch already applied.

像上面的例子,对于打过的补丁我又再打一遍,自然会产生冲突,但因为加上了 -3 选项,所以它很聪明地告诉我,无需更新,原有的补丁已经应用。
对于一次应用多个补丁时所用的 mbox 格式文件,可以用 am 命令的交互模式选项 -i,这样就会在打每个补丁前停住,询问该如何操作:

  1. $ git am -3 -i mbox
  2. Commit Body is:
  3. ————————–
  4. seeing if this helps the gem
  5. ————————–
  6. Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

在多个补丁要打的情况下,这是个非常好的办法,一方面可以预览下补丁内容,同时也可以有选择性的接纳或跳过某些补丁。
打完所有补丁后,如果测试下来新特性可以正常工作,那就可以安心地将当前特性分支合并到长期分支中去了

图片Premultiplied Alpha到底是干嘛用的

Premultiplied Alpha 这个概念做游戏开发的人都不会不知道。Xcode 的工程选项里有一项 Compress PNG Files,会对 PNG 进行 Premultiplied Alpha,Texture Packer 中也有Premultiplied Alpha 的选项。那么问题来了,Premultiplied Alpha 是什么呢?我被这个问题困惑了很久,之前搜到过 Nvidia的这篇文章,其实说的很清楚,只是当时有很多相关概念没搞清楚,所以没看懂。直到前几天读《Real Time Rendering》时终于搞懂了。

Alpha Blending

要搞清楚这个问题,先得理解Alpha通道的工作原理,如果你已经了解可以直接跳过。

最常见的像素表示格式是RGBA8888即 (r, g, b, a),每个通道8位,0-255。例如红色60%透明度就是 (255, 0, 0, 153),为了表示方便alpha通道一般记成正规化后的0-1的浮点数,也就是 (255, 0, 0, 0.6)。而 Premultiplied Alpha 则是把RGB通道乘以透明度也就是 (r * a, g * a, b * a, a),50%透明红色就变成了(153, 0, 0, 0.6)。

透明通道在渲染的时候通过 Alpha Blending 产生作用,如果一个透明度为 as 的颜色 Cs 渲染到颜色 Cd上,混合后的颜色通过以下公式计算,

Co=αsCs+(1αs)CdCo=αsCs+(1−αs)Cd

以60%透明的红色渲染到白色背景为例:

Co=(255,0,0)0.6+(255,255,255)(10.6)=(255,102,102)Co=(255,0,0)⋅0.6+(255,255,255)⋅(1−0.6)=(255,102,102)

也就是说,从视觉上,(255, 0, 0, 0.6)渲染到白色背景上 和 (255, 102, 102) 是同一个颜色。如果颜色以 Premultiplied Alpha 形式存储,也就是Cs已经乘以透明度了,所以混合公式变成了:

Co=Cs+(1αs)CdCo=Cs′+(1−αs)Cd

为什么要 Premultiplied Alpha 呢?

Premultiplied Alpha 后的像素格式变得不直观,因为在画图的时候都是先从调色板中选出一个RGB颜色,再单独设置透明度,如果RGB乘以透明度就搞不清楚原色是什么了。从前面的 Alpha Blending 公式可以看出,Premultiplied Alpha 之后,混合的时候可以少一次乘法,这可以提高一些效率,但这并不是最主要的原因。最主要的原因是:

没有 Premultiplied Alpha 的纹理无法进行 Texture Filtering(除非使用最近邻插值)。

以最常见的 filtering 方式线性插值为例,一个宽2px高1px的图片,左边的像素是红色,右边是绿色10%透明度,如果把这个图片缩放到1×1的大小,那么缩放后1像素的颜色就是左右两个像素线性插值的结果,也就是把两个像素各个通道加起来除以2。如果使用没有 Premultiplied Alpha 的颜色进行插值,那么结果就是:

((255,0,0,1)+(0,255,0,0.1))0.5=(127,127,0,0.55)((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)

如果绿色 Premultiplied Alpha,也就是 (0, 255 * 0.1, 0, 0.1),和红色混合后:

((255,0,0,1)+(0,25,0,0.1))0.5=(127,25,0,0.55)((255,0,0,1)+(0,25,0,0.1))⋅0.5=(127,25,0,0.55)

图片描述

从上面的图里第三个颜色是没有 Premultiplied Alpha 的混合结果,对比第四个 Premultiplied Alpha 后颜色的结果,显然第四个颜色更符合直觉,第三个颜色太绿了,因为绿色通道没有乘以透明度,所以在线性插值的时候占了过大的权重。

所以 Premultiplied Alpha 最重要的意义是使得带透明度图片纹理可以正常的进行线性插值。这样旋转、缩放或者非整数的纹理坐标才能正常显示,否则就会像上面的例子一样,在透明像素边缘附近产生奇怪的颜色。

纹理处理

我们使用的PNG图片纹理,一般是不会 Premultiplied Alpha 的。游戏引擎在载入PNG纹理后回手动处理,然后再glTexImage2D传给GPU,比如 Cocos2D-x 中的 CCImage::premultipliedAlpha:

cpp
  1. void Image::premultipliedAlpha() {
  2. unsigned int* fourBytes = (unsigned int*)_data;
  3. for (int i = 0; i &lt; _width * _height; i++) {
  4. unsigned char* p = _data + i * 4;
  5. fourBytes[i] = CC_RGB_PREMULTIPLY_ALPHA(p[0], p[1], p[2], p[3]);
  6. }
  7. _hasPremultipliedAlpha = true;
  8. }

而GPU专用的纹理格式,比如 PVR、ETC 一般在生成纹理都是默认 Premultiplied Alpha 的,这些格式一般是GPU硬解码,引擎用CPU处理会很慢。

总之 glTexImage2D 传给 GPU 的纹理数据最好都是 Multiplied Alpha 的,要么在生成纹理时由纹理工具 Pre-multiplied,要么载入纹理后由游戏引擎或UI框架 Post-multiplied。

原文地址:http://boundary.cc/2015/07/why-premultiplied-alpha/

透明像素-Premultiplied Alpha的秘密

我得承认题目有点标题党的意思,自从Flash播放器采用了BitmapData以来,Flash采用一种叫做Premultipled Alpha的技术来存储透明的像素。但是它还是有点…为了避免你觉得我啰里巴索你可以直接去检查示例程序, check out the demo right away 如果没看懂,呵呵……

“Premultiplied” alpha技术意味着不仅像素的Alpha信息存储在通道里,而且已经扩张到红,蓝,绿通道里,在实际应用中这意味着如果你保存着 #fff8000橙色,而将其透明度设置为50%,那么实际设置保存的是#80803f00.这意味着颜色通道中的任意像素值都不会比Alpha通道中的值来的大

这样做的原因是出于性能的考虑,图像处理算法在复合两张图片的时候总是需要将ALPHA通道的信息复合到各个颜色通道,因此如果你需要处理很多的图像复合时候,这样的做法就节省了很多的时间,而不需要对每个像素重新进行复合,正如我们所知道的Flash的日常处理中有很多时候都在处理复合,比如重合两张反锯齿的直线时就会有复合,有了这样的处理之后Flash的速度就很快了。

但是也有一个问题,像素是以32位的整数值存储的,也就是说每个通道有8位或者256种可能的值,另一方面像素的计算通常是以浮点数学的方式进行的,也就是说可能的值要远大的多,如果通常以浮点处理那很好,但是有些情况下你不得不将浮点的值回写到像素信息,也就是说比如43.7回写的时候就会圆整为44或者更糟的43回写到像素值里面

通常情况下这种小的误差不会对应用造成很大的麻烦,但是一旦你开始处理小的像素值,那么错误就会被基类放大,,例如如果你将一个像素的ALPHA值设置为16那么每一个像素都会被乘上一个因子16/256=0.0625,因此灰色像素就会变成128*0.0625=8 暗像素64就会变成4,但是稍微亮一点的像素如67也会变成4,注意没有小数这是圆整后的结果,你会发现颜色种类减少了,从原来的256×256×256减少到8*8*8,严重的问题

如果你将ALPHA值保持设置为8那么不会有太大的问题,如果你增大ALPHA值,那么灾难就出现了,大量的颜色信息由于进度原因会丢失,而且无法修复。

为了解释清楚到底是怎么回事我设置了一个Demo demo that visualizes the amount of information loss来解释。它先将图像的Alpha通道设置为一个选定值,然后再设置回255,你所观察到的现象正好说明了,当Alpha值很小的时候会有些海报斑驳效果,即使你将像素设置为254,你也会发现颜色丢失的现象(复选 观察数据丢失复选框)该复选框会对比颜色丢失和未丢失的情况,由于有的时候丢失不是很明显,DEMO将该丢失放大了以增强了对比度

那要怎样才能保存颜色信息呢,你只有慢慢来,效率和质量不可兼得么,将Alpha信息和像素分开存储,也就是说你维护着三张Bitmap一张存储颜色信息,一张存储Alpha值,第三章存储两者复合后的结果。

删除 GarageBand 留下的垃圾文件

GarageBand 基本上是我每次必删的一个 APP(无论是 Mac 还是 iOS 上),然并卵,Mac 的磁盘管理器上一看,他还是占用了 2.6G 的空间,使用清理工具或者系统自带的工具都没法删除。于是 Google 了一下,找到了 办法 ,删除如下文件即可:

Macintosh HD/Applications/GarageBand.app (1.16GB)
Macintosh HD/Library/Application Support/GarageBand (995MB)
Macintosh HD/Library/Application Support/Logic (880MB)
Macintosh HD/Library/Audio/Apple Loops (up to 10GB)*

make的-j命令(加速Linux程序编译)

项目越来越大,每次需要重新编译整个项目都是一件很浪费时间的事情。Research了一下,找到以下可以帮助提高速度的方法,总结一下。

  • 1. tmpfs

有人说在Windows下用了RAMDisk把一个项目编译时间从4.5小时减少到了5分钟,也许这个数字是有点夸张了,不过粗想想,把文件放到内存上做编译应该是比在磁盘上快多了吧,尤其如果编译器需要生成很多临时文件的话。

这个做法的实现成本最低,在Linux中,直接mount一个tmpfs就可以了。而且对所编译的工程没有任何要求,也不用改动编译环境。

mount -t tmpfs tmpfs ~/build -o size=1G

用2.6.32.2的Linux Kernel来测试一下编译速度:

用物理磁盘:40分16秒

用tmpfs:39分56秒

呃……没什么变化。看来编译慢很大程度上瓶颈并不在IO上面。但对于一个实际项目来说,编译过程中可能还会有打包等IO密集的操作,所以只要可能,用tmpfs是有益无害的。当然对于大项目来说,你需要有足够的内存才能负担得起这个tmpfs的开销。

  • make -j

既然IO不是瓶颈,那CPU就应该是一个影响编译速度的重要因素了。

用make -j带一个参数,可以把项目在进行并行编译,比如在一台双核的机器上,完全可以用make -j4,让make最多允许4个编译命令同时执行,这样可以更有效的利用CPU资源。

还是用Kernel来测试:

用make: 40分16秒

用make -j4:23分16秒

用make -j8:22分59秒

由此看来,在多核CPU上,适当的进行并行编译还是可以明显提高编译速度的。但并行的任务不宜太多,一般是以CPU的核心数目的两倍为宜。

不过这个方案不是完全没有cost的,如果项目的Makefile不规范,没有正确的设置好依赖关系,并行编译的结果就是编译不能正常进行。如果依赖关系设置过于保守,则可能本身编译的可并行度就下降了,也不能取得最佳的效果。

  • ccache

ccache用于把编译的中间结果进行缓存,以便在再次编译的时候可以节省时间。这对于玩Kernel来说实在是再好不过了,因为经常需要修改一些Kernel的代码,然后再重新编译,而这两次编译大部分东西可能都没有发生变化。对于平时开发项目来说,也是一样。为什么不是直接用make所支持的增量编译呢?还是因为现实中,因为Makefile的不规范,很可能这种“聪明”的方案根本不能正常工作,只有每次make clean再make才行。

安装完ccache后,可以在/usr/local/bin下建立gcc,g++,c++,cc的symbolic link,链到/usr/bin/ccache上。总之确认系统在调用gcc等命令时会调用到ccache就可以了(通常情况下/usr/local/bin会在PATH中排在/usr/bin前面)。

继续测试:

用ccache的第一次编译(make -j4):23分38秒

用ccache的第二次编译(make -j4):8分48秒

用ccache的第三次编译(修改若干配置,make -j4):23分48秒

看来修改配置(我改了CPU类型…)对ccache的影响是很大的,因为基本头文件发生变化后,就导致所有缓存数据都无效了,必须重头来做。但如果只是修改一些.c文件的代码,ccache的效果还是相当明显的。而且使用ccache对项目没有特别的依赖,布署成本很低,这在日常工作中很实用。

可以用ccache -s来查看cache的使用和命中情况:


可以看到,显然只有第二编次译时cache命中了,cache miss是第一次和第三次编译带来的。两次cache占用了81.7M的磁盘,还是完全可以接受的。

  • distcc

一台机器的能力有限,可以联合多台电脑一起来编译。这在公司的日常开发中也是可行的,因为可能每个开发人员都有自己的开发编译环境,它们的编译器版本一般是一致的,公司的网络也通常具有较好的性能。这时就是distcc大显身手的时候了。

使用distcc,并不像想象中那样要求每台电脑都具有完全一致的环境,它只要求源代码可以用make -j并行编译,并且参与分布式编译的电脑系统中具有相同的编译器。因为它的原理只是把预处理好的源文件分发到多台计算机上,预处理、编译后的目标文件的链接和其它除编译以外的工作仍然是在发起编译的主控电脑上完成,所以只要求发起编译的那台机器具备一套完整的编译环境就可以了。

distcc安装后,可以启动一下它的服务:

/usr/bin/distccd --daemon --allow 10.64.0.0/16

默认的3632端口允许来自同一个网络的distcc连接。

然后设置一下DISTCC_HOSTS环境变量,设置可以参与编译的机器列表。通常localhost也参与编译,但如果可以参与编译的机器很多,则可以把localhost从这个列表中去掉,这样本机就完全只是进行预处理、分发和链接了,编译都在别的机器上完成。因为机器很多时,localhost的处理负担很重,所以它就不再“兼职”编译了。

export DISTCC_HOSTS=&quot;localhost 10.64.25.1 10.64.25.2 10.64.25.3&quot;

然后与ccache类似把g++,gcc等常用的命令链接到/usr/bin/distcc上就可以了。

在make的时候,也必须用-j参数,一般是参数可以用所有参用编译的计算机CPU内核总数的两倍做为并行的任务数。

同样测试一下:

一台双核计算机,make -j4:23分16秒

两台双核计算机,make -j4:16分40秒

两台双核计算机,make -j8:15分49秒

跟最开始用一台双核时的23分钟相比,还是快了不少的。如果有更多的计算机加入,也可以得到更好的效果。

在编译过程中可以用distccmon-text来查看编译任务的分配情况。distcc也可以与ccache同时使用,通过设置一个环境变量就可以做到,非常方便。

总结一下:

  • tmpfs: 解决IO瓶颈,充分利用本机内存资源
  • make -j: 充分利用本机计算资源
  • distcc: 利用多台计算机资源
  • ccache: 减少重复编译相同代码的时间

这些工具的好处都在于布署的成本相对较低,综合利用这些工具,就可以轻轻松松的节省相当可观的时间。上面介绍的都是这些工具最基本的用法,更多的用法可以参考它们各自的man page。