Python第83講:Pygame—提高游戲的顏值3(圖像透明度的處理)


  圖像是特定像素的組合,而Surface 對象是Pygame里面對圖像的描述,在Pygame里面到處都是Surface 對象,set_mode() 返回的是一個Surface對象,在界面上打印文字也需要先把文字渲染成 Surface 對象,然后再貼上去,小蛇在上面爬呀爬,其實就是不斷調整Surface對象上的一些特定的像素的位置,把小蛇所在位置的像素進行移動,就是小蛇在上面爬,就是調用 blit() 方法。imag.load() 方法載入圖像並會返回一個 Surface 對象,我們此前都是直接拿來用,並沒有進行任何轉換,這樣子就是效率比較低的做法。如果你希望你的Pygame可以盡可能高效的處理你的圖片,你應該在圖片載入之后立刻調用 convert() 方法進行轉換。

如:bg = pygame.image.load(“bg.jpg”).convert()

  事實上,游戲都是由各種不同的圖片組成的,例如說:背景是一個圖片、里面的主角是圖片、反派也是圖片、還有路人甲乙丙都是圖片。你總不可能用一個圓形或者矩陣畫一個主角吧。我們只能在現實中先用Photoshop 畫一個惟妙惟肖的主角,然后貼進去,然后使用 blit() 方法讓它移動。

有的人就很好奇了,不是說 image.load() 返回一個 Surface 對象嗎,那還轉換個毛線啊,這里的轉換只是像素格式的轉換,而不是說轉換為Surface 對象,因為image.load() 載入之后就是一個 Surface 對象,但是我們載入之后(例如我們載入一個JPG格式的圖片,JPG圖片也是由像素組成的,而這些像素都是有顏色的,另外我們還可以將這個JPG圖片保存為PNG,GIF格式,你會發現尺寸會發生改變了,這是因為里面的像素格式發生改變了,也就是說它組合這些像素,把它描述的形式發生改變了),Surface 也有它自己的像素格式,所以我們這里的轉換指的就是圖片的像素格式的轉換。

如果我們沒有在 image.load() 之后立刻對它進行轉換,但是轉換非常重要,Pygame也會你在 調用 blit() 方法時自動進行轉化,就是將一個圖片貼到另一個圖片之上的時候,因為兩個圖片要進行復制拷貝的操作,它們的像素格式必須相同,因此在每次 blit() 的時候,它都會強制轉換一次,這樣子效率就相當低了,與其讓它每次循環去轉換一次,我們還不如在載入時調用 convert() 方法轉換為 Surface 的像素格式。

雖然現在的CPU速度都很快,這一點細微的差別你可能看不出來,但是我們都希望我們的程序小一點、效率高一點。今后我們都會在 image.load() 之后立刻調用 convert()。

還有一個就是 convert_alpha() 方法:

如:turtle = pygame.image.load(“python.png”).convert_alpha()

這兩個方法有什么區別呢?一般情況下,我們使用 RGB 來描述一個顏色,然而在游戲開發中,我們常常用到的是 RGBA (RGBA是代表Red(紅色) Green(綠色) Blue(藍色)和 Alpha的色彩空間)來描述。Alpha 通道是用來表示透明度的,A 占用一個字節,也就是8位(0-255,256種層次),用序號來索引,就是0-255,0表示完全透明,255表示完全不透明。

我們都知道,image.load() 支持多種格式的圖片導入,例如 gif、jpg、png等,這些都是當前流行的圖片格式,對於包含 Alpha 通道的圖片,我們就要用 convert_alpha() 方法來轉換格式了,其它的就用 conmvert() 方法。

我們也知道,jpg 格式的圖片是不包含 Alpha 通道的,因為它不能來表示透明。我們在做圖片的時候,我們知道,兩種常用的 透明格式就是 png和 gif 格式,而gif 還支持動圖,動圖在Pygame 里面是不能解析的,一般我們在Pygame 里面做的圖片都是以 png 圖片為主,因為 jpg 是有損的,你放大縮小它會損失精度,png 是無損壓縮。

jpg 是不支持透明的,所以我們載入這類圖片就用 convert();而 png 是支持透明的,所以載入就用 convert_alpha()。

大家可以看一下下面兩張圖片:

 

 

 

左邊是 png 格式的原圖,是透明背景的,當我另存為 jpg 格式時,背景就不透明了。

如果你載入左邊的透明 png 圖片,使用 convert_alpha() 方法轉換和使用 convert() 方法轉換,比較一下。

import pygame
import sys
 
pygame.init()
 
size = width, height = 900, 300
bg = (0, 255, 0) #為了便於區分,背景設為金色
 
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Python Demo")
 
turtle1 = pygame.image.load("turtle.png") #左圖
turtle2 = pygame.image.load("turtle.png").convert() #中圖
turtle3 = pygame.image.load("turtle.png").convert_alpha() #右圖
 
position1 = turtle1.get_rect()
position1.center = width // 6, height // 2#居左顯示
 
position2 = turtle2.get_rect()
position2.center = width // 2, height // 2#居中顯示
 
position3 = turtle3.get_rect()
position3.center = 5 * width // 6, height // 2#居右顯示
 
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
            
    screen.fill(bg)
    screen.blit(turtle1, position1)
    screen.blit(turtle2, position2)
    screen.blit(turtle3, position3)
        
    pygame.display.flip()
    
    clock.tick(30)

 

 

 這貌似跟正常出的結果不一樣,這可能是顏色本身的問題。(需要進一步研究~)

Pygame 支持三種透明度類型:colorkeys,surface alphas 和 pixel alphas(溫馨提示:colorkeys 是指設置圖像中的某個顏色值為透明(比如說上面的烏龜有很多種綠色,我把其中一種綠色變為透明,那么在這個圖片里面,與這個顏色相同的部分都不見了,取而代之的是背景的顏色,因為透明事實上就是把背景顯示出來。),surface alphas 是調整整個圖像的透明度,pixel alphas 則是獨立設置圖像中每一個像素的透明度)。

png 就是一個 piexl alphas,所以它每個像素都有一個 Alpha 通道,指定這個像素是否要變透明,透明度是多少。

surface alphas 可以和 colorkeys 混合使用,而 pixel alphas 不能和其他兩個混合。

說起來很復雜,其實說白了,convert() 方法轉換出來的 就支持 surface alphas 可以和 colorkeys 設置透明度,而且他們是可以混合設置的。而 convert_alpha() 方法轉換之后呢,就只支持 piexl alphas ,也就是說這個圖像本身每個像素就帶有Alpha 通道,我們載入一個帶有 Alpha 通道的圖片,我們會看到有一部分是透明的,就像我們上面的小烏龜,它的背景就是透明的。

我們來做一下實驗:

我們這里有兩張圖片,一種是 jpg,背景白色;一張是 png,背景透明。

我們首先載入這張 jpg 圖片,為了更好區分,我們加了一個背景。

import pygame
import sys
from pygame.locals import *
 
pygame.init()
 
size = width, height = 640, 480
bg = (0, 0, 0)
 
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Python Demo")
 
turtle = pygame.image.load("wagger.png").convert()
background = pygame.image.load("green_grue.jpg").convert()
position = turtle.get_rect()
position.center = width // 2, height // 2
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
 
    screen.blit(background, (0, 0))
    screen.blit(turtle, position)
    
    pygame.display.flip()
    
    clock.tick(30)

 

 

 

 現在分別使用 set_colorkey() 把所有的白絲變為透明,turtle.set_alpha(200)把整幅圖像變透明,以及同時使用兩者,來看一下效果:

import pygame
import sys
from pygame.locals import *

pygame.init()

size = height,width = 640,480
bg=(0,0,0)
clock = pygame.time.Clock()

screen = pygame.display.set_mode(size)

pygame.display.set_caption("遇見你真好")

turtle = pygame.image.load("wagger.png").convert()
bg = pygame.image.load("green_grue.jpg").convert()

position =turtle.get_rect()
position.center =  height // 2,width // 2

#############################################################
#試圖使用 set_colorkey() 把所有的白絲變為透明
turtle.set_colorkey((255, 255, 255))
#############################################################

#用 set_alpha() 方法來調節整個圖像的透明度為200
turtle.set_alpha(200)
#############################################################

while True:

    for event in pygame.event.get():
        if event.type == quit:
            sys.exit()

        screen.blit(bg,(0,0))
        screen.blit(turtle,position)
        

        pygame.display.flip()

        clock.tick(30)

 

 

 

 

 

 仔細觀察我們可以發現,set_colorkey()結果並不優秀,因為邊緣並不是純白色的,結果並不是我們想要的,並不理想。 set_alpha() 方法將整個圖片都變得微微透明了,但是這個把背景也帶上了,我們就想要小烏龜變透明,不想要白色邊框。兩種混合使用的話,效果依然不優秀。

 wagger.jpg的圖像沒有上述wagger.png圖像的效果好。由於我們這個 png 是帶 Alpha 通道的,而且我們在做這個圖片的時候,已經把它的背景給扣成透明的了,下面的陰影不透明,但是不影響美觀(是故意做出的效果)。我們想把整個小烏龜調為透明度 200,讓小烏龜有一種隱身的既視感。但是我們說這個是 piexl alphas,也就是每個像素都有一個 Alpha 通道,因此我們不能使用 turtle.set_alpha(200) 方法,我們可以使用 get_at() 方法來獲得單個像素的透明度,並且用 set_at() 方法來修改它。

這樣可以在獲取單個像素的透明度的情況下,

#############################################################
#嘗試是否能夠獲得 單個像素的透明度
 
print(turtle.get_at(position.center)) #獲取中間那個像素的顏色
 
#############################################################
>>> 
============================== RESTART ==============================
pygame 1.9.4
Hello from the pygame community. https://www.pygame.org/contribute.html
(130, 131, 26, 255)

利用循環把透明度設置為200。

#############################################################
for i in range(position.width):
    for j in range(position.height):
        temp = turtle.get_at((i, j))
        if temp[3] != 0:
            temp[3] = 200
        turtle.set_at((i, j), temp)
#############################################################

結果如下:

 

  PS:上圖有一個小瑕疵,不知道你們看出來了沒有,就是青蛙不能居中顯示,具體的原因和解決方法有待研究。

效果還是不優秀啊。而且就算效果優秀了,你這樣一個一個像素的來計算透明度,效率未免也很低

沒問題,程序是死的,人是活的,我這里教大家一個新技能來 搞定這個問題:

先看一下解決方案,我們再來分析:

import pygame
import sys
from pygame.locals import *
 
pygame.init()
 
size = width, height = 800, 600
bg = (0, 0, 0)
 
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Python Demo")
 
turtle = pygame.image.load("wagger.png").convert_alpha()
background  = pygame.image.load("white.png").convert()
position = turtle.get_rect()
position.center = width // 2, height // 2
 
#############################################################
def blit_alpha(target, source, location, opacity):
    x = location[0]
    y = location[1]
    temp = pygame.Surface((source.get_width(), source.get_height())).convert()
    temp.blit(target, (-x, -y ))
    temp.blit(source, (0, 0))
    temp.set_alpha(opacity)        
    target.blit(temp, location)
#############################################################
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
 
    screen.blit(background, (0, 0))
    ############################################################
    #screen.blit(turtle, position)
    blit_alpha(screen, turtle, position, 200)
    ############################################################
    
    pygame.display.flip()
    
    clock.tick(30)

 

 PS:這效果又跟平常的不一樣,需要課后研究深入了解一下。

據說,效果是這樣的:

這是如何實現的呢?我們來逐句分析一下:

我們封裝了一個名為 blit_alpha() 的函數,下面我們就不要調用 blit() 方法了,而是調用 blit_alpha(),因為我們在函數的末尾 調用了 blit() 方法。

在函數中,我們首先創建一個 Surface ,傳進來的是一個帶 Alpha 的,我們將其變為不帶 Alpha 的,其實我們只是需要它的一個矩形區域,

即:temp = pygame.Surface((source.get_width(), source.get_height())).convert()

temp 就是下圖種黑色矩形的位置,其實就是小烏龜圖片的覆蓋區域范圍。

然后我們在上邊繪制背景

即:temp.blit(target, (-x, -y ))

為什么是(-x, -y)呢?

我們知道, x = location[0],y = location[1],而形參 location對應的實參是 position,就是小烏龜圖像的位置,其實(x, y)就是下圖的A點,這個位置是相對於頂點B(0,0)(對於screen 來說,B就是(0,0))來說的,而 temp.blit()是相對於小烏龜圖像的來粘貼背景,所以B相對於A點來說,就是(-x,-y)。

 

然后我們就在A的位置貼上背景透明的 小烏龜圖片

即:temp.blit(source, (0, 0))

然后我們將整個 temp 區域設為 透明度 200,其實現在的整個 temp 區域(Surface 對象)就是A區域大小,然后背景是該區域的草地,主角是小烏龜的一幅圖片。如圖所示:(這個就是目前的temp了)

 

 

我們將這個圖片 透明度設為 200。

即:temp.set_alpha(opacity) #opacity=200

這就巧妙的避開了 帶Alpha通道的Surface 不能調用set_alpha()方法的問題。因為現在的temp 是不帶Alpha通道的。

然后我們就把這個透明度為200的圖片貼在背景屏幕上,

即:target.blit(temp, location)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM