0%

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的插件,来实现图片的局部放大,让我们来感受一下

在线演示

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

插件文件

其中包含了两个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插件来帮助我们完成这件事情,让我们拭目以待吧

在线演示

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

插件文件

其中包含了两个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是一个用来验证表单提交的插件,应用十分广泛,具有如下的几个功能

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

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

插件下载

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

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

实例引入

我们先用一个小例子来感受一下使用 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高级特性七之验证码操作 随着学习的进行,会不断更新,希望对大家有帮助!