Bernhard Häussner
Journal: Neueste Artikel erscheinen hier (Seite 1 von 21)

Javascript in Photoshop

22.10.2011, 13:32
Photoshop-Skripting-Beispielbild

Photoshop-Skripting-Beispielbild

Als ich kürzlich eine ganze Reihe Ausweise mit Namen ausfüllen musste, habe ich mir angeschaut, wie man Photoshop CS3 automatisieren kann. Ich war erstaunt herauszufinden, das PS mit Javascript gesteuert werden kann, neben den zusammenklickbaren Makros, VBScript und AppleScript.

Hello-Word-Beispieldateien

Da der komplette Code etwas länger ist, habe ich eine Beispiel-PSD und alle Skripte auf Github online gestellt. Dort unter Downloads findet sich eine zip-Datei mit allem Nötigen, um das hier beschriebene Hello-World-Skript auszuführen.

Dokumentation

Das Photoshop-Scripting ist sehr ausführlich dokumentiert. Zunächst gibt es im Ordner C:\Programme\Adobe\Adobe Utilities\ExtendScript Toolkit 2\SDK zwei Dokumente: Einmal Adobe Intro to Scripting.pdf, welches man lesen sollte, wenn man „noch nie programmiert hat“ oder nicht so genau weiß, was Anwendungen skripten allgemein ist.

Außerdem JavaScript Tools Guide CS3.pdf. Hier findet sich zunächst ein Überblick über das IDE von Adobe (ich habe aber alles direkt in kwrite geschrieben) und dann folgen die Dokumentationen zu den Libraries für Dateisystem, UI, HTTP, Sockets, und vieles weitere. In dem Dokument wird regelmäßig auf Beispieldateien aus dem Brige SDK hingewiesen, dieses findet sich hier.

Die Photoshop-Spezifischen APIs stehen in der Photoshop Javascript Referenz. Photoshop stellt ein DOM zur Verfügung.

Erstellen der Photoshop-Datei

Zuerst kann man eine ganz normale Photoshop-Datei erstellen. Gute Ebenen-Organisation erleichtert später das Skripten. Wenn möglich, packt man alle Ebenen, die bearbeitet werden sollen, zusammen in eine Gruppe (Ordner). Dann findet man sie im Skript leichter wieder. Wenn man, wie ich, mehrere Ausweise o.ä. auf eine DIN-A4-Seite zusammenstellt, kann man jeden Ausweis gleich und in einer eigenen Gruppe anlegen, auch das macht das Skripten einfacher.

Skripte ausführen

Um ein fertiges Skript auszuführen klickt man im Photoshop-Menü Datei > Skripten > Durchsuchen und wählt die .jsx Datei. Das Skript läuft sofort los, eventuell erscheinen Fehlermeldungen als Dialogbox.

Exkurs: Eingabe-Daten verarbeiten

Man könnte zwar rein technisch gesehen mit Javascript die Daten von einem FTP-Server laden, dann irgendwie verarbeiten und letztendlich daraus die Bilder generieren. Ich habe mich jedoch darauf beschränkt, die Daten von einer Tabelle auf einer Webseite zu kopieren (sie sind dann Tabulator-Getrennt in der Zwischenablage) und sie mit awk in ein entsprechendes JSON-Format zu bringen:

awk 'BEGIN{RS="\r\n";FS="\t+"} {print "{planet:\"" $1 "\",comment:\"" $2 "\"},"}' planeten.raw.txt > planeten.json.txt

Da ich auf Windows arbeite (awk läuft auf Linux, die Dateien liegen in einem Samba-Share), musste ich awk sagen, dass die Zeilen auch noch einen Wagenrücklauf beinhalten. Die entstandenen JSON-Daten habe ich dann in das Skript hart kodiert:

var data=[
  {name:"world"},
  {name:"venus"},
  {name:"mars"},
  {name:"pluto",
   comment:"You're not a real planet though. But still, welcome. "}
];

Photoshop-Ebenen bearbeiten und speichern

Hauptsächlich soll das Skript Photoshop-Textebenen mit neuen Texten versehen. Hierzu gibt es in der Dokumentation auch Beispiele. Im Wesentlichen iteriert man durch die Daten, findet in layerSets den Ebenen-Ordner mit den Texten, und bearbeitet dann die Eigenschaft textItem.contents der verschiedenen artLayers (Ebenen). Der Code sieht dann so aus:

for (var dataI = 0; dataI < data.length; dataI++) {
    
  var textGroup = activeDocument.layerSets[0];
  
  textGroup.artLayers[0].textItem.contents=
    "Hello, "+data[dataI].name+"!";
  
  if(data[dataI].comment) {
    textGroup.artLayers[1].textItem.contents=
      data[dataI].comment;
  } else {
    textGroup.artLayers[1].textItem.contents=
      "Welcome to Photoshop scripting.";
  }
  
  // code zum speichern
  
}

Speichern und Exportieren

Ich bearbeite immer das activeDocument und speichere das neue Bild dann als Kopie. Hier überschreibt Photoshop bereits generierte Dateien ohne nachzufragen. Die Speicher-Optionen können als XxxSaveOptions-Objekte mitgegeben werden.

// Erstellen der Optionen fuer JPEG-Export:
var jpgOpts = new JPEGSaveOptions();
jpgOpts.embedColorProfile=true;
jpgOpts.quality=12; // hoechste Q

// Dokument speichern:
activeDocument.saveAs(filename,jpgOpts,true/*=als Kopie*/);

Den neuen Pfad generiere ich aus dem Dateinamen das aktuellen Dokuments und einer fortlaufenden Nummer. Dazu benutze ich eine Reihe nützlicher Funktionen. Damit ich diese nicht in jeder Skript-Datei einfügen muss, benutze ich die include-Funktion von Adobes ExtendedScript:

#include util.jsxinc

Der vollständige Code der util.jsxinc findet sich im Github-Repository.

Benutzeroberfläche

Bisher wird einfach nur eine Sanduhr angezeigt während das Skript läuft, also erstelle ich ein Fenster mit einem Fortschrittsbalken. Man kann auch komplexere UIs zur Eingabe von Parametern usw. erstellen, aber dies lohnt sich vermutlich selten. Ein Fortschrittsbalken mit Cancel-Funktion ist jedoch nicht so schwer zu erstellen. Im Wesentlichen benötigt man dies:

var dlg = new Window('window', _(names));
dlg.progressbar = dlg.add('progressbar');
dlg.progressbar.preferredSize = [400,20];
dlg.show();

// Zur Haelfte fertig:
dlg.progressbar.value = 50;

Hilfslinen-Skript

Ein weiteres Skript kann Hilfslinien erstellen. Am Besten kopiert man es in den Ordner Adobe Photoshop CS3\Vorgaben\Skripten, damit es immer im Skript-Menü auftaucht. Ich finde es immer extrem nervig, für Druckränder und Mittellinien die ganzen Hilfslinien zu erstellen. Genauso nervig ist es, für jedes Druckformat eine Vorlage zu basteln. Lieber generiere ich die Hilfslinien mit diesem kurzen Skript.

ExtendScript hat auch noch weitere Features, wie Reflection und Operator Overloading, welches dann bei den UnitValues Rechnen mit Einheiten ermöglicht. In ExtendScript geht das:

alert( (UnitValue(3,"mm") + UnitValue(7,"pt")*0.9).as("cm") );

Da es für die Hilfslinienerstellung keine Funktionen gibt, habe ich die Methode createGuide(offs,orientation) mit dem ScriptListener erstellt. Man kopiert Adobe Photoshop CS3\Skript Handbuch\Hilfsprogramme\ScriptListener.8li nach Adobe Photoshop CS3\Zusatzmodule\Automatisieren und startet PS neu. Dieses Plugin erstellt auf dem Desktop ein ScriptingListenerJS.log, in dem die u.A. die aufgenommenen Aktionen als Javascript-Code gespeichert werden. Diesen Code muss man dann noch parametrisieren und erhält so für alles, was man in Photoshop anklicken kann, den entsprechenden Code.

Auch die Ressourcen-Deskriptoren habe ich im Skript verwendet. Mit ihnen kann man aus einem String direkt einen ganzen UI-Baum erstellen.

Zusammenfassung

Ressourcen zum Text:

Das Skripten von Photoshop ist schon nach kurzem Einarbeiten für den Javascript-Kenner wohl wesentlich einfacher als das zusammenklicken von Aktionen und Stapelverarbeitungen. Wer sich intensiver mit der Dokumentation befasst kann darüber hinaus sehr flexibel mit Daten aus verschiedenen Quellen automatisiert Dokumente erstellen.

Kommentare: keine

Über Pythons kurze Listennotation

03.07.2011, 20:34
Python-Code mit Listennotation

Python-Code mit Listennotation

Als ich mir Python (3.2) anschaute, musste ich mich zunächst an das Fehlen traditioneller for-Schleifen gewöhnen, doch inzwischen finde ich großen Gefallen an den Iterationsmethoden von Python und an der Listennotation (List Comprehensions).

Zunächst ein einfaches Programm mir dieser Listenverarbeitungssyntax. Folgendes Python-Stück erstellt eine Liste mit den ersten 5 Quadratzahlen von ungeraden Zahlen:

>>> [i**2 for i in range(10) if i%2==1]
[1, 9, 25, 49, 81]

Jetzt ein Beispiel aus der Python-Dokumentation, wie man zwei Vektoren multiplizieren kann:

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

Wobei ich anhand dieses Codefetzens zeigen will, dass sich Dinge auf vielerlei Arten und mitunter einfacher machen lassen. Man kann unter Verwendung von zip() über zwei Listen gleichzeitig iterieren: (mit enumerate() ließe sich auch noch über den Index iterieren)

>>> [e1*e2 for e1,e2 in zip(vec1,vec2)]
[8, 12, -54]

An den nächsten Codezeilen sehen wir erstmals, wie schön in Python ähnliche Datentypen die gleichen Operatoren verwenden: Der Intervall-Itarator, welcher von range() kommt, kann als Sequenztyp wie eine Liste zerstückelt werden:

>>> a=5
>>> [i for i in range(200-a*2,200,2)]
[190, 192, 194, 196, 198]
>>> [i*2 for i in range(100)[-a:]]
[190, 192, 194, 196, 198]

Sehr praktisch sind auch die Aggeregatfunktionen all(), any(), max(), min(), sum(), len() da sich mit ihnen viel Boilerplate-Code vermeiden lässt, der so ähnlich aussieht wie dieser:

>>> testlist=[1,8,9]
>>> test=False
>>> for x in testlist:
...     if(x>4):
...         test=True
...         break
...
>>> test
True

Nach dramatischer Verkürzung:

>>> any(x>1 for x in testlist)
True

Interessanterweise bleibt der Effekt von break erhalten. Das kann man prüfen, indem man mit yield-Syntax ein eigenes, iterierbares Objekt erstellt, welches beim iteriert-werden Statusmeldungen ausgibt. Hier wird die 9 nicht mehr getestet:

>>> def worker(l):
...     for i in l:
...         print("Proccessing %02d" % i)
...         yield i>4
...
>>> w2=worker(testlist)
>>> any(w2)
Proccessing 01
Proccessing 08
True

PS: In Haskell (von welchem Python seine Listennotation hat) kann man den selben Effekt noch schöner an unendlichen Listen sehen: any (> 5) [0..] ergibt True.

Man kann die Aggregatfunktionen auch auf Strings anwenden, da auch diese iterierbar sind:

>>> any(""),any("ha")
(False, True)
>>> max("Panzer")
'z'

Für eine Suchfunktion (theoretische Informatik...) brauchte ich eine Methode herauszufinden, wie lange das längste Präfix eines Strings ist, welches ein Suffix eben jenes Strings und eines weiteren Buchstaben ist. Was ich zunächst hier mit traditionellen Methoden als 10-zeilige Funktion geschrieben, habe ich dann hier als eine Listenverarbeitung schreiben können:

>>> v="abcabd"
>>> i=5
>>> b="c"
>>> max([0]+[k for k in range(0,i+2) if (v[:i]+b)[-k:]==v[:k]])
3

Oder ich habe gemessen, dass die Diagonale in einem Rechteck 29,4° geneigt ist, aber wie war das Seitenverhältnis? Da sorted() eine lexikographische Ordnung herstellt, kann man mehrere Tupel sortieren, wobei der erste Eintrag das Sortierkriterium ist und die weiteren die Ausgangsdaten. Dann baut man noch eine zweite Listennotaton außen herum zum Formatieren. Hiermit findet man also heraus, dass es wohl 16:9 war:

>>> import math,fractions
>>> ", ".join(["{}:{}".format(b,a) for diff,a,b in sorted(
...     [(abs(math.atan(a/b)-((29.4/360)*2*math.pi)),a,b)
...         for a in range(20) for b in range(20)
...       if b>a>0 and fractions.gcd(a,b)==1 ]
... )[:3]])
'16:9, 7:4, 9:5'

Hier sieht man schon ein Problem der Listennotation: Sie wird schnell unübersichtlich, da dutzende Befehle in einem Ausdruck stehen, und man auch eher von hinten nach vorne lesen muss.

Da man mit dem +-Operator Listen konkatenieren kann, kann auch die sum()-Funktion beliebig viele Listen aus einer Liste konkatenieren. Hiermit werden je 3 Zahlen startend bei 0,9 und 18 in eine Liste geschrieben:

>>> sum([range(i,i+3) for i in range(0,27,9)],[])
[0, 1, 2, 9, 10, 11, 18, 19, 20]

Das einzige, was man hier tun muss, ist sum() eine leere Liste als zweites Argument zu übergeben, da sonst die Listen zum Standartanfangswert 0 addiert werden würden (Exception). Etwas komisch ist dann leider das:

>>> sum((str(i) for i in [1,2,345,6,78]),"")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: sum() can't sum strings [use ''.join(seq) instead]

Und wenn man denkt, man weiß alles über Listen, fängt man mit Mengen an:

>>> noprimes={j for i in range(2, 8) for j in range(i*2, 50, i)}
>>> set(range(2, 50))-noprimes # Primzahlen!
{2, 3, 5, 7, 41, 11, 13, 47, 17, 37, 19, 43, 23, 29, 31}
>>> {chr(i) for i in range(97,97+26)} - set( # Pangram powers!
... "The quick brown fox jumps over the lazy dog" )
set() 

Wie man sieht bilden die Iterator- und Sequenztypen ein mächtiges Instrument, wenn man Python programmiert. Wie vieles kann man sie aber auch missbrauchen.

Kommentare: keine

100. Blogeintrag

26.02.2011, 15:12

Das ist nun also der 100. Blogeintrag, und er enthält eine kleine Chronik.

Die Graphik hier ist übrigens ein Screenshot aus einer Processing-Sketch, wo ich mit ein bisschen Java-Code eine Timeline zusammengestrickt habe, die die chronologische Verteilung der Blogeinträge zeigt und die Verlinkung mit den häufigsten Tags. Man sieht eindeutig, wann ich in Bremerhaven bzw. Wilhelmshaven war.

Angefangen hat das Blog als kleines Experiment. Ich hatte schon länger vor meine diversen Seiten in einer großen, irgendwie dynamischen, Seite unterzubringen und zu dokumentieren. Tatsächlich hatte ich wohl schon die ein oder andere Blog-ähnliche Seite gebastelt, mal um Smarty zu testen, mal um AJAX zu testen, außerdem hatte ich natürlich eine Menge Design-Entwürfe. Irgendwann musste ich all dies unter einen Hut bringen, und da ich meine Festplatte formatiert hatte, konnte ich bei 0 anfangen.

Der neue Plan war: Meine Seitenstrukturen mit XML-Dateien basteln, und außerdem objektorientiertes PHP schreiben.

Beide technischen Experimente sind inzwischen etwas veraltet. Meine PHP-Klassen von damals entsprechen heute nicht mehr meinen Ansprüchen, außerdem gibt es ja inzwischen Namespaces. Und das mit dem XML hat sich als problematisch herausgestellt, da bei jedem Seitenaufruf eine Menge XML-Dateien verarbeitet werden müssen. Zum Glück ist die Server-Hardware leistungsstark und die Besucherzahlen sind nicht zu hoch.

Andere technische Errungenschaften waren: Eine eigene Markup-Sprache, „progressive enhancement“ mit jQuery und PHP-Klassen-Autoloading.

Der erste Blogeintrag Hello World! wurde am 15.10.2008 geschrieben. Damals gab es allerdings noch kein CMS, das bedeutet des Eintrag wurde direkt in die Datenbank geschrieben. Online ging die Seite dann gut zwei Monate später, am 19. Dezember 2008. Das schließe ich jetzt einfach mal aus den ersten Log-Einträgen:

84.56.72.255 - - [19/Dec/2008:14:18:14 +0100] "GET / HTTP/1.1" 200 566 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4"
[...]
92.75.61.95 - - [19/Dec/2008:19:31:46 +0100] "GET /index.php HTTP/1.1" 200 3001 "http://78.47.239.227/blogentry.php?link=28_Pretty_Good_Privacy_%28PGP%29" "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.0.3) Gecko/2008092700 SUSE/3.0.3-4.4 Firefox/3.0.3" 

Ich stelle gerade erstaunt fest, dass die Links von damals, noch mit IP-Adresse und ohne die „schönen“ URLs, noch immer wie gewünscht weitergeleitet werden.

Auch an der live-Seite wurden immer weitere Verbesserungen vorgenommen, wie „schöne“ URLs mit htaccess, Kurz-URLs, erklärte Abkürzungen, und viele jQuery-Spielereien. Meine Aufzeichnungen über diese Seite finden sich allesamt unter dem Tag Projekt: Mein Blog.

Kommentare: keine

5gon

21.01.2011, 18:45
5gon-no1-seq01-0001-blackCrystal-w1

5gon-no1-seq01-0001-blackCrystal-w1

Im Rahmen meiner Studien zu Pentagrammen, Fünfecken, und ähnlichem sind ein paar interessante Geometrische Figruen entstanden.


Pentagramm [5gon-no3-seq01-all]

Dodecaicosahedron (seq4)
Kommentare: keine

Pentagramme, 5-Ecke, √5 und Φ

21.01.2011, 18:44

In letzter Zeit habe ich mich einal genauer mit den Winkeln und Strecken-Verhältnissen in Fünfecken und Pentagrammen beschäftigt, und bin dabei auf allerlei Vielfache der Wurzel 5 und der Goldenen Zahl Φ gestoßen. Dem zweidimensionalen Pentagramm, wie auch dem Dodekaeder, und in gewisser Weise auch dem Ikosaeder liegt das Fünfeck als Baustein zugrunde.

Pentagramm und Fünfeck

Die Winkel im Pentagramm sind fast alle 36°, oder Vielfache, wie 18°, 72°, 108°, 144°. Die Längenverhältnisse sind schon interessanter: Beim Fünfeck stehen Diagonale (in der Skizze blau) und Seitenlänge (magenta) im Verhältnis des Goldenen Scnittes, genauso im Pentagramm der große (hellblau, [AS]) und der kleine (hellmagenta, [SS'']) Abschnitt (gleichzeitig Seite des kleinen Fünfecks) der Seite. Dadurch, dass das Pentagramm aus den Diagonalen des Fünfecks gebildet wird, kann man von der Seitenlänge des kleinen Fünfecks auf die des Großen schließen:

[AD] = d(S,S'') + 2*Φ*d(S,S'')
[AD] = Φ*[AB]
1/Φ  = Φ - 1

[AB] = [ d(S,S'') + 2*Φ*d(S,S'') ]/Φ = ( 1/Φ + 2 )*d(S,S'') 
     = (Φ + 1)*d(S,S'')

Interessant für meine Zwecke war dies, um die Polarkoordinaten aller Punkte herauszubekommen. Dabei dient M als Ursprung, und wenn die drei Punkte A, M' und S bestimmt wurden, lassen sich die übrigen Punkte durch Rotation finden. Durch Rotation (und Spiegelung) der beiden in Cyan markierten Strecken lässt sich das gesamte Pentagramm bilden. Das habe ich bei meinen Konstruktionen verwendet. Der Azimut φ ist bei allen 3 Punkten trivial. Allein der Radius r bedarf einiger Überlegung.

Zum Glück hilft Wikipedia hier mit 2 recht praktischen Formeln für In- und Umkreisradius, sodass man sich einiges Gerechne mit Dreiecks-Ähnlichkeiten, Pythagoras etc. sparen kann:

r_i = a/10 * √(50+10√5)
r_u = a/10 * √(25+10√5)

Da r(M') der Inkreisradius des kleinen Fünfecks ist, r(S) der Umkreisradius des kleinen Fünfecks und r(A) der des großen ist, kann man die Entfernungen zunächst leicht als Vielfache der Kantenlänge des kleinen Fünfecks (d(S,S'') hier a) schreiben:

r(M') = a/10 * √(25+10√5)
r(S)  = a/10 * √(50+10√5)
r(A)) = (Φ+1) * a/10 * √(50+10√5)

Die Länge a wird nicht benötigt, und ich habe r(M') auf 1 festgelegt. Dann löst man auf:

r(M') = 1
a     = 10 / √(25+10√5)
r(S)  = √(50+10√5) / √(25+10√5)
      = -1 + √5
r(A)) = (Φ+1) * √(50+10√5) / √(25+10√5)
      =  1 + √5
      =  3.2360679774997896964091736687312762354406183596115257...

Hier ergeben sich überraschend sehr einfache Zahlenverhältnisse. Zumal da ich nicht wirklich sehe, wie man die Wurzeln so fein auflösen kann, aber der Computer schafft es irgendwie.

Dodekaeder und Ikosaeder

Offensichtlich kann man ein Dodekaeder aus 12 regulären Fünfecken bauen. Doch auch die Kanten des Ikosaeders bestehen aus den selben (also gleich ausgerichteten) 12 Fünfecken. Dies wird in der Videoanimation klar:


Dodecaicosahedron (seq3)

Übrigens sieht man in der Animation für 1 Frame einen Ikosidodekaeder, der ebenfalls gebaut werden könnte.

Einige der Diagonalen und Kanten der Fünfecke kann man zu 3 („goldenen“) Rechtecken zusammenfügen, an deren Ecken alle Kanten zusammenlaufen. Diesen Sachverhalt kann man nutzen, um das Ikosaeder zu zeichnen, wie hier im Video beschrieben:


Ikosaeder zeichnen in 3 Schritten

Oder man scheidet die 3 Rechtecke aus Papier oder Pappe und „vernäht“ sie dann mit den übrigen Kanten. Dann entsteht eine Figur, wie oben im Foto. Dabei bin ich mir noch nicht sicher, ob es eigentlich trivial ist, die Kanten so mit einem durchgehenden Faden zu nähen, ohne dass man keine Kante doppelt, eine Ecke dreimal beschickt und dann wieder dort heraus kommt, wo man angefangen hat. Eine Art dreidimensionales Nikolaushaus. Ich habe es auf Anhieb geschafft, aber instinktiv ein bisschen Taktik verwendet und stets etwas voraugedacht... (Das ist sicher interessant zu modellieren, wenn man mal Zeit hat)

Kommentare: keine
[ Seite 1 2 3 4 5 6 7 8 …21 ] Nächste Seite »
 
Χρόνογραφ
© 2008-2012 by Bernhard Häussner - Impressum - Login
Kurz-Link zu dieser Seite: http://1-co.de/bj