Pygame 实现一个简单画图板

Pygame 版本:1.9.6 ; python3.7; pycharm工具

1.安装

pip install pygame

2.介绍

Pygame 包含有许多的模块,这里我只简单列出几个将用到的核心模块,其它模块可以自行到 Pygame 官网查看文档。

参考:Pygame 官方文档

     模块	        主要作用
pygame.display	 用于配置显示窗口
pygame.event		 用于管理事件队列
pygame.draw		 用于绘制图像
pygame.image	 用于加载、存储图片资源等
pygame.Rect		 自由可控的矩形容器
pygame.Surface	 图像与屏幕的对象

Pygame 程序的主要流程图如下:
Pygame 实现一个简单画图板_第1张图片

这儿有一个简单的例子,先了解一下功能:

import pygame
from pygame.locals import *

def main():
    # 创建窗口
    screen = pygame.display.set_mode((480, 800))
    # 设置窗口名称
    pygame.display.set_caption("My Game")
    # 创建 Clock 对象
    clock = pygame.time.Clock()

    # 加载所需的图像资源
    bg = pygame.image.load('images/background.png').convert()
    plane = pygame.image.load('images/plane.png').convert_alpha()

    # 程序逻辑主体
    while True:
        # 设置帧数为 30
        clock.tick(30)
        # 绘制背景
        screen.blit(bg, (0, 0))
        # 获取鼠标坐标
        (x, y) = pygame.mouse.get_pos()
        # 分别获取图像宽高
        x -= plane.get_width() / 2
        y -= plane.get_height() / 2
        # 绘制飞机
        screen.blit(plane, (x, y))

        # 遍历处理事件
        for event in pygame.event.get():
            if event.type == QUIT:
                return
        # 更新画面
        pygame.display.update()
    
if __name__ == '__main__':
    main()

背景图片下载

运行效果如下:
Pygame 实现一个简单画图板_第2张图片

讲解如下:
pygame.display 是 Pygame 中一个很重要的模块,它主要负责控制播放窗口与屏幕的模块。它有以下几个常用的方法:
①pygame.display.set_mode(resolution=(0,0), flags=0, depth=0) 这个函数将创建一个窗口,第一个参数为一个整型元组 (w, h) 分别指定了所创建窗口的宽与高;第二个参数负责控制窗口的展示模式;第三个参数指定了颜色深度,程序将自动为系统适配最佳的值。

②pygame.display.set_caption(title) 这个函数用于设置窗口的标题。

③pygame.display.update(rectangle=None) 该函数用于更新窗口中指定部分的内容,如果没有传入任何值,则更新整个窗口的内容。

④pygame.time.Clock() 用于创建一个对象,该对象主要负责在 Pygame 程序中跟进时间。该对象可以通过 tick(framerate=0) 方法来指定画面更新的帧率,通常我们把它设置为 30。

⑤pygame.image.load(filename) 用于加载指定图像,并且返回一个 pygame.Surface 对象。值得注意的是,我们通常还使用 Surface.convert() 方法来获取原图像的副本,因为这个副本能在窗口屏幕上以更快的速度绘制。另外,对于包含透明通道的图片(PNG)我们还必须使用 Surface.convert_alpha() 方法来确保透明通道信息被正确加载。(例子中飞机的图片就是透明的,但是背景不是透明的)

⑥要将已加载的图片资源绘制到屏幕上使用 Surface.blit(source, dest) 方法。它主要接收两个参数,第一个是要绘制的图像,是一个 Surface 对象;第二个参数是一个整型元组,指定了绘制图像左上角的坐标。

⑦pygame.mouse.get_pos() 这个函数很简单,就是返回一个元组,包含了当前鼠标的坐标信息。

⑧Pygame 的事件交由 pygame.event 模块进行管理。通过 pygame.event.get() 方法可以获得事件队列。
我们通过循环遍历事件队列来判断我们需要的事件是否被触发,从而执行相应的操作。
常见的事件如下:
事件 说明
QUIT 关闭程序
KEYDOWN 按键按下
MOUSEBUTTONDOWN 鼠标按键按下
MOUSEMOTION 鼠标移动
MOUSEBUTTONUP 鼠标按键松开

3.项目实例

代码较多,需要慢慢理解

# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
import math

class Brush:
    def __init__(self, screen):
        # pygame.Surface 对象
        self.screen = screen
        self.color = (0, 0, 0)
        # 初始时候默认设置画笔大小为 1
        self.size = 1
        self.drawing = False
        self.last_pos = None
        # 如果 style 是 True ,则采用 png 笔刷
        # 若是 style 为 False ,则采用一般的铅笔画笔
        self.style = True
        # 加载刷子的样式
        self.brush = pygame.image.load("images/brush.png").convert_alpha()
        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 < 1:
            size = 1
        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 range(self.brush.get_width()):
            for j in range(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):
                if self.style:
                    self.screen.blit(self.brush_now, p)
                else:
                    pygame.draw.circle(self.screen, self.color, p, self.size)
            self.last_pos = pos

    # 获取前一个点与当前点之间的所有需要绘制的点
    def _get_points(self, pos):
        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 range(int(length)):
            points.append((points[-1][0] + step_x, points[-1][1] + step_y))
        # 对 points 中的点坐标进行四舍五入取整
        points = map(lambda x: (int(0.5 + x[0]), int(0.5 + x[1])), points)
        # 去除坐标相同的点
        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("images/pen1.png").convert_alpha(),
            pygame.image.load("images/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("images/big.png").convert_alpha(),
            pygame.image.load("images/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):
        # 绘制画笔样式按钮
        for (i, img) in enumerate(self.pens):
            self.screen.blit(img, self.pens_rect[i].topleft)
        # 绘制 + - 按钮
        for (i, img) in enumerate(self.sizes):
            self.screen.blit(img, self.sizes_rect[i].topleft)
        # 绘制用于实时展示笔刷的小窗口
        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
        # 如果当前画笔为 png 笔刷,则在窗口中展示笔刷
        # 如果为铅笔,则在窗口中绘制原点
        if self.brush.get_brush_style():
            x = x - size
            y = y - size
            self.screen.blit(self.brush.get_current_brush(), (x, y))
        else:
            # BUG
            pygame.draw.circle(self.screen,
                               self.brush.get_color(), (x, y), size)
        # 绘制色块
        for (i, rgb) in enumerate(self.colors):
            pygame.draw.rect(self.screen, rgb, self.colors_rect[i])

    # 定义菜单按钮的点击响应
    def click_button(self, pos):
        # 笔刷
        for (i, rect) in enumerate(self.pens_rect):
            if rect.collidepoint(pos):
                self.brush.set_brush_style(bool(i))
                return True
        # 笔刷大小
        for (i, rect) in enumerate(self.sizes_rect):
            if rect.collidepoint(pos):
                # 画笔大小的每次改变量为 1
                if i:
                    self.brush.set_size(self.brush.get_size() - 1)
                else:
                    self.brush.set_size(self.brush.get_size() + 1)
                return True
        # 颜色
        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")
        # 创建 Clock 对象
        self.clock = pygame.time.Clock()
        # 创建 Brush 对象
        self.brush = Brush(self.screen)
        # 创建 Menu 对象,并设置了默认笔刷
        self.menu = Menu(self.screen)
        self.menu.set_brush(self.brush)

    def run(self):
        self.screen.fill((255, 255, 255))
        # 程序的主体是一个循环,不断对界面进行重绘,直到监听到结束事件才结束循环
        while True:
            # 设置帧率
            self.clock.tick(30)
            # 监听事件
            for event in pygame.event.get():
                # 结束事件
                if event.type == QUIT:
                    return
                # 键盘按键事件
                elif event.type == KEYDOWN:
                    # 按下 ESC 键,清屏
                    if event.key == K_ESCAPE:
                        self.screen.fill((255, 255, 255))
                # 鼠标按下事件
                elif event.type == MOUSEBUTTONDOWN:
                    # 若是当前鼠标位于菜单中,则忽略掉该事件
                    # 否则调用 start_draw 设置画笔的 drawing 标志为 True
                    if event.pos[0] <= 74 and self.menu.click_button(event.pos):
                        pass
                    else:
                        self.brush.start_draw(event.pos)
                # 鼠标移动事件
                elif event.type == MOUSEMOTION:
                    self.brush.draw(event.pos)
                # 松开鼠标按键事件
                elif event.type == MOUSEBUTTONUP:
                    # 调用 end_draw 设置画笔的 drawing 标志为 False
                    self.brush.end_draw()
            # 绘制菜单按钮
            self.menu.draw()
            # 刷新窗口
            pygame.display.update()

def main():
    app = Painter()
    app.run()

if __name__ == '__main__':
    main()

pygame.Rect.collidepoint((x, y)) 函数的使用非常简单,只要传入一个坐标元组作为参数,如果该点位于 pygame.Rect 对象中的话,函数返回 True ,否则返回 False

直接运行这个文件,效果如下
Pygame 实现一个简单画图板_第3张图片

你可能感兴趣的:(python,pygame,画图板,画板)