Slideshow mit TKinter und PIL

Fragen zu Tkinter.
Antworten
andieh
User
Beiträge: 4
Registriert: Montag 12. März 2007, 03:17
Wohnort: Offenbach
Kontaktdaten:

Dienstag 13. März 2007, 01:43

Servus Leute!

Ich habe eben mal eine kleine Slideshow gebastelt, weil ich so was für ein Programm brauche. Nur leider hab ich das Problem, dass die Bilder ziemlich flackern, woran kann das denn liegen? Hab die Tage erst mit Python und Tkinter angefangen, aber es gefällt mir eigentlich sehr gut. Geht das nicht besser, oder mach ich nur etwas falsch.

Der Kode ist noch nicht sehr sauber, bitte entschuldigt das.

Die Klasse Move liest ein Verzeichnis ein, in meinem Fall (und wenn mans nicht ändern will) einfach ein Verzeichnis media/slide1 erzeugen und ein paar Bilder reinpacken (müssen noch die gleiche Größe haben). Die Größe der Bilder kann es noch nicht anpassen, darauf freu ich mich schon :)

Code: Alles auswählen

import Tkinter as tk
from PIL import Image, ImageTk
from time import sleep
from thread import start_new_thread
from os import listdir

class Move:
	def __init__(self, dir, speed):
		self.cur_alpha = 0
		self.cur_count = 1
		self.count2 = 0
		self.speed = speed
		self.canvas = tk.Canvas(root, width=500, height=425)
		
		# verzeichnis einlesen
		self.filelist = []
		for file in listdir('media/slide1/'): 
			self.filelist.append("%s%s"%(dir,file))
		self.filecount = len(self.filelist)
		if self.filecount >= 2:
			# bild erzeugen
			self.img1 = Image.open(self.filelist[0])
			self.img2 = Image.open(self.filelist[1])
			img  = ImageTk.PhotoImage(self.img1)
			self.bild = self.canvas.create_image(50,50, image=img, anchor="nw")
			self.canvas.pack()
		
			print start_new_thread(self.image_smooth_action,())
		
	def image_smooth_action(self):
			try:
				while 1:
					self.chg_alpha()
					new = ImageTk.PhotoImage(Image.blend(self.img1, self.img2, self.cur_alpha))
					self.canvas.itemconfigure(self.bild, image=new)
					sleep(.3)
					
			except:
				pass
				
	def chg_alpha(self):
		# bild erst mal ein paar sekunden anzeigen
		if self.cur_alpha == 0 and self.count2 < 10:
			self.count2 += 1
		else:
			# ein bild langsam ueberblenden
			if self.cur_alpha < .9:
				self.cur_alpha += self.speed
			# bild ist komplett ueberblendet, naechstes anzeigen
			else:
				self.count2 = 0
				self.cur_alpha = 0
				if self.cur_count < self.filecount-1:
					self.cur_count +=1
					bild1 = self.cur_count-1
					bild2 = self.cur_count
				else:
					bild1 = self.cur_count
					bild2 = 0
					self.cur_count = 0
				self.img1 = Image.open(self.filelist[bild1])
				self.img2 = Image.open(self.filelist[bild2])
				new = ImageTk.PhotoImage(self.img1)
				self.canvas.itemconfigure(self.bild, image=new)					
		
# root fenster erzeugen
root = tk.Tk()
# slideshow objekt erzeugen
slideshow = Move("media/slide1/", .1)
# der dolle mainloop
root.mainloop()
Wäre schön, wenn mal einer drüber gucken könnte. Ich bin für jeden Tipp dankbar, wieso die Slideshow so komisch "ruckelt" und flackert.

Grüße und in Hoffnung auf eine Antwort
andieh
Benutzeravatar
Michael Schneider
User
Beiträge: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Dienstag 13. März 2007, 08:40

Hi Andieh,

das liegt höchstwahrscheinlich an der enormen Performance, die das Umrechnen und Neuanlegen von neuen Bildern und deren Anzeige kostet - in Kombination zum Umstand, dass kein double-buffering verwendet (also erst ein Bild gelöscht und dann das andere gezeichnet) wird. Das ist je nach Größe der Bilder und Rechner unterschiedlich. Ich habe nicht den langsamsten Rechner, komme aber beim Überblenden von 2 MPixel Bildern bei 5 fps schon an die Grenzen.

Du könntest also einerseits die Lade- und Rechenzeiten minimieren, und andererseits versuchen, das alte Bild nicht zu ersetzen, sondern vielmehr das Neue in das Alte hineinzukopieren (z.B. mit der paste-Methode).
Ich bin auch kein Profi, aber lass mich Dir noch ein paar Tips mit auf den Weg geben:

1. Eigentlich muss man PhotoImages extra speichern, weil sie, wenn nur local erzeugt (Z. 25), am Ende des Namensbereichs verloren gehen und Du nur einen leeren Canvas siehst. Warum das bei Deinem Original ging, ist mir immernoch ein Rätsel... ;o)

2. Wenn Du bestimmte oder alle Dateien eines Ordners bekommen möchtest, empfehle ich Dir die Funktion glob.glob(Pattern). Das Pattern ist der String, den Du eingeben würdest, wenn Du in Deinem OS in einer Shell Dateien suchen würdest. Z.B. "/var/tmp/*.jpg".

3. Der Abschnitt Z. 54 ff. lädt nach jedem Übergang beide Bilder noch einmal, obwohl das zweite Bild gleich dem Ersten ist. Darum vielleicht besser vertauschen, statt 4 MPixel-Bilder neu zu laden.

4. Die Zeilen 64 und 65 sind aus meiner Sicht überflüssig (Du zeigst ja im Idealfall schon das aktuelle Bild an) und führen zu eben diesen Flackereffekt.

5. Bitte KEINE BuiltIn-Funktionen wie "dir" überschreiben, auch wenn es möglich ist. Sonst kann es sehr schnell zu Verwechslungen kommen. In meinem Vorschlag habe ich es durch "directory" ersetzt und im glob benutzt.

6. Nur zur Info: ich habe das Attribut speed in der Sleepfunktion verwendet.

Hier mein Code. Ich hoffe, nicht zu sehr eingegriffen zu haben:

Code: Alles auswählen

import Tkinter as tk
from PIL import Image, ImageTk
from time import sleep
from thread import start_new_thread
from os.path import join
from glob import glob

class Move:
    def __init__(self, directory, speed):
        self.cur_alpha = 0
        self.cur_count = 1
        self.count2 = 0
        self.speed = speed
        self.canvas = tk.Canvas(root, width=500, height=425)
       
        # verzeichnis einlesen
        self.filelist = glob(join(directory, "*.JPG"))
        self.filecount = len(self.filelist)
        if self.filecount >= 2:
            # bild erzeugen
            self.img1 = Image.open(self.filelist[0])
            self.img2 = Image.open(self.filelist[1])
            self.img  = ImageTk.PhotoImage(self.img1)
            self.bild = self.canvas.create_image(50, 50, image=self.img, anchor="nw")
            self.canvas.pack()
       
            print start_new_thread(self.image_smooth_action,())
       
    def image_smooth_action(self):
            try:
                while 1:
                    self.chg_alpha()
                    if self.cur_alpha:
                        new = ImageTk.PhotoImage(Image.blend(self.img1, self.img2, self.cur_alpha))
                        self.canvas.itemconfigure(self.bild, image=new)
                        self.img = new
                    sleep(self.speed)
                   
            except:
                pass
               
    def chg_alpha(self):
        # bild erst mal ein paar sekunden anzeigen
        if self.cur_alpha == 0 and self.count2 < 10:
            self.count2 += 1
        else:
            # ein bild langsam ueberblenden
            if self.cur_alpha < .9:
                self.cur_alpha += self.speed
            # bild ist komplett ueberblendet, naechstes anzeigen
            else:
                self.count2 = 0
                self.cur_alpha = 0

                ##  jetzt ist nur noch das naechste Bild wichtig
                if self.cur_count < self.filecount-1:
                    self.cur_count +=1
                else:
                    self.cur_count = 0
                bild2 = self.cur_count

                self.img1 = self.img2       ##  neues altes Image
                self.img2 = Image.open(self.filelist[bild2])
       
# root fenster erzeugen
root = tk.Tk()
# slideshow objekt erzeugen
slideshow = Move(r"media/slide1/", .2)
# der dolle mainloop
root.mainloop()
Viel Spaß dann noch beim Zoomen der Bilder. :-)

Grüße,
der Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
BlackJack

Dienstag 13. März 2007, 08:55

Ich sehe so auf den ersten Blick zwei Probleme: Du hältst keine Referenz auf das alte Bild, das ist bei Tkinter wichtig, da sonst das Bild im Speicher freigegeben und überschrieben werden kann.

Und das zweite ist: Der Zugriff auf die GUI von zwei Threads aus, kann sehr komische Folgen haben. Auf die GUI sollte man immer aus dem Thread zugreifen, in dem man auch die `mainloop()` ausführt. Das ist übrigens bei den meisten GUI-Toolkits so.

Statt eines Threads bietet sich bei Tkinter die `after()`-Methode auf Widgets an.
andieh
User
Beiträge: 4
Registriert: Montag 12. März 2007, 03:17
Wohnort: Offenbach
Kontaktdaten:

Dienstag 13. März 2007, 09:46

Danke Michel, danke Blackjack!

Ich habe erst mal den Kode so verändert, wie Michel es gesagt hat. Es ist doch immer wieder schön, wie ein Kode von jemand anderem aussehen kann :) Aber hat damit schon sehr gut funktioniert. Ich denke mal, alleine, das pro Durchlauf nicht 2 Bilder geladen werden, hat schon viel verändert, danke!

Und das mit after() ist auch sehr gut. Diese Whileschleife da hat mir nie gefallen und sleep und so was, aber ich wusste sonst nicht wie. Jetzt läuft es perfekt, mal sehen, was für Effekte ich mir noch überlege, so zum überblenden!

Grüße und noch mal tausend Dank
andieh
Antworten