作为一个图形图像方向的研究生,我经常都在和 OpenGL 、OpenCV 等多种 C++ 库打交道。这些库遵循着不同的规则和用法;另外,为了让自己的程序具有更多的交互能力,编写界面也是一个家常便饭的工作。

然而,随着工程复杂性的增加,库的管理和界面的维护也变得越来越困难:一方面,库的增加和删除不仅会增加学习成本,也会对系统的逻辑层带来影响。而另一方面,如果要让自己的项目易于维护,就要尽可能地应用设计模式,让逻辑和界面分离。但对于科研,一味陷入设计模式的桎梏又会带来过早优化的问题,影响科研进度。

直到后来,我接触到了 openFrameworks ,简直有种相逢恨晚的感觉。openFrameworks 封装了常用的 C++ 库,在此基础上提供了一个直观统一的接口,也大幅简化了编写界面的流程,使得开发图形程序变得很轻松。

本文将为大家介绍这个让人着迷的开发框架 —— openFrameworks。

什么是 openFrameworks

openFrameworks(以下简称 oF) 是一个开源的、跨平台的 C++ 工具包,它的设计目的为开发创造过程提供一个更加简单和直观的框架。

oF 的强大之处在于,它不仅是一个通用的胶水(glue),同时它还封装了多种常用的库,包括:

这些库虽然遵循着不同的规则和用法,但 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() 则常用来在你的窗口中绘制内容;
  • keyPressedkeyReleasedmouseMovedmouseDraggedmousePressedmouseReleasedwindowResizedgotMessagedragEvent - 与前面三种函数不同,这类函数仅当用户触发某类事件才会被调用。

我们先试着直接编译这个项目,此时的程序窗口里还没有东西:

绘制图形

之后,我们可以试着在窗口中画一个圆。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 、sad 四个按键控制圆圈的运动。出于鲁棒性考虑,小写和大写的字母都要考虑进去,因为按键是通过十进制的 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 的另外一大杀手锏在于它的社区非常活跃,现在已经开发出了数量可观的第三方插件。这里只收集了极小一部分实用插件。更全面的插件列表可以访问 ofxaddons.com 9 9什么?有墙?!其实几乎所有插件都是托管在 Github 上的。所以如果在 Github 上搜 “ofx” ,也可以找到这些 oF 插件哦。。

  • ofxUI - 华丽丽的 UI 库。提供了很多新颖而实用的界面控件。
  • ofxCv - OpenCV 的另一套可选的 oF 插件(oF 自带一个 oFOpenCv 插件);
  • ofxLibRocket - 对 librocket 库的封装,这个库允许你使用 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 物理模拟库 box2d 的封装;
  • ofxBullet - 对另一个物理模拟库 Bullet Physics 的封装;
  • ofxLearn - 通用的机器学习插件,支持分类、回归、聚类等任务;
  • ofxJSON - 对 Json 库 JsonCpp 的封装;
  • ofxHttpServer - 一个基于 libmicrohttpd 的 http 服务器插件;
  • ofxAddonTemplate - 一个空的目录框架,可以借鉴它自己编写插件(这都有……--bb)。

使用这些插件的方法很简单:

  1. 访问这个插件的 Github 项目主页;
  2. 复制它的代码仓库地址;
  3. 进入你的 oF 根目录下的 addons 目录,git clone 这个项目;
  4. 如果这个项目自带 example ,可以直接 make && make run 编译和执行它看看结果。

相关链接

介绍一个开源的 C++ 开发框架 openFrameworks 。的更多相关文章

  1. 介绍一个开源的在线管理SQLServer的小工具--SQLEntMan

    近来有许多人问起SQL在线管理的问题,遂将以前用过的一个开源SQL 在线管理工具修改了一下,并分享. 看下效果图: 原项目的地址:http://sourceforge.net/projects/asp ...

  2. 给各位聚聚和大大介绍一个开源项目 Expression2Sql(转)

    阅读目录 一.Expression2Sql介绍 二.单表简单查询 三.Where条件 四.多表关联查询 五.group by 六.order by 七.函数 八.delete 删除 九.update ...

  3. 介绍一个开源的SIP(VOIP)协议库PJSIP

    本文系转载,出处不可考. 假设你对SIP/VoIP技术感兴趣,哪希望你不要错过:),假设你对写出堪称优美的Code感兴趣 ,那么你也不可错过:) 这期间我想分析一下一个实际的协议栈的设计到实现的相关技 ...

  4. 介绍一个非常好用的跨平台C++开源框架:openFrameworks

    介绍一个非常好用的跨平台C++开源框架:openFrameworks 简介 首先需要说明的一点是: openFrameworks 设计的初衷不是为计算机专业人士准备的, 而是为艺术专业人士准备的, 就 ...

  5. 可跨平台C++开源图形图像框架:openFrameworks

    博客参考:https://www.hahack.com/codes/openframeworks-intro/#%E4%BB%80%E4%B9%88%E6%98%AF-openframeworks 和 ...

  6. 教你打造一个Android组件化开发框架

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 CC:Component Caller,一个android组件化开发框架, 已开源,github地址:https://github ...

  7. Fixflow引擎解析(一)(介绍) - Fixflow开源流程引擎介绍

    Fixflow引擎解析(四)(模型) - 通过EMF扩展BPMN2.0元素 Fixflow引擎解析(三)(模型) - 创建EMF模型来读写XML文件 Fixflow引擎解析(二)(模型) - BPMN ...

  8. 【Hades】ades是一个开源库,基于JPA和Spring构建,通过减少开发工作量显著的改进了数据访问层的实现

    几乎每个应用系统都需要通过访问数据来完成工作.要想使用领域设计方法,你就需要为实体类定义和构建资源库来实现领域对象的持久化.目前开发人员经常使用JPA来实现持久化库.JPA让持久化变得非常容易,但是仍 ...

  9. 怎样在Github参与一个开源项目

    转载:http://www.csdn.net/article/2014-04-14/2819293-Contributing-to-Open-Source-on-GitHub 最近一年开源项目特别的热 ...

随机推荐

  1. HDU 4725 The Shortest Path in Nya Graph(最短路建边)题解

    题意:给你n个点,m条无向边,每个点都属于一个层,相邻层的任意点都能花费C到另一层任意点,问你1到n最小路径 思路:没理解题意,以为每一层一个点,题目给的是第i个点的层数编号.这道题的难点在于建边,如 ...

  2. Linux清除Windows密码

    下载安装ntfs-3g 下载驱动让linux挂载windows磁盘 https://tuxera.com/opensource/ntfs-3g_ntfsprogs-2017.3.23.tgz 安装 t ...

  3. shell 判断一个字符串是否为空

    test.sh #!/bin/bash echo "enter the string:" read filename if test $filename ; then echo & ...

  4. 菜单项onCreateOptionsMenu()和onOptionsItemSelected()的使用

    Java源文件 package com.example.macname.myapplication; import android.support.v7.app.AppCompatActivity; ...

  5. window.frames && iframe 跨页面通信

    1.定义 frames[]是窗口中所有命名的框架组成的数组.这个数组的每个元素都是一个Window对象,对应于窗口中的一个框架. 2.用法 假设iframe 是一个以存在的 iframe 的 ID 和 ...

  6. YAML(摘录)

    YAML:维基百科 一个用来表达数据序列的格式.强调以数据为中心的标记语言. 使用空白符缩进和大量依赖外观的特殊,适合编辑数据结构,配置文件. 基本格式: 缩进/区块 和内置两者格式,来表示array ...

  7. ✅问题:Rails.ajax的data不支持{}hash格式。必须使用string。 dataType的格式。

    Rails.ajax({ url: url, type: "PATCH", data: {"post":{"category_id":thi ...

  8. Python 实现图片上表格的写入

    直接上代码:import matplotlib.pylab as pltimport numpy as npplt.figure()axes=plt.gca()y= np.random.randn(9 ...

  9. vue 表单校验 一

    表单校验 一 最近使用elment-ui表单进行各种校验,心力交瘁,依旧不能很好地解决,先列出自己的归类,后期一个个攻破 表单校验史 表单校验准则 参考资源 1 2 3 4 5 第一种 显示明确的错误 ...

  10. CMD中goto语句会中断for循环特性详解

    在这个程序里面由于用到了上篇文章中所说的字符串切割,而用到了Goto强制跳转语句 但是在程序中使用的时候却发现一个错误,当把这个字符切割的代码段如果直接作为非嵌套语句执行正常 但是一旦放到for循环的 ...