Maskieren von Surfaces (pygame)

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Arachnophobius
User
Beiträge: 23
Registriert: Mittwoch 7. April 2010, 11:51

Hallo miteinander,

ich habe eine kleine Frage eher der Logik halber, ich bin nämlich
mit meinen grauen Zellen hier am Ende. Ich suche eine Möglichkeit,
eine bestimmte stelle innerhalb eines Surfaces durch ein anderes
Surface zu 'maskieren'. Wobei maskieren vllt nicht ganz der richtige
Ausdruck ist, mal zur Veranschaulichung:
surface_one = ein mit diversen Formen/Farben gefüllter Bereich
surface_two = eine einzelne Form zur Maskierung
Nun soll der Teil von surface_two, welcher Daten enthält, an der Stelle von
surface_one Daten entfernen, wo erst genanntes Surface auftrifft.
Sagen wir also surface_two fällt auf surface_one und bestimmt damit
einen Bereich der von surface_one entfernt wird, basierend auf der
Tiefe die surface_two überlappt.

Allerdings stoße ich bei der surfarray methode an eine kleine Grenze,
denn wie soll ich hier feststellen, an welcher Position sich die Pixel gerade befinden?
Lasse ich einfach nur darüber iterieren, wird logischerweise nur der erste Teil verglichen
und der Rest wird ignoriert weil keine Daten zum Vergleichen mehr vorhanden sind.
Ich habe daraufhin versucht, im Modul 'pygame.mask' etwas passendes zu finden,
allerdings komme ich hier zu keinem Ergebnis.

Vielleicht ist die Lösung absolut trivial, aber ich komme einfach nicht darauf.

Bin über kleine Schubser in die richtige Richtung dankbar.

Mit freundlichen Grüßen,

Arach
Der obige Teil ist jetzt zum Teil bereits gelöst. Es hakt zwar noch an einer
schnellen und sauberen Umsetzung, aber das kriege ich hoffentlich
auch noch in den Griff. Ich habe jetzt zu Testzwecken einfach die
zu 'maskierenden' Elemente auf zwei unterschiedliche fiktive Layer
gesetzt (in form von Surfaces) und zeichne lediglich an eine bestimmte
Stelle das Objekt, mit dem ich die untere Fläche maskieren möchte.
Später soll dies aus Performance Gründen dann in in kleine subsurfaces
unterteil werden, da sonst eine Maskierung einer 1280x1024 Fläche
relativ rechenaufwendig wäre. Nun hat sich allerdings ein zweites
Problem aufgetan. Ich zeig euch wohl besser erstmal mein Testskript
(bitte ignoriert erstmal das absolut falsche Programmieren an dieser Stelle,
es geht mir jetzt nur eben um eine bestimmte Funktion innerhalb
des skripts):

Code: Alles auswählen

# Testmodul for masking Surfaces

import pygame
import pygame.locals as pgl

layer_one = pygame.Surface((50,50))
layer_two = pygame.Surface((50,50))

pygame.draw.circle(layer_two,(100,255,100),(2,2),15)
layer_one.fill((255,200,200))

screen = pygame.display.set_mode((50,50))
clock = pygame.time.Clock()
run = True

mask_object = pygame.surfarray.pixels3d(layer_two)
base_object = pygame.surfarray.pixels3d(layer_one)

index = [0,0,0]

for element in base_object:
    for part in element:
        for parts in part:
            if mask_object[index[0]][index[1]][index[2]] != 0:
                #print('Es wurde folgender Pixel deleted: ')
                #print('%i'%(parts))
                #print('In Index: %i %i %i'%(index[0],index[1],index[2]))
                #print('Von mask: %i'%(mask_object[index[0]][index[1]][index[2]]))
                base_object[index[0]][index[1]][index[2]] = 0
            index[2] = index[2] + 1
        index[2] = 0
        index[1] = index[1] + 1
    index[1] = 0
    index[0] = index[0] + 1
# hier wirds merkwürdig, ich habe trotz aufruf von
# .unlock eine ValueError Excepion bezüglich blockierter
# surfaces. es zeigt stets eine pygame._view.View
# instance auf diese surfaces.
print(layer_one.get_locks())
layer_one.unlock()
layer_two.unlock()
while run:
    clock.tick(20)
    for event in pygame.event.get():
        if event.type == pgl.QUIT:
            run = False
    screen.fill((0,0,0))
    # screen.blit(layer_one,(0,0))
    pygame.surfarray.blit_array(screen,base_object)
    pygame.display.flip()
pygame.quit()
Resultierender Traceback:

Code: Alles auswählen

Traceback (most recent call last):
  File "D:/python_projekte/pygame_dev/development_files/array_test.py", line 53, in <module>
    screen.blit(layer_one,(0,0))
error: Surfaces must not be locked during blit
Trotz der Aufrufe der pygame.Surface.unlock() methoden, bleibt vor
und nach dem Aufruf stets eine Instanz von pygame._view.View
zurück, die es mir nicht erlaubt, dass Surface ohne die
methode pygame.surfarray.blit_array zu zeichnen.

Ich hoffe man kann mir im neuen Sachverhalt helfen.

MfG Arach
deets

Ich hab' erst jetzt dein Update gesehen, halte deine Vorgehensweise aber fuer falsch - du solltest Numpy-Arrays auch wie gedachth verwenden, und einfach eine Multiplikation von Bild-Alpha mit Masken-Alpha durchfuehren. Wenn das zwischen 0..1.0 liegt, kannst du auch noch nette halb-transparenzeffekte erreichen. Und performanter isses auch ;)

Code: Alles auswählen


import pygame, os
from pygame.locals import *
from math import sin
import time
from random import random
from math import cos

WIDTH, HEIGHT = (640, 480)

FPS = 30


class Ball(object):


    def __init__(self, pos, width=20, height=20):
        self.image = pygame.Surface((width, height), flags=pygame.SRCALPHA)
        self.image.fill((255, 255, 255, 100))
        self.startpos = self.pos = pos
        self.t = 0.0

    def update(self, world, elapsed):
        self.t += elapsed
        self.pos = sin(self.t) * 100 + self.startpos[0], sin(self.t * 1.5) * 100 + self.startpos[1]


    @property
    def rect(self):
        return self.image.get_rect().move(*self.pos)
    
    

class Mask(object):
    def __init__(self, pos, width=80, height=80):
        self.image = pygame.Surface((width, height), flags=pygame.SRCALPHA)
        self.image.fill((255, 255, 0, 255))
        alpha = pygame.surfarray.pixels_alpha(self.image)

        for x in xrange(width):
            for y in xrange(height):
                if (x+y) % 2:
                    alpha[x, y] = 0
        del alpha
        
        self.pos = pos
        self.mask = pygame.surfarray.array_alpha(self.image)
        self.rect = pygame.Rect(pos, (width, height))


class World(object):

    def __init__(self, display):
        self.display = display
        self.masks = []
        self.balls = []
        self.show_masks = False
        self.speed = 1.0


    def toggle_mask_display(self):
        self.show_masks = not self.show_masks

    

    def update(self, elapsed):
        elapsed = elapsed * self.speed
        
        for ball in self.balls:
            ball.update(self, elapsed)

        for ball in self.balls:
            rect = ball.rect
            olmasks = [m for m in self.masks if m.rect.colliderect(rect)]
            if not olmasks:
                ball_image = ball.image
            else:
                ball_image = ball.image.copy()
                alpha = pygame.surfarray.pixels_alpha(ball_image)
                for mask in olmasks:
                    overlap = mask.rect.clip(rect)
                    left_index = max(overlap.left - rect.left, 0)
                    right_index = left_index + overlap.width
                    top_index = max(overlap.top - rect.top, 0)
                    bottom_index = top_index + overlap.height

                    left_mask_index = max(overlap.left - mask.rect.left, 0)
                    right_mask_index = left_mask_index + overlap.width
                    top_mask_index = max(overlap.top - mask.rect.top, 0)
                    bottom_mask_index = top_mask_index + overlap.height

                    alpha[left_index:right_index,top_index:bottom_index] *= mask.mask[left_mask_index:right_mask_index, top_mask_index:bottom_mask_index]
                    
                del alpha # unlock ball_image!

            self.display.blit(ball_image, ball.pos)        

            if self.show_masks:
                for m in self.masks:
                    self.display.blit(m.image, m.pos)

    

def main():
    # initialize and setup screen
    pygame.init()

    screen = pygame.display.set_mode((WIDTH, HEIGHT), HWSURFACE|DOUBLEBUF)


    world = World(screen)

    world.balls.append(Ball((WIDTH/2, HEIGHT/ 2)))
    world.masks.append(Mask((WIDTH/2, HEIGHT/ 2)))

    stopevents = QUIT, #, KEYDOWN, MOUSEBUTTONDOWN

    now = time.time()
    while True:
        elapsed = time.time() - now
        now += elapsed
        for e in pygame.event.get():
            if e.type == KEYDOWN:
                if e.key == K_ESCAPE:
                    return
                if e.key == K_UP:
                    world.speed += .1
                if e.key == K_DOWN:
                    world.speed -= .1
            if e.type == KEYUP:
                if e.key == K_m:
                    world.toggle_mask_display()
            if e.type in stopevents:
                return

        screen.fill((0, 40, 0))
        world.update(elapsed)
        pygame.display.flip()
        pygame.time.wait(int(1.0 / FPS * 1000))
        

if __name__ == '__main__':
    main()
Arachnophobius
User
Beiträge: 23
Registriert: Mittwoch 7. April 2010, 11:51

deets hat geschrieben:Ich hab' erst jetzt dein Update gesehen, halte deine Vorgehensweise aber fuer falsch - du solltest Numpy-Arrays auch wie gedachth verwenden, und einfach eine Multiplikation von Bild-Alpha mit Masken-Alpha durchfuehren. Wenn das zwischen 0..1.0 liegt, kannst du auch noch nette halb-transparenzeffekte erreichen. Und performanter isses auch ;)
Moin deets,

erstmal danke für deine Antwort. Das mit den Masken und der Pixel Alpha methode
kam mir heute zwar in den Sinn, war mir aber nicht sicher. Das mit der Performance
verbesserung ist schonmal optimal. Und wenigstens scheint dein Skript auch
mit der Surface.blit methode zu funktionieren ohne eine Exception zu werfen.

Nur wüsste ich gern wieso in meinem Skript diese Instanz ständig übrigbleibt, obwohl
das Array gelöscht und die .unlock methode aufgerufen wurde. Liegt das nur
an der etwas ungewöhnliche Art und Weise wie ich es hier verwendet habe?

Danke nochmal für deine Antwort.

MfG Arach
Antworten