0%

个人日记

二零一五年七月九日,伴随着最后一门图形学交卷铃声的想起,我的大三正式结束了。首先很抱歉,最近一段时间一直在忙考试复习,时间也比较紧张,另外就是整理各种材料,还有准备一些面试,也算是忙得不亦乐乎,所以,这段时间也没有去编代码,大约也有一个月的时间没有更新博客了,有好多热心的朋友在我博客留言评论,也没有及时回复,在这里向大家说一声抱歉。放假回家之后,我会好好研究一下大家提出的意见和建议,给予大家回复的。 恩,接下来的日子应该会稍微轻松那么一点,自己的研究生路大体有了轮廓,有了规划。将来想从事的方向为信息安全方向,自己也做过一些项目和网站,对一些安全性问题要求也是比较高的,自己也对安全问题比较感兴趣,嗯。 现在时间是二零一五年七月十二日,昨天回家,收拾整顿一番,今天假期正式开始啦! 上学期是我大学的第六个学期了,对于保研来说,也着实是最后的一个重要的学期了,学习上也要更加重视。仔细回想起来,这个学期实际的项目开发没有做过多少,和同学开发了一个演唱会支付系统,又完善了一下自己之前的项目。学期后期,基本上就是准备一些面试和靠前复习,另外还有一些研究生招生的面试咨询等等,结果就不透露啦,自我感觉还是不错。 本来有好多话想说,然而这个时间,却不想多言了。 心里的石头还没落地,最近的日子,好好准备即将到来的面试。暑假到了,想要学习的东西还有很多,重新开启我的代码之路!Come On!

崔庆才

2015/7/12

C/C++

综述

通过上一节说的绘制 3D 图形基础,我们应该对绘制 3D 图形有了基本的认识,接下来我们就进行一个实例,绘制一个 3D 机器人。 本节我们要完成的任务有:

1.绘制一个仿真 3D 机器人(样式自选,参考例图),至少包含头、躯干、四肢三个部分. 2.对机器人填充颜色。 3.增加点光源,使得机器人更加真实。 4.实现交互,使得能够控制机器人进行旋转、前进、后退等动作。 键盘”w”:前进 键盘”s”:后退 键盘”a”:顺时针旋转 键盘”d”:逆时针旋转

接下来我们就一步步实现机器人的绘制吧。

绘制球体

绘制球体我们有两种方法,一个叫 glutSolidSphere,另一个叫 glutWireSphere,这两个的区别在于,一个绘制的是实心的球体,另一个绘制的是现状描绘而成的球体,在绘制过程中,我们可以通过一个参数来对其进行控制。 glutSolidSphere 是 GLUT 工具包中的一个函数,该函数用于渲染一个球体。球体球心位于原点。在 OpenGL 中默认的原点就是窗口客户区的中心。 函数原型

1
2
void glutSolidSphere(GLdouble radius , GLint slices , GLint stacks);
void glutWireSphere(GLdouble radius, GLint slices, GLint stacks );

radius,球体的半径 slices,以 Z 轴上线段为直径分布的圆周线的条数(将 Z 轴看成地球的地轴,类似于经线) stacks,围绕在 Z 轴周围的线的条数(类似于地球上纬线) 一般而言, 后两个参数赋予较大的值, 渲染花费的时间要长, 效果更逼真。 然而我们可以发现,这里并没有定义球中心的参数,所以,我们可以利用平移函数组合实现。即利用 glTranslated 函数来实现。最后,我们定义的画球体的方法如下

1
2
3
4
5
6
7
8
9
10
11
//画球
void drawBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glPopMatrix();
}

其中两个常量定义如下

1
2
#define SOLID 1
#define WIRE 2

在这里我们还用到了上一节所说的 PushMatrix 和 PopMatrix 方法。如果不熟悉,请查看上一节的内容。

绘制长方体

同样地,利用变换平移放缩的方法,再加上类库的绘制正方体的方法,我们也可以轻松地实现绘制长方体的方法。 正方体怎样变成长方体,很简单,拉伸一下就好了。所以,我们用到了 glScaled 方法。 绘制长方体的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
//画长方体
void drawSkewed(double l, double w, double h, double x, double y, double z, int MODE) {
glPushMatrix();
glScaled(l, w, h);
glTranslated(x, y, z);
if (MODE == SOLID) {
glutSolidCube(1);
} else if (MODE == WIRE) {
glutWireCube(1);
}
glPopMatrix();
}

这里仍然还是定义了绘图模式,是线条还是实体。

定义光照

在这里提供一篇博文,讲光照讲得比较细致 OPENGL 光照 那么在这里我就直接贴上光照设置的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void init() {
//定义光源的颜色和位置
GLfloat ambient[] = { 0.5, 0.8, 0.1, 0.1 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { -80.0, 50.0, 25.0, 1.0 };
//选择光照模型
GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
GLfloat local_view[] = { 0.0 };
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//设置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//设置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//启动光照
glEnable(GL_LIGHTING);
//启用光源
glEnable(GL_LIGHT0);
}

其中设置了光源颜色,位置等参数。

图形的移动

在这里我们实现了鼠标的监听旋转和键盘的监听旋转,在这里分别描述如下

1.鼠标监听

对于鼠标监听事件,在前面的文章中已经做了说明,如果大家不熟悉可以看下面这篇文章 鼠标监听 我们要实现的就是在拖动鼠标的时候实现图形的旋转功能。 在点击鼠标时,我们记录下来点击的位置,然后在鼠标移动的时候记录下当前坐标与上一个位置的坐标之差。通过定义一个角度的变量,每次鼠标移动的时候让整个图形旋转角度加上这个差值,然后重新绘制图形,就可以实现整个图形的 旋转了。 鼠标监听的两个方法如下,分别是鼠标点击和鼠标移动。 定义两个变量,旋转角度

1
2
int spinX = 0;
int spinY = 0;

然后定义两个鼠标事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 鼠标移动事件
void mouseMove(int x, int y) {
int dx = x - moveX;
int dy = y - moveY;
printf("dx;%dx,dy:%dy\n",dx,dy);
spinX += dx;
spinY += dy;
glutPostRedisplay();
moveX = x;
moveY = y;
}
//鼠标点击事件
void mouseClick(int btn, int state, int x, int y) {
moveX = x;
moveY = y;
}

恩,通过定义上面的方法,然后在 main 中加入监听。

1
2
3
4
//鼠标点击事件,鼠标点击或者松开时调用
glutMouseFunc(mouseClick);
//鼠标移动事件,鼠标按下并移动时调用
glutMotionFunc(mouseMove);

display 函数中,在绘图前调用该方法即可

1
2
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);

分别是绕 y 轴和 x 轴旋转一定的角度。这样就可以实现鼠标的监听了。

2.键盘监听

键盘事件也很简单,同样是监听按键的按下。其中 w、s 键是用来控制机器人的远近的。 定义一个变量叫 dis,代表远近 键盘事件函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//键盘事件
void keyPressed(unsigned char key, int x, int y) {
switch (key) {
case 'a':
spinX -= 2;
break;
case 'd':
spinX += 2;
break;
case 'w':
des += 2;
break;
case 's':
des -= 2;
break;
}
glutPostRedisplay();
}

在 main 函数中加入监听

1
2
//键盘事件
glutKeyboardFunc(keyPressed);

display 函数中加入如下的变换

1
2
3
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);

这样通过上述方法,我们便可以实现鼠标和键盘的监听了。

裁切物体

在这里我们可能要画一个半圆,那么最方便的方法就是裁切了,利用下面的函数,我们可以方便地实现。

void glClipPlane(GLenum plane, const GLdouble *equation);

定义一个裁剪平面。equation 参数指向平面方程 Ax + By + Cz + D = 0 的 4 个系数。 equation=(0,-1,0,0),前三个参数(0,-1,0)可以理解为法线向下,只有向下的,即 Y<0 的才能显示,最后一个参数 0 表示从 z=0 平面开始。这样就是裁剪掉上半平面。 equation=(0,1,0,0)表示裁剪掉下半平面, equation=(1,0,0,0)表示裁剪掉左半平面, equation=(-1,0,0,0)表示裁剪掉右半平面, equation=(0,0,-1,0)表示裁剪掉前半平面, equation=(0,0,1,0)表示裁剪掉后半平面 代码示例如下

1
2
3
4
5
GLdouble eqn[4]={0.0,0.0,-1.0,0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
glutSolidSphere(headR,slices,slices);
glDisable(GL_CLIP_PLANE0);

首先我们必须要定义一个 GLdouble 数组,然后利用 glClipPlane 方法来设置,然后开启裁切,最后关闭裁切。 利用类似的方法我们可以写出绘制半球的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//画半球
void drawHalfBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
GLdouble eqn[4]={0.0, 1.0, 0.0, 0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glDisable(GL_CLIP_PLANE0);
glPopMatrix();
}

恩,通过上述方法,我们可以方便地绘制出一个半球体。

绘制机器人

有了上述的铺垫,我们绘制机器人简直易如反掌,同样还可以实现各式各样的监听。 主要就是位置的确定了。 display 函数如下

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
void display(void) {
//清除缓冲区颜色
glClear(GL_COLOR_BUFFER_BIT);
//定义白色
glColor3f(1.0, 1.0, 1.0);
//圆点放坐标中心
glLoadIdentity();
//从哪个地方看
gluLookAt(-2.0, -1.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glPushMatrix();
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);
//头
drawBall(2, 0, 1, 0, SOLID);
//身体
drawSkewed(5, 4.4, 4, 0, -0.75, 0, SOLID);
//肩膀
drawHalfBall(1, 3.5, -2.1, 0, SOLID);
drawHalfBall(1, -3.5, -2.1, 0, SOLID);
//胳膊
drawSkewed(1, 3, 1, 3.5, -1.3, 0, SOLID);
drawSkewed(1, 3, 1, -3.5, -1.3, 0, SOLID);
//手
drawBall(1, 3.5, -6.4, 0, SOLID);
drawBall(1, -3.5, -6.4, 0, SOLID);
//腿
drawSkewed(1.2, 3, 2, 1, -2.4, 0, SOLID);
drawSkewed(1.2, 3, 2, -1, -2.4, 0, SOLID);
//脚
drawSkewed(1.5, 1, 3, 0.9, -9.2, 0, SOLID);
drawSkewed(1.5, 1, 3, -0.9, -9.2, 0, SOLID);
glPopMatrix();
glutSwapBuffers();
}

恩,通过调用这个函数我们便可以完成机器人的绘制了。

完整代码

在这里提供完整代码示例,仅供参考

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <glut.h>
#include <stdlib.h>
#include <stdio.h>

#define SOLID 1
#define WIRE 2

int moveX,moveY;
int spinX = 0;
int spinY = 0;
int des = 0;


void init() {
//定义光源的颜色和位置
GLfloat ambient[] = { 0.5, 0.8, 0.1, 0.1 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { -80.0, 50.0, 25.0, 1.0 };
//选择光照模型
GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
GLfloat local_view[] = { 0.0 };
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//设置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//设置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//启动光照
glEnable(GL_LIGHTING);
//启用光源
glEnable(GL_LIGHT0);
}

//画球
void drawBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glPopMatrix();
}

//画半球
void drawHalfBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
GLdouble eqn[4]={0.0, 1.0, 0.0, 0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glDisable(GL_CLIP_PLANE0);
glPopMatrix();
}

//画长方体
void drawSkewed(double l, double w, double h, double x, double y, double z, int MODE) {
glPushMatrix();
glScaled(l, w, h);
glTranslated(x, y, z);
if (MODE == SOLID) {
glutSolidCube(1);
} else if (MODE ==WIRE) {
glutWireCube(1);
}
glPopMatrix();
}

void display(void) {
//清除缓冲区颜色
glClear(GL_COLOR_BUFFER_BIT);
//定义白色
glColor3f(1.0, 1.0, 1.0);
//圆点放坐标中心
glLoadIdentity();
//从哪个地方看
gluLookAt(-2.0, -1.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glPushMatrix();
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);
//头
drawBall(2, 0, 1, 0, SOLID);
//身体
drawSkewed(5, 4.4, 4, 0, -0.75, 0, SOLID);
//肩膀
drawHalfBall(1, 3.5, -2.1, 0, SOLID);
drawHalfBall(1, -3.5, -2.1, 0, SOLID);
//胳膊
drawSkewed(1, 3, 1, 3.5, -1.3, 0, SOLID);
drawSkewed(1, 3, 1, -3.5, -1.3, 0, SOLID);
//手
drawBall(1, 3.5, -6.4, 0, SOLID);
drawBall(1, -3.5, -6.4, 0, SOLID);
//腿
drawSkewed(1.2, 3, 2, 1, -2.4, 0, SOLID);
drawSkewed(1.2, 3, 2, -1, -2.4, 0, SOLID);
//脚
drawSkewed(1.5, 1, 3, 0.9, -9.2, 0, SOLID);
drawSkewed(1.5, 1, 3, -0.9, -9.2, 0, SOLID);
glPopMatrix();
glutSwapBuffers();
}
//鼠标点击事件
void mouseClick(int btn, int state, int x, int y) {
moveX = x;
moveY = y;
GLfloat ambient[] = { (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, 0.1 };
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//启用光源
glEnable(GL_LIGHT0);
}

//键盘事件
void keyPressed(unsigned char key, int x, int y) {
switch (key) {
case 'a':
spinX -= 2;
break;
case 'd':
spinX += 2;
break;
case 'w':
des += 2;
break;
case 's':
des -= 2;
break;
}
glutPostRedisplay();
}
// 鼠标移动事件
void mouseMove(int x, int y) {
int dx = x - moveX;
int dy = y - moveY;
printf("dx;%dx,dy:%dy\n",dx,dy);
spinX += dx;
spinY += dy;
glutPostRedisplay();
moveX = x;
moveY = y;
}

void reshape(int w, int h) {
//定义视口大小
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
//投影显示
glMatrixMode(GL_PROJECTION);
//坐标原点在屏幕中心
glLoadIdentity();
//操作模型视景
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char** argv) {
//初始化
glutInit(&argc, argv);
//设置显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
//初始化窗口大小
glutInitWindowSize(500, 500);
//定义左上角窗口位置
glutInitWindowPosition(100, 100);
//创建窗口
glutCreateWindow(argv[0]);
//初始化
init();
//显示函数
glutDisplayFunc(display);
//窗口大小改变时的响应
glutReshapeFunc(reshape);
//鼠标点击事件,鼠标点击或者松开时调用
glutMouseFunc(mouseClick);
//鼠标移动事件,鼠标按下并移动时调用
glutMotionFunc(mouseMove);
//键盘事件
glutKeyboardFunc(keyPressed);
//循环
glutMainLoop();
return 0;
}

仅供参考,如有问题,敬请指正。 运行结果如下 QQ截图20150607013139 恩,大体就是这样。

总结

本次实验做的比较匆忙,只研究了一个晚上的时间,所以有些地方还是不太完善,希望发出来对小伙伴们有所启发,有所帮助。如果有问题,欢迎同我交流。谢大家!

C/C++

综述

在前面我们进行了 2D 图形的绘制,接下来,我们将步入 3D 图形的世界,绘制出一个 3D 机器人,好,废话不多说,让我们一起来迈入 3D 绘图之旅吧。

基本函数

那么在绘图之前呢,我们首先要介绍几个新的函数,弄懂了这几个函数我们才能方便地绘制出我们的 3D 机器人。 那么我们介绍一下gluPerspectivegluLookAt、glPushMatrix、glPopMatrix()函数

1.void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar)

参数说明: fovy,视角的大小,如果设置为 0,相当闭上眼睛,什么也看不到,如果为 180,则可以看到几乎所有的内容。一般设置为 60 即可。 aspect,就是实际窗口的纵横比,即 x/y,一般都是要按照窗口比例来,否则看到的图形和实际图形就会不一样了。 zNear,表示近处的裁面。 zFar,表示远处的裁面。 gluPerspective 这个函数指定了观察的视景体在世界坐标系中的具体大小,一般而言,其中的参数 aspect 应该与窗口的宽高比大小相同。比如说,aspect=2.0 表示在观察者的角度中物体的宽度是高度的两倍,在视口中宽度也是高度的两倍,这样显示出的物体才不会被扭曲。 详细说明 03087bf40ad162d9f337731d11dfa9ec8b13cdd1

2.void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz)

该函数定义一个视图矩阵,并与当前矩阵相乘。

第一组 eyex, eyey,eyez 相机在世界坐标的位置

第二组 centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置

第三组 upx,upy,upz 相机向上的方向在世界坐标中的方向

你把相机想象成为你自己的脑袋:

第一组数据就是脑袋的位置

第二组数据就是眼睛看的物体的位置

第三组就是头顶朝向的方向(因为你可以歪着头看同一个物体)

在这里,我们就涉及到一个十分重要的概念,坐标系。 在 3D 绘图中,坐标系是右手系,如果 x 轴指向我们的右侧,那么 y 则指向上侧,z 轴指向屏幕外。如下图所示。 20150606234046 理解了上述两个函数,下面我们来用一个小例子感受一下 运行如下代码

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
#include <glut.h>
#include <stdlib.h>


void display(void) {
//清除缓冲区颜色
glClear(GL_COLOR_BUFFER_BIT);
//定义白色
glColor3f(1.0, 1.0, 1.0);
//圆点放坐标中心
glLoadIdentity();
//从哪个地方看
gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glutWireTeapot(2);
//清除缓冲区刷新
glutSwapBuffers();
}

void reshape(int w, int h) {
//定义视口大小
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
//投影显示
glMatrixMode(GL_PROJECTION);
//坐标原点在屏幕中心
glLoadIdentity();
//操作模型视景
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char** argv) {
//初始化
glutInit(&argc, argv);
//设置显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
//初始化窗口大小
glutInitWindowSize(500, 500);
//定义左上角窗口位置
glutInitWindowPosition(100, 100);
//创建窗口
glutCreateWindow(argv[0]);
//清除颜色缓冲区
glClearColor(0.0, 0.0, 0.0, 0.0);
//显示函数
glutDisplayFunc(display);
//窗口大小改变时的响应
glutReshapeFunc(reshape);
//循环
glutMainLoop();
return 0;
}

运行结果如下 QQ截图20150606234448 在这里,我们定义了

1
gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

意思就是把我们的眼睛放到了(0,0,5)的位置,茶壶放到了原点,脑袋顶朝上,即正视茶壶。 如果我们改变了几个参数的值,例如

1
gluLookAt(0.0, 0.0, 5.0, 4.0, 1.0, 0.0, 0.0, 0.0, 1.0);

眼睛位置不变,茶壶位置变为(4,1,0),脑袋顶的方向朝屏幕外侧,那么看到的效果如下。 QQ截图20150606234708 大家可以通过更改这些参数的值来对视图进行变换。

3.glPushMatrix()、glPopMatrix()

这个方法在绘制 3D 图形时依然十分有用,因为我们需要绘制好多个图形,绘制多个不一样的图形必然要经过各种矩阵变换,所以,少不了的利用平移变换放缩的功能,所以为了保证每个图形不受影响,在绘制一个新图形前我们只要调用一下 glPushMatrix 函数,保存当前的变换矩阵,然后绘制完一个图形之后再调用 glPopMatrix 函数来恢复之前的变换矩阵就可以了。 比如,绘制球体

1
2
3
4
glPushMatrix();
glTranslated(x,y,z);
glutWireSphere(R,20,20);
glPopMatrix();

在两个函数中间我们可以进行任意的变换放缩等操作,然后绘制想要的图形即可。

绘图函数

接下来我们说一下一些常用的绘图函数,直接用类库函数来绘制即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void glutWireSphere(GLdouble radius, GLint slices, GLint stacks); 线框球
void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks); 实心球
void glutWireCube(GLdouble size); 线框立方体
void glutSolidCube(GLdouble size); 实心立方体
void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings); 线框圆环
void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings); 实心圆环
void glutWireIcosahedron(void); 线框20面体
void glutSolidIcosahedron(void); 实心20面体
void glutWireOctahedron(void); 线框8面体
void glutSolidOctahedron(void); 实心8面体
void glutWireTetrahedron(void); 线框4面体
void glutSolidTetrahedron(void); 实心4面体
void glutWireDodecahedron(GLdouble radius); 线框12面体
void glutSolidDodecahedron(GLdouble radius); 实心12面体
void glutWireCone(GLdouble radius, GLdouble height, GLint slices, GLint stacks); 线框圆锥体
void glutSolidCone(GLdouble radius, GLdouble height, GLint slices, GLint stacks); 实心圆锥体
void glutWireTeapot(GLdouble size); 线框茶壶
void glutSolidTeapot(GLdouble size); 实心茶壶

利用上面的绘图函数我们便可以方便地实现各种图形的绘制。

结语

那么本节就先告一段落,本节介绍了相关的绘图函数以及几个重要的函数,为接下来我们绘制机器人做下铺垫,希望小伙伴们可以好好理解。

PHP

综述

有时我们在textarea中输入了正常的文本内容,然而如果直接将文本取出来呈现在网页中,原本的换行和回车功能便会失效,完全丢失了原来的样式,这是因为在HTML中的空格和换行分别为 和
,所以我们需要将其替换才能变成我们想要的样式。 注:本篇内容非常基础,仅作参考,不喜勿喷。

替换函数

我们可以定义这样的一个函数,将原来的字符串替换为HTML文本。即把空格和换行替换掉即可

1
2
3
4
5
6
function htmtocode($content) { 
$content = htmlspecialchars($content, ENT_QUOTES);
$content = str_replace(" ", "&nbsp;", $content);
$content = str_replace("\n", "<br>",$content);
return $content;
}

通过上述方法,我们首先将HTML的特殊标记转化,然后替换掉空格和换行即可。

用法

写个最简单的小例子,在textarea中输入任意的文本,存储到数据库,然后再取出来,可以实现textarea输入的效果,完全一样。 数据库名称:demo 数据库表:

1
2
3
4
5
CREATE TABLE IF NOT EXISTS `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` text NOT NULL,
PRIMARY KEY (`id`)
)

插入数据:

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
<head>
<meta charset="utf-8">
</head>
<body>
<form action="" method="post">
<textarea name="text"></textarea>
<input type="submit" name="sub">
</form>
</body>
<?php
$con = mysql_connect("localhost", "root", "");
if (!$con) {
die("数据库连接失败");
}
mysql_select_db("demo",$con);
mysql_query("set names utf8");
$text = @$_POST['text'];
$sql = "insert into article(text) values ('$text')";
echo $sql;
$result = mysql_query($sql,$con);
if ($result) {
echo "录入成功";
} else {
echo "录入失败";
}

?>

查询数据:

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
<head>
<meta charset="utf-8">
</head>
<body>
<form action="" method="post">
<textarea name="text"></textarea>
<input type="submit" name="sub">
</form>
</body>
<?php
$con = mysql_connect("localhost", "root", "");
if (!$con) {
die("数据库连接失败");
}
mysql_select_db("demo",$con);
mysql_query("set names utf8");
$text = @$_POST['text'];
$sql = "select * from article";

$result = mysql_query($sql,$con);
if ($result) {
while ($row = mysql_fetch_array($result)) {
echo htmtocode($row['text']);
}
} else {
echo "录入失败";
}



function htmtocode($content) {
$content = htmlspecialchars($content, ENT_QUOTES);
$content = str_replace(" ", "&nbsp;", $content);
$content = str_replace("\n", "<br>",$content);
return $content;
}
?>

嗯,通过上面的方法我们便可以正常地实现输入内容和输出内容的匹配呈现。

JavaScript

前言

今天发现博客评论的默认头像不显示了,博客使用的是多说评论,不知道啥情况,之前设置过一个默认头像,然而今天图片都加载不出来了,可对于我这种强迫症来说,这能忍吗?你能忍我也不能忍啊,这必须不能忍啊,看到一个个的红叉号简直刺透了心。于是,我决定,必须要干掉它!

初步尝试

默认头像不行了,我就重新上传个默认头像对吧,简单粗暴易行。来来来,说试就试! 20150525114559 然而上传完毕之后,它是这个样子滴,依然不给我面子! 20150525114655 你在逗我吗?是不是多说服务器哪里出毛病了?

究其原因

然而,美好的事情总是意想不到的发生,当我知道真相的一刻,整个人都惊呆了。 浏览器里审查一下元素,看它到底引用了神马鬼。 20150525115038 好,我就打开这个链接,惊呆了。简直棒到无极限! 域名已过期!域名已过期!域名已过期!重要的话说三遍!简直溜到不行! 20150525115447 好吧,这真是太棒了!!我还能说些什么!!!

峰回路转

行,既然这样,那我自己提供 CDN,用我自己的七牛云存储! 那就用 jQuery 好了,方便简单,在</head>标签前面加上这么个代码,来试试。

1
2
3
4
5
$(document).ready(function(){
$('.ds-avatar img[src*="cdncache"]').each(function(){
$(this).attr("src",'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525112155.jpg');
})
});

意思是,在页面加载完成之后,把 src 里面包含 cdncache 的多说头像 img 替换掉。 这句话最好加在所有 JS 的最后面,要不然可能不会生效。JS 执行是有先后顺序的。 这样,我们就会发现,头像已经奇迹般地发生了变化。 20150525123151 本身有头像的仍然是那个头像,使用默认头像的已经替换了新头像。

更上一层

然而这并不能满足我的愿望啊,我想要多变的头像样式,就是这么任性! 来来来,改代码,我再传上几个头像,让默认头像的孩纸们随机地使用我的头像,一定是炫爆了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
$(document).ready(function(){
var imgs = new Array();
imgs[0] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525111154.jpg';
imgs[1] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525111447.jpg';
imgs[2] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525112058.jpg';
imgs[3] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525112112.jpg';
imgs[4] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525112129.jpg';
imgs[5] = 'http://cdn.cuiqingcai.com/wp-content/uploads/2015/05/20150525112155.jpg';
$('.ds-avatar img[src*="cdncache"]').each(function(){
var rand = Math.floor(Math.random()*imgs.length);
$(this).attr("src",imgs[rand]);
})
});
</script>

嗯,把刚才代码优化成这样,我们就可以发现,整个网站的头像就被替换成我们想要的效果了! 20150525123537 简直酷炫!小伙伴们可以尝试一下!

C/C++

综述

在上一篇文章中我们已经实现了图形的取点绘制,接下来我们还要实现的功能有图形的删除、移动以及存盘读盘功能。 概述如下

  • 鼠标点击某个绘制的图形,将其删除
  • 鼠标拖动某个图形,可以对其随意拖拽改变位置
  • 点击存盘,将图形的位置及颜色保存到文件
  • 点击读盘,将图形的位置颜色从文件中读取出来并重新绘制

好,接下来我们在上一篇的基础上进行一步步的说明。

图形删除

首先像之前一样,我们定义一个删除的按钮,指定好颜色。

1
GLubyte delBtn[3] = {30, 30, 30};

在 display 函数中绘制一个删除的按钮

1
glColorCircle(220, 280, 10, delBtn);

顶部变变成了这个样子,其中最左边的那个按钮便是删除按钮 QQ截图20150511124225 接下来我们想一下删除的实现。 首先我们点击删除按钮之后要设置一下 drawStatus,比如设置为 2 代表删除。 然后鼠标点击下方任意位置时,如果当前 drawStatus 为 2,如果鼠标处在一个多边形内部,我们就把它删除掉。 那么就会出现两个问题了,怎样判断点击的点处在多边形的内部?怎样执行删除操作? 下面我们分别来研究一下

1. 判断点在多边形内部

思路:求解通过该点的水平线与多边形各边的交点 ,如果交点为奇数,那么就判定为在多边形内部,否则在外部。 因此,我的算法实现如下,传入的参数一个是 ploygon 结构体,结构体中包含了顶点坐标,另外就是这个点的 x,y 坐标。 思路参考来源: 判断点在多边形内的多种写法 经过我的改写,变成了如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 判断一个点是否在多边形内部 */
bool pointInPoly(polygon poly, int x, int y){
int nCross = 0;
for (int i = 0; i < poly.verNum; i++) {
int p1x = poly.x[i];
int p1y = poly.y[i];
int p2x = poly.x[(i + 1) % poly.verNum];
int p2y = poly.y[(i + 1) % poly.verNum];
/* 平行或在延长线上,忽略 */
if (p1y == p2y || y < min(p1y, p2y) || y > max(p1y, p2y)) {
continue;
}
/* 计算交点横坐标 */
double m = (double)(y - p1y) * (double)(p2x - p1x) / (double)(p2y - p1y) + p1x;
if ( m > x ) nCross++;
}
return (nCross % 2 == 1);
}

以上便是判断在多边形内部的算法。

2.删除图形实现

好了,有了上面的算法,我们就依次查找我们声明的 polygons 数组,找到第一个符合要求的多边形结构体 polygon 返回它的索引。 这个方法就是传入了点击点的位置 x,y 坐标,然后判断是在哪个多边形内,然后移除之。如果没有找到,则不执行删除操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 删除多边形 */
void deletePoly(int x, int y) {
int delenum = -1;
for (int i = 0; i< con; i++) {
if (pointInPoly(polygons[i], x, y)) {
delenum = i;
}
}
if (delenum != -1) {
removePoly(delenum);
}
/* 重新绘制 */
glutPostRedisplay();
}

其中 removePoly(delenum) 就是通过传入这个结构体的索引,然后将它从 polygons 里面删除即可,方法实现如下

1
2
3
4
5
6
7
8
9
10
11
/* 删除多边形 */
void removePoly(int index)
{
if (index>con) {
printf("Out Of Index\n");
}
for (int i = index; i < con; i++) {
polygons[i] = polygons[i+1];
}
con--;
}

其实就是相当于从数组中移除一个元素,然后后面的元素依次前移。 恩好了,准备工作都做好了,我们只需要在点击了删除按钮之后,调用一下 deletePoly 函数即可。 把原来的 mouseClick 方法改写如下即可,代码较长,不完全展开,双击查看完整代码

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
56
57
58
59
60
61
62
63
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
} else if (sameColor(color, delBtn)) {
/* 删除时设置drawStatus为2 */
drawStatus = 2;
printf("drawStatus:%d\n", drawStatus);
}

/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}else if (drawStatus == 2) {
/* 删除图形 */
deletePoly(x, y);
}
}
}
}

经过上面的步骤,我们便可以很方便地实现图形的删除了。运行结果如下

[embed]http://cdn.cuiqingcai.com/robot2.mp4[/embed]

嗯,就是这样,现在删除就大功告成了。

移动图形

像之前一样,定义一个移动图形的按钮。在此不再赘述

1
GLubyte moveBtn[3] = {40, 40, 40};
1
glColorCircle(190, 280, 10, moveBtn);

那么图形移动怎么实现?思路如下 首先利用判断点击的点是否在多边形内部的算法,找到需要移动的多边形。然后在监听鼠标移动的事件中设置这个多边形各个顶点的相对移动距离等于鼠标相对移动距离即可。 首先,移动的 drawSatus 我们设置为 3,在 mouseClick 方法中加入如下部分,在哪加你应该已经知道了。

1
2
3
4
5
else if (sameColor(color, moveBtn)) {
/* 移动时设置drawStatus为3 */
drawStatus = 3;
printf("drawStatus:%d\n", drawStatus);
}

在 mouseMove 方法中(另一个监听鼠标移动的事件)中首先判断当前的 drawStatus 如果为 3,那么就执行移动操作。 加入如下的方法即可。

1
2
3
4
5
6
7
8
/* 鼠标移动事件 */
void mouseMove(int x, int y)
{
if(drawStatus == 3 && moveNum != -1){
printf("move x:%d,y:%dmoveNum:%d\n", x, y,moveNum);
movePoly(x, y);
}
}

这里两点需要说明,moveNum 就是我们需要移动的图形的索引,定义为了全局变量。在鼠标点击时,首先判定点击点在哪个图形中,然而因为有两个鼠标事件(一个是点击,一个是移动),所以就把这个变量定义为全局变量,来标记该移动哪个图形。moveNum 的初始值为-1,如果为-1,那么就代表没有选中任何图形,不移动。如果不是-1,就进行移动。 其次,movePloy 方法实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 移动多边形 */
void movePoly(int x, int y) {
/* 取到要移动的图形 */
int verNum = polygons[moveNum].verNum;
printf("verNum%d,x%d,y%d,clickX%d,clickY%d\n",verNum,x,y,clickX,clickY);
for (int i = 0; i < verNum; i++) {
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
printf("polyx%d\n",polygons[moveNum].x[i]);
}
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;
/* 重新绘制 */
glutPostRedisplay();
}

这个方法的实现有一部分需要说明,首先是 clickX,clickY。 因为在移动时移动的是相对距离。所以,鼠标每移动一个像素,我们就要计算下一步鼠标移动的位置和上一个位置的差值,所以每次移动我们都需要标记一下点击的位置。所以上面的方法中才有了

1
2
3
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;

然后,在图形移动时,我们需要移动的是相对位置。所以我们将一个图形的所有顶点进行遍历,加上鼠标移动的相对位置即可。就是如下的实现

1
2
3
4
for (int i = 0; i < verNum; i++) {
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
}

另外,mouseClick 方法中我们需要调用一下即可。mouseClick 方法现在改写如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 赋值全局变量 */
clickX = x;
clickY = y;
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
} else if (sameColor(color, delBtn)) {
/* 删除时设置drawStatus为2 */
drawStatus = 2;
printf("drawStatus:%d\n", drawStatus);
} else if (sameColor(color, moveBtn)) {
/* 移动时设置drawStatus为3 */
drawStatus = 3;
printf("drawStatus:%d\n", drawStatus);
}

/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}else if (drawStatus == 2) {
/* 删除图形 */
deletePoly(x, y);
} else if(drawStatus == 3) {
moveNum = -1;
for (int i = 0; i< con; i++) {
if (pointInPoly(polygons[i], x, y)) {
moveNum = i;
}
}
}
}
}
}

好,现在就大功告成了,看一下运行结果吧

[embed]http://cdn.cuiqingcai.com/robot3.mp4[/embed]

现在完整的代码如下,代码过长,不完全显示,双击查看完整代码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
#include <glut.h>
#include <math.h>
#include <stdio.h>
#include <iostream>

using namespace std;

#define PI 3.1415926
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 窗口长宽的一半 */
int halfWidth, halfHeight;
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;
int moveNum = -1;
/* 定义点击点的x,y*/
int clickX, clickY;
/* 多边形结构体 */
struct polygon {
/* 顶点坐标 */
int x[MAX_VERTEX];
int y[MAX_VERTEX];
/* 定义第几个顶点 */
int verNum;
GLubyte color[3];
};
/* 各种颜色 */
GLubyte border[3] = { 0, 0, 0 };
GLubyte grey[3] = { 195, 195, 195 };
GLubyte yellow[3] = { 255, 243, 0 };
GLubyte red[3] = { 237, 28, 36 };
GLubyte darkGrey[3] = { 126, 126, 126 };
GLubyte white[3] = { 255, 255, 255 };
GLubyte startBtn[3] = { 10, 10, 10 };
GLubyte endBtn[3] = { 20, 20, 20 };
GLubyte delBtn[3] = { 30, 30, 30 };
GLubyte moveBtn[3] = { 40, 40, 40 };
/* 当前颜色 */
GLubyte nowColor[3] = { 0, 0, 0 };
/* 声明多边形数组 */
polygon polygons[MAX_PLOY];
/* 记录画了几个多边形 */
int con = 0;

/* 绘制多边形 */
void glPolygons()
{
if ( con >= 0 )
{
for ( int i = 0; i <= con; i++ )
{
/* 取到这个多边形 */
polygon poly = polygons[i];
/* 画封闭线 */
glBegin( GL_LINE_LOOP );
int num = poly.verNum;
printf( "num:%d\n", num );
for ( int j = 0; j < num; j++ )
{
glColor3ub( poly.color[0], poly.color[1], poly.color[2] );
glVertex2d( poly.x[j] - halfWidth, halfHeight - poly.y[j] );
printf( "polyx:%d,polyy:%d", poly.x[j], poly.y[j] );
}
/* 结束画线 */
glEnd();
/* 刷新 */
glFlush();
}
}
}


/* 绘制填充的圆形 */
void glColorCircle( int x, int y, int R, GLubyte color[3] )
{
/* 开始绘制曲线 */
glBegin( GL_POLYGON );
/* 设置颜色 */
glColor3ub( color[0], color[1], color[2] );
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for ( double i = 0; i <= 2 * PI; i += delta_angle )
{
/* 绝对定位加三角函数值 */
double vx = x + R * cos( i );
double vy = y + R*sin( i );
glVertex2d( vx, vy );
}
/* 结束绘画 */
glEnd();
glFlush();
}


/* 画矩形,传入的是左下角XY坐标和右上角XY坐标 */
void glRect( int leftX, int leftY, int rightX, int rightY )
{
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 左下角 */
glVertex2d( leftX, leftY );
/* 右下角 */
glVertex2d( rightX, leftY );
/* 右上角 */
glVertex2d( rightX, rightY );
/* 左上角 */
glVertex2d( leftX, rightY );
/* 结束画线 */
glEnd();
}


/* 画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标 */
void glRoundRec( int centerX, int centerY, int width, int height, float cirR )
{
/* 二分之PI,一个象限的角度 */
float PI_HALF = PI / 2;
/* 划分程度,值越大画得越精细 */
float divide = 20.0;
/* 圆角矩形的坐标 */
float tx, ty;
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 四个象限不同的操作符 */
int opX[4] = { 1, -1, -1, 1 };
int opY[4] = { 1, 1, -1, -1 };
/* 用来计数,从第一象限到第四象限 */
float x = 0;
/* x自增时加的值 */
float part = 1 / divide;
/* 计算内矩形宽高一半的数值 */
int w = width / 2 - cirR;
int h = height / 2 - cirR;
/* 循环画线 */
for ( x = 0; x < 4; x += part )
{
/* 求出弧度 */
float rad = PI_HALF * x;
/* 计算坐标值 */
tx = cirR * cos( rad ) + opX[(int) x] * w + centerX;
ty = cirR * sin( rad ) + opY[(int) x] * h + centerY;
/*传入坐标画线 */
glVertex2f( tx, ty );
}
/* 结束画线 */
glEnd();
}


/* 画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径 */
void glArc( double x, double y, double start_angle, double end_angle, double radius )
{
/* 开始绘制曲线 */
glBegin( GL_LINE_STRIP );
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for ( double i = start_angle; i <= end_angle; i += delta_angle )
{
/* 绝对定位加三角函数值 */
double vx = x + radius * cos( i );
double vy = y + radius*sin( i );
glVertex2d( vx, vy );
}
/* 结束绘画 */
glEnd();
}


/* 画圆 */
void glCircle( double x, double y, double radius )
{
/* 画全圆 */
glArc( x, y, 0, 2 * PI, radius );
}


/* 画三角形,传入三个点的坐标 */
void glTri( int x1, int y1, int x2, int y2, int x3, int y3 )
{
/* 画封闭线 */
glBegin( GL_LINE_LOOP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/*三点 */
glVertex2d( x3, y3 );
/* 结束画线 */
glEnd();
}


/* 画线,传入两点坐标 */
void glLine( int x1, int y1, int x2, int y2 )
{
/* 画封闭线 */
glBegin( GL_LINE_STRIP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/* 结束画线 */
glEnd();
}


/* 函数用来画图 */
void display( void )
{
/* GL_COLOR_BUFFER_BIT表示清除颜色 */
glClear( GL_COLOR_BUFFER_BIT );
/* 设置画线颜色 */
glColor3f( 0.5, 0.5, 0.5 );
/* 画点大小 */
glPointSize( 2 );
/* 画圆角矩形,大肚子 */
glRoundRec( 0, 0, 146, 120, 15 );
/* 画圆,中间小圈 */
glCircle( 0, 0, 10 );
/* 画矩形,脖子 */
glRect( -25, 60, 25, 76 );
/* 画圆角矩形,大脸 */
glRoundRec( 0, 113, 128, 74, 10 );
/* 两个眼睛 */
glCircle( -30, 111, 10 );
glCircle( 30, 111, 10 );
/* 两条天线 */
glLine( -35, 150, -35, 173 );
glLine( 35, 150, 35, 173 );
/* 圆角矩形,两个耳朵 */
glRoundRec( 81, 115, 20, 34, 5 );
glRoundRec( -81, 115, 20, 34, 5 );
/* 圆弧,画嘴 */
glArc( 0, 133, 11 * PI / 8, 13 * PI / 8, 45 );
/* 画三角,肚子里的三角 */
glTri( -30, -15, 30, -15, 0, 28 );
/* 画矩形,胳膊连接处 */
glRect( -81, 43, -73, 25 );
glRect( 81, 43, 73, 25 );
/* 画矩形,上臂 */
glRect( -108, 45, -81, 0 );
glRect( 108, 45, 81, 0 );
/* 画矩形,中臂 */
glRect( -101, 0, -88, -4 );
glRect( 101, 0, 88, -4 );
/* 画矩形,下臂 */
glRect( -108, -4, -81, -37 );
glRect( 108, -4, 81, -37 );
/* 画圆形,手掌 */
glCircle( -95, -47, 10 );
glCircle( 95, -47, 10 );

/* 绘制取色盘 */
glColorCircle( -280, 280, 10, red );
glColorCircle( -250, 280, 10, yellow );
glColorCircle( -220, 280, 10, grey );
glColorCircle( -180, 280, 10, nowColor );
glColorCircle( 250, 280, 10, startBtn );
glColorCircle( 280, 280, 10, endBtn );
glColorCircle( 220, 280, 10, delBtn );
glColorCircle( 190, 280, 10, moveBtn );
/* 保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待 */
/* 绘制多边形 */
glPolygons();
glFlush();
}


/* 窗口大小变化时调用的函数 */
void ChangeSize( GLsizei w, GLsizei h )
{
/* 避免高度为0 */
if ( h == 0 )
{
h = 1;
}
/* 定义视口大小,宽高一致 */
glViewport( 0, 0, w, h );
int half = 300;
/* 定义宽高的一半 */
halfHeight = 300;
halfWidth = 300;
/* 重置坐标系统,使投影变换复位 */
glMatrixMode( GL_PROJECTION );
/* 将当前的用户坐标系的原点移到了屏幕中心 */
glLoadIdentity();
/* 定义正交视域体 */
if ( w < h )
{
/* 如果高度大于宽度,则将高度视角扩大,图形显示居中 */
glOrtho( -half, half, -half * h / w, half * h / w, -half, half );
} else {
/* 如果宽度大于高度,则将宽度视角扩大,图形显示居中 */
glOrtho( -half * w / h, half * w / h, -half, half, -half, half );
}
}


/* 判断两个颜色是否相等 */
bool sameColor( GLubyte color1[3], GLubyte color2[3] )
{
if ( color1[0] - color2[0] < 5 && color1[1] - color2[1] < 5 && color1[2] - color2[1] < 5 )
{
return(true);
} else {
return(false);
}
}


/* 画点 */
void glPoints( int x, int y )
{
glBegin( GL_POINTS );
/* 点直接设置为黑色 */
glColor3ub( 0, 0, 0 );
glVertex2d( x - halfWidth, halfHeight - y );
glEnd();
glFlush();
}


/* 判断一个点是否在多边形内部 */
bool pointInPoly( polygon poly, int x, int y )
{
int nCross = 0;
for ( int i = 0; i < poly.verNum; i++ )
{
int p1x = poly.x[i];
int p1y = poly.y[i];
int p2x = poly.x[(i + 1) % poly.verNum];
int p2y = poly.y[(i + 1) % poly.verNum];
/* 平行或在延长线上,忽略 */
if ( p1y == p2y || y < min( p1y, p2y ) || y > max( p1y, p2y ) )
{
continue;
}
/* 计算交点横坐标 */
double m = (double) (y - p1y) * (double) (p2x - p1x) / (double) (p2y - p1y) + p1x;
if ( m > x )
nCross++;
}
return(nCross % 2 == 1);
}


/* 移动多边形 */
void movePoly( int x, int y )
{
/* 取到要移动的图形 */
int verNum = polygons[moveNum].verNum;
printf( "verNum%d,x%d,y%d,clickX%d,clickY%d\n", verNum, x, y, clickX, clickY );
for ( int i = 0; i < verNum; i++ )
{
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
printf( "polyx%d\n", polygons[moveNum].x[i] );
}
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;
/* 重新绘制 */
glutPostRedisplay();
}


/* 删除多边形 */
void removePoly( int index )
{
if ( index > con )
{
printf( "Out Of Index\n" );
}
for ( int i = index; i < con; i++ )
{
polygons[i] = polygons[i + 1];
}
con--;
}


/* 删除多边形 */
void deletePoly( int x, int y )
{
int delenum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
delenum = i;
}
}
if ( delenum != -1 )
{
removePoly( delenum );
}
/* 重新绘制 */
glutPostRedisplay();
}


/*鼠标点击事件 */
void mouseClick( int btn, int state, int x, int y )
{
/* 赋值全局变量 */
clickX = x;
clickY = y;
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if ( btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
{
/* 如果在颜色选项卡中点击 */
if ( y < optionHeight && x < optionWidth )
{
glReadPixels( x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor );
printf( "r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2] );
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle( -180, 280, 10, nowColor );
/* 如果点击的是右侧选项按钮 */
}else if ( y < optionHeight && x > optionWidth )
{
/* 取当前的点 */
GLubyte color[3];
glReadPixels( x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color );
printf( "r:%d,g:%d,b:%d\n", color[0], color[1], color[2] );
/* 如果点击了开始绘制的按钮 */
if ( sameColor( color, startBtn ) )
{
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf( "drawStatus:%d\n", drawStatus );
/* 如果点击了结束绘制的按钮 */
} else if ( sameColor( color, endBtn ) )
{
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf( "drawStatus:%d\n", drawStatus );
} else if ( sameColor( color, delBtn ) )
{
/* 删除时设置drawStatus为2 */
drawStatus = 2;
printf( "drawStatus:%d\n", drawStatus );
} else if ( sameColor( color, moveBtn ) )
{
/* 移动时设置drawStatus为3 */
drawStatus = 3;
printf( "drawStatus:%d\n", drawStatus );
}

/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if ( drawStatus == 0 )
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints( x, y );
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf( "polyColor%d%d%d\n", polygons[con].color[0], polygons[con].color[1], polygons[con].color[2] );
polygons[con].verNum++;
printf( "con:%d,verNum:%d\n", con, polygons[con].verNum );
}else if ( drawStatus == 2 )
{
/* 删除图形 */
deletePoly( x, y );
} else if ( drawStatus == 3 )
{
moveNum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
moveNum = i;
}
}
}
}
}
}


/* 鼠标移动事件 */
void mouseMove( int x, int y )
{
if ( drawStatus == 3 && moveNum != -1 )
{
printf( "move x:%d,y:%dmoveNum:%d\n", x, y, moveNum );
movePoly( x, y );
}
}


/*程序入口 */
int main( int argc, char *argv[] )
{
/* 对GLUT进行初始化,并处理所有的命令行参数 */
glutInit( &argc, argv );
/* 指定RGB颜色模式和单缓冲窗口 */
glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE );
/* 定义窗口的位置 */
glutInitWindowPosition( 100, 100 );
/* 定义窗口的大小 */
glutInitWindowSize( 600, 600 );
/* 创建窗口,同时为之命名 */
glutCreateWindow( "OpenGL" );
/* 设置窗口清除颜色为白色 */
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
/* 参数为一个函数,绘图时这个函数就会被调用 */
glutDisplayFunc( &display );
/* 参数为一个函数,当窗口大小改变时会被调用 */
glutReshapeFunc( ChangeSize );
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc( mouseClick );
/*鼠标移动事件,鼠标按下并移动时调用 */
glutMotionFunc( mouseMove );
/* 该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止 */
glutMainLoop();
/*程序返回 */
return(0);
}

以上,就可以实现图形的移动和删除了。

存盘和读盘

这个很简单,存盘我们只需要把所有图形的位置以及颜色输出到一个文本中,然后在读盘时,把文本中所有内容读入并存储到多边形结构体数组中,然后重新绘制即可。 代码的讲解直接写在注释中,比较好理解

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
56
57
58
/* 保存所有的图形 */
void savePolygons()
{
FILE *fp;
int i;
if ((fp = fopen("shapes.txt", "w")) == NULL)
{
printf("不能打开文件!");
return;
}
/* 保存一共多少个图形 */
fprintf(fp, "%d", con);
fprintf(fp, "\n");
for (i = 0; i < con; i++){
/* 保存颜色值 */
fprintf(fp, "%d %d %d ", polygons[i].color[0], polygons[i].color[1], polygons[i].color[2]);
/* 保存顶点数量 */
fprintf(fp, "%d ", polygons[i].verNum);
/* 保存顶点坐标 */
for (int j = 0; j < polygons[i].verNum; j++)
fprintf(fp, "%d ", polygons[i].x[j]);
for (int j = 0; j < polygons[i].verNum; j++)
fprintf(fp, "%d ", polygons[i].y[j]);
fprintf(fp, "\n");
}
fclose(fp);
printf("保存成功。\n");
}

/* 读取存档 */
void readPolygons()
{
FILE *fp;
int i;
if ((fp = fopen("shapes.txt", "r")) == NULL)
{
printf("不能打开文件!");
return;
}
fscanf(fp, "%d", &con);
printf("count%d",con);
fscanf(fp, "\n");
for (i = 0; i < con; i++){
/* 读取颜色值 */
fscanf(fp, "%d %d %d ", &polygons[i].color[0], &polygons[i].color[1], &polygons[i].color[2]);
/* 读取顶点数量 */
fscanf(fp, "%d ", &polygons[i].verNum);
/* 读取顶点坐标 */
for (int j = 0; j < polygons[i].verNum; j++)
fscanf(fp, "%d ", &polygons[i].x[j]);
for (int j = 0; j < polygons[i].verNum; j++)
fscanf(fp, "%d ", &polygons[i].y[j]);
fscanf(fp, "\n");
}
fclose(fp);
printf("读取成功。\n");

}

我们只需要再定义两个按钮,再 display 中画上这俩按钮

1
2
GLubyte storeBtn[3] = {50, 50, 50};
GLubyte loadBtn[3] = {55, 55, 55};

然后把 mouseClick 方法改写一下就好了。最终 mouseClick 方法如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/* 鼠标点击事件 */
void mouseClick( int btn, int state, int x, int y )
{
/* 赋值全局变量 */
clickX = x;
clickY = y;
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if ( btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
{
/* 如果在颜色选项卡中点击 */
if ( y < optionHeight && x < optionWidth )
{
glReadPixels( x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor );
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle( -180, 280, 10, nowColor );
/* 如果点击的是右侧选项按钮 */
}else if ( y < optionHeight && x > optionWidth )
{
/* 取当前的点 */
GLubyte color[3];
glReadPixels( x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color );
/* 如果点击了开始绘制的按钮 */
if ( sameColor( color, startBtn ) )
{
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
/* 如果点击了结束绘制的按钮 */
} else if ( sameColor( color, endBtn ) )
{
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
} else if ( sameColor( color, delBtn ) )
{
/* 删除时设置drawStatus为2 */
drawStatus = 2;
} else if ( sameColor( color, moveBtn ) )
{
/* 移动时设置drawStatus为3 */
drawStatus = 3;
} else if ( sameColor( color, storeBtn ) )
{
/* 存盘时设置drawStatus为4 */
drawStatus = 4;
glutPostRedisplay();
savePolygons();
} else if ( sameColor( color, loadBtn ) )
{
/* 读盘时设置drawStatus为5 */
drawStatus = 5;
readPolygons();
glutPostRedisplay();
}

/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if ( drawStatus == 0 )
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints( x, y );
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
polygons[con].verNum++;
}else if ( drawStatus == 2 )
{
/* 删除图形 */
deletePoly( x, y );
} else if ( drawStatus == 3 )
{
moveNum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
moveNum = i;
}
}
}
}
}
}

大功告成!现在只要你存盘成功,不管对图形进行怎样的移动,删除操作。只要再点击读盘,就可以恢复到存盘时的状态,是不是非常酷炫。 大家可以尝试一下!

总结

本篇文章说明了图形的删除,移动以及存盘读盘的功能。代码实现仅仅是我个人的思路,当然实现方式不止这一种,欢迎大家创新。 部分思路如果大家有更好的解决方法,欢迎同我交流,非常感谢。 总之希望对大家有帮助!小伙伴们加油!

C/C++

综述

在上一节我们学习了鼠标监听事件,在这里我们就利用它来做一个实例,对鼠标监听事件进行一个综合的应用。

要求

1. 绘制如下的机器人,并在此基础上进行创作 图片1 2. 对象创建:支持用户利用鼠标指定各个顶点位置,补充完整机器人的腿部。 3. 对象删除:支持用户选择一个腿部的多边形(与你的多边形保存的数据结构有关)并删除。 4. 对象存储:设计一种数据结构存储每个多边形的顶点与边,支持文件存盘,并支持读盘复原。 5. 支持用户选择多边形的颜色,支持用户移动多边形

成果

在这里为了动态演示,将我的成果以视频形式演示如下

[embed]http://cdn.cuiqingcai.com/robot.mp4[/embed]

好,欣赏完视频之后是不是非常想知道是怎样实现的呢?下面我们来一步一步进行讲解。

绘制半机器人

在前面的几篇中,已经详细描述了绘制机器人的方法,在这里我们简单贴一下代码来绘制一个半机器人。 直接上代码吧,不多说了

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <glut.h>
#include <math.h>
#define PI 3.1415926

/* 画矩形,传入的是左下角XY坐标和右上角XY坐标 */
void glRect( int leftX, int leftY, int rightX, int rightY )
{
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 左下角 */
glVertex2d( leftX, leftY );
/* 右下角 */
glVertex2d( rightX, leftY );
/* 右上角 */
glVertex2d( rightX, rightY );
/* 左上角 */
glVertex2d( leftX, rightY );
/* 结束画线 */
glEnd();
}


/* 画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标 */
void glRoundRec( int centerX, int centerY, int width, int height, float cirR )
{
/* 二分之PI,一个象限的角度 */
float PI_HALF = PI / 2;
/* 划分程度,值越大画得越精细 */
float divide = 20.0;
/* 圆角矩形的坐标 */
float tx, ty;
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 四个象限不同的操作符 */
int opX[4] = { 1, -1, -1, 1 };
int opY[4] = { 1, 1, -1, -1 };
/* 用来计数,从第一象限到第四象限 */
float x = 0;
/* x自增时加的值 */
float part = 1 / divide;
/* 计算内矩形宽高一半的数值 */
int w = width / 2 - cirR;
int h = height / 2 - cirR;
/* 循环画线 */
for ( x = 0; x < 4; x += part )
{
/* 求出弧度 */
float rad = PI_HALF * x;
/* 计算坐标值 */
tx = cirR * cos( rad ) + opX[(int) x] * w + centerX;
ty = cirR * sin( rad ) + opY[(int) x] * h + centerY;
/*传入坐标画线 */
glVertex2f( tx, ty );
}
/* 结束画线 */
glEnd();
}


/* 画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径 */
void glArc( double x, double y, double start_angle, double end_angle, double radius )
{
/* 开始绘制曲线 */
glBegin( GL_LINE_STRIP );
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for ( double i = start_angle; i <= end_angle; i += delta_angle )
{
/* 绝对定位加三角函数值 */
double vx = x + radius * cos( i );
double vy = y + radius*sin( i );
glVertex2d( vx, vy );
}
/* 结束绘画 */
glEnd();
}


/* 画圆 */
void glCircle( double x, double y, double radius )
{
/* 画全圆 */
glArc( x, y, 0, 2 * PI, radius );
}


/* 画三角形,传入三个点的坐标 */
void glTri( int x1, int y1, int x2, int y2, int x3, int y3 )
{
/* 画封闭线 */
glBegin( GL_LINE_LOOP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/*三点 */
glVertex2d( x3, y3 );
/* 结束画线 */
glEnd();
}


/* 画线,传入两点坐标 */
void glLine( int x1, int y1, int x2, int y2 )
{
/* 画封闭线 */
glBegin( GL_LINE_STRIP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/* 结束画线 */
glEnd();
}


/* 函数用来画图 */
void display( void )
{
/* GL_COLOR_BUFFER_BIT表示清除颜色 */
glClear( GL_COLOR_BUFFER_BIT );
/* 设置画线颜色 */
glColor3f( 0.5, 0.5, 0.5 );
/* 画点大小 */
glPointSize( 2 );
/* 画圆角矩形,大肚子 */
glRoundRec( 0, 0, 146, 120, 15 );
/* 画圆,中间小圈 */
glCircle( 0, 0, 10 );
/* 画矩形,脖子 */
glRect( -25, 60, 25, 76 );
/* 画圆角矩形,大脸 */
glRoundRec( 0, 113, 128, 74, 10 );
/* 两个眼睛 */
glCircle( -30, 111, 10 );
glCircle( 30, 111, 10 );
/* 两条天线 */
glLine( -35, 150, -35, 173 );
glLine( 35, 150, 35, 173 );
/* 圆角矩形,两个耳朵 */
glRoundRec( 81, 115, 20, 34, 5 );
glRoundRec( -81, 115, 20, 34, 5 );
/* 圆弧,画嘴 */
glArc( 0, 133, 11 * PI / 8, 13 * PI / 8, 45 );
/* 画三角,肚子里的三角 */
glTri( -30, -15, 30, -15, 0, 28 );
/* 画矩形,胳膊连接处 */
glRect( -81, 43, -73, 25 );
glRect( 81, 43, 73, 25 );
/* 画矩形,上臂 */
glRect( -108, 45, -81, 0 );
glRect( 108, 45, 81, 0 );
/* 画矩形,中臂 */
glRect( -101, 0, -88, -4 );
glRect( 101, 0, 88, -4 );
/* 画矩形,下臂 */
glRect( -108, -4, -81, -37 );
glRect( 108, -4, 81, -37 );
/* 画圆形,手掌 */
glCircle( -95, -47, 10 );
glCircle( 95, -47, 10 );
/* 保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待 */
glFlush();
}


/* 窗口大小变化时调用的函数 */
void ChangeSize( GLsizei w, GLsizei h )
{
/* 避免高度为0 */
if ( h == 0 )
{
h = 1;
}
/* 定义视口大小,宽高一致 */
glViewport( 0, 0, w, h );
int half = 300;
/* 重置坐标系统,使投影变换复位 */
glMatrixMode( GL_PROJECTION );
/* 将当前的用户坐标系的原点移到了屏幕中心 */
glLoadIdentity();
/* 定义正交视域体 */
if ( w < h )
{
/* 如果高度大于宽度,则将高度视角扩大,图形显示居中 */
glOrtho( -half, half, -half * h / w, half * h / w, -half, half );
} else {
/* 如果宽度大于高度,则将宽度视角扩大,图形显示居中 */
glOrtho( -half * w / h, half * w / h, -half, half, -half, half );
}
}


/*程序入口 */
int main( int argc, char *argv[] )
{
/* 对GLUT进行初始化,并处理所有的命令行参数 */
glutInit( &argc, argv );
/* 指定RGB颜色模式和单缓冲窗口 */
glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE );
/* 定义窗口的位置 */
glutInitWindowPosition( 100, 100 );
/* 定义窗口的大小 */
glutInitWindowSize( 600, 600 );
/* 创建窗口,同时为之命名 */
glutCreateWindow( "OpenGL" );
/* 设置窗口清除颜色为白色 */
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
/* 参数为一个函数,绘图时这个函数就会被调用 */
glutDisplayFunc( &display );
/* 参数为一个函数,当窗口大小改变时会被调用 */
glutReshapeFunc( ChangeSize );
/* 该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止 */
glutMainLoop();
/*程序返回 */
return(0);
}

运行结果如下 20150511002257 这一步非常非常简单,如果有不懂的小伙伴请参见 此文章

对象创建

通过上面的一步,我们已经可以轻松地绘制出一个半身机器人,接下来我们就要通过监听鼠标事件来实现动态多边形的绘制了。 因为实验要求中要求了多边形的颜色绘制,所以颜色怎么选取呢?我是这么想的,在面板的左上角绘制几个色块,通过鼠标点击这几个色块,读取出 RGB 值,然后相应地设置当前绘图的颜色即可。 所以首先我在左上角定义了几个颜色色块,同时监听鼠标事件。 首先定义几个颜色的 RGB 值

1
2
3
4
5
6
7
8
9
/* 各种颜色 */
GLubyte border[3] = {0, 0, 0};
GLubyte grey[3] = {195, 195, 195};
GLubyte yellow[3] = {255, 243, 0};
GLubyte red[3] = {237, 28, 36};
GLubyte darkGrey[3] = {126, 126, 126};
GLubyte white[3] = {255, 255, 255};
/* 当前颜色 */
GLubyte nowColor[3] = {0, 0, 0};

然后定义一个绘制填充色的圆形的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 绘制填充的圆形 */
void glColorCircle(int x, int y, int R, GLubyte color[3])
{
/* 开始绘制曲线 */
glBegin(GL_POLYGON);
/* 设置颜色 */
glColor3ub(color[0], color[1], color[2]);
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for (double i = 0; i <= 2 * PI; i += delta_angle)
{
/* 绝对定位加三角函数值 */
double vx = x + R * cos(i);
double vy = y + R*sin(i);
glVertex2d(vx, vy);
}
/* 结束绘画 */
glEnd();
glFlush();
}

那么在 display 函数中,我们就可以调用下面的方法来绘制三个取色盘拾取颜色,其中最后一个取色盘保存了当前的颜色。

1
2
3
4
5
/* 绘制取色盘 */
glColorCircle(-280, 280, 10, red);
glColorCircle(-250, 280, 10, yellow);
glColorCircle(-220, 280, 10, grey);
glColorCircle(-180, 280, 10, nowColor);

这样光有了取色盘可不行,需要有鼠标点击事动态地改变取色的色值。现在我们可以加入鼠标监听方法如下 通过上一节的说明,我们定义一个 mouseClick 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {

}else{

}
}
}

另外加入监听方法

1
2
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);

在这里有几个地方需要说明一下。

1.选项卡的分界范围是什么?

其实我是把整个画面分割成了三部分,其中最上面 40 像素的一个长条就是选项卡,选项卡又分为了两部分。左边是取色板,右边是绘制的选项,比如取点,绘制,删除等等的操作。所以,optionHeight 就指的是上面的 40 像素的分割线,optionWidth 是中间的分割线,即上方调色板和绘制选项点的分割线。 通过鼠标点击判断是在哪个范围内从而响应不同的事件。 从而鼠标监听事件就是下面的结构了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {

/* 如果点击的是下方的绘图页面 */
}else{

}

其中第四个色板就是显示了当前的颜色,在-180,280 的位置绘制了当前的颜色色盘。并同时颜色值保存在了 nowColor 中。

2. halfHeight 和 halfWidth

这里定义了 halfHeight 和 halfWdith,在后面的绘图中会用到,同时在取色时也会用到,实际就是整个窗口宽高的一半,定义为全局变量即可。如当前的窗口是 600x600,所以现在二者就都是 300,300. 到现在,如果你按照流程走下来,代码是可以正常运行的,现在可以通过点击左上角的色盘进行取色,同时第四个色盘显示了当前的颜色。运行结果截图如下 20150511005807 好,接下来我们就进行多边形的定义了,多边形的定义我们利用结构体的形式,假设我们绘制的边数最大是 10,可以定义如下的多边形结构体,结构体中包含了顶点坐标(数组形式),顶点数量,颜色(RGB)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 多边形结构体 */
struct polygon {
/* 顶点坐标 */
int x[MAX_VERTEX];
int y[MAX_VERTEX];
/* 定义第几个顶点 */
int verNum;
GLubyte color[3];
};
/* 声明多边形数组 */
polygon polygons[MAX_PLOY];

为了便于记录,我们定义一个 con 变量来记录当前绘制到了哪个多边形

1
2
/* 记录画了几个多边形 */
int con = 0;

好,定义好结构体之后,我们就可以先选取点,然后绘制多边形了。所以在这里我们定义两个按钮,取点和绘制。 定义两个颜色

1
2
GLubyte startBtn[3] = {10, 10, 10};
GLubyte endBtn[3] = {20, 20, 20};

我们可以用颜色来区分不同的按钮,现在原来的 display 函数中画上这俩按钮。

1
2
glColorCircle(250, 280, 10, startBtn);
glColorCircle(280, 280, 10, endBtn);

如图所示,当我们要取点的时候就可以点击一下左边的按钮,取完点后就可以点击右边的按钮完成绘制。 QQ截图20150511010802 在鼠标监听事件中,当点击了左边的按钮,便开始拾取几个顶点,然后点击右边的按钮之后,会将图形绘制出来。所以为了区分当前是在拾取点还是完成绘制,我们定义一个状态变量叫 drawStatus

1
2
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;

在原来的 mouseClick 函数中,对于判断点击右上方选项卡的响应就可以改写如下

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
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{

}
}
}

其中 sameColor 方法就是来判断当前点击的位置是否和按钮颜色相等,在这里为了避免误差,我们像往常一样设置一个容差。

1
2
3
4
5
6
7
8
9
10
/* 判断两个颜色是否相等 */
bool sameColor(GLubyte color1[3], GLubyte color2[3])
{
if (color1[0] - color2[0] < 5 && color1[1] - color2[1] < 5 && color1[2] - color2[1] < 5)
{
return(true);
} else {
return(false);
}
}

好,当点击了第一个黑色按钮(取点的按钮)时,我们便可以在下方拾取点来保存到数组中了。 在最后的 else 代码块中加入如下的方法,用来取点。 这里的取点是取到了点击的坐标值,当前的 RGB 值,每取一个点,响应的 verNum(顶点数目)就加一。 而且当前保存到的多边形是 polygons[con],con 初始值为 0,也就是保存到第一个多边形结构体中。 当我们点击了开始绘制按钮时,con 会加 1,并且将保存的多边形绘制出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}

到现在上面的 mouseClick 方法就变成了

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
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}
}
}
}

在这里有几点说明如下

1.glPoints 方法

即画点方法,为了直观地显示用户选取了哪个点,我们设置了这个画点的方法,每点一个点,就在屏幕上显示出来。 方法定义如下

1
2
3
4
5
6
7
8
9
/* 画点 */
void glPoints(int x, int y) {
glBegin(GL_POINTS);
/* 点直接设置为黑色 */
glColor3ub(0, 0, 0);
glVertex2d(x - halfWidth, halfHeight - y);
glEnd();
glFlush();
}

在这里,glVertex2d 方法的参数为什么不是 x,y 呢?很简单,因为我们绘画时的坐标原点是窗口的中心点,而鼠标点击的坐标原点是左上角,为了做一个映射变换,x 需要减去 halfWidth,y 需要取反并加 halfWidth。总之定义这两个 half 变量还是用处多多的。

2.glutPostRedisplay

这个方法就是重新绘制的方法,我们看到在结束绘制的地方调用了这个方法,其实就是重新调用了 display 方法,所以为了让我们绘制的图形正常显示,我们需要在 display 方法中来绘制我们已经存储好的多边形。我们可以把绘制多边形定义为一个方法,然后在 display 中调用即可。 绘制多边形的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 绘制多边形 */
void glPolygons()
{
if (con>=0) {
for (int i = 0; i <= con; i++) {
/* 取到这个多边形 */
polygon poly = polygons[i];
/* 画封闭线 */
glBegin(GL_LINE_LOOP);
int num = poly.verNum;
printf("num:%d\n",num);
for (int j = 0; j < num; j++)
{
glColor3ub(poly.color[0], poly.color[1], poly.color[2]);
glVertex2d(poly.x[j] - halfWidth, halfHeight - poly.y[j]);
printf("polyx:%d,polyy:%d",poly.x[j],poly.y[j]);
}
/* 结束画线 */
glEnd();
/* 刷新 */
glFlush();
}
}
}

在 display 函数中调用一下即可,方法的最后面调用一下。

1
2
/* 绘制多边形 */
glPolygons();

好,现在我们点击第一个黑色圆点,然后在屏幕上取点,取完点之后,然后点击右上角的黑色圆点,即可完成绘制 运行结果如下 QQ截图20150511013259 在此先告一段落,下一篇我们接着这里来实现图形的删除,移动以及存盘,读盘等操作。

本节代码

到这里,提供如下代码,代码运行结果即为上图所示,由于代码太长,不完全展开显示,双击查看全部代码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#include <glut.h>
#include <math.h>
#include <stdio.h>

#define PI 3.1415926
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 窗口长宽的一半 */
int halfWidth, halfHeight;
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;
/* 多边形结构体 */
struct polygon {
/* 顶点坐标 */
int x[MAX_VERTEX];
int y[MAX_VERTEX];
/* 定义第几个顶点 */
int verNum;
GLubyte color[3];
};
/* 各种颜色 */
GLubyte border[3] = {0, 0, 0};
GLubyte grey[3] = {195, 195, 195};
GLubyte yellow[3] = {255, 243, 0};
GLubyte red[3] = {237, 28, 36};
GLubyte darkGrey[3] = {126, 126, 126};
GLubyte white[3] = {255, 255, 255};
GLubyte startBtn[3] = {10, 10, 10};
GLubyte endBtn[3] = {20, 20, 20};
/* 当前颜色 */
GLubyte nowColor[3] = {0, 0, 0};
/* 声明多边形数组 */
polygon polygons[MAX_PLOY];
/* 记录画了几个多边形 */
int con = 0;

/* 绘制多边形 */
void glPolygons()
{
if (con>=0) {
for (int i = 0; i <= con; i++) {
/* 取到这个多边形 */
polygon poly = polygons[i];
/* 画封闭线 */
glBegin(GL_LINE_LOOP);
int num = poly.verNum;
printf("num:%d\n",num);
for (int j = 0; j < num; j++)
{
glColor3ub(poly.color[0], poly.color[1], poly.color[2]);
glVertex2d(poly.x[j] - halfWidth, halfHeight - poly.y[j]);
printf("polyx:%d,polyy:%d",poly.x[j],poly.y[j]);
}
/* 结束画线 */
glEnd();
/* 刷新 */
glFlush();
}
}
}

/* 绘制填充的圆形 */
void glColorCircle(int x, int y, int R, GLubyte color[3])
{
/* 开始绘制曲线 */
glBegin(GL_POLYGON);
/* 设置颜色 */
glColor3ub(color[0], color[1], color[2]);
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for (double i = 0; i <= 2 * PI; i += delta_angle)
{
/* 绝对定位加三角函数值 */
double vx = x + R * cos(i);
double vy = y + R*sin(i);
glVertex2d(vx, vy);
}
/* 结束绘画 */
glEnd();
glFlush();
}

//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY){
//画封闭曲线
glBegin(GL_LINE_LOOP);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
//设置画线颜色
glColor3f(0.5,0.5,0.5);
//画点大小
glPointSize(2);
//画圆角矩形,大肚子
glRoundRec(0,0,146,120,15);
//画圆,中间小圈
glCircle(0,0,10);
//画矩形,脖子
glRect(-25,60,25,76);
//画圆角矩形,大脸
glRoundRec(0,113,128,74,10);
//两个眼睛
glCircle(-30,111,10);
glCircle(30,111,10);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆角矩形,两个耳朵
glRoundRec(81,115,20,34,5);
glRoundRec(-81,115,20,34,5);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45);
//画三角,肚子里的三角
glTri(-30,-15,30,-15,0,28);
//画矩形,胳膊连接处
glRect(-81,43,-73,25);
glRect(81,43,73,25);
//画矩形,上臂
glRect(-108,45,-81,0);
glRect(108,45,81,0);
//画矩形,中臂
glRect(-101,0,-88,-4);
glRect(101,0,88,-4);
//画矩形,下臂
glRect(-108,-4,-81,-37);
glRect(108,-4,81,-37);
//画圆形,手掌
glCircle(-95,-47,10);
glCircle(95,-47,10);

/* 绘制取色盘 */
glColorCircle(-280, 280, 10, red);
glColorCircle(-250, 280, 10, yellow);
glColorCircle(-220, 280, 10, grey);
glColorCircle(-180, 280, 10, nowColor);
glColorCircle(250, 280, 10, startBtn);
glColorCircle(280, 280, 10, endBtn);
//保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待
/* 绘制多边形 */
glPolygons();
glFlush();
}


//窗口大小变化时调用的函数
void ChangeSize(GLsizei w,GLsizei h)
{
//避免高度为0
if(h==0) {
h=1;
}
//定义视口大小,宽高一致
glViewport(0,0,w,h);
int half = 300;
/* 定义宽高的一半 */
halfHeight = 300;
halfWidth = 300;
//重置坐标系统,使投影变换复位
glMatrixMode(GL_PROJECTION);
//将当前的用户坐标系的原点移到了屏幕中心
glLoadIdentity();
//定义正交视域体
if(w<h) {
//如果高度大于宽度,则将高度视角扩大,图形显示居中
glOrtho(-half,half,-half*h/w,half*h/w,-half,half);
} else {
//如果宽度大于高度,则将宽度视角扩大,图形显示居中
glOrtho(-half*w/h,half*w/h,-half,half,-half,half);
}

}
/* 判断两个颜色是否相等 */
bool sameColor(GLubyte color1[3], GLubyte color2[3])
{
if (color1[0] - color2[0] < 5 && color1[1] - color2[1] < 5 && color1[2] - color2[1] < 5)
{
return(true);
} else {
return(false);
}
}

/* 画点 */
void glPoints(int x, int y) {
glBegin(GL_POINTS);
/* 点直接设置为黑色 */
glColor3ub(0, 0, 0);
glVertex2d(x - halfWidth, halfHeight - y);
glEnd();
glFlush();
}

/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}
}
}
}




//程序入口
int main(int argc, char *argv[]){
//对GLUT进行初始化,并处理所有的命令行参数
glutInit(&argc, argv);
//指定RGB颜色模式和单缓冲窗口
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
//定义窗口的位置
glutInitWindowPosition(100, 100);
//定义窗口的大小
glutInitWindowSize(600, 600);
//创建窗口,同时为之命名
glutCreateWindow("OpenGL");
//设置窗口清除颜色为白色
glClearColor(1.0f,1.0f,1.0f,1.0f);
//参数为一个函数,绘图时这个函数就会被调用
glutDisplayFunc(&display);
//参数为一个函数,当窗口大小改变时会被调用
glutReshapeFunc(ChangeSize);
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);
//该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止
glutMainLoop();
//程序返回
return 0;
}

小伙伴们可以运行试试看。

综述

本节我们主要利用了鼠标时间来实现了取色,设置颜色,并成功通过取点绘制出了各种形状的多边形。

C/C++

综述

在前面的例子中我们绘制了 2D 机器人以及颜色的填充,另外还有平移放缩变换等等。不过这些绘图方式都有一个特点,那就是无法动态地响应事件,如果我们在例子中加入鼠标点击的响应时间,让图形根据鼠标点击的变化而变化,那将会是非常友好的。 那么这一篇我们就来看一下鼠标响应的实现。

鼠标点击

对于鼠标点击,无非是左右键,滚轮的点击以及区分按下还是按上。 监听方法如下: void mouseClick(int btn, int state, int x, int y) 方法名是任意的,参数有四个,分别是左右键,点击状态(按下还是松开),点击的 x,y 坐标。 那么我们在 main 函数中怎么调用,代码如下

1
2
/* 鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);

我们利用了类库中提供的方法 glutMouseFunc() 这个方法用来监听鼠标事件,我们传入刚才定义的 mouseClick 函数,即可完成调用,同时 mouseClick 函数的参数都会被赋值。 通过上面的代码,在鼠标点击窗口内部时,会自动调用 mouseClick 方法,而且四个参数都会有赋值。可以直接使用这些值来操作。

鼠标移动

void mouseMove(int x, int y) 方法名我们也可以任意指定,参数有 2 个,分别是 x,y 在 main 函数中可以这样调用

1
2
/*鼠标移动事件,鼠标按下并移动时调用 */
glutMotionFunc(mouseMove);

我们利用了类库中提供的方法 glutMotionFunc() 同上面的原理,传入了 mouseMove 方法,在鼠标移动时,就可以对 mouseMove 方法的参数进行赋值。

实例

好了,上面我们说了两个鼠标事件的方法,下面我们用一个例子来感受一下

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
56
57
58
59
60
61
62
63
64
65
#include <glut.h>
#include <math.h>
#include <stdio.h>


/* 函数用来画图 */
void display(void)
{

}


/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
printf("点击了鼠标,btn:%d,state:%d,x:%d,y:%d\n", btn, state, x, y);
if(btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN){
printf("按下了左边按钮\n");
}
if(btn == GLUT_LEFT_BUTTON && state == GLUT_UP){
printf("松开了左边按钮\n");
}
if(btn == GLUT_RIGHT_BUTTON && state == GLUT_DOWN){
printf("按下了右边按钮\n");
}
if(btn == GLUT_RIGHT_BUTTON && state == GLUT_UP){
printf("松开了右边按钮\n");
}
}



/* 鼠标移动事件 */
void mouseMove(int x, int y)
{
printf("移动鼠标中,x:%d,y%d\n", x, y);
}


/* 程序入口 */
int main(int argc, char *argv[])
{
/* 对GLUT进行初始化,并处理所有的命令行参数 */
glutInit(&argc, argv);
/* 指定RGB颜色模式和单缓冲窗口 */
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
/* 定义窗口的位置 */
glutInitWindowPosition(100, 100);
/* 定义窗口的大小 */
glutInitWindowSize(400, 400);
/* 创建窗口,同时为之命名 */
glutCreateWindow("OpenGL");
/* 设置窗口清除颜色为白色 */
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
/* 参数为一个函数,绘图时这个函数就会被调用 */
glutDisplayFunc(&display);
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);
/*鼠标移动事件,鼠标按下并移动时调用 */
glutMotionFunc(mouseMove);
/* 该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止 */
glutMainLoop();
/*程序返回 */
return(0);
}

在上面的代码中,我们监听了鼠标点击和鼠标移动事件,并打印输出了鼠标点击事件的响应。 比如我先点击了鼠标左键,然后点击了鼠标右键。然后移动了鼠标左键,然后移动了鼠标右键。运行结果如下 0150511000009 可以发现它打印输出了鼠标的按钮代号,点击状态,点击的位置。 另外我们发现鼠标点击的坐标原点是左上角,这跟我们之前读取色值的坐标原点(左下角)不一样,也跟我们绘图的坐标原点(中心点)不一样。编程时一定要注意。 获取到了点击点的位置,我们就可以利用这些值来进行相应的作图处理了。 小伙伴们可以尝试一下。

综述

本节说的不多,主要是说明了鼠标监听事件的用法。glutMouseFunc 以及 glutMotionFunc,另外通过一个实例来演示了鼠标监听事件的用法,下一篇我们就利用这个知识来进行一个实例演示。大家加油!

HTML

综述

LESS 包含一套自定义的语法及一个解析器,用户根据这些语法定义自己的样式规则,这些规则最终会通过解析器,编译生成对应的 CSS 文件。LESS 并没有裁剪 CSS 原有的特性,更不是用来取代 CSS 的,而是在现有 CSS 语法的基础上,为 CSS 加入程序式语言的特性。 本文部分内容参考 官方文档

编译器

建议使用Koala编译器 下载地址

LESS 原理及使用方式

本质上,LESS 包含一套自定义的语法及一个解析器,用户根据这些语法定义自己的样式规则,这些规则最终会通过解析器,编译生成对应的 CSS 文件。LESS 并没有裁剪 CSS 原有的特性,更不是用来取代 CSS 的,而是在现有 CSS 语法的基础上,为 CSS 加入程序式语言的特性。下面是一个简单的例子:

清单 1. LESS 文件
1
2
3
4
5
6
7
8
@color: #4D926F; 

#header {
color: @color;
}
h2 {
color: @color;
}

经过编译生成的 CSS 文件如下:

清单 2. CSS 文件
1
2
3
4
5
6
#header { 
color: #4D926F;
}
h2 {
color: #4D926F;
}

从上面的例子可以看出,学习 LESS 非常容易,只要你了解 CSS 基础就可以很容易上手。 LESS 可以直接在客户端使用,也可以在服务器端使用。在实际项目开发中,我们更推荐使用第三种方式,将 LESS 文件编译生成静态 CSS 文件,并在 HTML 文档中应用。

客户端

我们可以直接在客户端使用 .less(LESS 源文件),只需要从 LESSCSS 下载 less.js 文件,然后在我们需要引入 LESS 源文件的 HTML 中加入如下代码:

1
<link rel="stylesheet/less" type="text/css" href="styles.less">

LESS 源文件的引入方式与标准 CSS 文件引入方式一样:

1
<link rel="stylesheet/less" type="text/css" href="styles.less">

需要注意的是:在引入 .less 文件时,rel 属性要设置为“stylesheet/less”。还有更重要的一点需要注意的是:LESS 源文件一定要在 less.js 引入之前引入,这样才能保证 LESS 源文件正确编译解析。

使用编译生成的静态 CSS 文件

我们可以通过 LESS 的编译器,将 LESS 文件编译成为 CSS 文件,在 HTML 文章中引入使用。这里要强调的一点,LESS 是完全兼容 CSS 语法的,也就是说,我们可以将标准的 CSS 文件直接改成 .less 格式,LESS 编译器可以完全识别。

语法

变量

LESS 允许开发者自定义变量,变量可以在全局样式中使用,变量使得样式修改起来更加简单。 我们可以从下面的代码了解变量的使用及作用:

清单 3. LESS 文件
1
2
3
4
5
@border-color : #b5bcc7; 

.mythemes tableBorder{
border : 1px solid @border-color;
}

经过编译生成的 CSS 文件如下:

清单 4. CSS 文件
1
2
3
.mythemes tableBorder { 
border: 1px solid #b5bcc7;
}

从上面的代码中我们可以看出,变量是 VALUE(值)级别的复用,可以将相同的值定义成变量统一管理起来。 该特性适用于定义主题,我们可以将背景颜色、字体颜色、边框属性等常规样式进行统一定义,这样不同的主题只需要定义不同的变量文件就可以了。当然该特性也同样适用于 CSS RESET(重置样式表),在 Web 开发中,我们往往需要屏蔽浏览器默认的样式行为而需要重新定义样式表来覆盖浏览器的默认行为,这里可以使用 LESS 的变量特性,这样就可以在不同的项目间重用样式表,我们仅需要在不同的项目样式表中,根据需求重新给变量赋值即可。 LESS 中的变量和其他编程语言一样,可以实现值的复用,同样它也有生命周期,也就是 Scope(变量范围,开发人员惯称之为作用域),简单的讲就是局部变量还是全局变量的概念,查找变量的顺序是先在局部定义中找,如果找不到,则查找上级定义,直至全局。下面我们通过一个简单的例子来解释 Scope。

清单 5. LESS 文件
1
2
3
4
5
6
7
8
9
10
11
@width : 20px; 
#homeDiv {
@width : 30px;
#centerDiv{
width : @width;// 此处应该取最近定义的变量 width 的值 30px
}
}
#leftDiv {
width : @width; // 此处应该取最上面定义的变量 width 的值 20px

}

经过编译生成的 CSS 文件如下:

清单 6. CSS 文件
1
2
3
4
5
6
#homeDiv #centerDiv { 
width: 30px;
}
#leftDiv {
width: 20px;
}

Mixins(混入)

Mixins(混入)功能对用开发者来说并不陌生,很多动态语言都支持 Mixins(混入)特性,它是多重继承的一种实现,在 LESS 中,混入是指在一个 CLASS 中引入另外一个已经定义的 CLASS,就像在当前 CLASS 中增加一个属性一样。 我们先简单看一下 Mixins 在 LESS 中的使用:

清单 7. LESS 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个样式选择器
.roundedCorners(@radius:5px) {
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
border-radius: @radius;
}
// 在另外的样式选择器中使用
#header {
.roundedCorners;
}
#footer {
.roundedCorners(10px);
}

经过编译生成的 CSS 文件如下:

清单 8. CSS 文件
1
2
3
4
5
6
7
8
9
10
#header { 
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
}
#footer {
-moz-border-radius:10px;
-webkit-border-radius:10px;
border-radius:10px;
}

从上面的代码我们可以看出:Mixins 其实是一种嵌套,它允许将一个类嵌入到另外一个类中使用,被嵌入的类也可以称作变量,简单的讲,Mixins 其实是规则级别的复用。 Mixins 还有一种形式叫做 Parametric Mixins(混入参数),LESS 也支持这一特性:

清单 9. LESS 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义一个样式选择器
.borderRadius(@radius){
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
border-radius: @radius;
}
// 使用已定义的样式选择器
#header {
.borderRadius(10px); // 把 10px 作为参数传递给样式选择器
}
.btn {
.borderRadius(3px);// // 把 3px 作为参数传递给样式选择器

}

经过编译生成的 CSS 文件如下:

清单 10. CSS 文件
1
2
3
4
5
6
7
8
9
10
#header { 
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
}
.btn {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}

我们还可以给 Mixins 的参数定义一人默认值,如

清单 11. LESS 文件
1
2
3
4
5
6
7
8
.borderRadius(@radius:5px){ 
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
border-radius: @radius;
}
.btn {
.borderRadius;
}

经过编译生成的 CSS 文件如下:

清单 12. CSS 文件
1
2
3
4
5
.btn { 
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}

像 JavaScript 中 arguments一样,Mixins 也有这样一个变量:@arguments。@arguments 在 Mixins 中具是一个很特别的参数,当 Mixins 引用这个参数时,该参数表示所有的变量,很多情况下,这个参数可以省去你很多代码。

清单 13. LESS 文件
1
2
3
4
5
6
7
8
.boxShadow(@x:0,@y:0,@blur:1px,@color:#000){ 
-moz-box-shadow: @arguments;
-webkit-box-shadow: @arguments;
box-shadow: @arguments;
}
#header {
.boxShadow(2px,2px,3px,#f36);
}

经过编译生成的 CSS 文件如下:

清单 14. CSS 文件
1
2
3
4
5
#header { 
-moz-box-shadow: 2px 2px 3px #FF36;
-webkit-box-shadow: 2px 2px 3px #FF36;
box-shadow: 2px 2px 3px #FF36;
}

Mixins 是 LESS 中很重要的特性之一,我们这里也写了很多例子,看到这些例子你是否会有这样的疑问:当我们拥有了大量选择器的时候,特别是团队协同开发时,如何保证选择器之间重名问题?如果你是 java 程序员或 C++ 程序员,我猜你肯定会想到命名空间 Namespaces,LESS 也采用了命名空间的方法来避免重名问题,于是乎 LESS 在 mixins 的基础上扩展了一下,看下面这样一段代码:

清单 15. LESS 文件
1
2
3
4
#mynamespace { 
.home {...}
.user {...}
}

这样我们就定义了一个名为 mynamespace 的命名空间,如果我们要复用 user 这个选择器的时候,我们只需要在需要混入这个选择器的地方这样使用就可以了。#mynamespace > .user。

嵌套的规则

在我们书写标准 CSS 的时候,遇到多层的元素嵌套这种情况时,我们要么采用从外到内的选择器嵌套定义,要么采用给特定元素加 CLASS 或 ID 的方式。在 LESS 中我们可以这样写:

清单 16. HTML 片段
1
2
3
4
5
6
7
<div id="home"> 
<div id="top">top</div>
<div id="center">
<div id="left">left</div>
<div id="right">right</div>
</div>
</div>
清单 17. LESS 文件
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
#home{ 
color : blue;
width : 600px;
height : 500px;
border:outset;
#top{
border:outset;
width : 90%;
}
#center{
border:outset;
height : 300px;
width : 90%;
#left{
border:outset;
float : left;
width : 40%;
}
#right{
border:outset;
float : left;
width : 40%;
}
}
}

经过编译生成的 CSS 文件如下:

清单 18. CSS 文件
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
#home { 
color: blue;
width: 600px;
height: 500px;
border: outset;
}
#home #top {
border: outset;
width: 90%;
}
#home #center {
border: outset;
height: 300px;
width: 90%;
}
#home #center #left {
border: outset;
float: left;
width: 40%;
}
#home #center #right {
border: outset;
float: left;
width: 40%;
}

从上面的代码中我们可以看出,LESS 的嵌套规则的写法是 HTML 中的 DOM 结构相对应的,这样使我们的样式表书写更加简洁和更好的可读性。同时,嵌套规则使得对伪元素的操作更为方便。

清单 19. LESS 文件
1
2
3
4
5
6
7
8
a { 
color: red;
text-decoration: none;
&:hover {// 有 & 时解析的是同一个元素或此元素的伪类,没有 & 解析是后代元素
color: black;
text-decoration: underline;
}
}

经过编译生成的 CSS 文件如下:

清单 20. CSS 文件
1
2
3
4
5
6
7
8
a { 
color: red;
text-decoration: none;
}
a:hover {
color: black;
text-decoration: underline;
}

运算及函数

在我们的 CSS 中充斥着大量的数值型的 value,比如 color、padding、margin 等,这些数值之间在某些情况下是有着一定关系的,那么我们怎样利用 LESS 来组织我们这些数值之间的关系呢?我们来看这段代码:

清单 21 . LESS 文件
1
2
3
4
5
@init: #111111; 
@transition: @init*2;
.switchColor {
color: @transition;
}

经过编译生成的 CSS 文件如下:

清单 22. CSS 文件
1
2
3
.switchColor { 
color: #222222;
}

上面的例子中使用 LESS 的 operation 是 特性,其实简单的讲,就是对数值型的 value(数字、颜色、变量等)进行加减乘除四则运算。同时 LESS 还有一个专门针对 color 的操作提供一组函数。下面是 LESS 提供的针对颜色操作的函数列表:

1
2
3
4
5
6
7
8
lighten(@color, 10%); // return a color which is 10% *lighter* than @color 
darken(@color, 10%); // return a color which is 10% *darker* than @color
saturate(@color, 10%); // return a color 10% *more* saturated than @color
desaturate(@color, 10%);// return a color 10% *less* saturated than @color
fadein(@color, 10%); // return a color 10% *less* transparent than @color
fadeout(@color, 10%); // return a color 10% *more* transparent than @color
spin(@color, 10); // return a color with a 10 degree larger in hue than @color
spin(@color, -10); // return a color with a 10 degree smaller hue than @color

PS: 上述代码引自 LESS CSS 官方网站,详情请见 http://lesscss.org/#-color-functions 使用这些函数和 JavaScript 中使用函数一样。

清单 23. LESS 文件
1
2
3
4
init: #f04615; 
#body {
background-color: fadein(@init, 10%);
}

经过编译生成的 CSS 文件如下:

清单 24. CSS 文件
1
2
3
#body { 
background-color: #f04615;
}

从上面的例子我们可以发现,这组函数像极了 JavaScript 中的函数,它可以被调用和传递参数。这些函数的主要作用是提供颜色变换的功能,先把颜色转换成 HSL 色,然后在此基础上进行操作,LESS 还提供了获取颜色值的方法,在这里就不举例说明了。 LESS 提供的运算及函数特性适用于实现页面组件特性,比如组件切换时的渐入渐出。

模式匹配

清单 25. LESS文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.tri{
width: 0;
height: 0;
overflow: hidden;
.triangle(bottom);
}

.triangle(top,@w:5px,@c:#CCC){
border-width: @w;
border-color: transparent transparent red transparent;
border-style: dashed dashed solid dashed;
}
.triangle(bottom,@w:5px,@c:#CCC){
border-width: @w;
border-color: red transparent transparent transparent;
border-style: solid dashed dashed dashed;
}
清单 26. CSS文件
1
2
3
4
5
6
7
8
.tri {
width: 0;
height: 0;
overflow: hidden;
border-width: 5px;
border-color: red transparent transparent transparent;
border-style: solid dashed dashed dashed;
}

通过以上可以看出,我们可以在LESS中定义相同名字的样式,第一个参数不同,在调用时可以通过传入第一个参数进行匹配,得到不同的样式即可。

避免编译

清单27. LESS文件
1
2
3
.test{
width:calc(300px-30px);
}
清单28. CSS文件
1
2
3
.test {
width: calc(270px);
}

有时候一些样式如果编译了之后就会产生不必要的麻烦,如果我们不想要编译,可以改写如下

清单29. LESS文件
1
2
3
.test{
width:~'calc(300px-30px)';
}
清单30. CSS文件
1
2
3
.test {
width: calc(300px-30px);
}

通过在前面加上一个 ~然后将内容用引号引起来,就可以避免编译了。

总结

本文提到的只是 LESS 的基本功能,更高级的功能如:字符串插值,服务器端使用配置,JavaScript 表达式,避免编译等可以参看 LESS 的官方网站。 LESS 以 CSS 语法为基础,又借用了很多我们熟知编程式语言的特性,这对于我们开发人员来讲学习成本几乎可以忽略,它在保留 CSS 语法的基础上扩展了更多实用的功能,LESS 为我们提供了一种新的编写样式表的方法,我们可以根据我们的项目特性选择使用 LESS 的部分特性,我们只需用很少的成本就可以换了很大的回报,一句话,Less is more,借助 LESS 可以更便捷的进行 Web 开发。

个人日记

等啊等,终于等到五一放假,这次必须要出去好好玩一番,解脱一下,释放一下。经过一系列思想斗争,最终决定去泰安方特了!昨天方特之旅结束之后,甚是爽快,在此记流水账一篇,不喜勿喷哈! 话说回来还是要先说五月二号啊,简直了,本来打算是在五月二号去的,带上室友小周周和隔壁的花花学长。一号那天我定了第二天的火车,早上 7 点半的火车,室友小周周也提前约好了出租车,准备早上六点出发去火车站。万事俱备只欠东风了,当天晚上早早滚去睡觉,定好五点半的闹钟,准备静静等待闹钟的响起。可是,可是,一切都是来得那么突然,第二天一大早起来竟然下雨了!泰安那边下雨了!天哪,睡之前我还特意看了看二号是多云啊!这不是坑爹吗?下雨就下雨吧,可是你不知道下雨方特室外项目是不开放的好嘛!顿时心都凉了。╮(╯▽╰)╭ 哎,改签吧,小周周给那位出租司机打电话,我去重新改签票。嗯,又一个坑爹的事情发生了,7 点之前网上购票是不开放的好嘛?发车前两小时是不能在网上改签和退票的好嘛?哎,算了,还好票便宜,才十二块五,索性我不要了!不要了!默默去买了三号的车票,特么的还是无座啊,无座。哎,多么痛的领悟,╮(╯▽╰)╭。 然后,然后,就默默等到了三号,还是五点半的闹钟,这次终于不下雨了!苍天啊!带上手机,耳机,money,学僧证,充电宝,六点出门坐上 TAXI,朝火车站奔去,这次是真快啊这速度,路上都没车的,一路狂奔,竟然六点半多点就到了火车站了,可是我们七点半的火车啊,这不是坑爹吗?于是乎,我们决定改签一下提前一点,有个七点稍多的一趟车,于是乎我们兴冲冲地跑向了改签大厅,惊呆了!怎么它就那么长的队伍,哦你,那么长,么长,长。试着排了排,二十分钟往前走了不到十步,也是醉了。哎,默默走掉,去候车室等着吧,七点半就七点半! 呼呼呼,检票咯,K771,我们走上站台,看到一条漫长的绿皮车,不是说好的无座吗?怎么这么多空座,我们三个找到一个就坐下来,不得不再次吐槽一下这种座位真难受。早上好困,睡觉睡觉,带上耳机听着歌,像打坐一样睡着了,就那么睡着了,我好佩服我自己。睡了一个小时,一睁眼,泰山站到了,不错不错,下车,出发。 出站了,果然不出我所料,好多人都拿着牌子,问去方特吗?免费接送,立即出发,同时卖门票,220 一位,其实方特门票就是 220 的,学生票可能就便宜 10 块钱。哎,这样就这样吧,他们是打着免费接送的名义,实质钱都包含在门票里面了。我们也担心方特人特别多,到时候买票排个时候,就直接跟着他走吧,反正也贵不了多少。 车是一个公交车,等人来齐了一起发车出发,我们几个竟然赶到了第一波,进去的时候车是空的,要等到其他人来才发车,这不是坑爹吗?坑爹吗?说好的立马出发的呢?算了,不计较这些了,其实等了也没多久就走了。在车上交了钱买了门票,那个收钱的人(姑且叫导游吧)跟我们讲了方特的各个注意事项,还是挺贴心的嘛!在这里透露给大家一点,记不住的我就记不住了。 包包最好要寄存起来,最好寄存在方特外面,比较便宜,当然也可以背着如果你不嫌沉的话。 贵重物品一定要看管好,方特里面丢的最多的就是数码相机,然后是手机,保管好了就行咯。 如果你不想一开始就玩惊险刺激的,建议从右边开始玩,如果你一开始就想玩刺激的,建议还是从右边开始玩哈哈哈。 项链啊首饰啊最好要收起来,要不然你想体验在空中翻滚的时候项链打到牙的感受吗? 哦,其他的忘了,我也不知道他说了多少了,可能比较健忘,记不起来还有啥了。后面的我就根据我的亲身体验来说了,哈哈。 走了半个多小时,终于到了!我们去存上包,买好雨衣以防神秘河谷的时候淋湿。正式进门!进门!冲冲冲!方特之旅正式开始!差不多九点半! 放个地图先 20150504150649 恩,进去看过去就是一个方特城堡,好有艺术气息的,旁边还放着欢乐的音乐,走在小路上,还有阳光照射着,哈哈不错。而且关键是路上人好少啊,哈哈,是不是五月三号外地的都回家了呢?是吗?后面你就知道了。 4d6577b5ga2451346b0fc&690&690 恩,我们没有进城堡玩,直接奔向右边的飞跃极限。噢丫,谁说的人少的?谁说的人少的?人都跑进来了,排队啊,一层、两层、三层…不数了,心好累。也就十来层吧。排个毛线,我们直接冲向了下一站,神秘河谷。PO 个图先 25540577,1681257058& 简直是人品好,我们进去的时候竟然排到了前面,是不是刚开放就被我们赶上了呢?而且,我们赶上了第一波。这次在外面买的雨衣派上用场了,穿上我们的半透明红色雨衣,带上蓝色高筒鞋套,上船。船一排四个人,一共好像是五排吧,我们很果断地选择了第一排,雨衣全部裹起来。准备冲,看到图了吗?这是一个小船从河谷里面二十米高空俯冲下来的样子,看那浪花。这次我们真正地感受到了它的魅力。河谷里面其实是有两个小下坡的,第一次比较小,杀杀你的锐气,第二次就是图中这个超大的下坡,从楼顶的那个位置冲下来,自己想象!第一次下坡还好,等到第二次下坡的时候,啊啊啊啊,二三十米高空俯冲,那种朝下的重力加速度深深牵引着我们,俯冲的时候我们叫喊着,咆哮着,嘶吼着,身心全部都感受着重力加速,咣!超大的浪花被我们激起来了,溅起十多米,丫的坑爹的是浪花不只是超前喷的啊,特么的它在空中划了一道优美的弧线直接想瓢泼一样泼到了我们身上啊,尤其是第一排啊第一排,最惨烈啊,尼玛我当初怎么就选了第一排啊,这不是坑爹吗?我真的是穿了雨衣,但是我屁股底下的雨衣不够长啊,水落到了座位上,然后水就浸到我的裤子里面去了啊,一阵凉意袭来,嗖嗖嗖,我屁股湿了,湿了。坑爹啊,这雨衣抵了个毛用啊。随后船缓缓划回去,出来的时候我走出来,屁股底下湿了一片,像尿了裤子一样,怎么能这样啊?我才玩了一个就湿了,湿了啊,╭(╯^╰)╮ 怎么这样。 还好太阳好,我就叉开腿走着,自己想象!赶紧的太阳把我的裤子晒干吧。接下来我们去了维苏威火山,听说这个还不错。果然,走到旁边就听到了火山里面一群人的嘶吼,开始排队吧,人也不大多啊,应该还是人品好的缘故吧,嗖嗖就排上了。PO 个图先 3e4c18d1g778440eee0a9&690 对,就是这么酷炫,这就是传说中的过山车,一个个小匣子接起来,我和花花坐在了一个匣子里,抓紧扶手,小车就开动了,呼呼呼,伴随着轮子的摩擦,伴随着我们的尖叫,在急转弯的地方,车呼啸而过,整个人有种呼之欲出的感觉,巨大的离心力简直爽到爆。一个又一个急转,一次又一次的钻洞,一声又一声的呐喊,一下又一下的心跳。自行想象! 出来之后旁边有个火流星,可是排队的人太多了,机智的我们就继续绕,看到有一个影视特技演出室刚开放,就跑进去了。尼玛一进去就是一个人在表演剁手,当然那刀是假的,duang,duang,duang,加了特效而已,后来欣赏了一点电影特效制作的过程,整体不算经验刺激,还说得过去吧。 后来继续绕,到了未来警察和生命之光那里,可惜的是都没有到开放的点,就去了星际航班,这下排队的人真多,到底什么鬼,干嘛的。我先 PO 个图 别看这个外面有个火箭什么的,这么酷炫,其实进去啊,我总算见识到了,排了半个小时队,最后二十个人坐在一个舱里,这个舱是可以动的,上下左右倾斜,可以模拟一下加速度什么的,然后整个前面就是一个超大的圆形幕布,像放电影一样,来给你模拟一个飞行的过程。随着画面的切换,舱左右摆动,也算是逼真。出来之后看到外面这个火箭,我本来还真以为要飞天呢,结果给我整这么一出,我也是醉醉的。 接下来就是恐龙危机了,一走过去吓我一跳啊,这是什么玩意翻了啊,上面的字怎么还是倒着的?好吧,经过缓过神来,不得不佩服这建筑真是溜啊,真是设计师逼死建筑师的典范啊,来来来,看图你就知道我为啥这么说了,尼玛这整个建筑都被恐龙掀翻了好嘛? XocG88iM553THvvg 虽然外面看着是倒着的,不过进去之后是正着的。呸,我这不废话么,倒着谁能走啊?进去也是想刚才那样,几个人坐在一个小车里,有个跑道,小车会在跑道上走,有时候转来转去。而四周当然就是恐龙咯,带上 4D 眼镜,能够看到周围银幕上一只只恐龙扑过来,小车上还有各种音效。小车有时候没油了,有时候加速了,有时候甩尾了,有时候弹飞了,模拟得非常逼真。特么的走到一个地方上面突然出现一个霸王龙头啊,这次不是屏幕出来的啊,是实物啊,一动一动的,它的牙像魔鬼的爪牙,还迈着魔鬼的步伐,在我不经意间粗线了啊,不能忍,特么的还长着嘴朝着我吼啊,吓尿了有木有。晃来晃去,这破小车终于抵达了传说中的安全地带,我也解脱出来了,╭(╯^╰)╮。臭霸王龙。 哎呦终于出来了,这也不能光玩室内的啊,来点室外的体验一把。于是乎,我们走到了一个叫波浪翻滚的东西那里,我了个去,这不就是济南泉城公园里面的遨游太空加强版吗? 遨游太空是这样子的 20150504202138 这里的波浪翻滚是这样子的,这何止是一样啊,这简直就是一样啊! 1278644413015_1_o 坐在那上面,就尽情地摇摆摇摆吧,飞上来,飞上去,有时候这玩意还是倾斜的啊,哦飞天的感觉真爽哈哈哈! 接下来就是传说中的大摆锤了,据说好多人都在这一关不行不行的了,上吐下泻的统统地都有。我记得还有人说买了一张票一开始就坐了个大摆锤,直接不行了,后面的项目都玩不了了,合着 220 块钱就坐了个这个,真值!我还是准备挑战一下,会会它,看看这玩意到底多么厉害。先 PO 图一张。 20131119GAig3 嗯,就是这个样子滴,它一边摆,一边转啊,最开始摆的角度是很小的,后来最高的时候都有 90 度了吧,我一边承受着离心力,一边又在半空中飘荡,简直有一种超凡脱俗的感觉。啊,当到达最高点的时候,我整个人横在了半空中同时围绕着中间的转轴在转啊,转啊。神马姿态,多么优美啊!我当时脑子里在想神马?我还活着吗?神呐。 最终,我还是活着下来了,然后又来了一个刺激的,体验蹦极的感觉。PO 图一张,先提前感受一下。 624188preview4 对,就是它!太空飞梭,哈哈哈哈,体验一下从 100 米高空坠落的感觉吧!上去坐在座位上绑好了,深呼吸一下,就在那一瞬间,就是那一瞬间,座位以一个超快的速度飞到架子的顶端啊!注意它不是缓缓上去的啊,是突然地一下,升到最上面,只需要短短几秒,那一瞬间啊,我的天,周围的人的呐喊,小周周的咆哮,花花的哭喊混成一团,就像火箭升空的感觉一样。然后,座位在空中停住了,尼玛这时候我们已经升到了最高的地方了啊。还不够形象,再来一张图!QQ截图20150504211315 将近一百米的高空,我们坐在座位上,腿是悬空的,朝下一瞅你可能就会昏死过去。然后,然后!这座位突然不受力了,直接自由落体!我们也跟随着座位飞下来,呐喊声,咆哮声混作一团,而我也在撕吼,啊啊啊啊。真正的自由落体,感受到了吗?而且它接下来还没完啊,又升上去,然后又来一次降下来。呼呼呼呼呼,简直了!!嗯,最后我还是活着下来了。 其实,这个还真的不够刺激,然后我们又来了一个刺激的,空中飞舞,哈哈哈,堪称一绝了。先 PO 个图感受一下。 简直了,这个巨大的翅上都坐着人,翅子这玩意不仅是自己在转,而且这个长臂在左右上下摇摆,尼玛可想而知我们的人在天上是波浪翻滚啊,朝上甩,朝下甩,朝左甩,朝右甩,有时倒这甩,有时正着甩。有没有玩过小时候的波浪鼓,我们这些人就想波浪鼓上面的两个圆球,各种甩,各种浪,在上面想不喊都不行,在上面我们的撕吼声简直是无与伦比,有时真的感觉自己就要被甩出去的样子,自己感受一下吧。这个简直是爽哭我了,花花直接吓得没敢坐,我和小周周真是感受到了这玩意的魅力,太浪了,也是浪到不行不行的。 接下来坐了坐勇敢者转盘,就是它!就是它!罪该万死的家伙!直接把我转晕了! 68a8f2b5-02d5-4fda-87cb-ae5fca294f04 别看他只是个转盘拉着一个个小车在那里转,其实它离心力特别大,我的腿在小车里简直都抬不起来。看到这个转盘下面的支撑臂了没?一开始它是平的啊,也就是说我们最开始是在水平面上转的,随着时间推移,这个臂慢慢往上升,升到将近 90 度的样子,尼玛我就简直被离心力牵引着在转啊,你可以想想我在最高处就是脚朝上头朝下在转啊转啊转啊转。而且离心力没那么大的话我还是斜着的好嘛?我的脖子也在不由自主地斜着,出来的时候真是转得我晕死了,脖子差点没正过来啊尼玛。 就是因为这个破玩意,我走路都开始晕了,趴桌子上一会,碎一觉,就没事了,起来接着浪! 恩,然后接下来就是飞越极限了,这个效果简直赞啊。排队的人虽然多,但是一次性进去 50 多个人一起飞。坐在椅子上,脚是悬空的,在眼前会有一个超大超大的银幕,先 PO 图一张。 W020140505433553648700 应该就是这种感觉吧,一排排人坐着,座位可以模拟飞行时的感受,比如模拟加速,左右转等等的动作,相当逼真,而且还吹着风,模拟一下风中飞行的感觉。当然最享受的还是眼前的超大的幕布,一幅幅 3D 效果的画面,有艾菲尔铁塔,金字塔,故宫,长城,布达拉宫,大峡谷,珠穆朗玛峰,东方明珠等等,风景特别多。在飞行的过程中带你穿越世界各地,游遍千山万水。实在是太棒,不得不赞一个。像的看了一个超大的电影偶也! 恩,最最最重头戏来了,火流星!号称是最惊险刺激的一个项目,如果你一开始坐了这个,其他的真的一点意思都没有,所以我们把这个放在了最后。来张图先感受一下。 20131216191455574 哈哈哈,害怕了吧,整个人都是坐在座位上脚是完全悬空的,图上的人在以一个超快的速度在做空中大反转,我估计他们差点就要被甩出去了。来张全图感受一下。 QQ截图20150504215457 恩,这跑道有正的有倒的,最高处差不多有三十四米。花花没有一起坐这个,我和小周周去尝试了。绑好之后,我们在那条长直道上慢慢地往上升,尼玛在升的过程中其实是比较慢的,十米,二十米,三十米,升的过程中我的脚下是悬空的,没有任何踩踏的地方,朝下一看,整个就是一大悬崖啊,啊啊啊。我想,一声我爱过这个世界足以表达我当时的感受。接着升到最高地方,就立即向下俯冲了,尼玛啊,简直就是自由落体一般的速度啊,而且这轨道它还不是一个平稳的正圆啊,跑道就像麻花一样向前延伸着,而我们就一边在自转一边向下俯冲,我的妈啊,全车的人都在喊叫,整个在穿梭的过程中肩膀被死死压着,一个巨大的扭转力扭动着我的身体,身体向前前进的同时在做大回环运动。这还不算完,降落到最低点之后接着又是一个上升,一个弧形的 360 度大回环跑道,那时我们的脚完全是在上方,头在下方,死死地卡在座位上,巨大的离心力给我的身体难以承受之重。图中那么长的跑道,跑完需要多久?2 分钟?1 分钟?不,只需要 30 秒!30 秒啊!那速度简直太快了,一个劲的酸爽,真能到达极限了。下来之后,感觉整个人都接受了全身的按摩,浑身的酥麻感觉,也是爽到不行不行的! 火流星,真是名副其实,就像太空穿梭一遍回来了一样,无法比拟的刺激。嗯,坐完这个心里就踏实了。哈哈。 后来就是几个比较放松的项目了,海螺湾,生命之光,转转杯,放松体验一下,最后爬了一下方特城堡,也算是圆满了。 晚上在方特门口拍了拍照,买了个西部牛仔帽子,留作纪念。方特之旅正式圆满结束!偶也! 最后 PO 图几张,留作纪念!真是开心!愉快! 再见!方特!再见!泰安!么么哒! QQ截图20150504221857 QQ截图20150504222004 QQ截图20150504222028QQ截图20150504221741

Other

综述

优秀的代码风格如同一身得体的打扮,能够给人以良好的印象。初学程序设计,首先必须建立良好的编程习惯,这其中就包括代码风格。本文就代码风格中的几个重点问题进行了讨论,并在文后给出了一份优秀的代码作为风格模板。代码风格不必花费太多专门的时间研究,在使用中不断模仿模板代码,轻轻松松就能写出“专业的代码”。

80字符,代码行极限

无论时空怎么转变,世界怎样改变,一行80字符应始终铭记心间。古老的Unix终端以80列的格式显示文本,为了让源代码与手册具有最佳的可读性, Unix系统始终坚持着80列的传统。80列不多不少,足够写出一行有意义的代码,同时也足够显示在终端屏幕,足够打印在A4纸上。虽然时至今日,我们的屏幕分辨率早已足够高,一行能够显示的内容远超超过80字符,但我们的优秀传统已经形成──几乎所有的Unix/Linux内核源代码以及联机用户手册都严格地遵守着80列极限。如果你正好在使用Windows平台下的Dev C++,你是否有注意到代码编辑框里那条细细的灰色竖线?不错,那正是代码行极限。除了HTML、XML等冗长繁复的标记式语言,几乎所有的语言都需要严格遵守代码行极限,这包括C、C++、Java、C#、Python、PHP等等。不过有时,比如当PHP跟HTML打交道的时候,这个限制是可以暂时放松的。过长的代码行总是不好的,好的代码要始终保持苗条的身材。

Tab还是Space,众说纷纭的缩进方式

代码离不开缩进,关于缩进主要有两个争论,一个是该用空格(Space)还是用制表符(Tab),另外一个是该用4格缩进还是8格缩进甚至都不是。 先来谈谈Space与Tab的问题。坚持用Space的程序员会告诉你,如果你从来都不用Tab,那么你的代码放到所有的地方看都是一样的。没错,这是用Space缩进的优点,可惜的是,这是它唯一的优点。代码层次越多,内层代码最前面的缩进便越多,这意味着你需要敲很多很多次空格。即使你能忍受不厌其烦地按空格键直到它坏掉,你也一定会被IDE总是自作聪明地插入一些Tab字符的行为烦恼不已。建议总是使用Tab缩进,因为几乎所有的代码(不仅仅是C 代码)都在使用Tab缩进。 Tab到底是4格还是8格?这是Tab缩进会被某些人诟病的根源。当你写程序时使用的Tab大小与别人读程序时使用的Tab大小不同时,再漂亮的排版也会变得杂乱无章。标准的Tab是8格的,而不幸的是,几乎所有的Windows平台下的IDE,包括 Visual Studio、Dev C++,甚至跨平台的Eclipse等,都默认使用4格Tab。我使用的FreeBSD系统的所有的内核源代码都采用8格缩进,所以我一直坚持使用8格缩进。也许你不习惯太大的间距,如果不是在Unix平台下,或者不是C语言,那就采用4格Tab吧。如果你在Unix下编写C代码,使用8格的标准Tab是更好的习惯。

折行原则,容易被忽略的角落

既然有代码行极限,很多情况下我们不得不断开一个完整的代码行,这就带来了一个问题:折行后应该如何缩进?好的做法是,第一次折行后,在原来缩进的基础上增加1/2的Tab大小的空格,之后的折行全部对齐第二行。可能这样的文字描述过于晦涩了,还是举个例子罢(以8格缩进为例):

1
2
3
4
5
6
7
if (value > a && value > b && value > c && value < d && value < e && value < f
value < h && value < h) { /* 注意折行后的缩进 */
value = value + 1;
value = value * value * value * value * value * value * value * value
* value * value + value * value * value * value * value * value
* value * value; /* 注意再次折行后的缩进 */
}

显然这个段代码没有任何实际用处,只是为了说明折行缩进而编造的。

无处不在的空格,无处不在的空行

需要空格的位置有:

  • if、while、switch等关键字与之后的左括号(之间。
  • 左花括号{之前。
  • 双目运算符两侧,例如p == NULL。
  • 逗号,与分号;之后,例如for (i = 0; i < 10; i++)。

不要空格的位置有:

  • 函数名与之后的左括号(,包括带参数的宏与之后的左括号(,例如max(a, b)。
  • 分号;与冒号:之前。
  • 左括号(右边,右括号)左边,例如if (p == NULL)。

需要空行的位置有:

  • 函数的定义之前、函数的定义之后
  • 一组联系紧密的代码段之前和之后

这些规则并不完全,当你碰到上面没有列举出来的情况时,请参考本文提供的模板代码。

左花括号的争议──换行乎?不换乎?

这又是一个仁者见仁智者见智的问题了。从使代码更清晰的角度看,作为代码段开头标识的左花括号{应该另起一行:

1
2
3
4
5
if (p == NULL)
{
printf("error!\n");
exit(0);
}

可是,这看起来实在不够紧凑,所以大部分的C代码(至少Unix上如此)都采用了这样的方式:

1
2
3
4
if (p == NULL) {
printf("error!\n");
exit(0);
}

我的建议是采用后者,这会使你的代码显得更加紧凑,也更加专业。需要说明一个特例,在定义函数时,我们总是要给左花括号 { 换行:

1
2
3
4
static int maxint(int a, int b)
{
return a > b ? a : b;
}

额,不过我还是不太习惯额

总结

本文介绍了编程的代码风格,希望对大家有帮助 本文内容为转载,原文出处

HTML

目前响应式布局和扁平化越来越风靡,前端 UI 框架也是风生水起,在这里分享几款优秀的前端 UI 框架

BootStrap

相比这个大家都听说过吧,Bootstrap,来自 Twitter,是目前最受欢迎的前端框架。Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷。 20150427144535 官网链接 中文链接

Foundation

与 Bootstrap 相比,Foundation 屈居第二,不过这并不能说明它不受欢迎。 ZURB 在后面强有力的支持,使得 Foundation 更加强大!Foundation 被各大网站使用,如 Facebook、Mozilla、Ebay、 Yahoo、美国国家地理网站等等。 20150427154906 官网链接 中文链接

Semantic Ui

语义化设计的前端框架,为攻城师而制作的可复用的开源前端框架。 20150427155432 官网链接 中文链接

Amaze UI

Amaze UI 是一个移动优先的跨屏前端框架。… Amaze UI 以移动优先(Mobile first)为理念,从小屏逐步扩展到大屏,最终实现所有屏幕适配,适应移动互联潮流。 20150427155625 官网链接

Purecss

免费响应式 CSS 前端框架是有 Yahoo 发布的一个可以应用到所有 web 项目的小巧的、响应式的 CSS 模块集,Pure 是建立在 Normalize.css 的基础上的,对 HTML 元素提供了布局和样式,增加了一些公共的并且是你需要的 UI 组件。 20150427160204 官网链接 中文链接

JavaScript

综述

最近做项目的时候发现了一个非常奇怪的问题,就是对于click事件的响应。经过测试发现,对于IOS平台,直接监听click事件可能是没有响应的,而在Android和PC上则完全没有问题。所以通过获取设备信息实现了不同平台的不同监听。

IOS监听

对于IOS设备,只监听click方法可能是没有响应的。解决方法就是监听 “touchend click”事件。 而对于Android和PC,则只监听click事件即可。

平台检测

我们利用userAgent来检测平台

1
2
3
4
5
6
7
8
9
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
//alert(navigator.userAgent);
alert("iOS");
} else if (/(Android)/i.test(navigator.userAgent)) {
//alert(navigator.userAgent);
alert("Android");
} else {
alert("PC");
};

上面的JS代码可以检测三个平台。

实现监听

我们可以把方法自定义名字,比如

1
2
3
4
5
function back_click(){
$(".group-names").show();
$(".groups:visible").hide();
$(this).hide();
}

然后跨平台实现监听

1
2
3
4
5
6
/* bind the event */
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
$(".back").bind({"touchend click":back_click});
}else{
$(".back").bind({"click":back_click});
}

通过以上监听便没有问题了。

总结

通过以上方法便可以实现不同平台的监听。

JavaScript

综述

这里我们来利用jQuery实现瀑布流效果。

在线演示

我们首先来在线演示一下效果,然后我们说一下是怎样的实现,点开链接进行预览吧 在线预览

代码分析

首先我们在页面中加入了一些图片元素,都加到id为container的元素中,每一张图片都在一个class为box的容器里,然后里面是一张图片。

1
2
3
4
5
6
7
<div id="container">
<div class="box">
<div class="content">
<img src="img/1.jpg">
</div>
</div>
</div>

在JS中,我们遍历了每一个box,实现box的位置为上一行高度最小的box的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function imgLocation(){
var box = $(".box");
var boxWidth = box.eq(0).width();
var num = Math.floor($(window).width()/boxWidth);
var boxArr=[];
box.each(function(index,value){
var boxHeight = box.eq(index).height();
if(index<num){
boxArr[index]= boxHeight;
}else{
var minboxHeight = Math.min.apply(null,boxArr);
var minboxIndex = $.inArray(minboxHeight,boxArr);
$(value).css({
"position":"absolute",
"top":minboxHeight,
"left":box.eq(minboxIndex).position().left
});
boxArr[minboxIndex]+=box.eq(index).height();
}
});
}

最后实现了滚动条滑动时的动态加载

1
2
3
4
5
6
7
function scrollside(){
var box = $(".box");
var lastboxHeight = box.last().get(0).offsetTop+Math.floor(box.last().height()/2);
var documentHeight = $(document).width();
var scrollHeight = $(window).scrollTop();
return (lastboxHeight<scrollHeight+documentHeight)?true:false;
}

调用如下

1
2
3
4
5
6
7
8
9
10
window.onscroll = function(){
if(scrollside()){
$.each(dataImg.data,function(index,value){
var box = $("<div>").addClass("box").appendTo($("#container"));
var content = $("<div>").addClass("content").appendTo(box);
$("<img>").attr("src","./img/"+$(value).attr("src")).appendTo(content);
});
imgLocation();
}
};

通过上述简单的设置我们就实现了瀑布流的效果。

源码下载

源码下载

综述

以上便是瀑布流效果的实现,希望对大家有一定帮助!

C/C++

综述

OpenGL 中的变换可以分为下面的三种: 即模型变换、投影变换、视口变换。 模型变换其实就相当于图形的几何变换,包括平移、缩放、旋转等操作,下面我们来详细研究一下 OpenGL 中三种操作的函数应用。

准备工作

在变换前我们首先要做一下准备工作,首先我们需要调用 glMatrixMode 函数来设置变换模式。 该方法介绍如下 glMatrixMode(Glenum mode),设置当前矩阵模式,它具有三个参数,分别为GL_MODELVIEWGL_PROJECTIONGL_TEXTURE。三个参数的含义为:

GL_MODELVIEW,对模型视景矩阵堆栈应用随后的矩阵操作,也就是针对本节的模型几何变换设置的模式,本节所有内容均为此模式。 GL_PROJECTION,对投影矩阵应用随后的矩阵操作,在投影变换中设置的模式。 GL_TEXTURE,对纹理矩阵堆栈应用随后的矩阵操作,在设置动态纹理的过程中设置的模式。

所以,在本节我们就需要设置如下的模式

1
glMatrixMode(GL_MODELVIEW);

接下来,我们需要设置

1
glLoadIdentity();

这个方法的作用是设置将当前的用户坐标系的原点移到了屏幕中心,类似于一个复位操作。 好,设置好以上两个条件之后我们就可以来进行变换啦。主要有以下三个函数:

1
2
3
glRotatef(angle,vx,vy,vz) //绕向量(vx,vy,vz)为轴旋转角度angle
glTranslate(dx,dy,dz) //平移,位移量为向量(dx,dy,dz)
glScalef(sx,sy,sz) //比例缩放,x,y,z 方向的缩放因子分别为sx,sy,sz

下面我们来详细说明这三个函数的用法。

平移

三种变换中,平移变换是最简单的。 类库中提供了下面两个方法:

glTranslated(GLdouble x,GLdouble y,GLdouble z) glTranslatef(GLfloat x,GLfloat y,GLfloat z)

这两种方法是基本是等价的,一种方法是传入 double 类型的参数,另一个是传入 float 类型的参数。 方法的作用是将物体分别沿 x,y,z 轴平移 x,y,z 的单位长度。 利用上一节的机器人我们来感受一下

1.向右平移 100 像素

1
glTranslated(100,0,0);

运行结果 20150422142910 可以看到,机器人向右平移了 100 像素

2.向左下分别平移 50,50 像素

1
glTranslated(-50,-50,0);

运行结果 20150422143102 可以看到,机器人分别向左和向下平移了 50 像素

3.向左下分别平移 50,50,向前平移 100

1
glTranslated(-50,-50,100);

运行结果 20150422143102 哦,和上一个类似,也就是说在 z 轴上平移没有什么效果吗?难道因为我画的是二维平面图形?哦不,那不应该是距离我的视线更近了所以看到的更大了吗?好吧,事实好像不是这样的,我就姑且认为二维平面在 z 轴上的平移是没有影响的吧。 恩,这个函数基本用法就是这样,通过传入不同的 x,y,z 值实现平移即可。

旋转

旋转的方法有下面两个:

glRotatef(GLdouble angle,GLdouble x,GLdouble y,GLdouble z) glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)

其中第一个参数是旋转的角度,后面三个参数 x,yz 参数确立了旋转轴,旋转轴是原点(0,0,0)与(x,y,z)的连线。 下面我们用几个例子来感受一下

1.绕 x 轴旋转 60 度

1
glRotated(60,10,0,0);

运行结果 20150422144433 恩,它变扁了,旋转中心在机器人的中心,也就是中间的小红点,设想一下,绕 x 轴旋转 60 度,的确我们看到的应该就是变扁的机器人

2.绕 z 轴旋转 90 度

1
glRotated(90,0,0,10);

20150422144851 可以看到,机器人绕中心点旋转了 90 度,是逆时针方向。

3.旋转平移结合

感受完上面的两个例子之后,重头戏来了,上面的例子中旋转轴是(0,0,0)和(x,y,z)两点的连线,也就是旋转轴总是通过坐标原点的。 那如果我们要让物体绕特定的旋转轴来旋转,比如绕(1,2,3)和(4,5,6)来旋转怎么办? 这里就要用到平移和旋转的组合了。

如果旋转轴通过(xp,yp,zp),而不通过坐标原点。假如旋转轴为(xp,yp,zp)和(xq,yq,zq)确立的。我们首先要将图形沿 x,y,z 方向分别平移-xp,-yp,-zp,然后旋转参数(x,y,z)分别传入(xq-xp,yq-yp,zq-zp)三个值,旋转完毕之后再把图形沿 x,y,z 方向分别平移 xp,yp,zp 个单位长度即可。

在这里,我的教材和指导书中的内容又发生了冲突,教材中说的是先沿 x,y,z 方向分别平移-xp,-yp,-zp,再沿 x,y,z 方向分别平移xp,yp,zp还原。而指导书中则是先沿 x,y,z 方向分别平移xp,yp,zp,再沿 x,y,z 方向分别平移-xp,-yp,-zp还原,到底谁对谁错呢?我们来一个小例子验证一下。 如果觉得无聊,可以自行忽略下面小段内容。 为了直观地表示,我们统一设 z 坐标为 0,在 xy 平面中观察变换过程。 假设我们有一个点(2,2,0),旋转的轴我们设为(1,2,0)和(4,5,0)的连线,那么该点绕轴旋转 180 度之后应该会是(1,3,0),从图上可以很直观地看出 111 那么在调用类库的时候,我们首先就要对这个点进行平移,然后旋转,然后再反平移恢复。

首先,我们按照第一种说法,即先沿 x,y,z 方向分别平移-xp,-yp,-zp,即(2,2,0)减去(1,2,0),变为了(1,0,0),然后传入(xq-xp,yq-yp,zq-zp)实现绕(0,0,0)与(xq-xp,yq-yp,zq-zp)两点连线为旋转轴来旋转 180 度,显然是绕(0,0,0)与(3,3,0)的连线来旋转的,这个点变为了(0,1,0),然后再沿 x,y,z 方向分别平移 xp,yp,zp 还原,即加(1,2,0),即加得到的结果是(1,3,0)。确实与图中点的符合,所以这一种说法验证正确。 另一种说法,即先沿 x,y,z 方向分别平移 xp,yp,zp,即(2,2,0)加上(1,2,0),变为了(3,4,0),然后传入(xq-xp,yq-yp,zq-zp)实现绕(0,0,0)与(xq-xp,yq-yp,zq-zp)两点连线为旋转轴来旋转 180 度,显然是绕(0,0,0)与(3,3,0)的连线来旋转的,这个点变为了(4,3,0),然后再沿 x,y,z 方向分别平移-xp,-yp,-zp 还原,即减去(1,2,0),即加得到的结果是(3,1,0)。与图中的点不符,所以这一种说法验证失败。

可见,我们得到的结果是

如果旋转轴通过(xp,yp,zp),而不通过坐标原点。假如旋转轴为(xp,yp,zp)和(xq,yq,zq)确立的。我们首先要将图形沿 x,y,z 方向分别平移-xp,-yp,-zp,然后旋转参数(x,y,z)分别传入(xq-xp,yq-yp,zq-zp)三个值,旋转完毕之后再把图形沿 x,y,z 方向分别平移 xp,yp,zp 个单位长度即可。

所以,我们要实现机器人绕两点(10,20,30)和(40,50,60)定义的轴线旋转 45 度,代码实现如下

1
2
3
4
glTranslatef(-10,-20,-30);
//(40,50,60)-(10,20,30)=(30,30,30)
glRotatef(45,30,30,30);
glTranslatef(10,20,30);

运行结果 0150422151947 运行结果可能不太直观,可以直接参考代码实现

缩放

对于缩放,我们的方法也是有两个

glScaled(GLdouble x,GLdouble y,GLdouble z) glScalef(GLfloat x,GLfloat y,GLfloat z)

区别也就是一个参数为 double 类型,一个为 float 类型 函数的作用是将物体分别在 x,y,z 轴方向缩放为 x,y,z 倍,缩放中心为原点 下面我们来几个小例子感受一下

1.沿 x 轴放大 2 倍

1
glScaled(2,1,1);

运行结果 20150422153045 画面好美!

2.沿 y 轴 z 轴分别放大 2 倍

1
glScaled(1,2,2);

运行结果 20150422153914 因为整个图形是一个平面图,所以我们只可以看出 z 轴放缩的效果

3.放缩和平移的变换

和旋转的原理一样,默认的放缩中心是原点,在这里,如果我们的放缩中心如果不是原点,我们该怎样设置呢? 假设放缩中心为(xp,yp,zp),方法如下:

如果我们的放缩中心设置为(xp,yp,zp),而不是坐标原点。我们首先要将图形沿 x,y,z 方向分别平移-xp,-yp,-zp,然后放缩参数(x,y,z)分别传入三个值代表放缩比例,放缩完毕之后再把图形沿 x,y,z 方向分别平移 xp,yp,zp 个单位长度还原。

下面,我们以(50,50,50)为放缩中心来对机器人的 xy 方向分别放缩 1.5 倍

1
2
3
glTranslated(-50,-50,0);
glScaled(1.5,1.5,0);
glTranslated(50,50,0);

运行结果 20150422155459 这样,我们就实现了以(50,50,0)为放缩原点来进行放大 1.5 倍的操作。

综述

在本篇我们描述了 OpenGL 中的平移旋转放缩变换操作,以及一些实例演示,希望对大家有帮助!

C/C++

综述

上一节我们利用种子填充算法实现了机器人的区域填充,我们可以发现,种子填充需要一定是时间,并不能在第一时间填充完毕。这里我们介绍一种更加简单的方法,我们利用 glPolygonMode 来绘制机器人,感受一下。

注意

我们曾经分别使用过类库和直线圆弧生成算法来绘制过机器人的线条。 类库绘制法:OpenGL 绘图实例一之机器人的绘制 算法绘制法:OpenGL 绘图实例二之直线和圆弧的绘制 填充算法也有两种,一种是种子填充算法,另一种就是本篇说的方法。 在这里我们可以归一下类,算法绘制线条方法和种子填充算法中的每一个点都是我们一点点绘制的,都是我们利用算法一点点计算的坐标来绘制,因此,可以把他俩归为一对。而类库绘制线条方法和本篇说的填充方法都是指定一部分点,类库为我们实现了填充,因此可以把他俩归为一对。下面就让我们来感受一下类库填充的魅力吧!

函数说明

1.函数原型

函数原型:void glPolygonMode(GLenum face, GLenum mode); 说明:控制一个多边形的正面和背面的绘图模式。表示多边形应该被画成点、轮廓还是填充形式。在默认情况下,多边形的正面和背面都被画成填充形式。 函数 glPolygonMode()用于改变当前的多边形绘制模式,它有两个参数。第一个参数指定哪些面将被这个调用,可能的值为 GL_FRONT、GL_BACK 或 GL_FRONT_AND_BACK,分别对应于前向多边形、后向多边形或两者。第二个参数指定将要使用的模式,可以为 GL_POINT、GL_LINE 或 GL_FILL。 下面我们来用一个小 Demo 来感受一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_TRIANGLES);
glVertex3f(‐0.8f,0.0f,0.0f);
glVertex3f(‐0.6f,0.0f,0.0f);
glVertex3f(‐0.7f,0.2f,0.0f);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_TRIANGLES);
glVertex3f(0.1f,0.0f,0.0f);
glVertex3f(‐0.1f,0.0f,0.0f);
glVertex3f(0.0f,0.2f,0.0f);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
glBegin(GL_TRIANGLES);
glVertex3f(0.6f,0.0f,0.0f);
glVertex3f(0.8f,0.0f,0.0f);
glVertex3f(0.7f,0.2f,0.0f);
glEnd();

以上三种模式分别是绘制了填充的三角形,三角形的边界,三角形的三个点。

2.画多边形

在 glBegin 的参数传入 GL_POLYGON,绘制的时候所有的点就会连接而成一个多边形。

3.线条的粗细

1
glLineWidth(lineWidth);

利用这个方法,我们可以传入像素值大小,比如传入 2,画线的时候就是 2 像素的粗细。

绘制机器人

接下来我们利用这个方法来绘制机器人吧!我们可以注意到,定义了 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)之后, 如果 glBegin 传入的是 GL_LINE_LOOP,绘制的仍然是封闭的曲线,如果 glBegin 传入的是 GL_POLYGON,绘制的则是封闭的填充的图形。 图片1 如果要实现上图的机器人,我们既要绘制黑色的边线,又要绘制带颜色的区块。所以我们就需要分别调用两种绘制模式,绘制的过程是完全相同的。所以,我们可以把模式当做一个变量来传入参数中改变绘制模式。比如之前的绘制三角形的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

就可以改写为

1
2
3
4
5
6
7
8
9
10
11
12
13
//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3,int MODE){
//画封闭线
glBegin(MODE);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

在调用的时候我们只需要分别传入 GL_LINE_LOOP 和 GL_POLYGON 就可以了。 所有之前的方法改写如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY,int MODE){
//画封闭曲线
glBegin(MODE);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR,int MODE){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(MODE);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius,int MODE)
{
//开始绘制曲线
glBegin(MODE);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius,int MODE)
{
//画全圆
glArc(x,y,0,2*PI,radius,MODE);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3,int MODE){
//画封闭线
glBegin(MODE);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

那么我们实现了上面的方法之后。比如我现在要画一个矩形,就要传入 GL_LINE_LOOP 绘制一个边缘,然后再传入 GL_POLYGON 绘制一个填充的矩形,其中这个矩形的长宽等于边缘的长宽减去线的宽度(经过探索发现,应该减去线宽的一半才对,因为线的基准点是在线宽的中心的) 所以,我们可以定义一个画带边矩形的方法,比如

1
2
3
4
5
6
7
//画填充的矩形,传入左上角和右下角的坐标
void glFillRect(int leftX,int leftY,int rightX,int rightY,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glRect(leftX,leftY,rightX,rightY,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glRect(leftX+lineWidth/2,leftY-lineWidth/2,rightX-lineWidth/2,rightY+lineWidth/2,GL_POLYGON);
}

我们先定义了边界颜色绘制了边界,然后定义了图形的颜色来绘制填充的图形。其中 color 作为参数,可以传入不同的 RGB 值。 定义颜色 RGB 数组即可

1
2
3
4
5
6
int border[3]={0,0,0};
int grey[3]={195,195,195};
int yellow[3]={255,243,0};
int red[3]={237,28,36};
int darkGrey[3]={126,126,126};
int white[3]={255,255,255};

调用的时候我们只需要

1
glFillRect(-108,45,-81,0,darkGrey);

就可以绘制一个黑色边界的矩形了,填充色为暗灰色。 其他类似的方法实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//画填充的三角形,传值为逆时针,此法针对于机器人的正三角形
void glFillTri(int x1,int y1,int x2,int y2,int x3,int y3,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glTri(x1,y1,x2,y2,x3,y3,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glTri(x1+lineWidth/2,y1+lineWidth/2,x2-lineWidth/2,y2+lineWidth/2,x3,y3-lineWidth/2,GL_POLYGON);
}

//画填充的圆角矩形
void glFillRoundRec(int centerX,int centerY,int width,int height,float cirR,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glRoundRec(centerX,centerY,width,height,cirR,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glRoundRec(centerX,centerY,width-lineWidth/2,height-lineWidth/2,cirR,GL_POLYGON);
}

//画填充的圆形
void glFillCircle(double x, double y, double radius,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glCircle(x,y,radius,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glCircle(x,y,radius-lineWidth/2,GL_POLYGON);
}

恩,万事俱备只欠东风了,下面我们调用一下这几个函数直接进行绘制就好啦。 display 中的绘图调用如下

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
56
57
58
59
60
61
62
//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(lineWidth);

//设置画线颜色
glColor3ub(0,0,0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//画圆角矩形,大脸
glFillRoundRec(0,113,128,74,10,grey);
//两个眼睛
glFillCircle(-30,111,10,white);
glFillCircle(30,111,10,white);
//黑色线
glColor3ub(0,0,0);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45,GL_LINE_STRIP);
//画矩形,脖子
glFillRect(-25,76,25,60,grey);
//圆角矩形,两个耳朵
glFillRoundRec(81,115,20,34,5,darkGrey);
glFillRoundRec(-81,115,20,34,5,darkGrey);
//画圆角矩形,大肚子
glFillRoundRec(0,0,146,120,15,grey);
//画三角,肚子里的三角
glFillTri(-30,-15,30,-15,0,28,yellow);
//画圆,中间小圈
glFillCircle(0,0,10,red);
//画矩形,胳膊连接处
glFillRect(-81,43,-73,25,grey);
glFillRect(73,43,81,25,grey);
//画矩形,上臂
glFillRect(-108,45,-81,0,darkGrey);
glFillRect(81,45,108,0,darkGrey);
//画矩形,中臂
glFillRect(-101,0,-88,-4,grey);
glFillRect(88,0,101,-4,grey);
//画矩形,下臂
glFillRect(-108,-4,-81,-37,darkGrey);
glFillRect(81,-4,108,-37,darkGrey);
//画圆形,手掌
glFillCircle(-95,-47,10,grey);
glFillCircle(95,-47,10,grey);
//画腿连接处
glFillRect(-41,-62,-21,-66,grey);
glFillRect(21,-62,41,-66,grey);
//画圆角矩形,大长腿
glFillRoundRec(-32,-92,38,52,10,darkGrey);
glFillRoundRec(32,-92,38,52,10,darkGrey);
//画矩形,脚踝
glFillRect(-41,-117,-21,-125,grey);
glFillRect(21,-117,41,-125,grey);
//画矩形,大脚掌
glFillRect(-59,-125,-8,-137,darkGrey);
glFillRect(8,-125,59,-137,darkGrey);
glFlush();
}

恩,这样就大功告成啦,是不是比种子填充算法简单多了?

运行结果

QQ截图20150421000536 恩,那些小白点就忽略吧!!这叫不拘小节!!哈哈哈!!

总结

本节我们利用了类库的方法来实现了颜色填充,是不是很简单?

C/C++

综述

博主研究了一下午加一晚上,终于把种子填充算法实现出来并把机器人填充完毕,路途很艰辛,不过也学到了很多,在此和大家一起分享。

吐槽

与我不是同学的小伙伴,请自动忽略,我是来吐槽教材的。 在此不得不吐槽一下,不得不说教材实在太坑爹了。对于种子填充算法的后半部分,下一个种子点的寻找过程中,从 while(x<=xright)开始,我实在无法搞懂它里面的神逻辑,最初我认为它是对的,后来按照它的思路实现之后,填充基本上是错误的,比如圆角矩形下方的部分,它就无法正常填充。根本原因还是它的下一步种子点找错了,而博主依然在固执地 DeBug,看看是不是我哪里编码有问题。后来,干脆放弃了书上的逻辑了,自己改写了搜寻下一个种子点的算法,最后终于成功。 另外,教材上的这些伪代码写得也是太伪,算了,这不是重点,言归正传。

基本梳理

在博主的研究过程中,遇到了许许多多的小问题,在这里统一做一下总结,也希望大家少走弯路,吸取我的经验教训。

1.点的定义

在这里我们避免不了要使用点,一个点包括了 2 个元素,一个是横坐标一个是纵坐标,所以我们可以直接把它定义为一个结构体。

1
2
3
4
5
struct Point
{
int x;
int y;
};

这样的话,我们就可以直接声明一个 Point 类型的变量使用了,既方便又直观。

2.栈的使用

对于种子填充算法,肯定避免不了使用栈的,在这里博主分享一下一些使用心得。 栈的引入 C++代码中,可以直接用下面的代码来导入

1
2
#include <stack>
using namespace std;

注意,这里一定记得加上 using namespace std 这句话,否则会出现 stack 未定义的错误,哈哈哈,深有体会。 栈的定义 引入了栈之后,我们就可以直接来声明一个栈了

1
stack<Point> pixelStack;

其中,需要加一个尖括号,尖括号中声明了 Point 类型,这样我们就可以使用它了 取栈顶元素 C++中取栈顶元素是很坑的,有一个 top 方法,还有一个 pop 方法。 其中 top 方法是只取得栈顶的元素而不移除它,pop 方法是直接移除栈顶元素,没有返回值。 所以我们要想取出栈顶元素并移除的话,就要分别调用这两个方法

1
2
3
4
//获取最顶端的元素
Point tempPoint=pixelStack.top();
//删除最顶端的元素
pixelStack.pop();

是不是不友好?不友好的话,那就自己去定义一个新方法吧,我就先不这么干啦。 判断栈非空 判断当前的栈是否已经为空,只需要调用 empty 方法就可以了

1
2
3
while(!pixelStack.empty()){
//code
}

这里是一个 while 循环,如果栈为非空的话不断循环。 关于栈置空 教材中的种子填充算法中用到了栈置空,不过我感觉没有必要这么做,因为在方法最前面是新声明的栈变量,它一定是空的。不过如果非要置空的话,可以利用下面的代码

1
2
3
while(!pixelStack.empty()){
pixelStack.pop();
}

如果栈不为空,就一直取元素,就可以把它置空啦。

3.关于 glColor3b 和 glColor3ub

这的确也是坑得博主不浅,之前一直在用 glColor3b 这个方法来定义颜色,奇怪的是 glColor3b(255,0,0) 竟然不是红色,而是黑色!就是因为这个颜色问题,导致我在比对颜色的过程中走了很多弯路。在这里做一下说明 glColor3b()需要传入的是 byte 类型,它的数值范围是-128-127,也就是有符号数,我传入 255,由于越界了,255 这个数就相当于-128,难怪不变红啊。 glColor3ub()需要传入的是 unsigned byte 类型,范围是 0-255,无符号数,那么在这里我们传入 255,0,0 这三个数,就变成红色了。

4.取得某像素颜色

我想说的是,这也是个深坑啊,一下午的 Debug 全归它身上了。 获取某个像素的这个函数是

1
void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,GLenum format,GLenum type,GLvoid *pixel);

函数说明如下:

该函数总共有七个参数。前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度) 第五个参数表示读取的内容,例如:GL_RGB 就会依次读取像素的红、绿、蓝三种数据,GL_RGBA 则会依次读取像素的红、绿、蓝、alpha 四种数据,GL_RED 则只读取像素的红色数据(类似的还有 GL_GREEN,GL_BLUE,以及 GL_ALPHA)。如果采用的不是 RGBA 颜色模式,而是采用颜色索引模式,则也可以使用 GL_COLOR_INDEX 来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的深度数据等。 第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE 会把各种数据保存为 GLubyte,GL_FLOAT 会把各种数据保存为 GLfloat 等。 第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如一幅大小为 256*256 的图象,如果读取其 RGB 数据,且每一数据被保存为 GLubyte。

好了,那么重点来了,这个方法的坐标基准点是在画布的左下角!!而我们绘图的基准点是在画布的正中心!!所以我在获取某个点的颜色的时候一直都是错误的结果,这样的话在使用的时候我们的 xy 坐标值就要加上画布宽高的一半才能正常获取到像素的颜色,希望大家一定注意!! 那么我们如何来使用呢?实例如下,首先定义 GLByte 的数组

1
GLubyte iPixel[3];

另外还有画布的宽度高度的一半变量

1
int halfWidth,halfHeight;

我们可以调用如下的方法来获取(x,y)这个点像素的值

1
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);

在这里第五个参数我们定义了 GL_RGB,第六个参数我们定义了 GL_UNSIGNED_BYTE,最后是传入了数组的引用。 所以在调用这个方法之后,iPixel 数组里面的三个值就已经赋值为了该点的 RGB 值,可以拿来做下一步的判断。 比如我们边界可以定义为

1
GLubyte oldColor[3]={255,255,255};

在比较的时候就可以用下面的判别式

1
iPixel[0]!=borderColor[0]&&iPixel[1]!=borderColor[1]&&iPixel[2]!=borderColor[2]

这里我们是依次比较了三个 RGB 值是否与边界的 RGB 值相等,不过,有意思的是,识别颜色的这个方法,黑色的 RGB 值会识别成 1,1,1,而有时候在我调试的时候会识别为 0,1,1。我在想是不是系统计算误差问题,如果真是的话,因为这个小小的误差就影响了我们的判别条件岂不是亏大了?那么在这里我就定义了一个方法,允许一定的误差,这个误差姑且就称为 PS 里面的容差吧。

1
2
3
4
5
6
7
8
9
10
//传入两个颜色的RGB值,比较是否相同,容差为dis
bool sameColor(int r1,int g1,int b1,int r2,int g2,int b2){
//容差度
int dis = 10;
if(abs(r1-r2)<=dis&&abs(g1-g2)<=dis&&abs(b1-b2)<=dis){
return true;
}else{
return false;
}
}

那么我们的判定条件就改为了

1
!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])

这样系统误差便不会影响了。

5.下一个种子点的选取

教材上的种子点选取算法有点搞不懂,我按照上面的思路实现出来,在填充的时候出现了一系列问题。后来干脆放弃了教材中的方法,自己改写了一下。 思路大体上是这样的。

在填充完一行后,这一行最左边的像素点我们定义为(xLeft,y),最右边的像素我们定义为(xRight,y),扫描上一行找寻下一个种子点,这里 y 就要增加 1,如果(xRight,y+1)这个点不是边界不是已经填充的点,那么这个点就可以作为种子点压入堆栈。如果这个点是边界或者是已经填充的点,那么就继续往左搜索,如果找到既不是边界又未填充的点,那么这个点就是种子点,压入堆栈。如果一直往左找到 xLeft 还是没有找到的话,就不存在下一个种子点了。下一行扫描线也是同样的原理,y 要在这个基础上减去 2 即可。

恩,不知道大家有没有看懂,这是我自己想出来的方法,不敢保证完全正确,在此仅供参考。 如果大家真的可以按照教材中的方法实现成功的话,希望告诉我一下,感激不尽。

方法实现

恩,重要的地方都已经点明了,下面就直接附上我的种子填充算法吧!

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//种子填充算法
void zzFill(int startX,int startY,int r,int g,int b){
stack<Point> pixelStack;
//x,y是给定的种子像素点,rgb就是要填充的颜色的RGB值
Point point = {startX,startY};
pixelStack.push(point);
int saveX;
int xRight,xLeft;
int x,y;
//如果栈不为空
while(!pixelStack.empty()){
//获取最顶端的元素
Point tempPoint=pixelStack.top();
//删除最顶端的元素
pixelStack.pop();
saveX=tempPoint.x;
x=tempPoint.x;
y=tempPoint.y;
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
//如果没有到达右边界,就填充
while(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])){
glPoint(x,y,r,g,b);
x=x+1;
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
xRight=x-1;
x=saveX-1;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
//如果没有到达左边界,就填充
while(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])){
glPoint(x,y,r,g,b);
x=x-1;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
//保存左端点
xLeft=x+1;
//从右边的点开始
x=xRight;
//检查上端的扫描线
y=y+1;
while(x>=xLeft){
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
if(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])&&!sameColor(iPixel[0],iPixel[1],iPixel[2],r,g,b)){
//如果上方的点不是边界点,直接压入
Point p={x,y};
pixelStack.push(p);
//压入之后停止循环
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
//检查下端的扫描线
y=y-2;
//从右边的点开始
x=xRight;
while(x>=xLeft){
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
if(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])&&!sameColor(iPixel[0],iPixel[1],iPixel[2],r,g,b)){
//如果上方的点不是边界点,直接压入
Point p={x,y};
//压入之后停止循环
pixelStack.push(p);
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
}
}

以上便是我实现的种子填充算法,仅供参考 在这里我们用到了 glPoint 画点的方法,这是我们定义的,方法如下,为了便于调试,每画一个点刷新一下,这样我们就可以看到绘制的全部动态效果。

1
2
3
4
5
6
7
8
9
//画点
void glPoint(int x,int y,int r,int g,int b){
glColor3ub (r,g,b);
glPointSize(1);
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();
glFlush();
}

以上便是画点的函数

方法使用

种子填充算法肯定要在我们绘制完机器人之后使用,任意选取某个四连通区域的点,传入 xy 坐标值还有要填充的颜色的 RGB 值,就可以成功实现填充。 在上一篇机器人的基础上,我们在画机器人的方法最后加入下面的代码,即可实现填充。

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
//灰色:195,195,195
//黄色:255,243,0
//红色:237,28,36
//深灰色:126,126,126
//脖子
zzFill(0,70,195,195,195);
//头
zzFill(-50,110,195,195,195);
zzFill(0,93,195,195,195);
//肚子
zzFill(-50,0,195,195,195);
//耳朵
zzFill(-80,115,126,126,126);
zzFill(80,115,126,126,126);
//肚子三角
zzFill(-20,-10,255,243,0);
//肚子红色圆
zzFill(0,0,237,28,36);
//zzFill(-50,0,128,255,33);
//大臂
zzFill(-90,30,126,126,126);
zzFill(90,30,126,126,126);
//小臂
zzFill(-90,-20,126,126,126);
zzFill(90,-20,126,126,126);
//手
zzFill(-75,40,195,195,195);
zzFill(75,40,195,195,195);
//手
zzFill(-95,-47,195,195,195);
zzFill(95,-47,195,195,195);
//大腿连接处
zzFill(-40,-64,195,195,195);
zzFill(40,-64,195,195,195);
//大腿
zzFill(-40,-100,126,126,126);
zzFill(40,-100,126,126,126);
//脚踝
zzFill(-40,-121,195,195,195);
zzFill(40,-121,195,195,195);
//脚掌
zzFill(-40,-130,126,126,126);
zzFill(40,-130,126,126,126);
system("pause");

注意,有个很奇怪地方是绘制完了之后机器人就不见了,所以在这里加入了 system(“pause”)方法来暂停一下就好啦。 其他的代码基本都是上一篇中的了,大家自行整理。

运行结果

运行结果截图如下 20150419015002 恩,就是这样!

总结

以上便是博主利用种子填充算法来实现的机器人的颜色填充,在此分享给大家,希望对大家有帮助! 如有问题和错误,欢迎大家给予我批评和指正,谢谢!

福利专区

综述

小伙伴们总要有一些秘密是不能让别人知道的,之前我们使用的设置隐藏文件夹然后在控制面板设置不显示隐藏文件夹的方式都弱爆了,下面我们来用一种更高级的办法来设置隐藏文件夹,感受一下。

设置隐藏

首先我们创建一个文件夹,比如名字叫 SECRET,如图所示 20150415114405 接下来我们打开命令行,输入如下命令

1
attrib +s +h e:/SECRET

20150415114811 输入命令之后,我们再查看一下 E 盘的内容,刷新一下,记得刷新!20150415120601 恩,那个文件夹已经不见了,有人说设置一下控制面板就显示出来了。 好,我们试试,控制面板设置显示隐藏的文件夹 20150415120426 恩,我们再看看,它依然是找不到的,就是这样! 20150415120601 那么我们怎么找到它?很简单,访问的时候只要在输入文件夹名称就会找到它了。 比如,我在地址栏中输入 E:/SECRET,就可以找到它了 20150415115101 下面我们来介绍一下这个命令的用法

attrib 命令用来显示或更改文件属性。

ATTRIB [+R | -R] [+A | -A ] [+S | -S] [+H | -H] [[drive:] [path] filename] [/S [/D]] + 设置属性。 - 清除属性。 R 只读文件属性。 A 存档文件属性。 S 系统文件属性。 H 隐藏文件属性。 [drive:][path][filename] 指定要处理的文件属性。 /S 处理当前文件夹及其子文件夹中的匹配文件。 /D 也处理文件夹。

比如

1
attrib +a +s +r +h e:/SECRET

这句命令就是设置 E 盘的 SECRET 文件夹为存档文件、系统文件、只读文件、隐藏文件。

在这里我们只要设置为隐藏文件盒系统文件属性就可以完成文件夹的隐藏,是不是很酷炫?

取消隐藏

了解了上面的命令,取消隐藏就很简单啦,我们只需要输入下面的命令

1
attrib -h -s e:/SECRET

再刷新一下目录,SECRET 目录就又出现了。

20150415115831

恩,会了吧,小伙伴们还不赶紧试试。不用谢我,请叫我雷锋!

C/C++

综述

在上一篇文章我们介绍了利用类库来完成一个机器人绘制的过程,这里我们一起来看一下怎样直接利用直线和圆弧生成算法来进行图形的绘制。 P.S. 本篇文章针对《计算机图形学》张彩明 版来探讨学习。关于书中的详细算法不会再赘述。 P.P.S. 本篇文章算法扩展思路及代码实现为博主原创内容,如存在纰漏和错误,希望大家指正。

直线生成算法

1.DDA 算法

DDA 算法是最基本的一种直线生成算法了,代码实现简单,不过缺点是计算量比较大,画一个点要两次加法,两次取整运算。另外,DDA 算法还包括了除法运算。不仅算法复杂,而且硬件实现上有一定的难度。优点就是程序简单易懂,在这里实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//画直线的DDA算法
void dda(int x1,int y1,int x2,int y2){
int k,i;
float x,y,dx,dy;
//使k等于横纵坐标差值的较大者
k = abs(x2-x1);
if(abs(y2-y1)>k)
k = abs(y2-y1);
//直线被划分为每一小段的长度
dx = float(x2-x1)/k;
dy = float(y2-y1)/k;
x = float(x1);
y = float(y1);
for(i = 0;i<k;i++){
//此处先加0.5再取整的作用是四舍五入
gl_Point((int)(x+0.5),(int)(y+0.5));
//x和y分别增加相应的单位
x = x+dx;
y = y+dy;
}
}

解释一下这里的 gl_Point 方法,这个方法并不是直接调用类库的方法,而是我们自己来实现的画点的方法。

1
2
3
4
5
6
//画点
void gl_Point(int x,int y){
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();
}

用来画点的话,我们必须要在 glBegin 方法传入 GL_POINTS 参数,然后利用类库中画点的方法来绘制点。

2.正负法

在教材中只讨论了斜率在 0-1 之间的情况,代码的实现也是仅仅只有 0-1 一种情况,对于斜率大于 1,斜率在-1 和 0 之间以及斜率小于-1 的情况没有加以讨论。 如果我们直接拿来教材中的代码来用,我们会发现只能绘制出 0-1 斜率的直线,对于其他的情况,均绘制错误。 所以我们需要分四种情况来讨论,直线方程 F(x,y)=ax+by+c=0,其中 a=ys-ye,b=xe-xs 设直线的斜率为 k,讨论分类如下 (1)k∈[0,1) 此时有 a<0,b>0 d=F(M)=F(x+1,y+0.5)=a(x+1)+b(y+0.5)+c 当 d>=0 时,Q 在 M 点下方,取右下方的点,d1=F(x+2,y+0.5)=a(x+2)+b(y+0.5)+c=d+a 当 d<0 时,Q 在 M 点上方,取右上方的点,d2=F(x+2,y+1.5)=a(x+2)+b(y+1.5)+c=d+a+b 此时 d 的初始值 d0=F(xs+1,ys+0.5)=a+0.5b **(2)k∈[1,+∞]** 此时有 a<0,b>0 d=F(M)=F(x+0.5,y+1)=a(x+0.5)+b(y+1)+c 当 d>=0 时,Q 在 M 点右侧,取右上方的点,d1=F(x+1.5,y+2)=a(x+1.5)+b(y+2)+c=a+b+d 当 d<0 时,Q 在 M 点左侧,取左上方的点,d2=F(x+0.5,y+2)=b+d 此时 d 的初始值 d0=F(xs+0.5,ys+1)=0.5a+b **(3)k∈[-1,0)** 此时有 a>0,b>0 d=F(M)=F(x+1,y-0.5)=a(x+1)+b(y-0.5)+c 当 d>=0 时,Q 在 M 点下方,取右下方的点,d1=F(x+2,y-1.5)=a(x+2)+b(y-1.5)+c=a-b+d 当 d<0 时,Q 在 M 点上方,取右上方的点,d2=F(x+2,y-0.5)=a(x+2)+b(y-0.5)+c=a+d 此时 d 的初始值 d0=F(xs+1,ys-0.5)=a-0.5b **(4)k∈[-∞,-1)** 此时有 a>0,b>0 d=F(M)=F(x+0.5,y-1)=a(x+0.5)+b(y-1)+c 当 d>=0 时,Q 在 M 点左方,取左下方的点,d1=F(x+0.5,y-2)=a(x+0.5)+b(y-2)+c=d-b 当 d<0 时,Q 在 M 点右方,取右下方的点,d2=F(x+1.5,y-2)=a(x+1.5)+b(y-2)+c=a-b+d 此时 d 的初始值 d0=F(xs+0.5,ys-1)=0.5a-b 注意:上面的 a 和 b 的符号,是在默认起点在终点的左侧来看待的 所以,如果我们传入参数时,第二个点在第一个点的左侧时,我们可能就不会得到正确的结果。所以当我们发现第二个点不在第一个点右侧时,就需要把二者的横纵坐标交换。 代码实现如下,此实现仅供参考,未经优化。

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
56
57
58
59
60
61
62
63
64
65
66
67
//画直线的正负法
void midPointLine(int xs,int ys,int xe,int ye){
if(xs>xe){
swap(xs,xe);
swap(ys,ye);
}
float a,b,dt1,dt2,d,x,y;
a=ys-ye;
b=xe-xs;
float k =(float)(ye-ys)/(xe-xs);
if(k>=0&&k<1){
d=2*a+b;
dt1=2*a;
dt2=2*(a+b);
}else if(k>=1){
d=a+2*b;
dt1=2*(a+b);
dt2=2*b;
}else if(k<0&&k>=-1){
d=2*a-b;
dt1=2*(a-b);
dt2=2*a;
}else if(k<-1){
d=a-2*b;
dt1=-2*b;
dt2=2*(a-b);
}
x=xs;y=ys;
gl_Point(x,y);
if(k>=0&&k<1){
while(x<xe){
if(d<0){
x++;y++;d+=dt2;
}else{
x++;d+=dt1;
}
gl_Point(x,y);
}
}else if(k>=1){
while(y<ye){
if(d<0){
y++;d+=dt2;
}else{
y++;x++;d+=dt1;
}
gl_Point(x,y);
}
}else if(k<0&&k>=-1){
while(x<xe){
if(d<0){
x++;d+=dt2;
}else{
x++;y--;d+=dt1;
}
gl_Point(x,y);
}
}else if(k<-1){
while(y>ye){
if(d<0){
y--;x++;d+=dt2;
}else{
y--;d+=dt1;
}
gl_Point(x,y);
}
}
}

在一开始我们用到了 swap 方法,是用来交换两个数字的,实现如下

1
2
3
4
5
6
//交换两个数字
void swap(int &x1,int &x2){
int temp = x2;
x2=x1;
x1=temp;
}

以上便是正负法的实现,代码仅供参考。

3.Bresenham 算法

在教材中,同样是只针对斜率在 0-1 之间讨论。对于教材中的程序,我们也只能绘制斜率为 0-1 的直线,所以我们需要对另外三种情况进行扩充。 分类讨论如下 (1)k∈[0,1) 即教材中的讲解方法 (2)k∈[1,+∞] 需要把 x 和 y 互换即可 (3)k∈[-1,0) x 不变,y 换为-y (4)k∈[-∞,-1) x 换为-y,y 换为 x 程序实现如下

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
56
57
//画直线的Bresenham方法
void bresen(int x1,int y1,int x2,int y2){
if(x2<x1){
swap(x1,x2);
swap(y1,y2);
}
float dx=x2-x1;
float dy=y2-y1;
float k=dy/dx;
float m,e;int i;
float x=x1,y=y1;
if(k>=0&&k<1){
//斜率为0到1
m=dy/dx;
e=m-0.5;
for(i=0;i<dx;i++){
gl_Point(x,y);
if(e>=0){
y+=1;e-=1;
}
x+=1;e+=m;
}
}else if(k>=1){
//x换为y,y换为x
m=dx/dy;
e=m-0.5;
for(i=0;i<dy;i++){
gl_Point(x,y);
if(e>=0){
x+=1;e-=1;
}
y+=1;e+=m;
}
}else if(k<0&&k>=-1){
//x不变,y换为-y
m=-dy/dx;
e=m+0.5;
for(i=0;i<dx;i++){
gl_Point(x,y);
if(e<=0){
y-=1;e+=1;
}
x+=1;e-=m;
}
}else{
//将x换为-y,y换为x
m=-dx/dy;
e=m+0.5;
for(i=0;i<-dy;i++){
gl_Point(x,y);
if(e<=0){
x+=1;e+=1;
}
y-=1;e-=m;
}
}
}

以上便是 Bresenham 算法,经测试通过。

圆弧生成算法

1.正负法

教材中只讨论了圆弧在第一象限的情况,不过有趣的是,圆是具有对称性的,在绘制圆形时,我们如果把 x 换为-x,就可以绘制第二象限的图形,把 y 换为-y,就可以绘制第四象限的图形,代码也不需要改动很多。只需要在 gl_Point 上面下功夫即可。 另外,教材中圆弧生成算法中没有指定圆的中心点的坐标,我们可以把它当做参数来传递进来,然后传入 gl_Point 绘图函数即可,相当方便。 注:此方法不需要再繁琐地分类讨论。 在这里给出博主写出的两种方法,一种是如上所介绍的思路,利用对称性,另一种是分类讨论的思想。 (1)利用对称性实现

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
//正负法画圆
void pnarcArc(int radius,int centerX,int centerY,int area){
int x,y,f;
x=0;y=0+radius;f=0;
while(y>0){
switch(area){
case 1:
gl_Point(x+centerX,y+centerY);
break;
case 2:
gl_Point(-x+centerX,y+centerY);
break;
case 3:
gl_Point(-x+centerX,-y+centerY);
break;
case 4:
gl_Point(x+centerX,-y+centerY);
break;
}
if(f>0){
f=f-2*y+1;
y=y-1;
}else{
f=f+2*x+1;
x=x+1;
}
}
if(y==centerY){
gl_Point(x,y);
}
}

(2)分类讨论思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//正负法画圆3
void pnarcArc(int radius,int centerX,int centerY,int area){
int x,y,f;
int tag[4]={1,1,-1,-1};
int tagX[4]={1,-1,-1,1};
int tagY[4]={-1,-1,1,1};
x=0;y=tag[area-1]*radius;f=0;
while(tag[area-1]*y>0){
gl_Point(x+centerX,y+centerY);
if(f>0){
f=f+tagY[area-1]*2*y+1;
y=y+tagY[area-1];
}else{
f=f+tagX[area-1]*2*x+1;
x=x+tagX[area-1];
}
}
if(y==centerY){
gl_Point(x,y);
}
}

在上面的代码中,我们传入了 centerX 和 centerY 以及 area 参数。其中 centerX 和 centerY 是圆弧中心点的坐标,area 是所在的象限,传入的参数需要是 1,2,3,4 中的一个数字,如果传入其他数字则不会绘制出任何图形。 注:此方法只能一次性绘制一个四分之一圆弧,局限性比较大,如果要融入弧度,改动量比较大。

2.Bresenham 算法

和上面方法类似,我们的实现同样非常简单,即使教材中只讨论了八分之一圆,我们可以利用对称的思想来实现画圆。另外我们添加了圆弧中心点坐标已经所在的区块。 从(π/4,π/2)这个区块开始,编号为 1,角度为 45°,顺时针旋转(0,π/4)的编号为 2,以此类推,参数变量为 area 代码实现如下

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
//Bresenham画圆算法
void bresenhamArc(int R,int centerX,int centerY,int area){
int x,y,d;
x=0;y=R;d=3-2*R;
while(x<y){
switch(area){
case 1:
gl_Point(x+centerX,y+centerY);
break;
case 2:
gl_Point(y+centerX,x+centerY);
break;
case 3:
gl_Point(y+centerX,-x+centerY);
break;
case 4:
gl_Point(x+centerX,-y+centerY);
break;
case 5:
gl_Point(-x+centerX,-y+centerY);
break;
case 6:
gl_Point(-y+centerX,-x+centerY);
break;
case 7:
gl_Point(-y+centerX,x+centerY);
break;
case 8:
gl_Point(-x+centerX,y+centerY);
break;
}
if(d<0){
d=d+4*x+6;
}else{
d=d+4*(x-y)+10;
y=y-1;
}
x=x+1;
}
if(x==y){
gl_Point(x,y);
}
}

此段代码亲测可用,仅供参考。

终极目标

上一节我们实现了用类库的方法和 sin,cos 方法来定位坐标绘制机器人,在这一节我们就利用上述的直线和圆弧生成算法,对上一篇中的机器人进行绘制。 在这里只贴出最核心的部分,那就是绘画的函数了,只是简单地传入坐标点然后调用刚才实现的一些方法,比较繁琐,但是比较简单,核心代码如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//myDisplay函数用来画图
void myDisplay(void){
//清除。GL_COLOR_BUFFER_BIT表示清除颜色,glClear函数还可以清除其它的东西
glClear(GL_COLOR_BUFFER_BIT);
//设置黑色颜色
glColor3f (0.0f, 0.0f, 0.0f);
//中心的圆圈,四个象限,半径为10
pnarcArc(10,0,0,1);
pnarcArc(10,0,0,2);
pnarcArc(10,0,0,3);
pnarcArc(10,0,0,4);
//肚子中三角形,利用了DDA绘制
dda(-30,-15,30,-15);
dda(-30,-15,0,28);
dda(0,28,30,-15);
//肚子,四条直线
midPointLine(-60,60,60,60);
midPointLine(-60,-60,60,-60);
midPointLine(-74,46,-74,-46);
midPointLine(74,46,74,-46);
//肚子,四个半圆
pnarcArc(14,60,46,1);
pnarcArc(14,-60,46,2);
pnarcArc(14,-60,-46,3);
pnarcArc(14,60,-46,4);
//脖子,两条直线
bresen(-25,76,-25,60);
bresen(26,76,25,60);
//脸,四条直线
midPointLine(-54,150,54,150);
midPointLine(-54,76,54,76);
midPointLine(-64,140,-64,86);
midPointLine(64,140,64,86);
//脸,四个圆弧
pnarcArc(10,54,140,1);
pnarcArc(10,-54,140,2);
pnarcArc(10,-54,86,3);
pnarcArc(10,54,86,4);
//眼睛,两个正圆
pnarcArc(10,-30,111,1);
pnarcArc(10,-30,111,2);
pnarcArc(10,-30,111,3);
pnarcArc(10,-30,111,4);
pnarcArc(10,30,111,1);
pnarcArc(10,30,111,2);
pnarcArc(10,30,111,3);
pnarcArc(10,30,111,4);
//嘴巴,两个八分之一圆
bresenhamArc(20,0,111,4);
bresenhamArc(20,0,111,5);
//天线
dda(-35,150,-35,173);
dda(35,150,35,173);
//右耳朵,四条直线
bresen(88,98,88,131);
bresen(67,98,67,131);
bresen(70,95,85,95);
bresen(70,134,85,134);
//右耳朵,四个圆弧
pnarcArc(3,85,131,1);
pnarcArc(3,70,131,2);
pnarcArc(3,70,98,3);
pnarcArc(3,85,98,4);
//左耳朵,四条直线
bresen(-88,98,-88,131);
bresen(-67,98,-67,131);
bresen(-70,95,-85,95);
bresen(-70,134,-85,134);
//左耳朵,四个圆弧
pnarcArc(3,-70,131,1);
pnarcArc(3,-85,131,2);
pnarcArc(3,-85,98,3);
pnarcArc(3,-70,98,4);
//左胳膊衔接处
bresen(-73,25,-80,25);
bresen(-73,43,-80,43);
//右胳膊衔接处
bresen(73,25,80,25);
bresen(73,43,80,43);
//左大臂
dda(-108,45,-108,0);
dda(-81,45,-81,0);
dda(-108,45,-81,45);
dda(-108,0,-81,0);
//右大臂
dda(108,45,108,0);
dda(81,45,81,0);
dda(108,45,81,45);
dda(108,0,81,0);
//左中臂
bresen(-101,0,-101,-4);
bresen(-88,0,-88,-4);
//右中臂
bresen(101,0,101,-4);
bresen(88,0,88,-4);
//左小臂
dda(-108,-4,-108,-37);
dda(-81,-4,-81,-37);
dda(-108,-4,-81,-4);
dda(-108,-37,-81,-37);
//右小臂
dda(108,-4,108,-37);
dda(81,-4,81,-37);
dda(108,-4,81,-4);
dda(108,-37,81,-37);
//左手
pnarcArc(10,-95,-47,1);
pnarcArc(10,-95,-47,2);
pnarcArc(10,-95,-47,3);
pnarcArc(10,-95,-47,4);
//右手
pnarcArc(10,95,-47,1);
pnarcArc(10,95,-47,2);
pnarcArc(10,95,-47,3);
pnarcArc(10,95,-47,4);
//左腿衔接处
dda(-43,-62,-43,-69);
dda(-25,-62,-25,-69);
//右腿衔接处
dda(43,-62,43,-69);
dda(25,-62,25,-69);
//左大腿,四条直线
bresen(-47,-69,-21,-69);
bresen(-47,-117,-21,-117);
bresen(-51,-70,-51,-113);
bresen(-17,-70,-17,-113);
//左大腿,四条圆弧
pnarcArc(4,-21,-73,1);
pnarcArc(4,-47,-73,2);
pnarcArc(4,-47,-113,3);
pnarcArc(4,-21,-113,4);
//右大腿,四条直线
bresen(47,-69,21,-69);
bresen(47,-117,21,-117);
bresen(51,-70,51,-113);
bresen(17,-70,17,-113);
//右大腿,四条圆弧
pnarcArc(4,47,-73,1);
pnarcArc(4,21,-73,2);
pnarcArc(4,21,-113,3);
pnarcArc(4,47,-113,4);
//左脚踝
dda(-43,-118,-43,-125);
dda(-25,-118,-25,-125);
//右腿衔接处
dda(43,-118,43,-125);
dda(25,-118,25,-125);
//左脚
bresen(-59,-125,-8,-125);
bresen(-59,-137,-8,-137);
bresen(-59,-125,-59,-137);
bresen(-8,-125,-8,-137);
//右脚
bresen(59,-125,8,-125);
bresen(59,-137,8,-137);
bresen(59,-125,59,-137);
bresen(8,-125,8,-137);
//刷新机器人
glFlush();
}

其他的部分不再赘述,都十分基础。

运行结果

直接贴图如下 QQ截图20150413005949 嘿嘿,我们直接用生成算法绘制的图形是不是更好看一些呢?

总结

本节介绍了各种直线生成算法和圆弧生成算法,以及利用该算法重新绘制机器人,希望对大家有帮助!

C/C++

综述

计算机图形学教材中有多种绘图方法,如直线的 DDA 算法、正负法、Bresenham 算法和画圆弧的正负法和 Bresenham 算法。 同样,OpenGL 类库也为我们提供了多种绘图方法,比如 glVertex2d,在这里我们用类库的方法来实现一个机器人的绘制。DDA 等算法实现之后我们再替换类库的 glVertex2d 方法。

绘制要求

利用 glVertex2d 和 glVertex2f 在二维平面上绘制如下的机器人。所以我们现在不需要三维的绘图方法,仅在平面绘制即可。 20150410115231

问题分析

经过观察我们发现,图中包含了圆角矩形,矩形,直线,圆形,弧形,三角形,而对于 glVertex2d 的方法,只是定位好坐标点,然后利用坐标点连线或者形成封闭图形来绘制。所以,对于圆角矩形,弧形等,我们可以利用三角函数来求取坐标,并连线即可。

1.圆角矩形的解决方案

对于圆角矩形,顾名思义每个角是由一个四分之一圆弧组成的。我们定义这个圆弧的半径为 cirR,整个圆角矩形的宽度为 width,高度为 height,那么抛出四个角的圆弧,就会在圆角矩形内部形成一个小的矩形,我们定义它的宽高分别为 w,h。另外,圆角矩形的中心点为(centerX,centerY),如下图所示 (博主原创图,盗图必究) 在 OpenGL 中,可以定义一个绘图模式

1
glBegin(GL_LINE_LOOP);

可以绘制封闭曲线,也就是说,我们所有调用的 glVertex2f 函数绘制的点均可以自动连接成一个封闭曲线。 所以,我们要做的就是找出圆角的坐标,利用 C++ 中的三角函数来计算右上角小圆所在的路径,我们设角度为 X,所以圆弧的 x 方向延伸长度为 cirRcosX,在 y 方向延伸长度为 cirRsinX 对于右上角的四分之一圆弧,它所在的路径 x 坐标便是 centerX+w/2+cirRcosX = centerX+width/2-cirR+cirRcosX,同理,y 路径坐标便是 centerY+h/2+cirRsinX = centerY+height/2-cirR+cirRsinX 当然对于其他的角,是加 w/2 还是减 w/2 就要看它所在的象限了。 另外对于精确度的问题,我们可以定义一个 divide 变量,将四分之一圆弧分成若干个点来绘制,当然分的份数越多,越精细,分的份数的值就是 divide,所以每次增加的弧度便是 PI/(2*divide) 方法最终实现如下,我们传入矩形的宽高,矩形中心的坐标,圆角半径即可进行绘制。

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
//画圆角矩形,传入矩形中心点坐标,矩形宽高,角半径
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

例如我们可以调用

1
glRoundRec(0,0,146,120,15);

可以绘制如下的圆角矩形

2.圆弧的解决方案

对于圆弧的画法,我们也可以利用三角函数来解决,所以我们需要知道的参数就有半径,起始角度,还有圆弧的中心点。 代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}

至于画圆,我们只需要画一个弧度为 0 至 2PI 的圆弧即可。

1
2
3
4
5
6
//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

3.其他图形的解决方案

对于直线,三角形,矩形等等,就没有那么复杂了,只需要绘制几个端点即可完成绘制。在此不再赘述。

4.坐标的解决方案

要画图,最重要的便是坐标点,在此博主对图上的某些坐标进行了测量,假设总宽度为 300,对应的坐标值标注如下图所示,当然比例不一样的话,坐标会有成比例的变化,在此仅作参考。 robot 在图中,某些点的坐标已做好标注,仅供参考。

完整程序

完整的画机器人的程序实现如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <glut.h>
#include <math.h>
#define PI 3.1415926

//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY){
//画封闭曲线
glBegin(GL_LINE_LOOP);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
//设置画线颜色
glColor3f(0.5,0.5,0.5);
//画点大小
glPointSize(2);
//画圆角矩形,大肚子
glRoundRec(0,0,146,120,15);
//画圆,中间小圈
glCircle(0,0,10);
//画矩形,脖子
glRect(-25,60,25,76);
//画圆角矩形,大脸
glRoundRec(0,113,128,74,10);
//两个眼睛
glCircle(-30,111,10);
glCircle(30,111,10);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆角矩形,两个耳朵
glRoundRec(81,115,20,34,5);
glRoundRec(-81,115,20,34,5);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45);
//画三角,肚子里的三角
glTri(-30,-15,30,-15,0,28);
//画矩形,胳膊连接处
glRect(-81,43,-73,25);
glRect(81,43,73,25);
//画矩形,上臂
glRect(-108,45,-81,0);
glRect(108,45,81,0);
//画矩形,中臂
glRect(-101,0,-88,-4);
glRect(101,0,88,-4);
//画矩形,下臂
glRect(-108,-4,-81,-37);
glRect(108,-4,81,-37);
//画圆形,手掌
glCircle(-95,-47,10);
glCircle(95,-47,10);
//画腿连接处
glRect(-41,-62,-21,-66);
glRect(41,-62,21,-66);
//画圆角矩形,大长腿
glRoundRec(-32,-92,38,52,10);
glRoundRec(32,-92,38,52,10);
//画矩形,脚踝
glRect(-41,-125,-21,-117);
glRect(41,-125,21,-117);
//画矩形,大脚掌
glRect(-59,-125,-8,-137);
glRect(59,-125,8,-137);

//保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待
glFlush();
}


//窗口大小变化时调用的函数
void ChangeSize(GLsizei w,GLsizei h)
{
//避免高度为0
if(h==0) {
h=1;
}
//定义视口大小,宽高一致
glViewport(0,0,w,h);
int half = 200;
//重置坐标系统,使投影变换复位
glMatrixMode(GL_PROJECTION);
//将当前的用户坐标系的原点移到了屏幕中心
glLoadIdentity();
//定义正交视域体
if(w<h) {
//如果高度大于宽度,则将高度视角扩大,图形显示居中
glOrtho(-half,half,-half*h/w,half*h/w,-half,half);
} else {
//如果宽度大于高度,则将宽度视角扩大,图形显示居中
glOrtho(-half*w/h,half*w/h,-half,half,-half,half);
}

}

//程序入口
int main(int argc, char *argv[]){
//对GLUT进行初始化,并处理所有的命令行参数
glutInit(&argc, argv);
//指定RGB颜色模式和单缓冲窗口
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
//定义窗口的位置
glutInitWindowPosition(100, 100);
//定义窗口的大小
glutInitWindowSize(400, 400);
//创建窗口,同时为之命名
glutCreateWindow("OpenGL");
//设置窗口清除颜色为白色
glClearColor(1.0f,1.0f,1.0f,1.0f);
//参数为一个函数,绘图时这个函数就会被调用
glutDisplayFunc(&display);
//参数为一个函数,当窗口大小改变时会被调用
glutReshapeFunc(ChangeSize);
//该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止
glutMainLoop();
//程序返回
return 0;
}

运行效果如下图所示 20150410152854

综述

当然,这里直接调用了类库中的画线方法,而且用的是三角函数定位坐标。对于 DDA 算法等实现还没有进行替换,等实现 DDA 算法之后,再将绘制直线,圆弧等等的算法替换掉。同样可以实现机器人的绘制。 希望对大家有帮助!

JavaScript

综述

我们都见过淘宝上的宝贝,把鼠标放上去,会有局部放大的功能,现在我们可以利用一个叫jQZoom的插件,来实现图片的局部放大,让我们来感受一下

在线演示

我们首先来在线演示一下效果,然后我们说一下是怎样的实现,点开链接进行预览吧 在线预览

插件文件

  • jquery.min.js
  • jquery.jqzoom.js
  • jquery.jqzoom.css

其中包含了两个JS文件,一个是jQuery库,另一个就是jqzoom插件文件。另外还有一个css文件,主要作用是给图片放缩规定样式

HTML

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
<!DOCTYPE html>
<html>
<head>
<title>JQZoom放大镜</title>
<meta charset="utf-8">
<script src="js/jquery.min.js"></script>
<script src="js/jquery.jqzoom.js"></script>
<link rel="stylesheet" type="text/css" href="css/jquery.jqzoom.css" />
<style type="text/css">
body{font-size:13px}
span{color:Red;font-size:12px}
.divFrame{width:260px;border:solid 1px #666}
.divFrame .divTitle{padding:5px;background-color:#eee;font-weight:bold}
.divFrame .divContent{padding:8px;line-height:1.6em}
.divFrame .divContent img{border:1px solid #ccc}
</style>
<script type="text/javascript">
$(function() {
$("#jqzoom").jqzoom( //绑定图片放大插件jqzoom
{
zoomWidth: 230,
zoomHeight: 230,
position: 'right'
}
);
});
</script>
</head>
<body>
<div class="divFrame">
<div class="divTitle">
图片放大镜
</div>
<div class="divContent">
<a href="images/bag.jpg" id="jqzoom" title="我的背包">
<img src="images/bagsmall.jpg">
</a>
</div>
</div>
</body>
</html>

Demo的代码如上所示,下面我们来分析一下代码调用

代码分析

1
2
3
<a href="images/bag.jpg" id="jqzoom" title="我的背包">
<img src="images/bagsmall.jpg">
</a>

最主要的部分如上所示,是一个超链接包含了一张图片。超链接的href是图片的大图,里面的img的src是小图。 利用插件时,只需要取到超链接这个元素,然后调用jqzoom方法就可以了

1
2
3
4
5
6
7
$("#jqzoom").jqzoom( //绑定图片放大插件jqzoom
{
zoomWidth: 230,
zoomHeight: 230,
position: 'right'
}
);

用法很简单,只需要传入一些参数就可以了。

参数详解

下面是所有的参数详解

  • zoomType,默认值:’standard’,另一个值是’reverse’,是否将原图用半透明图层遮盖。
  • zoomWidth,默认值:200,放大窗口的宽度。
  • zoomHeight,默认值:200,放大窗口的高度。
  • xOffset,默认值:10,放大窗口相对于原图的x轴偏移值,可以为负。
  • yOffset,默认值:0,放大窗口相对于原图的y轴偏移值,可以为负。
  • position,默认值:’right’,放大窗口的位置,值还可以是:’right’ ,’left’ ,’top’ ,’bottom’。
  • lens,默认值:true,若为false,则不在原图上显示镜头。
  • imageOpacity,默认值:0.2,当zoomType的值为’reverse’时,这个参数用于指定遮罩的透明度。
  • title,默认值:true,在放大窗口中显示标题,值可以为a标记的title值,若无,则为原图的title值。
  • showEffect,默认值:’show’,显示放大窗口时的效果,值可以为: ‘show’ ,’fadein’。
  • hideEffect,默认值:’hide’,隐藏放大窗口时的效果: ‘hide’ ,’fadeout’。
  • fadeinSpeed,默认值:’fast’,放大窗口的渐显速度(选项: ‘fast’,’slow’,’medium’)。
  • fadeoutSpeed,默认值:’slow’,放大窗口的渐隐速度(选项: ‘fast’,’slow’,’medium’)。
  • showPreload,默认值:true,是否显示加载提示Loading zoom(选项: ‘true’,’false’)。
  • preloadText,默认值:’Loading zoom’,自定义加载提示文本。
  • preloadPosition,默认值:’center’,加载提示的位置,值也可以为’bycss’,以通过css指定位置。

大家可以尝试设置上面的参数来达到想要的效果。

综述

通过jQZoom这个插件我们可以很方便地实现图片的局部放缩预览,希望对大家有帮助。

JavaScript

综述

我们肯定用过QQ空间吧,看到QQ空间里面的照片,点一下就会出现一个悬浮框,显示放大后的图片,而且可以点击左右箭头来查看上一张和下一张照片,怎样?这种效果,想不想实现一下。 在这里,我们就引用一个jQuery插件来帮助我们完成这件事情,让我们拭目以待吧

在线演示

我们首先来在线演示一下效果,然后我们说一下是怎样的实现,点开链接进行预览吧 在线预览

插件文件

  • jquery-1.4.2.min.js
  • jquery.notesforlightbox.js
  • jquery.notesforlightbox.css

其中包含了两个JS文件,一个是jQuery库,另一个就是lightbox插件文件。另外还有一个css文件,主要作用是给图片浏览器规定样式

HTML

我们写一个DEMO,包含了六张图片,用一个ul列表呈现出来

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
56
57
58
59
60
61
62
<!DOCTYPE>
<html>
<head>
<meta charset="utf-8"/>
<title>notesforlightbox实例</title>
<script src="js/jquery-1.4.2.min.js"></script>
<script src="js/jquery.notesforlightbox.js"></script>
<link rel="stylesheet" type="text/css" href="css/jquery.notesforlightbox.css" />
<style type="text/css">
body{font-size:13px}
.divFrame{width:380px;border:solid 1px #666}
.divFrame .divTitle{padding:5px;background-color:#eee;font-weight:bold}
.divFrame .divContent{padding:8px;line-height:1.6em}
.divFrame .divContent .divPics{background-color: #777;padding: 10px;width: 344px}
.divFrame .divContent .divPics ul{list-style: none;padding:0px;margin:0px}
.divFrame .divContent .divPics ul li{display: inline;}
.divFrame .divContent .divPics ul img{border: 5px solid #444;border-width: 5px;width:100px;height:100px}
.divFrame .divContent .divPics ul a:hover img{border:5px solid #fff;border-width: 5px;color: #fff;}
.divFrame .divContent .divPics ul a:hover{color: #fff;}
</style>
<script type="text/javascript">
$(function() {
$('.divPics a').lightBox({
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;
})
})
</script>
</head>
<body>
<div class="divFrame">
<div class="divTitle">
我的相册
</div>
<div class="divContent">
<div class="divPics">
<ul>
<li><a href="images/img01.jpg" title="第1篇风景图片">
<img src="images/img01.jpg" alt="" />
</a></li>
<li><a href="images/img02.jpg" title="第2篇风景图片">
<img src="images/img02.jpg" alt="" />
</a></li>
<li><a href="images/img03.jpg" title="第3篇风景图片">
<img src="images/img03.jpg" alt="" />
</a></li>
<li><a href="images/img04.jpg" title="第4篇风景图片">
<img src="images/img04.jpg" alt="" />
</a></li>
<li><a href="images/img05.jpg" title="第5篇风景图片">
<img src="images/img05.jpg" alt="" />
</a></li>
<li><a href="images/img06.jpg" title="第6篇风景图片">
<img src="images/img06.jpg" alt="" />
</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>

其中images文件夹中存在了六张图片,其中每一个中的超链接的href链接是一张图片,超链接中还包含了img标签。这样可以保证点击图片的时候可以呼出一张大图。

功能简析

1
2
3
4
5
6
7
$(function() {
$('.divPics a').lightBox({
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;
})
})

我们取到了所有的超链接元素,然后调用了lightBox方法,参数是一系列集合,在这里定义了

1
2
3
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;

在这里给出所有的参数说明

名称

默认值

说明

overlayBgColor

000

背景色

overlayOpacity

0.8

背景色透明度

fixedNavigation

false

是否始终显示上一张、下一张按钮

imageLoading

images/lightbox-ico-loading.gif

加载图片时显示的图片

imageBtnPrev

images/lightbox-btn-prev.gif

上一张按钮的图片

imageBtnNext

images/lightbox-btn-next.gif

下一张按钮的图片

imageBtnClose

images/lightbox-btn-close.gif

关闭按钮的图片

imageBlank

images/lightbox-blank.gif

上一张、下一张按钮周围空白部分的图片(默认透明)

containerBorderSize

10

展示图片的边框宽度

containerResizeSpeed

400

展示过程切换的速度

txtImage

Image

页码辅助文字

txtOf

of

页码辅助文字

keyToClose

c

关闭展示的快捷键

keyToPrev

p

上一张的快捷键

keyToNext

n

下一张的快捷键

值得注意的地方是

imageLoading

‘images/lightbox-ico-loading.gif’

加载图片时显示的图片

imageBtnPrev

‘images/lightbox-btn-prev.gif’

上一张按钮的图片

imageBtnNext

‘images/lightbox-btn-next.gif’

下一张按钮的图片

imageBtnClose

‘images/lightbox-btn-close.gif’

关闭按钮的图片

这几张张图片,我们如果不定义,则会使用JS中默认的定义路径 在 jquery.notesforlightbox.js 中,如下程序便实现了上一张图片和下一张图片等按钮的定义

1
2
3
4
5
6
7
8
9
imageLoading: 'images/loading.gif',
imageBtnPrev: 'images/prev.png',
imageBtnNext: 'images/next.png',
imageBtnClose: 'images/close.png',
imageBlank: 'images/lightbox-blank.gif',
imageBtnBottomPrev: 'images/btm_prev.gif',
imageBtnBottomNext: 'images/btm_next.gif',
imageBtnPlay: 'images/start.png',
imageBtnStop: 'images/pause.png',

所以,如果发现图片不正常显示可以检查一下这里的路径设置问题

代码下载

源码下载 代码已部署在GitHub,可以下载查看

总结

通过这个插件我们可以方便地实现图片的加载预览,效果也比较酷炫,希望对大家有帮助!

JavaScript

综述

我想大家一定见到过,在某个网站填写邮箱的时候,还没有填写完,就会出现一系列下拉列表,帮你自动补全邮箱的功能。现在我们就用jQuery来实现一下。 博主原创代码,如有代码写的不完善的地方还望大家多多指教。

功能简述

  • 填写邮箱名字,出现下拉列表,自动补全邮箱
  • 点击上下按键,选取下拉列表邮箱
  • 按回车键,选中列表内容,隐藏下拉列表
  • 鼠标经过,下拉列表选项设置为高亮
  • 鼠标点击,选中下拉列表选项,隐藏下拉列表

在线演示

在此直接插入一个iframe进行演示 链接为 在线演示

HTML

HTML代码很简单,我们就一个简单的输入框,然后一个ul标签,在内部可以放好多li标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<meta charset="utf-8"/>
<script src="js/jquery.min.js"></script>
<script src="js/main.js"></script>
<link href="css/style.css" rel="stylesheet"/>
</head>
<body>
<div class="content">
<input type="text" name="email" id="email" placeholder="请输入您的邮箱"/>
<ul class="list"></ul>
</div>
</body>
</html>

以上便是HTML代码

CSS

在CSS中,定义也比较简单,其中有一个 lilight 的 class,可以使背景变色,通过 remove 和 add 这个 class,我们可以轻松地实现下拉列表元素是否选中的区分。 CSS所有样式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.content input{
padding:5px 10px;
width:200px;
}
ul.list{
list-style:none;
padding:0px;
margin:0px;
overflow:hidden;
}
ul.list li{
border:1px solid #EEE;
width:180px;
padding:5px 10px;
margin:0px;
text-overflow:ellipsis; //溢出时变为省略
overflow:hidden;
}
.lilight{
background-color:#fafafa;
}

以上便是CSS代码

JS

我们引入 jQuery 来实现对元素的操作,实现了按键和鼠标监听,代码如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
$(function(){
//声明所有的电子邮件变量
var mail=new Array("sina.com.cn","126.com","163.com","gmail.com","qq.com","vip.qq.com","hotmail.com","sohu.com","139.com","vip.sina.com","cuiqingcai.com");
//生成一个个li,并加入到ul中
for(var i=0;i<mail.length;i++){
var liElement=$("<li class=\"autoli\"><span class=\"ex\"></span><span class=\"at\">@</span><span class=\"tail\">"+mail[i]+"</span></li>");
liElement.appendTo("ul.list");
}
//首先让list隐藏起来
$("ul.list").hide();

$("#email").keyup(function(event){
//键入的内容不是上下箭头和回车
if(event.keyCode!=38&&event.keyCode!=40&&event.keyCode!=13){
//如果输入的值不是空或者不以空格开头
if($.trim($(this).val())!=""&& $.trim($(this).val()).match(/^@/)==null){
$("ul.list").show();
//如果当前有已经高亮的下拉选项卡,那么将其移除
if($("ul.list li:visible").hasClass("lilight")){
$("ul.list li").removeClass("lilight");
}
//如果还存在下拉选项卡,那么将其高亮
if($("ul.list li:visible")){
$("ul.list li:visible:eq(0)").addClass("lilight");
}
}else{
//否则不进行显示
$("ul.list").hide();
$("ul.list li").removeClass("lilight");
}
//输入的内容还没有包括@符号
if($.trim($(this).val()).match(/.*@/)==null){
$(".list li .ex").text($(this).val());
}else{
//输入的符号已经包含了@
var str = $(this).val();
var strs = str.split("@");
$(".list li .ex").text(strs[0]);
if($(this).val().length>=strs[0].length+1){
tail=str.substr(strs[0].length+1);
$(".list li .tail").each(function(){
//如果数组中的元素是以文本中的后缀开头,那么就显示,否则不显示
if(!($(this).text().match(tail)!=null&&$(this).text().indexOf(tail)==0)){
//隐藏其他的li
$(this).parent().hide();
}else{
//显示所在的li
$(this).parent().show();
}
});
}
}
}
//按了回车时,将当前选中的元素写入到文本框中
if(event.keyCode==13){
$("#email").val($("ul.list li.lilight:visible").text());
$("ul.list").hide();
}
});

//监听上下方向键
$("#email").keydown(function(event){
//下方向键按下了
if(event.keyCode==40){
if($("ul.list li").is(".lilight")){
if($("ul.list li.lilight").nextAll().is("li:visible")){
$("ul.list li.lilight").removeClass("lilight").next("li").addClass("lilight");
}
}
}
//下方向键按下了
if(event.keyCode==38){
if($("ul.list li").is(".lilight")){
if($("ul.list li.lilight").prevAll().is("li:visible")){
$("ul.list li.lilight").removeClass("lilight").prev("li").addClass("lilight");
}
}
}
});

//当鼠标点击某个下拉项时,选中该项,下拉列表隐藏
$("ul.list li").click(function(){
$("#email").val($(this).text());
$("ul.list").hide();
});

//当鼠标划过某个下拉项时,选中该项,下拉列表隐藏
$("ul.list li").hover(function(){
$("ul.list li").removeClass("lilight");
$(this).addClass("lilight");
});

//当鼠标点击其他位置,下拉列表隐藏
$(document).click(function(){
$("ul.list").hide();
});
});

以上便是 jQuery 代码

源码下载

源码下载

总结

其实还有一个比较强大的插件,叫autocomplete,同样可以实现下拉列表的自动补全,功能更加完善,如果大家有兴趣可以去试一下。不过感觉最常用的就是邮箱自动补齐,而且直接用 jQuery 就可以比较方便地实现,所以博主就没有使用autocomplete插件,而是自己写了一下,一来练习一下,二来对这种功能的实现了解得更加透彻。 大家也可以尝试下,希望小伙伴们有帮助,加油!

JavaScript

综述

validate是一个用来验证表单提交的插件,应用十分广泛,具有如下的几个功能

  • 自带了基本的验证规则
  • 提供了丰富的验证信息提示功能
  • 多种事件触发验证
  • 自定义验证规则

下面我们就来感受一下这个插件的强大之处吧

插件下载

在这里我们需要用到的插件文件有

  • jquery.validate.js
  • jquery.validate.messages_cn.js
  • jquery.min.js

一个是表单验证的主文件,另一个是设置中文提示的文件。

实例引入

我们先用一个小例子来感受一下使用 validate 插件的便捷之处,这个例子中加入了表单合法性验证和错误提示,代码如下

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
56
57
58
59
60
61
62
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css"></link>
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
}
}
);
});
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

运行结果如下 在这里我们定义了 rules 来控制表单的合法性,通过 errorPlacement 来控制错误的输出位置。

校验规则

下面我们详细说一下关于rules的相关知识,将校检规则总结如下

序号

规则

描述

1

required:true

必须输入的字段。

2

remote:”check.php”

使用 ajax 方法调用 check.php 验证输入值。

3

email:true

必须输入正确格式的电子邮件。

4

url:true

必须输入正确格式的网址。

5

date:true

必须输入正确格式的日期。日期校验 ie6 出错,慎用。

6

dateISO:true

必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22。只验证格式,不验证有效性。

7

number:true

必须输入合法的数字(负数,小数)。

8

digits:true

必须输入整数。

9

creditcard:

必须输入合法的信用卡号。

10

equalTo:”#field”

输入值必须和 #field 相同。

11

accept:

输入拥有合法后缀名的字符串(上传文件的后缀)。

12

maxlength:5

输入长度最多是 5 的字符串(汉字算一个字符)。

13

minlength:10

输入长度最小是 10 的字符串(汉字算一个字符)。

14

rangelength:[5,10]

输入长度必须介于 5 和 10 之间的字符串(汉字算一个字符)。

15

range:[5,10]

输入值必须介于 5 和 10 之间。

16

max:5

输入值不能大于 5。

17

min:10

输入值不能小于 10。

比如我们针对 email 这个表单就可以定义为

1
email: { required: true, email: true }

针对url的这个输入表单就可以定义为

1
url: { required: true, url: true }

以上便是校验规则的相关内容。

消息提示

在 jquery.validate.js 这个文件中,定义了默认的消息提示,不过它是英文的提示,默认的提示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
messages: {
required: "This field is required.",
remote: "Please fix this field.",
email: "Please enter a valid email address.",
url: "Please enter a valid URL.",
date: "Please enter a valid date.",
dateISO: "Please enter a valid date (ISO).",
dateDE: "Bitte geben Sie ein gültiges Datum ein.",
number: "Please enter a valid number.",
numberDE: "Bitte geben Sie eine Nummer ein.",
digits: "Please enter only digits",
creditcard: "Please enter a valid credit card number.",
equalTo: "Please enter the same value again.",
accept: "Please enter a value with a valid extension.",
maxlength: $.validator.format("Please enter no more than {0} characters."),
minlength: $.validator.format("Please enter at least {0} characters."),
rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
range: $.validator.format("Please enter a value between {0} and {1}."),
max: $.validator.format("Please enter a value less than or equal to {0}."),
min: $.validator.format("Please enter a value greater than or equal to {0}.")
},

比如,如果遇到 email 校验有问题,那么便会提示

1
Please enter a valid email address

不过我们通过引入 jquery.validate.messages_cn.js 这个文件,写入了如下代码,将默认的提示修改为中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
jQuery.extend(jQuery.validator.messages, {
required: "必选字段",
remote: "请修正该字段",
email: "请输入正确格式的电子邮件",
url: "请输入合法的网址",
date: "请输入合法的日期",
dateISO: "请输入合法的日期 (ISO).",
number: "请输入合法的数字",
digits: "只能输入整数",
creditcard: "请输入合法的信用卡号",
equalTo: "请再次输入相同的值",
accept: "请输入拥有合法后缀名的字符串",
maxlength: jQuery.format("请输入一个长度最多是 {0} 的字符串"),
minlength: jQuery.format("请输入一个长度最少是 {0} 的字符串"),
rangelength: jQuery.format("请输入一个长度介于 {0} 和 {1} 之间的字符串"),
range: jQuery.format("请输入一个介于 {0} 和 {1} 之间的值"),
max: jQuery.format("请输入一个最大为 {0} 的值"),
min: jQuery.format("请输入一个最小为 {0} 的值")
});

当然,以上的设置都是默认的提示,我们还可以通过 messages 来设置提示,举一个小例子,加入 messages 选项

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
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
email: { required: "请输入电子邮件", email: "请输入正确格式" }
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

运行结果如下

失败验证

1
2
3
  errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},

我们用 errorPlacement 来处理验证失败后的处理,方法有两个参数,一个是error,一个是element。 其中error是字符串,保存了messages中返回的错误信息,element是验证失败的input元素。 比如上面这一句

1
error.appendTo(element.siblings("span"));

就代表把错误加入到input元素同级的span元素中,从而在标签内部显示错误的内容。 其他的情况我们可以灵活处理。

成功验证

有失败就有成功,在这里我们可以用一个函数来实现成功的验证

1
2
3
success: function(label) {
label.html("OK");
}

这里的label指的是发生错误时那个标签,就是上面例子中的span,通过html()方法可以实现标签内容的变化。例如下面的例子

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<meta charset="utf-8"/>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
email: { required: "请输入电子邮件", email: "请输入正确格式" }
},
success: function(label) {
label.html("OK");
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span>呵呵</span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

上面就是验证成功之后的效果,在相应提示的地方会显示OK。

异步验证

有时候我们需要用到异步验证,我们可以在rules中加入remote进行远程验证,例子如下

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<meta charset="utf-8"/>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
phone: {
required: true,
remote:{
url: "check_phone.php", //后台处理程序
type: "post", //数据发送方式
dataType: "json", //接受数据格式
data: { //要传递的数据
phone: function() {
return $("#phone").val();
}
}
}
}
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
phone: { required: "请输入电话", remote: "请输入正确格式" }
},
success: function(label) {
label.html("OK");
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
电话号码:<br />
<input id="phone" name="phone"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

PHP处理程序,注意这里的返回值只能是true或者false,并且需要加引号。

1
2
3
4
5
6
7
8
<?php 
$phone = $_POST['phone'];
if((strlen($phone) != 11) || !(preg_match("/13[0123456789]{1}\d{8}|15[012356789]\d{8}|18[0123456789]\d{8}|17[0678]\d{8}|14[57]\d{8}/",$phone))){
echo "false";
}else{
echo "true";
}
?>

演示如下 上面就是进行ajax异步验证的处理方式

自定义方法

有时候我们需要自定义一些验证方法,我们就需要用到addMethod方法,介绍如下 addMethod(name,method,message)方法

参数name 是添加的方法的名字 参数method是一个函数,接收三个参数(value,element,param) value 是元素的值,element是元素本身 param是参数,我们可以用addMethod 来添加除built-in Validation methods 之外的验证方法

例如手机号码的验证如下

1
2
3
4
5
6
7
$.validator.addMethod("phone",function(value,element,params){
if((value.length != 11) || (!value.match(/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[0|6|7|8]|18[0-9])\d{8}$/))){
return false;
}else{
return true;
}
},"请输入正确的手机号");

使用时如下

1
2
3
4
5
rules:{
phone:{
required:true,phone:true
},
},

有一个字段,只能输一个字母,范围是a-f,写法如下

1
2
3
4
5
6
7
8
9
10
$.validator.addMethod(“af”,function(value,element,params){
if(value.length>1){
return false;
}
if(value>=params[0] && value<=params[1]){
return true;
}else{
return false;
}
},”必须是一个字母,且a-f”);

使用时如下

1
2
3
rules:{
username:{ af:["a","f"] }
},

以上便是自定义验证方法的方式

DeBug模式

开启DeBug模式后,不会进行提交,只需要在代码中加入

1
debug:true

即可,这样不论怎样,都不会提交表单,对于调试十分有用。

验证通过提交

在上面的例子中,我们没有设置表单验证通过之后才提交,通过加入以下代码,可以实现验证之后才提交的效果

1
2
3
submitHandler:function(form){ 
form.submit();
}

通过设置上面的内容,我们就可以避免验证不成功submit跳转了

忽略元素

有时候,我们想跳过某些元素不进行验证,可以通过加入如下代码来实现,举例如下

1
ignore:"input",

忽略所有input元素

1
ignore:"#username",

忽略id为username的元素

1
ignore:".input",

忽略所有class为input的元素

响应事件

在默认的响应事件是 submit 提交事件,我们还可以通过设置来改变事件的响应,比如失去焦点时验证等等,举例如下 Onubmit:类型 Boolean,默认 true,指定是否提交时验证。

1
$(".selector").validate({   	onsubmit:false })

onfocusout:类型 Boolean,默认 true,指定是否在获取焦点时验证。

1
$(".selector").validate({     onfocusout:false })

onkeyup:类型 Boolean,默认 true,指定是否在敲击键盘时验证。

1
$(".selector").validate({    onkeyup:false })

onclick:类型 Boolean,默认 true,指定是否在鼠标点击时验证(一般验证 checkbox、radiobox)。

1
$(".selector").validate({    onclick:false })

focusInvalid:类型 Boolean,默认 true。提交表单后,未通过验证的表单(第一个或提交之前获得焦点的未通过验证的表单)会获得焦点。

1
$(".selector").validate({    focusInvalid:false })

focusCleanup:类型 Boolean,默认 false。当未通过验证的元素获得焦点时,移除错误提示(避免和 focusInvalid 一起使用)。

1
$(".selector").validate({    focusCleanup:true })

上面的响应事件一般不太常用,仅作了解即可。

总结

以上便是jQuery插件validate的用法,利用好这款插件对于编写将有极其大的帮助,希望大家能好好学习!

个人随笔

4月4日,凌晨3点,我依旧无法入眠,心里想了很多,索性起床把现在的所想记录下来。也许,在一觉过后,这些情感或许就无法挥洒出来了。现在,借着那纯音乐,借着那静谧的夜,总结一下那逝去的三月。 三月一日开学,到现在已经有了一个月多一点的时间了,都说大三下是最难熬的一段时光,亲身经历之后才觉得真的是这样,这一个月,可以用浮躁两个字来形容。 浮躁之处在哪?学习,面试,备考,社交,做事还有其他,浮躁之气充斥在方方面面。 大三开始,不得不去考虑工作或是读研的事情,身边的朋友们,有的选择考研,有的选择工作。室友三个,选择的也都是工作。开学之初,各大公司的校招纷纷开始了,朋友们纷纷开始往各大公司投简历,BAT也不例外。当然BAT终究是BAT,不是省油的灯,一些朋友就在这条道路上遭遇了不少的坎坷,有的简历初审就被PASS,有的电面几轮之后也不幸失败,过程非常坎坷。其中有些朋友一份简历投上几十家公司,想碰运气,录用上哪一个是哪一个,而在我看来,这真的没有太大的意义。投一个公司,对该公司有全面的了解是至关重要,而且公司的理念文化适不适合自己去工作也是一个重要的因素,在我看来,这种碰运气的方法,实不可取。 而对于我自己,更加倾向于读研,鉴于自己的学习成绩还算不错,前百分之五,想申请一下保研,查找了各大学校的招生信息,对自己要复习和准备的东西也有了一些把握,可这一个月以来,自己难以静心去学习。其实说来,周围的朋友纷纷在准备找工作实习,而他们的焦点则就放在了填补一些职业相关的专业知识,掌握一些面试技巧,他们一轮轮的电面,也为自己的工作积累了经验。而我,也在想在大四的时候申请一份实习工作,就算没有拿到Offer,在面试的过程中也能积累一部分的经验,索性我也去试试吧。 制作了自己的一份简历,我也投上了BAT和一些其他公司,因为我本身对网页开发比较了解,尤其是后端开发,PHP部分。而像阿里这样的公司,他们的后端不是利用PHP处理的,所以我就报了前端开发的职位。但是自己的前端功底还不太强,所以我就开始补习JS,jQuery等等的知识,这一个月以来,大多数时间就在学习这些知识。对于找工作我可以说是试一试的态度,学习起来感觉沉不住气,而且可能觉得随时会有一个电话面试打过来,所以我觉得难以系统地,静心地去学习这些知识。 其实周围的朋友和我的情况是差不多的,我特意问过他们最近是怎样的状态,他们也回应说,感觉有些浮躁,完全是在一个应试的压力下在学习,对于一些知识只是修修补补,没有一个系统的把握。仔细想想,真的是这样,他们的路就是工作,谁也想进一个更好的公司,所以他们也在尽力提升着自己,可事实却是,太浮躁。 除了这些,我真的觉得在其他方面也应该好好反思一下自己。 这一个月的作息非常乱,在寒假时曾经做过打算,早睡早起,可慢慢地,睡得越来越晚,同样,起得也越来越晚。开学的前几天,坚持12点之前入睡,可慢慢地,拖到凌晨一两点钟才睡,而理所应当地,早上就难以起床,早起了也还是觉得萎靡不振。最初课程比较少,而且都集中在周二这一天,所以,除了周二,几乎每天都有机会睡个懒觉,由于这个原因,自己也经常早上起得很晚,有时甚至起床就是中午吃饭的时间了。这么一来,一上午的时光岂不是白白浪费了吗?这样的结果归咎于什么?我想,是自己的毅力问题,难以持之以恒,难以律己。 开学以来一个最不让我后悔的决定就是,健身。曾经的时候,一天没有什么活动,就坐在那学习,娱乐,一坐一玩就是一整天,自己的身体确实也难以承受得了,处在一个亚健康状态。开学之初,和好朋友一起去了学校对面的健身馆去办了一张健身年卡,几乎每天我们都去练上一个多小时,互相督促,一起锻炼。我的目标是增肌,自己真的是太瘦了,在健身房,跑上几千米后,尝试各种健身器材,锻炼胸肌等等,另外还有一套腹肌撕裂者的动作,几乎每天都这么坚持做下来。一个月的时间说长也长,说短也短,自己本身太瘦,也在努力多吃,不过现在腹肌和胸肌还没有明显的迹象。不管怎样,坚持长期练下去吧,我相信会有成果的。 另外,在待人处事方面,还是觉得自己不够稳重,现在是21岁了,也该变得成熟些了。在言谈举止方面,现在回想起这一个月自己曾经做过的事,真的有些好幼稚,好可笑。我想,尝试着去变得更稳重,考虑事情更加周全,做事更加细致,结果一定更好。 过去的一个月,我大部分时间都是在宿舍,学习啊,娱乐啊,都是宅在那里,室友也是一样。现在觉得,的确是有些懒散了,而且懒散得效率低下,作息不规律。清明回去之后,是该换一种学习环境了,找一个安静的自习室,在那里看看书,敲敲代码。学习累了,听听音乐,出去走走,望一望周围同学努力的身影,去感受一下这样的状态,告别懒散的生活方式,大胆做一番新的尝试吧。而且,是时候为自己的保研做好好的准备了,专业课知识,英语,真的挺重要。 最后,想说几句话。 为了自己的梦想和所喜欢的事而努力,这份动力是发自内心的。 一个成熟的人,有一颗强大的内心,有一种强大的毅力,做事稳重,注重细节,让自己变得更加稳重和成熟吧。 在做每一件事之前,好好想想怎样可以把这件事做得更好,好好想想自己以后会不会为自己的所作而感到后悔。 让自己变得更优秀,好好经营自己,自己所想要的,说不定会随之而来。 现在凌晨四点十九,写完之后,心中一丝畅然,晚安,世界。

PHP

综述

1.什么是mysqli

PHP-MySQL 函数库是 PHP 操作 MySQL 资料库最原始的扩展库,PHP-MySQLi 的 i 代表 Improvement ,相当于前者的增强版,也包含了相对进阶的功能,另外本身也增加了安全性,比如可以大幅度减少 SQL 注入等问题的发生。

2. mysql与mysqli的概念相关

(1)mysql与mysqli都是php方面的函数集,与mysql数据库关联不大。 (2)在php5版本之前,一般是用php的mysql函数去驱动mysql数据库的,比如mysql_query()的函数,属于面向过程 (3)在php5版本以后,增加了mysqli的函数功能,某种意义上讲,它是mysql系统函数的增强版,更稳定更高效更安全,与mysql_query()对应的有mysqli_query(),属于面向对象,用对象的方式操作驱动mysql数据库

3. mysql与mysqli的主要区别

(1)mysql是非持继连接函数,mysql每次链接都会打开一个连接的进程,所以mysqli耗费资源少一些。 (2)mysqli是永远连接函数,mysqli多次运行mysqli将使用同一连接进程,从而减少了服务器的开销。mysqli封装了诸如事务等一些高级操作,同时封装了DB操作过程中的很多可用的方法。 (3)mysqli提供了面向对象编程方式和面向过程编程方式,而mysql则只可以面向过程。 例如如下代码分别是mysqli的面向对象编程方式和面向过程方式 面向对象方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

printf("Host information: %s\n", $mysqli->host_info);

/* close connection */
$mysqli->close();
?>

面向过程方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

/* check connection */
if (!$link) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

printf("Host information: %s\n", mysqli_get_host_info($link));

/* close connection */
mysqli_close($link);
?>

(4)mysqli 可以通过预处理语句来减少开销和SQL注入的风险,而mysql则做不到。 综上所述,如果大家用的是PHP5,而且mysql版本在5.0以上,希望大家以后能用mysqli的就尽量使用mqsqli,不仅高效,而且更安全,而且推荐大家使用面向对象编程方式。 在这里,我们也只介绍面向对象编程方式。

函数使用

1. 连接数据库并获取相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$mysqli=@new mysqli("localhost", "root", "", "mysql");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//获取当前字符集
echo $mysqli->character_set_name()."<br>";
//获取客户端信息
echo $mysqli->get_client_info()."<br>";
//获取mysql主机信息
echo $mysqli->host_info."<br>";
//获取服务器信息
echo $mysqli->server_info."<br>";
//获取服务器版本
echo $mysqli->server_version."<br>";
//关闭数据库连接
$mysqli->close();
?>

如果连接成功则运行结果

latin1 mysqlnd 5.0.10 - 20111026 - $Id: e707c415db32080b3752b232487a435ee0372157 $ localhost via TCP/IP 5.6.12-log 50612

如果连接失败则可能结果为

连接数据库失败:Access denied for user ‘root’@’localhost’ (using password: YES) 连接数据库失败:Unknown database ‘hello’

2.查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//构造SQL语句
$query = "SELECT * FROM designer order by ID LIMIT 3";
//执行SQL语句
$result = $mysqli->query($query);
//遍历结果
while($row = $result->fetch_array(MYSQLI_BOTH)){
echo "id".$row['id']."<br>";
}
//释放结果集
$result->free();
//关闭数据库连接
$mysqli->close();
?>

运行结果

1
2
3
id10062
id10063
id10064

在这里需要注意的是

1
fetch_array(MYSQLI_BOTH)

这个方法,参数有三个,分别是 MYSQLI_BOTH,MYSQLI_NUM,MYSQLI_ASSOC。 如果参数传入了 MYSQLI_BOTH,返回数组的索引既包括数字和名称。

1
2
3
4
5
6
7
8
9
array (size=26)
0 => string '10062' (length=5)
'id' => string '10062' (length=5)
1 => string '??' (length=2)
'name' => string '??' (length=2)
2 => string '1016903103@qq.com' (length=17)
'email' => string '1016903103@qq.com' (length=17)
3 => string '18366119732' (length=11)
'phone' => string '18366119732' (length=11)

如果参数传入了 MYSQLI_NUM,返回数组的索引只包含数字。

1
2
3
4
5
array (size=13)
0 => string '10062' (length=5)
1 => string '??' (length=2)
2 => string '1016903103@qq.com' (length=17)
3 => string '18366119732' (length=11)

如果参数传入了 MYSQLI_BOTH,返回数组的索引只包含名称。

1
2
3
4
5
array (size=13)
'id' => string '10062' (length=5)
'name' => string '??' (length=2)
'email' => string '1016903103@qq.com' (length=17)
'phone' => string '18366119732' (length=11)

其实还有等价的方法 fetch_row(),fetch_assoc()

他们之间的关系如下

$result->fetch_row() = mysql_fetch_row() = $result->fetch_array(MYSQLI_NUM) = mysql_fetch_array(MYSQLI_NUM) 返回索引数组

$result->fetch_assoc() = mysql_fetch_assoc() = $result->fetch_array(MYSQLI_ASSOC) = mysql_fetch_array(MYSQLI_ASSOC) 返回索引列名

如果 fetch_array()方法什么也不传,则默认传入了 MYSQLI_BOTH

3.插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//插入数据
$sql="insert into designer(name,phone) values('hello','18352682923')";
//执行插入语句
$result=$mysqli->query($sql);
//如果执行错误
if(!$result){
echo "SQL语句有误<br>";
echo "ERROR:".$mysqli->errno."|".$mysqli->error;
exit;
}
//如果插入成功,则返回影响的行数
echo $mysqli->affected_rows;
//关闭数据库连接
$mysqli->close();
?>

如果插入成功,那么结果则会是1,如果失败,则会报出错误。

4.修改内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//插入数据
$sql="update designer set name = 'hello' where id = 10062";
//执行插入语句
$result=$mysqli->query($sql);
//如果执行错误
if(!$result){
echo "SQL语句有误<br>";
echo "ERROR:".$mysqli->errno."|".$mysqli->error;
exit;
}
//如果插入成功,则返回影响的行数
echo $mysqli->affected_rows;
//关闭数据库连接
$mysqli->close();
?>

如果修改成功,同样返回被修改的行数。

5.预处理语句

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
<?php
$mysqli = @new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//准备好一条语句放到服务器中,插入语句
$sql = "insert into designer(name, email) values(?, ?)";
//生成预处理语句
$stmt = $mysqli->prepare($sql);
//给占位符号每个?号传值(绑定参数) i d s b,第一个参数为格式化字符,ss代表两个字符串,d代表数字
$stmt->bind_param("ss", $name, $email);
//为变量赋值
$name = "Mike";
$email = "mike@live.cn";
//执行
$stmt->execute();
//为变量赋值
$name = "Larry";
$email = "larry@live.cn";
//执行
$stmt->execute();
//最后输出
echo "最后ID".$stmt->insert_id."<br>";
echo "影响了".$stmt->affected_rows."行<br>";
//关闭数据库连接
$mysqli->close();
?>

通过以上的预处理语句,我们也可以实现数据插入。 那么预处理语句有什么特点呢?

1. 效率上更高, 就是如果执行多次相同的语句,只有语句数据不同, 因为将一条语句在服务器端准备好,然后将不同的值传给服务器,再让这条语句执行。相当于编译一次,使用多次。 2. 安全上:可以防止SQL注入(? 占位)这样就可以防止非正常的变量的注入。

所以,推荐大家使用mysqli的预处理语句的方式,不仅效率高,而且更加安全。

综述

以上就是对mysqli的一些方法的介绍,更加详细的内容,请查看 PHP 手册。 希望对大家有帮助!

PHP

综述

对于PHP的图像处理来说,应用最广泛的便是验证码处理了,上一节我们学习到了PHP绘图的一些基本操作。现在我们实际运用一下,来感受一下验证码的相关应用。 在这里,我们将整个验证码写成了一个PHP类,以后我们用的时候直接调用这个类就好了。 传入的参数为验证码的宽度,高度,还有验证码的字符。

验证码类

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<?php

class Code {
//验证码的宽度
private $width;
//验证码的高度
private $height;
//验证码字符的个数
private $codeNum;
//图像资源
private $image;
//干扰点的个数
private $disturbColorNum;
//验证码字符内容
private $checkCode;

//构造方法,默认宽为80,高为20,字符个数为4
function __construct($width=80, $height=20, $codeNum=4){
//赋值成员变量
$this->width=$width;
$this->height=$height;
$this->codeNum=$codeNum;
//获得验证码字符内容
$this->checkCode=$this->createCheckCode();
//设置干扰点的个数
$number=floor($width*$height/15);
if($number > 240-$codeNum){
$this->disturbColorNum= 240-$codeNum;
}else{
$this->disturbColorNum=$number;
}
}

//通过访问该方法向浏览器中输出图像
function showImage($fontFace=""){
//第一步:创建图像背景
$this->createImage();
//第二步:设置干扰元素
$this->setDisturbColor();
//第三步:向图像中随机画出文本
$this->outputText($fontFace);
//第四步:输出图像
$this->outputImage();
}

//通过调用该方法获取随机创建的验证码字符串
function getCheckCode(){
return $this->checkCode;
}

//设置图像资源
private function createImage(){
//创建图像资源
$this->image=imagecreatetruecolor($this->width, $this->height);
//随机背景色
$backColor=imagecolorallocate($this->image, rand(225, 255), rand(225,255), rand(225, 255));
//为背景添充颜色
imagefill($this->image, 0, 0, $backColor);
//设置边框颜色
$border=imagecolorallocate($this->image, 0, 0, 0);
//画出矩形边框
imagerectangle($this->image, 0, 0, $this->width-1, $this->height-1, $border);
}

//创建干扰元素
private function setDisturbColor(){
//创建干扰的点
for($i=0; $i<$this->disturbColorNum; $i++){
$color=imagecolorallocate($this->image, rand(0, 255), rand(0, 255), rand(0, 255));
imagesetpixel($this->image, rand(1, $this->width-2), rand(1, $this->height-2), $color);
}
//创建干扰的线条
for($i=0; $i<10; $i++){
$color=imagecolorallocate($this->image, rand(200, 255), rand(200, 255), rand(200, 255));
imagearc($this->image, rand(-10, $this->width), rand(-10, $this->height), rand(30, 300), rand(20, 200), 55, 44, $color);
}
}

//创建随机验证码
private function createCheckCode(){
$code="23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKMNPQRSTUVWXYZ";
$string='';
//从字符串中取出随机的字符
for($i=0; $i < $this->codeNum; $i++){
$char=$code{rand(0, strlen($code)-1)};
$string.=$char;
}
//返回字符内容
return $string;
}

//设置验证码的字符
private function outputText($fontFace=""){
for($i=0; $i<$this->codeNum; $i++){
$fontcolor=imagecolorallocate($this->image, rand(0, 128), rand(0, 128), rand(0, 128));
if($fontFace==""){
$fontsize=rand(3, 5);
$x=floor($this->width/$this->codeNum)*$i+3;
$y=rand(0, $this->height-15);
imagechar($this->image,$fontsize, $x, $y, $this->checkCode{$i},$fontcolor);
}else{
$fontsize=rand(12, 16);
$x=floor(($this->width-8)/$this->codeNum)*$i+8;
$y=rand($fontSize+5, $this->height);
imagettftext($this->image,$fontsize,rand(-30, 30),$x,$y ,$fontcolor, $fontFace, $this->checkCode{$i});
}
}
}

//输出验证码图像资源
private function outputImage() {
if(imagetypes() & IMG_GIF){
header("Content-Type:image/gif");
imagepng($this->image);
}else if(imagetypes() & IMG_JPG){
header("Content-Type:image/jpeg");
imagepng($this->image);
}else if(imagetypes() & IMG_PNG){
header("Content-Type:image/png");
imagepng($this->image);
}else if(imagetypes() & IMG_WBMP){
header("Content-Type:image/vnd.wap.wbmp");
imagepng($this->image);
}else{
die("PHP不支持图像创建");
}
}
//析构方法
function __destruct(){
//销毁图像资源
imagedestroy($this->image);
}
}

以上便是我们的验证码类的全部实现,保存文件名为 code.class.php, 下面我们来看一下怎样应用。

实际应用

我们写好了这个类之后,该怎么来调用呢?我们写一个demo如下

1
2
3
4
5
6
7
<?php
session_start();
include "code.class.php";
$code=new Code(80, 20, 4);
$code->showImage(); //输出到页面中供 注册或登录使用
$_SESSION["code"]=$code->getCheckCode(); //将验证码保存到服务器中
?>

这段代码声明了一个code对象,然后调用 showImage 方法,显示出验证码,最后通过getCheckCode 方法,获得验证码字符串的内容,保存到了 session 全局变量中。 以上代码我们保存成 code.php 文件 之后,我们写一个表单来提交验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
session_start();
if(@$_POST["code"]&&strtoupper($_POST["code"])==strtoupper($_SESSION["code"])){
echo "ok";
}else{
echo "error";
}
?>
<body>
<form action="login.php" method="post">
code: <input type="text" name="code"> <img src="code.php" onclick="this.src='code.php?'+Math.random()"><br>
<input type="submit" name="sub" value="login"><br>
</form>
</body>

上面的代码包括了一个输入框还有验证码,验证码的 src 资源可以直接引用为 code.php 文件,这里需要注意的是,后面要加一个参数,防止因为浏览器缓存原因而导致验证码无法更换。 最后表单是提交到文件本身,所以就可以比较提交的数据和 session 全局变量之间的关系,如果相同输出 ok,如果不同输出 error 小伙伴可以验证一下!希望对大家有帮助!

PHP

综述

PHP 的图像处理主要有下面几个用途,一个是验证码的操作,另一个就是图像水印操作,这里我们一起来学习一下吧。

图像处理基础

1.总体流程

首先,我们必须先了解 PHP 图像处理的基本函数的用法。总体来说分为以下四个步骤

(1)创建画布,创建资源类型,指定 高度 宽度

1
2
resource imagecreate ( int x_size, int y_size )
resource imagecreatetruecolor ( int x_size, int y_size )

imagecreate() 返回一个图像标识符,代表了一幅大小为 x_size 和 y_size 的空白图像。不过推荐使用 imagecreatetruecolor(),这个是新建一个真彩色图像。

(2)绘制图像

制定各种颜色,矩形, 圆, 点, 线段, 扇形, 画字(字符, 字符串, freetype),每一个形状和字符绘制对应一个函数,这部分我们后面详细介绍。

(3)输出图像/保存处理好的图像

1
2
3
imagegif();
imagejpeg();
imagepng();

例如,imagegif() 则将其保存为 gif 格式的图像。 bool imagegif ( resource image [, string filename] )

imagegif() 从 image 图像以 filename 为文件名创建一个 GIF 图像。image 参数是 imagecreatetruecolor() 函数的返回值。 filename 参数为可选,如果省略,则原始图像流将被直接输出。通过 header() 发送 Content-type: image/gif 可以使 PHP 脚本直接输出 GIF 图像。

(4)释放资源

1
imagedestroy($img);

所以,我们创建图像的一般流程用代码可以总结如下

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//绘制图形
$red = imagecolorallocate($img, 0xFF, 0, 0);
imagechar($img, 5, 100, 100, "A", $red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

输出的结果如下,它直接在浏览器中显示如下,背景默认为黑色。 20150328153220 如果我们不加 header,那么则会显示字符乱码,所以一定要记得下面这句话。

1
header("Content-Type:image/gif");

2.设置色彩

我们主要用到下面这个函数 int imagecolorallocate ( resource image, int red, int green, int blue ) imagecolorallocate() 返回一个标识符,代表了由给定的 RGB 成分组成的颜色。image 参数是 imagecreatetruecolor() 函数的返回值。red,green 和 blue 分别是所需要的颜色的红,绿,蓝成分。这些参数是 0 到 255 的整数或者十六进制的 0x00 到 0xFF。imagecolorallocate() 必须被调用以创建每一种用在 image 所代表的图像中的颜色。 例如下面的例子

1
2
3
4
5
6
7
8
9
10
<?php
//十进制方式
$red = imagecolorallocate($img, 0xFF, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// 十六进制方式
$gray = imagecolorallocate($img, 0xEE, 0xEE, 0xEE);
$white = imagecolorallocate($im, 0xFF, 0xFF, 0xFF);
$black = imagecolorallocate($im, 0x00, 0x00, 0x00);
?>

3.区域填充色彩

bool imagefill ( resource image, int x, int y, int color ) imagefill() 在 image 图像的坐标 x,y(图像左上角为 0, 0)处用 color 颜色执行区域填充(即与 x, y 点颜色相同且相邻的点都会被填充)。 好,让我们尝试一下填充背景色吧

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//绘制图形
$red = imagecolorallocate($img, 0xFF, 0, 0);
imagefill($img,0,0,$red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

运行结果如下,背景被填充为了红色。 20150328154707

4.画各种元素

(1)画空心矩形

bool imagerectangle ( resource image, int x1, int y1, int x2, int y2, int col ) imagerectangle() 用 col 颜色在 image 图像中画一个矩形,其左上角坐标为 x1, y1,右下角坐标为 x2, y2。图像的左上角坐标为 0, 0。

(2)画填充矩形

bool imagefilledrectangle ( resource image, int x1, int y1, int x2, int y2, int color ) imagefilledrectangle() 在 image 图像中画一个用 color 颜色填充了的矩形,其左上角坐标为 x1,y1,右下角坐标为 x2,y2。0, 0 是图像的最左上角。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//画一个矩形并填充
imagefilledrectangle($img, 10, 10, 80, 80, $green);
//画一个矩形
imagerectangle($img, 90, 10, 190, 80, $green);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328155313

(3)画一条线段

bool imageline ( resource image, int x1, int y1, int x2, int y2, int color ) imageline() 用 color 颜色在图像 image 中从坐标 x1,y1 到 x2,y2(图像左上角为 0, 0)画一条线段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//线段
imageline($img,0, 0, 200, 200 ,$blue);
imageline($img,200, 0, 0, 200, $blue);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328155846

(4)画点

bool imagesetpixel ( resource image, int x, int y, int color ) imagesetpixel() 在 image 图像中用 color 颜色在 x,y 坐标(图像左上角为 0,0)上画一个点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//点
imagesetpixel($img,50, 50 ,$red);
imagesetpixel($img,55, 50 ,$red);
imagesetpixel($img,59, 50 ,$red);
imagesetpixel($img,64, 50 ,$red);
imagesetpixel($img,72, 50 ,$red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328160304

(5)画空心圆

bool imageellipse ( resource image, int cx, int cy, int w, int h, int color ) imageellipse() 在 image 所代表的图像中画一个中心为 cx,cy(图像左上角为 0, 0)的椭圆。w 和 h 分别指定了椭圆的宽度和高度,椭圆的颜色由 color 指定。

(6)画实心圆

bool imagefilledellipse ( resource image, int cx, int cy, int w, int h, int color ) imagefilledellipse() 在 image 所代表的图像中以 cx,cy(图像左上角为 0, 0)为中心画一个椭圆。w 和 h 分别指定了椭圆的宽和高。椭圆用 color 颜色填充。如果成功则返回 TRUE,失败则返回 FALSE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//圆
imageellipse($img, 100, 100, 100, 100,$green);
//实心圆
imagefilledellipse($img, 100, 100, 10, 10,$blue);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328161138

(7)水平画一个字符

bool imagechar ( resource image, int font, int x, int y, string c, int color ) imagechar() 将字符串 c 的第一个字符画在 image 指定的图像中,其左上角位于 x,y(图像左上角为 0, 0),颜色为 color。如果 font 是 1,2,3,4 或 5,则使用内置的字体(更大的数字对应于更大的字体)。 bool imagestring ( resource image, int font, int x, int y, string s, int col ) imagestring() 用 col 颜色将字符串 s 画到 image 所代表的图像的 x,y 坐标处(这是字符串左上角坐标,整幅图像的左上角为 0,0)。如果 font 是 1,2,3,4 或 5,则使用内置字体。

(8)竖直画一个字符

bool imagecharup ( resource image, int font, int x, int y, string c, int color ) imagecharup() 将字符 c 垂直地画在 image 指定的图像上,位于 x,y(图像左上角为 0, 0),颜色为 color。如果 font 为 1,2,3,4 或 5,则使用内置的字体。 bool imagestringup ( resource image, int font, int x, int y, string s, int col ) imagestring() 用 col 颜色将字符串 s 垂直地画到 image 所代表的图像的 x, y 座标处(图像的左上角为 0, 0)。如果 font 是 1,2,3,4 或 5,则使用内置字体。

(9)带字体写入字符

array imagettftext ( resource image, float size, float angle, int x, int y, int color, string fontfile, string text )

image:图像资源。见 imagecreatetruecolor()。 size: 字体大小。根据 GD 版本不同,应该以像素大小指定(GD1)或点大小(GD2)。 angle: 角度制表示的角度,0 度为从左向右读的文本。更高数值表示逆时针旋转。例如 90 度表示从下向上读的文本。 x: 由 x,y 所表示的坐标定义了第一个字符的基本点(大概是字符的左下角)。这和 imagestring() 不同,其 x,y 定义了第一个字符的左上角。例如 “top left” 为 0, 0。 y: Y 坐标。它设定了字体基线的位置,不是字符的最底端。 color: 颜色索引。使用负的颜色索引值具有关闭防锯齿的效果。见 imagecolorallocate()。 fontfile: 是想要使用的 TrueType 字体的路径。 text: 文本字符串。

imagettftext() 返回一个含有 8 个单元的数组表示了文本外框的四个角,顺序为坐下角,右下角,右上角,左上角。这些点是相对于文本的而和角度无关,因此“左上角”指的是以水平方向看文字时其左上角。

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
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
$navy=imagecolorallocate($img, 0, 0, 0x80);
//填充背景
imagefill($img,0,0,$yellow);
//画字符
imagechar($img, 2, 100, 100, "A", $red);
imagechar($img, 5, 120, 120, "B", $red);
imagecharup($img, 4, 60, 60, "C", $red);
imagecharup($img, 5, 80, 80, "D", $red);
imagestring($img, 3, 10, 10, "Hello", $navy);
imagestringup($img, 3, 10, 80, "Hello", $navy);
imagettftext($img, 25, 60, 150, 150, $blue, "simkai.ttf", "Hello");
imagettftext($img, 12, -60, 50, 150, $green, "simli.ttf", "Nihao");
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328163147

总结

以上我们总结了 PHP 图像绘图的相关函数的使用,在后面我们将应用于实际,比如验证码和图像水印操作中。 希望对大家有帮助!

HTML

综述

CSS3 已经变得非常流行,原本的 CSS 不支持自定义字体,但是传说中的 CSS3 基本上什么都可以,那么 CSS3 中可不可以自定义英文字体呢?这里我们就一起来感受一下。

语法规则

1
2
3
4
5
6
7
8
9
10
11
@font-face {

  font-family: 自定义的字体名称;

  src: 自定义的字体的存放路径;

  font-weight: normal;是否为粗体

  font-style: normal;定义字体样式,如斜体

}

取值说明

font-famliy

此值指的就是你自定义的字体名称,最好是使用你下载的默认字体,他将被引用到你的 Web 元素中的 font-family。如“font-family:”YourWebFontName”;”

source

此值指的是你自定义的字体的存放路径,可以是相对路径也可以是绝路径;

format

此值指的是你自定义的字体的格式,主要用来帮助浏览器识别,其值主要有以下几种类型:truetype,opentype,truetype-aat,embedded-opentype,avg 等

weight 和 style

这两个值大家一定很熟悉,weight 定义字体是否为粗体,style 主要定义字体样式,如斜体

各个浏览器需要字体的格式

TureTpe(.ttf)

.ttf 字体是 Windows 和 Mac 的最常见的字体,是一种 RAW 格式,因此他不为网站优化,支持这种字体的浏览器有 IE9+,Firefox3.5+,Chrome4+,Safari3+,Opera10+,iOS Mobile Safari4.2+

OpenType(.otf)

.otf 字体被认为是一种原始的字体格式,其内置在 TureType 的基础上,所以也提供了更多的功能,支持这种字体的浏览器有 Firefox3.5+,Chrome4.0+,Safari3.1+,Opera10.0+,iOS Mobile Safari4.2+

Web Open Font Format(.woff)

.woff 字体是 Web 字体中最佳格式,他是一个开放的 TrueType/OpenType 的压缩版本,同时也支持元数据包的分离,支持这种字体的浏览器有 IE9+,Firefox3.5+,Chrome6+,Safari3.6+,Opera11.1+

Embedded Open Type(.eot)

.eot 字体是 IE 专用字体,可以从 TrueType 创建此格式字体,支持这种字体的浏览器有 IE4+

SVG(.svg)

.svg 字体是基于 SVG 字体渲染的一种格式,支持这种字体的浏览器有 Chrome4+,Safari3.1+,Opera10.0+,iOS Mobile Safari3.2+ 所以,@font-face 中我们至少需要.woff,.eot 两种格式字体,甚至还需要.svg 等字体达到更多种浏览版本的支持。

综合写法

1
2
3
4
5
6
7
8
9
10
11
12
13
 @font-face {
    font-family: 'YourWebFontName';
    /* IE9 Compat Modes */
    src: url('YourWebFontName.eot');
    /* IE6-IE8 */
    src: url('YourWebFontName.eot?#iefix') format('embedded-opentype'),
        /* Modern Browsers */
        url('YourWebFontName.woff') format('woff'),
        /* Safari, Android, iOS */
        url('YourWebFontName.ttf'format('truetype'),
        /* Legacy iOS */
        url('YourWebFontName.svg#YourWebFontName') format('svg');
}

获取字体

在这里介绍一个网站,叫做 fontsquirrel

在这里,你可以通过上传你的字体,来获取上面四种格式的字体文件。

20150327005358

我们点击按钮 UPLOAD FONTS,选择本地的字体文件,然后网站就会为我们生成上述格式的字体文件,勾选 Agreement,然后直接点击下载即可,DOWNLOAD YOUR KIT。

20150327005550

比如我上传的字体名叫做 FuturaICG-Light,那么下载之后的文件目录就如下

20150327010127

其中,这个目录下给我们生成了一个 demo,可以用浏览器打开 html 后缀的文件,预览一下 demo 是怎么写的。

应用字体

如果我们要用,就把五个字体文件复制一下,复制到项目目录里。

20150327010641

然后在样式表 css 中加入如下代码即可生效啦,这个代码在 demo 的 stylesheet 文件中,我们直接复制即可,比如我的便是

1
2
3
4
5
6
7
8
9
10
11
@font-face {
font-family: 'futuraicg_lightregular';
src: url('FuturaICG-Light-webfont.eot');
src: url('FuturaICG-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('FuturaICG-Light-webfont.woff2') format('woff2'),
url('FuturaICG-Light-webfont.woff') format('woff'),
url('FuturaICG-Light-webfont.ttf') format('truetype'),
url('FuturaICG-Light-webfont.svg#futuraicg_lightregular') format('svg');
font-weight: normal;
font-style: normal;
}

在这里要注意路径问题,如果 css 在字体的上级目录,那么就要在前面加上字体文件夹的名称,我想大家都能理解。 刷新一下页面,我们可以发现页面的字体效果就已经生效啦。 如果有不生效的地方,很可能是 CSS 表中设置了 html 或者 body 的 font-family 样式,在这里我们只需要把它们去掉即可。如图所示,把改行删掉即可。 20150327011119

这时,如果还不行,请检查路径设置。 以上就是我们用 CSS3 来自定义网页字体的方法,希望对大家有帮助。

Other

在远程主机上,我开启了 mysql 服务,用 phpmyadmin 可以打开,比如说用户名为 root,密码为 123456。不过用 Mysql 客户端远程连接时却报了错误,比如 Mysql-Front 报了如下错误。 Access denied for user ‘root’@’121.42.8.33’(using password:YES) 20150327004005 比较奇怪,phpmyadmin 可以正常访问,而 Mysql-Front 为什么无法连接呢?可能的原因,应该就是 IP 限制了,phpmyadmin 在连接时使用的是 localhost,而我们访问页面才使用的远程主机的 IP,而 Mysql-Front 连接的是远程主机。 解决方法如下, 首先修改 mysql 的配置文件,my.cnf,将

1
#bind-address = 127.0.0.1

这一行注释掉,要不然它永远限制了只能本机连接 然后我们需要新建一个用户,然后授予所有 IP 可以访问的权限就好啦。 在下面的 sql 语句中,username 即为用户名,password 为你要设置的密码。

1
2
3
4
5
6
7
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

GRANT ALL PRIVILEGES ON *.* TO 'username'@'localhost' WITH GRANT OPTION;

CREATE USER 'username'@'%' IDENTIFIED BY 'password';

GRANT ALL PRIVILEGES ON *.* TO 'username'@'%' WITH GRANT OPTION;

通过执行以上语句,便创建了一个用户名为 username,密码为 password 的新账户,再用新账号登录,就可以连接成功啦。

PHP

搞WEB开发,PHP后台当然少不了,PHP的高级用法虽然不一定用到,但是作为WEB开发人员,是必须要了解的。在这里,博主把自己学习的一些高级特性总结如下,希望对大家有帮助。

PHP高级特性总结

1. PHP高级特性一之正则表达式用法 2. PHP高级特性二之文件处理 3. PHP高级特性三之文件上传和下载 4. PHP高级特性四之SMTP邮件发送 5. PHP高级特性五之时间处理 6. PHP高级特性六之图像处理 7. PHP高级特性七之验证码操作 随着学习的进行,会不断更新,希望对大家有帮助!

PHP

综述

PHP中的时间处理函数是非常常用的,在这里我们介绍的主要内容有

UNIX时间戳

以整数表示格林威治标准时间,英文叫做 timestamp,例如 11230499325,它在UNIX系统中是以32位存储的。从协调世界时1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。 作用:方便我们计算使用(参于运算)。

获取时间函数

1.int time ( void )

返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数,即返回的就是UNIX时间戳。

1
2
3
<?php 
echo time();
?>
1
 1426741342

2.array getdate ( [int timestamp] )/array getDate ( [int timestamp] )

可以通过传入时间戳获得时间,也可以不传参数获取时间。例如下面的例子获取了当前时间,返回类型是一个数组。

1
2
3
4
5
<?php 
echo "<pre>";
print_r(getDate()); //或者getdate();
echo "</pre>";
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array
(
[seconds] => 4
[minutes] => 6
[hours] => 5
[mday] => 19
[wday] => 4
[mon] => 3
[year] => 2015
[yday] => 77
[weekday] => Thursday
[month] => March
[0] => 1426741564
)

数组各个元素的说明如下

键名

说明

返回值例子

“seconds”

秒的数字表示

0 到 59

“minutes”

分钟的数字表示

0 到 59

“hours”

小时的数字表示

0 到 23

“mday”

月份中第几天的数字表示

1 到 31

“wday”

星期中第几天的数字表示

0(表示星期天)到 6(表示星期六)

“mon”

月份的数字表示

1 到 12

“year”

4 位数字表示的完整年份

例如:1999 或 2003

“yday”

一年中第几天的数字表示

0 到 365

“weekday”

星期几的完整文本表示

Sunday 到 Saturday

“month”

月份的完整文本表示

January> 到 December

可以传入时间戳参数,如下所示

1
2
3
4
5
6
<?php 
echo "<pre>";
print_r(getDate(time()+60*60*24));
print_r(getdate(time()));
echo "</pre>";
?>

时间戳转日期

1.string date ( string format [, int timestamp] )

返回将整数 timestamp 按照给定的格式字串而产生的字符串。如果没有给出时间戳则使用本地当前时间。换句话说,timestamp 是可选的,默认值为 time()。

1
2
3
4
5
<?php
echo date("Y-m-d H:i:s")."<br>";
echo date("Y-m-d H:i:s",time())."<br>";
echo date("Y-m-d H:i:s",time()+60*60*24)."<br>";
?>
1
2
3
2015-03-19 05:45:49
2015-03-19 05:45:49
2015-03-20 05:45:49

下面是格式化的字符表

format 字符

说明

返回值例子

-—

-—

d

月份中的第几天,有前导零的 2 位数字

01 到 31

D

星期中的第几天,文本表示,3 个字母

Mon 到 Sun

j

月份中的第几天,没有前导零

1 到 31

l(“L”的小写字母)

星期几,完整的文本格式

Sunday 到 Saturday

N

ISO-8601 格式数字表示的星期中的第几天(PHP 5.1.0 新加)

1(表示星期一)到 7(表示星期天)

S

每月天数后面的英文后缀,2 个字符

st,nd,rd 或者 th。可以和 j 一起用

w

星期中的第几天,数字表示

0(表示星期天)到 6(表示星期六)

z

年份中的第几天

0 到 366

星期

-—

-—

W

ISO-8601 格式年份中的第几周,每周从星期一开始(PHP 4.1.0 新加的)

例如:42(当年的第 42 周)

-—

-—

F

月份,完整的文本格式,例如 January 或者 March

January 到 December

m

数字表示的月份,有前导零

01 到 12

M

三个字母缩写表示的月份

Jan 到 Dec

n

数字表示的月份,没有前导零

1 到 12

t

给定月份所应有的天数

28 到 31

-—

-—

L

是否为闰年

如果是闰年为 1,否则为 0

o

ISO-8601 格式年份数字。这和 Y 的值相同,只除了如果 ISO 的星期数(W)属于前一年或下一年,则用那一年。(PHP 5.1.0 新加)

Examples: 1999 or 2003

Y

4 位数字完整表示的年份

例如:1999 或 2003

y

2 位数字表示的年份

例如:99 或 03

时间

-—

-—

a

小写的上午和下午值

am 或 pm

A

大写的上午和下午值

AM 或 PM

B

Swatch Internet 标准时

000 到 999

g

小时,12 小时格式,没有前导零

1 到 12

G

小时,24 小时格式,没有前导零

0 到 23

h

小时,12 小时格式,有前导零

01 到 12

H

小时,24 小时格式,有前导零

00 到 23

i

有前导零的分钟数

00 到 59>

s

秒数,有前导零

00 到 59>

时区

-—

-—

e

时区标识(PHP 5.1.0 新加)

例如:UTC,GMT,Atlantic/Azores

I

是否为夏令时

如果是夏令时为 1,否则为 0

O

与格林威治时间相差的小时数

例如:+0200

T

本机所在的时区

例如:EST,MDT(【译者注】在 Windows 下为完整文本格式,例如“Eastern Standard Time”,中文版会显示“中国标准时间”)。

Z

时差偏移量的秒数。UTC 西边的时区偏移量总是负的,UTC 东边的时区偏移量总是正的。

-43200 到 43200

完整的日期/时间

-—

-—

c

ISO 8601 格式的日期(PHP 5 新加)

2004-02-12T15:19:21+00:00

r

RFC 822 格式的日期

例如:Thu, 21 Dec 2000 16:01:07 +0200

U

从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数

参见 time()

日期转时间戳

1.int mktime ( [int hour [, int minute [, int second [, int month [, int day [, int year [, int is_dst]]]]]]] )

根据给出的参数返回 Unix 时间戳。时间戳是一个长整数,包含了从 Unix 纪元(January 1 1970 00:00:00 GMT)到给定时间的秒数。参数可以从右向左省略,任何省略的参数会被设置成本地日期和时间的当前值。 例如我们获取1997年12月1日的时间戳

1
2
3
<?php
echo mktime(0, 0, 0, 12, 1, 1997)."<br>";
?>
1
 880934400

在这里如果我们的年份如果填2位数,函数可以智能给我们转化,例如第六个参数填 97,那么则会识别为 1997年,如果填 03,那么则会识别为 2003年。那么有的小伙伴就问了,两个之间的分界点是哪一年哪一月哪一日呢?我们来看下面的例子

1
2
3
4
5
6
7
<?php
echo date("m-d-Y", mktime(0, 0, 0, 12, 32, 1997))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 3, 14, 98))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 1, 03))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 19, 38))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 20, 38))."<br>";
?>
1
2
3
4
5
01-01-1998
Mar-14-1998
Jan-01-2003
Jan-19-2038
Jan-01-1970

在这里我们可以发现,当我们输入2038年的1月19日时,可以正常识别为2038-1-19,不过当我们输入2038年1月20日时,则识别成了1970-1-1,可见分界点就在2038-1-19这一天。 在这里有个有趣的2038年问题,扩展阅读一下

在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。这种时间表示法在类Unix(Unix-like)操作系统上是一个标准,并会影响以其C编程语言开发给其他大部份操作系统使用的软件。在大部份的32位操作系统上,此“time_t”数据模式使用一个有正负号的32位元整数(signedint32)存储计算的秒数。依照此“time_t”标准,在此格式能被表示的最后时间是2038年1月19日03:14:07,星期二(UTC)。超过此一瞬间,时间将会被掩盖(wrap around)且在内部被表示为一个负数,并造成程序无法工作,因为它们无法将此时间识别为2038年,而可能会依个别实作而跳回1970年或1901年。错误的计算及动作可能因此产生。

修改PHP默认时区

我们来看下面一个例子,是上面所说的调用date方法返回的时间。

1
2
3
<?php
echo date("Y-m-d H:i:s");
?>
1
2015-03-19 06:03:22

而现在我的电脑系统时间为

1
2015-03-19 14:03:22

这是什么原因?很简单,PHP程序中返回的是中时区的格林威治时间,而我们国家使用的是东八区区时,所以程序中的时间会慢8个小时。所以,我们只需要设置一下时区即可。

1.设置PHP.ini文件

这种方法是通用的设置方法,一次设置,其他所有编写的PHP文件都会生效,而且不需要在程序中设置了。只需要修改

1
2
3
4
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = PRC

默认的 date.timezone = UTC,在这里我们修改为中国的代号PRC即可。修改完之后记得重启服务器才能生效。 再运行程序,发现时间已经很准确了。

2.在程序中设置

1
2
3
4
5
<?php
date_default_timezone_set("PRC");
date_default_timezone_set("Asia/Shanghai");
date_default_timezone_set("Gtc/GET-8");
?>

上面三行语句均可使用,效果相同。第一个是设置中国时区,第二个是设置上海时区,第三个是设置东八区区时。 在此附亚洲区时

Asia/Aden

Asia/Almaty

Asia/Amman

Asia/Anadyr

Asia/Aqtau

Asia/Aqtobe

Asia/Ashgabat

Asia/Ashkhabad

Asia/Baghdad

Asia/Bahrain

Asia/Baku

Asia/Bangkok

Asia/Beirut

Asia/Bishkek

Asia/Brunei

Asia/Calcutta

Asia/Chita

Asia/Choibalsan

Asia/Chongqing

Asia/Chungking

Asia/Colombo

Asia/Dacca

Asia/Damascus

Asia/Dhaka

Asia/Dili

Asia/Dubai

Asia/Dushanbe

Asia/Gaza

Asia/Harbin

Asia/Hebron

Asia/Ho_Chi_Minh

Asia/Hong_Kong

Asia/Hovd

Asia/Irkutsk

Asia/Istanbul

Asia/Jakarta

Asia/Jayapura

Asia/Jerusalem

Asia/Kabul

Asia/Kamchatka

Asia/Karachi

Asia/Kashgar

Asia/Kathmandu

Asia/Katmandu

Asia/Khandyga

Asia/Kolkata

Asia/Krasnoyarsk

Asia/Kuala_Lumpur

Asia/Kuching

Asia/Kuwait

Asia/Macao

Asia/Macau

Asia/Magadan

Asia/Makassar

Asia/Manila

Asia/Muscat

Asia/Nicosia

Asia/Novokuznetsk

Asia/Novosibirsk

Asia/Omsk

Asia/Oral

Asia/Phnom_Penh

Asia/Pontianak

Asia/Pyongyang

Asia/Qatar

Asia/Qyzylorda

Asia/Rangoon

Asia/Riyadh

Asia/Saigon

Asia/Sakhalin

Asia/Samarkand

Asia/Seoul

Asia/Shanghai

Asia/Singapore

Asia/Srednekolymsk

Asia/Taipei

Asia/Tashkent

Asia/Tbilisi

Asia/Tehran

Asia/Tel_Aviv

Asia/Thimbu

Asia/Thimphu

Asia/Tokyo

Asia/Ujung_Pandang

Asia/Ulaanbaatar

Asia/Ulan_Bator

Asia/Urumqi

Asia/Ust-Nera

Asia/Vientiane

Asia/Vladivostok

Asia/Yakutsk

Asia/Yekaterinburg

Asia/Yerevan

微妙精确计算

1.mixed microtime ( [bool get_as_float] )

microtime() 当前 Unix 时间戳以及微秒数。本函数仅在支持 gettimeofday() 系统调用的操作系统下可用。 如果调用时不带可选参数,本函数以 “msec sec” 的格式返回一个字符串,其中 sec 是自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数,msec 是微秒部分。字符串的两部分都是以秒为单位返回的。如果传入true参数,那么则会返回sec.msec的形式,是一个浮点数。 我们大多数用来计算程序执行时间。

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
<?php
class Timer {

private $startTime; //开始时间
private $stopTime; //结束时间
private $count = 0;

//初始化两个时间为0
function __construct(){
$this->startTime=0;
$this->stopTime=0;
}

//设置开始时间
function start(){
$this->startTime=microtime(true);
echo $this->startTime."<br>";
}

//设置结束时间
function stop(){
$this->stopTime=microtime(true);
echo $this->stopTime."<br>";
}

//计算过了多长时间
function spent(){
$this->start();
$this->dosomething();
$this->stop();
return ($this->stopTime - $this->startTime);
}

//程序执行某些任务
function dosomething(){
for($i=0; $i<10000; $i++)
{
$this->count ++;
}
}
}

$timer = new Timer();

echo $timer->spent();

?>
1
2
3
1426746064.4247
1426746064.4267
0.0019998550415039

可以发现,程序执行用了 1.99 微秒。 好,关于PHP中的时间问题,我们就介绍到这里,希望对大家有帮助。

PHP

综述

PHP的邮件发送最常见的便是SMTP,通过编写一个Smtp类,设置好smtp服务器,邮箱用户名,密码,即可实现邮件的发送

邮件发送

邮件发送的类如下,文件名叫做

1
email.class.php
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<?php

/* smtp class */
class smtp {
/* Public Variables */
var $smtp_port;
var $time_out;
var $host_name;
var $log_file;
var $relay_host;
var $debug;
var $auth;
var $user;
var $pass;
/* Private Variables */
var $sock;
/* Constractor */
function smtp($relay_host = "", $smtp_port = 25, $auth = false, $user, $pass) {
$this->debug = FALSE;
$this->smtp_port = $smtp_port;
$this->relay_host = $relay_host;
$this->time_out = 30; //is used in fsockopen()
//
$this->auth = $auth; //auth
$this->user = $user;
$this->pass = $pass;
//
$this->host_name = "localhost"; //is used in HELO command
$this->log_file = "";
$this->sock = FALSE;
}
/* Main Function */
function sendmail($to, $from, $subject = "", $body = "", $mailtype, $cc = "", $bcc = "", $additional_headers = "") {
$mail_from = $this->get_address($this->strip_comment($from));
$body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body);
$header = "MIME-Version:1.0\r\n";
if ($mailtype == "HTML") {
$header.= "Content-Type:text/html\r\n";
}
$header.= "To: " . $to . "\r\n";
if ($cc != "") {
$header.= "Cc: " . $cc . "\r\n";
}
$header.= "From: $from<" . $from . ">\r\n";
$header.= "Subject: " . $subject . "\r\n";
$header.= $additional_headers;
$header.= "Date: " . date("r") . "\r\n";
$header.= "X-Mailer:By Redhat (PHP/" . phpversion() . ")\r\n";
list($msec, $sec) = explode(" ", microtime());
$header.= "Message-ID: <" . date("YmdHis", $sec) . "." . ($msec * 1000000) . "." . $mail_from . ">\r\n";
$TO = explode(",", $this->strip_comment($to));
if ($cc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($cc)));
}
if ($bcc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($bcc)));
}
$sent = TRUE;
foreach ($TO as $rcpt_to) {
$rcpt_to = $this->get_address($rcpt_to);
if (!$this->smtp_sockopen($rcpt_to)) {
$this->log_write("Error: Cannot send email to " . $rcpt_to . "\n");
$sent = FALSE;
continue;
}
if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) {
$this->log_write("E-mail has been sent to <" . $rcpt_to . ">\n");
} else {
$this->log_write("Error: Cannot send email to <" . $rcpt_to . ">\n");
$sent = FALSE;
}
fclose($this->sock);
$this->log_write("Disconnected from remote host\n");
}
return $sent;
}
/* Private Functions */
function smtp_send($helo, $from, $to, $header, $body = "") {
if (!$this->smtp_putcmd("HELO", $helo)) {
return $this->smtp_error("sending HELO command");
}
//auth
if ($this->auth) {
if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) {
return $this->smtp_error("sending HELO command");
}
if (!$this->smtp_putcmd("", base64_encode($this->pass))) {
return $this->smtp_error("sending HELO command");
}
}
//
if (!$this->smtp_putcmd("MAIL", "FROM:<" . $from . ">")) {
return $this->smtp_error("sending MAIL FROM command");
}
if (!$this->smtp_putcmd("RCPT", "TO:<" . $to . ">")) {
return $this->smtp_error("sending RCPT TO command");
}
if (!$this->smtp_putcmd("DATA")) {
return $this->smtp_error("sending DATA command");
}
if (!$this->smtp_message($header, $body)) {
return $this->smtp_error("sending message");
}
if (!$this->smtp_eom()) {
return $this->smtp_error("sending <CR><LF>.<CR><LF> [EOM]");
}
if (!$this->smtp_putcmd("QUIT")) {
return $this->smtp_error("sending QUIT command");
}
return TRUE;
}
function smtp_sockopen($address) {
if ($this->relay_host == "") {
return $this->smtp_sockopen_mx($address);
} else {
return $this->smtp_sockopen_relay();
}
}
function smtp_sockopen_relay() {
$this->log_write("Trying to " . $this->relay_host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Error: Cannot connenct to relay host " . $this->relay_host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
return FALSE;
}
$this->log_write("Connected to relay host " . $this->relay_host . "\n");
return TRUE;;
}
function smtp_sockopen_mx($address) {
$domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);
if (!@getmxrr($domain, $MXHOSTS)) {
$this->log_write("Error: Cannot resolve MX \"" . $domain . "\"\n");
return FALSE;
}
foreach ($MXHOSTS as $host) {
$this->log_write("Trying to " . $host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Warning: Cannot connect to mx host " . $host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
continue;
}
$this->log_write("Connected to mx host " . $host . "\n");
return TRUE;
}
$this->log_write("Error: Cannot connect to any mx hosts (" . implode(", ", $MXHOSTS) . ")\n");
return FALSE;
}
function smtp_message($header, $body) {
fputs($this->sock, $header . "\r\n" . $body);
$this->smtp_debug("> " . str_replace("\r\n", "\n" . "> ", $header . "\n> " . $body . "\n> "));
return TRUE;
}
function smtp_eom() {
fputs($this->sock, "\r\n.\r\n");
$this->smtp_debug(". [EOM]\n");
return $this->smtp_ok();
}
function smtp_ok() {
$response = str_replace("\r\n", "", fgets($this->sock, 512));
$this->smtp_debug($response . "\n");
if (!preg_match("/^[23]/", $response)) {
fputs($this->sock, "QUIT\r\n");
fgets($this->sock, 512);
$this->log_write("Error: Remote host returned \"" . $response . "\"\n");
return FALSE;
}
return TRUE;
}
function smtp_putcmd($cmd, $arg = "") {
if ($arg != "") {
if ($cmd == "") $cmd = $arg;
else $cmd = $cmd . " " . $arg;
}
fputs($this->sock, $cmd . "\r\n");
$this->smtp_debug("> " . $cmd . "\n");
return $this->smtp_ok();
}
function smtp_error($string) {
$this->log_write("Error: Error occurred while " . $string . ".\n");
return FALSE;
}
function log_write($message) {
$this->smtp_debug($message);
if ($this->log_file == "") {
return TRUE;
}
$message = date("M d H:i:s ") . get_current_user() . "[" . getmypid() . "]: " . $message;
if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) {
$this->smtp_debug("Warning: Cannot open log file \"" . $this->log_file . "\"\n");
return FALSE;;
}
flock($fp, LOCK_EX);
fputs($fp, $message);
fclose($fp);
return TRUE;
}
function strip_comment($address) {
$comment = "/\([^()]*\)/";
while (preg_match($comment, $address)) {
$address = preg_replace($comment, "", $address);
}
return $address;
}
function get_address($address) {
$address = preg_replace("/([ \t\r\n])+/", "", $address);
$address = preg_replace("/^.*<(.+)>.*$/", "\1", $address);
return $address;
}
function smtp_debug($message) {
if ($this->debug) {
echo $message;
}
}
}
?>

然后便是一个php文件引用该文件,设置好各种参数

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
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>发送状态</title>
</head>
<body>
<?php

require_once "email.class.php";
//SMTP服务器
$smtpserver = "smtp.163.com";
//SMTP端口号?
$smtpserverport = 25;
//SMTP发邮件的邮箱
$smtpusermail = "cqc@163.com";
//发给谁
$smtpemailto = $_POST['toemail'];
//SMTP用户名,不加@163.com
$smtpuser = "cqc";
//SMTP用户密码
$smtppass = "wtf";
//主题
$mailtitle = $_POST['topic'];
//构建内容
$mailcontent = "TA的名字 ".$_POST['name']."<br>内容: ".$_POST['content'];
//邮件内容为HTML格式
$mailtype = "HTML";
//实例化对象
$smtp = new smtp($smtpserver,$smtpserverport,true,$smtpuser,$smtppass);
//关闭调试信息
$smtp->debug = false;
//发送邮件
$state = $smtp->sendmail($smtpemailto, $smtpusermail, $mailtitle, $mailcontent, $mailtype);
//检查发送状态
if($state==""){
echo "邮件发送失败,请检查密码或其他设置";
}else if(strlen($state)!=0){
//发送成功
echo "邮件发送成功";
}else{
echo "未知错误";
}

?>
</body>
</html>

最后是一个表单提交,包括目的邮箱,主题,内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PHP利用SMTP发送邮件范例</title>
</head>
<body>
<form action="sendmail.php" method="post">
<p>收件人:<input type="text" name="toemail" /></p>
<p>&nbsp;&nbsp;题:<input type="text" name="topic" /></p>
<p>&nbsp;&nbsp;名:<input type="text" name="name" /></p>
<p>&nbsp;&nbsp;容:<textarea name="content" cols="50" rows="5"></textarea></p>
<p><input type="submit" value="发送" /></p>
</form>
</body>
</html>

代码下载

代码下载

个人随笔

博主来自山东大学,现在大三了,想保研北京航空航天大学。去年的保研政策,其中复试是这么规定的

北京航空航天大学计算机学院 1、学院复试工作小组对申请者各项材料(含体格检查表及推免服务系统填写的网上报名志愿)进行综合评审,确定复试名单,通过“推免服务系统”发放复试通知。接到“推免服务系统”复试通知的申请者,须通过“推免服务系统”接受学院的复试通知。学院将对接受复试通知的申请者组织复试,申请者复试时需交纳复试费100元/人。 2、.复试形式:采取差额复试。复试内容包括C语言上机考试和综合面试两部分,C语言上机考试为资格考试,通过上机考试后方可进入综合面试环节,面试总成绩300分。综合面试内容包括英语能力、数理基础、专业素质、综合素质等四个方面的内容。 ⑴ 英语能力方面主要考核内容包括:口语、听力、现场阅读与翻译; ⑵ 数理基础方面主要考核内容包括:本专业研究方向应该掌握的数学基础理论(或离散数学,或其他基础理论知识); ⑶ 专业素质方面主要考核内容包括:专业基础知识和专业综合能力。专业基础知识考核专业知识问题,考生;专业综合能力主要考核考生综合运用所学知识解决计算机领域具体应用问题(或设计问题,或工程问题)的能力,以考核考生的分析能力和专业综合能力。 ⑷ 综合素质方面主要考核内容包括:语言表达能力、逻辑思维能力、对学科热点的关注和了解情况、考生本科阶段的专业背景、本科阶段参与的各类科技活动以及获得过的各种奖励、考生的行为举止、特点特长、综合印象等。

看完之后,心中生出淡淡的忧伤,上机考试竟然是C语言,上机题目估计和ACM题目差不多,接下来要重拾C语言了。 另外,对于英语,也是比较看重的,该下点功夫学习英语了,本来英语不算太差,不过也毕竟有一年多没系统学习英语了,希望这几个月可以有些提高。 还有很重要的就是联系北航的导师,如果小伙伴们知道有优秀的导师,可以推荐给我,感激不尽~ 加油吧,崔庆才,为了将来的辉煌!拼一把又何妨!

JavaScript

综述

原创内容,个人学习记录,部分API来自W3C,本篇主要介绍了JavaScript中常见的内置对象及其相关用法。

Date

日期对象,它没有专有属性,有一系列相关方法。

1.三种创建方式

1
2
3
date = new Date();&nbsp;                                //直接创建
date = new Date(val); //指定日期创建
date = new Date(Y,m,d[,h[,min[,second[,ms]]]]) //传入具体时间创建

2.API

方法

描述

Date()

返回当日的日期和时间。

getDate()

从 Date 对象返回一个月中的某一天 (1 ~ 31)。

getDay()

从 Date 对象返回一周中的某一天 (0 ~ 6)。注:0-6分别表示星期天-星期六。

getMonth()

从 Date 对象返回月份 (0 ~ 11)。注:0-11分别表示1-12月份,其值总是比实际月份小1。

getFullYear()

从 Date 对象以四位数字返回年份。

getYear()

请使用 getFullYear() 方法代替。

getHours()

返回 Date 对象的小时 (0 ~ 23)。

getMinutes()

返回 Date 对象的分钟 (0 ~ 59)。

getSeconds()

返回 Date 对象的秒数 (0 ~ 59)。

getMilliseconds()

返回 Date 对象的毫秒(0 ~ 999)。

getTime()

返回 1970 年 1 月 1 日至今的毫秒数。

getTimezoneOffset()

返回本地时间与格林威治标准时间 (GMT) 的分钟差。

getUTCDate()

根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。

getUTCDay()

根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。

getUTCMonth()

根据世界时从 Date 对象返回月份 (0 ~ 11)。

getUTCFullYear()

根据世界时从 Date 对象返回四位数的年份。

getUTCHours()

根据世界时返回 Date 对象的小时 (0 ~ 23)。

getUTCMinutes()

根据世界时返回 Date 对象的分钟 (0 ~ 59)。

getUTCSeconds()

根据世界时返回 Date 对象的秒钟 (0 ~ 59)。

getUTCMilliseconds()

根据世界时返回 Date 对象的毫秒(0 ~ 999)。

parse()

返回1970年1月1日午夜到指定日期(字符串)的毫秒数。

setDate()

设置 Date 对象中月的某一天 (1 ~ 31)。

setMonth()

设置 Date 对象中月份 (0 ~ 11)。

setFullYear()

设置 Date 对象中的年份(四位数字)。

setYear()

请使用 setFullYear() 方法代替。

setHours()

设置 Date 对象中的小时 (0 ~ 23)。

setMinutes()

设置 Date 对象中的分钟 (0 ~ 59)。

setSeconds()

设置 Date 对象中的秒钟 (0 ~ 59)。

setMilliseconds()

设置 Date 对象中的毫秒 (0 ~ 999)。

setTime()

以毫秒设置 Date 对象。

setUTCDate()

根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。

setUTCMonth()

根据世界时设置 Date 对象中的月份 (0 ~ 11)。

setUTCFullYear()

根据世界时设置 Date 对象中的年份(四位数字)。

setUTCHours()

根据世界时设置 Date 对象中的小时 (0 ~ 23)。

setUTCMinutes()

根据世界时设置 Date 对象中的分钟 (0 ~ 59)。

setUTCSeconds()

根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。

setUTCMilliseconds()

根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。

toSource()

返回该对象的源代码。

toString()

把 Date 对象转换为字符串。

toTimeString()

把 Date 对象的时间部分转换为字符串。

toDateString()

把 Date 对象的日期部分转换为字符串。

toGMTString()

请使用 toUTCString() 方法代替。

toUTCString()

根据世界时,把 Date 对象转换为字符串。

toLocaleString()

根据本地时间格式,把 Date 对象转换为字符串。

toLocaleTimeString()

根据本地时间格式,把 Date 对象的时间部分转换为字符串。

toLocaleDateString()

根据本地时间格式,把 Date 对象的日期部分转换为字符串。

UTC()

根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。

valueOf()

返回 Date 对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<script>
date = new Date();
date.setMonth(10)
document.write(date.toDateString()+"<br>");
document.write(date.toTimeString()+"<br>");
document.write(date.toLocaleDateString()+"<br>");
document.write(date.toLocaleTimeString()+"<br>");
</script>
</body>
</html>
1
2
3
4
Mon Nov 16 2015
20:06:14 GMT+0800 (中国标准时间)
2015/11/16
下午8:06:14

Math

1.属性

E

返回算术常量 e,即自然对数的底数(约等于2.718)。

LN2

返回 2 的自然对数(约等于0.693)。

LN10

返回 10 的自然对数(约等于2.302)。

LOG2E

返回以 2 为底的 e 的对数(约等于 1.414)。

LOG10E

返回以 10 为底的 e 的对数(约等于0.434)。

PI

返回圆周率(约等于3.14159)。

SQRT1_2

返回返回 2 的平方根的倒数(约等于 0.707)。

SQRT2

返回 2 的平方根(约等于 1.414)。

2.API

方法

描述

abs(x)

返回数的绝对值。

acos(x)

返回数的反余弦值。

asin(x)

返回数的反正弦值。

atan(x)

以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值。

atan2(y,x)

返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间)。

ceil(x)

对数进行上舍入。

cos(x)

返回数的余弦。

exp(x)

返回 e 的指数。

floor(x)

对数进行下舍入。

log(x)

返回数的自然对数(底为e)。

max(x,y)

返回 x 和 y 中的最高值。

min(x,y)

返回 x 和 y 中的最低值。

pow(x,y)

返回 x 的 y 次幂。

random()

返回 0 ~ 1 之间的随机数。

round(x)

把数四舍五入为最接近的整数。

sin(x)

返回数的正弦。

sqrt(x)

返回数的平方根。

tan(x)

返回角的正切。

toSource()

返回该对象的源代码。

valueOf()

返回 Math 对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<script>
document.write(Math.E+"<br>");
document.write(Math.ceil(0.6)+"<br>");
document.write(Math.max(5,7) + "<br>");
document.write(Math.min(-3,5) + "<br>");
document.write(Math.random()+"<br>");
document.write(Math.round(0.49)+"<br>");
</script>
</body>
</html>
1
2
3
4
5
6
2.718281828459045
1
7
-3
0.6166561744175851
0

String

1.属性

constructor

对创建该对象的函数的引用

length

字符串的长度

prototype

允许您向对象添加属性和方法

2.方法

anchor()

创建 HTML 锚。

big()

用大号字体显示字符串。

blink()

显示闪动字符串。

bold()

使用粗体显示字符串。

charAt()

返回在指定位置的字符。

charCodeAt()

返回在指定的位置的字符的 Unicode 编码。

concat()

连接字符串。

fixed()

以打字机文本显示字符串。

fontcolor()

使用指定的颜色来显示字符串。

fontsize()

使用指定的尺寸来显示字符串。

fromCharCode()

从字符编码创建一个字符串。

indexOf()

检索字符串。

italics()

使用斜体显示字符串。

lastIndexOf()

从后向前搜索字符串。

link()

将字符串显示为链接。

localeCompare()

用本地特定的顺序来比较两个字符串。

match()

找到一个或多个正则表达式的匹配。

replace()

替换与正则表达式匹配的子串。

search()

检索与正则表达式相匹配的值。

slice()

提取字符串的片断,并在新的字符串中返回被提取的部分。

small()

使用小字号来显示字符串。

split()

把字符串分割为字符串数组。

strike()

使用删除线来显示字符串。

sub()

把字符串显示为下标。

substr()

从起始索引号提取字符串中指定数目的字符。

substring()

提取字符串中两个指定的索引号之间的字符。

sup()

把字符串显示为上标。

toLocaleLowerCase()

把字符串转换为小写。

toLocaleUpperCase()

把字符串转换为大写。

toLowerCase()

把字符串转换为小写。

toUpperCase()

把字符串转换为大写。

toSource()

代表对象的源代码。

toString()

返回字符串。

valueOf()

返回某个字符串对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<body>
<script>
var txt="Hello world!"
document.write(txt.anchor("myanchor")+"<br>");
document.write(txt.big()+"<br>");
document.write(txt.blink()+"<br>");
document.write(txt.small()+"<br>");
document.write(txt.sup()+"<br>");
var str1="Hello "
var str2="world!"
document.write(str1.concat(str2))
</script>
</body>
</html>
1
2
3
4
5
6
 Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

Array

1.创建方式

1
2
3
4
var a = new Array();
var a = new Array(1,2,3,4,5,6);
var a = [1,2,3,4,5];
var a = new Array(3);

2.属性

constructor

返回对创建此对象的数组函数的引用。

length

设置或返回数组中元素的数目。

prototype

使您有能力向对象添加属性和方法。

3.API

JavaScript Array 对象

concat()

连接两个或更多的数组,并返回结果。

join()

把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。

pop()

删除并返回数组的最后一个元素

push()

向数组的末尾添加一个或更多元素,并返回新的长度。

reverse()

颠倒数组中元素的顺序。

shift()

删除并返回数组的第一个元素

slice()

从某个已有的数组返回选定的元素

sort()

对数组的元素进行排序

splice()

删除元素,并向数组添加新元素。

toSource()

返回该对象的源代码。

toString()

把数组转换为字符串,并返回结果。

toLocaleString()

把数组转换为本地数组,并返回结果。

unshift()

向数组的开头添加一个或更多元素,并返回新的长度。

valueOf()

返回数组对象的原始值

3.Demo

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
<!DOCTYPE html>
<html>
<body>
<script>
var arr1 = new Array(1,2,3);
document.write(arr1.concat([6,7])+"<br>");
var arr2 = new Array(2);
arr2[0] = 'Hello';
arr2[1] = 'World';
document.write(arr2.join()+"<br>");
document.write(arr2.pop()+"<br>");
var arr = new Array(6);
arr[0] = "George";
arr[1] = "John";
arr[2] = "Thomas";
arr[3] = "James";
arr[4] = "Adrew";
arr[5] = "Martin";
document.write(arr + "<br />");
arr.splice(2,3,"William"); //删除index2开始的3个元素
document.write(arr+"<br>");
document.write(arr.valueOf());
</script>
</body>
</html>
1
2
3
4
5
6
1,2,3,6,7
Hello,World
World
George,John,Thomas,James,Adrew,Martin
George,John,William,Martin
George,John,William,Martin

s

Other

综述

博主原创内容。 在 PS 里,对于抠图,比较有技术含量的便是抠头发丝了,下面为大家带来一个比较详细的抠头发丝的教程。

素材准备

在这里我们用这张图片作为抠图素材,下面让我们一步步来演示抠图的过程,并为之更换背景。 3f5733ef0e9cd8dafeb910d866284128

抠图过程

1.打开素材

第一步,当然是用 PS 打开这张图片素材啦。 20150314235120

2.复制通道

打开通道选项卡,按每个通道的前面的小眼睛,让红绿蓝通道依次显示,观察哪一个通道明暗对比更加明显,在这里,我们发现蓝色通道的明暗对比更加明显,那我们就右键复制,建立蓝色通道的一个副本,如图所示。 20150314235402

3.调整色阶

打开图像-调整-色阶,将低色阶调高,高色阶调低,让图片的明暗对比更加明显,点击确定。 未标题-1 0150314235948

4.减淡加深

切换至减淡或加深工具,首先我们切换到减淡工具,范围选择高光,直接对图片的亮白部分进行涂抹,这时我们会发现比较亮的部分都被减淡为白色,如图所示。 20150315000249 然后切换到加深工具,范围选择阴影,直接涂抹人物,将人物全部涂抹成黑色,如图所示 20150315000438 20150315000519 20150315000617

5.画笔修补

这时我们会发现几乎所有的人物都已经变成了黑色,不过还有小瑕疵,有一小部分还没有变成黑色,没关系,切换到画笔工具,颜色选择黑色,让我们把没有变成黑色的部分进行填充。 20150315000838 20150315001006

6.反向检查

按 Ctrl+I,将图片切换为反向,将黑白部分没有填充好的地方重新用画笔工具填充一下,保证做到黑白分明。 20150315001156

7.抠出人物

按住 Ctrl 键,鼠标移至图层的小图标处点击,即图中红色框处的区域,点击一下,即可选中人物的轮廓。 20150315001334 将图层的 RGB 通道全部复原,切换至图层面板 20150315001556 20150315001638 按 Ctrl+J,复制选区中的内容到新的图层,这时我们会发现新建了一个图层,它的背景是透明的。(P.S.按第一次不生效可以尝试按第二次,我这边出现了一个副本,然后便是透明背景的图片) 20150315002534

8.更换背景

我们在该图层下面新建一个图层,填充为自己想要的颜色,看一下效果,在这里我设置了灰白渐变效果。 20150315002659

9.去除白边

选中人物图层,选择图层-修边-移除白色杂边,即可去除边缘的杂边干扰 20150315004324 这样一来,抠头发丝的完整过程就结束啦。

总结

以上便是抠头发丝的全部过程,如果做证件照的话,可以尝试一用,希望对大家有帮助

HTML

综述

纯原创内容,HTML5 实现个人简介界面,响应式布局的进度条以及利用 Font-Awesome 实现分享图标,完美支持手机端。 已经在博主个人网站应用 进入页面

截图

1.电脑端

20150313145032

2.手机端

20150313145528 20150313145629

在线演示

在线演示

源码下载

源码下载

HTML

综述

改编内容,HTML5 实现 2048 游戏的开发,添加了小屏幕适配,支持手机端触控。

截图

1.电脑端

20150313140648

2.平板端

20150313142906

3.手机端

20150313143200

在线演示

在线演示

源码下载

源码下载

HTML

综述

原创内容,纯 CSS3 实现微信扫一扫的功能,页面一共五个二维码,鼠标移上二维码会自动放大,同时支持响应式布局,整体会根据页面响应式缩放。 在我的个人网站赞助页面即存在这个 DEMO 进入页面

截图

1.正常情况,大屏幕

20150312235820

2.鼠标移上,大屏幕

20150312235953 3.小屏幕 20150313000056

在线演示

在线演示

源码下载

源码下载

JavaScript

关于JavaScript

JavaScript,一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML网页上使用,用来给HTML网页增加动态功能。然而现在JavaScript也可被用于网络服务器,如Node.js。 不同于服务器端脚本语言,例如PHP与ASP,JavaScript主要被作为客户端脚本语言在用户的浏览器上运行,不需要服务器的支持。所以在早期程序员比较青睐于JavaScript以减少对服务器的负担,而与此同时也带来另一个问题:安全性。而随着服务器的强壮,虽然现在的程序员更喜欢运行于服务端的脚本以保证安全,但JavaScript仍然以其跨平台、容易上手等优势大行其道。同时,有些特殊功能(如AJAX)必须依赖Javascript在客户端进行支持。随着引擎如V8和框架如Node.js的发展,及其事件驱动及异步IO等特性,JavaScript逐渐被用来编写服务器端程序。 视频来源:兄弟连教育 感谢 @兄弟连教育

关于兄弟连

兄弟连成立于2006年,专注于IT技术培训,是国内最早及最大的PHP/LAMP技术专业培训学校。 兄弟连现已开设PHP/Java/Android/IOS/手游/云计算/UI等多学科,累计培养逾万名学员,2014年学员就业平均起薪高达5500元+。 兄弟连已是第9个年头,这条路虽历尽艰辛,但我们痴心不改。我们就是想让学员们知道:不是所有的培训机构都是骗人的! 在兄弟连,你可以找到自我、重拾自信;在兄弟连,你会每天渴求成长,学到深夜; 在兄弟连,你把学习当成一种习惯;在兄弟连,你有更多的兄弟姐妹; 在兄弟连,有陪你一起熬夜的老师;在兄弟连,你会被“狠狠”的爱着…… 兄弟连已在北京、上海和广州设立校区,今后几年内将会陆续在成都、西安等地建设校区,每年有数十万名学员受益于兄弟连教育的职业培训、教学视频、网络公开课。

兄弟连

“我们不仅仅是老师,我们是学员的梦想守护者与职场引路人。” 我们不敢妄言改变中国教育,只是低下头认认真真做教育。兄弟连没有做什么惊天动地的大事,我们就是把别人不愿做的脏活累活做到极致,做教育就是需要这种工匠精神。 在中国,选择职业培训的学生,一定是对自己未来有憧憬、想改变命运的有志青年。主观上有学习的欲望,客观上自控能力差,需要外力协助其改变。 教学靠谱/变态严管/职业素养课我们的核心竞争力。 培训结束会有脱胎换骨的感觉,怕死别来兄弟连!

视频下载

视频下载