GIMP-batch & animaatio

Lyhyet ohjeet GIMP:n scriptaamiseen Scheme-kielellä. GIMP on todella monipuolinen ja kätevä kuvankäsittelyohjelma — harjoitustehtäviä ja opastusta voit katsoa vaikka luntti.net/atk:sta. Siellä on suurinpiirtein vaikeustason mukaisesti hyviä harjoitustehtäviä.

Kaiken mitä GIMPin graafisella kilkkeellä saa tehtyä, saa tehtyä myös skriptaamalla.

Tässä tavoitteena on generoida hakemistossa olevista kuvista .gif-animaatio ja optimoida se. Homma on hyvin yksinkertainen ja tylsä, siis helppo automatisoida. Muista menetelmistä lisää muualla.

Kommentteja ja vinkkejä ja virheistä voi kertoa tai kysyä vaikkapa twitteristä @MarkkuOpe.

GIMP:llä on helppo tuottaa .gif-animaatio valmiista kuvista:

Valitse kuvat:

Vielä ennen talletusta (File → Export) animaatio kannattaa optimoida:

Tylsää, mutta tuottaa hyvän lopputuloksen. Miksei sitä siis automatisoida!

Schemeä ei tarvitse erikseen asentaa näitä esimerkkejä varten, vaan voit suoraan kokeilla sitä GIMP:llä: Filters → Script Fu → Console.

Scheme perustuu prefix-notaatioon, eli operaattorit (funktiot) kirjoitetaan ensin. Välilyönneillä ja rivinvaihdoilla on merkitystä ainakin lukemisen kannalta, eli alla oleva sijoituslauseke

( let*
  (
    ( a 1 )
    ( b 2 )
  )
  (+ a b )
)

voidaan kirjoittaa myös lyhyesti

( let* ( ( a 1 ) ( b 2 )) (+ a b )) 

Muuttujalle voidaan antaa uusi arvo set-käskyllä:

( let* ( ( theNum 2 ) ) (set! theNum (+ theNum theNum ) )) 

Muuttujia ei tarvitse määritellä erikseen, vaan Scheme ymmärtänee ne. Usein kirjoitetaan the tai joku muu vastaava kuvaamaan sisäisiä muuttujia, ja in on sisääntuleva (argumentti) muuttuja. Ulostulevaa ei tarvitse erikseen return-lauseella eritellä.

Nämä ovat lokaaleja muuttujia.

Funktioiden määrittely onnistuu define:llä:

( define (Addxy inX inY ) (+ inX inY) ) 

jolloin voidaan laskea esim

( Addxy (Addxy 2 3) 4 ) 

Myös taulukot (eli listat) onnistuu ongelmitta

(let* (( x '(1 2 3))) x)

Viimeinen, perässä oleva x tulostaa ruudulle, jotta nähdään mitä tehdään. Tyhjä taulukko voidaan luoda tyhjillä sulkeilla, esimerkiksi

(let* (( y '() y  )))

Taulukon alkuun voidaan lisätä alkioita cons-käskyllä

(cons 1 '(2 3 4))

Taulukon alusta saadaan alkioi car-käskyllä:

(car '("eka" 2 "kolmas"))

ja lopputaulukko saadaan cdr-käskyllä.

Lähde: http://www.gimp.org/tutorials/Basic_Scheme/

GIMP:n kaikki käskyt ja filtterit (tosiaan kaikki) ovat käytettävissä Scheme-skriptistä, PDB:stä eli proseduraalisesta tietokannasta. Katso ohjeet ja lista Help → Procedure Browser. Hakutoiminto on erittäin kätevä. Nyt tarvitaan mahdollisesti seuraavia funktioita

  • gimp-file-load-layer ja/tai gimp-file-load-layers
  • file-gif-save
  • plug-in-animationoptimize ja plug-in-animationoptimize-diff

Ladataan kuva konsolilla:

 (gimp-file-load RUN-NONINTERACTIVE "kuva.png" "kuva.png")

kunhan tiedosto kuva.png on juuressa, niin tuntui toimivan. Tietenkin kuvan koko polun voi antaa skriptille. Lisäksi kuva pitäisi näyttää ruudulla, mutta jätetään se harjoitusteähäväksi. Siirrytään suoraan skriptailemaan.

Scheme-skriptit voi kirjoittaa lempieditorilla, ja tallettaa scm-muodossa vaikka .gimp-kansiossa olevaan scripts/-hakemistoon.

Scheme-skriptit voi kirjoittaa lempieditorilla, ja tallettaa scm-muodossa vaikka .gimp-kansiossa olevaan scripts/-hakemistoon.

Alla oleva skripti lataa ensin yhden kuvan, sitten siihen toisen kuvan toiseksi tasoksi. Lopuksi kuva pitää muuntaa indeksoiduksi värikuvaksi ja tallettaa .gif-animaatioksi. Toimii aika hyvin:

 (define ( two-image-animation fname-one fname-two)
  (let* (
         ; first main image
         (image (car (gimp-file-load RUN-NONINTERACTIVE fname-one fname-one)))
         ; them layer
         (drawable1 (car (gimp-image-get-active-drawable image)))
         ; open image as layer
         (drawable2 (car (gimp-file-load-layer RUN-NONINTERACTIVE image fname-two)))
         )

    ; add layer to image
    (gimp-image-insert-layer image drawable2 0 0)

    ;set layer mixing mode
    (gimp-layer-set-mode drawable2 SCREEN-MODE)


    ; convert to indexed (not working otherwise):
    (gimp-image-convert-indexed image 3 2 32 0 0 "huhuu")


    ; save
    (file-gif-save RUN-NONINTERACTIVE image drawable2 "my.gif" "my.gif" 0 1 200 0)
    )
  )

Skriptiä käytetään käskyllä

gimp -i -b '(two-image-animation "kuva1.png" "kuva2.png" )' -b '(gimp-quit 0)' 

Seuraavaksi ladataan kaikki hakemistossa olevat kuvat.

Ladataan kaikki annetun nimen mukaiset kuvat, esimerkiksi "*.png". Ikävä kyllä, file-glob lataa kuvat satunnaisessa järjestyksessä. Se korjataan vasta optimoinnin jälkeen. Alla oleva koodinpätkä luo ensin kuvan, sitten hakee kaikki tiedostot. Tämän jälkeen kuvatiedostot avataan uudeksi tasoksi ja liitetään kuvaan.

Lopuksi muutetaan kuvan koko tasojen kokoiseksi, muutetaan kuva taas indeksoiduksi ja talletetaan.

(define (image-animation pattern )
  (let* (
          ; Create a new image
          (theImg (car (gimp-image-new 10 10 RGB)  ) )
          ; load a list of files  )
          (filelist (cadr (file-glob pattern 1))) 
        )
       ; Loop through all files
       (while (not (null? filelist))
         (let* (
                  ; Get the name of the file
                  (filename (car filelist)) 
                  ; Load the current file in as a layer
                  (layer (car (gimp-file-load-layer RUN-NONINTERACTIVE theImg filename)))
                )

                ; Insert the new layer into the image
                (gimp-image-insert-layer theImg layer 0 0)
                ; Set the mode to 
                (gimp-layer-set-mode layer NORMAL-MODE)  

                ; Increment the file pointer
                (set! filelist (cdr filelist))
         )
       )
       ; Resize the image to fit all layers
       (gimp-image-resize-to-layers theImg)

       ; convert to indexed (not working otherwise):
       (gimp-image-convert-indexed theImg 3 2 32 0 0 "huhuu")

       ; save
       (file-gif-save RUN-NONINTERACTIVE theImg (car (gimp-image-get-active-layer theImg)) "my.gif" "my.gif" 0 1 200 0)
  )
)

Tässä kannattaa katsoa, miten let*-käsky toimii ja kuinka niitä on sisäkkäin — ja kuinka silmukka on let*:n sisällä! Lisäksi kannattanee huomata, miten taso otettiin käyttöön talletus-kohdassa. Koska layer-niminen muuttuja ei ollut enää määritelty, car-käskyllä haettiin kuvan ensimmäinen aktiivinen taso.

Skriptiä käytetään käskyllä

gimp -i -b '(image-animation "*.png" )' -b '(gimp-quit 0)' 

Ehkäpä parhaana ohjeena tähän oli Automating Astrophoto Processing.

Ideana tässä jutussa on saada kooltaan pieni .gif-animaatio, ja aiemmin on huomattu että GIMPin optimointialgoritmit ovat todella hyviä. Siispä käytetään niitä skriptissä.

Tässä oleellista on huomata, että optimointikäskyt palauttavat uuden kuvan. Siis se kuva pitää let*-käskyllä sijoittaa uuteen muuttujaan. Vastaavalla tavalla otetaan kuvasta aktiivinen taso omaksi muuttujakseen.

(define (animate pattern )
  (let* (
          ; Create a new image
          (theImg (car (gimp-image-new 10 10 RGB)  ) )
          ; load a list of files  )
          (filelist (cadr (file-glob pattern 1))) 
        )
        ; Loop through all files
        (while (not (null? filelist))
         (let* (
                  ; Get the name of the file
                  (filename (car filelist)) 
                  ; Load the current file in as a layer
                  (layer (car (gimp-file-load-layer RUN-NONINTERACTIVE theImg filename)))
                )
                ; Insert the new layer into the image
                (gimp-image-insert-layer theImg layer 0 0)
                ; Set the mode to . . . NORMAL
                (gimp-layer-set-mode layer NORMAL-MODE)  

                ; Increment the file pointer
                (set! filelist (cdr filelist))
         )
        )
        ; Resize the image to fit all layers
        (gimp-image-resize-to-layers theImg)

        ; convert to indexed (not working otherwise):
        (gimp-image-convert-indexed theImg 3 2 32 0 0 "huhuu")

        (let* ( 
              (theLayer (car (gimp-image-get-active-layer theImg))  )
              (theImgOpt1 ( car (plug-in-animationoptimize-diff RUN-NONINTERACTIVE theImg theLayer   ) ) )
              (theLayer1 (car (gimp-image-get-active-layer theImgOpt1))  )
              (theImgOpt2 ( car (plug-in-animationoptimize RUN-NONINTERACTIVE theImgOpt1 theLayer1   ) ) )
              (theLayer2 (car (gimp-image-get-active-layer theImgOpt2))  )
              )
          ; save
          (file-gif-save RUN-NONINTERACTIVE theImg theLayer "my.gif" "my.gif" 0 1 200 0)
          (file-gif-save RUN-NONINTERACTIVE theImgOpt1 theLayer1 "myOpt1.gif" "myOpt1.gif" 0 1 200 0)
          (file-gif-save RUN-NONINTERACTIVE theImgOpt2 theLayer2 "myOpt2.gif" "myOpt2.gif" 0 1 200 0)
        )
   )
)

Testiajossa tiedostojen koot pienenivät selvästi:

$ ls -lrt my*gif
-rw-rw-r-- 1 mol mol 5514342 syys  14 09:57 my.gif
-rw-rw-r-- 1 mol mol 5001055 syys  14 09:57 myOpt1.gif
-rw-rw-r-- 1 mol mol 4375280 syys  14 09:57 myOpt2.gif

Tiedostojen lataus file-glob -käskyllä palauttaa listan, jota ei ole järjestetty. Siispä järjestetään se vaikka quicksort-algoritmilla. Katso vaikka Rosettacode.org:sta, miten se koodataan.

Ainoa ongelma on, että se toimii kokonaisluvuille. Rosetta-koodin srfi-1 -koodi on kätevä, ja se ottaa sisäänsä vertailuoperaattorin, tässä tapauksessa ">". Siispä laitetaan sinne "string>?". Tarkista myös, miten quicksort-funktio määritellään.

 (define ( animate pattern time )
   ; sorting algorithm using quicksort-algorithm defined at rosettacode
   (define (quicksort l gt?)
        (if (null? l)
                 '()
                 (append (quicksort (filter (lambda (x) (gt? (car l) x)) (cdr l)) gt?)
                 (list (car l))
                 (quicksort (filter (lambda (x) (not (gt? (car l) x))) (cdr l)) gt?)))
   )      
   (let* (
          ; Create a new image
          (theImg (car (gimp-image-new 10 10 RGB)  ) )
          ; load a list of files unsorted BUT sort the list! 
          (filelist (quicksort (cadr (file-glob pattern 1)) string>?) )
        )
       ; Loop through all files
       (while (not (null? filelist))
         (let* (
                  ; Get the name of the file
                  (filename (car filelist)) 
                  ; Load the current file in as a layer
                  (layer (car (gimp-file-load-layer RUN-NONINTERACTIVE theImg filename)))
                )
                ; Insert the new layer into the image
                (gimp-image-insert-layer theImg layer 0 0)
                ; Set the mode to 
                (gimp-layer-set-mode layer NORMAL-MODE)  

                ; Increment the file pointer
                (set! filelist (cdr filelist))
         )
       )
       ; Resize the image to fit all layers
       (gimp-image-resize-to-layers theImg)
       ; convert to indexed (not working otherwise):
       (gimp-image-convert-indexed theImg 3 2 32 0 0 "huhuu")
       (let* ( 
             (theLayer (car (gimp-image-get-active-layer theImg))  )
             (theImgOpt1 ( car (plug-in-animationoptimize-diff RUN-NONINTERACTIVE theImg theLayer   ) ) )
             (theLayer1 (car (gimp-image-get-active-layer theImgOpt1))  )
             (theImgOpt2 ( car (plug-in-animationoptimize RUN-NONINTERACTIVE theImgOpt1 theLayer1   ) ) )
             (theLayer2 (car (gimp-image-get-active-layer theImgOpt2))  )
             )
        ; save
        (file-gif-save RUN-NONINTERACTIVE theImg theLayer "my.gif" "my.gif" 0 1 time 0)
        (file-gif-save RUN-NONINTERACTIVE theImgOpt1 theLayer1 "myOpt1.gif" "myOpt1.gif" 0 1 time 0)
        (file-gif-save RUN-NONINTERACTIVE theImgOpt2 theLayer2 "myOpt2.gif" "myOpt2.gif" 0 1 time 0)
      )
  )
)

Lisäksi lisäsin funktiokutsuun time-argumentin, jotta animaation ruutujen välistä aikaa voisi säätää helpommin. Ikävä kyllä, optimoiduilla animaatioilla aika-argumentti ei toiminut.