Исходный программный код PyPhoto

ishodnyj programmnyj kod pyphoto Примеры законченных программ с графическим интерфейсом

Поскольку PyPhoto просто расширяет и повторно использует приемы и программный код, с которыми мы встречались ранее в книге, здесь мы опустим детальное обсуждение исходных текстов. За исходными сведениями обращайтесь к обсуждению приемов обработки изображений и применения PIL в главе 8 и к описанию виджета холста в главе 9. В двух словах отмечу, что PyPhoto использует холсты в двух случаях: для отображения коллекций миниатюр и для вывода открываемых изображений. Для вывода миниатюр используется тот же прием компоновки, что и раньше, в примере 9.15. Для вывода полноразмерных изображений также используется холст, прокручиваемая (полная) область которого соответствует размеру изображения, а видимая область вычисляется как минимум из размера физического экрана и размера самого изображения. Физический размер экрана можно определить вызовом метода maxsize() окна Toplevel. Благодаря этому полноразмерное изображение можно прокручивать, что очень удобно при просмотре изображений, размеры которых слишком велики, чтобы уместиться на экране (что весьма характерно для фотографий, снятых новейшими цифровыми фотокамерами).

Кроме того, PyPhoto выполняет привязку событий от клавиатуры и мыши для реализации операций изменения размеров и масштабирования. Благодаря PIL эти операции реализуются очень просто — мы сохраняем оригинальное изображение в объекте изображения PIL, вызываем его метод resize, передавая новые размеры, и перерисовываем изображение на холсте. Программа PyPhoto также использует диалоги открытия и сохранения файла, чтобы запомнить последний посещенный каталог.

Расширение PIL поддерживает дополнительные операции, которыми мы могли бы расширить набор обрабатываемых событий, но для просмотра изображений вполне достаточно изменения размеров. В настоящее время PyPhoto не использует потоки выполнения, чтобы с их помощью избежать блокирования во время выполнения продолжительных операций (например, операция первого открытия большого каталога). Такие расширения я оставляю для самостоятельного упражнения.

Программа PyPhoto реализована в виде единого файла, представленного в примере 11.5, хотя она получает бесплатно некоторую дополнительную функциональность от повторного использования функции, генерирующей миниатюры, из модуля viewer_thumbs, который мы написали в конце главы 8, в примере 8.45. Чтобы не заставлять вас листать страницы взад и вперед, ниже приводится фрагмент программного кода импортируемой функции создания миниатюр, используемой здесь:

# импортировано из главы 8…

def makeThumbs(imgdir, size=(100, 100), subdir=’thumbs’):

#  возвращает список кортежей

#  (имя_файла_изображения, объект_миниатюры_изображения);

thumbdir = os.path.join(imgdir, subdir)

if not os.path.exists(thumbdir):

os.mkdir(thumbdir)

thumbs = []

for imgfile in os.listdir(imgdir):

thumbpath = os.path.join(thumbdir, imgfile)

if os.path.exists(thumbpath):

thumbobj = Image.open(thumbpath) # использовать созданные ранее

thumbs.append((imgfile, thumbobj)) else:

print(‘making’, thumbpath)

imgpath = os.path.join(imgdir, imgfile)

try:

imgobj = Image.open(imgpath) # создать миниатюру

imgobj.thumbnail(size, Image.ANTIALIAS) # фильтр, дающий

#  лучшее качество при

#  уменьшении размеров

imgobj.save(thumbpath) # тип определяется

thumbs.append((imgfile, imgobj)) # расширением

except: print(“Skipping: “, imgpath) return thumbs

Программный код, реализующий окно выбора миниатюр, также очень схож с представленным в главе 9 примером с прокручиваемой коллекцией миниатюр, но он не импортируется этим файлом, а просто повторяется в нем, чтобы обеспечить будущее его развитие (и его статус в главе 9 — функционального подмножества — здесь понижен до уровня прототипа).

При изучении этого файла особое внимание обратите на организацию программного кода в виде набора функций и методов многократного пользования, которая позволяет избежать избыточности, — если нам, например, когда-нибудь придется изменить реализацию операции изменения размеров, нам достаточно будет изменить один метод, а не два. Кроме того, обратите внимание на класс ScrolledCanvas — компонент многократного пользования, который обеспечивает автоматическое связывание полос прокрутки и холстов.

Пример 11.5. PP4E\Gui\PIL\pyphoto1.py

############################################################################ PyPhoto 1.1: программа просмотра миниатюр изображений с возможностью изменения размеров и сохранения.

Позволяет открывать несколько окон для просмотра миниатюр из разных каталогов — в качестве начального каталога с изображениями принимается аргумент командной строки, каталог по умолчанию imagesили выбранный щелчком на кнопке в главном окне; последующие каталоги могут открываться нажатием клавиши Dв окне с миниатюрами или в окне просмотра полноразмерного изображения.

Программа также позволяет прокручивать изображения, если они слишком большие и не умещаются на экране;

все еще необходимо: (1) реализовать переупорядочение миниатюр при изменении размеров окна, исходя из текущего размера окна; (2) [ВЫПОЛНЕНО] возможность изменения размеров изображения в соответствии с текущими размерами окна?

(3) отключать прокрутку, если размер изображения меньше максимального размера окна: использовать Label, если шир_изобр <= шир_окна и выс_изобр <= выс_окна?

Новое в версии 1.1: работает под управлением Python 3.1 и с последней версией PIL;

Новое в версии 1.0: реализован пункт (2) выше: щелчок мышью изменяет размер изображения в соответствии с одним из размеров экрана, и предусмотрена возможность увеличения и уменьшения масштаба изображения с шагом 10% нажатием клавиши; требуется поискать более универсальные решения; ВНИМАНИЕ: похоже, что после многократного изменения размеров теряется качество изображения (вероятно, это ограничение PIL)

подпись: заимствованный из реализации создания алгоритм масштабирования по высоте экрана, для сжатия:подпись: // x, 1); x = scrwideСледующий алгоритм масштабирования, миниатюр средствами PIL, напоминает используемый в программе, но только x, y = imgwide, imghigh

if x > scrwide: y = max(y * scrwide

if y > scrhigh: x = max(x * scrhigh // y, 1); y = scrhigh

############################################################################ import sys, math, os from tkinter import * from tkinter.filedialog import SaveAs, Directory

from PIL import Image # PIL Image: также имеется в tkinter

from PIL.ImageTk import PhotoImage # версия виджета PhotoImage из PIL from viewer_thumbs import makeThumbs # разработан ранее в книге

# запомнить последний открытый каталог

saveDialog = SaveAs(title=’Save As (filename gives image type)’) openDialog = Directory(title=’Select Image Directory To Open’)

trace = print # or lambda *x: None

appname = ‘PyPhoto 1.1: ‘

class ScrolledCanvas(Canvas):

холст в контейнере, который автоматически создает вертикальную и горизонтальную полосы прокрутки

def __init__(self, container):

Canvas.__init__(self, container)

self.config(borderwidth=0)

vbar = Scrollbar(container)

hbar = Scrollbar(container, orient=’horizontal’)

vbar.pack(side=RIGHT, fill=Y) # холст прикрепляется после

hbar.pack(side=BOTTOM, fill=X) # полос, чтобы обрезался первым

self.pack(side=TOP, fill=BOTH, expand=YES)

vbar.config(command=self.yview) # вызвать при перемещении полосы

hbar.config(command=self.xview) # прокрутки

self.config(yscrollcommand=vbar.set) # вызвать при прокрутке холста

self.config(xscrollcommand=hbar.set)

class ViewOne(Toplevel):

при создании открывает единственное изображение во всплывающем окне; реализовано в виде класса, потому что объект PhotoImage должен сохраняться, иначе изображение будет стерто при утилизации; обеспечивает прокрутку больших изображений; щелчок мыши изменяет размер изображения в соответствии с высотой или шириной окна: растягивает или сжимает; нажатие клавиш I и O увеличивает и уменьшает размеры изображения; оба алгоритма изменения размеров предусматривают сохранение оригинального отношения сторон; программный код организован так, чтобы избежать избыточности, насколько это возможно;

def __init__(self, imgdir, imgfile, forcesize=()):

Toplevel.__init__(self)

helptxt = ‘(click L/R or press I/O to resize, S to save, D to open)’

self.title(appname + imgfile + ‘ ‘ + helptxt)

imgpath = os.path.join(imgdir, imgfile)

imgpil = Image.open(imgpath)

self.canvas = ScrolledCanvas(self)

self.drawImage(imgpil, forcesize)

self.canvas.bind(‘<Button-1>’, self.onSizeToDisplayHeight)

self.canvas.bind(‘<Button-3>’, self.onSizeToDisplayWidth)

self.bind(‘<KeyPress-i>’, self.onZoomIn)

self.bind(‘<KeyPress-o>’, self.onZoomOut)

self.bind(‘<KeyPress-s>’, self.onSaveImage)

self.bind(‘<KeyPress-d>’, onDirectoryOpen)

self.focus()

def drawImage(self, imgpil, forcesize=())

imgtk =

PhotoImage(image=imgpil)

#

file != imgpath

scrwide,

scrhigh = forcesize or self.maxsize()

#

размеры x,y экрана

imgwide

= imgtk.width()

#

размеры в пикселях

imghigh

= imgtk.height()

#

то же,

 

 

#

что и imgpil.size

fullsize

= (0, 0, imgwide, imghigh)

#

прокручиваемая

viewwide

= min(imgwide, scrwide)

#

видимая

viewhigh

= min(imghigh, scrhigh)

 

 

canvas =

self.canvas

 

 

canvas.delete(‘all’)

#

удалить предыд. изобр

canvas.config(height=viewhigh, width=viewwide) # видимые размеры окна canvas.config(scrollregion=fullsize) # размер прокр. области canvas.create_image(0, 0, image=imgtk, anchor=NW)

 

 

if imgwide <= scrwide and imghigh <= scrhigh: # слишком велико?

self.state(‘normal’)

# нет: размер окна по изобр

elif sys.platform[:3] == ‘win’:

# в Windows на весь экран

self.state(‘zoomed’)

# в других исп. geometry()

self.saveimage = imgpil

 

self.savephoto = imgtk

# сохранить ссылку на меня

trace((scrwide, scrhigh), imgpil.size)

 

 

def sizeToDisplaySide(self, scaler):

# изменить размер, чтобы полностью заполнить одну сторону экрана imgpil = self.saveimage

scrwide, scrhigh = self.maxsize() # размеры x,y экрана

imgwide, imghigh = imgpil.size # размеры изображения в пикселях

newwide, newhigh = scaler(scrwide, scrhigh, imgwide, imghigh)

if (newwide * newhigh < imgwide * imghigh):

filter = Image.ANTIALIAS # сжатие: со сглаживанием

else: # растягивание: бикубическая

filter = Image.BICUBIC # аппроксимация

imgnew = imgpil.resize((newwide, newhigh), filter) self.drawImage(imgnew)

def onSizeToDisplayHeight(self, event):

def scaleHigh(scrwide, scrhigh, imgwide, imghigh):

newhigh = scrhigh

newwide = int(scrhigh * (imgwide / imghigh)) # истинное деление

return (newwide, newhigh) # пропорциональные

self.sizeToDisplaySide(scaleHigh)

def onSizeToDisplayWidth(self, event):

def scaleWide(scrwide, scrhigh, imgwide, imghigh): newwide = scrwide

newhigh = int(scrwide * (imghigh / imgwide)) # истинное деление return (newwide, newhigh)

self.sizeToDisplaySide(scaleWide)

def zoom(self, factor):

# уменьшить или увеличить масштаб с шагом

imgpil = self.saveimage

wide, high = imgpil.size

if factor < 1.0: # сглаживание дает лучшее качество

filter = Image.ANTIALIAS # при сжатии, также можно

else: # использовать NEAREST, BILINEAR

filter = Image.BICUBIC

new = imgpil.resize((int(wide * factor), int(high * factor)), filter) self.drawImage(new)

def onZoomIn(self, event, incr=.10):

self.zoom(1.0 + incr)

def onZoomOut(self, event, decr=.10):

self.zoom(1.0 decr)

def onSaveImage(self, event):

#   сохранить изображение в текущем виде в файл

filename = saveDialog.show()

if filename:

self.saveimage.save(filename)

def onDirectoryOpen(event):

открывает новый каталог с изображениями в новом окне

может вызываться в обоих окнах, с изображением и с миниатюрами

dirname = openDialog.show()

if dirname:

viewThumbs(dirname, kind=Toplevel)

def viewThumbs(imgdir, kind=Toplevel, numcols=None, height=400, width=500):

создает окно и кнопки с миниатюрами;

использует кнопки фиксированного размера, прокручиваемый холст;

устанавливает прокручиваемый (полный) размер и размещает миниатюры

в холсте по абсолютным координатам x,y;

больше не предполагает, что все миниатюры имеют одинаковые размеры: за основу берет максимальные размеры (x,y) среди всех миниатюр, некоторые могут быть меньше;

win = kind()

helptxt = ‘(press D to open other)’

win.title(appname + imgdir + ‘ ‘ + helptxt)

quit = Button(win, text=’Quit’, command=win.quit, bg=’beige’)

quit.pack(side=BOTTOM, fill=X)

canvas = ScrolledCanvas(win)

canvas.config(height=height, width=width) # видимый размер окна, может # изменяться пользователем

thumbs = makeThumbs(imgdir) # [(imgfile, imgobj)]

numthumbs = len(thumbs) if not numcols:

numcols = int(math.ceil(math.sqrt(numthumbs))) # фиксир. или N x N numrows = int(math.ceil(numthumbs / numcols)) # истинное деление

#   максимальная шир|выс: thumb=(name, obj), thumb.size=(width, height) linksize = max(max(thumb[1].size) for thumb in thumbs) trace(linksize)

fullsize = (0, 0, # X,Y верхн. левого угла

(linksize*numcols),(linksize*numrows)) # X,Y прав. нижнего угла

canvas.config(scrollregion=fullsize) # размер прокруч. области

rowpos = 0

savephotos = []

while thumbs:

thumbsrow, thumbs = thumbs[:numcols], thumbs[numcols:]

colpos = 0

for (imgfile, imgobj) in thumbsrow:

photo = PhotoImage(imgobj)

link = Button(canvas, image=photo)

def handler(savefile=imgfile):

ViewOne(imgdir, savefile)

link.config(command=handler, width=linksize, height=linksize) link.pack(side=LEFT, expand=YES)

canvas.create_window(colpos, rowpos, anchor=NW, window=link, width=linksize, height=linksize)

colpos += linksize

savephotos.append(photo)

rowpos += linksize

win.bind(‘<KeyPress-d>’, onDirectoryOpen)

win.savephotos = savephotos

return win

if __name__ == ‘__main__’:

открываемый каталог = по умолчанию или из аргумента командной строки, иначе вывести простое окно с кнопкой для выбора каталога

imgdir = ‘images’

if len(sys.argv) > 1: imgdir = sys.argv[1]

if os.path.exists(imgdir):

mainwin = viewThumbs(imgdir, kind=Tk)

else:

mainwin = Tk()

mainwin.title(appname + ‘Open’)

handler = lambda: onDirectoryOpen(None)

Button(mainwin, text=’Open Image Directory’, command=handler).pack() mainwin.mainloop()

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий