miercuri, 8 septembrie 2010

Photo Viewer folosind Python si Pygame

    Cautam zilele trecute un photo viewer care sa imi deschida pozele pe tot ecranul, sa pot face zoom si sa fac trecerea de la o poza la alta intr-un mod simplu si placut dar nu am gasit decat programe care costa destul de mult sau programe gratuite care nu mi-au atras atentia intr-un mod deosebit. Cei drept, nici nu prea am avut chef sa caut; poate as fi gasit in cele din urma. Insa, tot raul inspre bine: de ce sa nu imi fac propriul Photo Viewer?! Mi-a sunat interesant, asa ca am inceput sa ma gandesc ce vreau sa faca si cum l-as putea scrie. Ce vreau sa faca am spus deja, iar ca sa il scriu m-am gandit ca ar fi bine sa folosesc Python. De ce Python? Pentru ca e foarte rapid sa scrii cod si, in plus, am mai folosit odata Pygame si mi-a placut.

    Tot ce trebuie sa aveti pentru a rula exemplul sunt: Python 2.6 sau mai nou (nu Python 3 pentru ca e un pic altfel), Pygame (o versiune care sa se potriveasca cu Python), o poza intr-un tip de fisier cunoscut.

   Descrierea codului
   Programul are o structura simpla: importarea bibliotecilor, o clasa Image care stie sa prelucreze imaginile, initializarea Pygame si un while in care se afiseaza continutul pe ecran si se verifica daca utilizatorul apasa vreo tasta.

   Programul

#importam bibliotecile necesare
import pygame, sys, time
from pygame.locals import *

# cream clasa Image
class Image:
    # apelam constructorul clasei caruia ii transmitem ca parametrii screen
    # (fereastra programului), screenWidth si screenHeight (dimensiunile
    # ferestrei) si imagePath (calea catre imagine) in interiorul constructorului
    # initializam datele membre: _image (e imaginea propriu-zisa incarcata
    # de la adresa transmisa ca parametru), _temporaryImage (imaginea
    # prelucrata), _width si _height (dimensiunile imaginii), _adaptedWidth si
    # _adaptedHeight (dimensiunile imaginii adaptate la dimensiunea ecranului)
    # _XPosition si _YPosition (locatia coltului imaginii din stanga-sus) si _adapted
    # (False daca imaginea nu e adaptata la dimensiunile ecranului)

    def __init__(self, screen, screenWidth, screenHeight, imagePath):
        self._screen = screen
        self._screenWidth = screenWidth
        self._screenHeight = screenHeight
        self._imagePath = imagePath
        self._image = pygame.image.load(self._imagePath).convert()
        self._temporaryImage = self._image
        self._width = self._image.get_width()
        self._height = self._image.get_height()
        self._adaptedWidth = 0
        self._adaptedHeight = 0
        self._XPosition = 0
        self._YPosition = 0
        self._adapted = False

    # modifica data _adapted astfel incat imaginea sa fie readaptata la
    # dimensiunile ecranului
    def restoreInitialDimensions(self):
        self._adapted = False

    # mareste imaginea cu 10% daca prin marirea ei are lungimea si inaltimea mai mici de
    # 9000 de pixeli formula de calcul pentru XPosition si YPosition face ca sa ramana pe
    # loc centrul imaginii
    def zoomIn(self):
        nextWidth = self._width + (self._adaptedWidth * 10) / 100
        nextHeight = self._height + (self._adaptedHeight * 10) / 100
        if nextWidth < 9000 and nextHeight < 9000:
            self._width += (self._adaptedWidth * 10) / 100
            self._height += (self._adaptedHeight * 10) / 100
            self._XPosition -= ((self._adaptedWidth * 10) / 100) / 2
            self._YPosition -= ((self._adaptedHeight * 10) / 100) / 2

    # micsoreaza imaginea cu 10% daca noile dimensiuni nu vor fi mai mici de 10 pixeli
    def zoomOut(self):
        nextWidth = self._width - (self._adaptedWidth * 10) / 100
        nextHeight = self._height - (self._adaptedHeight * 10) / 100
            if nextWidth > 10 and nextHeight > 10:
                self._width -= (self._adaptedWidth * 10) / 100
                self._height -= (self._adaptedHeight * 10) / 100
                self._XPosition += ((self._adaptedWidth * 10) / 100) / 2
                self._YPosition += ((self._adaptedHeight * 10) / 100) / 2

    # adapteaza dimensiunile imaginii pentru a fi cuprinsa de ecran
    def adaptToScreen(self):
        if self._adapted == False:
            if self._width > self._screenWidth or self._height > self._screenHeight:
                self._percent = float(self._screenWidth) / float(self._width)
                self._width = int(self._width * self._percent)
                self._height = int(self._height * self._percent)

                if self._height > self._screenHeight:
                    self._percent = float(self._screenHeight) / float(self._height)
                    self._width = int(self._width * self._percent)
                    self._height = int(self._height * self._percent)
                    self._XPosition = (self._screenWidth - self._width) / 2

            self._adaptedWidth = self._width
            self._adaptedHeight = self._height

    # centreaza imaginea
    def center(self):
        if self._width <= self._screenWidth:
            self._XPosition = (self._screenWidth - self._width) / 2

        if self._height <= self._screenHeight:
            self._YPosition = (self._screenHeight - self._height) / 2

    # muta imaginea spre dreapta cu 10 pixeli
    def moveLeft(self):
        if self._XPosition <= 0:
            self._XPosition += 10

    # muta imaginea spre stanga cu 10 pixeli
    def moveRight(self):
        if self._width - self._screenWidth >= self._XPosition * -1 :
            self._XPosition -= 10

    # muta imaginea in jos cu 10 pixeli
    def moveUp(self):
        if self._YPosition <= 0:
            self._YPosition += 10

    # muta imaginea in sus cu 10 pixeli
    def moveDown(self):
        if self._height - self._screenHeight >= self._YPosition * -1 :
            self._YPosition -= 10

    # prelucreaza si afiseaza imaginea
    def draw(self):
        self.adaptToScreen()
        self.center()
        self._adapted = True
        self._temporaryImage = pygame.transform.scale(self._image, (self._width, self._height))
        self._screen.blit(self._temporaryImage, (self._XPosition, self._YPosition))


# initializeaza modulul display din Pygame si ia dimensiunile ecranului
pygame.display.init()
width = pygame.display.Info().current_w
height = pygame.display.Info().current_h

# face ca fereastra programului sa fie de dimensiunea ecranului in mod Fullscreen
screen = pygame.display.set_mode((width, height), FULLSCREEN)

# seteaza culoarea de fundal ca fiind un gri inchis
# culorile se seteaza ca (Rosu, Verde, Albastru) si iau valori intre 0 si 255
bgColor = (40,40,40)
screen.fill(bgColor)

# programul ruleaza
running = True
# numarul de cadre (frames) pe secunda
clock = pygame.time.Clock()


# calea catre imagine
imagePath = 'D:\Poze\poza.jpg'

# initializeaza obiectul img ca fiind o clasa Image
img = Image(screen, width, height, imagePath)


# rularea programului
# cat timp programul ruleaza...
while running:
    # seteaza rularea la 30 de cadre pe secunda
    clock.tick(30)
    # seteaza culoarea de fundal / poate fi inteleasa ca refresh
    screen.fill(bgColor)

    # afiseaza imaginea
    img.draw()

    # apeleaza o metoda membra a obiectului img in functie de tasta apasata
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == K_PLUS or event.key == K_EQUALS: img.zoomIn()
            elif event.key == K_MINUS: img.zoomOut()
            elif event.key == K_LEFT: img.moveLeft()
            elif event.key == K_RIGHT: img.moveRight()
            elif event.key == K_UP: img.moveUp()
            elif event.key == K_DOWN: img.moveDown()
            elif event.key == K_r: img.restoreInitialDimensions()
            elif event.key == K_ESCAPE: sys.exit(0)
            else: pass

    pygame.display.flip()



    Gata. Asta e tot. In mod normal ar trebui sa ruleze imediat ce salvati codul intr-un fisier cu extensia .py. Atentie la identare.

    Daca va intrebati de ce am scris cu underscore datele membre ale clasei Image, le-am scris asa pentru ca sa fie tratate ca si date protected. In Python nu exista notiunile de private si protected ca si in alte limbaje, ci totul e public. Totusi, e recomandat sa istiintati programatorii cu care lucrati la un proiect ca nu garantati existenta sau continutul unei date membre si nu ar trebui sa le foloseasca. In plus, daca in interiorul expresiei while as incepe sa modific dimensiunile imaginii scriind img.width = 20, codul ar arata inestetic si greu de inteles. In cazul unui program de cateva mii de linii, ar fi un cosmar sa incercati sa il intelegeti. Totusi, daca vreti sa oferiti posibilitatea modificarii unei date membre sau afisarea continutului ei, puteti scrie o metoda care permite modificarea sau afisarea.
    De exemplu, daca vrem sa oferim posibilitatea afisarii continutului date _adaptedWidth, putem scrie metoda getAdaptedWidth in interiorul clasei Image astfel:
    def getAdaptedWidth(self):
        return self._adaptedWidth
    Si va putea fi folosita scriind: adeptedWidth = img.getAdaptedWidth()



    Photo View-ul scris aici e foarte simplu si nu ofera prea multe functii. Trebuie tratat ca si un exemplu, un fel de tutorial, si introducere in Python si Pygame. Mai departe puteti dezvolta programul astfel incat sa deschida o imagine atunci cand dati click pe acea imagine (exact asa cum se deschide un fisier .doc cu Microsoft Word cand dati click pe el), puteti face ca prin apasarea unei taste sa treceti de la o imagine la alta in cazul in care sunt mai multe intr-un folder, puteti sterge imaginea la apasarea unei taste, puteti scrie un algoritm pentru un zoom mai placut, ar fi o idee buna sa mutati in interiorul clasei codul care verifica daca o tasta a fost apasata.

2 comentarii:

  1. Este o ideea buna , rezolvarea este simplista prin folosirea modulului pygame. Insa daca vrei sa faci ceva mai "deosebit" atunci iti recomand sa combini modulul pygame cu modulul OpenGL. Se "misca" mai bine si poti folosi OpenGL.

    RăspundețiȘtergere
  2. Multumesc pentru sfat. O sa vad curand cum functioneaza modulul OpenGL.

    RăspundețiȘtergere