nach einem Monat Zwangspause wegen Umzug und resultierender Internetauszeit möchte ich mal einen einfachen Bildbetrachter vorstellen. Er ist noch nicht ganz fertig und wird in folgenden Versionen auch Effektfilter wie Schärfen, Weichzeichnen, Emboss usw. regulär enthalten. Die Funktionen habe ich aber mangels Tests aus dieser Version entfernt. Eure Meinungen und Anregungen sind generell gewünscht und begehrt. Ich hoffe, der Code ist nicht zu lang...
EDIT: Übrigens, die Einzelbildframes kann man mit Rechtsklick jederzeit schließen
Code: Alles auswählen
# -*- encoding: iso-8859-1 -*-
from Tkinter import *
from glob import glob
import os
import sys
import Image
import ImageTk
import ImageFilter
import tkFileDialog
class CONST:
"""Verfügbare Konstanten
PROGPATH - Pfad, in dem sich das Startprogramm befindet
"""
PROGPATH = os.path.split(sys.argv[0])[0] ## Pfad des Programms
class Thumbnail(Button):
def __init__(self, wMaster, Bild):
self.Bild = Bild
self.wMainWindow = None
## show image in a new window
def open_picture(self=self):
Canvas_Window(self, Bild=self.Bild)
Button.__init__(self, wMaster, image=Bild.get_thumb(), command=open_picture)
self.iID = self.master.wCanvas.create_window((0, 0), window=self, anchor=NW)
self.iX, self.iY = 0, 0
def update_position(self, x, y):
self.move_to(x=20+x*180, y=20+y*180)
def move_to(self, x=0, y=0):
"Verschiebt das Thumbnail an die Stelle x, y"
iDX = x - self.iX
iDY = y - self.iY
self.master.wCanvas.move(self.iID, iDX, iDY)
self.iX, self.iY = x, y
class Thumbnail_Frame(Frame):
def __init__(self, wMaster):
Frame.__init__(self, wMaster)
self.lThumbnails = [] ## Liste mit Thumbnail Instanzen
self._create_widgets()
def _create_widgets(self):
self.wCanvas = Canvas(self, width=800, height=600)
self.wCanvas.grid(row=0, column=0)
self.wScrollV = Scrollbar(self, orient=VERTICAL, command=self.wCanvas.yview)
self.wScrollV.grid(row=0, column=1, sticky=NSEW)
self.wCanvas.config(yscrollcommand=self.wScrollV.set)
def __add__(self, Bild):
"Fügt das Bild im Overview-Canvas ein"
self.lThumbnails.append(Thumbnail(self, Bild))
def update_thumbnails(self):
iColumnsPerLine = 4
for iThumb in xrange(len(self.lThumbnails)):
Thumb = self.lThumbnails[iThumb]
iY, iX = divmod(iThumb, iColumnsPerLine)
Thumb.update_position(iX, iY)
self.update_scrollregion()
def remove_all_thumbnails(self):
while self.lThumbnails:
self.lThumbnails.pop().destroy()
def update_scrollregion(self):
self.wCanvas.configure(scrollregion=self.wCanvas.bbox("all"))
class Overview_Window(Toplevel):
def __init__(self, wMaster=None):
## initialisiert Variablen
self.sCurrPath = CONST.PROGPATH
## initialisiert Basisklasse
if not wMaster:
wMaster = Tk()
wMaster.iconify()
Toplevel.__init__(self, wMaster)
self.title("Phantom View: %s" % self.sCurrPath)
self._create_widgets()
def _create_widgets(self):
"""Erzeugt die Elemente des Übersichtfensters"""
## Menuezeile erzeugen und einfuegen
wMenuLine = Menu(self)
wMenuLine.add_command(label="Ende", command=self.destroy)
wMenuLine.add_command(label="Verzeichnis", command=self._choose_directory)
self.config(menu=wMenuLine)
self.wThumbnailFrame = Thumbnail_Frame(self)
self.wThumbnailFrame.grid(row=0, column=0)
def _choose_directory(self):
import tkFileDialog
sDir = tkFileDialog.askdirectory(initialdir=self.sCurrPath)
if sDir:
self._change_path(sDir)
def _change_path(self, sDir):
if os.path.isdir(sDir):
self.sCurrPath = sDir
self.title("Phantom View: %s" % self.sCurrPath)
self.remove_all_thumbnails()
self.load_dir(sDir)
def __add__(self, Bild):
if not isinstance(Bild, BILD):
print Bild, "ist keine Instanz von BILD"
return
else:
self.wThumbnailFrame + Bild ## Thumbnail des Bilds hinzufügen
def load_dir(self, sDir):
if os.path.isdir(sDir):
## bringt die Canvasposition (0, 0) ins Bild
wCanvas = self.wThumbnailFrame.wCanvas
wCanvas.xview("moveto", "0")
wCanvas.yview("moveto", "0")
for sExtension in ("jpg", "jpeg", "pgj", "gpj", "gif", "tif", "png"):
for sBild in glob(os.path.join(sDir,"*."+sExtension)):
print "lade Bild", sBild
Bild = BILD(sBild)
#Bild.add_filter(name="thumbnail", size=(800, 800), antialias=1)
self + Bild
self.update_thumbnails()
self.update_idletasks()
def update_thumbnails(self):
self.wThumbnailFrame.update_thumbnails()
def remove_all_thumbnails(self):
self.wThumbnailFrame.remove_all_thumbnails()
class Dummy_Callback:
import tkMessageBox
def __call__(self, *args, **kw):
self.tkMessageBox.showerror("Achtung!", "Noch keine Funktion verlinkt!")
class Canvas_Frame(Frame):
"""Frame, der die den Canvas enthält"""
def __init__(self, wMaster, Bild=None, width=800, height=600):
Frame.__init__(self, wMaster)
## Variablen initialisieren
self.iWidth, self.iHeight = width, height
self.iImageItem = 0
## Widgets erzeugen
self._create_widgets()
## wenn ein Bild angegeben ist, zeige es an
if Bild:
self.show_bild(Bild)
def _create_widgets(self):
## widgets erzeugen
self.wCanvas = Canvas(self, width=self.iWidth, height=self.iHeight)
self.wScrollX = Scrollbar(self, orient=HORIZONTAL, command=self.wCanvas.xview)
self.wScrollY = Scrollbar(self, orient=VERTICAL, command=self.wCanvas.yview)
## widgets plazieren
self.wCanvas.grid(row=0, column=0, sticky=NSEW)
self.wScrollX.grid(row=1, column=0, sticky=EW)
self.wScrollY.grid(row=0, column=1, sticky=NS)
## Konfiguration des Canvas-Widgets
self.wCanvas.config(xscrollcommand=self.wScrollX.set,
yscrollcommand=self.wScrollY.set)
## bei Rechtsklick Fenster schliessen
self.wCanvas.bind("<3>", self._destroy)
def show_bild(self, Bild=None):
if Bild: ## Bild wenn neues Bild angegeben, sonst gespeichertes Bild aktualisieren
self.Bild = Bild
tCenter = self.Bild.get_stats("center")
if self.iImageItem:
self.wCanvas.delete(self.iImageItem)
print self.Bild.fZoom
self.iImageItem = self.wCanvas.create_image(tCenter[0], tCenter[1], image=self.Bild())
self._update_scrollregion()
def update_bild(self, fZoom=0.0):
"""Ändert das Bild, mögliche Argumente: fZoom(0.0):float"""
if fZoom > 0.0:
self.Bild.fZoom = fZoom
self.show_bild()
def fit_bild(self, show_zoom_callback):
"""Passt das Bild in die Framegröße ein."""
iTargetW, iTargetH = 800, 600
dStats = self.Bild.get_stats()
iSourceW, iSourceH = dStats["width"], dStats["height"]
fZoom = min(float(iTargetW)/iSourceW,
float(iTargetH)/iSourceH)
self.update_bild(fZoom=fZoom)
show_zoom_callback(fZoom) ## zeige den Zoom im Optionsfeld an
def _update_scrollregion(self):
self.wCanvas.config(scrollregion=self.wCanvas.bbox("all"))
def _destroy(self, Event=None):
if self.Bild:
self.Bild.del_image()
self.master.destroy()
class Zoom_Frame(Frame):
"""Frame mit Knöpfen zur Größenkontrolle des Bildes"""
def __init__(self, wMaster):
Frame.__init__(self, wMaster)
self._init_vars()
self._init_widgets()
def _init_vars(self):
self.vZoom = StringVar()
self.vZoom.set("100%")
def show_zoom(self, fZoom):
print "aendere Zoom in %i%%" % round(100*fZoom)
self.vZoom.set("%i%%" % round(100*fZoom))
def _init_widgets(self):
self.vZoom.set(self.master.Bild.get_zoom())
self.wOptionMenu = wOM = OptionMenu(self, self.vZoom, self.master.Bild.get_zoom())
wMenu = Menu(wOM, tearoff=False)
for sEntry in ("25%", "50%", "75%", "100%", "150%", "200%", "400%"):
def set_zoom(sValue=sEntry):
wOM["text"] = sValue
self.vZoom.set(sValue)
print "neuer Zoomlevel: %s" % self.vZoom.get()
## Ändere Zoomlevel und zeige das neue Bild an
self.master.wCanvasFrame.update_bild(fZoom=float(sValue[:-1])/100)
if sEntry == "100%":
set_zoom_100 = set_zoom
def create_entry(sEntry=sEntry, Callback=None):
wMenu.add_command(label=sEntry, command=Callback)
create_entry(Callback=set_zoom)
wOM.config(menu=wMenu)
wOM.grid(row=0, column=1, padx=10)
self.wButton100 = wB100 = Button(self, text=r"100%", command=set_zoom_100)
wB100.grid(row=0, column=2, padx=10)
self.wButtonFit = wBF = Button(self, text="Einpassen", command=self.fit_bild)
wBF.grid(row=0, column=3, padx=10)
def fit_bild(self):
self.master.wCanvasFrame.fit_bild(self.show_zoom)
class Canvas_Window(Toplevel):
"""Fenster für das anzuzeigende Bild"""
def __init__(self, wMaster, Bild=None):
Toplevel.__init__(self, wMaster)
self.Bild = Bild
self._init_widgets(Bild)
self.set_title(Bild)
def _init_widgets(self, Bild):
self.wCanvasFrame = Canvas_Frame(self, Bild=Bild)
self.wCanvasFrame.grid(row=1, column=1)
self.wZoomFrame = Zoom_Frame(self)
self.wZoomFrame.grid(row=2, column=1)
self.wZoomFrame.fit_bild() ## Bild einpassen
def set_title(self, Bild):
if Bild:
FILENAME = 1
self.title(os.path.split(Bild.sName)[FILENAME])
class BILD:
"""Verwaltet Bilddaten und -funktionen
Der Bool'sche Wert einer BILD-Instanz ist True, wenn ein Bild geladen ist, sonst False"""
def __init__(self, Source, preload=False):
self.imgBild = None ## None als Bild initialisieren
self.phThumb = None ## Thumbnail initialisieren
self.phBild = None ## Bild als Photoimage
self.sName = "" ## Name des Bildes
self.tThumbSize = (0, 0) ## letzte Größe des Thumbnails
self.lFilters = [] ## Liste der Bearbeitungsfilter
self.iItemNum = 0 ## Itemnummer des Bildes im OverviewCanvas
self.fZoom = 0.5 ## Zoomfaktor
self.Source = Source
if preload:
self._load_image()
def _load_image(self):
"""Lade image in Bild-Instanz"""
########################################################################################
## initialisiere das Objekt je nach Typ der Quelle
if type(self.Source) == type(""):
###############################################
## Bild aus Datei gelesen
if os.path.isfile(self.Source):
self.imgBild = Image.open(self.Source)
self.sName = self.Source
if self.imgBild:
self.tSize = self.iWidth, self.iHeight = self.imgBild.size
self.tCenter = self.iCenterX, self.iCenterY = self.iWidth / 2, self.iHeight / 2
def get_zoom(self):
"""Liefert die Zoomstufe als String in Prozent, z.B. 87%"""
if (self.imgBild) and (self.phBild):
return "%i%%" % (100*self.phBild.width()/self.imgBild.size[0])
else:
return "0%"
def get_image(self):
"""Liefert das Originalimage, lädt es wenn notwendig."""
if not self.imgBild:
self._load_image()
return self.imgBild
def __nonzero__(self):
if self.Source:
return True
else:
return False
def del_image(self):
"Löscht das intern gespeicherte Image, um Ressourcen freizugeben."""
del self.imgBild
self.imgBild = None
def __call__(self, size=(), keepratio=True, match=True):
"Create PhotoImage and return it"""
if not self: ## Gib None zurück, wenn kein Bild geladen ist
return None
## wenn keine Filter angegeben, liefere nur das PhotoImage vom Originalbild
if not self.lFilters:
imgBild = self.get_image().copy()
## sonst wende die Filter auf das Originalbild an
else:
imgBild = self._create_filtered_image()
#self.phBild = ImageTk.PhotoImage(self.resize_image(imgBild, size, keepratio, match))
if not self.fZoom == 1.0:
imgBild = self._apply_zoom(imgBild)
self.phBild = ImageTk.PhotoImage(imgBild)
return self.phBild
def get_thumb(self, size=(160, 160), reset=False):
"""Erzeugt und liefert ein Thumbnail des Bildes.
Parameter:
size((160, 160)) - Größe des Thumbnails
reset(False) - wenn True wird die Erzeugung eines neuen Thumbs erzwungen"""
if (not self.phThumb) or reset or (not size==self._tThumbSize):
#img = self._create_filtered_image() ## gefiltertes Bild als Vorlage
if self.imgBild:
img = self.imgBild.copy()
else:
img = Image.open(self.Source)
img.thumbnail(size, 1)
self.phThumb = ImageTk.PhotoImage(img)
self.tThumbSize = size
return self.phThumb
def get_stats(self, sStat=""):
imgTemp = self._create_filtered_image()
dStats = {}
dStats["size"] = dStats["width"], dStats["height"] = imgTemp.size
dStats["center"] = dStats["center x"], dStats["center y"] = dStats["width"] / 2, dStats["height"] / 2
if sStat:
return dStats.get(sStat, "")
return dStats
def set_item(self, iItemNum):
"Speichert ID (Itemnummer) des Bildes im Overview Canvas."
self.iItemNum = iItemNum
def get_item(self):
"Liefert die ID des Bildes im Overview Canvas."
return self.iItemNum
def _create_filtered_image(self):
"Rechnet die Filter in das Image ein."""
imgTemp = self.get_image().copy() ## Kopie des Originals anlegen
for dFilter in self.lFilters:
imgTemp = self._apply_filter(dFilter, imgTemp)
return imgTemp
def _apply_zoom(self, imgSource, sFilter=Image.LINEAR):
iW, iH = imgSource.size
return imgSource.resize((int(iW*self.fZoom), int(iH*self.fZoom)), sFilter)
def _apply_filter(self, dFilter, imgTemp):
"""Wendet einen von mehreren Filtern an:
Thumbnail erzeugen:
name = thumbnail,
size = Tuple der Zielgröße,
antialias = Ausbesserung der Ränder
Effektfilter:
name = blur - Verschwimmen
name = contour - Konturen verstärken
name = edge_enhance - Kanten verstärken
name = edge_enhance_more - Kanten mehr verstärken
name = emboss - Reliefbild
name = sharpen - Schärfen
name = smooth - Weichzeichnen
name = smooth_more - stark Weichzeichnen"""
## erzeuge Thumbnail
if dFilter.get("name", "") == "thumbnail":
tTargetSize = dFilter.get("size", (100,100))
iAntialias = dFilter.get("antialias", 1)
imgTemp.thumbnail(tTargetSize, iAntialias)
## Effektfilter
if dFilter.get("name", "") in ("blur", "contour", "edge_enhance", "edge_enhance_more",
"emboss", "sharpen", "smooth", "smooth_more"):
imgTemp = imgTemp.filter(getattr(ImageFilter, dFilter.get("name").upper()))
return imgTemp
def add_filter(self, **dFilter):
"""Fuege das Filterdictionary dFilter der Bildfilterliste hinzu"""
if not dFilter == {}:
self.lFilters.append(dFilter)
def main():
OW = Overview_Window()
OW.load_dir("./")
OW.update_thumbnails()
OW.mainloop()
if __name__ == "__main__":
main()
Michael