0%

问题

hexo 博客里的图片资源一般这样解决

1
2
3
4
5
6
7
-root-dir
--PostXXX
---pic1.png
--PostXXX.md

// PostXXX.md
![image](pic1.png)

然后 pic1.png 被引用到同名目录下的图片

问题是,这与我们习惯的markdown 编辑器(typora、vscode 吧啦吧啦)行为不一致

typora要求 相对路径如 ![image](./PostXXX/pic1.png)

解决

核心在于 处理 post 的context内容

我们按 typora的习惯来,要求

  • image_url = ![name](<prefix>path)
  • prefix = <url-root>
  • url-root = <title>

1. 文章角注 url-root

更改 hexo-root/scaffolds/post.md

1
2
3
4
5
6
7
8
9
---
title: {{ title }}
category:
- uncategorized
date: {{ date }}
draft: true
tags:
url-root: {{title}}
---

2. content内容处理

添加文件 hexo-root/scripts/modify_image_path.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

hexo.extend.filter.register('before_post_render', function (data) {
if (data.content != undefined && data['url-root'] != undefined){
// console.log('processing: ',data.path)
const prefix = '\\.\\/' + escapeRegExp(data['url-root']) + '\\/'
const reg = new RegExp('!\\[(.*)\\]\\(' + prefix + '(.*)\\)');
data.content = data.content.replace(reg, '![$1]($2)');
}
return data;
});

That’s all

此章介绍使用DType绘制字体,并介绍排版方法

DType 渲染引擎

Home
一套高性能和便携式软件库,用于渲染高质量文本和分辨率无关的可扩展二维图形

绘制字体流程大致如下

  1. 创建引擎实例
  2. 加载字体
  3. 加载字形信息
  4. 绘制
  5. 排版

1. 创建引擎实例

调用dtEngineIniViaStream 时,提供的 DT_STREAM有点讲究
虽然只指定了 dtype.inf ,但是该文件中指定了其他的好些依赖文件

1
2
3
4
5
6
// config/dtype.inf
{
0,system/ini/dtype.fls
0,system/ini/dtype.pat
0,system/ini/gs256.gsl
}

这些依赖文件的路径都是相对路径,所以要求 copy这些文件到指定位置, 不然会初始化失败的

1
2
3
4
5
6
7
DT_SWORD dtEngineIniViaStream(DT_DTENGINE* engine, const DT_STREAM_DESC* sd, DT_INIT_INFO* init_info);

DT_STREAM(sd_ini,"config/dtype.inf")
stdout = fopen("/dev/null", "w");
auto init_ret = dtEngineIniViaStream(&ret, &sd_ini, DV_NULL);
fclose(stdout);
stdout = oldStdOut;

2. 加载字体

1
2
DT_ID_SWORD dtFontAddViaStream(DT_DTENGINE engine, DT_ID_SWORD font_format_id, const DT_CHAR* fuid, DT_SWORD fcnr, DT_ID_SWORD cmap_id, DT_ID_UBYTE caching, DT_ID_UBYTE hinting, const DT_STREAM_DESC* sd);
DT_SWORD dtFontGetMetrics(DT_DTENGINE engine, DT_ID_SWORD font_index, DT_UWORD* font_xbs, DT_UWORD* font_ybs, DT_RECT_SWORD* extent);

添加字体之后,一般返回值都是 ok的 引擎的字体目录索引,问题是加载不一定成功,等到 绘制时拿到的就是 0值的 advance 和 extent,实际是失败了
所以 加载完字体之后需要检查 该字体(加载结果)的字体信息(metrics)以检查实际 成功与否, 这便用上了 dtFontGetMetrics

加载 字体时,主要有个参数需要理解的是 font_format_id:

1
2
3
4
5
6
0 (DV_FONT_DTYPE) — for D-Type fonts (.dtf)
3 (DV_FONT_OPENTYPE_TTF) — for TrueType/OpenType fonts (.ttf/.otf) and font collections (.ttc) with TrueType outlines
5 (DV_FONT_OPENTYPE_CFF) — for OpenType (.otf) fonts and font collections (.ttc) with Type 2/CFF outlines
8 (DV_FONT_TYPE1) — for Adobe Type 1 fonts (.pfb or .pfa)
12 (DV_FONT_PSTYPE3) — for Adobe Type 3 PostScript fonts (.ps)
16 (DV_FONT_BARECFF) — for Bare CFF fonts (.cff)

加载字体时匹配字体后缀,尝试按某些 font_format_id 加载
configs 是 无构造开销的静态数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int Helper::DT::DTLoadFont(DT_DTENGINE engine, const char *font_path, int font_type_hint)
{
#define DTYPE "\u0000"
#define OPENTYPE_TTF "\u0003"
#define OPENTYPE_CFF "\u0005"
#define TYPE1 "\u0008"
#define PSTYPE3 "\u000c"
#define BARECFF "\u0010"
struct
{
const char *const suffix;
const char *candiates;
} static const configs[] = {
{".cff", BARECFF OPENTYPE_CFF OPENTYPE_TTF},
{".ps", PSTYPE3 },
{".pfb", TYPE1 },
{".pfa", TYPE1 },
{".otf", OPENTYPE_CFF OPENTYPE_TTF },
{".ttc", OPENTYPE_CFF OPENTYPE_TTF },
{".ttf", OPENTYPE_TTF },
{".dtf", DTYPE }
};

int font_index = -1;
for (auto &config : configs)
{
if (font_index == -1 && strlen(config.suffix) <= strlen(font_path) &&
!strcmp(config.suffix, font_path + (strlen(font_path) - strlen(config.suffix))))
{
std::vector<int> candiates;
if (font_type_hint > 0)
candiates.push_back(font_type_hint);
candiates.insert(candiates.end(), config.candiates, config.candiates + strlen(config.candiates));
for (auto font_format_id : candiates)
{
DT_STREAM_FILE(sd_font, font_path);
font_index = dtFontAddViaStream(engine, font_format_id, NULL, 0, -1, 0, 1, &sd_font);
if (font_index != -1)
{
DT_UWORD font_xbs, font_ybs;
DT_RECT_SWORD extent;
int result = dtFontGetMetrics(engine, font_index, &font_xbs, &font_ybs, &extent);
if (1 != result)
{
dtFontRemove(engine, font_index);
font_index = -1;
}
else
break;
}
}
}
}
return font_index;
}

3. 加载字形信息

1
2
3
DT_SWORD dtGlyphGetMetricsPlus(DT_DTENGINE engine, DT_ID_SWORD font_index, DT_ID_ULONG glyph_index, DT_ADVANCE* advance, DT_RECT_SWORD* extent, DT_SWORD reserved);
DT_SWORD dtCharGetMetricsPlus(DT_DTENGINE engine, DT_ID_SWORD font_index, DT_ID_ULONG char_code, DT_ADVANCE* advance, DT_RECT_SWORD* extent, DT_SWORD reserved);
DT_ID_ULONG dtFontGetGlyphIndex(DT_DTENGINE engine, DT_ID_SWORD font_index, DT_ID_ULONG char_code);

字符: char
字形: glyph

字符 对应一个字形(dtFontGetGlyphIndex),但是 glyph 还可以表示 非字符的 图形
所以此处使用字形来指代更一般的 绘制目标

字形信息主要是 步长, 字形范围矩形框

  • 步长: 建议的 字体占用矩形大小(至少会拿到占用长度)
  • 字形范围矩形框: 说明字形像素实际触及的 最左、最右、最上、最下 的横纵坐标值
    可视化观察这些参数的实际意义 见排版

dtCharGetMetricsPlus

Parameter Description
engine Handle of the previously created Standard Engine instance.
font_index Font index of the font or font instance in the Font Catalog.
char_code Unicode character code of the glyph.
glyph_index Font dependent index of the glyph.
advance A pointer to the DT_ADVANCE structure that will receive the horizontal and vertical origin, advance width and advance height of the glyph in font units.However, if you do not wish to receive this information, you may set the advance pointer to DV_NULL. Otherwise, this must be a valid pointer to the DT_ADVANCE structure.
extent A pointer to the DT_RECT_SWORD structure that will receive the glyph’s extent. This is the minimum (x, y) and maximum (x, y) coordinate for the glyph in font units. If you do not wish to receive this information, you may set the extent pointer to DV_NULL. Otherwise, this must be a valid pointer to the DT_RECT_SWORD structure.See the comments below for additional details.

4. 绘制

1
2
3
DT_SWORD dtOutputSetAsMDC(DT_DTENGINE engine, DT_ID_SWORD format, DT_ID_SWORD subformat, const DT_MDC* memory_surface, DT_SWORD clip_x, DT_SWORD clip_y, DT_SWORD clip_w, DT_SWORD clip_h);
DT_SWORD dtTypesetterSetTypeEffects(DT_DTENGINE engine, const DT_TYPE_EFFECTS_L* type, DT_UBYTE reserved);
DT_SWORD dtGlyphDoOutput(DT_DTENGINE engine, DT_ID_ULONG glyph_index, DT_FLOAT x, DT_FLOAT y, DT_SWORD reserved, DT_BMP* memory_bitmap);
  • dtOutputSetAsMDC: 设置输出位图平面
  • dtTypesetterSetTypeEffects: 设置 字形 映射(宽高,横纵倾斜…)
  • dtGlyphDoOutput: 输出 字形到位图

dtTypesetterSetTypeEffects 的主要参数 type 示例值
其中 transform.size_h 和size_v 将用来作为 说明 字形绘制大小的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DT_TYPE_EFFECTS type = {MYFONT_SIMPLE, 0, 0, {{200, 200, 0, 0, 20}}, {0, 0, 0, 0, 0, DV_SCALE_100}};

/*
The above initialization is the same as

type.font_index = MYFONT_SIMPLE;
type.reserved = 0;
type.descriptor = 0;
type.transform.params.size_h = 200;
type.transform.params.size_v = 200;
type.transform.params.skew_h = 0;
type.transform.params.skew_v = 0;
type.transform.params.rotation = 20;
type.linedecor.thickness = 0;
type.linedecor.segment = 0;
type.linedecor.shift = 0;
type.linedecor.dash_size = 0;
type.linedecor.flags = 0;
type.linedecor.scale_id = DV_SCALE_100;
*/

dtTypesetterSetTypeEffects(engine, &type, 0);

dtGlyphDoOutput 参数如下

Parameter Description
engine Handle of the previously created Standard Engine instance.
char_code Unicode character code of the glyph to be rendered.
glyph_index Font dependent index of the glyph to be rendered.
x Glyph’s X coordinate in pixels.
y Glyph’s Y coordinate in pixels.
reserved Reserved for future use. Must be set to 0.
memory_bitmap **

其中 char_code 是 字形的 unicode 值, 简单获取为 L'繁' 或者 wchar_t 的值 也可以 (只要 程序当前locale正确应该就ok)

5.排版

排版 主要关注的是,字形位置

术语定义

  • advance_x (x轴步长): 当前字形左边沿 到 下一字形 左边沿 的坐标差值
  • extent: 包裹 字形的最小矩形盒子(边当然平行于坐标轴)
    • xmn: x轴左界
    • xmx: x轴右界
    • ymn: y轴下界
    • ymx: y轴上界

字体基本信息

1
DT_SWORD dtFontGetMetrics(DT_DTENGINE engine, DT_ID_SWORD font_index, DT_UWORD* font_xbs, DT_UWORD* font_ybs, DT_RECT_SWORD* extent);
Parameter Description
engine Handle of the previously created Standard Engine instance.
font_index Font index of the font or font instance in the Font Catalog.
font_xbs A valid pointer to the DT_UWORD type that will receive the base font width (or units per em-square in the horizontal direction), in font units.However, if you do not wish to receive the value of the above parameter, you may set this pointer to DV_NULL.
font_ybs A valid pointer to the DT_UWORD type that will receive the base font height (or units per em-square in the vertical direction), in font units.However, if you do not wish to receive the value of the above parameter, you may set this pointer to DV_NULL.
extent A pointer to the DT_RECT_SWORD structure that will receive global font extent as defined in the font file. This is the minimum (x, y) and maximum (x, y) coordinate for the entire font in font units. If you do not wish to receive this information, you may set the extent pointer to DV_NULL. Otherwise, this must be a valid pointer to the DT_RECT_SWORD structure.

字体基本信息里的 extent基本用不上,但是,font_xbsfont_ybs 是两个很重要的数值
font_xbsfont_ybs 规定了 字体基本宽高

字形信息

通过 3-加载字形信息 一节的 api,我们加载到了 字形的 advance(包括 advance_x的信息) 和 extent

Demo演示

此节 说明以上 字体基本信息 dtTypesetterSetTypeEffectssize_h size_v 与 字形信息 advance_x extent的意义

  • 红色边框 : 由dtCharDoOutput::x、y 与 DT_STYLE_EFFECTS::size_h size_v 指定的范围,代表了 font_xbsfont_ybs 对应的范围
  • 绿色边框 : 由 字形信息 extent 指定的范围, 是字形的边界范围

output

字体和字形 信息如下
输出格式 见 demo source

1
2
3
4
5
6
7
8
9
        xybs:  1000 1000        extent:  -994 2930-1050 1810     /usr/share/fonts/opentype/noto/NotoSerifCJK-Light.ttc
ch: W advance: 0 880 1045 1000 extent: 13 1037 -7 725 Box: 100 100 100 100
ch: l advance: 0 880 330 1000 extent: 35 295 0 802 Box: 220 100 100 100
ch: ? advance: 0 880 1000 1000 extent: 51 956 400 509 Box: 340 100 100 100
ch: 1 advance: 0 880 472 1000 extent: 74 426 0 735 Box: 460 100 100 100
ch: . advance: 0 880 325 1000 extent: 105 219 -14 99 Box: 100 220 100 100
ch: ? advance: 0 880 1000 1000 extent: 43 322 -81 198 Box: 220 220 100 100
ch: , advance: 0 880 325 1000 extent: 65 219 -174 97 Box: 340 220 100 100
ch: , advance: 0 880 1000 1000 extent: 82 228 -191 113 Box: 460 220 100 100

Decorator 与 Proxy 都涉及到 兄弟类之间的 修饰
Decorator 目的在于 为兄弟类 增加更多功能
Proxy 在于 控制兄弟类的接口访问

Decorator

注:Decorator 类 假如不存在,scrollDecrator依然可以直接操作component,但是中间多一个Decorator,可以提供 dynamic_cast 这种方式检查VisualComponent 是否是 Decorator的子类;并且拥有更好的类层次设计。

BorderDecorator 通过在 虚接口 Draw中调用Decorator::Draw完成组件职责外,插入了自己的工作内容。
如上所示,DrawBorder就是 BorderDecorator新引入的功能。

诚然,通过继承比如(TextView)然后重写Draw也可以达到一样的效果,但是就避免不了类爆炸

Proxy

与Decorator大同小异,可将Proxy类看作 Decrator中的Decorator类,负责代理 调用组件方法的同时,插入一些逻辑控制代码。
在 组件方法调用时,插入逻辑控制代码,就是Proxy的核心

总结

1. 实现的Hook

这几个模式里,或多或少都跟 接口有关,这使得我们可以把一部分工作 委托给子类去实现。
这一点我们会在后边的 诸如桥模式、模版模式等中再次见到

善用接口

2. 隐藏可变部分

隐藏 可变的部分或者可能会变化的部分,可以提供更安全的接口和更简单的环境去 更改实现

Builder 模式

核心在于 隐藏 复杂对象创建过程过程中的细节
Builder 隐藏了 ComplicatedObj的创建细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct ComplicatedObj;
class Builder{
ComplicatedObj * obj;
Builder();

virtual void AddInt(int i);
virtual void AddFloat(double f);
virtual ComplicatedObj * GetObj();
}
struct ComplicatedObj{
std::vector<std::variant<int,char *>> comps;
};

// ... .cpp
struct ComplicatedObj{
std::vector<std::variant<int,double>> comps;
};

Builder::Builder{
obj = new ComplicatedObj;
}
Builder::AddInt(int i){
obj->comps.push_back(i);
}
Builder::AddFloat(double f){
obj->comps.push_back(f);
}
Builder::GetObj(){
auto ret = obj;
obj = 0;
return ret;
}

  • 意图: 某个接口–提供创建 一系列相关或相互依赖的对象 的接口,而无需指定这些对象的类

  • 解释: 这种接口 也可以说将创建一系列对象的行为 交由子类实现。这个视角看,抽象工厂是 提供 多个 工厂方法的接口

    这种设计模式简单的理解为 多个 工厂方法的集合就好

实例

考虑一个支持多种视感(look-and-feel)标准的用户易界面工具包,例如 Motif 和 PresentationManager。不同的视感风格为诸如滚动条、窗口和按钮等用户界面“窗口组件“定义不同的外观和行为。为保证视感风格标准间的可移植性,一个应用不应该为一个特定的视感外观硬编码它的窗口组件。在整个应用中实例化特定视感风格的窗口组件类将使得以后很难改变视感风格。

每一种视感标准都对应于一个具体的 WidgetFactory 子类。每一子类实现那些用于创建合适视感风格的窗口组件的操作。例如,MotifWidgetFactory 的 CreateScrollBar 操作实例化并返回一个 Motif 滚动条,而相应的 PMWidgetFactory 操作返回一个 Presentation Manager 的滚动条。客户仅通过 WidgetFactory 接口创建窗口组件,他们并不知道哪些类实现了特定视感风格的窗口组件。换言之,客户仅与抽象类定义的接口交互,而不使用特定的具体类的接口。

  1. gitignore 写 /build只忽视当前路径下build文件夹,但是build会忽视所有名为build的文件夹,包括子路径
  2. 片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)
  3. vetices数组里, 两个 float属性值 中间没有逗号分割也是可以通过的,应为第二个可能是负数表示
  4. glUniform4fv is for array,not for vec, follow pe1 get GL_INVALID_OPERATION
1
2
3
4
5
6
7
8
9
10
11
12
//in shader : uniform vec4 offset
GLfloat offset[]={sinf(tm)/4,cosf(tm)/4,0,0};
glUseProgram(program.getProgram());

auto uniforms=program.getUniforms();
auto p=program.getUniformLocation("offset");
auto pe=glGetError();

glUniform4fv(p,1,offset);
auto pe1=glGetError();
GL_INVALID_OPERATION;
// glUniform4f(p,sinf(tm)/4,cosf(tm)/4,0,0);
  1. 一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码
  2. 解绑VAO、VBO、EBO时,先解绑VAO
  3. // 不要忘记在设置uniform变量之前激活着色器程序!
  4. glUniform1i 这种函数如果给定的第一个参数是-1,即想要设置的变量未找到,这个函数不会设置全局的错误信息(通过glGetError()获取),默认其变量被优化啦
  5. glTexImage2D 设置图片格式参数要小心,如果是RGBA的图像被设置为RGB的格式就会出现显示错误,试试就知道咯

预测编码

帧间预测编码

  • 均方误差(MSE)最小准则
  • 绝对误差均值(MAD)最小准则
  • 快速搜索法 :一个16×16搜索区中心点为(i, j)=(0,0),以搜索区最大搜索长度的一半为步长,计算中心点及其周围8个邻近点的MAD值,如找到某个点MAD值最小,再以该点为中心,步长减为原来的一半,依次类推。到了第三步,再把步长减半,计算MAD值,其中MAD值最小点的运动矢量即为所求的值。快速算法的速度快,但不能保证全局最优。
  • 前向和后向运动估计: H.264编码标准和以往采用的视频压缩标准的最大不同在于,在运动估计过程中采用了多参考帧预测来提高预测精度。多参考帧预测就是在编解码端建一个存储M个重建帧的缓存,当前的待编码块可以在缓存内的所有重建帧中寻找最优的匹配块进行运动补偿,以便更好地去除时间域的冗余度。由于视频序列的连续性,当前块在不同的参考帧中的运动矢量也是有一定的相关性的。假定当前块所在帧的时间为t,则对应前面的多个参考帧的时间分别为:t-1,t-2,…则当在帧t-2中搜索当前块的最优匹配块时,可以利用当前块在帧t-1中的运动矢量MVNR来估测出当前块在帧t-2的运动矢量。
  • 亚像素位置的内插: 一个视频序列当采用1/2像素精度、1/4像素精度和1/8像素精度时编码效率的情况如图3.17所示。可以看出,1/4像素精度相对于1/2像素精度的编码效率有很明显的提高,但是1/8像素精度相对于1/4像素精度的编码效率除了在高码率情况下并没有明显的提高,而且1/8像素的内插公式更为复杂。因此,在H.264的制定过程中,1/8像素精度的运动矢量模型逐渐被取消,而只采用了1/4像素精度的运动矢量模型。

运动矢量在时空域的预测方式

  • 运动矢量空间域预测方式
    • 运动矢量中值预测(Median Prediction)。利用与当前块E相邻的左边块A,上边块B和右上方的块C的运动矢量,取其中值作为当前块的预测运动矢量。
      设E为当前宏块、宏块分割或者亚宏块分割,A在E的左侧,B在E的上方,C在E的右上方,如果E的左侧多于一个块,那么选择最上方的块作为A,在E的上方选择最左侧的块作为B。图3.18表示所有的块尺寸相同,图3.19表示邻近块尺寸不同时作为预测E的运动矢量的块的选择方法。
    • 上层预测(Uplayer Prediction
  • 匹配函数在时间域的预测方式。
    • 前帧对应块的预测(Corresponding-block Prediction)。利用前一帧中与当前块相同坐标位置的块在帧t′-1中搜索得到的最小SAD,作为当前搜索块在帧t′中进行搜索的SAD的预测值
    • 时间域的邻近参考帧预测(Neighboring Reference-frame Prediction)。如图3.26所示,假设当前块所在帧的时间为t,则当在前面的参考帧t′中搜索时,可以利用当前块在参考帧t′+1中搜索得到的最小SAD值SADNR作为当前块在帧t′搜索的SAD预测值
  1. 运动估计准则分类:

运动搜索的目的就是在搜索窗内寻找与当前块最匹配的数据块,这样就存在着如何判断两个块是否匹配的问题,即如何定义一个匹配准则。而匹配准则的定义与运算复杂度和编码效率都是直接相关的,通常有如下几类比较常用的匹配函数的定义:

设当前帧为f2,参考帧为f1。

(1)最小均方差函数(MSE)

(2)最小平均绝对值误差(MAD)

等效于常用的绝对差值和(SAD)准则,性能很好,而且对硬件的要求相对简单,因而得到了最广泛的应用。

(3)阈值差别计数(NTD)

  1. 运动搜索算法
  • 全局搜索算法
  • 分数精度搜索:1/k 像素精度搜索优化,对于低清晰度视频友好
  • 快速搜索算法:快速搜索算法和全局搜索算法相比,虽然只能得到次最佳的匹配结果,但在减少运算量方面效果显著
  • 混合搜索算法

变换编码和预测编码的比较

  • 由以上讨论可知,变换编码实现比较复杂,预测编码的实现相对容易,但预测编码的误差会扩散。以一行为例,由于后面的像素以前面的像素为参考,前面的像素的预测误差会逐步向后面的像素扩散。而且在二维预测时,误差会扩散至后面几行,形成区域误码。这样一来,对信道误码率的要求提高,一般要求不大于10-6。相比之下,变换编码则不会误码扩散,其影响只限制在一个块内,而且反变换后误码会均匀分散到块内的各个像素上,对视觉无甚影响。这时信道误码率一般要求不大于10-4即可。

    两者各有优缺,特别是变换编码随着VLSI技术的飞跃发展,实现起来十分容易。现实中,往往采用混合编码方法,即对图像先进行带有运动补偿的帧间预测编码,再对预测后的残差信号进行DCT变换。这种混合编码方法已成为许多视频压缩编码国际标准的基本框架。

cmake 构建系统

cmake build-system doc

cmake: crossplatform make
make:The Linux make command is used to build and maintain groups of programs and files from the source code. In Linux, it is one of the most frequently used commands by the developers. It assists developers to install and compile many utilities from the terminal. Linux make 命令用于从源代码构建和维护程序和文件组。在 Linux 中,它是开发人员最常用的命令之一。它帮助开发人员从终端安装和编译许多实用程序。

然而,windows上并没有make程序,(mingw-make那种不算)
cmake 表现得更先像是一个 build-tool generator,生成类似于make、ninja、vcproj或者codeblocks-proj(这个几乎就是makefile的,跟原始的codeblocks感觉出入有点大)的工程
由于make的构建系统极为简单,(ninja也不难,不过懒得学习ninja了),后边我们在考察cmake脚本的变化的时候可以到最终生成的makefile里去找他的变化,从而认识cmake

make

自然是需要简单的介绍一下make,如下是一个简单的makefile

1
2
test:test.cpp include/header.h
g++ test.cpp -Iinclude -lstdc++ -o test

make目标由三个部分组成

1
2
${targetname}: ${target_dependencies}
${build_command}

即是说,当 ${target_dependencies} 发生变化时,认为 ${targetname} 过时了,然后运行${build_command} 重新生成它

奇怪的问题:
1. 要是构建命令没有生成目标怎么办,比如g++ test.cpp -Iinclude -lstdc++ -o test_another_target

1
2
3
4
lull@DESKTOP-TUB0OQU:/mnt/e/CODE/doc/dev/common/proj/cmake/test$ make
g++ test.cpp -Iinclude -lstdc++ -o tests
lull@DESKTOP-TUB0OQU:/mnt/e/CODE/doc/dev/common/proj/cmake/test$ make
g++ test.cpp -Iinclude -lstdc++ -o tests
他每次都会重新调用构建命令,至于为何,请见(todo)

cmake 变量 和属性

正如你调试程序 一般都需要观察程序状态才能去推断程序的执行行为一样,你可以通过断点查看变量值,或者打印日志的方式去通知你你的程序的状态、
我们观察cmake的执行状态只能通过打印变量值的方式,如果你知道有什么cmake断点调试工具,请一定通知我(我好像发现了什么了不得的商机)

关于变量和属性的关系的理解,可以看一下include_directories., 这篇文档说的是变量CMAKE_INCLUDE_DIRECTORIES_BEFORE影响函数 include_directories 的默认行为,函数include_directories 会更改 当前 cmakefile的属性INCLUDE_DIRECTORIES ,然后当前cmakefile的属性INCLUDE_DIRECTORIES会被添加到当前CMakeLists文件中的目标的 属性INCLUDE_DIRECTORIES中去(注意目录和目标都有属性INCLUDE_DIRECTORIES)

如下是简单的打印语句

1
message("this message call print var of project name :${PROJECT_MAME}")

打印什么?

打印变量?,自然是要打印变量,但问题是哪些变量

首先,cmake可以选择 generator,

1
2
3
lull@DESKTOP-TUB0OQU:/mnt/e/CODE/doc/dev/common/proj/cmake/test$ cmake --help |grep generator
-G <generator-name> = Specify a build system generator.
KDevelop3 - Unix Makefiles = Generates KDevelop 3 project files.

我们选择生成器 为 Unix MakeFiles 时,cmake 的configure阶段会生成makefile,瞅瞅这些makfile对于理解cmake有益处

参照 makefile的目标的结构,我们主要关注三种属性及其相关的变量

name kind specificition
*INCLUDE_DIRECTORIES DC 文件的查找路径
*LINK_LIBRARIES C 链接的对象,如静态库
COMPILE_OPTIONS C 编译选项
COMPILE_DEFINITIONS C 编译预定义宏
*OUTPUT_DIRECTORY C 输出目录

以上几乎就是所有需要被关注的属性,官方属性文档在这里,kind是指种类:C:构建指令,D:依赖
更多私人的理解仿佛无根之萍的猜测,便不先拿出来了。

cmake生成makefile的流程(猜测)

cmake依然是逐行解释cmake脚本的,include指令就像是cpp的include预处理器一样工作,if-else、foreach也无需多说
就之前介绍的make的观点来说,我们关注三个东西,目标-依赖-构建命令

可以认为在处理完cmake脚本之前,cmake不会生成最终版本的 makefile,所有对目标属性有影响的语句都会工作(好废话)
处理完所有cmake脚本后,根据每个目标的属性去生成目标的依赖和构建指令
如果genrator比如Unix Makefile支持 目录式的属性声明(就是说一个makefile里的属性被继承给改makefile或子makefile里的目标们),那可能生成的结果的属性声明就会和 cmake本身的属性(全局,目录,目标,源文件)一样带有层次性

属性的工作原理

你可以在属性文档里查到如下的属性

1
2
3
4
5
Directories::INCLUDE_DIRECTORIES
Targets::INCLUDE_DIRECTORIES
Targets::INTERFACE_INCLUDE_DIRECTORIES
Targets:SYSTEM_INCLUDE_DIRECTORIES
Source Files::INCLUDE_DIRECTORIES

他们都是包含目录
但依我们前面所述,我们直接关注cmake的目标的属性,应为makefile就是直接处理cmake的目标的属性(当然 Source File ::: INCLUDE_DIRECTORIES比较特殊)而来的,那么这些属性如何影响最终的makefile? 如下是我的理解

1
2
3
4
2. target_include_directories(*** dirs ***)
根据调用参数 [SYSTEM] <INTERFACE|PUBLIC|PRIVATE> 追加 dirs 到 INCLUDE_DIRECTORIES | INTERFACE_INCLUDE_DIRECTORIES | INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
3.

BMP file format

image file 1440x900.bmp 1440x900.bmp

1
2
-rw-rw-r-- 1 lull lull 3888054 Mar  1 14:08 1440x900.bmp
-rw-rw-r-- 1 lull lull 3888000 Mar 1 14:08 1440x900.rgb
3888000 = 1440*900*3

.rgb 是裸rgb数据,在yuvview 指定 BGR8 1440*900 之后可以看到如.bmp一样的正常图像


开始分析 .bmp 文件 八进制编辑器打开bmp,我们只关注 前54个字节
Offset hex Offset dec Size Example Purpose
00 0 2 bytes 0x42 0x4D The header field used to identify the BMP and DIB file is 0x42 0x4D in hexadecimal, same as BM in ASCII. The following entries are possible:BMWindows 3.1x, 95, NT, … etc.BAOS/2 struct bitmap arrayCIOS/2 struct color iconCPOS/2 const color pointerICOS/2 struct iconPTOS/2 pointer
02 2 4 bytes 0xB6533B(小端序3888054) The size of the BMP file in bytes
06 6 2 bytes Reserved; actual value depends on the application that creates the image, if created manually can be 0
08 8 2 bytes Reserved; actual value depends on the application that creates the image, if created manually can be 0
0A 10 4 bytes 0x36 The offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found.

00 处指出头类型为 Windows Bitmap Header

0A处指出的头长度 0x36=54, 这就是这个bmp文件头的长度


于是我们接着看 Windows Bitmap Header
Offset (hex) Offset (dec) Size (bytes) Windows BITMAPINFOHEADER
0E 14 4 0x28=40 the size of this header, in bytes (40)
12 18 4 0xA0050000=1440 the bitmap width in pixels (signed integer)
16 22 4 0x84030000=900 the bitmap height in pixels (signed integer)
1A 26 2 0x0100 the number of color planes (must be 1)
1C 28 2 0x1800=24 the number of bits per pixel, which is the color depth of the image. Typical values are 1, 4, 8, 16, 24 and 32.
1E 30 4 the compression method being used. See the next table for a list of possible values
22 34 4 0x80533B00=1440*900=3888000 the image size. This is the size of the raw bitmap data; a dummy 0 can be given for BI_RGB bitmaps.
26 38 4 the horizontal resolution of the image. (pixel per metre, signed integer)
2A 42 4 the vertical resolution of the image. (pixel per metre, signed integer)
2E 46 4 the number of colors in the color palette, or 0 to default to 2n
32 50 4 the number of important colors used, or 0 when every color is important; generally ignored

Bitmap的头分两部分:

  • Bitmap file header: 12 字节
  • Window Bitmap Header: 40 字节
  1. 第一部分的头长度是标准,其中的前两个字段指定接下来的第二段头的标准
  2. 第二段头中指定该段头的长度

两部分加起来就是.bmp文件(带有bitmap头)相对于 原始rgb文件的 增量大小

参考
BMP file format wiki

工厂方法指的是 创建接口类实例的接口
工厂方法的核心在于 将所依赖的接口的实例化延迟到 子类

框架使用抽象类定义和维护对象之间的关系,于是框架中的某些顶层抽象类 并没有 能够实例化 其所依赖的接口类的能力–所以该顶层抽象类只能将 实例化 某些接口类 的方法 委托给自己的子类(那些有足够信息的子类)去实现这些接口

实例

如下的 IMachine::CreateComps 就是 工厂方法,没有定义返回值或者没有参数影响创建结果并不影响这是 工厂方法(见CMaine:::CreateComps 直接影响到 compA 和 compB 成员不是一样的么)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 框架接口层
struct IComp{
virtual void StartWork()=0;
};
struct IMachine{
virtual void StartAllComps{
CreateComps();
compA->StartWork();
compB->StartWork();
}
virtual void CreateComps()=0;
private:
IComp * compA,compB;
}

// 框架实现
struct CCompA:IComp{
void StartWork()override{}
};
struct CCompB:IComp{
void StartWork()override{}
};

struct CMachine:IMachine{
virtual void CreateComps()override{
compA = new CCompA;
compB = new CCompB;
}
}

另一方面,假如顶层抽象类有 对工厂方法的缺省实现,这个时候,工厂方法的存在就是对组件的扩展的支持—允许子类重载工厂方法

相关 设计模式

  • 模版模式:工厂模式也像 模版模式,由顶层类定义一部分行为,将对象创建这个过程留待子类实现
  • 抽象工厂方法:

延伸

连接平行类层次

当一个类将它的一些职责委托给一个独立的类的时候,就产生了平行类层次。考虑可以被交互操纵的图形;也就是说,它们可以用鼠标进行伸展、移动,或者旋转。实现这样一些交互并不总是那么容易,它通常需要存储和更新在给定时刻刻记录操纵状态的信息,这个状态仅仅在操纵时需要。因此它不需要被保存在图形对象中。此外,当用户操纵图形时,不同的图形有不同的行为。例如,将直线图形拉长可能会产生一个端点被移动的效果,而伸展正文图形则可能会改变行距。

有了这些限制,最好使用一个独立的 Manipulator 对象实现交互并保存所需要的任何与特定操纵相关的状态。不同的图形将使用不同的 Manipulator 子类来处理特定的交互。得到的 Manipulator 类层次与 Figure 类层次是平行(至少部分平行),如下图所示。

Figure 类提供了一个 CreateManipulator 工厂方法,它使得客户可以创建一个与 Figure 相对应的 Manipulator。Figure 子类重定义该方法以返回一个合适的 Manipulator 子类实例。做为一种选择,Figure 类可以实现 CreateManipulator 以返回一个默认的 Manipulator 实例,而 Figure 子类可以只是继承这个缺省实现。这样的 Figure 类不需要相应的 Manipulator 子类——因此该层次只是部分平行的。

注意工厂方法是怎样定义两个类层次之间的连接的。它将哪些类应一同工作工作的信息局部化了。