image: rainbow.webp

Pixelgrafik programmieren

In diesem Kapitel lernst du, wie du Pixelgrafiken programmieren kannst. Dazu installieren wir das Paket »Pixelflow Canvas« und erstellen ein erstes Bild mit einer hügeligen Wiese aus Bézierkurven und einem Regenbogen aus konzentrischen Krei­sen.

Stelle zuerst sicher, dass du keinen Ordner geöffnet hast. Um sicherzugehen, drücke einfach den Shortcut für »Ordner schließen«: StrgK und dann F. Dein Work­space sollte jetzt ungefähr so aussehen:

1. Pixelflow Canvas installieren

Falls du es noch nicht getan hast, musst du zuerst eine Er­wei­te­rung für Visual Studio Code installieren. Anschließend installieren wir ein Rubygem, das uns das Pro­gram­mier­en von Pixelgrafiken er­mög­licht.

VS Code-Er­wei­te­rung installieren

Um Pixelgrafiken im Work­space programmieren zu können, benötigen wir die Er­wei­te­rung »Pixelflow Canvas«. Klicke dazu auf das Extensions-Symbol in der Seitenleiste und suche nach »Pixelflow Canvas«. Klicke auf »Install«, um die Er­wei­te­rung zu installieren.

Rubygem installieren

Um bequem von Ruby Befehle an das Canvas schicken zu können, benötigen wir das Rubygem »pixelflow_canvas«. Öffne das Ter­mi­nal, indem du den Shortcut StrgJ drückst. Dein Work­space sollte jetzt ungefähr so aussehen:

Installiere es, indem du folgenden Befehl in das Ter­mi­nal eingibst:

gem install pixelflow_canvas

2. Zeichenfläche ausprobieren

Wähle im Menü links oben »File« / »New Text File« (oder drücke StrgAltN), um eine neue Datei anzulegen:

Schreibe den folgenden Code in die Datei:

require 'pixelflow_canvas'

Pixelflow::Canvas.new(320, 180, :palette) do
    set_pixel(160, 90, 10)
end

Da Visual Studio Code noch nicht weiß, dass es sich um Ruby-Code handelt, sieht dein Code noch etwas farblos aus:

Speichere die Datei unter dem Da­tei­na­men rainbow.rb, indem du StrgS drückst und den Da­tei­na­men eingibst.

Dein Programm sollte jetzt ungefähr so aussehen:

Bevor wir das Programm ausführen können, müssen wir das Pixelflow Canvas öffnen. Drücke dazu StrgShiftP oder F1 und gib dann »Show Pixelflow Canvas« ein.

Tipp: Du kannst auch einfach »pixel« eingeben, es werden dir dann automatisch alle Befehle angezeigt, die mit »Pixelflow Canvas« zu tun haben.

Du solltest jetzt ein neues Panel sehen, in dem das Pixelflow Canvas angezeigt wird:

Führe das Programm aus, indem du im Ter­mi­nal ruby rainbow.rb eingibst. Du solltest nun einen einzelnen, grünen Pixel in der Mitte der Zeichenfläche sehen:

Schauen wir uns den Code genauer an:

require 'pixelflow_canvas'

Pixelflow::Canvas.new(320, 180, :palette) do
    set_pixel(160, 90, 10)
end
  • require 'pixelflow_canvas' lädt das Rubygem »pixelflow_canvas«.
  • Pixelflow::Canvas.new(320, 180, :palette) erstellt ein neues Canvas mit einer Breite von 320 Pixeln und einer Höhe von 180 Pixeln. Der dritte Parameter :palette gibt an, dass wir eine Palette verwenden wollen.
  • der do / end-Block enthält die Befehle, die auf das Canvas angewendet werden sollen.
  • set_pixel(160, 90, 10) setzt den Pixel an der Position (160, 90) auf die Farbe 10.

Der Ursprung der Zeichenfläche ist oben links, der x-Wert nimmt nach rechts zu und der y-Wert nach unten. Da wir eine Palette verwenden, geben wir Farben einfach als Zahl von 0 bis 255 an. Welche Farbe sich hinter welcher Zahl verbirgt, ist in der Palette definiert.

Da standardmäßig die VGA-Palette ver­wen­det wird, entspricht die Farbe 10 einem hellen Grün.

Hier findest du eine Übersicht über alle Farben der Standard-VGA-Palette.

Kannst du die Farbe des Pixels auf Blau ändern? Versuche auch, den Pixel in alle vier Ecken der Zeichenfläche zu schieben.
Anstelle einer Palette kannst du auch RGB-Farben verwenden. Dazu ersetzt du einfach :palette durch :rgb. Anschließend musst du alle Farben als Tripel von rot, grün und blau im Bereich von 0 bis 255 angeben. Der historische Hintergrund für die Verwendung von Paletten rührt daher, dass man so mit weniger Speicherplatz auskam (während ein RGB-Pixel 3 Bytes benötigt, benötigt ein Pixel mit Palette nur 1 Byte, da die Farbe nur als Index in der Palette gespeichert wird). Heute ist das eigentlich nicht mehr nötig, aber es kann trotzdem von Vorteil sein, eine Palette zu verwenden, da du so eine einheitliche Farbgebung hast und nicht jedes Mal die passenden RGB-Werte heraussuchen musst.

3. Palette wählen

Die Standard-VGA-Palette ist nicht die einzige Palette, die du verwenden kannst. Du kannst auch eine andere Palette wählen oder sogar deine eigene Palette definieren. Eine Übersicht über alle verfügbaren Paletten findest du hier. Da wir eine Wiese mit Regenbogen zeichnen möchten, wählen wir die »zughy_32«-Palette:

0 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

Verändere den Code wie folgt und führe ihn erneut aus:

require 'pixelflow_canvas'

Pixelflow::Canvas.new(320, 180, :palette) do
    set_predefined_palette(:zughy_32)
    set_pixel(160, 90, 10)
end

Führe das Programm aus, indem du im Ter­mi­nal ruby rainbow.rb eingibst (du kannst auch einfach den letzten Befehl wiederholen, indem du und Enter drückst).

Du solltest jetzt sehen, dass sich die Farben geändert haben – der Hintergrund ist nicht mehr schwarz, weil die Farbe 0 nun ein dunkles Lila ist und die Farbe 10 ist nun ein dunkles Türkis.

4. Hintergrund zeichnen

Lösche den set_pixel-Befehl und füge stattdessen den folgenden Code ein:

set_color(16)
fill_rect(0, 0, 319, 179)

Wir füllen die gesamte Zeichenfläche mit der Farbe 16 (hellblau), indem wir den Befehl fill_rect verwenden. Dieser Befehlt benötigt vier Parameter: die x- und y-Koordinaten der oberen linken Ecke des Rechtecks und die x- und y-Koordinaten der unteren rechten Ecke des Rechtecks.

Obwohl die Zeichenfläche eine Größe von 320x180 Pixeln hat, hat der Pixel rechts unten die Koordinaten (319, 179), da wir bei 0 anfangen zu zählen.

Dein Bild sollte jetzt so aussehen:

5. Hügelige Wiese zeichnen

Um eine hügelige Wiese zu zeichnen, werden wir keine geraden Linien, sondern Bézierkurven verwenden. Eine Bézierkurve wird durch eine Reihe von Punkten definiert, die die Kurve beeinflussen. Wir können hier zwei Arten von Bézierkurven verwenden: eine quadratische Bézierkurve (mit drei Punkten) und eine kubische Bézierkurve (mit vier Punkten).

Du kannst hier interaktiv die Punkte der Bézierkurven verschieben, um zu verstehen, wie Bézierkurven funktionieren:

Quadratische Bézierkurve

Kubische Bézierkurve

Wir können also z. B. eine kubische Bézierkurve verwenden, um eine hügelige Wiese zu zeichnen. Dabei sind der erste und der letzte Punkt die Endpunkte der Kurve, während die beiden mittleren Punkte die Kontrollpunkte sind, die die Form der Kurve beeinflussen. Wir zeichnen drei Kurven mit verschiedenen Grüntönen, um eine hügelige Wiese darzustellen. Füge den folgenden Code hinzu:

set_color(7)
draw_cubic_bezier(0, 120, 100, 140, 200, 100, 320, 120)
set_color(8)
draw_cubic_bezier(0, 140, 100, 150, 200, 110, 320, 140)
set_color(9)
draw_cubic_bezier(0, 170, 110, 140, 220, 140, 320, 170)

Dein Bild sollte jetzt so aussehen:

6. Flächen füllen

Um den Hügel mit Farbe zu füllen, können wir den Befehl flood_fill verwenden. Dieser Befehl füllt eine Fläche mit einer bestimmten Farbe, beginnend von einem bestimmten Punkt. Füge den folgenden Code hinzu:

set_color(7)
flood_fill(0, 130)

Der Punkt an der Stelle (0, 130) liegt zwischen den oberen beiden Kurven, und nun wird der Bereich zwischen diesen beiden Kurven mit der Farbe 7 (hellgrün) gefüllt. Dein Bild sollte danach so aussehen:

Füge anschließend die folgenden Zeilen hinzu, um die restlichen Flächen zu füllen:

set_color(8)
flood_fill(0, 150)
set_color(9)
flood_fill(0, 179)

Dein Bild sollte jetzt so aussehen:

7. Regenbogen zeichnen

Wir wollen nun einen Regenbogen zeichnen, der aus ineinander gelegten Krei­sen besteht. Würden wir die Kreise einfach so zeichnen, würden sie den Rasen überdecken. Um das zu verhindern, können wir eine Maske verwenden. Eine Maske ist ein Bereich, in dem wir zeichnen können, ohne den Rest des Bildes zu beeinflussen. Wir definieren uns eine Maske, die nur die Hintergrundfarbe 16 (hellblau) umfasst, und zeichnen dann die Kreise.

Mit dem Befehl set_mask können wir eine Maske definieren. Innerhalb des do / end-Blocks können wir dann definieren, was alles zur Maske gehört. Mit dem Befehl add_color(16) geben wir an, dass alle Pixel, die momentan die Farbe 16 haben, zur Maske gehören. Alle anderen Pixel werden durch die nachfolgenden Zeichenbefehle nicht beeinflusst. Füge den folgenden Code hinzu:

set_mask do
    add_color(16)
end

Nun können wir die Kreise zeichnen. Wir verwenden gefüllte Kreise und zeichnen sie von außen nach innen. Wir beginnen mit einem Radius von 125 Pixeln und verringern ihn dann um 5 Pixel für jeden Kreis, während wir die Farben rot (28), orange (27), gelb (26), grün (8), indigo (19), violett (23) und himmelblau (16) durchlaufen. Füge den folgenden Code hinzu:

r = 125
[28, 27, 26, 8, 19, 23, 16].each do |color|
    set_color(color)
    fill_circle(160, 180, r)
    r -= 5
end

Dein Bild sollte anschließend so aussehen:

8. Himmel zeichnen

Für den Himmel müssen wir die Maske erneuern, da sie nach wie vor gilt und somit folgende Zeichenbefehle auch den Regenbogen überdecken würden. Füge den folgenden Code ein:

set_mask do
    add_color(16)
end

Im Bereich von 16 bis 20 gibt es ein paar schöne Blautöne, die wir für den Himmel verwenden können. Wir gehen zeilenweise von oben nach unten durch das Bild und wählen eine Farbe aus diesem Spektrum (mit etwas zufälliger Variation) und zeichnen damit eine horizontale Linie von links nach rechts. Füge den folgenden Code hinzu:

(0...180).each do |y|
    set_color(20 - 3.5 * (y / 179.0) - rand() * 1.5)
    draw_line(0, y, 319, y)
end

Anschließend entfernen wir die Maske wieder:

remove_mask()

Das fertige Bild sollte nun so aussehen:

9. Bild speichern

Um das Bild zu speichern, füge den folgenden Code hinzu:

save_as_png('rainbow.png')

Hier siehst du noch einmal den gesamten Code:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'pixelflow_canvas.rb'

# Wir erstellen ein neues Pixelflow-Canvas mit einer
# Größe von 320x180 Pixeln und wir möchten eine
# Palette verwenden.
canvas = Pixelflow::Canvas.new(320, 180, :palette) do
    # Wir wählen die Zughy-32-Palette
    set_predefined_palette(:zughy_32)

    # Hintergrund
    set_color(16)
    fill_rect(0, 0, 319, 179)

    # drei grüne Graskurven
    set_color(7)
    draw_cubic_bezier(0, 120, 100, 140, 200, 100, 320, 120)
    set_color(8)
    draw_cubic_bezier(0, 140, 100, 150, 200, 110, 320, 140)
    set_color(9)
    draw_cubic_bezier(0, 170, 110, 140, 220, 140, 320, 170)

    # Grasflächen füllen
    set_color(7)
    flood_fill(0, 130)
    set_color(8)
    flood_fill(0, 150)
    set_color(9)
    flood_fill(0, 179)

    # Maske setzen: nur noch Pixel setzen,
    # die die Farbe 16 (Hintergrund) haben
    set_mask do
        add_color(16)
    end

    # Regenbogen zeichnen
    r = 125
    [28, 27, 26, 8, 19, 23, 16].each do |color|
        set_color(color)
        fill_circle(160, 180, r)
        r -= 5
    end

    # Maske erneuern: nur noch Pixel setzen,
    # die jetzt die Farbe 16 (Hintergrund) haben
    set_mask do
        add_color(16)
    end

    # Himmel zeichnen: Verlauf von dunkelblau
    # nach hellblau
    (0...180).each do |y|
        set_color(20 - 3.5 * (y / 179.0) - rand() * 1.5)
        draw_line(0, y, 319, y)
    end

    # Maske aufheben
    remove_mask()

    # Bild speichern
    save_as_png("rainbow.png")
end

10. Zusammenfassung

In diesem Kapitel hast du gesehen, wie du Pixelgrafiken mit dem Pixelflow Canvas programmieren kannst. Du hast gesehen, wie man komplexe Szenen aus einfachen Formen zusammensetzen kann und anschließend das Bild speichern kann. Eine Übersicht über alle Befehle findest du in der Dokumentation des Pixelflow Canvas.