Abstrakte 3D Rasterung in Processing
Bilder abstrakt dreidimensional und interaktiv darstellen
Ziel des Tutorials
Mithilfe dieses Tutorials erklären wir dir Schritt für Schritt, wie du Bilder im dreidimensionalen Raum rastern und abstrakt darstellen kannst. Durch das Drücken verschiedener Tasten und das Ziehen mittels der Maus kann das Bild gestalterisch verändert und individualisiert werden. Zusätzlich zeigen wir dir, wie du ein Foto von dir selbst mithilfe deiner Webkamera in das Programm einbindest.
Was ist Processing?
Processing ist eine JavaScript-Bibliothek, spezialisiert auf Grafik, Simulation und Animation. Um es nutzen zu können, musst du das Programm erst auf deinen Rechner runterladen. Download
Processing eignet sich für dieses Tutorial gut, da es umfangreiche Einstellungsmöglichkeiten für den dreidimensionalen Raum bietet.
Nachdem du das Programm öffnest, wird dir ein leerer Sketch angezeigt. Keine Sorge, in den nächsten Schritten erklären wir dir genau, wie du für dieses Tutorial das Programm füllen sollst.
Falls du dich davor lieber selbst mit dem Programm auseinandersetzen möchtest, wirf ein Blick auf die examples und reference.
Bild laden
Die setup()
-Funktion wird nur einmal bei Start des Programms ausgeführt. Sie wird verwendet um Grundbedingungen wie die Bildschirmgröße, oder das Laden von Bildern oder Schriften festzulegen. Mittels dem Rendermodus P3d wird dieses sketch in ein 3-dimensionales umgewandelt, um sowohl die x-, y-, als auch z-Achse nutzbar zu machen.
Anfangs wird ein PImage
Objekt, welches ein Bild darstellt erzeugt. PImage
ist ein Datentyp innerhalb der Processing Software welcher für das Speichern von Bilddateien wie png, gif oder jpg zuständig ist.
Bevor ein Bild verwendet werden kann muss es mittels der loadImage-Funktion geladen werden. Das zu verwendete Bild muss hierbei im Projektordner gespeichert sein. Die resize
-Methode skaliert da bild auf die vorgegebene Bildgröße, bzw. ändert sie die Höhe und Breite des Bildes dementsprechend.
PImage img;
void setup() {
size(800, 800, P3D); //Bildschirmgröße, Rendermodus
img = loadImage("1.jpg");
img.resize(800,0); // 0 an 2. Stelle um das Seitenverhältnis beizubehalten
}
Rasterung
Die nächsten Schritte erfolgen inenrhalb der void draw()
-Funktion, welche kontinuierlich ausgeführt wird.
Für die Rasterung erstellen wir zunächst die Variable "tiles
", welches die Anzahl der Rasterkästchen beschreibt. Mit "tileSize
" wird die Größe der Rasterkästchen berechnet, indem die Anzahl durch die Breite geteilt wird. Width
bezieht sich hier immer auf die Bildschirmgröße.
background()
legt die Hintergrundfarbe fest.
void draw(){
float tiles = 300; // Anzahl Rasterkästchen bzw. Detailgrad
float tileSize = width/tiles; //Größe des einzelnen Rasterkästchens
background(#FFFFFF);//Weißer Hintergrund
Geschachtelte For-Schleife
Die erste for-Schleife durchläuft die Kästchen auf der x-Achse, während die zweite for-Schleife die Kästchen auf der y-Achse repräsentiert. Innerhalb der Schleife wird nun das Raster durchlaufen.
Um die Helligkeit der jeweiligen Pixel an der jeweiligen Rasterstelle zu erfragen benötigen wir zunächst einmal die Farbe, welches du mit der color-Variable, hier color c
erfragen kannst. Mit img.get
ist es möglich die Farbe eines Bildpixels zu erhalten. Um den Pixel an genau der Stelle der Rasterkästchen zu erfragen wird x und mit der tileSize
multipliziert. Da tileSize jedoch eine float-Variable ist und nicht wie x und y ein Integer, wird dieses vorher konvertiert.
Im nächsten Schritt möchten wir herausfinden wie hell die Farbe der Variable color c ist. brightness()
gibt den Helligkeits wert der zugehörigen Farbe wieder.
Die push()
- und pop()
-Funktionen ermöglichen eine genauere Transformation durch das Erstellen eines Koordinatensystems.
Die translate(x,y,z)
-Funktion gibt einen Betrag zum Verschieben von Objekten innerhalb des Anzeigefensters an. Um den z-Parameter ist das Verwenden von P3D
, wie vorher festgelegt, nötig. Mit der translate-Funktion wird das Bild hier innerhalb des Koordinatensystems zentriert, indem die x-Position*tileSize mit der Breite/2, da sich hier die Mitte befindet, bzw. Höhe des Fensters subtrahiert wird.
Mit sphere(radius)
werden Sphären erstellt. Die Größe der Sphäre ändert sich basierend auf den Helligkeitswerten der Pixel. Da die Sphären etwas zu groß erscheinen multiplizieren wir den Wert einfach mit 0,8 um die Größe etwas zu verringern.
Bei gedrückter Maustaste soll sich die Verteilung der Sphären auf der z-Achse ändern, hierfür legen wir die Variable z fest. Die map(value, start1, stop1, start2, stop2 )
-Funktion ordnet eine Zahl von einem Bereich zu einem anderen neu zu. value beschreibt den erwarteten Wert der neu berechnet werden soll, während start und stop jeweils die untere, bzw. die obere Grenze des Wertebereichs festlegen. Die ersten Werte legen den Startwertebereich fest und die zweiten Werte den Zielbereich. Die z-Positionierung ändert sich also basierend auf den Helligkeitswerten der jewiligen Pixel im Bereich von -100 und 100. Je dunkler der Wert, desto geringer die Position auf der z-Achse.
Ist die Maustaste nicht gedrückt, ist die z-Positionierung fest.
for (int x = 0; x < tiles; x++){
for (int y = 0; y < tiles; y++) {
color c = img.get(int(x*tileSize),int(y*tileSize)); //Farbe der Pixel
float b = map(brightness(c),0,255,1,0); //Helligkeitswert
if (mousePressed){ //wenn Maus geklickt, verteilen sich die "Partikel" basierend auf der Maus-Position
float z = map(b,0,1,mouseX/2,mouseY/2);
push();
translate(x*tileSize - width/2, y*tileSize - height/2, z); //Positionieren des Bildes im Zentrum
sphere(tileSize*b*0.8);
pop();
}
else { //wenn Maus NICHT gedrückt wird, werden die "Partikel" gleichmäßig verteilt
float z = map(b,0,1,-100,100);
push();
translate(x*tileSize - width/2, y*tileSize - height/2, z);
sphere(tileSize*b*0.8);
pop();
}
}
}
Die Sphären sollen keine Kontur haben und den Detail der Sphären kannst du auch anpassen. sphereDetail()
steuert das Detail, das zum Rendern einer Kugel verwendet wird, indem die Anzahl der Scheitelpunkte des Kugelnetzes angepasst wird.
Mit rotateY(winkel)
wird eine Form an der y-Achse gedreht.
noStroke();
sphereDetail(0);
rotateY(radians(frameCount));
Steuerung
Die Steuerung lässt sich beliebig erweitern oder ändern und nicht nur die Verteilung der Partikel anhand der Mausposition:
Zum Beispiel lassen sich die Farben der Sphären ändern. Bei Drücken der Leertaste färben sich die Sphären schwarz und sie werden sichtbar.
if (keyPressed && key ==' '){
fill(#000000);
}
So werden bei Drücken der r-Taste auf der Tastatur die Sphären rot, oder demenstprechend grün oder blau.
if (keyPressed && key =='r'){ //rot
fill(#FF0000);
}
if (keyPressed && key =='g'){ //grün
fill(#008800);
}
if (keyPressed && key =='b'){ //blau
fill(#021BF9);
}
Auch das Bild kannst du ändern:
if (keyPressed && key =='1'){
img = loadImage("1.jpg");
img.resize(800,0);
}
if (keyPressed && key =='2'){
img = loadImage("2.jpg");
img.resize(800,0);
}
if (keyPressed && key =='3'){
img = loadImage("3.jpg");
img.resize(800,0);
}
if (keyPressed && key =='4'){
img = loadImage("4.jpg");
img.resize(800,0);
}
if (keyPressed && key =='5'){
img = loadImage("5.jpg");
img.resize(800,0);
}
Ein Foto von dir in das Programm einbinden
Wir setzten uns das Ziel für dieses Tutorial eine Art Live-Kamera-Funktion einzubinden, sodass der Nutzer sich Live in dem Programm projizieren lassen kann. Wir stießen hier auf Probleme und schafften es leider nicht, die perfekte Lösung zu finden. Jedoch umgingen wir das Problem, indem wir ein weiteres Programm schrieben, welches dir ermöglicht ein Foto von dir selbst zu schießen und schließlich im 3D-Programm angezeigt werden kann.
Bevor du damit anfängst den Code zu schreiben, musst du erst die nötige Library runterladen, welche in Processing enthalten ist. Dafür gehst du zuerst auf Sketch, Library importieren und Manage Libraries. Hier gibst du ins Suchfeld „Video Processing 4“ ein und lädst dir die Library runter.
Auch musst du ein Objekt vom Typen Capture deklarieren, um auf die Webcam auf deinem Computer zuzugreifen.
import processing.video.*;
Capture webcam;
Die Funktion setup()
muss das Capture
-Objekt mit den benötigten Einstellungen initialisiert werden, um mit der Erfassung der Kamera beginnen zu können. In der Funktion draw()
wird dann das Bild der Webkamera auf den Bildschirm gezeichnet.
void setup()
{
size(640, 480);
smooth();
webcam = new Capture(this, width, height, 30);
webcam.start();
}
void draw()
{
background(#FFFFFF);
image(webcam, 0, 0);
}
Die Funktion captureEvent()
wird jedes Mal aufgerufen, wenn ein neuer Frame von der Webcam verfügbar ist. Innerhalb dieser Funktion musst du die Methode webcam.read()
aufrufen, um das Webcam-Bild stetig zu aktualisieren.
void captureEvent(Capture webcam)
{
webcam.read();
}
Schlussendlich sorgt die Funktion void keyPressed()
dafür, dass bei Drücken der c-Taste ein Bild geschossen wird, welches dann auf den Ordner gespeichert wird und vom Hauptprogramm aufgerufen werden kann.
Übrigens: Achte beim Foto schießen auf einen weißen Hintergrund, der 3D-Effekt funktioniert so am besten.
void keyPressed () { //Screenshot
if (key =='c') {
save("5.jpg");
}
}
Hier musst du auch auf die richtige Ordnerstruktur achten, damit das Hauptprogramm auf das Bild zugreifen kann. (Processing-Datei "tutorial" befindet sich im Überordner gemeinsam mit den Bildern, Screenshot-Unterordner und Unterordner mit Processing-Datei "camera")
Nun kannst du wieder in das Hauptprogramm gehen (ggf. Neustart nötig) und die Taste 5 drücken, um das Bild von dir anzeigen zu lassen.
Screenshots speichern
Um unser abstrahiertes Bild zu speichern erstellen wir die Funktion keyPressed()
, in welcher wir bei Drücken der s-Taste auf der Tastatur, das Bild speichern können. Save("Speicherort") erstellt einen Screenshot des Fenster und speichert ihn im vorgegebenen Speicherort.
void keyPressed () { //save screenshot
if (key =='s') {
save("screenshots/tutorial.jpg");
}
}
Quellcode
Download (Kompletter Ordner)
Tutorial
PImage img;
void setup() {
size(800, 800, P3D); //Erstellt eine Zeichenfläche im 3D Raum
img = loadImage("1.jpg");
img.resize(800,0);
}
void draw() {
background(#FFFFFF);
if (keyPressed && key ==' '){ //schwarz bzw. Programm starten
fill(#000000);
}
if (keyPressed && key =='r'){ //rot
fill(#FF0000);
}
if (keyPressed && key =='g'){ //grün
fill(#008800);
}
if (keyPressed && key =='b'){ //blau
fill(#021BF9);
}
if (keyPressed && key =='1'){ //lädt Bild 1
img = loadImage("1.jpg");
img.resize(800,0);
}
if (keyPressed && key =='2'){ //lädt Bild 2
img = loadImage("2.jpg");
img.resize(800,0);
}
if (keyPressed && key =='3'){ //lädt Bild 3
img = loadImage("3.jpg");
img.resize(800,0);
}
if (keyPressed && key =='4'){ //lädt Bild 4
img = loadImage("4.jpg");
img.resize(800,0);
}
if (keyPressed && key =='5'){ //lädt Bild 5 bzw. eigenes Bild
img = loadImage("camera/5.jpg");
img.resize(800,0);
}
noStroke();
sphereDetail(0);
float tiles = 300; //Menge an Partikeln
float tileSize = width/tiles;
push();
translate(width/2,height/2);
rotateY(radians(frameCount)); //Rotation des Objektes (Kann auch mausgesteuert mit mouseX bzw. mouseY)
for (int x = 0; x < tiles; x++){
for (int y = 0; y < tiles; y++) {
color c = img.get(int(x*tileSize),int(y*tileSize));
float b = map(brightness(c),0,255,1,0);
if (mousePressed){ //"Parikel" werden längst der z Achse verteilt, wenn Maus gedrückt und gezogen wird
float z = map(b,0,1,mouseX/2,mouseY/2);
push();
translate(x*tileSize - width/2, y*tileSize - height/2, z);
sphere(tileSize*b*0.8);
pop();
}
else { //wenn Maus NICHT gedrückt wird, werden die "Partikel" gleichmäßig verteilt
float z = map(b,0,1,-100,100);
push();
translate(x*tileSize - width/2, y*tileSize - height/2, z);
sphere(tileSize*b*0.8); //Definition der Form
pop();
}
}
}
pop();
}
void keyPressed () { //Screenshot
if (key =='s') {
save("screenshots/tutorial.jpg");
}
}
Kamera
import processing.video.*;
Capture webcam;
void setup()
{
size(640, 480);
smooth();
webcam = new Capture(this, width, height, 30);
webcam.start();
}
void draw()
{
background(#FFFFFF);
image(webcam, 0, 0);
}
void captureEvent(Capture webcam)
{
webcam.read();
}
void keyPressed () { //Screenshot
if (key =='c') {
save("5.jpg");
}
}
Dieser Code lässt viel Spielraum für deine eigene Kreativität, also probier dich aus.
Die Inspiration für unser Projekt war dieses Tutorial von Tim Rodenbröker.
Der Code für die Kamerafunktion stammt von dieser Webseite.