开源框架 openFrameworks
作为一个图形图像方向的研究生,我经常都在和 opengl 、opencv 等多种 c++ 库打交道。这些库遵循着不同的规则和用法;另外,为了让自己的程序具有更多的交互能力,编写界面也是一个家常便饭的工作。
然而,随着工程复杂性的增加,库的管理和界面的维护也变得越来越困难:一方面,库的增加和删除不仅会增加学习成本,也会对系统的逻辑层带来影响。而另一方面,如果要让自己的项目易于维护,就要尽可能地应用设计模式,让逻辑和界面分离。但对于科研,一味陷入设计模式的桎梏又会带来过早优化的问题,影响科研进度。
直到后来,我接触到了 openframeworks ,简直有种相逢恨晚的感觉。openframeworks 封装了常用的 c++ 库,在此基础上提供了一个直观统一的接口,也大幅简化了编写界面的流程,使得开发图形程序变得很轻松。
本文将为大家介绍这个让人着迷的开发框架 —— openframeworks。
什么是 openframeworks
openframeworks(以下简称 of) 是一个开源的、跨平台的 c++ 工具包,它的设计目的为开发创造过程提供一个更加简单和直观的框架。
of 的强大之处在于,它不仅是一个通用的胶水(glue),同时它还封装了多种常用的库,包括:
- opengl、glew、glut、、 - 用于处理图形;
- rtaudio、portaudio、openal、kiss fft、fmod - 用于音频的输入、输出和分析;
- freetype - 用于字体显示;
- freeimage - 用于图像存储和载入;
- quicktime、gstreamer、videoinput - 用于视频播放和截取;
- poco - 用于开发网络应用;
- opencv - 用于计算机视觉;
- assimp - 用于读入 3d 模型。
这些库虽然遵循着不同的规则和用法,但 of 在它们基础上提供了一个通用的接口,使得使用它们变得很容易。
除此之外,of 的另一亮点在于它具有很好的跨平台特性。目前它支持 5 种操作系统(windows、osx、linux、ios、android)以及 4 种 集成开发环境(xcode、code::blocks、visual studio、eclipse)。
安装和配置 of
下面介绍如何在 linux 下安装和配置 of 。
下载 of
访问 of 的,找到适用于你的操作系统和 ide 的版本,点击下载。例如,我的计算机是 linux arch 64位的系统,所以选择的是 code::blocks (64 bit)。
安装依赖
下载完成后,将其解压,开启终端,cd
到解压后目录,例如:
1
|
$ cd $home/documents/programming/openframeworks
|
之后,根据你的 linux 发行版的不同,cd
进入 scripts/linux/<操作系统发行版名称> ,例如:
1
|
$ cd scripts/linux/archlinux
|
执行两个命令,安装 code::block 和其他依赖(需要 root 权限):
1
2
3
|
$ sudo ./install_codeblocks.sh
$ sudo ./install_dependencies.sh
$ sudo ./install_codecs.sh
|
编译 of
安装完依赖后,回到上一级目录:
1
|
$ cd ..
|
编译 of:
1
|
$ ./compileof.sh
|
编译过程中,如果你和我一样遇到找不到 freetype.h 的问题,可能是 freetype 在 2.5.1 之后改变了头文件的结构导致的。需要将根目录里的 /libs/openframeworks/graphics/
目录下的 oftruetypefont.cpp 开头部分改为:
1
2
3
4
5
6
7
8
9
10
11
|
#include "ft2build.h"
/* corrected setup of include files for freetype as of 2.5.1 dh
#include "freetype2/freetype/freetype.h"
#include "freetype2/freetype/ftglyph.h"
#include "freetype2/freetype/ftoutln.h"
#include "freetype2/freetype/fttrigon.h"
*/
#include ft_freetype_h
#include ft_glyph_h
#include ft_outline_h
#include ft_trigonometry_h
|
此时的 of 已经可以工作了。我们可以测试它提供的示例。cd 到根目录里的 /examples/gui/guiexample/ 目录,编译该工程并执行:
1
2
|
$ make
$ make run
|
将会运行一个界面如下图的程序。与左侧面板里的控件交互将可以改变该形状的属性1 1多阅读 example 的示例代码是个好习惯。。
编译项目生成器
为了方便日后创建工程,of 还提供了一个项目生成器 projectgenerator 。使用它前同样需要先编译。回到 compileof.sh
脚本所在的目录,敲入如下命令:
1
|
$ ./compilepg.sh
|
完成后,在 of 的根目录下找到 projectgenerator 目录,进去里面可以找到 projectgenerator ,我们可以执行它:
1
2
|
$ cd ../../projectgenerator
$ ./projectgenerator
|
程序界面如下图所示。点击左侧的每个黑色按钮将可以修改项目名、生成路径,以及依赖的插件(addon)。
点击右下角的 generate project
按钮后,将会在 path 字段指定的路径中生成一个项目,如上图所示就是 /home/ehome/documents/programming/openframeworks/apps/myapps/mysketch
:
1
2
3
|
$ cd /home/ehome/documents/programming/openframeworks/apps/myapps
$ ls
addons.make bin config.make makefile mysketch.cbp mysketch.workspace src
|
其中:
- addons.make 文件 - 用于维护这个工程所依赖的插件列表;
- config.make 文件 - 用于添加查找路径,修改优化标记以及其他的设置;
- makefile 文件 - 工程的 makefile ,一般不需要直接修改它。在 of 中,make 的目标包括:
- debug:生成带有调试标记的可执行程序;
- release:生成经编译器优化的可执行程序;
- clean:清除目标文件和可执行程序;
- cleandebug:只清除 debug 目标的生成结果;
- cleanrelease:只清除 release 目标的生成结果;
- help:打印帮助信息;
- run:执行生成的可执行程序。
- mysketch.cbp 和 mysketch.workspace 文件 - code::blocks 的工程文件。
注册环境变量
我们可以看看 makefile 文件的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ cat makefile
# attempt to load a config.make file.
# if none is found, project defaults in config.project.make will be used.
ifneq ($(wildcard config.make),)
include config.make
endif
# make sure the the of_root location is defined
ifndef of_root
of_root=../../..
endif
# call the project makefile!
include $(of_root)/libs/openframeworkscompiled/project/makefilecommon/compile.project.mk
|
如上所示,编译 openframeworks 的工程时,系统需要从 of 的根路径中引入另一个名为 compile.project.mk
的 makefile,这个根路径存储在 of_root
变量中,默认值是 ../../..
,即当前目录往上三级的目录。之所以使用这个默认值,是因为使用 projectgenerator 生成的项目都默认存放在 of根目录/apps/myapps
目录下。为了方便在其他地方创建和编译工程,可以人为地定义一个 of_root
变量。将下面这一行添加到用户主目录下的 .bashrc
文件中:
1
|
export of_root=<你的 of 根目录>
|
入门实例
接下来将介绍如何开发基于 of 的 c++ 程序2 2主要参考了 jeff crouse 所编写的教程 oftutorials - chapter 1 - getting started。。
testapp.cpp
双击 mysketch.cbp 文件,打开 code::blocks 开发环境,在左边的项目管理器中双击打开 test.app 文件。如下图所示:
testapp.cpp 将会是你的好朋友。在编辑窗口中,你可以看到如下的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include "testapp.h"
//--------------------------------------------------------------
void testapp::setup(){
}
//--------------------------------------------------------------
void testapp::update(){
}
//--------------------------------------------------------------
void testapp::draw(){
}
//--------------------------------------------------------------
void testapp::keypressed(int key){
}
...
|
上面的代码包含了 4 类函数:
-
setup
- 这个函数将在应用程序生命期的最开始就被调用,甚至在你编写的程序窗口打开之前。利用这个函数,我们可以做一些准备工作,例如在窗口打开之前,先修改窗口的大小; -
update
和draw
- 当setup
函数运行完成后,系统将进入一个update
和draw
不断交替运行的循环,这个循环将持续到程序结束。也就是说,setup()
运行完成后,update()
开始运行,然后是draw()
,然后又是update()
,然后又是draw()
…… 3 3这个交替频率就是帧率,它的默认值取决于你的电脑的处理速度。update()
通常用来更新你的程序的状态(例如改变变量的值),而draw()
则常用来在你的窗口中绘制内容; -
keypressed
、keyreleased
、mousemoved
、mousedragged
、mousepressed
、mousereleased
、windowresized
,gotmessage
、dragevent
- 与前面三种函数不同,这类函数仅当用户触发某类事件才会被调用。
我们先试着直接编译这个项目,此时的程序窗口里还没有东西:
绘制图形
之后,我们可以试着在窗口中画一个圆。of 提供了 ofcircle
函数用于绘制圆。
往 draw
函数里头添加一句内容,:
1
2
3
|
void testapp::draw(){
ofcircle(200, 300, 60);
}
|
第二行告诉系统在坐标 (200, 300) 处画一个半径为 60 的圆。
添加颜色
现在这个圆看起来很单调,可以给这个圆添加颜色。of 提供了 ofsetcolor
函数用于设置颜色。将 draw()
函数改为:
1
2
3
4
|
void testapp::draw(){
ofsetcolor(255, 0, 255);
ofcircle(200, 300, 60);
}
|
新加的这一行(第2行)告诉系统在绘制图形前选择一个颜色,这个颜色的 r、g、b 三原色的色值分别为 (255, 0, 255) 。
我们可以用同样的方法再画一个青色的圆:
1
2
3
4
5
6
7
|
void testapp::draw(){
ofsetcolor(255, 0, 255);
ofcircle(200, 300, 60);
ofsetcolor(0, 255, 255);
ofcircle(500, 500, 100);
}
|
其他的形状
除了画圆,of 也可以画其他的图案:
-
ofrect
- 画一个矩形。参数是:(x, y, width, height) ; -
oftriangle
- 画一个三角形。参数是三个顶点的坐标:(x1, y1, x2, y2, x3, y3) -
ofline
- 画一条线段。参数是两个端点的坐标 (x1, y1, x2, y2) -
ofellipse
- 画椭圆。参数是:(x, y, width, height) -
ofcurve
- 画一条从点 (x1, y1) 到 (x2, y2) 的贝塞尔曲线,曲线的形状由两个控制点 (x0, y0) 和 (x3, y3) 控制 4 4贝塞尔曲线 的控制点比较难以掌握。如果你用过 photoshop 里的钢笔工具,你大概就会明白是怎么一回事。。
让形状动起来
接下来我们将编写代码让窗口里的图形动起来。主要的思路就是用两个变量控制圆的坐标,然后在程序的运行过程中改变这个变量。在 test.cpp 文件的开头声明两个变量,分别用于存放圆的 x 坐标和 y 坐标:
1
2
3
4
|
#include "testapp.h"
int mycirclex;
int mycircley;
|
在 setup()
函数中为这两个变量添加初始值 5 5别忘了前面介绍的。:
1
2
3
4
|
void testapp::setup(){
mycirclex = 0;
mycircley = 200;
}
|
用这两个变量绘制图形:
1
2
3
4
|
void testapp::draw(){
ofsetcolor(255, 0, 255);
ofcircle(mycirclex, mycircley, 60);
}
|
要在运行过程中修改这两个变量,可以在 update()
函数中编写相关代码。例如,让这个圆一直向右移动,当超出屏幕时,再回到原来开始的地方:
1
2
3
4
5
|
void testapp::update(){
mycirclex++;
if (mycirclex > ofgetwindowwidth())
mycirclex = 0;
}
|
其中,第 3 行的 ofgetwindowwidth()
函数用来获取窗口的宽度6 6如果不考虑拉伸窗口,也可以用 1024 这个值代替,因为 of 的默认窗口大小是 1024x768 。。
改变帧率
你可能会发现上面的程序在运行的时候有一个问题:圆圈的运动存在时快时慢的情况。如前面所说,这是由于你的程序的帧率,或者说 update()
函数和 draw()
函数交替执行的频率不稳定造成的。在 draw()
函数中添加下面这一行代码可以在窗口的左上方显示帧率信息:
1
|
ofdrawbitmapstring(oftostring(ofgetframerate())+"fps", 10, 15);
|
你可以发现这个数值会在程序运行的过程中存在较大波动,尤其是当你同时还在执行其他耗费计算资源的任务时,这个数值会下降得更加明显,相应的这个圆圈的运动速度也会跟着变慢。
让窗口中的动画变得更加平滑的方法是把帧率限制在一个合理的值,例如 60 fps :
1
2
3
4
5
6
|
void testapp::setup(){
ofsetframerate(60);
mycirclex = 300;
mycircley = 200;
}
|
如果你觉得经过这么一改动之后这个圆圈慢的让你无法忍受,你可以通过修改圆圈的移动速度来加速。例如:
1
2
3
4
5
|
void testapp::update(){
mycirclex+=4;
if (mycirclex > ofgetwindowwidth())
mycirclex = 0;
}
|
添加交互
接下来,我们将为这个程序添加键盘和鼠标的交互。要添加键盘交互,可以通过修改 keypressed()
函数和 keyreleased()
函数来完成。其中,keypressed()
捕获的是按下键盘按键的事件,而 keyreleased()
捕获的是松开键盘按键的事件 7 7额外提一下, of 似乎并不能很好的识别 dvorak 等其他键盘布局。解决方法见。。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void testapp::keypressed(int key){
if('w' == key || 'w' == key)
{
mycircley-=4;
}
if('s' == key || 's' == key)
{
mycircley+=4;
}
if('a' == key || 'a' == key)
{
mycirclex-=4;
}
if('d' == key || 'd' == key)
{
mycirclex+=4;
}
}
|
将通过 w
、s
、a
、d
四个按键控制圆圈的运动。出于鲁棒性考虑,小写和大写的字母都要考虑进去,因为按键是通过十进制的 ascii 码来判断的,而大写字母和小写字母的 ascii 码是不同的。上面的代码也可以等价的用 ascii 码来代替8 8温馨小提示:linux 下可以通过 man ascii
查询每个字母对应的 ascii 编码。一般人我不告诉他。:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void testapp::keypressed(int key){
if(119 == key || 87 == key) // w key
{
mycircley-=4;
}
if(115 == key || 83 == key) // s key
{
mycircley+=4;
}
if(97 == key || 65 == key) // a key
{
mycirclex-=4;
}
if(100 == key || 68 == key) // d key
{
mycirclex+=4;
}
}
|
添加鼠标事件则通过修改 mousemoved()
、mousedragged()
、mousepressed()
和 mousereleased()
来完成,顾名思义,分别捕获的是鼠标的移动、拖拽、单击、松开操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void testapp::mousemoved(int x, int y ){
}
void testapp::mousedragged(int x, int y, int button){
}
void testapp::mousepressed(int x, int y, int button){
}
void testapp::mousereleased(int x, int y, int button){
}
|
例如,我们可以编写代码实现鼠标拖动圆圈:
1
2
3
4
5
6
7
8
9
10
|
void testapp::mousedragged(int x, int y, int button){
if (0 == button) { // left button
float distance = ofdist(mycirclex, mycircley, x, y);
if (distance < 100){
mycirclex = x;
mycircley = y;
}
}
}
|
第 2 行用于判断触发此事件的按键是否为左键;第 3 行的 ofdist()
函数用于计算鼠标当前位置和圆心的距离。如果这个距离小于半径 100 ,则可以判断当前鼠标落在这个圆圈的范围以内,可以用鼠标的位置代替圆心的位置。
其他优化
调整圆圈精度
如果近一点观察圆圈,你可能会发现圆圈的周围有点粗糙。
可以修改圆圈的绘制精度来让圆圈更加平滑。在 setup()
函数中添加这一句:
1
|
ofsetcircleresolution(120);
|
抗锯齿和垂直同步
抗锯齿和垂直同步也是常常使用的优化画面的手段:
1
2
|
ofsetverticalsync(true);
ofenablesmoothing();
|
实用的插件
of 的另外一大杀手锏在于它的社区非常活跃,现在已经开发出了数量可观的第三方插件。这里只收集了极小一部分实用插件。更全面的插件列表可以访问 9 9什么?有墙?!其实几乎所有插件都是托管在 github 上的。所以如果在 github 上搜 “ofx” ,也可以找到这些 of 插件哦。。
- ofxui - 华丽丽的 ui 库。提供了很多新颖而实用的界面控件。
- ofxcv - opencv 的另一套可选的 of 插件(of 自带一个 ofopencv 插件);
- ofxlibrocket - 对 库的封装,这个库允许你使用 html 和 css 来布局 c++ 窗口;
- ofxtruetypefontuc - 对 oftruetypefont 类的扩展,使其支持 unicode 字符(例如汉字);
- ofxpcl - 对 pcl(一个专门用于处理点云的库) 的封装;
- ofxtimeline - 一个用来绘制可编辑的 timeline 控件的插件;
- ofxmidi - midi 音乐的插件;
- ofxspeech - 语音识别插件;
- ofxvideorecorder - 录制视频插件;
- ofximagesequence - 一个用于像播放视频一样播放图像序列的插件;
- ofxgifencoder - 生成 gif 动画的插件;
- ofxvolumetrics - 简单的体绘制插件;
- ofxdelaunay -
- ofxfft - 对两个用于进行傅里叶变换的库 fftw 和 kissfft 的封装;
- ofxnodejs - 桥接 node.js 的插件;
- ofxlua - 桥接 lua 的插件;
- ofxbox2d - 对流行的 2d 物理模拟库 的封装;
- ofxbullet - 对另一个物理模拟库 bullet physics 的封装;
- ofxlearn - 通用的机器学习插件,支持分类、回归、聚类等任务;
- ofxjson - 对 json 库 jsoncpp 的封装;
- ofxhttpserver - 一个基于 的 http 服务器插件;
-
ofxaddontemplate - 一个空的目录框架,可以借鉴它自己编写插件(这都有……
--bb
)。
使用这些插件的方法很简单:
- 访问这个插件的 github 项目主页;
- 复制它的代码仓库地址;
- 进入你的 of 根目录下的 addons 目录,
git clone
这个项目; - 如果这个项目自带 example ,可以直接
make && make run
编译和执行它看看结果。
相关链接
- about openframeworks:更多关于 openframeworks 的资料,包括设计方法;
- :更多 openframeworks 的教程;
- official documentation:官方文档;
- of forum:官方论坛;
- :这里搜集了非常丰富的 openframeworks 插件;
- programming interactivity:一本介绍 openframeworks、processing 以及 arduino 的好书;