Gimp to wielki powód do dumy całego środowiska Open Source. Jest jednym z najlepszych graficznych narzędzi dla Linuxa. Jest to program do tworzenia i obróbki grafiki rastrowej. Również znakomicie sobie radzi przy tworzeniu obrazków dla stron WWW. Program działa w środowisku XFree86, choć istnieją wersję na inne platformy takie jak MS Windows, Mac etc.

Wprowadzenie do Scheme dla użytkowników Gimpa

Spis treści

motylek1. Wprowadzenie

Jedną z ciekawszych właściwości Gimp'a jest fakt iż cała jego funkcjonalność może być oprogramowalna. Głównym językiem używanym do pisania skryptów w Gimp'ie jest pochodna Lispa - Scheme ( w najbliższej przyszłości ma być zastąpiony przez jeszcze bardziej okrojony język - tiny-Fu ). W tej częsci referatu postaram się przedstawić krótko ten stosunkowo prosty język programowania. Będą to niesetty tylko podstawy, ponieważ mimo swej prostoty język Scheme to dość potężne narzędzie.

motylek2. Podstawy

W Scheme tak samo jak w lispie, wszystkie funkcje wywołujemy otaczając je nawiasami. Na początku wyrażenia podajemy nazwę funkcji, a następnie jej argumenty. Np.: suma 1 i 2 będzie wyglądała następująco:
( + 1 2 )
gdzie '+' to nazwa funkcji ( suma ), a 1 i 2 to jej argumenty. Wyrazenia takie mozemy dowolnie zagniezdzac np:
	( + 1 ( - 4 5 ) )
	
oznacza 1 + ( 4 - 5 ) Tzw. "białe znaki" ( takie jak spacje czy tabulatory) nie mają znaczenia .

motylek3. Funkcje

Poza funkcjami arytmetycznymi, Scheme ( jak każdy porządny język programowania ), ma wiele innncyh. Wszystkie odpalamy analogicznie do operatorow czyli:
	( funkcja arg1 arg2 ... )
Aside from the four arithmetic functions that are represented through the symbols + - * / there are lots of other functions built into the language. All of them have the form
    (foo param1 param2 ...)
Własne funkcje możemy definiowac za pomoca slowa kluczowego define. Przykladowo wyrazeniem:
	( define ( kwadrat x ) ( * x x ) )
tworzymy funkcję podnoszącą liczbę do kwadratu, z której możemy skorzystać w sposób następujący:
	( kwadrat 3 ) 
( a ile to bedzie ?!? ) ;)

motylek4. Zmienne i listy

Zmienne deklarujemy i ustawiamy za pomocą słowa kluczowego set!. Zmienne tak zadeklarowane będą globalne, lecz my jako programiści gimpa nie musimy się tym przejmować ( po każdym uruchomieniu środowisko samo się czyści ). A oto i kilka przypisań:
	(set! zielony 4352 )
	(set! kat_w_radianach ( * 3.141 ( / 60 180 ) ) )
Podobnie jak w lispie kluczową rolę dla języka Scheme mają listy. Script-fu ( odmiana Scheme którą będziemy się zajmować ) nie jest wyjątkiem i również bardzo mocno wykorzystuje listy, np. przy zapisie liczb w systemie RGB ( red green blue ).
	'(255 127 0)
to na przykład zapis koloru pomarańczowego. Znak ' zawarty przed listą ma kluczowe znaczenie. Oznacza on cytowanie - czyli mówi on imterpreterowi by nie traktował listy jako wywołanie funkcji 255 ( ;) ), lecz raczej jako lista będąca stałą.

Aby stworzyć zmienną nazwaną jako pomarancz z daną wartoscią, a następnie użyć jej jako tła piszemy co następuje:

	(set! pomarancz '(255 127 0) )
	(gimp-set-background-color pomarancz)
Uwaga dla programujących w LISP'ie i pochodnych : W Scheme inną wartością oznacza się fałsz, listę pustą i wartość niezdefiniowaną

motylek4.1. car, cdr i inne gady (*)

Lista, tak jak w lispie składa się "głowy" i "ogona". "Głową" nazywamy jej pierwszy element, a "ogonem" resztę listy. Dla przykładu dla listy '( 127 0 0 ) , głową jest 127, a ogonem lista (!) '( 0 0 ). Funkcja car zwraca nam właśnie "głowę", cdr "ogon". A oto i przykład użycia wymienionych funkcji:
	(set! kolor '( 127 0 1 ) )
	( 127 0 1 )
	 ( car kolor ) 
	127
	 (cdr kolor) 
	( 0 1 )
Aby pobrac wartosc niebieskiego dla koloru trzeba sie troszke nameczyc stosujac kombinacje wczesniej poznanych funkcji:
	( car ( cdr ( cdr ( kolor ) ) ) ) 
Zeby ulatwić prace programistom wprowadzono dodatkowe funkcje, takie jak: - cadr - cddr - caddr dzięki którym można w dosyć prosty sposób dostać sie do odpowiednich elementów. Dla przykładu nasza operacja w skrócie wygląda następująco:
	( caddr kolor
W Script-fu funkcje zwracają wartości w postaci list, co czyni car jedną z najbardziej użytecznych funkcji. Np. dla funkcji gimp-new-image czy gimp-new-layer ( które użyjemy za chwilę ), wartością zwracaną jest tylko jeden element, ale ponieważ zawiera się on w liście, dostęp do niego mamy dzięki funkcji car

motylek4.2. Zmienne lokalne (*)

Bardziej doświadczeni programiści używają zmiennych lokalnych, co jest oczywistym wyborem szczególnie w dużych i rozbudowanych funkcji lub w takich w których stosujemy rekurencję.

Zmienne lokalne deklarujemy za pomocą słowa kluczowego let* tak jak w następującym przykładzie:

	(let* ( ( a 5 )
			( b 14 )
			( ( ( * a b ) ) )
	)
W naszym przykładzie zmienne a i b obowiązują tylko w obrębie nawiasów okaląjacych funkcję let*

motylek4.3. Tablice

Dostęp do tablic realizowany jest poprzez konstrukcje:
(set! tab (cons-array 4 'byte))
(aset a 2 42)
(aref a 3)
gdzie CONS-ARRAY tworzy tablicę, ASET wpisuje podaną wartość do tablicy, natomiast AREF zwraca zawartość odpowiedniej komórki tablicy.

motylek5. Instrukcje sterujące

Jak każdy sznujący się język programowania, Scheme posiada ( co prawda mocno okrojone, ale za to bardzo funkcjonalne instrukcje sterujące. Jeśli chodzi o instrukcję warunkową to mamy:
	( if ( warunek ) ( jesli_warunek_spelniony ) ( jesli_warunek_nie_spelniony )) 
Jedyną pętlą obecną w script-fu jest while. Oto jej składnia:
	( while ( warunek )
			( dzialanie1 )
			( dzialanie2 )

	)

motylek6. PDB - gimpowa procedural database

Wszystkie funkcje z jakich możemy korzystać, by oprogramować GIMP'a są dostępne dzięki tzw. PDB ( procedural database ). Każda z procedur tam zawartych ma odpowienik w postaci funkcji scheme, np:
	( gimp-image-new 640 480 RGB )
tworzy nowy obrazek w gimpie o rozmiarach 640x480 ( słodkie VGA :) ) z paletą kolorów RGB/

Dla przykładu procedura gimp-image-new wygląda następująco:

i jak widać dostarcza nam wielu cennych informacji takich jak: - parametry - wartości zwracane - autor - etc.

motylek7. Rejestrowanie skryptu w Script-Fu

Po tym jak już uda nam się napisać jakąś użyteczną funkcję ( co uczynimy niebawem ), aby móc jej używać, musimy zarejestrować ją w script-fu. Robimy to za pomocą funkcji script-fu-reqister. Oto cele rejestrowania funkcji:
  1. Wybór miejsca zadokowania skryptu w rozwijanych menu Script-fu
  2. Powiedzenie script-fu jaki typ parametrów skrypt pobiera i nadanie im domyślnych wartości
  3. Zarejestrowanie skryptu w PDB
Ostatni punkt dokładnke oznacza, że skrypt stanie się integralną częścią Gimpa tak jak komendy wbudowane czy plug-in'y. Tak długo jak będzie zarejestrowany będziemy go mogli używać z wnętrza programu. Parametry przyjmowane przez script-fu-register możemy podzielić na dwie części. Pierwsza to te które zawsze muszą być podane. Są to:
  1. Nazwa funkcji
  2. Umiejscowienie w menu.
  3. Krótki opis funkcji.
  4. Autor skryptu.
  5. Licencja.
  6. Data utworzenia.
  7. Lista obrazków dla których może być wywoływany ten skrypt. Dotyczy to tylko obrazków które już istnieją.
Poza tym podajemy argumenty jakie przyjmuje skrpyt. Każdy parametr ma trzy atrybuty:
Typ argumentu Typ danych Opis
SF-IMAGE Wartość całkowita (id obrazka) Do pobrania id obrazka
SF-DRAWABLE Wartość całkowita (id obszaru) Pobranie id obszaru
SF-VALUE String Wartość wprowadzona
SF-TOGGLE Wartość logiczna (TRUE lub FALSE) Do wprowadzania wartości logicznych
SF-PATTERN String (Nazwa patternu) Pozwala wybrać pattern( deseń )
SF-ADJUSTMENT Lista (wart-startowa wart-min wart-max maly-krok duzy-krok [int=0 lub float=1] [slider=0 lub roll-box=1]) Tworzy suwak lub okienko do wprowadzania wartości
SF-FILENAME String (nazwa pliku) Pozwala wybrać plik
SF-STRING String Do wprowadzania stringów
SF-FONT String ( nazwa fonta ) Pozwala wybrać czcionkę
SF-COLOR Lisa (RGB) [0-255] Pozwala wybrać kolor
SF-OPTION Lista stringów Pozwala wybrać wartość z listy
SF-GRADIENT String (nazwa gradientu) Pozwala wybrać gradient
A oto przykład rejestracji skryptu ( ze wszystkimi możliwymi opcjami ):
(script-fu-register "my-demo-box"
 "/kubek2k/Script-Fu/Demko..."
 "Do nothing"
 "Joe User"
 "Joe User"
 "August 2000"
 ""
 SF-ADJUSTMENT "SF-ADJUSTMENT (slider)" '( 30 1 2000 1 10 1 0)
 SF-ADJUSTMENT "SF-ADJUSTMENT"         '(400 1 2000 1 10 1 1)
 SF-COLOR      "SF-COLOR" '(255 0 255)
 SF-DRAWABLE   "SF-DRAWABLE" 0
 SF-FONT       "SF-FONT" ""
 SF-GRADIENT   "SF-GRADIENT"  "Golden"
 SF-IMAGE      "SF-IMAGE" 0
 SF-OPTION     "SF-OPTION" '("Option 1" "Option 2" "Option 3")
 SF-PATTERN    "SF-PATTERN" "Wood"
 SF-STRING     "SF-STRING" "Testowy String"
 SF-TOGGLE     "SF-TOGGLE" TRUE
 SF-VALUE      "SF-VALUE" "0"
 SF-FILENAME   "SF-FILENAME" "/")

motylek7.1. Umieszczanie skryptu w menu

Jak wiemy umiejscowienie skryptu w menu określa argument 2. W naszym przypadku jest to pasek narzędziowy, ale mamy też inne możliwości. Nie podając na początku ścieżki specjalnej sekwencji umieszczmy swoje skrypty w głównym oknie Gimpa. Możemy też podać <Image> co będzie oznaczać że skrypt będzie się znajdował w menu okienka z obrazkiem ( najlepszy wybór ).

motylek8. Zaznaczenia

Wklejając coś ze schowka resultat nie jest wprowadzany bezpośrednio na warstwę, lecz znajduje się w tzw. warstwie tymczasowej. Możemy sobie manipulować tą warstwą ile tylko chcemy do czasu aż nie wkleimy czegoś nowgo lub jej nie zaktwiczymy ( symbol kotwicy ).

motylek8.1. Kopiowanie zaznaczonego obszaru

Aby dokonać skopiowania zaznaczonego obszaru, używamy funkcji gimp-edit-copy. Po takiej operacji całe zaznaczenie znajdzie się w buforze który możemy następnie wykorzystać, na przykład jako nową warstwę w rysunku.

motylek9. Hello World - pisanie po obrazku

Pierwszym skryptem który wykonamy będzie prościutki ( tak się wydaje ) skrypt piszący nam po obrazku dowolny tekst i tworzący cień dla niego. Rozpiszmy sobie kroki:
  1. Utworznie napisu takiego jaki chcemy
  2. Stworzenie jego kopii
  3. cieniowanie z pomocą kopii
Teraz należy się zastanowić czy to wszystko ( ;) ) i czy jesteśmy to w stanie uczynić bez ingerencji użytkownika. Pierwszy krok wygląda na prosty - szukamy zatem funkcji która będzie w stanie nam pomóc w wypisywaniu tekstu na obrazku i ... znajdujemy ( gimp-text-fontname ). Wygląda na to że będziemy potrzebować: numeru obrazka ( o to zadba program ), umiejscowienia nowej warstwy ( to nie jest aż takie istotne ), wielkości i kroju czcionki ( tym zajmie się użytkownik ). Kiedy już zwalilismy większość pracy na innych ( ;) ) testujemy nową procedurę z pomocą konsoli ( jest ona osiągalna z menu Dodatki->Script-Fu->Script-Fu console... ).
Następnym krokiem który powinniśmy uczynić to stworzenie kopii naszego napisu. Posłużymy się tu operacją ( gimp-layer-copy ) - funkcja ta jest stosunkowo prosta - jej wywołanie polega na podaniu kopiowanej warstwy i kanału alpha ( to nie jest dla nas istotne ). Niestety brak duplikacji warstw w script-fu zmusza nas do dodania kopii do obrazka. Uważnie szukając odnajdujemy ( gimp-image-add-layer ). Podajmy jej numerek obrazka, warstwy do dodania i pozycję nowej warstwy.
Super - mamy dwie kopie stworzonego tekstu, ale co z tym zrobić? Słuszne pytanie - tutaj musimy posłużyć się wrodzonym instynktem i zastanowić się jak cień może wyglądać. Autor po przeczytaniu jednego z podręczników do Gimp'a ( bo sam oczywiście nie był na tyle inteligentny by na to wpaść ), dowiedział się, że jednym z lepszych sposobów na stworzenie efektownego cienia jest wprowadzenie tak zwanego zakłócenia do obrazka ( ang. noise ). Chcąc to uczynić posłużymy się tzw. rozmyciem Gaussa ( funkcja ( plug_in_gauss ) ). Następnie chcąc wmówić użytkownikowi że znajduje się w trzecim wymiarze, przesuwamy nieco rozmyta warstwę względem pierwotnej ( funkcja ( gimp-layer-translate ) ). Dobrze, ale jak ma wyglądać taki skrypt?? Oto i on:
	
( define ( hello-world image warstwa text font-size font )
		 ( let* ( ( nasztext ( car ( gimp-text-fontname image -1 
							( / ( car (  gimp-image-width image ) ) 2 )
							( / ( car ( gimp-image-height image ) ) 2 )
								  text
								  0 TRUE font-size 1
								  font ) )
				  ) 
				  ( kopia ( car (gimp-layer-copy nasztext 1 ) ) ) 
				 )
				  ( gimp-image-add-layer image kopia 0 )
				  ( plug-in-gauss 1 image kopia 3 3 0 )
				  ( gimp-layer-translate kopia 3 3  )
				  ( gimp-image-raise-layer image nasztext )
				  ( gimp-image-merge-down image nasztext 0 )
		 		 ( gimp-displays-flush )
		 )
)
			    

(script-fu-register "hello-world"
					_"/Script-Fu/kubek2k/Hello World..."
					"Wypisuje dowolny tekst i tworzy cien..."
					"Jakub Janczak"
					"Jakub Janczak"
					"January 2005"
					""
					SF-IMAGE "Image" 0
					SF-DRAWABLE "Drawable" 0
					SF-STRING      "Text" "Hello World!!!"
					SF-VALUE "Font-size" "14"
					SF-FONT "Fontname" "Arial" )


Źródło skryptu
Użyte funkcje
wyjaśnienia wymagają jeszcze funkcje ( gimp-image-raise-layer ), ( gimp-image-merge-down ) i ( gimp-displays-flush ). Pierwsza odpowiada za wyniesienie warstwy pierwotnej nad rozmytą ( bez tego nie byłoby efektu ). Druga powoduje scalenie warstw. Trzecia powoduje wyświetlenie wszystkich zmian znajdujących się jeszce w buforach wewnętrznych Gimp'a. A oto i przykładowy obrazek wygenerowany skryptem:

motylekPrzykłady

Pierwszy z przykładów wymazuje co n-ty pikselowy pasek z obrazka
Oto przykład działania skryptu:
;;
;;  Skrypt do wymazywania co n-tej kolumny
;; 	Jakub Janczak
;; 
;;
(define (script-fu-wymaz-co-n img drawable n)
  (gimp-undo-push-group-start img)
  (let* (
  			(szer (car (gimp-drawable-width drawable)))
  			(wys (car (gimp-drawable-height drawable)))
			( x 0 )
		)
		(
  			(while (< x szer )
        	 (gimp-rect-select img x 0 1 wys REPLACE FALSE 0)
    	     (gimp-edit-clear drawable)
         	 (set! x (+ x n))
        	)
		)
	)
  	(gimp-selection-none img)
  	(gimp-undo-push-group-end img)
  	(gimp-displays-flush)
  )

(script-fu-register "script-fu-wymaz-co-n"
                    _"<Image>/Script-Fu/moje/Wymaz co n-ta kolumne..."
                    "Wymazuje co n-ta kolumne"
                    "Jakub Janczak"
                    "GNU GPL"
                    "9 sty 2005"
                    "RGB* GRAY* INDEXED*"
                    SF-IMAGE "Image" 0
                    SF-DRAWABLE "Drawable" 0
                    SF-ADJUSTMENT "Every Nth Pixel" '(3 2 10 1 1 0 0) 
						;; ciekawa rzecz - tworzy dosyć wygodny suwak
                    )'
Źródło skryptu
Użyte funkcje:
( gimp-rect-select obraz x y szer wys operacja zmiekcz wsp_zmiekcz ) -worzy zaznaczenie prostokątne w p (x,y) o wysokości wys i szerokości szer. Trzeci argument to typ operacji ( przecięcie, dodanie, odjęcie ). Czwarty argument jest odpowiedzialny za zmiękczenie brzegów ( ustawienie go daje całkiem ciekawe efekty ).

( gimp-edit-clear obszar ) - czyści zaznaczony obszar
( gimp-selection-none obraz ) - wyłącza wszelkie zaznaczenia na obrazie
( gimp-displays-flush) pokazuje wszystkie zmiany na obrazkach ( tzw. flush )
Drugi omawiany skrypt tworzy efekt półprzezroczystej siatki. A oto przykład działania:

Przed:

Po:

Po niewielkich zmianach w skrypcie możemy uzyskać jeszcze ładniejszą siatkę:

;;
;; Jakub Janczak
;; Tworzy ciekawy efekt półprzezroczystej siatki na zaznaczonej częsci obrazka  
;; 
;;
(define (script-fu-grid-overlay img drawable color size)
  (define (draw-line drawable startx starty endx endy)
    (let ((line (cons-array 4 'double)))
      		(aset line 0 startx)
      		(aset line 1 starty)
		    (aset line 2 endx)
     		(aset line 3 endy)
		    (gimp-pencil drawable 4 line)
      ))
  (gimp-undo-push-group-start img)
  (let* ((szer (car (gimp-drawable-width drawable)))
         (wys (car (gimp-drawable-height drawable)))
         (siatka (car (gimp-layer-new img szer wys 
                                          RGB-IMAGE 
                                          "grid" 
                                          100 
                                          NORMAL-MODE)))
         (sx 0)
         (sy 0)
         )
    (gimp-layer-add-alpha siatka)

    (gimp-image-add-layer img siatka -1)
    
    (gimp-rect-select img 0 0 szer wys REPLACE FALSE 0)
    (gimp-edit-clear siatka)
    
    (gimp-palette-set-foreground color)
    (gimp-brushes-set-brush "pixel (1x1 square)")

    (while ( > sy wys)
           (set! sy (+ sy size))
           (draw-line siatka 0 sy szer sy)
           )
    (while (> sx szer )
           (set! sx (+ sx size))
           (draw-line siatka sx 0 sx wys)
           )
    
    (gimp-layer-set-opacity siatka 25)
    
    (gimp-selection-none img)
    (gimp-undo-push-group-end img)
    (gimp-displays-flush)
    )
)

(script-fu-register "script-fu-grid-overlay"
                    _"<Image>/Script-Fu/kubek2k/Dodaj przezroczysta siatke..."
                    "Dodaje przezroczysta siatke"
                    "Jakub Janczak"
                    "Jakub Janczak"
                    "Sty 2005"
                    "RGB* GRAY* INDEXED*"
                    SF-IMAGE "Image" 0
                    SF-DRAWABLE "Drawable" 0
                    SF-COLOR      _"Color"  '(255 255 255)
                    SF-VALUE      _"Size" "10")
' Źródło skryptu
Użyte funkcje:
( gimp-layer-new obraz szer wys paleta_warstwy nazwa_warstwy stopien_pokrycia sposób_scalenia ) - tworzy nową warstwę ( ważne aby paleta kolorów warstwy nie była bogatsza od palety obrazu ). Stopień pokrycia oznacza jak bardzo przezroczytsta będzie nowa warstwa.
( gimp-pencil obszar ilosc_wspolrzednych wspolrzedne ) - narzedzie rysuje ołówkiem po zadanych w 3. parametrze współrzędnych
( gimp-palette-set-foreground kolor ) - ustawia kolor wypełnienia
( gimp-layer-set-opacity wsp_przezroczystosci ) - ustawia współczynnik przezroczystości warstwy
Przy okazji zobaczyliśmy jak korzystamy z tablic i funkcji wewnętrznych w Scheme
Ostatni z przykładów kopiuje obrazy ze schowka do aktualnego obrazu w sposób losowy dobierając pozycję i kąt obrotu
Nazwa motylki pochodzi od pierwotnego przeznaczenia skryptu który miał symulować nalot chmary motyli ;). Obecnie służy np. do zaśnieżania obrazków ( śniegu mamy w tym momencie aż zanadto ;) ).

Kod programu:

( define ( motylki image warstwa n ) 
		 ( let* ( 
				  ( wys ( car ( gimp-image-height image ) ) )
				  ( szer ( car ( gimp-image-width image ) ) )
				  ( i 0 )
				  )
				( while ( < i n )
					( let* ( 
					 ( tmp ( car ( gimp-edit-paste warstwa 100 ) ) )
					 ( wystmp ( car ( gimp-drawable-height tmp ) ) )
					 ( szertmp ( car ( gimp-drawable-width tmp ) ) )
					 )
 					   ( gimp-layer-set-offsets tmp 
					   	( rand ( - szer szertmp ) ) 
					   	( rand ( - wys wystmp ) ) )
					   ( gimp-drawable-transform-rotate tmp 
						( / ( rand 180 ) 6.28 ) 1 0 0 0 0 0 3 0 )
					   ( print i )
					   ( set! i ( + i 1 ) )
					   )
					)
		( gimp-image-merge-visible-layers image 1 )
	)
 )

(script-fu-register "motylki"
					_"<Image>/Script-Fu/kubek2k/Motylki..."
					"Wrzuca n zaznaczen..."
					"Jakub Janczak"
					"Jakub Janczak"
					"January 2005"
					""
					SF-IMAGE "Image" 0
					SF-DRAWABLE "Drawable" 0
					SF-VALUE      _"Size" "10")

Źródło skryptu
Jedyne co wymaga wyjasnienia to funkcje:
( gimp-layer-set-offsets ) - funkcja ustalająca oległość warstwy od górnego lewego rogu obrazka
( gimp-image-merge-visible-layers ) - funkcja scala wszustkie widoczne warstwy

motylek11. Materiały:

To wszystko - miłego Gimpowania!!!
Autor: Jakub Janczak. W ramach referatu z przedmiotu Obliczenia Symboliczne prowadzonych w Katedrze Informatyki AGH
Adres orginału http://student.uci.agh.edu.pl/~janczak/gimp-scheme/scheme.html