Processing

Bei Zurück in die Schule gefunden: Processing, eine Java-Programmierumgebung, mit der man Bilder erzeugen kann. Processing ist eine Lernumgebung und Einführung in das (Java-)Programmieren. Man kann damit einerseits voll objektorientiert schreiben, andererseits kann man genauso gut ohne Objektkrams die vorhanden Methoden benutzen, deren Überbau wunderschön transparent ist.

(Fußnote: Transparent heißt überall anders soviel wie: „Durchsichtig, so dass man ins Innere blicken und die Zusammenhänge verstehen kann.“ In der Informatik heißt transparent allerdings: „Durchsichtig, so dass man hindurchschaut und nichts sieht, also quasi unsichtbar und vor leicht zu verwirrenden Augen verborgen.“)

Man kann mit Processing neue Bilder zeichnen oder bestehende Bilder bearbeiten. Das kann man statisch machen oder dynamisch: dann werden daraus bewegte Bilder, entweder automatisch oder durch den Benutzer mit Maus oder Tastatur gesteuert. So kann man auch ganze Simulationen entwerfen, wenn man möchte. Man kann sich auch eine Methode schreiben, um die ausgegebenen Bilder zu speichern, die sieht so aus:

void mousePressed() {
  save("bild.jpg");
}

Viel einfacher geht es wirklich nicht.

Ich wollte aber Filter schreiben, so wie man sie aus Bildbearbeitungsprogrammen kennt. Hier sind ein paar davon. Die Vorgehensweise ist meist die: Man lädt erst einmal das Originalbild in den Speicher und legt eine – noch leere – Zeichenfläche im gleichen Format an. Dann schaut man sich der Reihe nach jeden Pixel des Ursprungsbildes an, merkt sich dessen Farbe (bzw. den Rot-, Grün-, Blauanteil davon), verändert diese Farbe, und schreibt an dieselbe Position auf der Zeichenfläche einen Pixel mit der neuen Farbe.

1. Hier ein einfaches Weichzeichnen, das erste Bild ist das Original:

Man ändert dabei jede Farbe so, dass man sich von einem Pixel und allen seinen umliegenden Nachbarpixeln (im ersten Beispiel nur die direkt umliegenden, im zweiten auch die in etwas weiterem Abstand) die Durchschnittsfarbe ausrechnet und diese dem Pixel zuweist. Das macht man mit allen Pixeln so und heraus kommt eine Weichzeichnung.

2. Hier eine Gammakorrektur. Die dient zu einer differenzierten Aufhellung oder Abdunklung von Bildern. Dabei wird nicht jeder Pixel im gleichen Maß heller oder dunkler gemacht, sondern es werden zum Beispiel die dunklen Pixel mehr aufgehellt als die (ja eh schon hellen) helleren. Im ersten Bild mit gamma=0.5, im zweiten gamma = 1.5:

Hier sind einem die Nachbarn egal. Man wendet einfach auf den Rot-, Grün-, Blauteil jedes Pixels die überraschend einfache Funktion an:

farbeneu = (farbealt/255)gamma * 255

Die Zahl 255 kommt daher, weil es für jeden Farbton 255 Möglichkeiten gibt. Der Wert des ursprünglichen Blauanteils (von 0-255) wird durch 255 geteilt, womit man diesen Anteil auf eine Zahl zwischen 0 und 1 normalisiert hat. Das wird dann mit dem Gammawert potenziert (bzw. je nach Definition auch dessen Kehrwert) und dann mit dem letzten Faktor wieder auf den ursprünglichen Raum (zwischen 0 und 255) gestreckt.

3. Gemischte weitere Filter:

Der erste ist ein einfaches Schärfen: Man schaut sich wieder die Rot-, Grün- Blauwerte eines Pixels und seiner Nachbarn an und errechnet daraus wieder einen Durchschnitt. Allerdings werden vor der Berechnung die Nachbarn und der Pixel selber noch gewichtet: die Werte der 8 unmittelbaren Nachbarn werden zum Beispiel jeweils um 1 reduziert, die des zentralen Pixels um 9 erhöht – die Durchschnittswerte bleiben also gleich (das Bild wird insgesamt nicht heller oder dunkler), aber mit Betonung des Zentrums.

Der zweite zeichnet einfach an jede n-te Stelle des Bildes einen Kreis mit Durchmesser n von der Farbe des Pixels, der sich im Originalbild an dieser Stelle befindet.

Der dritte ist ein Mosaik. Man könnte zwar, ähnlich wie bei den Ellipsen, an jede n-te Stelle des Bildes ein Quadrat zeichnen, aber so wollte ich das nicht machen. Jeder Pixel sollte extra gezeichnet werden. Ich machte mir dazu folgende Skizze:

Der erste Pixel in der Reihe (in der 0-Spalte) sei zum Beispiel rot. Die beiden Pixel rechts daneben sollten dessen Farbe annehmen. Die Pixel in den Spalten 3-7 sollten die Farbe des Pixels in Spalte 5 annehmen, und so weiter. (Für die y-Koordinate analog, das habe ich bald gemerkt.)

Weil ich in Mathe nicht gut bin, zeichnete ich mir eine Tabelle auf: links die Position des Pixels, rechts die Position des Pixels, dessen Farbe angenommen werden sollte. f(linkeSpalte) = rechteSpalte. Der Rest ist einfach nur Rechnen. Mathekönner sehen das durch Überlegen, ich habe halt so lange herumgepfuscht, bis ich die entsprechende Funktion hatte. Die musste ich dann noch etwas verallgemeinern, damit sie nicht nur für die Rechnung mit den Mosaiksteinchen von Seitenlänge 5, sondern für beliebige Seitenlängen gilt. Sieht dann so aus:

xZentrumspixel = (x+size/2)/size*size bzw.
yZentrumspixel = (y+size/2)/size*size,

wobei size die Seitenlänge des Mosaiksteins ist. Und nein, das /size und *size kann man nicht einfach kürzen, da x eine Ganzzahl und die Divison in diesem Fall eine Ganzzahldivision ist – es wird immer abgerundet, der Rest verworfen.

Processing gibt’s für alle gängigen Betriebssystem, läuft unter Windows auch ohne Installationsrechte. Tutorials gibt es dort auch ein paar.

***

Anhang: So sieht ein vollständiger Sketch in Processing aus. Java-Code, ohne Klassendefinitionen, besteht aus 1 Attribut und den Methoden setup() und draw() und der Berechnungsmethode für das Mosaik, mosaik().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
PImage img; //Platz fuer das im Speicher gehaltene Bild
 
//einmaliges Aufrufen am Anfang
void setup() {
  img = loadImage("test.jpg"); //Laden des Bildes
  size(img.width, img.height); //Groesse der Zeichenflaeche
}
 
//das eigentliche Zeichnen
void draw() {
  //Pixel-Feld zur Verfuegung stellen
  loadPixels();
  //Zaehlschleife fuer alle Pixel mit x- und y-Koordinate
  for (int x = 0; x < img.width; x++) {
    for (int y = 0; y < img.height; y++ ) {
      //Berechnen der neuen Farbe - hier koennte man auch andere Methoden aufrufen
      color c = mosaik(x, y, 9);
      //Umrechnen der Koordinaten in Index des eindimensionalen Pixel-Felds
      int loc = x + y*img.width;
      //Aendern des Pixels im Pixel-Feld
      pixels[loc] = c;
    }
  }
  //Zeichnen aller Pixel
  updatePixels();
}
 
 
//Methode zur Farbberechnung beim Mosaik
color mosaik(int x, int y, int groesse) {
  //Koordinaten des Zentrums ermitteln
  int xZentrum = (x+groesse/2)/groesse*groesse;
  int yZentrum = (y+groesse/2)/groesse*groesse;
 
  //Umrechnen der Koordinaten in Index des eindimensionalen Pixel-Felds
  int loc = xZentrum + yZentrum*img.width;
 
  //sicher stellen, dass Bildgrenzen nicht ueberschritten werden
 loc = constrain(loc, 0, img.pixels.length-1);
 
  //Rot-, Gruen-, Blauanteile des Zentrumspixels speichern
  float rtotal = red(img.pixels[loc]);
  float gtotal = green(img.pixels[loc]);
  float btotal = blue(img.pixels[loc]);           
 
  // Rueckgabe der neuen Farbe
  return color(rtotal, gtotal, btotal);
}

Tagged: Tags

8 Thoughts to “Processing

  1. Großartig! Für mich als Bildverarbeitungslaien sind v.a. der Gammafilter und das Schärfen eine Offenbarung…

    Hat mich natürlich auch gleich wieder zum Rumspielen inspiriert. Irgendwo hatte ich mal einen „pointilistischen“ Filter gesehen – hier also mein Versuch:

    // BEGINN CODE
    PImage img; // Originalbild
    int minR = 5; // minimaler Kreisradius
    int maxR = 12; // maximaler Kreisradius
    int s = 3; // Skalierfaktor
    float circleRatio = 0.5; // Verhältnis Kreise/Gesamtpixel

    void setup() {
    img = loadImage(„herr-rau.jpg“);
    size(s*img.width, s*img.height);
    smooth();
    noStroke();
    int circles = (int) (circleRatio * img.width * img.height);

    int x, y;
    for(int i=0; i < circles; i++) {
    x = (int) random(0, img.width);
    y = (int) random(0, img.height);
    color c = img.get(x, y);
    fill(c, 100);
    float r = random(minR, maxR);
    ellipse(s*x, s*y, r, r);
    }
    }
    // ENDE CODE

    (Falls die WordPress-Kommentarfunktion die Formatierung zerstört, gibt es in Processing unter "Edit" eine Autoformat-Funktion.)

  2. Ich lese ja schon eine ganze Zeit lang still mit, aber dieser Eintrag hat es mir wirklich angetan. Ich finde es immer wieder schön zu sehen, wie einfach man Dinge erklären kann.
    Wenn ich an stundenlange Vorlesungen zurückdenke, in denen es um sehr ähnliche Dinge ging und am Ende nur fragende Gesichter übrig blieben, freue ich mich, hier in wenigen Sätzen eine kurze und knackige Zusammenfassung zu lesen.
    Sehr erfreut bin ich auch über die Ehrlichkeit bei den Mathe-Kenntnissen :)

  3. Sehr schöner Filter, embee. Liefert schöne Ergebnisse, ist verständlich, und am Code sehe ich, dass das in Processing alles noch einfacher geht, als ich dachte.

    Peter! Sie! Schön von Ihnen zu hören. (Grüße und danke.)

  4. Wow! Das sieht wriklich gut aus! Ich hätte nicht gedacht, dass es so einfach ist Bilder in Processing zu manipulieren. Ich dachte immer, dass es eine eigene Sprache hat, die nichts mit Java zu tun hat. Irrtum. Na ja, was solls.
    Ich sehe dankbare Aufgabestellungen für den Inforamatikunterricht am Ende der Zehnten auftauchen: „Programmiere einen Filter deiner Wahl!“, „Programmiere einen Filter, der dieses Bild erzeugt!“ „Programmiere einen Schwarzweiß-Filter!“ Vielleicht immer mit einem vorgegebenen Bild, dann kann man später vergleichen und diskutieren.

  5. Hallo, ich hätte mal eine Frage zu dem 2. Filter unter Punkt 3, wie krieg ich es hin, dass die Kreise die Farbe des darunter liegenden Originalbildes haben? Zurzeit hab ich nur weiße Kreise auf dem Foto…
    Das ist mein Code bisher:
    PImage duck;
    void setup(){
    size(500,438);
    duck = loadImage(„duck.jpg“);
    }
    void draw(){
    loadPixels();
    image(duck,0,0);

    for (int px = 0; px < duck.width; px = px + 10) {
    // Schleife für die Pixelreihen des Bildes
    for (int py = 0; py < duck.height; py = py + 10) {
    ellipse (px, py, 10, 10);
    }
    }}

    Über eine Antwort würde ich mich sehr freuen :)

  6. In die Schleife unmittelbar vor dem Zeichnen der Ellipse gehört Folgendes:

    int position = px + py*duck.width;
    fill(duck.pixels[position]);

    Die zweite Zeile legt die Füllfarbe fest, nämlich die eines Pixel an einer bestimmten Stelle im Pixel-Array des Bildes. Und die erste Zeile ermittelt aus den aktuellen px und py, welche Stelle im (eindimensionalen) Array den (zweidimensionalen) Koordinaten px und py entspricht.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.