趁熱打鐵趕快把我們這個畫板完成吧~
……鼠繪無能,不準笑!所有評論中“噗嗤”、“畫的好搓啊”、“畫的好棒啊”等,都會被我無情撲殺掉!但是能告訴我怎樣畫可以更漂亮的話,絕對歡迎。
上次講Brush的時候,因為覺得太簡單把color設定跳過了,現在實際寫的時候才發現,因為我們設定了顏色需要對刷子也有效,所以實際上set_color方法還有一點點收尾工作需要做:
1 2 3 4 5 6 |
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值,其實也很簡單就是了……
按鈕選單部分
上圖可以看到,按鈕部分分別為鉛筆、毛筆、尺寸大小、(當前樣式)、顏色選擇者幾個組成。我們只以筆刷選擇為例講解一下,其他的都是類似的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 初始化部分 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也好久不用了……如果有哪裡寫的有問題(沒有就怪了),還請不吝指出!
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 |
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
- 影像繪製和圖形繪製(是不是有人不明白“影像”和“圖形”的區別?簡單的說,影像指的是那些圖片檔案,圖形指的是用命令畫出來形狀)
- 按鈕的實現(新內容)
認真的朋友一定發現了本次沒有涉及到動畫和聲音,畢竟這次只是簡單的例子,太複雜了不免讓人生畏。
實際用一下,會發現這個例子有很多不足,比如畫錯了不能撤消,只能用白色畫掉(當然真正的藝術家都不用橡皮來著);調節畫筆大小的時候太麻煩,點一下跳個0.5(你可以試著加上快捷鍵);視窗尺寸不能變,圖片不能開啟不能儲存……不足一大堆啊,不說了,自己都要傷心了~ 但是隻要你掌握了原理,所有的自己期望的功能都能慢慢實現。看著手中的程式慢慢成長,不是很有成就感麼?它甚至有可能變的史無前例的強大,難道不是麼?
下一個實戰是什麼?盡請期待~
# 另,非常歡迎有繪圖高手用這個畫個漂亮點的給我,我好把題頭的圖片換掉,太嚇人了……