Un­se­re Dienst­lei­stun­gen

Sie be­nö­ti­gen Hil­fe bei der Er­wei­te­rung oder Au­to­ma­ti­sie­rung von GIMP oder bei der Ent­wick­lung in Scheme, LISP oder an­de­ren funk­tio­na­len Spra­chen?

Dann neh­men Sie Kon­takt mit uns auf. Wir neh­men uns ger­ne Zeit für ei­ne aus­führ­li­che und ko­sten­lo­se Erst­be­ra­tung.

Hin­weis

Die­ser Ar­ti­kel wur­de in et­was an­de­rer Form im ent­wick­ler-​ma­ga­zin (Aus­ga­be 3.10) ver­öf­fent­licht.

Die­ser Ar­ti­kel wur­de im Ok­to­ber 2010 erst­mals auf un­se­rer Web­site ver­öf­fent­licht und seit­dem nicht ak­tua­li­siert; der In­halt ist even­tu­ell ver­al­tet. Wir pla­nen kei­ne Über­ar­bei­tung des Ar­ti­kels, wer­den aber Feh­ler bei Be­kannt­wer­den kor­ri­gie­ren.

Mit­tei­lun­gen über Feh­ler neh­men wir ger­ne per E-​Mail ent­ge­gen.

Ex­ten­sion (Plug­in, Script) für Gimp 2.6:
Batch-​Ska­lie­rung von TIFF-​Bil­dern und Um­wand­lung ins JPEG-​For­mat

Gimp ist ei­ne freie Bild­be­ar­bei­tung, die be­kann­ten Pro­gram­men aus dem Pro­fi­be­reich be­züg­lich des Funk­ti­ons­um­fangs und der Qua­li­tät der im­ple­men­tier­ten Al­go­rith­men über wei­te Stre­cken zu­min­dest gleich­wer­tig ist. Der pro­fes­sio­nel­le Ein­satz wird im Mo­ment noch durch man­ches feh­len­de Fea­tu­re ver­hin­dert, doch die Ent­wick­lung schrei­tet vo­ran.

Gimp steht un­ter der GPL und ist für meh­re­re Be­triebs­sy­ste­me ver­füg­bar, da­run­ter Win­dows, Unix, Li­nux und Mac OS X. Die zum Zeit­punkt der Er­stel­lung die­ses Ar­ti­kels ak­tu­el­le Ver­si­on war 2.6.7. Al­le Aus­sa­gen in die­sem Ar­ti­kel be­zie­hen sich auf die­se Ver­si­on. Das hier vor­ge­stell­te Script wur­de für die­se Ver­si­on ent­wi­ckelt, soll­te aber min­de­stens mit je­der Ver­si­on und je­der Sprach­va­ri­an­te aus der Rei­he 2.6 zu­sam­men­ar­bei­ten.

Ta­ges­kar­te

Die­ser Ar­ti­kel zeigt, wie Se­ri­en von Di­gi­tal­fo­tos be­quem und zeit­spa­rend von der Ka­me­ra zur Ver­öf­fent­li­chung ge­lan­gen kön­nen. We­sent­li­che Ar­beits­schrit­te wer­den da­bei von Gimp er­le­digt, wo­bei die Steue­rung durch ein Script er­folgt. Das Script wird im wei­te­ren Ver­lauf ge­nau­er vor­ge­stellt und kann he­run­ter­ge­la­den wer­den [Down­load].

Die ein­fach­ste Me­tho­de, das Script in Gimp ein­zu­bin­den, be­steht darin, es ins Script-​Ver­zeich­nis von Gimp zu ko­pie­ren (bei ei­ner Stan­dard-​In­stal­la­ti­on von Gimp un­ter deut­schem Win­dows XP SP3: C:\Pro­gram­me\GIMP-​2.0\share\gimp\2.0\scripts). Kön­nen Sie die­ses Ver­zeich­nis nicht aus­fin­dig ma­chen, so su­chen Sie auf Ih­rer Fest­plat­te nach ei­nem Ver­zeich­nis, wel­ches Dut­zen­de von Da­tei­en mit der En­dung .scm ent­hält; mit ho­her Wahr­schein­lich­keit ist dies das Script-​Ver­zeich­nis von Gimp. Nach ei­nem Neu­start von Gimp ist das Script dann re­gi­striert und ein­satz­be­reit; spä­ter da­zu mehr.

Der vor­ge­schla­ge­ne Work­flow ist un­ge­fähr der fol­gen­de: Aus­le­sen der RAW-​Da­tei­en aus der Ka­me­ra, Um­wand­lung in TIFF, Be­ar­bei­tung, Ska­lie­rung, Um­wand­lung in JPEG. Im fol­gen­den wer­den die ein­zel­nen Schrit­te ge­nau­er be­schrie­ben und de­ren Not­wen­dig­keit be­grün­det.

Roh­kost

Mo­der­ne Di­gi­tal­ka­me­ras stel­len die Er­geb­nis­se ih­rer Ar­beit zu­meist un­ter an­de­rem in Form von JPEG-​Da­tei­en zur Ver­fü­gung, die zur wei­te­ren Ver­wen­dung von der Ka­me­ra auf den PC he­run­ter­ge­la­den wer­den kön­nen. Da die mei­sten Bil­der im JPEG-​For­mat ar­chi­viert oder ver­öf­fent­licht wer­den, mag es zu­nächst be­quem er­schei­nen, die­se Bil­der als Aus­gangs­punkt für wei­te­re Ak­tio­nen zu ver­wen­den. Dies soll­te je­doch aus meh­re­ren Grün­den nicht ge­sche­hen:

Die Firm­ware der Ka­me­ra "ent­wi­ckelt" JPEG-​Bil­der aus den Roh­da­ten, die der Bild­sen­sor der Ka­me­ra bei der Auf­nah­me er­zeugt hat. An die­sem Vor­gang ist ei­ne Viel­zahl kom­ple­xer Al­go­rith­men be­tei­ligt, de­ren je­der von ei­ner Un­zahl von Pa­ra­me­tern ge­steu­ert wird. Der Be­nut­zer hat auch bei gu­ten Ka­me­ras nur auf ei­nen win­zi­gen Bruch­teil die­ser Pa­ra­me­ter Ein­fluß.

Da­rü­ber hi­naus bringt die JPEG-​Kom­pri­mie­rung im­mer ei­nen Qua­li­täts­ver­lust mit sich, so daß die­ses Da­ten­for­mat als Aus­gangs­punkt für die Ar­chi­vie­rung oder Nach­be­ar­bei­tung denk­bar un­ge­eig­net ist. Es liegt auf der Hand, daß die Be­ar­bei­tung von An­fang an und so lan­ge wie mög­lich auf voll­stän­di­gen Bild­in­for­ma­tio­nen durch­ge­führt wer­den soll­te. Die Kon­ver­tie­rung in ein ver­lust­be­haf­te­tes For­mat wie JPEG soll­te der letz­te Schritt in der Be­ar­bei­tungs­ket­te sein.

Am­bi­tio­nier­te Fo­to­gra­fen grei­fen des­halb ger­ne auf Bil­der im RAW-​For­mat zu­rück. Die­ser Be­griff be­zeich­net noch kein kon­kre­tes Da­ten­for­mat, son­dern ist ein Ober­be­griff für ein (mehr oder we­ni­ger) un­ver­än­der­tes Bild, wie es der Bild­sen­sor auf­ge­nom­men hat. Die mei­sten ak­tu­el­len Di­gi­tal­ka­me­ras ge­ben die er­zeug­ten Bil­der auch im RAW-​For­mat nach au­ßen wei­ter. Wir hal­ten die­se Fä­hig­keit für sehr wich­tig und emp­feh­len, sie als un­ab­ding­ba­re Vo­raus­set­zung für ei­nen Kauf fest­zu­le­gen.

Die kon­kre­te Da­ten­an­ord­nung in ei­nem RAW-​Bild un­ter­schei­det sich von Her­stel­ler zu Her­stel­ler, teil­wei­se so­gar in­ner­halb der ver­schie­de­nen Bau­rei­hen ei­nes Her­stel­lers. Des­halb kann bis­lang kein Bild­ver­ar­bei­tungs­pro­gramm al­le exi­stie­ren­den RAW-​For­ma­te di­rekt le­sen. Es gibt je­doch im­mer mehr auf die RAW-​Ver­ar­bei­tung spe­zia­li­sier­te Soft­ware, die zu­min­dest ei­ni­ge der RAW-​For­ma­te der be­kann­ten Her­stel­ler ver­ar­bei­ten kann, und auch im­mer mehr der ge­wöhn­li­chen Bild­be­ar­bei­tun­gen kön­nen ei­nen Teil der RAW-​For­ma­te le­sen.

Daß die­se Ent­wick­lung der Qua­li­tät der Er­geb­nis­se för­der­lich ist, darf be­zwei­felt wer­den. Im Ide­al­fall sind RAW-​Da­tei­en di­rek­te, un­ver­än­der­te Ab­bil­dun­gen des Sen­sors, und es ist Ex­per­ten­wis­sen über den tech­ni­schen Auf­bau des je­wei­li­gen Sen­sors und sei­nes Um­fel­des so­wie über die Phy­sik und die elek­tro­ni­schen Ei­gen­schaf­ten der ein­zel­nen Sen­sor­zel­len er­for­der­lich, um die RAW-​Da­tei­en best­mög­lich in Bil­der um­zu­wan­deln.

Aus die­sem Grund soll­te nach un­se­rer An­sicht der er­ste kri­ti­sche Schritt der Bild­ver­ar­bei­tung, näm­lich die Um­wand­lung der RAW-​Da­tei­en in Da­tei­en ei­nes gän­gi­gen For­mats, mit­tels der da­für vor­ge­se­he­nen Pro­gram­me des je­wei­li­gen Her­stel­lers ge­sche­hen. Die­ser kennt die Ei­gen­tüm­lich­kei­ten sei­ner Sen­so­ren und der um­ge­ben­den Elek­tro­nik schließ­lich am be­sten - trotz si­cher nicht schlech­ter Pro­gram­me wie Ado­be Light­room oder App­le Ap­er­ture.

Vie­le Her­stel­ler lie­fern die ent­spre­chen­de Soft­ware ko­sten­los mit ih­ren Ka­me­ras aus, an­de­re nur ge­gen ei­nen Obu­lus. Da die Soft­ware un­ver­zicht­ba­rer Be­stand­teil der Ka­me­ra ist, soll­te bei ei­ner an­ste­hen­den Kauf­ent­schei­dung beim Preis­ver­gleich zwi­schen den Her­stel­lern im­mer der Preis der Soft­ware zum Ka­me­ra­preis hin­zu­ad­diert wer­den.

Vor­ver­dau­ung

Soft­ware zur RAW-​Ver­ar­bei­tung ist oft auf eben die­se Auf­ga­be op­ti­miert, oh­ne wei­te­re Funk­tio­nen ei­ner ty­pi­schen Bild­ver­ar­bei­tung zu bie­ten. Dies ist eher ein Vor­teil denn ein Nach­teil: Die RAW-​Soft­ware ist gut be­dien­bar, weil auf ih­re spe­ziel­len Auf­ga­ben fo­kus­siert. Nach ge­ta­ner Ar­beit wer­den die Bil­der in ein be­kömm­li­ches For­mat ex­por­tiert; ab dann kann das gan­ze Spek­trum der Bild­ver­ar­bei­tungs­pro­gram­me für wei­te­re Schrit­te be­nutzt wer­den.

Das Ex­port­for­mat soll­te ver­lust­frei sein, JPEG schei­det al­so aus. Statt­des­sen bie­ten sich TIFF oder PNG an, die mitt­ler­wei­le je­der ernst­zu­neh­men­den Bild­ver­ar­bei­tung be­kannt sind.

Gimp 2.6 be­sitzt ge­gen­über an­de­ren Pro­gram­men (im Mo­ment noch) ei­nen schwer­wie­gen­den Nach­teil: Es kann der­zeit nur mit 8 Bit Farb­tie­fe (pro Ka­nal) um­ge­hen. Fast al­le Ka­me­ra-​Sen­so­ren lie­fern je­doch ei­ne hö­he­re Farb­tie­fe, die von RAW-​Soft­ware auch kor­rekt be­han­delt wird. Mehr Farb­tie­fe be­deu­tet im we­sent­li­chen ei­nen grö­ße­ren Dy­na­mik­um­fang des Bil­des und mehr Re­ser­ven bei vie­len Be­ar­bei­tungs­schrit­ten wie der Auf­hel­lung dunk­ler Be­rei­che, der Än­de­rung von Kon­tra­sten, der Ent­fer­nung von Farb­sti­chen und an­de­ren.

Des­halb ist es beim hier vor­ge­stell­ten Work­flow mit Gimp als wich­ti­gem Be­stand­teil emp­feh­lens­wert, die­je­ni­gen Kor­rek­tu­ren, die von der RAW-​Soft­ware an­ge­bo­ten wer­den, auch dort durch­zu­füh­ren, be­vor das Er­geb­nis zwar ver­lust­frei, aber farb­re­du­ziert nach 8-​Bit-​TIFF ex­por­tiert wird. Ty­pi­sche Kor­rek­tu­ren, die am be­sten noch in der RAW-​Soft­ware er­le­digt wer­den, sind Schär­fung, Hi­sto­gramm- und Ton­wert­kor­rek­tur, Ver­zeich­nungs­kor­rek­tur und Rausch­un­ter­drü­ckung.

Mas­sen­spei­sung

Im fol­gen­den wird da­von aus­ge­gan­gen, daß ei­ne Se­rie von TIFF-​Bil­dern mit 8 Bit pro Ka­nal ska­liert und nach JPEG kon­ver­tiert wer­den soll. Es gibt vie­le Pro­gram­me, die dies lei­sten kön­nen, da­run­ter auch ko­sten­lo­se. Be­nut­zer von Gimp wer­den al­ler­dings un­gern auf an­de­re Tools aus­wei­chen wol­len und statt­des­sen lie­ber die ge­wohn­ten Rou­ti­nen von Gimp ein­set­zen. Lei­der bie­tet Gimp 2.6 von sich aus kei­ne be­que­me Batch-​Ver­ar­bei­tung für die­se Auf­ga­be an.

Hier stellt sich als nütz­lich he­raus, daß Gimp sehr fle­xi­bel er­wei­tert wer­den kann, un­ter an­de­rem durch Ein­bin­dung von Plug­ins. Ein Plug­in kann in Form ei­nes Scripts rea­li­siert wer­den, wel­ches in ver­schie­de­nen Pro­gram­mier­spra­chen im­ple­men­tiert wer­den kann [Link]. Am häu­fig­sten wird da­bei Sche­me ver­wen­det, ei­ne Spra­che ähn­lich LISP. Die Ent­wick­lung von Scripts für Gimp wird durch die ex­trem schlech­te Do­ku­men­ta­ti­on mas­siv er­schwert. Uns ist kei­ne Do­ku­men­ta­ti­on be­kannt, die das The­ma er­schöp­fend be­han­delt; für dies­be­züg­li­che Hin­wei­se sind wir dank­bar.

Der Teil von Gimp, der für die Aus­füh­rung sol­cher Scripts ver­ant­wort­lich ist, heißt Script-​Fu (auch Script Fu) und wird In­ter­pre­ter ge­nannt. Script-​Fu war ur­sprüng­lich im we­sent­li­chen ei­ne Im­ple­men­tie­rung von SIOD-​Scheme, ist mitt­ler­wei­le aber durch ei­nen mo­der­ne­ren Sche­me-​In­ter­pre­ter na­mens TinySche­me er­setzt wor­den [Link].

Der ur­sprüng­li­che Na­me Script-​Fu blieb da­bei er­hal­ten, und na­he­zu al­le der Scripts, die für die SIOD-​ba­sier­te Ver­si­on des In­ter­pre­ters ge­schrie­ben wur­den, sind auch im neu­en In­ter­pre­ter lauf­fä­hig. Für al­te Ver­sio­nen von Gimp er­stell­te Scripts funk­tio­nie­ren al­so auch mit den ak­tu­el­len Ver­sio­nen oder be­dür­fen nur ge­rin­ger An­pas­sun­gen [Link].

Sche­me ist für Pro­gram­mie­rer, die sich sonst nur mit gän­gi­ge­ren Spra­chen wie C, As­semb­ler oder Ba­sic be­fas­sen, ge­wöh­nungs­be­dürf­tig. Kon­zep­te wie die Prä­fix-​No­ta­ti­on, die Not­wen­dig­keit voll­stän­di­ger Klam­me­rung, Lamb­da-​Aus­drü­cke und ei­ni­ges mehr wol­len ver­in­ner­licht wer­den, was auch hier durch spär­li­che und ab­strak­te Do­ku­men­ta­ti­on er­schwert wird. Als Re­fe­renz­werk darf ak­tu­ell der Sprach­stan­dard R6RS gel­ten; ei­ne wei­te­re (nicht voll­stän­di­ge, aber kom­pak­te) Über­sicht ist auf der Home­pa­ge von SIOD-​Sche­me zu fin­den.

So schlecht die Do­ku­men­ta­ti­on, so vor­bild­lich das Zu­sam­men­spiel zwi­schen Sche­me und Gimp: Je­de über die nor­ma­le Be­nut­zer­ober­flä­che von Gimp an­ge­bo­te­ne Funk­tion steht auch zur Be­nut­zung durch Scrip­te zur Ver­fü­gung. Zur Steue­rung der Scrip­te kön­nen simp­le Be­nut­zer­ober­flä­chen auf denk­bar ein­fa­che Wei­se er­stellt wer­den.

Ape­ri­tif

Im fol­gen­den wird ge­zeigt, wie die oben er­wähn­te Auf­ga­be (Ska­lie­rung und Kon­ver­tie­rung nach JPEG für ei­ne Se­rie von TIFF-​Da­tei­en) in Gimp mit­hil­fe ei­nes in Sche­me ge­schrie­be­nen Scripts durch­ge­führt wer­den kann. Wir stel­len den voll­stän­di­gen Quell­code des Scripts vor, er­läu­tern die­sen und zei­gen, wie das Script in Gimp ein­ge­bun­den wird.

Das Script soll ei­ne Se­rie von TIFF-​Bil­dern ska­lie­ren und ins JPEG-​For­mat wan­deln. Wel­che Bil­der be­han­delt wer­den, soll ein Da­tei­fil­ter an­ge­ben, den der Be­nut­zer in ei­ne Dia­log­box ein­trägt. Als ein­zi­ger wei­te­rer Pa­ra­me­ter soll die Län­ge der län­ge­ren Sei­te der ska­lier­ten Bil­der an­ge­ge­ben wer­den; die kür­ze­re Sei­te soll au­to­ma­tisch aus dem Sei­ten­ver­hält­nis des Ori­gi­nal-​Bil­des er­rech­net wer­den.

Die­se Vor­ge­hens­wei­se ist auch für Bild­se­ri­en ge­eig­net, die so­wohl Bil­der im Hoch­for­mat als auch Bil­der im Quer­for­mat ent­hal­ten. Wä­re hier nur die Ein­ga­be der ge­wünsch­ten Brei­te er­laubt, so wä­ren die Bil­der im Hoch­for­mat nach der Ska­lie­rung grö­ßer als die Bil­der im Quer­for­mat; wä­re nur die Ein­ga­be der ge­wünsch­ten Hö­he er­laubt, ver­hiel­te es sich um­ge­kehrt.

Wir wäh­len hier den Top-​Down-​An­satz zur Er­klä­rung des Scripts. Die­ses be­steht aus drei Tei­len. Der er­ste Teil de­kla­riert die ein­fa­che Be­nut­zer­ober­flä­che zur Ein­ga­be der Pa­ra­me­ter und ver­bin­det die­se mit ei­ner Haupt­pro­ze­dur, dem zwei­ten Teil des Scripts. Die Haupt­pro­ze­dur durch­läuft al­le Da­tei­en, de­ren Na­me auf den Fil­ter paßt, und ruft für je­de Da­tei ei­ne Hilfs­pro­ze­dur auf, die die Da­tei ska­liert und im JPEG-​For­mat spei­chert. Die­se Hilfs­pro­ze­dur ist der drit­te Teil des Scripts.

Zu­nächst wird die Be­nut­zer­ober­flä­che de­kla­riert und mit dem Script ver­bun­den:

1  ;In GIMP / im Me­nü re­gi­strie­ren
2  (script-​fu-​reg­is­ter
3    "script-​fu-​Tiff-​To-​JPEG-​Size"
4    "<Tool­box>/File/Tiffs To JPEGs (Size Pi­xels)..."
5    "Tiffs To JPEG (Dest Si­ze of lon­gest Si­de in Pi­xels)"
6    "Pe­ter Pfan­nen­schmid, Bi­na­rus GmbH & Co. KG"
7    "(c) Bi­na­rus GmbH & Co. KG"
8    "2008-12-16"
9    ""
10   SF-​STRING "Quell-​Da­tei­en" "d:\\test\\*.tif"
11   SF-​VAL­UE "Laengs­te Sei­te (in px)" "400")

Die­se Zei­len ste­hen im Script au­ßer­halb an­de­rer Pro­ze­du­ren und stel­len die Ver­bin­dung zwi­schen der Be­nut­zer­ober­flä­che, dem Script und Gimp her. Die er­ste Zei­le ist ein Kom­men­tar, in Sche­me durch ei­nen Strich­punkt ein­ge­lei­tet. Die Pro­ze­dur script-​fu-​reg­is­ter wird von Gimp in­ner­halb von Sche­me zur Ver­fü­gung ge­stellt und er­war­tet beim Auf­ruf ei­ni­ge Pa­ra­me­ter:

Die letz­ten sechs Pa­ra­me­ter (Zei­len 10 bis 11) de­kla­rie­ren zwei Ein­ga­be­fel­der ei­ner Dia­log­box mit den üb­li­chen Con­trols (Help, Re­set, Can­cel, OK). Die De­kla­ra­tion ei­nes sol­chen Ein­ga­be­fel­des be­steht aus drei Pa­ra­me­tern: Typ (zum Bei­spiel SF-​STRING oder SF-​VAL­UE), Be­zeich­nung (die­se steht in der Dia­log­box als La­bel vor dem Feld) und Vor­ga­be­wert. Hier nimmt das er­ste Feld den Da­tei­fil­ter ent­ge­gen (Vor­ga­be d:\test\*.tif), das zwei­te die Län­ge der län­ge­ren Sei­te (Vor­ga­be 400 Pi­xel).

Wird die Dia­log­box mit OK be­stä­tigt, wird ei­ne Pro­ze­dur auf­ge­ru­fen, der die In­hal­te der Ein­ga­be­fel­der als Pa­ra­me­ter über­ge­ben wer­den. Die Rei­hen­fol­ge der Pa­ra­me­ter der Pro­ze­dur ent­spricht der Rei­hen­fol­ge, in der die Ein­ga­be­fel­der de­kla­riert sind.

Der Na­me der auf­zu­ru­fen­den Pro­ze­dur wird beim Auf­ruf von script-​fu-​reg­is­ter als er­ster Pa­ra­me­ter über­ge­ben (Zei­le 3). Der zwei­te Pa­ra­me­ter (Zei­le 4) be­stimmt, wo das Script in der Me­nü­struk­tur von Gimp zum Auf­ruf be­reit­steht, so­wie den Wort­laut des Me­nü­ein­trags. Der ge­zeig­te Code er­zeugt den Ein­trag "Tiffs To JPEGs (Size Pi­xels)..." im Da­tei-​Me­nü.

Ab und an fin­det sei­tens der Ent­wick­ler ei­ne Um­struk­tu­rie­rung der Me­nüs in Gimp statt, so daß es manch­mal nicht ein­fach ist, die­sen Pa­ra­me­ter so zu be­stim­men, daß der neue Me­nü­ein­trag an der ge­wünsch­ten Stel­le auf­taucht. Die­se Pro­gramm­zei­le (Zei­le 4) ist denn auch die ein­zi­ge Zei­le des ge­sam­ten Scripts, die even­tu­ell für an­de­re Gimp-​Ver­sio­nen der Rei­he 2.x an­ge­paßt wer­den muß oder dort an­ders wirkt als in 2.6.7.

Die näch­sten fünf Pa­ra­me­ter (Zei­len 5 bis 9) sind in die­ser Rei­hen­fol­ge: Text, der im Tool­tip er­scheint, wenn der Maus­zei­ger auf dem zu­ge­hö­ri­gen Me­nü­ein­trag ver­weilt; Au­tor des Scripts; In­for­ma­tio­nen zum Copy­right; Da­tum der letz­ten Än­de­rung des Scripts; Typ des Bilds, auf dem das Script ar­bei­tet. Die letzt­ge­nann­ten vier Tex­te (Zei­len 6 bis 9) sind für den Nut­zer des Scripts nor­ma­ler­wei­se nicht sicht­bar, wer­den aber im Pro­ze­dur­brow­ser an­ge­zeigt – da­zu spä­ter mehr. Der letz­te Pa­ra­me­ter (Zei­le 9) kann in un­se­rem Fall leer blei­ben, weil Gimp den Bild­typ au­to­ma­tisch er­kennt.

Der Auf­ruf von script-​fu-​reg­is­ter muß in Klam­mern ste­hen, da Sta­te­ments in Sche­me grund­sätz­lich voll­stän­dig zu klam­mern sind. Je­doch müs­sen iro­ni­scher­wei­se die Pa­ra­me­ter­li­sten von Pro­ze­du­ren we­der bei De­kla­ra­tion noch bei Ver­wen­dung ge­klam­mert wer­den.

Der auf­merk­sa­me Le­ser wird an die­ser Stel­le be­reits ein Hen­ne-​Ei-​Prob­lem be­merkt ha­ben: Die Pro­ze­dur script-​fu-​reg­is­ter ver­mag wohl das Script in Gimp zu re­gi­strie­ren; selbst­re­dend wä­re da­zu al­ler­dings ihr Auf­ruf nö­tig, und die­ser kann ei­gent­lich nicht er­fol­gen, so lan­ge das Script noch nicht re­gi­striert und da­mit im Me­nü noch nicht sicht­bar ist.

Des­halb scannt Gimp beim Start die Ver­zeich­nis­se, in de­nen Scrip­te lie­gen könn­ten, un­ter­sucht je­des Script auf das Vor­han­den­sein von script-​fu-​re­gis­ter, re­gi­striert neue oder ge­än­der­te Scrip­te in sei­ner in­ter­nen Da­ten­bank und fügt ent­spre­chen­de Ein­trä­ge im Me­nü hin­zu [Link]. Um Gimp in der Ent­wick­lungs­pha­se ei­nes Scripts zum Te­sten nicht stän­dig neu star­ten zu müs­sen, kann die­ser Vor­gang auch ma­nu­ell an­ge­sto­ßen wer­den (Fil­ter - Skript-​Fu - Skrip­te auf­fri­schen).

Hors d'oeuv­re

Wie oben er­läu­tert, be­nö­tigt das Script ei­ne Pro­ze­dur, die nach Ab­schluß der Dia­log­box mit OK auf­ge­ru­fen wird. Im Bei­spiel muß die­se Pro­ze­dur den Na­men script-​fu-​Tiff-​To-​JPEG-​Size tra­gen und zwei Pa­ra­me­ter ver­ar­bei­ten, näm­lich den Da­tei­fil­ter und die ge­wünsch­te Län­ge der län­ge­ren Sei­te. Wir ha­ben die­se Pro­ze­dur auf fol­gen­de Wei­se imp­le­men­tiert:

1   (de­fine (script-​fu-​Tiff-​To-​JPEG-​Size File­Pat­tern Lon­gest­Side)
2
3     (gimp-​mes­sage-​set-​hand­ler MES­SAGE-​BOX)
4
5     (let*
6
7       ;Va­ri­ab­le File­List ge­maess Fil­ter fuel­len
8       ;File­List hat fol­gen­den Auf­bau: (An­zahl (File_1 File_2 ...))
9       ;Min­de­stens der Li­sten­ein­trag An­zahl_Files ist vor­han­den
10      (
11        (File­List (file-​glob File­Pat­tern 0))
12        (Num­ber­Of­Files (car File­List))
13        (Act­File "")
14      )
15
16      ;File­List auf die Li­ste der Da­tei­na­men set­zen,
17      ;er­stes Ele­ment (An­zahl) ent­fer­nen
18
19      (set! File­List (car (cdr File­List)))
20
21      ;Li­ste mit Da­tei­na­men durch­lau­fen und Ska­lie­rung durch­füh­ren
22
23      (while (> Number­Of­Files 0)
24
25        (set! Act­File (car File­List))
26        (Re­size­By­Size Act­File Lon­gest­Side ".jpg")
27
28        (set! Number­Of­Files (- Number­Of­Files 1))
29        (set! File­List (cdr File­List))
30      )
31    )
32  )

Das Schlüs­sel­wort de­fine (Zei­le 1) ist in Sche­me für die De­fi­ni­tion glo­ba­ler Va­ria­blen und Pro­ze­du­ren zu­stän­dig; die De­fi­ni­tion der Pro­ze­dur muß wie­der ge­klam­mert sein. Der Pro­ze­dur sind beim Auf­ruf fol­gen­de Pa­ra­me­ter zu über­ge­ben: File­Pat­tern ist der Da­tei­fil­ter, Lon­gest­Side die ge­wünsch­te Län­ge der län­ge­ren Sei­te.

Der Auf­ruf von gimp-​mes­sage-​set-​hand­ler (Zei­le 3) sorgt da­für, daß Mel­dun­gen, die von Gimp bei der Aus­füh­rung des Scripts er­zeugt wer­den, in ei­ner Dia­log­box auf­tau­chen. Gimp kennt für der­ar­ti­ge Mel­dun­gen, zu de­nen auch Feh­ler­mel­dun­gen ge­hö­ren, noch an­de­re Aus­ga­be­zie­le wie die Script-​Fu-​Kon­so­le oder die Feh­ler­kon­so­le.

Die ei­gent­li­che Ar­beit der Pro­ze­dur be­steht in der De­fi­ni­tion ei­ni­ger lo­ka­ler Va­ria­blen und der Mo­di­fi­zie­rung der­sel­ben, wo­für ei­ne kru­de Syn­tax not­wen­dig ist: Mit der Kon­struk­tion

(let* ((Var_1 Wert_1) ... (Var_N Wert_N)) (Anw_1) ... (Anw_K))

wer­den die Va­ria­blen Var_1 bis Var_N de­fi­niert und da­bei mit den Wer­ten Wert_1 bis Wert_N ini­tia­li­siert, da­nach wer­den die An­wei­sun­gen (Anw_1) bis (Anw_K) aus­ge­führt, je­weils in der no­tier­ten Rei­hen­fol­ge. So de­fi­nier­te Va­ria­blen sind im lo­ka­len Kon­text gül­tig, al­so in­ner­halb der Klam­mern, die das let*-​Kon­strukt um­ge­ben. Die An­wei­sun­gen ha­ben Zu­griff auf die Va­ri­ab­len, so­fern sie im sel­ben Kon­text ste­hen, al­so eben­falls in­ner­halb die­ser Klam­mern [Link].

Im Bei­spiel wird zu­nächst die Va­ri­ab­le File­List de­fi­niert und mit dem Rück­ga­be­wert ei­nes Auf­rufs von file-​glob ini­tia­li­siert (Zei­le 11), ei­ner von Gimp zur Ver­fü­gung ge­stell­ten Pro­ze­dur, die ih­rer­seits als Plug­in rea­li­siert ist [Link]. Die­se er­war­tet als er­sten Pa­ra­me­ter ei­nen Da­tei­fil­ter, als zwei­ten Pa­ra­me­ter die ge­wünsch­te Zei­chen­co­die­rung für die Rück­ga­be.

file-​glob kon­stru­iert aus dem über­ge­be­nen Fil­ter zu­nächst ei­ne Li­ste mit Da­tei­na­men, die auf den Fil­ter pas­sen, und er­mit­telt die An­zahl die­ser Da­tei­na­men. Die Rück­ga­be von file-​glob ist nun ei­ne Li­ste, die zwei Ele­men­te ent­hält, näm­lich die An­zahl der pas­sen­den Da­tei­na­men und die Li­ste mit den Na­men selbst. Der Be­griff Li­ste ist da­bei nicht im Sin­ne des all­täg­li­chen Sprach­ge­brauchs zu ver­ste­hen, son­dern im Sin­ne der Syn­tax von Scheme.

file-​glob wird hier mit dem Da­tei­fil­ter auf­ge­ru­fen, den der Be­nut­zer in die Dia­log­box ein­ge­tra­gen hat­te; die Zei­chen­co­die­rung 0 ent­spricht UTF8. Nach Ab­ar­bei­tung der Zu­wei­sung (Zei­le 11) ent­hält die Va­ri­ab­le File­List ei­ne Li­ste, be­ste­hend aus der An­zahl pas­sen­der Da­tei­en und ei­ner Li­ste, in der die Da­tei­pfa­de UTF8-​co­diert ent­hal­ten sind.

Es folgt der Ein­satz ei­ner der am häu­fig­sten ge­brauch­ten Pro­ze­du­ren von Scheme: car nimmt ge­nau ei­nen Pa­ra­me­ter ent­ge­gen; ist dies ei­ne Li­ste, so gibt car das er­ste Ele­ment die­ser Li­ste zu­rück. Im Bei­spiel wird die Va­ri­ab­le Number­Of­Files de­fi­niert, und es wird ihr das er­ste Ele­ment der in der vor­her­ge­hen­den Zei­le ini­tia­li­sier­ten Va­ria­blen File­List zu­ge­wie­sen, mit­hin die An­zahl der Da­tei­en, de­ren Na­me auf den über­ge­be­nen Da­tei­fil­ter paßt (Zei­le 12).

Mit der De­fi­ni­tion der Va­ria­blen Act­File und de­ren Vor­be­set­zung mit dem lee­ren String (Zei­le 13) ist die De­fi­ni­tion und Ini­tia­li­sie­rung der be­nö­tig­ten Va­ria­blen (Zei­len 11 bis 13) ab­ge­schlos­sen. Act­File wird spä­ter stets ei­nen Da­tei­pfad ent­hal­ten.

Da Sche­me mit la­ten­ten Ty­pen ar­bei­tet, ist bei der De­fi­ni­tion von Va­ria­blen kei­ne Typ­an­ga­be not­wen­dig. Die­ser Ar­ti­kel soll kei­ne Ko­pie der Spe­zi­fi­ka­ti­on von Sche­me sein, des­halb ver­wei­sen wir be­züg­lich die­ser und wei­te­rer Fein­hei­ten auf die ein­schlä­gi­ge Li­te­ra­tur [Link]. Ins­be­son­de­re soll­ten die Un­ter­schie­de zwi­schen let* und let stu­diert wer­den [Link].

Der Code der näch­sten Zei­le dürf­te selbst für Hart­ge­sot­te­ne er­klä­rungs­be­dürf­tig sein, so­fern sie nicht be­reits mit funk­tio­na­ler Pro­gram­mie­rung ver­traut sind. Wir ge­hen aus­führ­lich da­rauf ein, weil ähn­li­che Kon­struk­te in vie­len Bei­spie­len ver­wen­det, aber sel­ten ver­ständ­lich er­klärt wer­den. Der Sinn die­ser Zei­le liegt darin, aus der Va­ria­blen File­List, de­ren bis­he­ri­ger In­halt oben er­läu­tert wur­de, das er­ste Ele­ment zu ent­fer­nen, so daß nur die Li­ste mit den Da­tei­na­men ver­bleibt, die dann be­quem durch­lau­fen wer­den kann (Zei­le 19).

Um den Code zu ver­ste­hen, muß zu­nächst auf den Zu­sam­men­hang zwi­schen Paa­ren und Li­sten in Sche­me ein­ge­gan­gen so­wie die Pro­ze­dur car noch­mals nä­her er­läu­tert wer­den. Ein Paar, auch cons ge­nannt, ist in Sche­me ei­ne Ver­bund­struk­tur (com­pound struc­ture) aus zwei Tei­len, die je­weils von be­lie­bi­gem Typ sein kön­nen, ins­be­son­de­re auch vom Typ Paar [Link].

Paa­re wer­den in Sche­me un­ter an­de­rem mit­tels der Pro­ze­dur cons er­zeugt. So lie­fert bei­spiels­wei­se das State­ment

(cons 1 2)

ein Paar, des­sen bei­de Tei­le aus den In­te­ger-​Wer­ten 1 und 2 be­ste­hen. Die Pro­ze­dur car gibt den er­sten (lin­ken) Teil des Paa­res zu­rück und fin­det ih­ren Ge­gen­part in der Pro­ze­dur cdr, die den zwei­ten (rech­ten) Teil des Paa­res zu­rück­gibt. Im wei­te­ren Ver­lauf des Ar­ti­kels wer­den wir die Be­grif­fe Paar und cons sy­no­nym ver­wen­den, so­fern nicht an­ders ver­merkt.

Ei­ne Li­ste in Sche­me ist per de­fi­ni­tio­nem ei­ne re­kur­siv ver­schach­tel­te An­ein­an­der­rei­hung von Paa­ren, de­ren in­ner­stes ei­ne lee­re Li­ste als rech­ten Teil be­sitzt. En­det die Rei­he nicht in ei­ner lee­ren Li­ste, heißt das be­tref­fen­de Ob­jekt un­ei­gent­li­che Li­ste oder un­gül­ti­ge Li­ste [Link].

Ein Paar wird in Sche­me ge­mein­hin in fol­gen­der Wei­se no­tiert:

(Links . Rechts)

Die üb­li­che Schreib­wei­se für ei­ne Li­ste mit N Ele­men­ten ist:

'(Ele­ment_1 Ele­ment_2 ... Ele­ment_N).

Der Zu­sam­men­hang zwi­schen Paa­ren und Li­sten soll an ei­ni­gen Bei­spie­len deut­lich wer­den:

1  (cons 1 2)               ;(1 . 2) (als Paar, KEI­NE gül­ti­ge Li­ste)
2  (1 . 2)                  ;äqui­va­lent zu Zei­le 1
                            ;di­rekt no­tiert statt per cons er­zeugt
3  (car (cons 1 2))         ;1 (lin­ke Hälf­te des Paars aus Zei­len 1,2,
                            ;  ska­la­rer Wert)
4  (cdr (1 . 2))            ;2 (rech­te Hälf­te des Paars aus Zei­len 1,2,
                            ;  ska­la­rer Wert)

5  (cons 2 '())             ;(2 . '()) (Paar, lin­ke Hälf­te In­te­ger 2,
                            ;  rech­te Hälf­te () lee­re Li­ste)
6  (2 . '())                ;äqui­va­lent zu Zei­le 5
                            ;di­rekt no­tiert statt per cons er­zeugt
7  '(2)                     ;'(2) (die Li­ste '(2))
                            ;äqui­va­lent mit Zei­le 5, In­ter­pre­ter
                            ;kon­stru­iert Li­ste re­kur­siv aus Paa­ren.
                            ;Zei­len 5, 6, 7 lie­fern iden­ti­sche Ob­jek­te.
8  (car '(2))               ;2 (als ska­la­rer Wert, lin­ke Hälf­te des
                            ;  Paars aus Zei­len 5 und 6, er­stes
                            ;  Ele­ment der Li­ste aus Zei­le 7)
9  (cdr '(2))               ;'() (lee­re Li­ste '(), rech­te Hälf­te des
                            ;  Paars aus Zei­len 5 und 6, Li­ste aus
                            ;  Zei­le 7 OH­NE ihr er­stes Ele­ment)

10 (cons 1 (cons 2 '()))    ;(1 . (cons 2 '()))
                            ;(Paar mit lin­ker Hälf­te 1 und rech­ter
                            ;Hälf­te (2 . '()), letz­te­re ist ein Paar,
                            ;das auch als Li­ste '(2) ge­schrie­ben wer­den
                            ;kann, sie­he Zei­len 6 und 7)
                            ;äqui­va­lent zu Zei­len 11 und 12
11 (1 . (2 . '()))          ;äqui­va­lent zu Zei­len 10 und 12
                            ;le­dig­lich di­rekt ge­schrie­ben an­statt
                            ;durch Auf­ru­fe der Pro­ze­dur cons er­zeugt
12 '(1 2)                   ;'(1 2) (die Li­ste '(1 2))
                            ;äqui­va­lent zu Zei­len 10 und 11, In­ter­pre­ter
                            ;bil­det Li­ste re­kur­siv aus Paa­ren.
13 (car '(1 2))             ;1 (als Ska­lar, lin­ker Teil des äu­ße­ren
                            ;  Paa­res aus Zei­len 10 und 11, er­stes
                            ;  Ele­ment der Li­ste aus Zei­le 12)
14 (cdr '(1 2))             ;(2 . '()) (Paar, rech­ter Teil des äu­ße­ren
                            ;  Paa­res aus Zei­len 10 und 11, Li­ste aus
                            ;  Zei­le 12 OH­NE ihr er­stes Ele­ment;
                            ;  ent­spricht Li­ste '(2), sie­he Zei­len 6, 7)
15 (car (cdr '(1 2)))       ;2 (als ska­la­rer Wert, lin­ke Hälf­te von
                            ;  (cdr '(1 2)), al­so von (2 . '()), er­stes
                            ;  Ele­ment der Li­ste '(2))
                            ;for­mal: (car (cdr '(1 2))) =
                            ;        (car (cdr (1 . (2 . ()))) =
                            ;        (car (2 . ())) = 2

Die von Gimp zur Ver­fü­gung ge­stell­ten Funk­tio­nen ge­ben im­mer gül­ti­ge Li­sten zu­rück [Link]. Da­mit gilt für die Va­ri­ab­le File­List vor Aus­füh­rung des Co­des (Zei­le 19) Fol­gen­des (N sei die An­zahl der Da­tei­en, de­ren Pfad­na­me auf den Fil­ter paßt, und L die Li­ste der Da­tei­na­men):

File­List = '(N L)                           ;Li­sten­schreib­wei­se
File­List = (N . (L . '()))                  ;Paar-​Schreib­wei­se
(cdr File­List) = (cdr (N . (L . '()))) =
               = (L . '())                  ;Paar-​Schreib­wei­se
(car (cdr File­List)) = (car (L . '())) = L  ;L ist Li­ste mit Na­men

In der letz­ten Zei­le ist even­tu­ell nicht so­fort ver­ständ­lich, wa­rum hier die Li­ste L zu­rück­ge­ge­ben wird und nicht de­ren er­stes Ele­ment. Der Grund wird klar, wenn L aus­führ­li­cher ge­schrie­ben wird (im Bei­spiel be­sitzt L drei Ele­men­te):

L = '(Na­me_1 Name_2 Name_3) =
  = (Name_1 . (Name_2 . (Name_3 . '())))
File­List = '(N L) =
  = (N . (L . '())) =
  = (N . ((Na­me_1 . (Name_2 . (Name_3 . '()))) . '()))
(cdr File­List) =
  ((Na­me_1 . (Name_2 . (Name_3 . '()))) . '())
(car (cdr File­List)) =
  (Name_1 . (Name_2 . (Name_3 . '())))

Bei der Aus­wer­tung von Li­sten durch car oder cdr wird stets das äu­ßer­ste der re­kur­siv ver­schach­tel­ten Paa­re he­ran­ge­zo­gen, die die Li­ste re­prä­sen­tie­ren. Des­halb gibt der Aus­druck in der letz­ten Zei­le nicht et­wa Name_1 zu­rück, son­dern das lin­ke Ele­ment des in der vor­letz­ten Zei­le ge­zeig­ten Paa­res, das eben aus der Li­ste mit den Na­men und der lee­ren Li­ste be­steht.

Der Aus­druck (car (cdr File­List)) lie­fert dem­nach in der Tat die rei­ne Li­ste der Da­tei­na­men. Nun muß noch auf die dort zu se­hen­de Wert­zu­wei­sung mit­tels set! ein­ge­gan­gen wer­den:

Zu­nächst ist zu kon­sta­tie­ren, daß set! nicht, wie an­dern­orts dar­ge­stellt, zu­stän­dig für die De­fi­ni­tion glo­ba­ler Va­ria­blen ist. set! kann zwar in die­sem Sin­ne ver­wen­det wer­den, ist aber pri­mär die Zu­wei­sungs­pro­ze­dur von Sche­me und wird in­so­fern haupt­säch­lich da­zu ge­braucht, ei­ner im je­wei­li­gen Kon­text be­reits exi­sten­ten Va­ria­blen ei­nen neu­en Wert zu­zu­wei­sen [Link]. Der tech­ni­sche Hin­ter­grund hier­für ist der fol­gen­de:

Bei der De­fi­ni­tion von Va­ria­blen in Scheme, bei­spiels­wei­se mit­hil­fe von let*, wird der be­tref­fen­den Va­ria­blen nicht wirk­lich ein Wert zu­ge­wie­sen, son­dern le­dig­lich ei­ne Spei­cher­stel­le, die die­sen Wert ent­hält [Link]. Nur mit­hil­fe von set! kann die­se Spei­cher­stel­le de­struk­tiv über­schrie­ben wer­den. Dies gilt auch für Va­ri­ab­len, die im lo­ka­len Kon­text de­fi­niert wur­den; set! ist un­ab­hän­gig vom Gül­tig­keits­be­reich der be­tref­fen­den Va­ria­blen zu ver­wen­den.

Nach Durch­lauf die­ses Co­des (Zei­le 19) ent­hält die Va­ri­ab­le File­List al­so nur noch die Li­ste mit den Da­tei­na­men. Vor dem Zu­griff von car auf die­se Li­ste wä­re nun ei­gent­lich ei­ne Ab­fra­ge da­rauf­hin nö­tig, ob die­se min­de­stens ein Ele­ment ent­hält: car und cdr dür­fen nicht ver­wen­det wer­den, um auf die lee­re Li­ste zu­zu­grei­fen [Link].

Der Zu­griff von cdr auf die von file-​glob ge­lie­fer­te Rück­ga­be muß nicht ab­ge­si­chert wer­den, weil file-​glob auf je­den Fall ei­ne nicht­lee­re Li­ste zu­rück­lie­fert, de­ren er­stes Ele­ment die An­zahl der pas­sen­den Da­tei­en ist. Das zwei­te Ele­ment die­ser Li­ste wä­re je­doch die lee­re Li­ste, falls die An­zahl der pas­sen­den Da­tei­en 0 be­trü­ge. In die­sem Fall dürf­te mit car nicht da­rauf zu­ge­grif­fen wer­den.

Der Sche­me-​In­ter­pre­ter in Gimp 2.6.7 hat sich in un­se­ren Tests da­ran al­ler­dings nicht ge­stört: car ver­ur­sach­te beim Zu­griff auf die lee­re Li­ste kei­ne Feh­ler­mel­dung. Die Be­hand­lung die­ser Son­der­si­tua­ti­on be­trach­ten wir des­halb als Übung für den Le­ser.

Im letz­ten Code­block (Zei­le 23 bis 30) wird schließ­lich die Li­ste mit den Da­tei­na­men durch­lau­fen und für je­des ih­rer Ele­men­te ei­ne wei­te­re Pro­ze­dur auf­ge­ru­fen, die die Ska­lie­rung des be­tref­fen­den Bil­des durch­führt. Der Durch­lauf ge­schieht mit­hil­fe ei­ner while-​Schlei­fe: So­lan­ge noch min­de­stens ein Li­sten­ele­ment vor­han­den ist (Zei­le 23), wird das er­ste Ele­ment der Li­ste, al­so der er­ste der noch zu ver­ar­bei­ten­den Da­tei­na­men, mit­tels der Pro­ze­dur car in die Hilfs­va­ri­ab­le Act­File ko­piert (Zei­le 25) und der wei­te­ren Ver­ar­bei­tung zu­ge­führt (Zei­le 26).

Dann wird die An­zahl der noch zu ver­ar­bei­ten­den Li­sten­ele­men­te (Da­tei­na­men) de­kre­men­tiert und die Li­ste selbst um ihr er­stes Ele­ment ver­kürzt (Zei­len 28, 29). Auf die­se Wei­se ist ei­ne je­der­zei­ti­ge Über­ein­stim­mung zwi­schen der Va­ri­ab­len, die die An­zahl der ver­blei­ben­den Li­sten­ele­men­te ent­hält und der Va­ri­ab­len, die die zu be­han­deln­de Li­ste selbst ent­hält, ge­ge­ben. Die ge­son­der­te Va­ri­ab­le für die An­zahl der Li­sten­ele­men­te könn­te auch kom­plett ein­ge­spart wer­den, weil Sche­me Mög­lich­kei­ten bie­tet, zu prü­fen, ob ei­ne Li­ste leer ist [Link]; dann wä­re nur die Ein­tritts­be­din­gung für die while-​Schlei­fe ent­spre­chend zu än­dern.

In den mei­sten funk­tio­na­len Spra­chen spielt das Kon­zept der Re­kur­si­on ei­ne zen­tra­le Rol­le. Auch Sche­me ist der­art kon­se­quent auf die­ses Kon­zept aus­ge­rich­tet, daß bei­spiels­wei­se die in im­pe­ra­ti­ven Pro­gram­mier­spra­chen es­sen­tiel­le while-​Schlei­fe kein Be­stand­teil von Sche­me ist [Link]. Je­der auf ei­ner Schlei­fe ite­rie­ren­de Al­go­rith­mus kann näm­lich durch ei­nen äqui­va­len­ten Al­go­rith­mus mit end­stän­di­ger (re­pe­ti­ti­ver) Re­kur­si­on er­setzt wer­den und um­ge­kehrt [Link]. So­fern ein Stack ein­be­zo­gen wer­den kann, kann so­gar je­de Art von Re­kur­si­on in­ner­halb ei­nes Al­go­rith­mus durch Ite­ra­ti­on er­setzt wer­den [Link]. Wei­te­re Er­läu­te­run­gen zu die­sem The­ma wür­den den Rah­men die­ses Ar­ti­kels lei­der spren­gen, so daß wir In­te­res­sier­te auf ein­schlä­gi­ge Li­te­ra­tur ver­wei­sen müs­sen [Link].

Die end­stän­di­ge Re­kur­si­on muß von al­len Sche­me-​Im­ple­men­tie­run­gen auf ef­fi­zi­en­te Wei­se um­ge­setzt wer­den, oh­ne Stack zu ver­brau­chen [Link]; dies ist Be­stand­teil der Sprach­de­fi­ni­ti­on [Link]. Ganz strin­gent ist die Um­set­zung des Sprach­pa­ra­dig­mas, Re­kur­si­on mög­lichst als al­lei­ni­ge Me­tho­de der Wie­der­ho­lung zu ver­wen­den, in Sche­me aber nicht: Die do-​Schlei­fe ist laut Sprach­de­fi­ni­ti­on zwar nicht Be­stand­teil des Sprach­kerns, ge­hört aber im­mer­hin zu den Stan­dard-​Li­bra­ries [Link].

Zum Ver­ständ­nis des ge­zeig­ten Bei­spiels ist nur wich­tig, daß die while-​Schlei­fe hier wie ge­wohnt be­nutzt wer­den kann, auch wenn sie hin­ter den Ku­lis­sen auf un­er­war­te­te Wei­se um­ge­setzt wird. Letzt­lich han­delt es sich da­bei um ein Mak­ro, wel­ches von TinySche­me der Kom­pa­ti­bi­li­tät zu SIOD-​Sche­me we­gen zur Ver­fü­gung ge­stellt wird [Link].

Haupt­gang

Bis­lang fehlt noch die Funk­ti­on, die die ei­gent­li­che Ar­beit er­le­digt, näm­lich die Ska­lie­rung ei­nes Bil­des und die Spei­che­rung im JPEG-​For­mat:

1   (de­fine (Re­size­By­Size Im­age­File­name Size­Lon­gest­Side Name­Ap­pend)
2
3     (gimp-​mes­sage-​set-​hand­ler MES­SAGE-​BOX)
4
5     (let*
6       (
7         (Act­Im­age (car (gimp-​file-​load RUN-​NON­IN­TER­AC­TIVE
                                         Im­age­File­name Im­age­File­name)))
8         (Org­Width (car (gimp-​im­age-​width Act­Im­age)))
9         (Org­Height (car (gimp-​im­age-​height Act­Im­age)))
10        (As­pect­Ra­tio (/ Org­Width Org­Height))
11        (New­Width 0)
12        (New­Height 0)
13        (Name­Part­List (str­break­up Im­age­File­name "."))
14        (New­File­name "")
15      )
16
17      ;Test, ob Punkt im File­na­men vor­han­den
18      (if (= (length Name­Part­List) 1)
19        (set! NewFileName (string-​ap­pend Im­age­File­name Name­Ap­pend))
20        (set! NewFileName (string-​ap­pend (sub­string Im­age­File­name 0
            (- (string-​length Im­age­File­name)
               (+ (string-​length (car (last Name­Part­List))) 1)))
            Name­Ap­pend))
21      )
22
23      ;Falls mehr als ei­ne Ebe­ne vor­han­den:
24      ;Ebe­ne mit In­dex 0 (ober­ste Ebe­ne, Mi­nia­tur­bild) ent­fer­nen
25
26      (gimp-​im­age-​un­do-​dis­able Act­Im­age)
27
28      (if (> (car (gimp-​im­age-​get-​lay­ers Act­Im­age)) 1)
29        (be­gin
30          (gimp-​im­age-​re­move-​lay­er Act­Im­age
              (aref (car (cdr (gimp-​im­age-​get-​lay­ers Act­Im­age))) 0))
31          (gimp-​im­age-​merge-​vis­i­ble-​lay­ers Act­Im­age
                                             EX­PAND-​AS-​NEC­ES­SARY)
32        )
33      )
34
35      ;Neue Brei­te und Hö­he so be­rech­nen, daß das Sei­ten­ver­hält­nis
36      ;gleich bleibt und die län­ge­re Sei­te die ge­wünsch­te Län­ge
37      ;er­hält
38
39      (if (> Org­Width Org­Height)
40        (be­gin
41          (set! New­Width (in­ex­act->ex­act
                  (round Size­Lon­gest­Side)))
42          (set! New­Height (in­ex­act->ex­act
                  (round (/ Size­Lon­gest­Side As­pect­Ra­tio))))
43        )
44        (be­gin
45          (set! New­Width (in­ex­act->ex­act
                  (round (* Size­Lon­gest­Side As­pect­Ra­tio))))
46          (set! New­Height (in­ex­act->ex­act
                  (round Size­Lon­gest­Side)))
47        )
48      )
49
50      (gimp-​im­age-​scale-​full Act­Im­age New­Width New­Height
                               IN­TER­PO­LA­TION-CU­BIC)
51      (gimp-​file-​save RUN-​NON­IN­TER­AC­TIVE Act­Im­age
                        (car (gimp-​im­age-​get-​ac­tive-​lay­er Act­Im­age))
                        NewFileName New­File­name)
52      (gimp-​im­age-​de­lete Act­Im­age)
53    )
54  )

Die Funk­tion ResizeBySize wird mit ent­spre­chen­den Pa­ra­me­tern von dem im vor­her­ge­hen­den Ka­pi­tel er­läu­ter­ten Code auf­ge­ru­fen. Der Pa­ra­me­ter Im­age­File­name ent­hält den Na­men (in­clu­si­ve Pfad) der zu ver­ar­bei­ten­den Da­tei, Size­Lon­gest­Side ent­hält die ge­wünsch­te Län­ge der län­ge­ren Sei­te (in Pi­xel), und Name­Ap­pend ent­hält die Da­tei­en­dung der zu spei­chern­den Da­tei; Gimp wird da­raus beim Spei­chern das zu ver­wen­den­de Da­tei­for­mat ab­lei­ten.

Auch ResizeBySize be­ginnt ih­re Ar­beit mit der De­fi­ni­tion und Ini­tia­li­sie­rung ei­ni­ger lo­ka­ler Va­ria­blen (Zei­len 6 bis 15). Die Wir­kung von gimp-​mes­sage-​set-​hand­ler (Zei­le 3) so­wie die Syn­tax von let* (Zei­le 5) sind im vor­her­ge­hen­den Ka­pi­tel be­schrie­ben.

An die­sem Co­de-​Ab­schnitt wird wie­der die be­reits er­wähn­te Tat­sa­che deut­lich, daß die Rück­ga­be der von Gimp zur Ver­fü­gung ge­stell­ten Pro­ze­du­ren stets ei­ne Li­ste ist, auch wenn die­se nur aus ei­nem ein­zi­gen Wert be­steht [Link]. Um die Rück­ga­be­wer­te selbst (und nicht die sie ent­hal­ten­de Li­ste) zu ver­ar­bei­ten, kann wie­der car ein­ge­setzt wer­den.

So öff­net gimp-​file-​load (Zei­le 7) die Da­tei, de­ren Na­men und Pfad im zwei­ten und drit­ten Pa­ra­me­ter über­ge­ben wur­den, und gibt ei­ne Li­ste zu­rück, die als ein­zi­ges Ele­ment ein Hand­le auf das ge­öff­ne­te Bild ent­hält; vor der Ver­wen­dung und Zu­wei­sung an die Va­ri­ab­le Act­Im­age muß es erst aus der Li­ste he­raus­ge­löst wer­den. Gimp ver­sucht beim Öff­nen, das Da­tei­for­mat aus dem In­halt der Da­tei au­to­ma­tisch zu er­ken­nen; falls dies nicht ge­lingt, wird die Er­wei­te­rung des Da­tei­na­mens als In­di­ka­tor für das Da­tei­for­mat be­nutzt, so­fern vor­han­den.

Ei­ne nä­he­re Be­trach­tung der Pa­ra­me­ter von gimp-​file-​load führt zu nur be­grenzt nütz­li­chen Er­kennt­nis­sen: Die An­ga­be von RUN-​NON­IN­TER­AC­TIVE im er­sten Pa­ra­me­ter sorgt da­für, daß das La­den der Bild­da­tei oh­ne Rück­fra­gen an den Be­nut­zer ge­schieht, was bei Batch-​Ver­ar­bei­tung die ein­zig an­ge­mes­se­ne Wahl dar­stel­len dürf­te. Selt­sam mu­tet an, daß für die Über­ga­be des Da­tei­pfa­des zwei Pa­ra­me­ter vor­ge­se­hen sind, näm­lich der zwei­te und drit­te. Dies bie­tet al­ler­dings die Mög­lich­keit, ei­ne Da­tei di­rekt von ei­ner URL aus öff­nen zu las­sen. Beim Öff­nen von Da­tei­en aus dem lo­ka­len Da­tei­sy­stem soll­te für bei­de Pa­ra­me­ter der­sel­be Wert über­ge­ben wer­den [Link].

Als näch­stes wird das Sei­ten­ver­hält­nis des so­eben ge­öff­ne­ten Bil­des be­rech­net (Zei­len 8 bis 10). Die so be­setz­te Va­ri­ab­le As­pect­Ra­tio wird spä­ter Ver­wen­dung fin­den. Auch hier müs­sen be­nö­tig­te Wer­te wie­der aus von GIMP zu­rück­ge­ge­be­nen (ein­ele­men­ti­gen) Li­sten ent­nom­men wer­den. Zu be­ach­ten ist wie­der die Prä­fix-​No­ta­ti­on der Di­vi­si­on. Wich­tig ist, daß gimp-​im­age-​height und gimp-​im­age-​width die Di­men­sio­nen des ge­sam­ten Bil­des zu­rück­ge­ben, auch wenn das Bild meh­re­re Ebe­nen ent­hält, die wo­mög­lich an­de­re Ab­mes­sun­gen be­sit­zen sind als das Bild selbst.

New­Width und New­Height (Zei­len 11, 12) sind Va­ria­blen für die Brei­te und Hö­he des ska­lier­ten Bil­des (in Pi­xeln) und wer­den ein­fach mit 0 vor­be­setzt; NewFileName (Zei­le 14) nimmt spä­ter den voll­stän­di­gen Pfad­na­men der neu zu er­stel­len­den Da­tei auf.

In­te­res­san­ter ist die Ini­tia­li­sie­rung von Name­Part­List: str­break­up nimmt ei­nen String und ein Trenn­zei­chen als Pa­ra­me­ter ent­ge­gen und kon­stru­iert da­raus ei­ne Li­ste mit den ein­zel­nen Tei­len des Strings, die durch das Trenn­zei­chen ge­trennt sind. str­break­up ge­hört zu den Pro­ze­du­ren, die von TinySche­me aus Grün­den der Kom­pa­ti­bi­li­tät zu SIOD an­ge­bo­ten wer­den; im­mer­hin ist die­se Pro­ze­dur aber nicht als ver­al­tet mar­kiert [Link].

Im Bei­spiel wird der Pfad­na­me der Quell­da­tei (Im­age­File­name) an vor­han­de­nen Punk­ten auf­ge­trennt; die ein­zel­nen Be­stand­tei­le wer­den in der Li­ste Name­Part­List ge­spei­chert (Zei­le 13). Der Aus­tausch der Da­tei­en­dung könn­te nun be­quem statt­fin­den, in­dem das letz­te Ele­ment der Li­ste mit­tels ein­schlä­gi­ger Pro­ze­du­ren ge­än­dert wird, bei­spiels­wei­se durch Kom­bi­na­tion von re­ver­se und set-​car!.

Um wei­te­re As­pek­te von Sche­me de­mon­strie­ren zu kön­nen, wer­den hier­für aber String-​Funk­tio­nen ein­ge­setzt (Zei­len 18 bis 21). Zu­nächst wird über­prüft, ob die Li­ste mit den Be­stand­tei­len des Da­tei­na­mens ge­nau ein Ele­ment ent­hält (Zei­le 18). Trifft dies zu, so ent­hielt der Da­tei­na­me kei­nen Punkt, und die neue En­dung (drit­ter Pa­ra­me­ter von Re­size­By­Size) wird ein­fach an­ge­hängt (Zei­le 19). string-​ap­pend gibt ei­nen String zu­rück, der durch An­ein­an­der­rei­hung der im er­sten und zwei­ten Pa­ra­me­ter über­ge­be­nen Strings ent­steht.

Trifft die if-​Be­din­gung nicht zu, so ent­hielt der Da­tei­na­me min­de­stens ei­nen Punkt. In die­sem Fall wird da­von aus­ge­gan­gen, daß der letz­te Teil des Da­tei­na­mens als Da­tei­er­wei­te­rung fun­giert und daß die­se ge­gen die neue En­dung aus­ge­tauscht wer­den soll. Der Na­me der neu­en Da­tei soll­te auf je­den Fall ei­ne der üb­li­chen En­dun­gen auf­wei­sen, weil Gimp da­raus bei der Er­zeu­gung der Da­tei das ge­wünsch­te Da­tei­for­mat er­kennt; da­zu spä­ter.

Der Code zum Aus­tausch der En­dung (Zei­le 20) ver­wen­det drei bis­lang nicht er­läu­ter­te Pro­ze­du­ren: last er­war­tet ei­ne Li­ste als ein­zi­gen Pa­ra­me­ter und gibt de­ren letz­tes (in­ner­stes) cons zu­rück, im Fal­le ei­ner gül­ti­gen Li­ste al­so ein Paar, wel­ches aus dem letz­ten Ele­ment der Li­ste und der lee­ren Li­ste be­steht (aus­führ­li­che Er­läu­te­rung im vor­ste­hen­den Ka­pi­tel). Da­mit ist klar, daß zum Zu­griff auf das letz­te Ele­ment der Li­ste wie­der car be­nö­tigt wird.

string-​length ar­bei­tet wie er­hofft und lie­fert die Län­ge des als ein­zi­gen Pa­ra­me­ter über­ge­be­nen Strings. Auch sub­string ver­hält sich wie er­war­tet: die drei Pa­ra­me­ter sind ein String, ein In­dex und ei­ne Län­ge, die Rück­ga­be be­steht im durch In­dex und Län­ge be­stimm­ten Teil des über­ge­be­nen Strings. Der In­dex wird da­bei ab 0 ge­zählt.

Der Aus­tausch der Da­tei­en­dung (Zei­le 20) er­folgt so: Be­rech­nung der Län­ge der bis­he­ri­gen Da­tei­en­dung in­clu­si­ve Trenn­zei­chen (Zei­le 20, drit­te Text­zei­le; Name­Part­List ent­hält die Tei­le des ur­sprüng­li­chen Na­mens oh­ne Trenn­zei­chen, des­halb muß zur aus dem letz­ten Ele­ment von Name­Part­List ge­won­ne­nen Län­ge der bis­he­ri­gen Da­tei­en­dung 1 ad­diert wer­den); Be­rech­nung der Län­ge des ur­sprüng­li­chen Da­tei­na­mens oh­ne En­dung durch Sub­trak­ti­on der Län­ge der bis­he­ri­gen Da­tei­en­dung in­clu­si­ve Trenn­zei­chen von der Ge­samt­län­ge des bis­he­ri­gen Da­tei­na­mens (Zei­le 20, zwei­te Text­zei­le); Ver­wen­dung die­ser Län­ge zur Ge­win­nung des ur­sprüng­li­chen Da­tei­na­mens oh­ne En­dung (Zei­le 20, er­ste Text­zei­le); An­hän­gen der neu­en En­dung an die­sen (Zei­le 20, er­ste / vier­te Text­zei­le).

Die Kon­struk­tion des Na­mens für die neue Da­tei könn­te auf ein­fa­che­re Wei­se statt­fin­den, zum Bei­spiel durch An­ein­an­der­rei­hung al­ler Be­stand­tei­le von Name­Part­List au­ßer dem letz­ten (un­ter Hin­zu­fü­gung der Punk­te zwi­schen den Be­stand­tei­len) so­wie An­hän­gen der neu­en En­dung (Name­Ap­pend), oder, wie oben er­wähnt, durch Ver­wen­dung an­de­rer Pro­ze­du­ren für die Li­sten­ver­ar­bei­tung, die in ty­pi­schen Lib­ra­ries für Sche­me exi­stie­ren [Link]. Die Wahl der hier ge­zeig­ten Lö­sung ist dem Wunsch ge­schul­det, den Um­gang mit den wich­ti­gen Pro­ze­du­ren string-​length, sub­string und last zu de­mon­strie­ren.

Bis­lang wur­de die Syn­tax der Fall­un­ter­schei­dung mit­tels if (Zei­le 18) noch nicht for­mal er­läu­tert: (if Bed Anw_1 Anw_2) führt den An­wei­sungs­block Anw_1 aus, falls die Be­din­gung Bed wahr ist, an­dern­falls den An­wei­sungs­block Anw_2, falls die­ser exi­stiert. Der Rück­ga­be­wert des aus­ge­führ­ten An­wei­sungs­blocks ist je­weils der Rück­ga­be­wert des Ge­sam­tkon­strukts. Trifft die Be­din­gung nicht zu und exi­stiert Anw_2 nicht, so ist der Rück­ga­be­wert des Ge­samt­kon­strukts un­de­fi­niert [Link].

Ei­ni­ge wei­ter­ge­hen­de Be­mer­kun­gen zu den ge­ra­de er­läu­ter­ten Kon­struk­tio­nen und Pro­ze­du­ren sind an­ge­bracht: TinySche­me ba­siert nach ei­ge­ner Aus­sa­ge auf dem (ver­al­te­ten) Stan­dard R5RS und setzt die­sen zwar nicht voll­stän­dig, aber so weit wie mög­lich um [Link]; dies ist auch der Grund da­für, daß un­se­re Li­te­ra­tur­hin­wei­se zu die­sem Ar­ti­kel oft auf Sei­ten füh­ren, die R5RS zum Ge­gen­stand ha­ben. Im Ge­gen­satz zur Ite­ra­ti­on mit­tels while ist nun die Fall­un­ter­schei­dung mit­tels if Be­stand­teil von R5RS und da­mit von Tiny­Scheme. Glei­ches gilt für die Pro­ze­du­ren sub­string und string-​length.

last hin­ge­gen wird trotz häu­fi­ger Ver­wen­dung von TinySche­me nur aus Grün­den der Kom­pa­ti­bi­li­tät zu SIOD-​Sche­me zur Ver­fü­gung ge­stellt und ist als ver­al­tet ge­kenn­zeich­net [Link]. In­so­fern hät­ten wir ei­ne ent­spre­chen­de an­de­re Pro­ze­dur ver­wen­den oder ei­ne ei­ge­ne Pro­ze­dur als Er­satz im­ple­men­tie­ren sol­len. Aus Grün­den der bes­se­ren Ver­ständ­lich­keit ha­ben wir je­doch da­rauf ver­zich­tet, zu­mal last in re­al exi­stie­ren­dem Code noch häu­fig Ein­satz fin­det.

Das fol­gen­de State­ment (Zei­le 26) sorgt für ei­nen sig­ni­fi­kan­ten Ge­winn an Ge­schwin­dig­keit und Spei­cher bei der Aus­füh­rung des Scripts. gimp-​im­age-​un­do-​dis­able nimmt als ein­zi­gen Pa­ra­me­ter ein Hand­le auf ein ge­öff­ne­tes Bild ent­ge­gen und de­ak­ti­viert für die­ses Bild die Me­cha­nis­men zum Un­do, die bei Batch-​Ver­ar­bei­tung meist oh­ne­hin ver­zicht­bar sind.

Der fol­gen­de Code­block (Zei­len 28 bis 33) kann even­tu­ell weg­ge­las­sen wer­den. Er er­füllt fol­gen­den Zweck: Falls das ur­sprüng­li­che Bild mehr als ei­ne Ebe­ne ent­hält, wird die er­ste Ebe­ne (mit dem In­dex 0) aus dem Bild ent­fernt. Für un­se­ren Work­flow ist er nö­tig, weil die zu ver­ar­bei­ten­den Bil­der gro­ßen­teils von Ca­non-​Equip­ment stam­men, als Raw in Ca­non DPP im­por­tiert und nach er­sten Be­ar­bei­tungs­schrit­ten von dort nach TIFF ex­por­tiert wer­den. Bei die­sem Vor­gang er­zeugt DPP in je­dem Bild zwei Ebe­nen, näm­lich ei­ne Ebe­ne mit dem be­gehr­ten Bild in vol­ler Grö­ße (In­dex 1) und ei­ne Ebe­ne mit ei­nem Mi­nia­tur­bild (Ebe­ne 0).

Da wir kei­nen Weg ge­fun­den ha­ben, die­sen Un­fug beim TIFF-​Ex­port aus dem (sonst sehr gu­ten) DPP ab­zu­stel­len, bot es sich an, die Ent­fer­nung der über­flüs­si­gen Ebe­ne eben­falls zu au­to­ma­ti­sie­ren. Um un­er­wünsch­te Feh­ler­mel­dun­gen und Ne­ben­wir­kun­gen bei nor­ma­len Bil­dern aus­zu­schlie­ßen, bei de­nen von vorn­he­rein nur ei­ne Ebe­ne vor­han­den ist, wird zu­nächst über­prüft, ob der De­lin­quent über­haupt mehr als ei­ne Ebe­ne ent­hält (Zei­le 28).

be­gin (Zei­le 29) dient zur Grup­pie­rung von Be­feh­len, ge­nau­er zur syn­tak­ti­schen Zu­sam­men­fas­sung meh­re­rer Aus­drü­cke zu ei­nem ein­zi­gen. Die hier ge­zeig­te An­wen­dung ist ty­pisch: Oh­ne ent­spre­chen­de Grup­pie­rung wür­de bei zu­tref­fen­der if-​Be­din­gung die er­ste Zei­le (Zei­le 30) aus­ge­führt, bei nicht zu­tref­fen­der if-​Be­din­gung die zwei­te Zei­le (Zei­le 31). be­gin ist ein Se­quen­zie­rer, der sei­ne Aus­drü­cke der Rei­he nach ab­ar­bei­tet und den Rück­ga­be­wert des letz­ten Aus­drucks zu­rück­gibt; so­mit wer­den die be­tref­fen­den Aus­drü­cke auch im­pli­zit grup­piert [Link]. Im Bei­spiel wird des­halb wie ge­wünscht bei zu­tref­fen­der if-​Be­din­gung der ge­sam­te mit be­gin um­schlos­se­ne Block (Zei­len 30, 31) aus­ge­führt.

gimp-​im­age-​get-​lay­ers er­war­tet als Pa­ra­me­ter ein Hand­le auf ein ge­öff­ne­tes Bild und lie­fert ei­ne Li­ste zu­rück, die als er­stes Ele­ment die An­zahl der ent­hal­te­nen Ebe­nen ent­hält, als zwei­tes Ele­ment ein Ar­ray mit den ein­zel­nen Ebe­nen. Mit­tels car wird der Li­ste wie­der das er­ste Ele­ment ent­nom­men, wo­mit die Wir­kungs­wei­se der if-​Ab­fra­ge (Zei­le 28) be­reits ge­klärt ist.

Wie­der et­was schwe­rer ver­ständ­lich ist die Zei­le, die den Lay­er ent­fernt (Zei­le 30); even­tu­ell soll­ten noch­mals die Aus­füh­run­gen zum Zu­sam­men­hang zwi­schen Li­sten und Paa­ren (vor­her­ge­hen­des Ka­pi­tel) über­flo­gen wer­den. Da­bei wird klar: (car (cdr (gimp-​ ...))) (Zei­le 30) lie­fert ein Ar­ray, wel­ches die Ebe­nen des be­tref­fen­den Bil­des ent­hält. Die Pro­ze­dur aref nimmt als Pa­ra­me­ter ein Ar­ray und ei­nen In­dex ent­ge­gen und gibt das da­durch be­stimm­te Ele­ment des Ar­rays zu­rück. Der Aus­druck (aref (...) 0) (Zei­le 30) lie­fert dem­nach für das be­tref­fen­de Bild die Ebe­ne mit dem In­dex 0, ge­nau­er aus­ge­drückt ein Hand­le auf die­se Ebe­ne, al­so auf die Ebe­ne, die ent­fernt wer­den soll.

Für die Ent­fer­nung ei­ner Ebe­ne aus ei­nem Bild ist gimp-​im­age-​re­move-​lay­er zu­stän­dig. Die­ser Pro­ze­dur wird ein Hand­le auf das be­tref­fen­de Bild als er­ster Pa­ra­me­ter über­ge­ben, ein Hand­le auf die zu ent­fer­nen­de Ebe­ne (nicht de­ren In­dex!) als zwei­ter. Die ge­zeig­te Co­de­zei­le (Zei­le 30) ent­fernt al­so ins­ge­samt die Ebe­ne mit dem In­dex 0 aus dem Bild. Auch aref ist üb­ri­gens in der Do­ku­men­ta­ti­on zu TinySche­me als ver­al­tet mar­kiert [Link]. Aus den glei­chen Grün­den wie bei last ha­ben wir uns den­noch zur Ver­wen­dung ent­schie­den.

Vie­le Da­tei­for­ma­te für Bil­der kön­nen kei­ne ein­zel­nen Ebe­nen spei­chern. Des­halb muß das Script, so­fern meh­re­re Ebe­nen im be­tref­fen­den Bild ent­hal­ten sind, die­se vor dem Spei­chern zu­sam­men­füh­ren. Dies gilt auch, wenn nach der Ent­fer­nung ei­ner Ebe­ne nur noch ei­ne Ebe­ne vor­han­den ist. Ent­schei­dend ist, daß im ur­sprüng­li­chen Bild meh­re­re Ebe­nen vor­han­den wa­ren.

Die Pro­ze­dur gimp-​im­age-​merge-​vis­i­ble-​lay­ers führt meh­re­re Ebe­nen ei­nes Bil­des auf ei­ne Ebe­ne zu­sam­men (Zei­le 31). Der er­ste Pa­ra­me­ter der Pro­ze­dur ist wie­der ein Hand­le auf das zu be­han­deln­de Bild, der zwei­te Pa­ra­me­ter be­stimmt die Can­vas-​Än­de­rung und Clip­ping-​Op­tio­nen für die re­sul­tie­ren­de Ebe­ne. Der im Bei­spiel ge­wähl­te Wert EX­PAND-​AS-​NEC­ES­SARY be­deu­tet, daß die re­sul­tie­ren­de Ebe­ne so groß sein sein soll, daß al­le im Mo­ment der Ver­schmel­zung sicht­ba­ren Ebe­nen da­rin Platz fin­den.

Der näch­ste Ab­schnitt (Zei­len 39 bis 48) dient der Be­rech­nung der Brei­te und Hö­he des ska­lier­ten Bil­des. Hier­zu wird das ein­gangs der Pro­ze­dur be­rech­ne­te Sei­ten­ver­hält­nis des Ori­gi­nal-​Bil­des ver­wen­det, wel­ches in der Va­ria­blen As­pect­Ra­tio ge­spei­chert ist; die Be­rech­nungs­me­tho­dik ist selbst­er­klä­rend. Die bei­den ver­wen­de­ten ma­the­ma­ti­schen Pro­ze­du­ren da­ge­gen be­dür­fen ei­ner Er­klä­rung:

round run­det sei­nen ein­zi­gen Pa­ra­me­ter ge­gen die nächst­lie­gen­de Ganz­zahl und lie­fert Letz­te­re als Rück­ga­be­wert; im Ge­gen­satz zu kauf­män­ni­schen Ge­pflo­gen­hei­ten, aber in Über­ein­stim­mung mit der Stan­dard-​Run­dungs­me­tho­de in den ein­schlä­gi­gen IEEE-​Spe­zi­fi­ka­tio­nen [Link] wird ge­gen die ge­ra­de Ganz­zahl ge­run­det, falls der zu run­den­de Wert ge­nau in der Mit­te zwi­schen zwei Ganz­zah­len liegt. Die Run­dung von Size­Lon­gest­Side (Zei­len 41, 46) ist nur nö­tig, falls die ge­wünsch­te Län­ge der län­ge­ren Sei­te kei­ne Ganz­zahl ist.

Zur Er­läu­te­rung der Pro­ze­dur in­ex­act-​>ex­act muß et­was wei­ter aus­ge­holt wer­den. Die Men­ge von Zah­len­wer­ten, die in der Hard­ware oder Soft­ware ei­nes Rech­ners re­prä­sen­tiert wer­den kön­nen, ist be­grenzt. Ar­bei­tet die Ma­schi­ne zum Bei­spiel mit 32 Bit brei­ten Re­gi­stern oder Spei­cher­stel­len, so kann je­des Re­gi­ster un­ge­fähr 4 Mil­li­ar­den ver­schie­de­ne Wer­te re­prä­sen­tie­ren, un­ab­hän­gig von der kon­kre­ten Co­die­rung. Des­halb kön­nen nicht al­le Zah­len exakt ab­ge­bil­det wer­den; so ist bei­spiels­wei­se das Er­geb­nis des Be­fehls "Bil­de die Wur­zel aus 2" in je­der Pro­gram­mier­spra­che und auf je­der Hard­ware prin­zi­pi­ell nur ei­ne Nä­he­rung an den exak­ten Wert [Link].

Für vie­le Pro­gram­mier­spra­chen und Hard­ware­ar­chi­tek­tu­ren exi­stie­ren Bib­lio­the­ken, die Be­rech­nun­gen mit be­lie­bi­ger Ge­nau­ig­keit er­mög­li­chen [Link]. Auch die­se lö­sen das Prob­lem nicht grund­sätz­lich, da hier die Ge­nau­ig­keit zu­min­dest durch den ver­füg­ba­ren Haupt­spei­cher be­grenzt wird. Sche­me kann eben­falls kei­ne grund­sätz­li­che Lö­sung des Prob­lems bie­ten, stellt aber mäch­ti­ge Hilfs­mit­tel zur Ver­fü­gung, um mit den Aus­wir­kun­gen best­mög­lich um­zu­ge­hen.

De­ren wich­tig­stes ist das Kon­zept der Exakt­heit: Je­de Va­ri­ab­le, die ei­ne Zahl ent­hält, kann mit­tels Pro­ze­du­ren wie ex­act?, in­ex­act? und an­de­rer da­rauf­hin über­prüft wer­den, ob der ent­hal­te­ne Wert ge­nau ist oder eben un­ge­nau, al­so le­dig­lich ei­ne Nä­he­rung an den ei­gent­lich ge­mein­ten Wert, der nicht re­prä­sen­tiert wer­den kann und nir­gends (mehr) vor­liegt. Da­rü­ber hi­naus be­hält Sche­me zu­min­dest für Zah­len, die in exak­ter Form ein­ge­ge­ben oder be­rech­net wur­den, die Exakt­heit so lan­ge wie mög­lich bei, und es exi­stiert ein aus­ge­feil­tes Sy­stem von Ver­er­bungs­re­geln, die be­stim­men, un­ter wel­chen Um­stän­den ei­ne Va­ri­ab­le ih­re Exakt­heit ver­liert oder ge­winnt. Fer­ner gibt es Pro­ze­du­ren, die ex­pli­zit zwi­schen exak­ter und un­exak­ter Re­prä­sen­ta­ti­on kon­ver­tie­ren [Link].

Wei­te­re Aus­füh­run­gen zu die­ser in­te­res­san­ten Ma­te­rie wür­den den Rah­men die­ses Ar­ti­kels bei wei­tem spren­gen; es gibt je­doch gu­te Li­te­ra­tur hier­zu [Link]. In un­se­rem Bei­spiel ver­wen­den wir die Pro­ze­dur in­ex­act->​ex­act, um die be­tref­fen­den in Va­ria­blen ge­spei­cher­ten Zah­len­wer­te in die exak­te Re­prä­sen­ta­ti­on zu über­füh­ren, be­vor sie an die von Gimp zur Ver­fü­gung ge­stell­ten Pro­ze­du­ren über­ge­ben wer­den. Zu­ge­ge­be­ner­ma­ßen ge­schieht dies hier eher, um ei­nen Grund für die Aus­füh­run­gen der letz­ten Ab­schnit­te zu ge­win­nen, als um dra­ma­ti­sche Fehl­funk­tio­nen zu ver­hin­dern.

Die Le­ser, die bis hier­her ge­folgt sind, könn­ten sich fra­gen, wa­rum die Exakt­heit des In­halts der als Pa­ra­me­ter über­ge­be­nen Va­ria­blen nicht auch beim Auf­ruf der an­de­ren Pro­ze­du­ren ex­pli­zit si­cher­ge­stellt wur­de. Der Ein­wand ist be­rech­tigt, denn auch Ganz­zah­len kön­nen in Sche­me un­exakt re­prä­sen­tiert wer­den (und tat­säch­lich ak­zep­tiert in­ex­act-​>ex­act so­gar aus­schließ­lich Ganz­zah­len als Pa­ra­me­ter). Die von Gimp zur Ver­fü­gung ge­stell­ten Pro­ze­du­ren, die Ganz­zah­len zu­rück­ge­ben (un­ter an­de­rem auch Hand­les auf Bil­der oder Ebe­nen), tun dies je­doch stets in exak­ter Re­prä­sen­ta­ti­on. Auch di­rekt im Quell­code exakt ein­ge­ge­be­ne Zah­len (bei­spiels­wei­se bei der Ad­di­ti­on von 1, Zei­le 20) wer­den vom In­ter­pre­ter nor­ma­ler­wei­se exakt re­prä­sen­tiert [Link].

Der Code­block zur Be­rech­nung der Brei­te und Hö­he des ska­lier­ten Bil­des ist nun der ein­zi­ge Ort in un­se­rem Bei­spiel, an dem oh­ne Zu­tun Un­exakt­heit ent­ste­hen könn­te. Zwar führt die Di­vi­si­on von Ganz­zah­len stets zu ra­tio­na­len Er­geb­nis­sen, und die­se wer­den der rei­nen Leh­re nach in Sche­me so lan­ge wie mög­lich exakt re­prä­sen­tiert; vie­le In­ter­pre­ter füh­ren in zu­künf­ti­gen Re­chen­ope­ra­tio­nen so lan­ge wie mög­lich Zäh­ler und Nen­ner des Di­vi­si­ons­aus­drucks ge­trennt mit, an­statt den Aus­druck zu be­rech­nen und nur das Er­geb­nis mit­zu­füh­ren [Link].

TinySche­me scheint an die­ser Stel­le aber un­sau­ber imp­le­men­tiert: (ex­act? (/ 1 2)) lie­fert in TinySche­me #f, (/ 1 2) wird dort al­so un­exakt re­prä­sen­tiert. In DrSche­me 4.2.4, ein­ge­stellt auf R5RS, lie­fert der­sel­be Aus­druck #t, (/ 1 2) wird dort al­so wie ver­mu­tet exakt re­prä­sen­tiert. Die­se Über­le­gun­gen sind für das Bei­spiel durch­aus re­le­vant, weil der Code Di­vi­sio­nen ver­wen­det (Zei­le 42) und der Wert der Va­ria­blen As­pect­Ra­tio (Zei­len 42, 45) sei­ner­seits durch Di­vi­si­on er­rech­net wur­de (Zei­le 10).

Ab­schlie­ßend muß das Bild ska­liert und un­ter neu­em Na­men im ent­spre­chen­den For­mat ge­spei­chert wer­den (Zei­len 50, 51), und die be­leg­ten Res­sour­cen sind frei­zu­ge­ben (Zei­le 52).

gimp-​im­age-​scale-​full (Zei­le 50) ar­bei­tet wie er­war­tet; die Pa­ra­me­ter sind selbst­er­klä­rend.

In­te­res­san­ter ist die Pro­ze­dur gimp-​file-​save (Zei­le 51): de­ren er­ste bei­de Pa­ra­me­ter er­klä­ren sich zwar wie­der von selbst, und für den vier­ten und fünf­ten Pa­ra­me­ter gilt das bei der Er­läu­te­rung von gimp-​file-​load Ge­sag­te. Als drit­ten Pa­ra­me­ter je­doch er­war­tet gimp-​file-​save ein "Draw­ab­le", wel­ches das ei­gent­lich zu spei­chern­de Ob­jekt dar­stellt; die Do­ku­men­ta­ti­on läßt sich zu die­sem Be­griff lei­der nicht wei­ter aus. In un­se­rem Fall kann die ak­ti­ve Ebe­ne da­für ver­wen­det wer­den. gimp-​im­age-​get-​ac­tive-​lay­er gibt ein Hand­le auf ge­nau die­se zu­rück, wie im­mer als Ele­ment ei­ner Li­ste, aus der es zu­nächst ent­nom­men wer­den muß. Das beim Spei­chern ge­wünsch­te Da­tei­for­mat lei­tet gimp-​file-​save aus der En­dung des über­ge­be­nen Da­tei­na­mens ab.

gimp-​im­age-​de­lete (Zei­le 52) schließ­lich er­war­tet als Pa­ra­me­ter ein Hand­le auf ein ge­öff­ne­tes Bild, um die­ses dann zu schlie­ßen und al­le da­mit ver­bun­de­nen Res­sour­cen (bei­spiels­wei­se Haupt­spei­cher, tem­po­rä­re Da­tei­en auf der Fest­plat­te, Da­tei­sper­ren) frei­zu­ge­ben.

Da­mit sind al­le Be­stand­tei­le des Scripts er­läu­tert. Das Script kann der Be­quem­lich­keit hal­ber kom­plett he­run­ter­ge­la­den wer­den [Down­load].

Des­sert

Un­ser klei­ner Koch­kurs, der sich haupt­säch­lich den Zu­ta­ten Sche­me und Gimp ge­wid­met hat, ist fast be­en­det. Die Zu­be­rei­tung des Des­serts über­las­sen wir un­se­ren Le­sern; das Script bie­tet ge­nü­gend Raum für Ex­pe­ri­men­te und Ver­bes­se­run­gen (die in un­se­rem Fall nicht ge­wünscht wa­ren und des­halb nicht im­ple­men­tiert wur­den). Ei­ni­ge Vor­schlä­ge:

- Fin­den Sie he­raus, was pas­siert, wenn Sie das Script auf JPEG-​Da­tei­en ope­rie­ren las­sen (al­so als Da­tei­fil­ter bei­spiels­wei­se c:\test\*.jpg) an­ge­ben. Kor­ri­gie­ren Sie das Ver­hal­ten, falls es un­er­wünscht ist.

- Er­wei­tern Sie die Be­dien­ober­flä­che und das Script so, daß das Da­tei­for­mat der er­zeug­ten ska­lier­ten Bil­der nicht grund­sätz­lich JPEG ist, son­dern fest­ge­legt wer­den kann.

- Spa­ren Sie Va­ria­blen im Pro­gramm­code, oder er­hö­hen Sie die Les­bar­keit durch Ein­füh­rung zu­sätz­li­cher Va­ri­ab­len.

- Er­set­zen Sie die von uns ver­wen­de­ten, aber in der Do­ku­men­ta­ti­on von TinySche­me als ver­al­tet ge­kenn­zeich­ne­ten Pro­ze­du­ren durch ih­re Nach­fol­ger, und falls sol­che nicht exi­stie­ren, durch selbst im­ple­men­tier­te Pro­ze­du­ren.

- Fü­gen Sie, wie oben dis­ku­tiert, beim Zu­griff von car auf die Li­ste mit den Da­tei­na­men die Über­prü­fung auf die lee­re Li­ste hin­zu.

Zu­letzt noch ein Tip zur Ent­wick­lung ei­ge­ner Ge­rich­te:

Das be­quem­ste Mit­tel, die von Gimp und TinySche­me zur Ver­fü­gung ge­stell­ten Pro­ze­du­ren zu er­for­schen, ist der in Gimp in­te­grier­te Pro­ze­dur­brow­ser (in Gimp: Me­nü Fil­ter -> Skript-​Fu -> Kon­so­le, dann den But­ton "Durch­su­chen..." be­tä­ti­gen). Wer sich da­mit be­faßt, er­kennt schnell die sinn­vol­le Sy­ste­ma­tik hin­ter der Be­nen­nung der Pro­ze­du­ren; aus de­ren Na­men läßt sich meist so­fort die ent­spre­chen­de Funk­tion von Gimp er­ken­nen.

Im Pro­ze­dur­brow­ser tau­chen auch die nach au­ßen ex­por­tier­ten Funk­tio­nen der ei­ge­nen Scrip­te auf. Wie ein­gangs ver­spro­chen, sind denn da­bei auch im rech­ten Fen­ster des Pro­ze­dur­brow­sers die Me­ta­in­for­ma­tio­nen sicht­bar, die beim Auf­ruf von script-​fu-​reg­is­ter an­ge­ge­ben wor­den wa­ren.

Un­se­re Dienst­lei­stun­gen

Sie be­nö­ti­gen Hil­fe bei der Er­wei­te­rung oder Au­to­ma­ti­sie­rung von GIMP oder bei der Ent­wick­lung in Scheme, LISP oder an­de­ren funk­tio­na­len Spra­chen?

Dann neh­men Sie Kon­takt mit uns auf. Wir neh­men uns ger­ne Zeit für ei­ne aus­führ­li­che und ko­sten­lo­se Erst­be­ra­tung.