作品1 可爱小熊
1.1 需求分析
安装绘图库EasyX Graphics Library
绘制一个小熊形象,并且形象鲜明容易辨别
添加眼睛、鼻子、嘴巴等面部表情
为小熊设计可爱的动作
1.2 方案设计
首先在网上查找关于小熊头像的简笔画,分析小熊的特征:
小熊整体以棕黄色为主
小熊头型通常为椭圆形或圆形
小熊耳朵内外两层颜色略有差异
小熊鼻子为棕色或黑色
小熊眼睛为体现炯炯有神可以添加高光
小熊嘴巴为体现可爱的特点可以绘制成微笑的表情
小熊的爪子可以使用两个圆形简化
综上所述,可以使用基本的几何图形(椭圆、椭圆弧线)来构建小熊的各个部分,以及要考虑颜色搭配、明暗对比和线条处理,也可以添加动作(抱着爱心)使小熊看起来可爱生动,富有立体感。
1.3 代码实现
#include<bits/stdc++.h>
#include<graphics.h>
#include<conio.h>
#include<math.h>
// 绘制心形
void heart(int x0, int y0, int size, COLORREF C);
int main()
{
initgraph(800,800); //画布大小800*800
setbkcolor(RGB(244,220,234)); //背景颜色粉色
cleardevice(); //清空屏幕
setorigin(400,400); //重置原点
//一、画耳朵
//1.1 大耳朵阴影
setfillcolor(RGB(134,95,64));
solidellipse(-255,-242,-40,-43); //左
solidellipse(255,-242,40,-43); //右
//1.2 大耳朵
setfillcolor(RGB(154,115,84));
solidellipse(-250,-240,-40,-50); //左
solidellipse(250,-240,40,-50); //右
//1.3 小耳朵阴影
setfillcolor(RGB(186,149,108));
solidellipse(-210,-200,-72,-71); //左
solidellipse(210,-200,72,-71); //右
//1.4 小耳朵
setfillcolor(RGB(205,178,151));
solidellipse(-205,-195,-70,-75); //左
solidellipse(205,-195,70,-75); //右
//二、画脸
//2.1 画大脸阴影
setfillcolor(RGB(134,95,64));
solidroundrect(-218,-200,218,168,400,400);
//2.2 画大脸
setfillcolor(RGB(154,115,84));
solidroundrect(-215,-200,215,160,400,400);
//2.3 画小脸阴影
setfillcolor(RGB(186,149,108));
solidellipse(-114,-20,114,146);
//2.4 画小脸
setfillcolor(RGB(205,178,151));
solidellipse(-110,-20,110,140);
//三、画脸部细节
//3.1 画眼睛
setfillcolor(RGB(35,31,32)); //眼睛
solidroundrect(-110,-90,-50,-30,200,200); //左
solidroundrect(110,-90,50,-30,200,200); //右
setfillcolor(WHITE); //高光
solidellipse(-98,-78,-83,-63); //左
solidellipse(98,-78,83,-63); //右
solidellipse(-72,-50,-65,-43); //左
solidellipse(72,-50,65,-43); //右
//3.2 画鼻子
setfillcolor(RGB(186,149,108)); //阴影
solidroundrect(-50,-25,50,25,100,100);
setfillcolor(RGB(61,35,20)); //鼻子
solidroundrect(-50,-30,50,22,100,100);
setfillcolor(WHITE); //高光
solidroundrect(-20,-20,20,-5,100,100);
//3.3 画嘴
setlinecolor(RGB(61,35,20));
setlinestyle(PS_SOLID,5); //线宽
line(0,0,0,60); //人中
double PI=3.1415926;
arc(-70,30,0,80,PI*3/2,PI*2); //左嘴角
arc(70,30,0,80,PI,PI*3/2); //右嘴角
//四、身体
//4.1 画爱心
heart(0,130,50,RED);
setfillcolor(RED);
floodfill(0,150,RED);
//4.2 画手
setfillcolor(RGB(134,95,64)); //阴影
solidellipse(-200,90,-90,205); //左
solidellipse(200,90,90,205); //右
setfillcolor(RGB(154,115,84)); //手
setlinecolor(RGB(134,95,64));
setlinestyle(PS_SOLID,2); //线宽
fillellipse(-200,90,-90,200); //左
fillellipse(200,90,90,200); //右
_getch();
return 0;
}
void heart(int x0, int y0, int size, COLORREF C)
{
double m, n, x, y; double i;
for (i = 0; i <= 2 * size; i = i + 0.01)
{
// 产生极坐标点
m = i;
n = -size * (((sin(i) * sqrt(fabs(cos(i)))) / (sin(i) + 1.4142)) - 2 * sin(i) + 2);
// 转换为笛卡尔坐标
x = n * cos(m) + x0;
y = n * sin(m) + y0;
setfillcolor(C);
solidcircle((int)x, (int)y, 2);
}
}
1.4 作品效果
作品 2 运用DDA画线算法的变换图形
2.1 需求分析
编写完整的直线生成算法,要求实现实线和虚线的绘制(DDA算法或中点画线法)
结合编写的函数绘制以下图形,要求不使用easyx库中已有的setlinestyle()和line()函数。
2.2 方案设计
首先编写两个函数drawline_solid()和drawline_dash(),分别实现实线和虚线的绘制,函数实现使用DDA算法。DDA算法本质是用数值方法解微分方程yi=ki+b和yi+1=yi+k×∆x,通过计算X和Y各增加的小增量,来计算下一步的X和Y的坐标值。
函数中使用for循环和putpixel()函数绘制一系列点,从而实现多个点汇成一条直线。为使点分布密集,需要分析沿X轴还是Y轴计算增量。若直线陡峭,则Y轴坐标差| dy |大(>X轴坐标差),选择| dy |作为步长length可绘制更多的点,反之,选择X轴坐标差。其中,计算绝对值可以使用abs()函数。
编写虚线函数时,可以在实线函数基础上实现虚线效果,即可以在某个(X,Y)点时不绘制该点。可以控制循环次数i,当i每自增5的倍数时,即每隔4个点,不绘制某个点,即判断i%5!=0则绘制。为使虚线清晰可见,选择了每绘制4个点,接下来连续的三个点都不绘制(使用x+x0+x0、y+y0+y0、length-2进行控制)。
需要绘制的图形3和图形4需要计算图形各边长的比例关系,等腰直角三角形中以斜边为底的情况下,斜边 :直角边 :高 = 2 :√2 :2。
编写完函数再在主函数中调用即可绘制组合图形。并且void drawline_solid(int x1,int y1,int x2,int y2,int color);中(x1,y1)为起点坐标,(y1,y2)为终点坐标,color为绘制的直线颜色。
2.3 代码实现
#include<graphics.h>
#include<conio.h>
void drawline_solid(int x1,int y1,int x2,int y2,int color); //实线函数
void drawline_dash(int x1,int y1,int x2,int y2,int color); //虚线函数
int main()
{
//准备工作
initgraph(1000,1000);
setbkcolor(WHITE); //背景颜色白色
cleardevice(); //清空屏幕
setorigin(500,500); //重置原点
setaspectratio(1,-1); //y轴向上
drawline_dash(-500,0,500,0,BLACK); //画坐标轴
drawline_dash(0,-500,0,500,BLACK);
//作业部分
//1 实线竖线
drawline_solid(-300,200,-300,-200,RED); //实线
//2 虚线竖线
drawline_dash(-200,200,-200,-200,BLUE); //虚线
//3 组合图形
drawline_solid(-100,200,-100,-200,RED); //实线
drawline_solid(-100,200,100,200,RED); //实线
drawline_solid(-100,-200,100,-200,RED); //实线
drawline_dash(-100,0,100,200,BLUE); //虚线
drawline_dash(-100,0,100,-200,BLUE); //虚线
//4 组合图形
drawline_dash(250,200,250,-200,BLUE); //虚线
drawline_dash(250,200,450,200,BLUE); //虚线
drawline_dash(250,-200,450,-200,BLUE); //虚线
drawline_solid(150,100,250,200,RED); //实线 左上三角
drawline_solid(150,100,250,0,RED); //实线
drawline_solid(150,-100,250,-200,RED); //实线 左下三角
drawline_solid(150,-100,250,0,RED); //实线
drawline_solid(350,300,250,200,RED); //实线 正上三角
drawline_solid(350,300,450,200,RED); //实线
drawline_solid(350,-300,250,-200,RED); //实线 正下三角
drawline_solid(350,-300,450,-200,RED); //实线
_getch();
return 0;
}
void drawline_solid(int x1,int y1,int x2,int y2,int color)
{
float dx = x2-x1; //x轴坐标差
float dy = y2-y1; //y轴坐标差
float x = x1; //画线起点坐标
float y = y1;
int length; //选择长度
if(abs(dy)<=abs(dx)) //|斜率|≤1,选择x递增
{
length = abs(dx);
}
else //|斜率|>1,选择y递增
{
length = abs(dy);
}
float x0 = (float)(dx/length); //x单次增量
float y0 = (float)(dy/length); //y单次增量
putpixel((int)(x+0.5), (int)(y+0.5),color); //画起点
for(int i=1; i<=length ; i++)
{
x += x0; //x增加
y += y0; //y增加
putpixel((int)(x+0.5), (int)(y+0.5),color); //画点
}
}
void drawline_dash(int x1,int y1,int x2,int y2,int color)
{
float dx = x2-x1; //x轴坐标差
float dy = y2-y1; //y轴坐标差
float x = x1; //画线起点坐标
float y = y1;
int length; //选择长度
if(abs(dy)<=abs(dx)) //|斜率|≤1,选择x递增
{
length = abs(dx);
}
else //|斜率|>1,选择y递增
{
length = abs(dy);
}
float x0 = (float)(dx/length); //x单次增量
float y0 = (float)(dy/length); //y单次增量
putpixel((int)(x+0.5), (int)(y+0.5),color); //画起点
for(int i=1; i<=length ; i++)
{
x += x0; //x增加
y += y0; //y增加
if(i%5==0) //接下来跳过三个点的绘制
{
x = x + x0 + x0; //x增加
y = y + y0 + y0; //y增加
length = length - 2; //自增次数-2
}
else
{
putpixel((int)(x+0.5), (int)(y+0.5),color); //画点
}
}
}
2.4 作品效果
作品 3 明暗鲜明的五角星
3.1 需求分析
绘画一个五角星
运用中点画线法和Bresenham算法函数实现画线
使用Easyx库自带的填充函数为五角星上色
3.2 方案设计
- 首先在网络上查找五角星的图片,如下图:
可见:五角星是关于竖轴对称的图形,上图的五角星大致可以分为10个三角形,包括5个明面三角形、5个暗面三角形。
首先,确定五角星各顶点坐标,使用GetData软件设中心点为原点以及设置单位长度,获取其余各点坐标,由于轴对称图形,获取一半点即可,因坐标会出现小数点,四舍五入处理。
为节省代码,编写void xduichen_line(int x1,int y1,int x2,int y2,int color,int way);函数。其中way=1时,调用中点画线法;way=2时,调用Bresenham算法。此函数是为画关于X轴对称的两条直线,因为关于X轴对称的两条直线的起点终点坐标仅横坐标相反、纵坐标相同。
为体现不同画线方法,内部线条采用中点画线法,使用明黄色;外部线条采用Bresenham算法,采用暗黄色。
使用类似POINT pts[] = { {50, 200}, {200, 200}, {200, 50} }; fillpolygon(pts, 3); 可以实现填充三角形颜色。为了节省代码,采用二维数组一次存储五个三角形的顶点信息,再使用循环遍历数组填充三角形。
3.3 代码实现
#include <graphics.h>
#include <conio.h>
int Sign(int x);
void MPLine_all(int x1,int y1,int x2,int y2,int color);
void BHLine_all(int x1,int y1,int x2,int y2,int color);
void xduichen_line(int x1,int y1,int x2,int y2,int color,int way);//画关于x轴对称的两条直线
int main(){
initgraph(1000,1000);
setbkcolor(WHITE); //背景颜色白色
cleardevice(); //清空屏幕
setorigin(500,500); //重置原点
setaspectratio(1,-1); //y轴向上
//画坐标轴
setlinecolor(RGB(161,161,160)); //灰色
setlinestyle(PS_DASH); //虚线
line(-500,0,500,0); //x轴
line(0,-500,0,500); //y轴
//五角星
//一、描边
//1.1 内线:调用中点画线法
xduichen_line(0,0,0,467,RGB(255,212,1),1);
xduichen_line(0,0,143,188,RGB(255,212,1),1);
xduichen_line(0,0,455,142,RGB(255,212,1),1);
xduichen_line(0,0,230,-77,RGB(255,212,1),1);
xduichen_line(0,0,280,-384,RGB(255,212,1),1);
xduichen_line(0,0,0,-240,RGB(255,212,1),1);
//1.2 外线:调用Bresenham算法
xduichen_line(0,467,143,188,RGB(177,135,0),2);
xduichen_line(455,142,143,188,RGB(177,135,0),2);
xduichen_line(455,142,230,-77,RGB(177,135,0),2);
xduichen_line(280,-384,230,-77,RGB(177,135,0),2);
xduichen_line(280,-384,0,-240,RGB(177,135,0),2);
Sleep(5000); //暂停一下、方便截图
//二、填充
//2.1 明面
setfillcolor(RGB(255,212,1));
POINT pts1[5][3] = {
{{0,0}, {0,467}, {143,188}},
{{0,0}, {455,142}, {230,-77} },
{{0,0}, {280,-384}, {0,-240} },
{{0,0}, {-280,-384}, {-230,-77} },
{{0,0}, {-455,142}, {-143,188} }}; //存储所有三角形顶点
for (int i=0;i<5;i++) {
fillpolygon(pts1[i],3); //循环填充
}
//2.2 暗面
setfillcolor(RGB(177,135,0));
POINT pts2[5][3] = {
{{0,0}, {0,467}, {-143,188}},
{{0,0}, {455,142}, {143,188} },
{{0,0}, {230,-77}, {280,-384} },
{{0,0}, {-280,-384}, {0,-240} },
{{0,0}, {-230,-77}, {-455,142} }}; //存储所有三角形顶点
for (int i=0;i<5;i++) {
fillpolygon(pts2[i],3); //循环填充
}
_getch();
return 0;
}
int Sign(int x)
{
if(x<0) return -1;
else if(x==0) return 0;
else return 1;
}
void MPLine_all(int x1,int y1,int x2,int y2,int color)
{
int x,y,a,b,d1,d2,d,i,s1,s2,temp,swap;
a=-abs(y2-y1); b=abs(x2-x1);
x=x1; y=y1;
s1=Sign(x2-x1);
s2=Sign(y2-y1);
if(-a>b){
temp=b;
b=-a;
a=-temp;
swap=1;
}
else{
swap=0;
}
d=2*a+b;
d1=2*a;
d2=2*(a+b);
putpixel(x,y,color);
for(i=1;i<=b;i++){
if(swap==1){
y+=s2;
}
else{
x+=s1;
}
if(d<0){
if(swap==1){
x+=s1;
}
else{
y+=s2;
}
d+=d2;
}
else{
d+=d1;
}
putpixel(x,y,color);
}
}
void BHLine_all(int x1,int y1,int x2,int y2,int color)
{
int x,y,dx,dy,dk,i,s1,s2,temp,swap;
dy=abs(y2-y1); dx=abs(x2-x1);
x=x1; y=y1;
s1=Sign(x2-x1);
s2=Sign(y2-y1);
if(dy>dx){
temp=dx;
dx=dy;
dy=temp;
swap=1;
}
else{
swap=0;
}
dk=2*dy-dx;
for(i=1;i<=dx;i++){
putpixel(x,y,color);
if(swap==1){
y+=s2;
}
else{
x+=s1;
}
dk+=2*dy;
if(dk>=0){
if(swap==1){
x+=s1;
}
else{
y+=s2;
}
dk-=2*dx;
}
}
}
//画两条关于x轴对称的直线
void xduichen_line(int x1,int y1,int x2,int y2,int color,int way)
{
if(way==1){ //=1 调用中点画线法
MPLine_all(x1,y1,x2,y2,color);
MPLine_all(-x1,y1,-x2,y2,color);
}
else if(way==2){ //=2 调用Bresenham算法
BHLine_all(x1,y1,x2,y2,color);
BHLine_all(-x1,y1,-x2,y2,color);
}
}
3.4 作品效果
作品 4 奥运五环
4.1 需求分析
绘画一个奥运五环
实现套圈效果
为五环填充颜色
4.2 方案设计
首先计算五环之间的关系,设置可变参数方便调整圆环半径以及圆环间间距。
然后绘制实心圆环,不能使用for循环画多个圆,因为会有多个点缺失未填充。设计如下函数绘画实心圆环:void DrawAnnulus(int xc,int yc,int r,int q,int color)。其中q为大环与小环的半径之差,在函数内实现大环用中点画圆法,小圆用Bresenham画圆算法。
逐一绘制五个实心圆环。但不能实现套圈效果,如下图,需要对四处进行换色。
使用floodfill()函数进行换色填充,发现无法形成封闭图形,所以需要补充各区域对应填充色的圆环,类似下图。举例发现图示浅绿色本为该填充颜色,只需要将浅蓝色绘制成填充色,即画部分圆环。
为画部分圆环编写了**v****oid drawpart(int xc,int yc,int r,int q,int color,int direction)。其中direction**用于确认画哪一部分的空心圆环。函数中会调用如图所示的函数,分别对应了画六个方位的点。
经过测试和分析后发现,蓝色圆环和黑色圆环需要补充5号位置,黄色圆环和绿色圆环需要补充1号位置。所以可以精简部分代码,如下所示。
绘画完部分圆环再填充该区域即可,如下图。
- 尝试失败的地方:先绘画空心圆环再逐一填充发现有白边,为填充白边又会导致圆环线串色如下图。
4.3 代码实现
#include<graphics.h>
#include<conio.h>
void CirclePoints(int xc,int yc,int x,int y,int color);
void MidpointCircle(int xc,int yc,int r,int color); //1.中点画圆法
void BresenhamCircle(int xc,int yc,int r,int color); //2.Bresenham画圆算法
void DrawAnnulus(int xc,int yc,int r,int r2,int color); //3.使用中点画圆法和Bresenham画圆算法画实心圆环
void CirclePoints_direction(int xc,int yc,int x,int y,int color,int direction); //4.为画部分空心圆环准备
void drawpart(int xc,int yc,int r,int q,int color,int direction); //5.画部分空心圆环
int main()
{
initgraph(800,800); //画布大小800*800
setbkcolor(RGB(249,249,249)); //背景颜色(因为后续画圆用白线,不能设置白色背景)
cleardevice(); //清空屏幕
setorigin(400,400); //重置原点
setaspectratio(1,-1); //y轴向上
//画坐标轴
setlinecolor(RGB(161,161,160));
setlinestyle(PS_DASH);
line(-400,0,400,0);
line(0,-400,0,400);
//绘制奥运五环
int r = 60;
int p = 19;
int s1 = 2 * r + 2 * p;
int s2 = r + p;
DrawAnnulus(-s1,r,r,12,RGB(40,20,103)); //蓝色RGB(40,20,103)
DrawAnnulus(-s2,0,r,12,RGB(254,240,7)); //黄色RGB(254,240,7)
DrawAnnulus(0,r,r,12,RGB(30,25,21)); //黑色RGB(30,25,21)
DrawAnnulus(s2,0,r,12,RGB(2,135,54)); //绿色RGB(2,135,54)
DrawAnnulus(s1,r,r,12,RGB(218,36,28)); //红色RGB(218,36,28)
//补充
drawpart(-s1,r,r,12,RGB(40,20,103),5);
setfillcolor(RGB(40,20,103));
floodfill(-s1+r+1,r,RGB(40,20,103));
drawpart(-s2,0,r,12,RGB(254,240,7),1);
setfillcolor(RGB(254,240,7));
floodfill(-s2+10,0+r+10,RGB(254,240,7));
drawpart(0,r,r,12,RGB(30,25,21),5);
setfillcolor(RGB(30,25,21));
floodfill(0+r+1,r,RGB(30,25,21));
drawpart(s2,0,r,12,RGB(2,135,54),1);
setfillcolor(RGB(2,135,54));
floodfill(s2+10,0+r+10,RGB(2,135,54));
_getch();
return 0;
}
void CirclePoints(int xc,int yc,int x,int y,int color)
{
putpixel(xc+x,yc+y,color);
putpixel(xc+x,yc-y,color);
putpixel(xc-x,yc+y,color);
putpixel(xc-x,yc-y,color);
putpixel(xc+y,yc+x,color);
putpixel(xc+y,yc-x,color);
putpixel(xc-y,yc+x,color);
putpixel(xc-y,yc-x,color);
}
//1.中点画圆法
void MidpointCircle(int xc,int yc,int r,int color)
{
int x = 0;
int y = r;
double d = 1 - r;
CirclePoints(xc,yc,x,y,color);
while(x < y){
if(d < 0){
d += 2 * x + 3;
}
else{
d += 2 * (x-y) + 5;
y--;
}
x++;
CirclePoints(xc,yc,x,y,color);
}
}
//2.Bresenham画圆算法
void BresenhamCircle(int xc,int yc,int r,int color)
{
int x = 0;
int y = r;
double d = 3 - 2 * r;
while(x < y){
CirclePoints(xc,yc,x,y,color);
if(d >= 0){
d += 4 - 4 * y;
y--;
}
d += 4 * x + 6;
x++;
}
if(x==y){
CirclePoints(xc,yc,x,y,color);
}
}
//3.使用中点画圆法和Bresenham画圆算法画实心圆环
void DrawAnnulus(int xc,int yc,int r,int q,int color)
{
MidpointCircle(xc,yc,r+q,color); //中点画圆法
BresenhamCircle(xc,yc,r,color); //Bresenham画圆算法
setfillcolor(color);
floodfill(xc-r-1,yc,color);
}
//4.为画部分空心圆环准备
void CirclePoints_direction(int xc,int yc,int x,int y,int color,int direction)
{
switch (direction) {
case 1:
putpixel(xc+x,yc+y,color);
break;
case 5:
putpixel(xc+y,yc+x,color);
putpixel(xc+y,yc-x,color);
break;
}
}
//5.画部分空心圆环
void drawpart(int xc,int yc,int r,int q,int color,int direction)
{
int x = 0;
int y = r;
double d = 1 - r;
CirclePoints_direction(xc,yc,x,y,color,direction);
while(x < y){
if(d < 0){
d += 2 * x + 3;
}
else{
d += 2 * (x-y) + 5;
y--;
}
x++;
CirclePoints_direction(xc,yc,x,y,color,direction);
}
x = 0;
int y2 = r + q;
double d2 = 1 - r - q;
CirclePoints_direction(xc,yc,x,y2,color,direction);
while(x < y2){
if(d2 < 0){
d2 += 2 * x + 3;
}
else{
d2 += 2 * (x-y2) + 5;
y2--;
}
x++;
CirclePoints_direction(xc,yc,x,y2,color,direction);
}
}