项目简介

OBS - Free and open source software for live streaming and screen recording(OBS是一款开源的用于录屏直播的工具软件)。

旧版的OBS只能支持Windows,目前已经停止开发。作者为了支持Windows/Mac/Linux重写了整个软件,项目地址为obs-studio in Github

新版的OBS的目标有以下几点:

  1. Make it multiplatform.(跨平台支持)
  2. Separate the application from the core, allowing custom application of the core if desired, and easier extending of the user interface.(模块化、易扩展)
  3. Simplify complex systems to not only make it easier to use, but easier to maintain.(简化系统,使其易用易维护)
  4. Make a better/cleaner code base, use better coding standards, use standard libraries where possible (not just STL and C standard library, but also things like ffmpeg as well), and improve maintainability of the project as a whole.(尽量利用其他开源软件成果)
  5. Implement a new API-independent shader/effect system allowing better and easier shaders usage and customization without having to duplicate shader code.(实现独立于API的shader/effect系统)
  6. Better device support. (更好的支持有录屏需求的设备)

OBS项目的语言分布:

  1. C: 57.6%
  2. C++: 36.3%
  3. Objective-C/Objective-C++: 4%
  4. others: 3%

OBS代码主要包含这些部分:

  1. libobs: 核心代码,定义项目框架以及核心API,主要用C语言编写。
  2. UI: 界面代码,采用C++的QT框架,开发出适用三大平台的界面。
  3. plugins: 插件代码,可独立编译成dll(windows平台)或so(*nix平台),包含Source(录屏输入源)、Output、Service(各种流播服务)等全部被定义为插件。
  4. libobs-d3d11: 基于D3D的图形子系统,主要用在Windows系统。
  5. libobs-opengl: 基于opengl的图形子系统,主要用在*uix系统。

OBS软件功能概述

OBS项目工程中以场景组的方式呈现给用户,可以自由设置场景、输入源、效果处理,配置直播服务。
2016121483032obs_ui.png

OBS项目工程结构

OBS项目中一个工程结构如下201612147022obs_project_stucture.png

一个场景组包含多个场景,OBS直播的时候是把整个场景流播给用户,那为什么需要多个场景?因为播主在直播时有快速切换场景的需要,所以播主需要在直播前编辑好多个场景(比如纯游戏场景;游戏+头像;解说;休息场景等),然后直播的过程中可以根据不同的需要快速切换。

OBS场景的转场

OBS中的转场,是场景切换时的动画效果,目前支持 Fade和Switch等多种效果。

OBS输入源的种类

一个场景可以包含多个输入源,一个直播工具可以支持的输入源种类反应了其强大性。OBS支持 输入源种类如下
2016121460496obs_source_type.png

OBS输入源的效果设置

针对每个输入源可以增加各种滤镜效果,以下列出我觉得最实用的几种:
2016122283761OBS_Effect.png

  1. 音频效果:
    1. Video Delay: 设置延迟时间,用来处理音视频不同步的的场景。
    2. Noise Suppression: 噪音抑制
    3. Gain: 音频增益
    4. Noise Gate: 噪声门,把小噪音去掉
  2. 视频效果:
    1. Crop: 就是最实用的Crop,不过OBS里不能用鼠标拖拽来控制Crop区域,略显不便
    2. Chroma Key: 如果有绿幕背景,可以用来去背景,在摄像头的输入源中最常用。
    3. Image Mask: 打水印
    4. Scroll: 滚动效果,在一些浏览器的输入源上最实用。

OBS工作室模式

2016122215478OBS_StdioMode.png
左边是预览界面,可以进行编辑。右边是正在直播的界面。中间是把预览界面切到直播界面的各种转场效果。

一般比较专业的直播都是使用这个模式,可以在预览界面编辑好画面之后再推送到直播画面。

OBS插件系统

OBS项目中把除了核心框架以及渲染系统之外的 部件全部抽象成了Module,一个或多个Module最后封装到插件中(以dll或so的形式),只要把插件放入特定的目录即可被主程序使用。

Mac版OBS的插件目录在/Applications/OBS.app/Contents/Resources/obs-plugins,其中

  1. mac-avcapture.so 对应Mac的视频捕获设备
  2. mac-capture.so 对应屏幕捕获 和 窗口捕获
  3. mac-syphon.so 对应注入捕获游戏画面

OBS插件定义

一个典型的OBS插件代码包含三个部分:

  1. 插件定义 -> plugin-main.c
  2. 编译打包 -> CMakeList.txt
  3. 内部实现代码 -> XX.c/YY.c …

下面以mac-capture.so插件为例来看看它的插件定义代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <obs-module.h>

OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("mac-capture", "en-US") //多语言支持

extern struct obs_source_info coreaudio_input_capture_info; //输入源1
extern struct obs_source_info coreaudio_output_capture_info;//输入源2
extern struct obs_source_info display_capture_info;//输入源3
extern struct obs_source_info window_capture_info;//输入源4

bool obs_module_load(void) //注册支持的输入源
{

obs_register_source(&coreaudio_input_capture_info);
obs_register_source(&coreaudio_output_capture_info);
obs_register_source(&display_capture_info);
obs_register_source(&window_capture_info);
return true;
}

可以看出这个插件定义了四个输入源,这里你可能有个疑问,为什么不是一个插件 对应 一个输入源,因为功能相近的输入源集成到一个插件里可以减少冗余代码。

所以OBS中插件可以定义为 包含 一个 或 多个 输入(或 输出/编码/服务)模块的动态库代码。
2016121549583obs_module_define.png
任意一个开源库比如FFmpeg,经过OBS统一的接口定义封装即可编译成OBS的一个插件为OBS系统所用。

OBS插件加载流程

插件系统大体都有一个类似的套路,OBS的也不例外。简单来说就是定义插件存放在特定目录,在程序启动时,动态加载所有的插件(存储为对象或一系列函数指针),存储在字典 或者 链表这样的数据结构里。

下面来详细分析一下OBS中插件加载流程:
2016121573630OBS_import_module-2.png

然后以mac-capture.so中的display_capture_info,来看看它的结构定义,可以看出它主要定义了id、type、name 以及一些接口API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct obs_source_info display_capture_info = {
.id = "display_capture",
.type = OBS_SOURCE_TYPE_INPUT,
.get_name = display_capture_getname,

.create = display_capture_create,
.destroy = display_capture_destroy,

.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
OBS_SOURCE_DO_NOT_DUPLICATE,
.video_tick = display_capture_video_tick,
.video_render = display_capture_video_render,

.get_width = display_capture_getwidth,
.get_height = display_capture_getheight,

.get_defaults = display_capture_defaults,
.get_properties = display_capture_properties,
.update = display_capture_update,
};

其中需要注意一下get_properties这个接口,这个接口是干啥用的?顾名思义是获取模块的属性数据,按我的理解 UI层可以利用这个属性数据来构建这个模块对应的界面,并设置这个模块的属性参数。

OBS视频处理流程

视频渲染输出流程

OBS视频渲染和输出是系统的核心流程,我们以Mac桌面录制输入,以及ffmpeg输出为例来分析一下整个流程(多路输入 和 多路输出道理也是类似的), 图中为了简单起见忽略了输出编码流程仅包含非编码流程。
2016122060378OBS_Video_Render_2.png
可以看出OBS创建了两个线程,一个用于显示渲染,另一个用于编码输出。
渲染部分最终调用的是 输入模块里的渲染代码,而编码输出部分最终也是调用 输出模块的代码。

另外在渲染线程中 也负责把图形系统的数据 拷贝到 输出数据的缓存中,以便于输出线程进行处理

视频输出数据结构分析

OBS的核心数据结构定义在libobs/obs-internal.h中 主体结构为obs_core如下图所示(仅保留的主要的数据结构)
2016121631085OBS_CoreVideo-2.jpg

右下方的video(结构为video_output)用在输出模块的raw_video接口进行处理,把 输出数据中的cache转成实际的输出,以下是video_output详细数据结构:
2016121665597OBS_Video_detail_2.jpg

OBS音频处理流程

OBS音频处理是在一个线程中完成了先渲染后输出的过程。而视频处理则是 分别开了渲染线程 和 输出线程。

具体流程如下, 在输出函数中在判断是否需要编码,再调用对应的非编码流程 或 编码流程:
2016122068454OBS_Audio_Render.png

OBS图形系统架构

OBS的图形系统主要负责 场景的渲染、场景的切换、以及各种输入源的音视频效果的处理,属于OBS的核心之一。
通过使用软件以及视频渲染流程的分析 得到OBS图形系统的大体的逻辑关系。

2016122324058OBS_Graphic_Structure_1.png

针对图形系统主要分析以下三个问题:

  1. 多滤镜叠加的渲染处理。
  2. 滤镜和转场效果的实现与集成。
  3. 图形API的封装。

单个场景的渲染流程

场景(Scene)也被封装成输入源(Source)的一种,所以UI层只要把当前的场景取出来,调用它的obs_source_video_render即可。

2017011791095OBS_sceneRender.png

在场景内部会渲染其包含的renderitem(也就是实际的输入源),比如前一个图所展示的游戏录制、桌面录制输入源等。

多个滤镜叠加的输入源渲染流程

这部分分析了很长时间一直没看懂,主要有两个原因:

  1. 之前不熟悉OpenGl的渲染流程,所以搞不懂滤镜的渲染流程。
  2. 这部分的逻辑比较绕,没分析出多个滤镜是怎么叠加渲染的。

前段时间花了点时间好好学习了一下OpenGl(仅仅学习和音视频处理相关的章节),写了一些demo

现在再来分析这部分相对轻松一些,简述一下:当渲染带滤镜的输入源时,会先渲染它最后一个滤镜,然后在这个滤镜的渲染代码又会调用渲染前一个滤镜,最后调用第一个滤镜的渲染代码。

在第一个滤镜的渲染代码里 直接渲染 调用输入源的渲染流程,然后生成texture。

每个滤镜都在前一个滤镜渲染生成的texture的基础渲染生成新的texture。

流程图如下:
2017011799373OBS_filterRender.png

滤镜和转场效果的实现与集成

OBS项目中,滤镜和转场效果都被抽象成插件。

以Mac版OBS为例:

  1. 所有的滤镜都在obs-filters.so这个插件里;
  2. 所有的转场效果都在obs-transitions.so里;

滤镜和转场其实分析起来是类似的,所以后续的暂时以滤镜为例来加以说明。

首先如果滤镜个数太多,拆分到两个插件里是没问题的。不过OBS项目中全部集中在一个插件里。

每个滤镜其实都被定义成了输入模块,以obs_source_info定义暴露API,以crop_filter为例见如下定义,唯一和普通输入模块不同的是类型定义(.type)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct obs_source_info crop_filter = {
.id = "crop_filter",
.type = OBS_SOURCE_TYPE_FILTER,
.output_flags = OBS_SOURCE_VIDEO,
.get_name = crop_filter_get_name,
.create = crop_filter_create,
.destroy = crop_filter_destroy,
.update = crop_filter_update,
.get_properties = crop_filter_properties,
.get_defaults = crop_filter_defaults,
.video_tick = crop_filter_tick,
.video_render = crop_filter_render,
.get_width = crop_filter_width,
.get_height = crop_filter_height
};

细心观察每个滤镜的代码组成发现都是一个套路,主要由两部分组成:

  1. XXX.c (C代码,用于暴露API以及例行处理)
  2. XXX.effect (自定义文件,实际效果处理逻辑)

关于这个effect留到后续讲解。

看到这里得出一个结论,OBS项目要增加新的滤镜效果只要编写对应的XXX.c 和 XXX.effect,放到对应的插件以输入模块的API暴露出来,并注册就可以了。

图形API的封装处理

目前OBS系统的图形API包括OpenGl以及d3d11:

  1. 在图形库加载层利用了Multi-Language GL/GLES/EGL/GLX/WGL Loader-Generator对不同平台加载图形库代码进行了封装。
  2. 在API调用层面也进行了抽象统一,具体可以查看 libobs/graphics/graphics-imports.c的定义。
  3. 自定义了效果描述文件 XXX.effect,这样就不用针对OpenGl和d3d11写两遍Shader。

我们以chroma-key-filter为例分析一下它的创建流程:
2016122616450OBS_filter_create.png

  1. 其中AddNewFilter是在界面中触发的添加效果的功能。
  2. ep_parse把xxx.effect配置文件解析成对应的配置结构。
  3. ep_compile把对应的配置结构解析 效果数据结构。

effect文件的作用可以参考程序中的注释

1
2
3
4
5
6
7
8
9
10
/*
* Effects introduce a means of bundling together shader text into one
* file with shared functions and parameters. This is done because often
* shaders must be duplicated when you need to alter minor aspects of the code
* that cannot be done via constants. Effects allow developers to easily
* switch shaders and set constants that can be used between shaders.
*
* Effects are built via the effect parser, and shaders are automatically
* generated for each technique's pass.
*/

effect文件包括这几个部分:
2016122616242effect_file_structure.png

pass对应 vertex_shader 和 pixel_shader

technique 对应一个具体效果的渲染设置,包含多个pass

effect文件包含多个technique渲染设置、可以共享文件中的参数和函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SolidVertInOut VSSolid(SolidVertInOut vert_in)
{
SolidVertInOut vert_out;
vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj);
return vert_out;
}

float4 PSSolid(SolidVertInOut vert_in) : TARGET
{
return color;
}

technique Solid
{
pass
{
vertex_shader = VSSolid(vert_in);
pixel_shader = PSSolid(vert_in);
}
}

effect文件支持基本的C语法,支持宏定义和include包含其他文件,由libobs/util中的cf-lexer.c和cf-parser.c提供解析支持。

支持OBS插件

OBS的插件是在OBS项目定义的比较宽泛,插件的范畴包括 整个录屏、处理、推流 中的各个功能模块.

如果我们的软件中可以直接支持OBS插件,就可以节省大量的开发、测试的时间。但由于我们的程序框架和OBS的完全不同,要如何支持OBS项目的插件呢?

想了两种方法,并尝试分析一下优缺点。

支持OBS的方法分析

二进制级别的支持

顾名思义,就是把OBS的插件直接放到我们程序的相应目录就可以用。这种方式下维护、更新、新增 OBS插件 代价是最小的。也是我心中理想的支持方式。

但是以这种方式支持的遇到较大困难。先看OBS项目中的代码的各个模块:

  1. libobs: 核心代码,定义项目框架以及核心API,主要用C语言编写。
  2. UI: 界面代码,采用C++的QT框架,开发出适用三大平台的界面。
  3. plugins: 插件代码,可独立编译成dll(windows平台)或so(*nix平台),包含Source(录屏输入源)、Output、Service(各种流播服务)等全部被定义为插件。
  4. libobs-d3d11: 基于D3D的图形子系统,主要用在Windows系统。
  5. libobs-opengl: 基于opengl的图形子系统,主要用在*uix系统。

我们想要支持plugins中的插件,

但是plugins中的插件要依赖libobs,

而libobs又要依赖libobs-opengl 和 QT界面库。

也就是除非 我们的项目支持基于OBS项目改写,否则这种支持方式的不太现实。

代码级别的支持

这是退而求其次的方式,简单的说就是把OBS的插件代码扣出来,确保其不依赖于libobs,然后集成我们的项目中。这种方式每支持一个插件都存在集成的工作量,也可能会引入Bug,不过不失为一个较为可行的方案。

OBS项目编译

尝试了在Mac平台上编译OBS项目,还比较顺利。具体可以参考install help

有个小问题,在cmake后报错提示无法找到QT5的cmake模块。需要给cmake指定一下QT5的安装目录,以我的安装目录为例,命令如下:

1
cmake .. -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.3.1/

cmake命令我是不熟悉的,不过看了这篇文章也基本懂了。

OBS项目最后的编译结果如下:

201702058452OBS_Compile.png

主要有三个目录:

  1. bin: 主程序
  2. data: 国际化资源 以及 视频effect效果资源
  3. obs-plugin: 插件编译结果

使用otool -L分析中其中主要动态库和插件(仅以mac-capture为例)的依赖关系如下:

2017020524659OBS_dependency.png

前面在代码层面分析直接二进制支持OBS插件感觉很困难。但这里基于编译后的动态库依赖关系分析好像又有一定可行性。我们把mac-capture.so、libobs.0.dylib、ffmpeg独立出来,去掉OBS主程序和QT等库,自己写代码来调用libobs.0.dylib提供的功能,以此直接支持OBS的插件。

后续进行完相关的实验,看看到底是否可行,再来补充。

模块列表

OBS项目中类型为OBS_SOURCE_TYPE_INPUT是我们可以考虑优先支持的模块。以下是功能说明。

插件 子模块 功能描述
mac-capture coreaudio_input_capture 音频输入获取
coreaudio_output_capture
display_capture 桌面获取
window_capture 窗体获取
mac-avcapture av_capture 摄像头获取
mac-syphon syphon 程序注入获取界面
obs-ffmpeg ffmpeg_source ffmpeg输入源
obs-browser browser_source 浏览器输入
text-freetype2 freetype2_source text输入
decklink decklink-input
image-source image_source 图片输入
slideshow
vlc-video vlc_source vlc输入

mac-syphon插件分析

mac-syphon是OBS项目中用于获取游戏画面(仅用于mac平台)的插件,十分重要。下面来分析一下它的实现原理。

首先mac-syphon是OBS的输入源插件,所以遵循OBS的插件API设定,具体可以查看OBS插件分析章节的介绍。
mac-syphon内部其实组合了多个开源项目功能来完成:获取游戏画面,并展示到OBS界面上的功能。

我们以OBS获取MineCraft这个游戏的画面为例,来看一张总的实现原理图:
2017010524004OBS_SyphonInject.png

简单解释一下这个过程:

2017010568862OBS_SyphonInject_flow.png

注入游戏进程的方法这里用的是Scripting Additions的方式,这是macOS独有的技术,windows上肯定要用其他方式,到时候再单独研究。除了注入方式的区别,其他流程Win和Mac平台应该类似的。

另外由于注入的函数中替换的是OpenGL的渲染API,所以这个插件支持的游戏必然是使用OpenGL渲染的。假如某个程序或游戏不使用OpenGL则无法注入。

ScriptingAdditions

ScriptingAdditions就是macOS中Applescript中一个技术,不太好解释,反正它的作用就是帮助注入到游戏进程里,直接上两个文档:

  1. Scripting Additions for Mac OS X
  2. Open Scripting Architecture

mach_override

这里其实利用了两个项目jrswizzlemach_override,功能就是把游戏进程中的flushBuffer替换为自己写的flushBufferSyphon,把orig_CGLFlushDrawable替换为CGLFlushDrawableOverride,从而实现把自己写的功能注入到游戏的渲染API中。

Syphon

Syphon项目是一套传输图形画面的Client/Server框架。

项目还提供了Client/Server Demo可以很方便的测试画面传输的功能。

Syphon Inject

SyphonInject项目组合Syphon的功能以及注入游戏的功能,提供了一个Demo。

下图中我用Syphon Inject注入到Dota2游戏,然后把界面传送给Client Demo

2017010588377SyphonInject_1.png

2017010548405SyphonInject_2.png

SyphonInject编译注意事项: SyphonInject项目早期利用mach_inject项目进行注入,后期已经修改为ScriptingAdditions方式注入。最新的代码没有依赖mach_inject,所以可以把mach_inject项目依赖去掉再编译。

最后OBS项目其实上述项目组合,用插件的API包装成了mac-Syphon插件。

mac-capture插件分析

mac-capture插件是OBS项目中对应mac平台的 屏幕界面获取、窗口界面获取、输入音频获取、输出音频获取 四大模块的具体实现。

obs模块的具体结构和API不再列出,详细情况可查看之前的文档,这里着重分析模块内部的功能实现。

mac-display模块

主要利用Quartz Display Services获取界面图像数据转换成texuture提供给主程序。

其中调用CGDisplayStreamCreateWithDispatchQueue接口获取的显示界面图像数据,数据结构是IOSurfaceRef。具体用法可以查看上述Quartz Display Services链接文档。

主要的实现流程如下图:
2017011827400OBS_macDisplay.png

mac-window-capture模块

获取其他程序窗体界面的模块,也是利用了Quartz Display Services。

使用CGWindowListCreateImage接口,通过windowID把对应程序的界面以 CGImage 的形式返回,直接放到输出的Cache里。

这里有个疑问,模块没有实现.video_render这个渲染API。可能和模块的output_flags设置的是异步有关。
OBS_SOURCE_ASYNC_VIDEO异步的渲染流程可能未放到模块内部实现。

mac-audio模块

音频模块包括两个,音频输入模块 和 音频输出模块。

音频输入设备比如 iMac上自带的外置麦克风;音频输出模块 比如 插入的耳机等。

由于mac平台限制,无法直接录制 音频输出设备的声音。比如我要录制浏览器上youtube视频的声音,在不借助第三方程序的情况下是做不到。使用OBS也做不到。

不过利用第三方开源程序soundflower可以解决这个问题。在安装soundflower之后,OBS可以设置 音频输出捕获模块,设备选择soundflower。同时系统声音输出设备 也选择为soundflower。
2017011845343OBS_audioOutput.png
2017011866291OBS_audioOutput1.png

这样系统的任何程序发出的声音就会先经过soundflower,然后被OBS捕获。

mac-audio中的音频输入模块 和 输出模块 实现流程基本相同,如下所示:
2017011826851obs_macAudio.png

基本原理是 在初始化模块的时候 使用系统接口AudioObjectAddPropertyListener注册音频的回调函数。然后在回调函数中完成音频渲染以及输出音频数据缓存。

注释和共享

亲密关系

吸引力

  • 要进一步探讨的微妙之处是,某些相像或许十分重要,而其他的相像 (或者相异) 可能无关痛痒。尤其是,如果伴侣在一些重要的议题上能和我们看法一致,这将特别具有奖赏价值。宗教就是这样一个议题:如果伴侣双方在宗教上都非常虔诚,共同的宗教信仰就非常有满足意义;而如果双方都不是积极的信徒,宗教上的相像就没什么影响,甚至有分歧也不打紧 (Lutz-Zois et al., 2006)。

    • 两个人三观要相同
  • “相异”还能相吸的一种方式:互朴性

    • 一个愿打一个愿挨

社会认知

  • 另外来看,如果人们了解伴侣各方面的情况,但却能以一种善意、大度的方式来进行诠释,这样的“错觉”就对亲密关系十分有益。

  • 此外,保护自己免遭幻想破灭的聪明方法是:随着对伴侣了解程度的增加,不断调整自己对理想伴侣的期望,这样,对伴侣的期望标准就能切合伴侣的现状 (Fletcher et al., 2000a)。

  • 人们常常能深切地感受到影响自己行为的外部压力,因而对自己行为的解释容易作出外部归因。但他们注意不到同样的环境也会影响他人,从而在解释他人的行为时,常常归因于他们的内部原因,如意图或性格。

  • 即使苦闷的夫妻彼此示好,但双方都会认为对方的体贴只不过是消极常态中短暂的、不具代表性的片刻安宁。当善意被看做是偶然的,伤害被看做蓄意的,亲密关系就很难得到满足。

  • 不良的归因方式会引起更多的纠纷和降低解决问题的效率,从而导致了那些本可避免的失落和不满。

  • 不过,在相互依赖更多、更投入的人际关系 (如婚姻) 中,自我证实的动机居于主导 (这种现象叫婚姻转变),人们需要支持他们自我概念的反馈 (Swann et al.,1994) (见图4.5)。

  • 设想有位男士把从妻子那里得到的表扬都解读为过誉之词。虽然这些表扬起初让他觉得乐观而幸福,但如果他最后判断妻子只不过是言不由衷…或者干脆认为妻子是个傻子,正面光辉就会慢慢消退。无论哪种情况,从自己非常熟悉的人那里得到了过分称赞的评价,都会使自己不安、并认为对方很虚伪 (Swann, 1996,p_118) 。

  • 显然自恋者结交的都是相当糟糕的伴侣,但要一开始就看穿他们却非常困难(Hotchkiss,2003)。他们的自信早先是能打动人的,常常要花很多时间才能认识到他们是多么自私、小气和暴躁。所以自恋者常以“致命的吸引”(fatalattraction)形式出现,一开始或许具有吸引力,但长期来看却是要人命的(Brunelletal.,2004)。这就给人们带来了挑战,在判断未来的亲密伴侣时要尽可能地运用辨别力和洞察力。

  • 举一点来说,我们都知道自己的好友和爱人已经喜欢上我们,所以营造迷人形象以贏得他们赞许的动机不足。也可能是因为他们十分了解我们,要改变他们的看法比较困难。不过,还有可能仅仅是因为我们变懒了。要表现出最好的言谈举止需要专心和努力。有礼貌的举止通常意味着某种形式的自我约束。在已经了解并喜爱我们的人身边,我们可以放松,无拘无束。但这也意味着人们对亲密伴侣常比对认识的其他人更为坦荡不羁 (Miller, 1997b)。

  • 一般而言,关系越亲近,人们越把好友的形象当成自己的,只要有可能就尽量美化好友的形象。

  • 有些人或许很难被人了解,但也有些人却善于观察别人。具备优秀社交技能的人往往擅长于评价和判断他人 (Letzring, 2008),这可能是因为他们的情绪智力 (e-motional intelligence) 很高。情绪智力指人们觉知、利用、理解和管理情绪的能力 (Salovey & Grewal, 2005)。如果人们有较高的情绪智力,就能驾轻就熟地调控自己的情感,从而很少在遭受打击和挫折时反应过度。他们还能敏感地体验到别人的感受,所以他们的人际交往更加满意和亲密 (Mirgain & Cordova, 2007)

友谊

  • 当同居的情侣有一方正在准备紧张的律师资格考试时,双方把各自给予和得到的支持都记录下来,结果发现最能减少应试者焦虑的,是情侣提供而应试者并未觉察到的帮助 (Bolger et al., 2000)。

  • 当受援者觉察到有形的支持时,如果这种支持能切合受援者当前的需要和目标就会更加有效。

  • 当受援者觉察到有形的支持时,如果这种支持能切合受援者当前的需要和目标就会更加有效。另一项研究考察了备考律师资格的学生,结果发现物质支持——比如情侣为之下厨烹调——有帮助作用,而感情支持只会使应试者更加焦虑 (Shrout et al., 2006)。

  • 事实上,如果夫妻双方没有共同的朋友,这样的婚姻通常很艰难。

  • 只要我们活着,友谊就是无价之宝。

爱情

  • 年轻人应该离开父母,自由地恋爱,自主地决定婚姻,并把恋人带回家与父母碰面,这样一种恋爱婚姻观,许多地方的人现在仍认为这是一种荒谬之极的婚恋观 (Macdonald & Jessica, 2006)。

  • 这些研究表明肾上腺素增强了人们的爱情体验。

  • 对于成人,爱人的触碰会刺激催产素的释放,尤其在性高潮时会释放大量的催产素;催产素可能正是夫妻在做爱之后会感到放松和嗜睡的原因之一 (Floyd, 2006)。

  • 浪漫的夫妻一起参加新奇、兴奋的活动会让他们彼此更加相爱

  • 浪漫因新奇、神秘和危险而繁盛;却因了解熟识而消亡。

  • 就浪漫的爱情而言,当伴侣变得熟悉时大脑可能根本无法产生足够多的多巴胺,所以即使你的伴侣能一如既往地完美,你也不能同样地被唤醒。

  • 当爱情关系变得重复、单调和沉闷时就会止步不前,并非一出现坏事情就会发生厌倦,而是在婚姻生活变得没有情趣、难以让人兴奋或者没有挑战性时才会滋生厌倦 (Harasymchuk & Fehr, 2007)。

  • 这就是你的爱情策略。享受激情,但不要把它作为维持爱情关系的基础。培养与爱人之间的友谊。努力保持新鲜感;把握住每一个与配偶共同进行新奇探索的机会

压力与紧张

  • 的确,那些坚持认为“相异相吸”的人可能会得到一些重大教训,只要他们与差异显著的人生活在一起,相异只会增加摩擦,而不会让关系一帆风顺。

  • 批评者的评论或行为所要表达的内容并不重要,要紧的是被批评者把这种行动诠释为不公正和吹毛求疵。伴侣一方提出如何使用洗碗机效率更高,这本来是一个温和的建议,但如果伴侣另一方认为这一建议是不必要的批评,就会受到伤害并引起冲突。

  • 我们不要低估公平争斗和进行“优质”争斗的难度。这需要自律和对自己伴侣真正的关爱。但正面结果通常值得努力。因此,冲突并不是可怕的问题,而是具有挑战性的机遇——理解自己和伴侣的机会,个体亲密关系变得更满意更亲密的契机。

注释和共享

macOS基于Unix系统,日常使用总会用到一些Terminal命令用来提高工作效率,或更好的完成任务。

开发类

  • 查看Swift真正方法:

xcrun swift-demangle SYMBOL xxx

  • 检查执行程序或库的依赖以及依赖路径

otool -L <Path>

  • 动态库dylib无法加载的原因调试

printf(dlerror());

  • 检查执行程序或库的导出函数

nm -m <Path>

  • 查看使用了XPC的程序

find /Applications/ -name \*.xpc >> xpc.txt

  • 卸载kext脚本

sudo kextunload /Library/Extensions/xxx.kext

sudo rm -rf /Library/Extensions/xxx.kext

  • 检查程序是否签名

codesign -vvv <Path>/xxx.app

  • 查看Swift版本

xcrun swift -version

  • 默认xcode路径以及选择

xcode-select -print-path

xcode-select -s /Applications/Xcode.app/Contents/Developer/

  • Frameworks 的放置位置

系统的Frameworks放在/System/Library/Frameworks/下;

一般App的Frameworks就放在App的Bundle里;

如果需要多个App共享Frameworks则可以放在/Library/Frameworks/下;

日常使用

  • 卸载kext脚本

sudo kextunload /Library/Extensions/xxx.kext

sudo rm -rf /Library/Extensions/xxx.kext

  • 检查程序是否签名

codesign -vvv <Path>/xxx.app

  • 10.12之后允许第三方App运行命令

sudo spctl --master-disable

  • Mount DMG

命令自动挂载DMG包

hdiutil mount <Path>.dmg

hdiutil unmount <Path>

  • 重启FaceTime 摄像头

sudo killall VDCAssistant

  • 开启以太网服务

networksetup -setnetworkserviceenabled ethernet on

networksetup -setnetworkserviceenabled ethernet off

  • 开启VPN

scutil --nc start 'hkVPN ' --user xxx --password yyy —secret zzz

scutil --nc stop 'hkVPN '

  • 屏幕共享

在Finder -> 前往连接服务器 -> 输入

vnc://共享地址

注释和共享

  • 人、家庭、社会是无法分割的一个整体。不过,家庭和社会可以制约人,人却没有选择父母、家庭、社会或时代的自由。
  • 然而,话又说回来,人是活的,有巨大的创造力,可以能动地、有限度地改变家庭乃至社会。

  • 本书在阐述梁启超家族浮沉的过程中特别注重了两点:一是将梁氏家族在近百年的起伏与特殊的时代和社会变动有机结合起来,以大社会为背景来考察小家庭,以小家庭为窗口来理解大社会,妥善处理全貌和一点的关系;二是着力研究梁启超家族独有的特色和长期积淀的文化内涵,写出知识分子家庭的文化底蕴、文化氛围、文化品位和文化走向,也就是注重了梁氏家族文化个性的表述。

百年一梦-解读梁家

  • 从梁启超的家庭文化中,可以切实感受到西方文化的潜移默化,更能够追寻到颇为深厚的中国文化底蕴,还会提炼出一种古今中西文化相融合后再合理升华的新的文化因子,感悟出近代中国新文化的一些新特点。

  • 梁启超因病住进了当时中国最好的协和医院之后,该院院长亲自主刀为其手术,但由于将X光片看错了,他有病的肾没有被割掉,反而把好肾给割掉了,造成手术失误,让梁过早地离开了人世。

  • 1925年7月10日,梁在给远在海外的孩子们的信中讲到对人生的体验时称:“其实我们刻刻在轮回中,一生不知经过多少天堂地狱。”[

探源——南国飞出个金凤凰

  • 耆老会设有自己的地方武装——乡团,由敢于争斗的青年自愿报名,耆老会批准。团民备受优待,分东西时可以领双份。每人或数人发一支枪,弹药则由值理统一保管。有盗卖枪支弹药者,必从重处罚。乡团定时操练,学一些武术和作战技能,有一定的战斗力。在维护茶坑村社会秩序方面,乡团具有举足轻重的地位;当遇到外来侵略,又可发挥组织民众,保卫家乡的积极作用。应该说,清末的乡团是较复杂而作用多变的乡村武装集团。

  • 犯有奸淫罪者,村民最恨,要将全村的猪杀光,平分给每户,钱则由犯罪者偿付,美其名曰“倒猪”。

  • 村里的娱乐活动,以正月十五的灯节和七月的祭神最为热闹。梁启超小时候,最喜欢这两个节日,往往不知疲倦地尽情玩耍。 1873年2月13日,当梁启超发出第一声啼哭时,给他提供的就是这样一个古朴、有序、守成、封闭而又缺少生机的小乡村的生活环境。

成也,政治;败也,政治

  • 因为在梁启超看来,中国国民素质太低,如果一下子推行民主共和,不仅行不通,而且会天下大乱,唯一可行的方法是选择一位开明的专制“伟人”,既有集权的本领,又愿意一步一步向民主过渡。如此,社会不会大乱,民主政治也会逐步实现。在他的心目中,袁世凯就是这样的合适人选。

  • 他满怀信心地扬言,如果这个内阁干不出一点名堂,或者在方针政策上出现什么失误,他“即行辞职”。梁启超亲自草拟了《政府大政方针宣言书》,洋洋万言,俨然要依法治国,全面推行资产阶级民主政治。具体方案是:第一,实行完全责任内阁制,划清总统府与国务院之权限;第二,司法独立,制订切合实际的法律;第三,重视教育;第四,军民分治,废省改道,整顿吏治,严定考试之制;第五,实施县和城镇或乡两级地方自治。

永垂不朽是文章

  • 什么是历史的目的?简单一句话,历史的目的在将过去的真事实予以新意义或新价值,以供现代人活动之资鉴。”[6]

  • 晚年,随着政治上的失意,精神上的匮乏,梁启超对佛学简直到了如痴如狂的地步。1922年在南京讲学期间,梁定期到支那内学院听欧阳竟无讲佛。他在致儿女的书信中,不仅把佛学誉为“宇宙间唯一真理”,而且要他们也经常念佛。如有闲空,梁还和夫人一起念经求佛,以解脱心中苦闷,寄托无限情思。

  • 关于佛学理论的研究,梁启超用力最深。梁的信佛和一般平民百姓的拜佛不同,他不是用迷信来解脱自己,而是用新的理念来重新塑造自己的人生观,解决心灵深处的疾苦,构筑新的思想理论体系。

  • 。在梁看来,佛教本身就是一种心理修养,它是通过各种人生的哲理并杂以不少玄学,再结合现实生活来规劝世人如何做人,如何解除烦恼,从而达到一种“无我”的生活境界。

妻爱无限 儿女情长

  • 然而,光绪十五年(1889年)的一次举人考试,不仅改变了她的命运,也再次演绎了一场传统士人梦寐以求的传奇式婚姻,今天我们可以称之灰姑娘式的爱情故事,只不过这位“灰姑娘”是才华横溢的青年梁启超。

  • 养猫是李蕙仙的一大爱好,尤其是从日本归国后,梁家生活日趋稳定,在她的带动下,养猫成了梁家老老少少的一大兴趣。这一嗜好从她身上传给了几乎所有的儿女,又传给了第三代,以至于梁家被誉为“爱猫家庭”。吴

  • 王桂荃是四川广元人,又名来喜,出生于1886年。王桂荃的童年非常悲惨,尚未尽情享受童年的幸福与欢乐,悲剧就不断向她袭来。先是母亲早逝,继母借口她命硬,是父母的克星,经常虐待她,动不动就不给饭吃,还拳打脚踢。四岁时,父亲又不幸抱病而亡,继母进城办丧事,账房先生乘机把家产席卷一空,还将她卖给了人贩子。从此以后,年幼的她先后被转卖了四次,其间所受的折磨与痛苦更是令人难以想象。后来,她被卖到了李蕙仙的娘家,转机发生了。1894年,李夫人回家探亲,见她年龄虽小,但聪明伶俐又勤快,便把她带到梁家作贴身丫鬟,并取名桂荃。从此年幼的王桂荃走进了梁家,这一进,就是七十年,直至1968年病逝于北京。

  • 梁家虽不乏近代民主意识,但传统伦理观念仍很深厚,尤其是李夫人,而梁启超又十分尊重与挚爱这位和梁家生死与共的结发夫人,对于李夫人能否接受王桂荃和自己结婚这一事实,他感到忐忑不安,曾一度将王桂荃接至上海,并在那里生下了一个男孩,取名梁思永。然而后来事实却出乎梁启超意料,当李夫人知道这一切时,尽管很震惊,但很快就平静地接受了这一现实。其一,作为结发妻子,她是深爱并了解梁启超的,知道他需要有人悉心照顾,而自己要管家教子,实在是力不从心;其二,梁家将振兴家族的希望完全寄予梁启超,在梁启超已成家立业,并名扬海内外之际,对于一位男性继承人的迫切需要也日益表现出来,而自己在1893年生下长女思顺后,

  • 王夫人出身贫苦,没有机会读书识字,但她聪明、好学,接受新事物很快,尤其是和梁启超一起流亡日本后,接触到了日本现代文明,大大开拓了眼界,成为全家第一个学会日常日语的人,能讲一口流利的东京话。她不识字,后来干脆等孩子长大一些时,和他们一起学习认字,很快,“她学会了读书,并且读得富有表情。她不仅精通看护和家务管理,而且学会了游泳、滑旱冰、滚铁环、编织、勾花边,会打桥牌、麻将,还学会了针灸”。[9]

  • 当年二舅思成学建筑,三舅思永学考古,四舅思忠学军事。她曾经非常风趣又得意地对别人说:‘我这几个儿子真有趣,思成盖房子,思忠炸房子,房子垮了埋在地里,思永又去挖房子。’”[

  • 1966年文化大革命爆发,梁家子女无一例外受到冲击,已经八十高龄的王桂荃也未能逃脱此劫。她被戴上了“保皇党梁启超的老婆”这顶“高帽”,家产被抄,房屋被占,被赶到一间阴冷潮湿的小屋居住,每天还要作为劳改队队员上街劳动,而此时,她已患晚期肠癌。在战火连天、朝不保夕的战争年代都挺过来的王夫人,这回却垮了,精神和肉体的折磨使她再也无法站起来。此时此刻,她是多么想念自己的儿孙们。然而,命运已使他们再也无法相见。怀着对亲人无限的爱,怀着对昔日家庭幸福生活的无限眷恋,也怀着莫名的困惑与痛苦,王夫人于1968年离开了人世。社会造成了她悲惨的童年,又导致了凄凉的晚年。她又是幸福的,因为,作为一个平凡的女性,她的大半生都是在爱与被爱之中度过的。

  • 数日后,梁将照片赐何蕙珍,何亦回赠亲手织绣的两把精美的小扇。梁启超不仅感慨:“见其事、闻其言,觉得心中时时刻刻有此人,不知何故也。”[13]

  • 宴毕,何蕙珍又深情地表示:“先生他日维新成功后,莫忘我,但有创办女学堂之事,以一电召我,我必来。我之心惟有先生!”两人握手珍重而别。回公寓后,梁启超激动的心情再也无法平静下来,自称“归寓后,愈益思念蕙珍,由敬重之心,生出爱恋之念来,几乎不能自持。明知待大家闺秀,不应起如是念头,然不能制也。酒阑人散,终夕不能成寐,心头小鹿,忽上忽落,自顾生平二十八年,未有如此可笑之事者。”[14]

  • 上学之余,“双涛园”后面的山林就成了孩子们真正的“天堂”。樱花烂漫的时节,大人们登山赏花,孩子们则前后奔跑,嬉戏不息,都要玩“疯”了。有时,孩子们会跟着父母,带着小炉子和酱油等佐料,在松林中,踩着厚厚的松叶,四处寻找鲜鲜的松蘑,边烤边吃。风景之美,松蘑之香,气氛之和谐,以至于已长大成人的“双涛园群童”每每想起,都会留恋不已。

  • 今吾朝受命而夕饮冰”。

  • 梁思忠问道:“为什么一个爱国的政治家要在一个通商口岸的外国租界里安家并造起书房和图书室?”显然,梁启超没有想到儿子会提出这么敏感的问题。但梁很认真地对待儿子的疑问,他非常平和地对儿子解释道:“别把私人的事情同国际事务搅在一起。除了我的家庭以外。我眼前主要关心的是我的图书室。我需要我的书,我必须使它们保持能用的状态。比起放在可能被某些愤怒的学生不明智地放火烧掉的易燃的宫殿来,放在附近港口城市的外国租界里可能更安全些。而要使用这些书,我必须时时住在它们的旁边。”[

  • 在梁启超看来,爱国绝不是单纯的空喊口号和自我标榜,爱国的行动可以而且应该包括各个方面,既有政治救国,军事救国,也应该有科学救国,学术救国等。不能为了标榜爱国而与一切“洋”事物都划清界限;也不能为了表明爱国,都去当兵或参与政治斗争,毕竟尺有所短,寸有所长。基于此种认识,尽管在政治上屡遭挫折,但梁启超的爱国热情始终未减,而且愈发坚定了自己学术救国的信念,直至生命最后一息。

  • 1925年4月17日,他在写给远在加拿大的女儿们的信中将自己的情绪毫无遮拦地表达出来,他这样写道:“宝贝思顺、小宝贝庄庄:你们走后,我很寂寞。当晚带着忠忠听一次歌剧,第二日整整睡了十三个钟头起来,还是无聊无赖……庄庄这几个月来天天挨着我,一旦远行,我心里着实有点难过。但为你成就学业起见,不能不忍耐这几年。庄庄跟着你姊姊,我是十二分放心了;但我十五日早晨吩咐你那几段话,你要常常记在心里,等到再见我时,把实行这话的成绩交还我,我便欢喜无量了。”[

“思成梁启超”

  • 当年,同在美国留学的梁思永为此专门做对联一副:“林小姐千妆万扮始出来;梁公子一等再等终成配。”

  • 我主张你们在坎京(旧译,指加拿大)行礼,你们意思如何?我想没有比这样再好的了。你们在美国两个小孩子自己实张罗不来,且总觉太草率,有姊姊代你们请些客,还在中国官署内行谒祖礼(婚礼还是在教堂内好),才庄严像个体统。 婚礼只要庄严不要侈靡,衣服首饰之类,只要相当过得去便够,一切都等回家再行补办,宁可从中节省点钱作旅行费。”[28]

  • 在大学生时代,他们性格上的差异就在工作作风上表现出来。满脑子创造性的徽因常常先画出一张草图或建筑图样。随着工作的进展,就会提出并采纳各种修正或改进的建议,它们自己又由于更好的意见的提出而被丢弃。当交图的最后限期快到的时候,就是在画图板前加班加点拼命赶工,也交不上所要求的齐齐整整的设计图定稿了。这时候思成就参加进来,以他那准确和漂亮的绘图功夫,把那乱七八糟的草图变成一张清楚整齐能够交卷的作品。他们的这种合作,每个人都向建筑事业贡献出他的(或她的)特殊天赋,在他们今后共同的专业生涯中一直坚持着。”[

  • 面对重病中的妻子,梁思成心如刀绞,但环境的恶劣、条件的简陋,丝毫没有令他畏缩,这位从未接受过医学训练的建筑学家开始学习打针,为了自己的妻子,天大的困难也要克服。爱能战胜一切,梁思成不仅学会了肌肉注射,而且学会了难度很大的静脉注射,成为林徽因最好的“护士”。

思成,有成;也难成

  • 很遗憾,他们的建议并未引起中央领导的足够重视。新中国的领导们对北京实现工业化的决心是不容许丝毫动摇的。彭真的一句话最具代表性,他告诉梁思成:毛主席希望北京成为一个现代化的大城市,并希望从天安门上望去,下面是一片烟囱。

群星闪烁——渐为平常百姓家

  • 在子女教育的问题上,一个重要的误区就是父母企图按照自己的人生理念和价值判断去改造子女,像制造产品一样重新创造自我,也就是说,是改造子女而不是引导子女。

  • 他在家信中说:“我以为一个人什么病都可以医,唯有‘悲观病’最不可医,悲观是腐蚀人心的最大毒菌。”[4]而悲观的产生往往是源于过高估计自己,目标过高而达不到,于是常常悲观失望。

  • 我生平最服膺曾文正两句话:‘莫问收获,但问耕耘。’将来成就如何,现在想他则甚?着急他则甚?一面不可骄盈自慢,一面又不可怯懦自馁,尽自己能力做去,做到那里是那里,如此则可以无入而不自得,

注释和共享

  • 一旦能够说出什么东西的名字,就会很容易注意到它。你就会掌握它,拥有它,使它在你的控制中。

    • 所以凡事要先掌握概念
  • 尽管我在后面将逐个讨论各个原则,不过要记住,它们实际上是相互关联的。只应用某一个原则的情况很少。

    • 4大设计原则是互相关联的
  • 亲密性的思想并不是说所有一切都要更靠近,其真正的含义是:如果某些元素在理解上存在关联,或者相互之间存在某种关系,那么这些元素在视觉上也应当有关联。除此以外,其他孤立的元素或元素组则不应存在亲密性。

  • 要有意识地注意你是怎样阅读的,你的视线怎样移动:从哪里开始;沿着怎样的路径;到哪里结束;读完之后,接下来看哪里?整个过程应当是一个合理的过程,有确定的开始,而且要有确定的结束。

  • 亲密性的根本目的是实现组织性。

  • 利用亲密性原则,还可以使空白(这是设计者们最喜欢的)更美观(也更有条理)。

  • 微微眯起眼睛,统计你的眼睛停顿的次数来数一数页面上有多少个元素。如果页面上的项超过3~5个(当然,这取决于具体情况),就要看看哪些孤立的元素可以归在一组建立更近的亲密性,使之成为一个视觉单元。

  • 避免一个页面上有太多孤立的元素。

  • 请注意你喜欢的那些设计。我敢保证,大多数看来精巧的设计都没有采用居中对齐。我知道,作为一个初学者,要完全摒弃居中对齐会很难,但你必须从一开始就强制自己避开它。通过充分利用亲密性,并结合明确的右对齐或左对齐,你会惊异于设计的改观。

  • 我并不是建议你绝对不要居中!只是要留意这种居中对齐的效果,这真的是你想要表达的效果吗?当然,有时候确实如此;例如,大多数婚礼都很庄重、很正式,所以,如果你想用居中方式设计你的结婚喜帖,完全可以在营造喜庆的同时有意这么做。

  • 在得到更多培训之前,一定要坚持一个原则:页面上只使用一种文本对齐:所有文本都左对齐,或右对齐,或者全部居中。

  • 有时,你可能喜欢在同一个页面上同时使用右对齐和左对齐文本,不过一定要确保让这些文本以某种方式对齐!

  • 尽管这些孤立元素在页面上的物理位置可能并不靠近,但是通过适当放置,可以让它们看上去是有联系而且相关的,并且与其他信息统一。

  • 可以把重复认为是“一致性”。

  • 不过重复还不只是自然的一致,这是一种统一设计各个部分的有意识的行为。

  • 如果一个出版物有非常好的一致性,则可以放入一些与众不同的元素,使读者真正注意到你希望他们关注的内容。

  • 不要把重复用得太滥,而应当尽量“采用多样性实现统一”。也就是说,如果一个重复元素很明确(如一个圆),那么可以采用多种不同方式重复这个圆,而不是简单地重复同一个圆。

    • 感觉重复性原则很难应用好
  • 重复还会为你的作品带来一种专业性和权威性。它会使读者感觉有人在负责,因为重复显然是一种经过深思熟虑的设计决策。

  • 重复并不表示必须重复完全相同的东西。

  • 重复可以认为是保持一致性,而且我相信你早已经这样做过。现在,需要把现有的一致性更向前推进一步。

  • 要避免太多地重复一个元素,重复太多会让人讨厌。要注意对比的价值

  • 要增加有意思的对比,最容易的方法就是实现字体对比(这也是本书第二部分的重点)。不过不要忘记,还可以利用线、颜色、元素之间的间隔、材质等形成对比。

  • 在设计原则中,对比最有意思,同时效果也最为显著!只需几个小小的改动,就能把一个普普通通的设计变成一个精美的设计。

  • 希望你已经看出,对比对于设计作品来说是何等重要,另外也应该能看出增加对比实际上是何等容易。

  • 对比的根本目的有两方面,这两个方面相辅相成,无法分开。一个目的是增强页面的效果,如果一个页面看起来很有意思,往往更有可读性。另一个目的是有助于信息的组织。读者应当能立即了解信息以何种方式组织,以及从一项到另一项的逻辑流程。对比元素不能让读者混淆,也不能错误地强调重点(即本不该是重点的元素)。

  • 重要的是:对比一定要强烈。

注释和共享

支持多日期串行查询

test

查到票后Reminder提醒

若查到票时不在电脑旁边,则会通过iPhone提醒

test

或者Apple Watch提醒

test

改进车次筛选功能

test

日历显示更多节假日

test

登录界面快捷键

右手用鼠标或触控板选择验证码,左手按Space键确定,非常方便
test

节假日预售期提醒

可以一键生成 之后一年的节假日 预售期的日历提醒,提前做好买票计划。
test

注释和共享

  • 第 1 页 共 1 页

max Lin

make complex simple


Developer


shenzhen