Skip to content

U0302 Das typografische Ornament

Aufgabenstellung

Erkunde die verschiedenen Schriftklassifikationen unter dem Gesichtspunkt ein typografisches Ornament zu erstellen.

Wählen Sie vier unterschiedliche Schriften aus. Aus diesen vier Schriften wählen Sie jeweils einen Buchstaben, der sich dazu eignet, wiederholend angeordnet, ein Ornament zu bilden. Nutzen Sie zur Umsetzung Processing. Erstellen Sie von einer Schrift mehrere Ornamentvarianten (mindestens 12), die sie in einer 4x3 Matrix auf einer Grundfläche der Größe 1920x1080px anordnen. Wählen Sie das beste Ornament pro Schrift aus und heben Sie es gegenüber den Varianten hervor.

Ergebnis

Für diese Aufgabe habe ich mein Tool aus dem letzten Aufgabenblock mit einem Ornament-Modus erweitert (siehe unten). Typografie-Ornamente sind interessant, weil sie sich durch kleinste Rotations- oder Positionsänderungen je nach Font komplett verändern können – Ihnen bei dieser Transformation zuzuschauen macht richtig Spaß.

Besonders die Buchstaben mit Punkten, also Ä, Ö, Ü, j, etc. bieten sich für typografische Ornamente an, da ihre von der Mittellinie abgehobenen Punkte nochmal mehr Variation reinbringen.

Manchmal hat mich auch überrascht, aus welchen "einfachen" Buchstaben doch recht komplexe Ornamente entstehen können, z.B. dem X.

Auffallend ist auch, dass die Ornamente umso komplexer werden, je näher die Buchstaben zusammenrücken. Es gibt außerdem eine Art "magische Schwelle" in der Dichte, die erst überschritten werden muss, bis die angeordneten Buchstaben nicht mehr als solche, sondern als "kollektives" Ornament erkannt werden.

Inter

Zuerst habe ich eine normale Font ausprobiert als "Vergleichsgröße", auch weil meine anderen ausgewählten Schriftarten nur über Groß-, nicht aber über Kleinschreibung verfügen. Die Kapitalisierung macht einen Unterschied – Großbuchstaben erstellen hier eher kantigere, einfachere Ornamente, während kleinbuchstaben durch ihre heterogenere Form in der Lage sind, geschwungenere Ornamente zu kreieren.

Diese Ornamente sind – wenig überraschend – recht einfach und wenig komplex im Vergleich zu den anderen ausgewählten Fonts.

Hier finde ich am interessantesten:

  • Ö (simple, aber ansprechende Form)
  • ä (erinnert an indische Symbolik)
  • X (Schneeflocke)
  • # (komplexe Schneeflocke)
T ü Ö ä
Y S E <
j X q #

Ornamente mit der Font "Inter"

Rusty Cage

The quick brown fox...

Am interessantesten finde ich die Ornamente aus Ü, X (erinnert an ein Zahnrad) und S (sieht aus wie ein Sägeblatt).

Q Z Ü X
J R Ä Ö
S L F S

Ornamente mit der Font "Rusty Cage"

Super Woobly

The quick brown fox...

Diese Schriftart ist mit Abstand mein Favorit für diese Aufgabe. Auch generell ist es eine sehr interessante Font! Die Ornamente erinnern sehr an Tintenkleckse, besonders das letzte (N).

Y Ü Ö X
ß 0 T S
S Z B N

Ornamente mit der Font "Super Woobly"

Drip October

The quick brown fox...

Die Ornamente dieser Schriftart sind zwar recht interessant, aber da die Buchstaben alle recht gleich aussehen, gibt es hier keine neuen Erkenntnisse. Generell scheint die Font nicht von hoher Qualität zu sein, und hat außerdem keine Umlaute. Die hätte ich gern ausprobiert.

Das M gefällt mir am besten durch seine dynamische Form.

E U C A
Q S K X
Z M T W

Ornamente mit der Font "Drip October"

Code

Der Code ist recht fürchterlich, aber er funktioniert!

Die Basis-Steuerung für das Grid und den Rahmen bleibt dieselbe wie bereits vorgestellt ([CTRL] + Mausrad, Rücktaste). Zwischen Punkt- und Ornamentmodus umschalten lässt sich mit der oberen bzw. der unteren Pfeiltaste.

Im Ornamentmodus:

  1. Font auswählen mit Alt + <Ziffer> (beginnend bei 1)
  2. Linke Maustaste, um die Position des Ornaments zu wählen
  3. Eine beliebige, druckbare Taste drücken
  4. Ornament verschieben und rotieren mit Shift + Mausrad bzw. Alt + Mausrad
  5. ggf. rückgängig machen mit rechter Maustaste, kombiniert mit Ctrl lassen sich alle Ornamente löschen
int gridSizeX = 1;
int gridSizeY = 1;
int gridChunkX = 9999;
int gridChunkY = 9999;
int pointWeight = 8;

int letterRotation = 0;
int letterGap = -25;

String header = "";

StringBuilder customFontName = new StringBuilder();
boolean isEnteringFont = false;
String drawMode = "ORNAMENT";
int[] lastMouseClick = {width / 2, height / 2};
// 0 = None, 1 = Thin, 2 = Thick
int edgeMode = 0;

// [ x, y, weight ]
ArrayList<int[]> pointStore = new ArrayList<>();
// [ x, y, character, rotation, gap ]
ArrayList<ArrayList<Object>> ornamentStore = new ArrayList<>();

boolean setup = false;
boolean ctrlPressed = false;
boolean altPressed = false;

final HashMap<String, PFont> fontDir = new HashMap<>();

final int BLACK = 0;
final int WHITE = 255;
final int THIN_MARGIN = 4;
final int THICK_MARGIN = 20;

void setup() {
  fullScreen();
  background(BLACK);

  // Font initialization
  fontDir.put("Inter", createFont("./font/Inter-Regular.ttf", 32));
  fontDir.put("Drip October", createFont("./font/DripOctober.ttf", 32));
  fontDir.put("Rusty Cage", createFont("./font/RustyCage.ttf", 32));
  fontDir.put("Super Wobbly", createFont("./font/SuperWoobly.ttf", 32));
}

void draw() {
  stroke(WHITE);
  fill(WHITE);
  if (!setup) {
    drawGrid();
    setup = true;
  }
}

// ---- Events ----
void mouseClicked() {
  if (mouseButton == RIGHT) {
    if (drawMode.equals("POINTS")) {
      if (ctrlPressed) {
        pointStore.clear();
      } else if (pointStore.size() > 0) {
        pointStore.remove(pointStore.size() - 1);
      }
    } else if (drawMode.equals("ORNAMENT")) {
      if (ctrlPressed) {
        ornamentStore.clear();
      } else if (ornamentStore.size() > 0) {
        ornamentStore.remove(ornamentStore.size() - 1);
      }
    }
    drawGrid();
  } else if (mouseButton == CENTER) {
    saveFrame("<Screenshot-Pfad>");
  } else {
    lastMouseClick[0] = mouseX;
    lastMouseClick[1] = mouseY;
    if (drawMode.equals("POINTS")) {
      int[] points = {mouseX, mouseY, pointWeight};
      pointStore.add(points);
    }
    drawGrid();
  }
}

void mouseWheel(MouseEvent event) {
  int direction = event.getCount() / Math.abs(event.getCount());
  if (event.isShiftDown()) {
    if (drawMode.equals("POINTS")) {
      pointWeight -= 2 * direction;
      if (pointWeight <= 0) pointWeight = 1;
    } else if (drawMode.equals("ORNAMENT")) {
      letterGap = letterGap + direction * 3;
      if (ornamentStore.size() > 0) {
        ornamentStore.get(ornamentStore.size() - 1).set(4, letterGap);
      }
      drawGrid();
    }
  } else {
    if (event.isControlDown()) {
      gridSizeX -= direction;
      if (gridSizeX <= 0) gridSizeX = 1;
    } else if (event.isAltDown()) {
      if (drawMode.equals("ORNAMENT")) {
        letterRotation = (letterRotation + direction * 3 + 360) % 360;
        if (ornamentStore.size() > 0) {
          ornamentStore.get(ornamentStore.size() - 1).set(3, letterRotation);
        }
      }
    } else {
      gridSizeY -= direction;
      if (gridSizeY <= 0) gridSizeY = 1;
    }
    drawGrid();
  }
}

void keyPressed() {
  if (key == BACKSPACE) {
    edgeMode = (edgeMode + 1) % 3;
    drawGrid();
  } else if (keyCode == CONTROL) {
    ctrlPressed = true;
  } else if (keyCode == ALT) {
    altPressed = true;
  } else if (Character.isDigit(key)) {
    int number = key - '0';
    if (ctrlPressed) {
      if (number == 0) {
        gridChunkX = 9999;
      } else {
        gridChunkX = number;
      }
    } else if (altPressed) {
      if (number <= fontDir.size() && number > 0) {
        Object[] fontDirArray = fontDir.keySet().toArray();
        String fontName = (String) fontDirArray[number - 1];
        PFont font = fontDir.get(fontName);
        textFont(font);
        header = fontName;
      }
    } else if (number == 0) {
      gridChunkY = 9999;
    } else {
      gridChunkY = number;
    }
    drawGrid();
  } else if (keyCode == UP) {
    drawMode = "POINTS";
    displayInfo(drawMode);
  } else if (keyCode == DOWN) {
    drawMode = "ORNAMENT";
    displayInfo(drawMode);
  }

  if (drawMode.equals("POINTS")) {
    if (key == 'c' || key == 's' || key == 't') {
      drawGrid();
      fill(BLACK);
      stroke(BLACK);
      for (int i = 0; i < 3; i++) {
        if (key == 'c') {
          strokeWeight((float) Math.random() * 700);
          point(
            (float) Math.random() * 1620, (float) Math.random() * 780
          );
        } else if (key == 's') {
          square(
            (float) Math.random() * 1620, (float) Math.random() * 780,
            (float) Math.random() * 700
          );
        } else {
          triangle(
            (float) Math.random() * 1920, (float) Math.random() * 1080,
            (float) Math.random() * 1920, (float) Math.random() * 1080,
            (float) Math.random() * 1920, (float) Math.random() * 1080
          );
        }
      }
      strokeWeight(0);
    }
  } else if (drawMode.equals("ORNAMENT")) {
    if (isPrintableChar(key)) {
      ArrayList<Object> data = new ArrayList<>();
      data.add(lastMouseClick[0]);
      data.add(lastMouseClick[1]);
      data.add(key);
      data.add(letterRotation);
      data.add(letterGap);
      ornamentStore.add(data);
      drawOrnaments();
    }
    /* } */
  }
}
void keyReleased() {
  if (keyCode == CONTROL) {
    ctrlPressed = false;
  } else if (keyCode == ALT) {
    altPressed = false;
  }
}

// ---- logic ----
void drawGrid() {
  fill(WHITE);
  background(BLACK);

  int chunkAmountX = (gridSizeX - 1) / gridChunkX;
  int chunkAmountY = (gridSizeY - 1) / gridChunkY;
  int edge = getEdgeWidth();

  float currentY = edge;
  float fractionX = (width
     - THIN_MARGIN * (gridSizeX - chunkAmountX - 1)
     - THICK_MARGIN * chunkAmountX
     - edge * 2) / (float)gridSizeX;
  float fractionY = (height
     - THIN_MARGIN * (gridSizeY - chunkAmountY - 1)
     - THICK_MARGIN * chunkAmountY
     - edge * 2) / (float)gridSizeY;

  for (int i = 0; i < gridSizeY; i++) {
    float currentX = edge;
    for (int j = 0; j < gridSizeX; j++) {
      rect(currentX, currentY, fractionX, fractionY);
      currentX += fractionX;
      if ((j + 1) % gridChunkX == 0) {
        currentX += THICK_MARGIN;
      } else {
        currentX += THIN_MARGIN;
      }
    }
    currentY += fractionY;
    if ((i + 1) % gridChunkY == 0) {
      currentY += THICK_MARGIN;
    } else {
      currentY += THIN_MARGIN;
    }
  }

  if (drawMode.equals("POINTS")) {
    drawPoints();
  } else if (drawMode.equals("ORNAMENT")) {
    drawOrnaments();
  }
  displayHeader();
}

void drawOrnaments() {
  fill(BLACK);
  textSize(64);
  textAlign(CENTER, CENTER);
  for (ArrayList<Object> data : ornamentStore) {
    int gap = (int) data.get(4);
    int rotation = (int) data.get(3);
    for (int i = 0; i < 8; i++) {
      pushMatrix();
      translate((int) data.get(0), (int) data.get(1));
      rotate((i * (TWO_PI / 8)));

      pushMatrix();
      translate(gap, gap);

      pushMatrix();
      translate(48, 48);
      rotate(radians(rotation));

      text(data.get(2).toString(), 0, 0);

      popMatrix();
      popMatrix();
      popMatrix();
    }
  }
}
void drawPoints() {
  stroke(BLACK);
  for (int[] points : pointStore) {
    strokeWeight(points[2]);
    point(points[0], points[1]);
  }
  strokeWeight(0);
}

int getEdgeWidth() {
  return edgeMode == 0 ? 0 : (edgeMode == 1 ? THIN_MARGIN : THICK_MARGIN);
}

void displayInfo(String mode) {
  drawGrid();
  fill(BLACK);
  textSize(128);
  textAlign(CENTER, CENTER);
  text(mode, width / 2, height / 2);
}

void displayHeader() {
  if (header.length() > 0) {
    fill(BLACK);
    textSize(48);
    textAlign(LEFT, TOP);
    text(header, 10, 10);
  }
}

boolean isPrintableChar(char input) {
  Character.UnicodeBlock block = Character.UnicodeBlock.of(input);
  return !Character.isISOControl(input)
      && block != null
      && block != Character.UnicodeBlock.SPECIALS;
}