使用Python编写简单的画图板程序的示例教程


Posted in Python onDecember 08, 2015

从这次开始,我会由简单到困难(其实也不会困难到哪里去)讲几个例程,每一个例程都是我自己写(或者修改,那样的话我会提供原始出处)的,都具有一定的操作性和娱乐性。例程中汇尽量覆盖到以前所讲的pygame中方方面面,如果看到哪一步不明白,那就再回去复习复习,基本没有人会看一遍什么都记住什么都掌握的,重复是学习之母,实践是掌握一门技艺的最好手段!

这次就先从一个最简单的程序开始,说实话有些太简单我都不好意思拿出手了,不过从简单的开始,容易建立自信培养兴趣。兴趣是学习之母嘛。我们这次做一个画板,类似Windows里自带的画板,还记不记得第一次接触电脑用画板时的惊叹?现在想起来其实那个真的非常简陋,不过我们的比那个还要朴素,因为打算一篇讲完,就不追加很多功能了,等你把这一次讲解的都理解了,很容易可以自己给它增加新的机能。没准,你就开发出一个非常牛X的画图工具击败了Photoshop,然后日进斗金名垂千古(众:喂,别做梦了!)……

功能样式

做之前总要有个数,我们的程序做出来会是个什么样子。所谓从顶到底或者从底到顶啥的,咱就不研究了,这个小程序随你怎么弄了,而且我们主要是来熟悉pygame,高级的软件设计方法一概不谈~

因为是抄袭画图板,也就是鼠标按住了能在上面涂涂画画就是了,选区、放大镜、滴管功能啥的就统统不要了。画笔的话,基本的铅笔画笔总是要的,也可以考虑加一个刷子画笔,这样有一点变化;然后颜色应该是要的,否则太过单调了,不过调色板啥的就暂时免了,提供几个候选色就好了;然后橡皮……橡皮不就是白色的画笔么?免了免了!还有啥?似乎够了。。。 OK,开始吧!

框架

pygame程序的框架都是差不多的,考虑到我们这个程序的实际作用,大概建立这样的一个代码架子就可以了。

import pygame
from pygame.locals import *

class Brush():
 def __init__(self):
  pass

class Painter():
 def __init__(self):
  self.screen = pygame.display.set_mode((800, 600))
  pygame.display.set_caption("Painter")
  self.clock = pygame.time.Clock()

 def run(self):
  self.screen.fill((255, 255, 255))
  while True:
   # max fps limit
   self.clock.tick(30)
   for event in pygame.event.get():
    if event.type == QUIT:
     return
    elif event.type == KEYDOWN:
     pass
    elif event.type == MOUSEBUTTONDOWN:
     pass
    elif event.type == MOUSEMOTION:
     pass
    elif event.type == MOUSEBUTTONUP:
     pass

   pygame.display.update()

if __name__ == '__main__':
 app = Painter()
 app.run()

import pygame
from pygame.locals import *
 
class Brush():
 def __init__(self):
  pass
 
class Painter():
 def __init__(self):
  self.screen = pygame.display.set_mode((800, 600))
  pygame.display.set_caption("Painter")
  self.clock = pygame.time.Clock()
 
 def run(self):
  self.screen.fill((255, 255, 255))
  while True:
   # max fps limit
   self.clock.tick(30)
   for event in pygame.event.get():
    if event.type == QUIT:
     return
    elif event.type == KEYDOWN:
     pass
    elif event.type == MOUSEBUTTONDOWN:
     pass
    elif event.type == MOUSEMOTION:
     pass
    elif event.type == MOUSEBUTTONUP:
     pass
 
   pygame.display.update()
 
if __name__ == '__main__':
 app = Painter()
 app.run()

这个非常简单,准备好画板类,画笔类,暂时还都是空的,其实也就是做了一些pygame的初始化工作。如果这样还不能读懂的话,您需要把前面22篇从头再看看,有几句话不懂就看几遍:)

这里只有一点要注意一下,我们把帧率控制在了30,没有人希望在画画的时候,CPU风扇狂转的。而且只是画板,没有自动运动的物体,纯粹的交互驱动,我们也不需要很高的刷新率。

第一次的绘图代码

按住鼠标然后在上面移动就画东西,我们很容易可以想到这个流程:

按下左键  →  绘制flag开
移动鼠标  →  flag开的时候,在移动坐标上留下痕迹
放开左键  →  绘制flag关

按下左键  →  绘制flag开
移动鼠标  →  flag开的时候,在移动坐标上留下痕迹
放开左键  →  绘制flag关
立刻试一试吧:

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False

 def start_draw(self):
  self.drawing = True
 def end_draw(self):
  self.drawing = False

 def draw(self, pos):
  if self.drawing:
   pygame.draw.circle(self.screen, self.color, pos, self.size)

class Painter():
 def __init__(self):
  #*#*#*#*#
  self.brush = Brush(self.screen)

 def run(self):
   #*#*#*#*#
    elif event.type == KEYDOWN:
     # press esc to clear screen
     if event.key == K_ESCAPE:
      self.screen.fill((255, 255, 255))
    elif event.type == MOUSEBUTTONDOWN:
     self.brush.start_draw()
    elif event.type == MOUSEMOTION:
     self.brush.draw(event.pos)
    elif event.type == MOUSEBUTTONUP:
     self.brush.end_draw()

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
 
 def start_draw(self):
  self.drawing = True
 def end_draw(self):
  self.drawing = False
 
 def draw(self, pos):
  if self.drawing:
   pygame.draw.circle(self.screen, self.color, pos, self.size)
 
class Painter():
 def __init__(self):
  #*#*#*#*#
  self.brush = Brush(self.screen)
 
 def run(self):
   #*#*#*#*#
    elif event.type == KEYDOWN:
     # press esc to clear screen
     if event.key == K_ESCAPE:
      self.screen.fill((255, 255, 255))
    elif event.type == MOUSEBUTTONDOWN:
     self.brush.start_draw()
    elif event.type == MOUSEMOTION:
     self.brush.draw(event.pos)
    elif event.type == MOUSEBUTTONUP:
     self.brush.end_draw()

框架中有的代码我就不贴了,用#*#*#*#*#代替,最后会给出完整代码的。

这里主要是给Brush类增加了一些功能,也就是上面我们提到的流程想对应的功能。留下痕迹,我们是使用了在坐标上画圆的方法,这也是最容易想到的方法。这样的效果好不好呢?我们试一试:

使用Python编写简单的画图板程序的示例教程

哦,太糟糕了,再劣质的铅笔也不会留下这样断断续续的笔迹。上面是当我们鼠标移动的快一些的时候,点之间的间距很大;下面是移动慢一些的时候,勉勉强强显得比较连续。从这里我们也可以看到pygame事件响应的频度(这个距离和上面设置的最大帧率有关)。

怎么办?要修改帧率让pygame平滑的反应么?不,那样做得不偿失,换一个角度思考,如果有间隙,我们让pygame把这个间隙连接起来不好么?

第二次的绘图代码

思路还是很简单,当移动的时候,Brush在上一次和这一次的点之间连一条线就好了:

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
  self.last_pos = None  # <--
 
 def start_draw(self, pos):
  self.drawing = True
  self.last_pos = pos # <--
 def end_draw(self):
  self.drawing = False
 
 def draw(self, pos):
  if self.drawing:
   pygame.draw.line(self.screen, self.color,
     self.last_pos, pos, self.size * 2)
   self.last_pos = pos

使用Python编写简单的画图板程序的示例教程

在__init__和start_draw中各加了一句,用来存储上一个点的位置,然后draw也由刚刚的话圆变成画线,效果如何?我们来试试。嗯,好多了,如果你动作能温柔一些的话,线条已经很圆润了,至少没有断断续续的存在了。

满足了么?我希望你的回答是“NO”,为什么,如果你划线很快的话,你就能明显看出棱角来,就好像左图上半部分,还是能看出是由几个线段组合的。只有永不满足,我们才能不停进步。

不过对我们这个例程而言,差不多了,一般人在真正画东西的时候,也不会动那么快的:)

那么这个就是我们最终的绘图机制了么?回头看看我们的样式,好用还需要加一个笔刷……所谓笔刷,不仅仅是很粗,而且是由很多细小的毛组成,画出来的线是给人一种一缕一缕的感觉,用这个方法可以实现么?好像非常非常的困难。。。孜孜不倦的我们再次进入了沉思……

这个时候,如果没有头绪,就得借鉴一下前辈的经验了。看看人家是如何实现的?

使用Python编写简单的画图板程序的示例教程

如果你的Photoshop不错,应该知道它里面复杂的笔刷设定,而Photoshop画出来的笔画,并不是真正一直线的,而是由无数细小的点组成的,这些点之间的间距是如此的密,以至于我们误会它是一直线……所以说,我们还得回到第一种方法上,把它发扬光大一下~ 这没有什么不好意思的,放弃第二种方法并不意味着我们是多么的愚蠢,而是说明我们从自己身上又学到了很多!

(公元前1800年)医生:来,试试吃点儿这种草根,感谢伟大的部落守护神赐与我们神药!
(公元900年)医生:别再吃那种草根,简直是野蛮不开化不尊重上帝,这是一篇祈祷词,每天虔诚地向上帝祈祷一次,不久就会治愈你的疾病。
(公元1650年)医生:祈祷?!封建迷信!!!来,只要喝下这种药水,什么病都能治好!
(公元1960年)医生:什么药水?早就不用了!别喝那骗人的”万灵药”,还是这种药片的疗效快!
(公元1995年)医生:哪个庸医给你开的处方?那种药片吃半瓶也抵不上这一粒,来来来,试试科技新成果—抗生素
(公元2003年)医生:据最新科学研究,抗生素副作用太强,毕竟是人造的东西呀……来,试试吃点儿这种草根!早在公元前1800年,文献就有记载了。
返璞归真,大抵如此了。

第三次的绘图代码

这次我们考虑的更多,希望在点与点之间充满我们的笔画,很自然的我们就需要一个循环来做这样的事情。我们的笔画有两种,普通的实心和刷子,实心的话,用circle来画也不失为一个好主意;刷子的话,我们可能需要一个刷子的图案来填充了。

下面是我们新的Brush类:

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
  self.last_pos = None
  self.space = 1
  # if style is True, normal solid brush
  # if style is False, png brush
  self.style = False
  # load brush style png
  self.brush = pygame.image.load("brush.png").convert_alpha()
  # set the current brush depends on size
  self.brush_now = self.brush.subsurface((0,0), (1, 1))

 def start_draw(self, pos):
  self.drawing = True
  self.last_pos = pos
 def end_draw(self):
  self.drawing = False

 def set_brush_style(self, style):
  print "* set brush style to", style
  self.style = style
 def get_brush_style(self):
  return self.style

 def set_size(self, size):
  if size < 0.5: size = 0.5
  elif size > 50: size = 50
  print "* set brush size to", size
  self.size = size
  self.brush_now = self.brush.subsurface((0,0), (size*2, size*2))
 def get_size(self):
  return self.size

 def draw(self, pos):
  if self.drawing:
   for p in self._get_points(pos):
    # draw eveypoint between them
    if self.style == False:
     pygame.draw.circle(self.screen,
       self.color, p, self.size)
    else:
     self.screen.blit(self.brush_now, p)

   self.last_pos = pos

 def _get_points(self, pos):
  """ Get all points between last_point ~ now_point. """
  points = [ (self.last_pos[0], self.last_pos[1]) ]
  len_x = pos[0] - self.last_pos[0]
  len_y = pos[1] - self.last_pos[1]
  length = math.sqrt(len_x ** 2 + len_y ** 2)
  step_x = len_x / length
  step_y = len_y / length
  for i in xrange(int(length)):
   points.append(
     (points[-1][0] + step_x, points[-1][1] + step_y))
  points = map(lambda x:(int(0.5+x[0]), int(0.5+x[1])), points)
  # return light-weight, uniq list
  return list(set(points))

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
  self.last_pos = None
  self.space = 1
  # if style is True, normal solid brush
  # if style is False, png brush
  self.style = False
  # load brush style png
  self.brush = pygame.image.load("brush.png").convert_alpha()
  # set the current brush depends on size
  self.brush_now = self.brush.subsurface((0,0), (1, 1))
 
 def start_draw(self, pos):
  self.drawing = True
  self.last_pos = pos
 def end_draw(self):
  self.drawing = False
 
 def set_brush_style(self, style):
  print "* set brush style to", style
  self.style = style
 def get_brush_style(self):
  return self.style
 
 def set_size(self, size):
  if size < 0.5: size = 0.5
  elif size > 50: size = 50
  print "* set brush size to", size
  self.size = size
  self.brush_now = self.brush.subsurface((0,0), (size*2, size*2))
 def get_size(self):
  return self.size
 
 def draw(self, pos):
  if self.drawing:
   for p in self._get_points(pos):
    # draw eveypoint between them
    if self.style == False:
     pygame.draw.circle(self.screen,
       self.color, p, self.size)
    else:
     self.screen.blit(self.brush_now, p)
 
   self.last_pos = pos
 
 def _get_points(self, pos):
  """ Get all points between last_point ~ now_point. """
  points = [ (self.last_pos[0], self.last_pos[1]) ]
  len_x = pos[0] - self.last_pos[0]
  len_y = pos[1] - self.last_pos[1]
  length = math.sqrt(len_x ** 2 + len_y ** 2)
  step_x = len_x / length
  step_y = len_y / length
  for i in xrange(int(length)):
   points.append(
     (points[-1][0] + step_x, points[-1][1] + step_y))
  points = map(lambda x:(int(0.5+x[0]), int(0.5+x[1])), points)
  # return light-weight, uniq list
  return list(set(points))

我们增加了几个方法,_get_points()返回上一个点到现在点之间所有的点(这话听着真别扭),draw根据这些点填充。
同时我们把get_size()、set_size()也加上了,用来设定当前笔刷的大小。
而变化最大的,则是set_style()和get_style(),我们现在载入一个PNG图片作为笔刷的样式,当style==True的时候,draw不再使用circle填充,而是使用这个PNG样式,当然,这个样式大小也是应该可调的,所有我们在set_size()中,会根据size大小实时的调整PNG笔刷。

当然,我们得在主循环中调用set方法,才能让这些东西工作起来~ 过一会儿再讲。再回顾下我们的样式,还有什么?颜色……我们马上把颜色设置代码也加进去吧,太简单了!我这里就先偷偷懒了~

控制代码

到现在,我们已经完成了绘图部分的所有功能了。现在已经可以在屏幕上自由发挥了,但是笔刷的颜色和大小好像不能改啊……我们有这样的接口你却不调用,浪费了。
趁热打铁赶快把我们这个画板完成吧~

使用Python编写简单的画图板程序的示例教程

现在实际写的时候才发现,因为我们设置了颜色需要对刷子也有效,所以实际上set_color方法还有一点点收尾工作需要做:

def set_color(self, color):
  self.color = color
  for i in xrange(self.brush.get_width()):
   for j in xrange(self.brush.get_height()):
    self.brush.set_at((i, j),
      color + (self.brush.get_at((i, j)).a,))

 def set_color(self, color):
  self.color = color
  for i in xrange(self.brush.get_width()):
   for j in xrange(self.brush.get_height()):
    self.brush.set_at((i, j),
      color + (self.brush.get_at((i, j)).a,))

也就是在设定color的时候,顺便把笔刷的颜色也改了,但是要保留原来的alpha值,其实也很简单就是了……

按钮菜单部分

上图可以看到,按钮部分分别为铅笔、毛笔、尺寸大小、(当前样式)、颜色选择者几个组成。我们只以笔刷选择为例讲解一下,其他的都是类似的。

# 初始化部分
  self.sizes = [
    pygame.image.load("big.png").convert_alpha(),
    pygame.image.load("small.png").convert_alpha()
   ]
  self.sizes_rect = []
  for (i, img) in enumerate(self.sizes):
   rect = pygame.Rect(10 + i * 32, 138, 32, 32)
   self.sizes_rect.append(rect)

# 绘制部分
  for (i, img) in enumerate(self.pens):
   self.screen.blit(img, self.pens_rect[i].topleft)

# 点击判断部分
  for (i, rect) in enumerate(self.pens_rect):
   if rect.collidepoint(pos):
    self.brush.set_brush_style(bool(i))
    return True

# 初始化部分
  self.sizes = [
    pygame.image.load("big.png").convert_alpha(),
    pygame.image.load("small.png").convert_alpha()
   ]
  self.sizes_rect = []
  for (i, img) in enumerate(self.sizes):
   rect = pygame.Rect(10 + i * 32, 138, 32, 32)
   self.sizes_rect.append(rect)
 
# 绘制部分
  for (i, img) in enumerate(self.pens):
   self.screen.blit(img, self.pens_rect[i].topleft)
 
# 点击判断部分
  for (i, rect) in enumerate(self.pens_rect):
   if rect.collidepoint(pos):
    self.brush.set_brush_style(bool(i))
    return True

这些代码实际上是我这个例子最想给大家说明的地方,按钮式我们从未接触过的东西,然而游戏中按钮的应用我都不必说。

不过这代码也都不困难,基本都是我们学过的东西,只不过变换了一下组合而已,我稍微说明一下:

初始化部分:读入图标,并给每个图标一个Rect
绘制部分: 根据图表的Rect绘制图表
点击判断部分:根据点击的位置,依靠“碰撞”来判断这个按钮是否被点击,若点击了,则做相应的操作(这里是设置样式)后返回True。这里的collidepoint()是新内容,也就是Rect的“碰撞”函数,它接收一个坐标,如果在Rect内部,就返回True,否则False。

好像也就如此,有了一定的知识积累后,新东西的学习也变得易如反掌了。

在这个代码中,为了明晰,我把各个按钮按照功能都分成了好几组,在实际应用中按钮数量很多的时候可能并不合适,请自己斟酌。

完整代码

OK,这就结束了~ 下面把整个代码贴出来。不过,我是一边写代码一遍写文章,思路不是很连贯,而且python也好久不用了……如果有哪里写的有问题(没有就怪了),还请不吝指出!

import pygame
from pygame.locals import *
import math

# 2011/08/27 Version 1, first imported

class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
  self.last_pos = None
  self.space = 1
  # if style is True, normal solid brush
  # if style is False, png brush
  self.style = False
  # load brush style png
  self.brush = pygame.image.load("brush.png").convert_alpha()
  # set the current brush depends on size
  self.brush_now = self.brush.subsurface((0,0), (1, 1))

 def start_draw(self, pos):
  self.drawing = True
  self.last_pos = pos
 def end_draw(self):
  self.drawing = False

 def set_brush_style(self, style):
  print "* set brush style to", style
  self.style = style
 def get_brush_style(self):
  return self.style

 def get_current_brush(self):
  return self.brush_now

 def set_size(self, size):
  if size < 0.5: size = 0.5
  elif size > 32: size = 32
  print "* set brush size to", size
  self.size = size
  self.brush_now = self.brush.subsurface((0,0), (size*2, size*2))
 def get_size(self):
  return self.size

 def set_color(self, color):
  self.color = color
  for i in xrange(self.brush.get_width()):
   for j in xrange(self.brush.get_height()):
    self.brush.set_at((i, j),
      color + (self.brush.get_at((i, j)).a,))
 def get_color(self):
  return self.color

 def draw(self, pos):
  if self.drawing:
   for p in self._get_points(pos):
    # draw eveypoint between them
    if self.style == False:
     pygame.draw.circle(self.screen, self.color, p, self.size)
    else:
     self.screen.blit(self.brush_now, p)

   self.last_pos = pos

 def _get_points(self, pos):
  """ Get all points between last_point ~ now_point. """
  points = [ (self.last_pos[0], self.last_pos[1]) ]
  len_x = pos[0] - self.last_pos[0]
  len_y = pos[1] - self.last_pos[1]
  length = math.sqrt(len_x ** 2 + len_y ** 2)
  step_x = len_x / length
  step_y = len_y / length
  for i in xrange(int(length)):
   points.append(
     (points[-1][0] + step_x, points[-1][1] + step_y))
  points = map(lambda x:(int(0.5+x[0]), int(0.5+x[1])), points)
  # return light-weight, uniq integer point list
  return list(set(points))

class Menu():
 def __init__(self, screen):
  self.screen = screen
  self.brush = None
  self.colors = [
    (0xff, 0x00, 0xff), (0x80, 0x00, 0x80),
    (0x00, 0x00, 0xff), (0x00, 0x00, 0x80),
    (0x00, 0xff, 0xff), (0x00, 0x80, 0x80),
    (0x00, 0xff, 0x00), (0x00, 0x80, 0x00),
    (0xff, 0xff, 0x00), (0x80, 0x80, 0x00),
    (0xff, 0x00, 0x00), (0x80, 0x00, 0x00),
    (0xc0, 0xc0, 0xc0), (0xff, 0xff, 0xff),
    (0x00, 0x00, 0x00), (0x80, 0x80, 0x80),
   ]
  self.colors_rect = []
  for (i, rgb) in enumerate(self.colors):
   rect = pygame.Rect(10 + i % 2 * 32, 254 + i / 2 * 32, 32, 32)
   self.colors_rect.append(rect)

  self.pens = [
    pygame.image.load("pen1.png").convert_alpha(),
    pygame.image.load("pen2.png").convert_alpha()
   ]
  self.pens_rect = []
  for (i, img) in enumerate(self.pens):
   rect = pygame.Rect(10, 10 + i * 64, 64, 64)
   self.pens_rect.append(rect)

  self.sizes = [
    pygame.image.load("big.png").convert_alpha(),
    pygame.image.load("small.png").convert_alpha()
   ]
  self.sizes_rect = []
  for (i, img) in enumerate(self.sizes):
   rect = pygame.Rect(10 + i * 32, 138, 32, 32)
   self.sizes_rect.append(rect)

 def set_brush(self, brush):
  self.brush = brush

 def draw(self):
  # draw pen style button
  for (i, img) in enumerate(self.pens):
   self.screen.blit(img, self.pens_rect[i].topleft)
  # draw < > buttons
  for (i, img) in enumerate(self.sizes):
   self.screen.blit(img, self.sizes_rect[i].topleft)
  # draw current pen / color
  self.screen.fill((255, 255, 255), (10, 180, 64, 64))
  pygame.draw.rect(self.screen, (0, 0, 0), (10, 180, 64, 64), 1)
  size = self.brush.get_size()
  x = 10 + 32
  y = 180 + 32
  if self.brush.get_brush_style():
   x = x - size
   y = y - size
   self.screen.blit(self.brush.get_current_brush(), (x, y))
  else:
   pygame.draw.circle(self.screen,
     self.brush.get_color(), (x, y), size)
  # draw colors panel
  for (i, rgb) in enumerate(self.colors):
   pygame.draw.rect(self.screen, rgb, self.colors_rect[i])

 def click_button(self, pos):
  # pen buttons
  for (i, rect) in enumerate(self.pens_rect):
   if rect.collidepoint(pos):
    self.brush.set_brush_style(bool(i))
    return True
  # size buttons
  for (i, rect) in enumerate(self.sizes_rect):
   if rect.collidepoint(pos):
    if i: # i == 1, size down
     self.brush.set_size(self.brush.get_size() - 0.5)
    else:
     self.brush.set_size(self.brush.get_size() + 0.5)
    return True
  # color buttons
  for (i, rect) in enumerate(self.colors_rect):
   if rect.collidepoint(pos):
    self.brush.set_color(self.colors[i])
    return True
  return False

class Painter():
 def __init__(self):
  self.screen = pygame.display.set_mode((800, 600))
  pygame.display.set_caption("Painter")
  self.clock = pygame.time.Clock()
  self.brush = Brush(self.screen)
  self.menu = Menu(self.screen)
  self.menu.set_brush(self.brush)

 def run(self):
  self.screen.fill((255, 255, 255))
  while True:
   # max fps limit
   self.clock.tick(30)
   for event in pygame.event.get():
    if event.type == QUIT:
     return
    elif event.type == KEYDOWN:
     # press esc to clear screen
     if event.key == K_ESCAPE:
      self.screen.fill((255, 255, 255))
    elif event.type == MOUSEBUTTONDOWN:
     # <= 74, coarse judge here can save much time
     if ((event.pos)[0] <= 74 and
       self.menu.click_button(event.pos)):
      # if not click on a functional button, do drawing
      pass
     else:
      self.brush.start_draw(event.pos)
    elif event.type == MOUSEMOTION:
     self.brush.draw(event.pos)
    elif event.type == MOUSEBUTTONUP:
     self.brush.end_draw()

   self.menu.draw()
   pygame.display.update()

if __name__ == '__main__':
 app = Painter()
 app.run()

import pygame
from pygame.locals import *
import math
 
# 2011/08/27 Version 1, first imported
 
class Brush():
 def __init__(self, screen):
  self.screen = screen
  self.color = (0, 0, 0)
  self.size = 1
  self.drawing = False
  self.last_pos = None
  self.space = 1
  # if style is True, normal solid brush
  # if style is False, png brush
  self.style = False
  # load brush style png
  self.brush = pygame.image.load("brush.png").convert_alpha()
  # set the current brush depends on size
  self.brush_now = self.brush.subsurface((0,0), (1, 1))
 
 def start_draw(self, pos):
  self.drawing = True
  self.last_pos = pos
 def end_draw(self):
  self.drawing = False
 
 def set_brush_style(self, style):
  print "* set brush style to", style
  self.style = style
 def get_brush_style(self):
  return self.style
 
 def get_current_brush(self):
  return self.brush_now
 
 def set_size(self, size):
  if size < 0.5: size = 0.5
  elif size > 32: size = 32
  print "* set brush size to", size
  self.size = size
  self.brush_now = self.brush.subsurface((0,0), (size*2, size*2))
 def get_size(self):
  return self.size
 
 def set_color(self, color):
  self.color = color
  for i in xrange(self.brush.get_width()):
   for j in xrange(self.brush.get_height()):
    self.brush.set_at((i, j),
      color + (self.brush.get_at((i, j)).a,))
 def get_color(self):
  return self.color
 
 def draw(self, pos):
  if self.drawing:
   for p in self._get_points(pos):
    # draw eveypoint between them
    if self.style == False:
     pygame.draw.circle(self.screen, self.color, p, self.size)
    else:
     self.screen.blit(self.brush_now, p)
 
   self.last_pos = pos
 
 def _get_points(self, pos):
  """ Get all points between last_point ~ now_point. """
  points = [ (self.last_pos[0], self.last_pos[1]) ]
  len_x = pos[0] - self.last_pos[0]
  len_y = pos[1] - self.last_pos[1]
  length = math.sqrt(len_x ** 2 + len_y ** 2)
  step_x = len_x / length
  step_y = len_y / length
  for i in xrange(int(length)):
   points.append(
     (points[-1][0] + step_x, points[-1][1] + step_y))
  points = map(lambda x:(int(0.5+x[0]), int(0.5+x[1])), points)
  # return light-weight, uniq integer point list
  return list(set(points))
 
class Menu():
 def __init__(self, screen):
  self.screen = screen
  self.brush = None
  self.colors = [
    (0xff, 0x00, 0xff), (0x80, 0x00, 0x80),
    (0x00, 0x00, 0xff), (0x00, 0x00, 0x80),
    (0x00, 0xff, 0xff), (0x00, 0x80, 0x80),
    (0x00, 0xff, 0x00), (0x00, 0x80, 0x00),
    (0xff, 0xff, 0x00), (0x80, 0x80, 0x00),
    (0xff, 0x00, 0x00), (0x80, 0x00, 0x00),
    (0xc0, 0xc0, 0xc0), (0xff, 0xff, 0xff),
    (0x00, 0x00, 0x00), (0x80, 0x80, 0x80),
   ]
  self.colors_rect = []
  for (i, rgb) in enumerate(self.colors):
   rect = pygame.Rect(10 + i % 2 * 32, 254 + i / 2 * 32, 32, 32)
   self.colors_rect.append(rect)
 
  self.pens = [
    pygame.image.load("pen1.png").convert_alpha(),
    pygame.image.load("pen2.png").convert_alpha()
   ]
  self.pens_rect = []
  for (i, img) in enumerate(self.pens):
   rect = pygame.Rect(10, 10 + i * 64, 64, 64)
   self.pens_rect.append(rect)
 
  self.sizes = [
    pygame.image.load("big.png").convert_alpha(),
    pygame.image.load("small.png").convert_alpha()
   ]
  self.sizes_rect = []
  for (i, img) in enumerate(self.sizes):
   rect = pygame.Rect(10 + i * 32, 138, 32, 32)
   self.sizes_rect.append(rect)
 
 def set_brush(self, brush):
  self.brush = brush
 
 def draw(self):
  # draw pen style button
  for (i, img) in enumerate(self.pens):
   self.screen.blit(img, self.pens_rect[i].topleft)
  # draw < > buttons
  for (i, img) in enumerate(self.sizes):
   self.screen.blit(img, self.sizes_rect[i].topleft)
  # draw current pen / color
  self.screen.fill((255, 255, 255), (10, 180, 64, 64))
  pygame.draw.rect(self.screen, (0, 0, 0), (10, 180, 64, 64), 1)
  size = self.brush.get_size()
  x = 10 + 32
  y = 180 + 32
  if self.brush.get_brush_style():
   x = x - size
   y = y - size
   self.screen.blit(self.brush.get_current_brush(), (x, y))
  else:
   pygame.draw.circle(self.screen,
     self.brush.get_color(), (x, y), size)
  # draw colors panel
  for (i, rgb) in enumerate(self.colors):
   pygame.draw.rect(self.screen, rgb, self.colors_rect[i])
 
 def click_button(self, pos):
  # pen buttons
  for (i, rect) in enumerate(self.pens_rect):
   if rect.collidepoint(pos):
    self.brush.set_brush_style(bool(i))
    return True
  # size buttons
  for (i, rect) in enumerate(self.sizes_rect):
   if rect.collidepoint(pos):
    if i: # i == 1, size down
     self.brush.set_size(self.brush.get_size() - 0.5)
    else:
     self.brush.set_size(self.brush.get_size() + 0.5)
    return True
  # color buttons
  for (i, rect) in enumerate(self.colors_rect):
   if rect.collidepoint(pos):
    self.brush.set_color(self.colors[i])
    return True
  return False
 
class Painter():
 def __init__(self):
  self.screen = pygame.display.set_mode((800, 600))
  pygame.display.set_caption("Painter")
  self.clock = pygame.time.Clock()
  self.brush = Brush(self.screen)
  self.menu = Menu(self.screen)
  self.menu.set_brush(self.brush)
 
 def run(self):
  self.screen.fill((255, 255, 255))
  while True:
   # max fps limit
   self.clock.tick(30)
   for event in pygame.event.get():
    if event.type == QUIT:
     return
    elif event.type == KEYDOWN:
     # press esc to clear screen
     if event.key == K_ESCAPE:
      self.screen.fill((255, 255, 255))
    elif event.type == MOUSEBUTTONDOWN:
     # <= 74, coarse judge here can save much time
     if ((event.pos)[0] <= 74 and
       self.menu.click_button(event.pos)):
      # if not click on a functional button, do drawing
      pass
     else:
      self.brush.start_draw(event.pos)
    elif event.type == MOUSEMOTION:
     self.brush.draw(event.pos)
    elif event.type == MOUSEBUTTONUP:
     self.brush.end_draw()
 
   self.menu.draw()
   pygame.display.update()
 
if __name__ == '__main__':
 app = Painter()
 app.run()

200行左右,注释也不是很多,因为在这两篇文章里都讲了,有哪里不明白的请留言,我会根据实际情况再改改。

本次使用的资源文件打包

这次的pygame知识点:

  • 屏幕Surface和图像Surface
  • 图像绘制和图形绘制(是不是有人不明白“图像”和“图形”的区别?简单的说,图像指的是那些图片文件,图形指的是用命令画出来形状)
  • 按钮的实现(新内容)

认真的朋友一定发现了本次没有涉及到动画和声音,毕竟这次只是简单的例子,太复杂了不免让人生畏。

Python 相关文章推荐
python计算书页码的统计数字问题实例
Sep 26 Python
讲解Python中的递归函数
Apr 27 Python
python中print的不换行即时输出的快速解决方法
Jul 20 Python
python 读文件,然后转化为矩阵的实例
Apr 23 Python
Python多进程multiprocessing.Pool类详解
Apr 27 Python
使用Python微信库itchat获得好友和群组已撤回的消息
Jun 24 Python
Python设计模式之享元模式原理与用法实例分析
Jan 11 Python
jupyter notebook中新建cell的方法与快捷键操作
Apr 22 Python
Python 实现将numpy中的nan和inf,nan替换成对应的均值
Jun 08 Python
Matlab使用Plot函数实现数据动态显示方法总结
Feb 25 Python
python绘制简单直方图(质量分布图)的方法
Apr 21 Python
python实现双链表
May 25 Python
一波神奇的Python语句、函数与方法的使用技巧总结
Dec 08 #Python
Python使用pygame模块编写俄罗斯方块游戏的代码实例
Dec 08 #Python
用Python抢过年的火车票附源码
Dec 07 #Python
Python随手笔记之标准类型内建函数
Dec 02 #Python
python抓取网页中图片并保存到本地
Dec 01 #Python
利用Python学习RabbitMQ消息队列
Nov 30 #Python
MySQL中表的复制以及大型数据表的备份教程
Nov 25 #Python
You might like
yii2使用ajax返回json的实现方法
2016/05/14 PHP
PHP实现文件上传下载实例
2016/10/18 PHP
thinkPHP中U方法加密传递参数功能示例
2018/05/29 PHP
详解json在php中的应用
2018/09/30 PHP
laravel接管Dingo-api和默认的错误处理方式
2019/10/25 PHP
JavaScript中数组的排序、乱序和搜索实现代码
2011/11/30 Javascript
JavaScript中常用的运算符小结
2012/01/18 Javascript
js禁止document element对象选中文本实现代码
2013/03/21 Javascript
详谈JavaScript 匿名函数及闭包
2014/11/14 Javascript
JavaScript中操作Mysql数据库实例
2015/04/02 Javascript
js实现圆盘记速表
2015/08/03 Javascript
js实现页面跳转的几种方法小结
2016/05/16 Javascript
JS实现title标题栏文字不间断滚动显示效果
2016/09/07 Javascript
BootStrap中的表单大全
2016/09/07 Javascript
vue 挂载路由到头部导航的方法
2017/11/13 Javascript
iview实现select tree树形下拉框的示例代码
2018/12/21 Javascript
通过Nodejs搭建网站简单实现注册登录流程
2019/06/14 NodeJs
最全vue的vue-amap使用高德地图插件画多边形范围的示例代码
2020/07/17 Javascript
js前端对于大量数据的展示方式及处理方法
2020/12/02 Javascript
python模块之paramiko实例代码
2018/01/31 Python
Python实现的自定义多线程多进程类示例
2018/03/23 Python
python3.6使用pickle序列化class的方法
2018/10/22 Python
利用python实现简易版的贪吃蛇游戏(面向python小白)
2018/12/30 Python
在Pycharm中使用GitHub的方法步骤
2019/06/13 Python
详解Python中的正斜杠与反斜杠
2019/08/09 Python
Python插入Elasticsearch操作方法解析
2020/01/19 Python
Django中的AutoField字段使用
2020/05/18 Python
python tkinter实现连连看游戏
2020/11/16 Python
HTML5自定义视频播放器源码
2020/01/06 HTML / CSS
Haglöfs瑞典官方网站:haglofs火柴棍,欧洲顶级户外品牌
2018/10/18 全球购物
创造美妙香氛体验:Aera扩散器和香水
2018/11/25 全球购物
美国价格实惠的在线眼镜网站:Zeelool
2020/12/25 全球购物
刊首寄语大全
2014/04/11 职场文书
学生喝酒检讨书500字
2014/11/02 职场文书
换届选举主持词
2015/07/03 职场文书
搭建zabbix监控以及邮件报警的超级详细教学
2022/07/15 Servers