Pripravil sem nam zanimiv modul. Imenuje se risar. Čim ga uvozimo, odpre okno, v katerega lahko s funkcijami, ki so v modulu, rišemo črte, kroge, po njem pišemo, vanj vstavljamo slike...

Datoteko risar.py shranimo v direktorij, v katerem je program, ki ga poganjamo, ali pa v direktorij, v katerem se nahaja naš projekt v PyCharmu.

Za uporabo Risarja si boste morali namestiti Qt. Za tole, kar bomo z njim počeli na teh in naslednjih predavanjih, je Qt nekoliko neroden. Izbrali pa smo si ga, ker je relativno preprost, predvsem pa gre za močno orodje za sestavljanje uporabniških vmesnikov (za kar ga bomo čez nekaj časa tudi uporabili).

Modul vsebuje naslednje funkcije

risar.crta(x0, y0, x1, y1, barva=bela, sirina=1)
Nariše črto od (x0, y0) do (x1, y1). Zadnja dva argumenta, barvo in širino črte (v točkah na zaslonu) smemo izpustiti; v tem primeru dobimo belo črto širine ena točka.
risar.tocka(x, y, barva=bela)
Nariše piko na koordinatah (x, y). Če ne določimo barve, bo pika bela.
risar.krog(x, y, r, barva=bela, sirina=1)
Nariše krog s polmerom r in središčem na koordinatah (x, y). Z zadnjima argumentoma je pa tako kot pri črti.
risar.elipsa(x, y, rx, ry, barva=bela, sirina=1)
Nariše elipso s polmeroma rx in ry ter središčem na koordinatah (x, y).
risar.besedilo(x, y, s, barva=bela, velikost=20, pisava="Arial")
Izpiše niz s tako, da je zgornji levi vogal na koordinatah (x, y). Zadnji trije argumenti, ki jih lahko tudi izpustimo, določijo barvo, velikost in pisavo ter imajo privzete vrednosti, kot piše.
risar.slika(x, y, ime)
Naloži sliko iz datoteke z imenom ime in jo postavi tako, da je njen gornji levi vogal na koordinatah (x, y).
risar.barvaOzadja(barva)
Nastavi barvo ozadja
risar.barva(r, g, b)
Sestavi in vrne barvo z danimi deleži rdeče, zelene in modre (argumenti morajo biti med 0 in 255).
risar.nakljucna_barva()
Vrne naključno barvo.
risar.nakljucne_koordinate()
Vrne naključne koordinate kot terko (x, y).
risar.obnovi()
S to funkcijo zahtevamo, da se slika ponovno izriše. Ali se slika obnovi sama od sebe ali je potrebno poklicati obnovi, je odvisno predvsem od tega, na kakšen način (iz kakšnega okolja) poganjamo Python.
risar.obnavljaj
ni funkcija, temveč spremenljivka, ki jo lahko postavimo na True ali False. Privzeta vrednost je False; če jo postavimo na True se bo po vsakem klicu funkcij za risanje poklicala tudi funkcija obnovi. Risanje bo s tem počasnejše, vendar bomo sproti videli, kaj se dogaja s sliko.
risar.cakaj(t)
Ustavi program za t sekund, pri čemer je lahko t tudi necelo število, npr. 0.01 (stotinka sekunde). Funkcijo uporabljajmo namesto time.sleep, ki tule ne bi delovala povsem pravilno.
risar.stoj()
To funkcijo pokličemo, da se program ustavi, ne da bi se zaprl. Če jo izpustimo, bo program le naredil, kar smo mu rekli in okno se bo takoj nato zaprlo. (Zgodba za to funkcijo je sicer nekoliko bolj zapletena - v resnici programa ne ustavi, temveč ga na nek način šele zares požene. A o tem se bomo učili čez dva ali tri tedne.)

Koordinatni sistem je malo drugačen, kot ste ga vajeni: točka (0, 0) ni v spodnjem, temveč v zgornjem levem vogalu, in koordinata y narašča navzdol, ne navzgor - nižje ko gremo, večji je y. Če se zdi to komu nenavadno, naj razmisli, kako šteje vrstice na zaslonu: je prva vrstica čisto zgoraj ali čisto spodaj? No, to je isto. Koordinatni sistemi v računalniku so navadno obrnjeni tako.

Spodnja desna točka ima koordinati (risar.maxX-1, risar.maxY-1).

Kot argumente, s katerimi sporočamo barvo, lahko uporabimo bodisi to, kar vrne funkcija risar.barva bodisi konstante risar.bela, risar.crna, risar.rdeca, risar.zelena, risar.modra, risar.vijolicna, risar.rumena, risar.siva, risar.rjava. Vse te barve so zbrane tudi v terki risar.barve.

Črte

Ko si človek pripravi tak imeniten modul, si seveda ne more kaj, da ne bi takoj narisal slike, na kateri je sto naključnih pisanih črt različnih debelin. Ne bo imel veliko dela.

import risar
from random import randint

for i in range(100):
    x0, y0 = risar.nakljucne_koordinate()
    x1, y1 = risar.nakljucne_koordinate()
    barva = risar.barva(randint(0, 255), randint(0, 255), randint(0, 255))
    sirina = randint(2, 20)
    risar.crta(x0, y0, x1, y1, barva, sirina)
risar.stoj()

V njem se ne dogaja nič zapletenega: naložimo modul risar, iz modula random pa funkcijo randint, ki nam žreba naključna cela števila znotraj podanega intervala. Nato si stokrat izberemo začetne in končne koordinate, barvo in širino ter narišemo črto.

Program je preprost, veličina umetnosti pa nesporna.

Besede

Začutili smo umetniško žilico; škoda bi bilo ne nadaljevati v tem slogu. Napišimo funkcijo, ki razkosa besedilo, ki ga podamo kot argument, na besede in jih v pisanih barvah razmeče po sliki.

import risar
from random import randint

def besedici(besedilo):
    for beseda in besedilo.split():
        risar.besedilo(
            randint(0, 750), randint(0, 400), beseda, risar.nakljucna_barva(),
            velikost=randint(30, 80), pisava="Calibri")

besedici(open("krst .txt").read())
risar.stoj()

Rezultat je gotovo vreden diplome na ALUO. ;)

Za doktorat pa je morda potrebne še nekaj multikulturnosti.

Da, da, to bi moralo zadoščati. ;)

Face

Isto bi lahko naredili tudi s slikami - recimo slikami, ki so jih študenti, ki poslušajo Programiranje 1, naložili v Moodle in jih imamo slučajno na disku v poddirektoriju "slike" (da se boste lahko igrali z njimi sem vam jih zapakiral v tale arhiv; gre za predlanske slike, saj zdaj slike niso v modi in je ni naložil skoraj nihče).

for fn in os.listdir("slike"):
    risar.slika(randint(0, risar.maxX-100), randint(0, risar.maxY-100),
                "slike/"+fn)

Ponovno gre brez vsakega dvoma za umetniško stvaritev, morda celo inštalacijo. Še več, ni vrag, da ne gre za virtualno inštalacijo. No, tega ne vem, a dasiravno je razpored slik naključen, je globoki simbolizem nastale slikovne kompozicije, ki poudarja brezčasno stalnost človeškega čutenja v odnosu do presežnega, razumljiv celo popolnim analfabetom umetnosti.

Trikotnik Sierpinskega

V svojem iskanju lepote se zatecimo tja, kjer je je največ (morda to vseeno ni ALUO): v matematiko. Poiščimo jo tako, da znotraj trikotnika narišemo obrnjen trikotnik, s čimer ga razdelimo na štiri trikotnike.

Potem to ponovimo vse skupaj na treh "zunanjih" trikotnikih. Kako "ponovimo vse skupaj"? Pač tako, da tudi v vsakega od teh treh trikotnikov vrišemo narobe obrnjen trikotnik in potem ponovimo vse skupaj.

Ponovimo vse skupaj? Da, v vsakem od po treh (zdaj skupaj že devetih) novonastalih narišemo narobe obrnjen trikotnik in potem ponovimo vse skupaj.

Pa znamo to sprogramirati? I, seveda. Da nam bo lažje, najprej napišimo funkcijo, ki nariše trikotnik med točkami A, B in C, podanimi s koordinatami Ax, Ay, Bx, By, Cx, Cy.

def trikotnik(Ax, Ay, Bx, By, Cx, Cy):
    risar.crta(Ax, Ay, Bx, By)
    risar.crta(Bx, By, Cx, Cy)
    risar.crta(Cx, Cy, Ax, Ay)

Takole smo narisali črto od A do B, od B do C in od C do A. Zdaj pa narišimo zunanji trikotnik.

Ax, Ay =  10, 475
Bx, By = 537, 475
Cx, Cy = 271,  10
trikotnik(Ax, Ay, Bx, By, Cx, Cy)

Do sem je preprosto. Naprej pa tudi, saj se rekurzije ne bojimo. Koordinate notranjega trikotnika izračunamo, kot kaže slika - notranje točke so pač na polovici med zunanjimi. Napisati moramo funkcijo za razdeljevanje trikotnika, ki dobi tri točke (oglišča trikotnika) in naredi tole:

  • znotraj trikotnika, ki ga opisujejo te točke, izriše obrnjeni trikotnik,
  • za vsakega od treh zunanjih trikotnikov pokliče funkcijo za razdeljevanje trikotnika.

    def sierpinski(Ax, Ay, Bx, By, Cx, Cy):
        ABx, ABy = (Ax + Bx) / 2, (Ay + By) / 2
        BCx, BCy = (Bx + Cx) / 2, (By + Cy) / 2
        CAx, CAy = (Cx + Ax) / 2, (Cy + Ay) / 2
        trikotnik(ABx, ABy,   BCx, BCy,   CAx, CAy)
        sierpinski(Ax, Ay,     ABx, ABy,   CAx, CAy)
        sierpinski(ABx, ABy,   Bx, By,     BCx, BCy)
        sierpinski(CAx, CAy,   BCx, BCy,   Cx, Cy)
    

Samo še en detajl moramo rešiti: gornja reč se nikoli ne ustavi. Funkcija riše manjše in manjše in manjše trikotnike, v neskončnost. Da to preprečimo, bomo dodali še en argument, ki bo povedal, kolikokrat še želimo razdeliti trikotnik. Po vsaki delitvi, pri vsakem rekurzivnem klicu bomo ta parameter zmanjšali: ko funkcija, ki ji je rečeno, da je potrebno trikotnik razdeliti še štirikrat, kliče samo sebe, si bo rekla, da ga je treba zdaj pa samo še trikrat. Ko ga je potrebno ničkrat, funkcija ne bo naredila ničesar več.

def sierpinski(Ax, Ay, Bx, By, Cx, Cy, n):
    if n == 0:
      return
    ABx, ABy = (Ax + Bx) / 2, (Ay + By) / 2
    BCx, BCy = (Bx + Cx) / 2, (By + Cy) / 2
    CAx, CAy = (Cx + Ax) / 2, (Cy + Ay) / 2
    trikotnik( ABx, ABy,   BCx, BCy,   CAx, CAy)
    sierpinski(Ax, Ay,     ABx, ABy,   CAx, CAy,  n-1)
    sierpinski(ABx, ABy,   Bx, By,     BCx, BCy,  n-1)
    sierpinski(CAx, CAy,   BCx, BCy,   Cx, Cy,  n-1)

In pokličemo:

trikotnik(Ax, Ay, Bx, By, Cx, Cy)
sierpinski(Ax, Ay, Bx, By, Cx, Cy, 6)

Lepo, ne?

Kar smo dobili je zelo znana reč, tudi ime ima: trikotnik Sierpinskega.

Naloga za frajerje (da ne boste izgubljali časa samo z grdimi programi v eni vrstici): napiši funkcijo tako, da bo narisala najprej veliki obrnjeni trikotnik, nato vse tri manjše obrnjene trikotnike, nato še vseh 9 še manjših ... in tako naprej. Funkcija pa naj bo še vedno rekurzivna (z malo iteracije od zunaj (tole je bil namig)).

Omahljivi vojaki

Trije hudobni desetniki poveljujejo veliki četi vojakov. Desetniki se postavijo v oglišča trikotnika in eden od njih zavpije "k meni"! Vojaki se poženejo proti njemu, a ko pridejo ravno na pol poti, eden od desetnikov (morda isti, morda kateri drugi), zavpije "k meni". Spet, čim pridejo na pol poti do njega, eden od desetnikov (naključno izbrani), zavpije "k meni". Tedaj eden od vojakov omaga, se zgrudi sredi polja, ostali pa tečejo. Na pol poti spet dobijo nov ukaz, eden od vojakov omahne ... in tako naprej. Če bi narisali polje, na katerem potekajo te "vojaške vaje" in vsako mesto, kjer je omahnil kak vojak, označili s piko: kaj bi dobili?

import risar
from random import randint

desetnik = [(10, 475), (537, 475), (273, 10)]
ceta_x = randint(0, 600)
ceta_y = randint(0, 600)

for i in range(50000):
    tuli = randint(0, 2)
    kje_tuli = desetnik[tuli]
    ceta_x = (ceta_x + kje_tuli[0]) / 2
    ceta_y = (ceta_y + kje_tuli[1]) / 2
    risar.tocka(ceta_x, ceta_y, risar.bela)
    if i < 500 or i < 10000 and i % 100==0:
        risar.cakaj(0.01)

V desetnik smo shranili mesta, kjer stojijo desetniki, ceta_x in ceta_y pa sta trenutni koordinati čete. Nato petdeset tisočkrat ponovimo tole: izberemo, kateri desetnik bo zatulil, ničti, prvi ali drugi. Iz seznama desetnik preberemo, koordinate tulečega desetnika. Nato izračunamo, kje je pol poti med trenutnim položajem čete (katere koordinati sta ceta_x in ceta_y) in desetnikom (čigar koordinati sta kje_tuli[0] in kje_tuli[1]). Na tem mestu narišemo piko. Po petdeset tisoč ponovitvah in petdeset tisoč omahnjenih vojakih dobimo takšnole sliko