Malen mit Pygame

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

Ich habe versucht mit Pygame zu malen. Das heißt, ich habe versucht einen Kreis zu erstellen, den man hinter der Maus herziehen kann, sodass eine Linie entsteht. Das Problem ist, dass beim Klicken nur ein Kreis entsteht.

Hier der Code:

Code: Alles auswählen

import pygame
from pygame.locals import*
from sys import exit
 
class Pen:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.start = (300,200)
        pygame.init()
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.screen.fill((255, 255, 255))
        self.mouse_pos = pygame.mouse.get_pos()
        self.draw_color = (0,0,0)
        pygame.display.update()


  
    def circle(self):
        pygame.draw.circle(self.screen, (0,0,0), (self.mouse_pos), 10)
        pygame.display.update()
 
    def event_loop(self):
        while True:

            self.mouse_pos = pygame.mouse.get_pos()
            
            for event in pygame.event.get():
                if event.type == QUIT:
                    exit()

                if event.type ==  MOUSEBUTTONDOWN:
                    self.circle()
 
if __name__ == '__main__':
    p = Pen(800,600)
    p.event_loop()
Zuletzt geändert von Anonymous am Sonntag 4. Juli 2010, 13:37, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hallo blob,

naja, das Problem liegt doch auf der Hand: Das Ereignis MOUSEBUTTONDOWN wird nur einmal gesendet - und nicht immer wieder. Entsprechend wird auch nur einmal der Kreis gezeichnet.
Ich habe das immer gelöst, indem ich eine Variabel "drawing" genutzt habe, die beim Event "MOUSEBUTTONDOWN" auf "True" und beim Event MOUSEBUTTONUP auf "False" gesetzt wurde.

Du zeichnest den Kreis dann nicht, wenn der Mausknopf gedrückt wurde, sondern solange "drawing" wahr ist.

Kurz:

Code: Alles auswählen

#!/usr/bin/env python
# coding:utf-8

import pygame
from pygame.locals import*
from sys import exit
 
class Pen:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.start = (300,200)
        pygame.init()
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.screen.fill((255, 255, 255))
        self.mouse_pos = pygame.mouse.get_pos()
        self.draw_color = (0,0,0)
        pygame.display.update()

    def circle(self):
        pygame.draw.circle(self.screen, (0,0,0), (self.mouse_pos), 10)
        pygame.display.update()
 
    def event_loop(self):
        drawing = False
        
        while True:

            self.mouse_pos = pygame.mouse.get_pos()
            
            for event in pygame.event.get():
                if event.type == QUIT:
                    exit()

                if event.type ==  MOUSEBUTTONDOWN:
                    drawing = True
                if event.type == MOUSEBUTTONUP:
                    drawing = False
            
            if drawing:    
                self.circle()
 
if __name__ == '__main__':
    p = Pen(800,600)
    p.event_loop()
Was mir noch auffällt: Deine Klasse Pen ist in sich nicht ganz schlüssig: Eigentlich würde ich erwarten, dass es eine Klasse "Zeichenbrett" gibt und die "Zeichenstift"-Logik davon unabhängig ist. Oder ich würde die Klasse in "Zeichenbrett" umbenennen ;).

Noch wichtiger: Bei der jetzigen Lösung wird deine Linie bei schnellem Zeichnen Lücken enthalten. Der übliche Lösungsweg ist, nicht einzelne Punkte zu zeichnen, sondern jeweils Linien von der vorherigen Maus-Position zur jetzigen Maus-Position. (Nur) so bekommst du eine vernünftige Zeichenstift-Funktion hin.

Schönen Gruß,

brb
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

Das ganze soll eigentlich eine Art Paint werden. Also später möchte ich noch ein Menü, in dem man die Farbe auswählen kann, machen, usw. .
Zu den Linien:
Meinst du pygame.draw.line? Damit würde es nähmlich nicht gehen.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hallo blob,

ich habe pygame.draw.line() jetzt noch nicht benutzt - mit Pygame habe ich allgemein wenig gemacht. Aber warum würde es damit nicht gehen? Prinzipiell solltest du wie folgt vorgehen:

1. Wenn Maustaste gedrückt wird: "drawing" auf "True" setzen und die Koordinaten der Maus in einer Variable speichern. Vll. direkt einen Punkt zeichnen, um den Benutzer eine Rückmeldung zu geben.
2. Wenn drawing "True" ist und die derzeitigen Koordinaten anders sind als die gespeicherten: Eine Linie von den alten Koordinaten zu den neuen Koordinaten zeichnen. Hier werden also keine Punkte mehr gezeichnet!
3. Wenn Maustaste losgelassen wird: "drawing" auf "False" setzen.

Ohne es wirklich zu wissen behaupte ich einfach mal, dass die meisten Zeichenprogramme (genau) so Zeichenstift-Funktionen implementieren. Versuche einfach mal mit deinem jetzigen Code sehr schnell irgendwas zu kritzeln - es werden überall hässliche Lücken entstehen.

Wie gesagt: Vielleicht meintest du ja auch was ganz anderes und ich habe dich falsch verstanden, dann frag einfach nochmal nach ;).

Besten Gruß,

brb

//edit: Grundsätzlich könnte man sich auch überlegen, ob man so ein Malprogramm nicht lieber mit GTK oder QT macht (abhängig von OS und Fenstermanager). Immerhin möchte man ja sehr wahrscheinlich auch typische Widgets und Fenster in seinem Malprogramm nutzen - etwa Slider oder Comboboxen. In Kombination mit der DrawingArea (GTK) und vll. Cairo könnte man da vll. auch ein mächtiges Zeichenprogramm erstellen.
Aber zum Experimentieren und Tüfteln ist Pygame natürlich eine schöne Sache - ich wollte es nur mal erwähnt haben ;)
Zuletzt geändert von Barabbas am Montag 5. Juli 2010, 09:45, insgesamt 1-mal geändert.
BlackJack

@Barabbas: PyGame's Funktion zum Linien zeichnen kann keine "Pinsel" verwenden.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

BlackJack hat geschrieben:@Barabbas: Die PyGame's Funktion zum Linien zeichnen kann keine "Pinsel" verwenden.
Achso, dann muss man das vermutlich "emulieren", indem man selbst mit dem circle-Tool entsprechende Linien zeichnet (in einer Schleife z.B.)?
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

Ich habe das jetzt mal so probiert:

Code: Alles auswählen

import pygame
from pygame.locals import*
from sys import exit
 
class  DrawTablet(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.start = (300,200)
        pygame.init()
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.screen.fill((255, 255, 255))
        self.mouse_pos1 = pygame.mouse.get_pos()
        self.draw_color = (0,0,0)
        pygame.display.update()

    def circle(self):
        pygame.draw.circle(self.screen, (0,0,0), (self.mouse_pos), 10)
        pygame.display.update()

    def draw_line(self):
        pygame.draw.aaline(self.screen, (0,0,0), self.mouse_pos1, self.mouse_pos2)
        self.mouse_pos1 = self.mouse_pos2
        pygame.display.update()
 
    def event_loop(self):
        
        drawing = False
       
        while True:
           
            for event in pygame.event.get():
                if event.type == QUIT:
                    exit()

                if event.type ==  MOUSEBUTTONDOWN:
                    drawing = True
                    self.mouse_pos2 = pygame.mouse.get_pos()
                    
                    
                if event.type == MOUSEBUTTONUP:
                    drawing = False
           
            if drawing:    
                self.draw_line()
    
 
if __name__ == '__main__':
    p = DrawTablet(800,600)
    p.event_loop()

Allerdings folgt die Linie der Maus nicht wenn ich sie gedrückt halte. Ich muss immer klicken, damit eine Linie gezeichntet wird.

Wenn ich den Code so ändere, folgt die Linie der Maus, allerdings muss man sie dabei nicht gedrückt halten:

Code: Alles auswählen

if event.type ==  MOUSEMOTION:
                    drawing = True
                    self.mouse_pos2 = pygame.mouse.get_pos()
                    self.draw_line()
Was muss ich machen, damit mir die Linie bei gedrückter Maustaste folgt?


EDIT:

Kann mir einer die Funktion pygame.Rect() genauer erklären? Die Erklärung auf pygame.org verstehe ich nicht.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

blob hat geschrieben:Allerdings folgt die Linie der Maus nicht wenn ich sie gedrückt halte. Ich muss immer klicken, damit eine Linie gezeichntet wird.
Dann überleg mal wann du von welchem Punkt zu welchem Punkt die Linie ziehst und denk speziell darüber nach, was self.mouse_pos2 wohl für einen Wert enthält.
blob hat geschrieben:Kann mir einer die Funktion pygame.Rect() genauer erklären? Die Erklärung auf pygame.org verstehe ich nicht.
pygame.Rect ist keine Funktion sondern eine Klasse. Diese Klasse wird an mehreren anderen Stellen in Pygame verwendet. Du benötigst sie zum Beispiel um ein Rechteck zu erstellen, das du dann mit pygame.draw.rect() zeichnen kannst.
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

self.mouse_pos2 enthält die aktuelle Mausposition, oder nicht? Aber die Linie wird eben nur gezeichnet, wenn ich mit der Maus klicke.

Und pygame.Rect(), wollte ich für das Menü benutzen. Wenn man z.B auf rot klickt ändert sich die Farbe und das wollte ich mit pygame.Rect() machen, oder geht das damit nicht?
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

blob hat geschrieben:self.mouse_pos2 enthält die aktuelle Mausposition, oder nicht?
Nein. Schau mal, wann der Wert überhaupt gesetzt wird.
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

Ah, ok. Ich hab self.mouse_pos2 jetzt aus MOUSEBUTTONDOWN genommen und es klappt.
Danke.

Weißt du, wie ich das mit dem Menü realisieren kann?
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Wie ich das oben schon geschrieben habe: Bevor du dir mit Pygame eine eigene Menü-Klasse zimmerst und dann noch Knöpfe, Dialoge etc brauchst.... erstelle ein richtiges UI mit GTK, QT oder wxPython. Wie viele Zeichenprogramme kennst du, die mit DirectDraw, OpenGL oder wasauchimmer Menüstrukturen nachbilden?

Ich selbst habe bisher noch nie versucht mit Python ein Zeichenprogramm zu schreiben - aber Pygame halte ich da echt für den falschen Weg.

Schönen Gruß,

brb
BlackJack

@Barabbas: Blender und Wings3D machen das. :-)
blob
User
Beiträge: 19
Registriert: Mittwoch 14. April 2010, 20:09

Dieses Programm wurde auch nur mit Pygame geschrieben : http://pygame.org/project-PaintBrush-1280-2230.html

Aber ich werde es mal mit GTK, QT oder wxPython versuchen. Welches würdest du mir empfehlen?
Pa5tA
User
Beiträge: 21
Registriert: Sonntag 1. August 2010, 16:37

Benutzt du GNOME oder KDE oder Windows? Bei GNOME wuerde ich GTK empfehlen, beim Rest Qt (KDE ist denke ich mal klar und bei Windows ist Qt afaik besser integriert).
MGS_Freak
User
Beiträge: 35
Registriert: Donnerstag 13. Januar 2011, 13:50
Wohnort: Schweiz

Hallo zusammen

Da ich selber auch so ein Malprogramm am schreiben bin wollte ich keinen neuen Post erstellen, hänge mein Problem einfach hier an.

Meine Klasse sieht (wohl nicht überraschend) sehr ähnlich aus, einfach ohne draw_line()-Funktion. Sprich bei mir zeichnets Kreise bei gedrückter Maustaste wie hier am Anfang. Wie man die hässlichen Leerstellen weg kriegt hab ich mir so vorgestellt: alle Mauspositionen während gedrückter Maustaste sollen abgefangen und in einer Liste gespeichert werden. Eine Prüfung soll ergeben welche Positionen "leer" sind und diese dann ausfüllen. Klingt in meinem Kopf schon mal gut, allerdings raucht mir der Schädel was die Umsetzung betrifft! Wie kann ich alle Mauspositionen (bei gedrückter Taste) abfangen?

Um einen Gedankeanstoss freue ich mich,
Euer MGS_Freak
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

In dem du in deiner Hauptschleife, dort wo du die events abrufst (pygame.event.get()), einfache bei dem "event.type == pygame.MOUSEBUTTONDOWN" alle events an eine Liste oder Queue per append/put anhängst. Diese kannst du dann nach dem Eventhandling auswerten.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
MGS_Freak
User
Beiträge: 35
Registriert: Donnerstag 13. Januar 2011, 13:50
Wohnort: Schweiz

Xynon1 hat geschrieben:In dem du in deiner Hauptschleife, dort wo du die events abrufst (pygame.event.get()), einfache bei dem "event.type == pygame.MOUSEBUTTONDOWN" alle events an eine Liste oder Queue per append/put anhängst. Diese kannst du dann nach dem Eventhandling auswerten.
Echt genial wie schnell man hier immer Antwort kriegt - vielen Dank!
So, hab das mal versucht, dummerweise speichert's bei mir nur die Positionen wenn ich klicke, wenn ich die Taste halte zeichnet's zwar weiter, speichert allerdings nur die erste Position (dafür immer wieder). Was mache ich falsch?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Stimmt, sry.
Dann nimm einfach "event.type == MOUSEMOTION" und dann auf die gedrückte Taste mit "pygame.mouse.get_pressed()"* prüfen.

* Gibt dir ein Tuple mit den Statuswerten der Tasten 1, 2 und 3. Ist dieser 0, so ist die Taste nicht gedrückt.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
MGS_Freak
User
Beiträge: 35
Registriert: Donnerstag 13. Januar 2011, 13:50
Wohnort: Schweiz

Xynon1 hat geschrieben:Stimmt, sry.
Dann nimm einfach "event.type == MOUSEMOTION" und dann auf die gedrückte Taste mit "pygame.mouse.get_pressed()"* prüfen.

* Gibt dir ein Tuple mit den Statuswerten der Tasten 1, 2 und 3. Ist dieser 0, so ist die Taste nicht gedrückt.
null problemo, mousemotion: sehr interessant, hab's bisher nicht wirklich mitgekriegt. teste ich mal, sollte alles glatt gehen freut's mich, ansonsten muss du wohl nochmals dran glauben ;-)
Antworten