实验名称:图形变换
一、实验目的
1,设置一个函数,该函数的功能是将给定坐标点进行旋转、平移或放缩变换。
2,利用1中的函数对你所做的简笔画进行向左平移15像素,以图像中心为中心点缩小至原始图像的0.75倍,然后以图像中心为中心旋转30度,然后输出。
二、设计方案
1、根据书中的矩阵,分别设计图形的旋转、平移与放缩矩阵
2、对于放缩与旋转变化,需要注意的是需要先将中心点移到原点,然后进行变换,最后移回去。这是因为图形的旋转、放缩变换是基于原点进行的
3、将之前的简笔画用一个框框起来,然后将整个框的所有点进行变换,这样很方便
三、详细代码
import numpy as np
from math import *
from PIL import Image #引入函数包
def T1(x,y):#将中心点移到原点的矩阵
T1=np.array([[1,0,0],[0,1,0],[-x,-y,1]])
return T1
def T2(x,y):#移到原点的中心点移回去
t2=np.array([[1,0,0],[0,1,0],[x,y,1]])
return t2
def s(sx,sy):#放缩矩阵
s=np.array([[sx,0,0],[0,sy,0],[0,0,1]])
return s
def r(o):#旋转矩阵
r=np.array([[cos(o),sin(o),0],[-sin(o),cos(o),0],[0,0,1]])
return r
def move(x,c):#移动函数,采用不同的矩阵实现不同的移动效果
answer=np.dot(x,c)
return np.array([answer[0],answer[1],1])
x=np.array([1,2,1])
c=np.array([[1,0,0],[0,1,0],[-15,0,1]])#计算向左移15个单位
x0=np.array([395,310])
xuan1=T1(395,310)#中心移至原点
xuan=r(pi/6)#旋转30°
xuan2=T2(395,310)#移回原来的中心
fangsuo=T1(395,310)*s(0.75,0.75)*T2(395,310)#放缩为原来的0.75
xxd=c*fangsuo
g=Image.new("RGB",(800,800),color="white") #创建白色背景
background=Image.new("RGB",(800,800),color='white')
#画身体
#小黄人的身体是一个跑道形状的,先画两边的半圆,设置输入圆的中心坐标,半径,以及两个圆之间的距离
def circle(xr,yr,r,f):#Bresenham画圆的改造
x=0#设置起始点
y=r#起始点
p=3-2*r#判别量
while x<=y: #循环画圆
g.putpixel((x + xr, y + yr+f), 0) #放像素点到对应的位置,将其向下移动f
g.putpixel((xr - x, y + yr+f), 0)#放像素点到对应的位置,将其向下移动f
g.putpixel((x + xr, yr - y), 0)#放像素点到对应的位置
g.putpixel((xr - x, yr - y), 0)#放像素点到对应的位置
g.putpixel((y + xr, x + yr+f), 0)#放像素点到对应的位置,将其向下移动f
g.putpixel((xr - y, x + yr+f), 0)#放像素点到对应的位置,将其向下移动f
g.putpixel((y + xr, yr - x), 0)#放像素点到对应的位置
g.putpixel((xr - y, yr - x), 0)#放像素点到对应的位置
if p>=0:#通过判别量,计算下一点的位置
p+=4*(x-y)+10
y-=1
x+=1
else:
p+=4*x+6
x+=1
def DDA(x1, y1, x2, y2):#DDA画线法
dx = x2 - x1#计算两点间的横坐标距离
dy = y2 - y1#计算两点间纵坐标距离
e = abs(dx) if (abs(dx) > abs(dy)) else abs(dy) #计算两个距离哪个大,用大的画小的
dx /= e#计算每次画点时的增量
dy /= e#同上
x = x1#给初始点复制
y = y1#同上
i = 0
while i <= e:#循环画点
g.putpixel((int(x + 0.5), int(y + 0.5)), 0)#四舍五入
x+=dx#x增加 dx=1
y+=dy#y增加 dy=(dx)*(y2-y1)/(x2-x1) 其中dx=1
i+=1
#普通的画圆
#画小黄人的眼睛、眼镜
def circle1(xr,yr,r,e):#Bresenham画圆
x=0#设置起始点
y=r#设置起始点
p=3-2*r#判别量
while x<=y:#循环画点
g.putpixel((x +xr, y + yr), e)#对应的位置画点
g.putpixel((xr - x, y + yr), e)#对应的位置画点
g.putpixel((x + xr, yr - y), e)#对应的位置画点
g.putpixel((xr - x, yr - y), e)#对应的位置画点
g.putpixel((y + xr, x + yr), e)#对应的位置画点
g.putpixel((xr - y, x + yr), e)#对应的位置画点
g.putpixel((y + xr, yr - x), e)#对应的位置画点
g.putpixel((xr - y, yr - x), e)#对应的位置画点
if p>=0:#计算判别量,判断下一个点的落点位置
p+=4*(x-y)+10
y-=1
x+=1
else:
p+=4*x+6
x+=1
#绘制嘴,是一个半圆(下半圆)
def circle2(xr,yr,r,e=0):#Bresenham画圆
x=0#设置起始点
y=r#设置起始点
p=3-2*r#判别量
while x<=y:
g.putpixel((x +xr, y + yr), e)#对应的位置画点
g.putpixel((xr - x, y + yr), e)#对应的位置画点
g.putpixel((y + xr, x + yr), e)#对应的位置画点
g.putpixel((xr - y, x + yr), e)#对应的位置画点
if p>=0:#计算判别量,判断下一个点的落点位置
p+=4*(x-y)+10
y-=1
x+=1
else:
p+=4*x+6
x+=1
#填充算法,用线性填充
#默认黑色边框
def tianchong(x, y, z):#填充
if g.getpixel((x, y)) == (0, 0, 0) or g.getpixel((x, y)) == z:#如果遇到黑色或需要填充的颜色,停止
return
g.putpixel((x,y),z)#改为z这个颜色
dx = x#起始坐标横坐标
dy = y#起始坐标纵坐标
while g.getpixel((dx, dy + 1)) != (0, 0, 0):#循环,往上不断更改像素点颜色
dy += 1
g.putpixel((dx,dy),z) #改为z这个颜色
dx = x#起始坐标横坐标
dy = y#起始坐标纵坐标
while g.getpixel((dx, dy - 1)) != (0, 0, 0):#循环,往下不断更改像素点颜色
dy -= 1
g.putpixel((dx,dy),z)#改为z这个颜色
tianchong(x + 1, y, z)#递归调用,向右
tianchong(x - 1, y, z)#递归调用,向左
#填充算法
#用与所需要的颜色相同的颜色做填充
def tianchong1(x, y, z):#填充
if g.getpixel((x, y)) == z:#如果遇到需要填充的颜色,停止
return
g.putpixel((x,y),z)#改为z这个颜色
dx = x#起始坐标横坐标
dy = y#起始坐标纵坐标
while g.getpixel((dx, dy + 1)) != z:#循环,往上不断更改像素点颜色
dy += 1
g.putpixel((dx,dy),z) #改为z这个颜色
dx = x#起始坐标横坐标
dy = y#起始坐标纵坐标
while g.getpixel((dx, dy - 1)) != z:#循环,往下不断更改像素点颜色
dy -= 1
g.putpixel((dx,dy),z)#改为z这个颜色
tianchong1(x + 1, y, z)#递归调用,向右
tianchong1(x - 1, y, z)#递归调用,向左
#其实是四分之一圆,由右上角的
def circle3(xr,yr,r,e=0):#Bresenham画圆 画头发
x=0#设置起始点
y=r#设置起始点
p=3-2*r#判别量
while x<=y:#循环画点
g.putpixel((y + xr, yr - x), e)#对应位置画点
g.putpixel((y + xr+1, yr - x), e)#对应位置画点
if p>=0:#计算判别量,判断下一个点的位置
p+=4*(x-y)+10
y-=1
x+=1
else:
p+=4*x+6
x+=1
#是个半圆
def circle4(xr,yr,r,e=0):#Bresenham画圆 用于画脚
x=0#设置起始点
y=r#设置起始点
p=3-2*r#判别量
while x<=y:#循环画点
g.putpixel((x + xr, yr - y), e)#对应位置画点
g.putpixel((xr - x, yr - y), e)#对应位置画点
g.putpixel((y + xr, yr - x), e)#对应位置画点
g.putpixel((xr - y, yr - x), e)#对应位置画点
if p>=0:#计算判别量,判断下一个点的位置
p+=4*(x-y)+10
y-=1
x+=1
else:
p+=4*x+6
x+=1
#画身体
DDA(300,200,300,420)#跑道旁的左边线
DDA(299,200,299,420)#在旁边画一层,要不太细了,看不到边框线,一下称“加粗”
DDA(500,200,500,420)#跑道右边线
DDA(501,200,501,420)#加粗
circle(400,200,100,220)#跑道两端的半圆
circle(400,200,101,220)#加粗
circle(400,200,99,220)#加粗
tianchong(400,300,(255,255,0))#上色,黄色
#眼睛
circle1(370,210,30,0)#眼睛左
tianchong(370,210,(255,255,255))#上色,白
circle1(430,210,30,0)#眼睛右
tianchong(430,210,(255,255,255))#上色,白
#画眼镜框
for i in range(0,11):#画左侧
DDA(300,197+i,340,205+i)
for i in range(0,11):#画右侧
DDA(500,197+i,460,205+i)
#画瞳孔
circle1(370,210,18,0)#左瞳孔
tianchong(370,210,(0,0,0))#上色,黑
circle1(430,210,18,0)#右瞳孔
tianchong(430,210,(0,0,0))#上色,黑
#画瞳孔反光
circle1(370-10,210-3,7,(255,255,255))#左侧
tianchong1(360,207,(255,255,255))#上色,白
circle1(430+10,207,7,(255,255,255))#右侧
tianchong1(440,207,(255,255,255))#上色,白
#画嘴
circle2(400,320,20,0)#画半圆
DDA(380,320,420,320)#画线,给半圆封口
tianchong(400,321,(255,255,255))#填充,白
#画手,左侧
DDA(300,420,270,390)#下边线
DDA(269,390,300,359)#加粗
DDA(300,360,270,390)#上边线
DDA(300,421,269,390)#加粗
tianchong(280,390,(255,255,0))#上色,黄
#画手,右侧,镜像,横坐标关于400对称
DDA(500,420,270+260,390)#下边线
DDA(270+261,390,300+200,359)#加粗
DDA(300+200,360,270+260,390)#上边线
DDA(300+200,421,270+261,390)#加粗
tianchong(520,390,(255,255,0))#上色,黄
#画裤子
DDA(300,420-1,350,420-1)#左侧小线
DDA(500,420-1,450,420-1)#右侧小线
DDA(350,420-1,350,390)#左侧高
DDA(450,420-1,450,390)#右侧高
DDA(350,390,450,390)#连接封口
tianchong(400,420,(0,0,255))#上色,蓝
#画背带
DDA(300,360,350,400)#左上边线
DDA(300,345,360,390)#左下
DDA(300+200,360,350+100,400)#右上
DDA(300+200,345,360+80,390)#右下
#左上背带上色,蓝
#由于这个背带的中心线不在一条线上,选择一点一点的填,分三部分
tianchong(302,360,(0,0,255))
tianchong(321,375,(0,0,255))
tianchong(349,389,(0,0,255))
#右背带
tianchong(498,360,(0,0,255))
tianchong(479,375,(0,0,255))
tianchong(451,389,(0,0,255))
#裤子的黑色,中间的那条黑线
DDA(400,520,400,480)
DDA(399,520,399,480)
DDA(401,520,401,480)
#两根头发
circle3(300,100,100)
circle3(350,100,50)
#画腿左
DDA(390,520,390,570)#左侧
DDA(370,516,370,570)#右侧
circle4(365,570,25)#脚(半圆)
DDA(340,570,390,570)#封口半圆
tianchong(380,530,(0,0,0))#上色,分四部分,腿,半圆里面、和没填充到的两块
tianchong(380,560,(0,0,0))
tianchong(389,569,(0,0,0))
tianchong(365,569,(0,0,0))
#右腿,和左腿一直,横坐标关于400对称
DDA(410,520,410,570)
DDA(430,516,430,570)
circle4(435,570,25)
DDA(460,570,410,570)
tianchong(420,530,(0,0,0))
tianchong(420,560,(0,0,0))
tianchong(411,569,(0,0,0))
tianchong(435,569,(0,0,0))
num=[]
end=[]
#为了方便移动,我不只是移动了一个图形,而是做了一个框,将所有的图形框在里面,将框里的做有点按照要求移动,这是最方便的方法。
#为了避免新旧图形交叉造成干扰,我把它画在一个新的画布。将移动好的点保存在一个列表里,记得存入这点的颜色。
#其实我采取的两个方法用一个就行,画在新画布或者存起来最后画。建议用第二种,可以把图形变换一步一步实现后再画,清晰一些
for i in range(250,550):
for j in range(30,571):
#将所有的点先向左移动5个单位,再放缩为原来的0.75
num.append([move(np.array([i,j,1]),xxd),g.getpixel((i,j))])
#图形的旋转,将图形旋转30°
#按照书上的矩阵不能成功,所以我把它按步骤拆开写:
#1、将中心移到原点
#2、将图形旋转30°
#3、将中心从原点移回去
for i in num:
end.append([move(move(move(i[0],xuan1),xuan),xuan2),i[1]])
#将移动好的点再次画出来
for i in end:
background.putpixel((int(i[0][0]),int(i[0][1])),i[1])
background.show()
四、实验结果
变换之前
变换后
五、心得体会
这次的实验是在之前的基础上做的,一直想如何实现图形的整体变换,原来想了一个办法,就是将每个输出的点都经过变换之后再画点,由于工程量太大了,一直没有实现。今天突发奇想,反正我的背景是单色的,索性连背景一起挪动,被移动的背景不会被发现,仅图形会看出变化,实现了图形的移动。这种方法有很好的优点,最大的好处是省事,可以用循环实现“自动化”,这是一种非常好的优点,尽管可能会有额外的时间消耗…。幸亏图形不大,所以这个“歪招儿”还可以实现。