Die Postkarte drehen und vergrößern

CBeginner
Jetzt üben

Einführung

Die Druckwerkstatt möchte dieselbe Postkarte in einer neuen Richtung und mit mehr Pixeln. Du verfolgst die Bewegung eines Pixels, implementierst die Drehung im Uhrzeigersinn und ergänzt anschließend eine ganzzahlige Vergrößerung ohne Farbmischung.

Dieses Lab beginnt unabhängig mit eigenen Motiven und Quelldateien. Du brauchst die Raster-, Paletten-, P3- und Terminalkenntnisse aus dem vorherigen Lab. Ein bereitgestellter kleiner Leser verarbeitet die Kommandozeilenoptionen; du implementierst die Koordinatenfunktionen. Lasse Motiv und Optionsleser unverändert. Die Übungsdauer ist eine Schätzung des Autors.

Eine rechteckige Postkarte im Uhrzeigersinn drehen

In diesem Schritt verwandelst du ein Bild mit fünf mal drei Pixeln in eines mit drei mal fünf, ohne seine Farben zu ändern.

Öffne Workspace. Dieses Lab hat ein eigenes frisch vorbereitetes /home/labex/project/postcards und verwendet keine Dateien des vorherigen Labs. base.c ist das vollständige Beispiel in ursprünglicher Ausrichtung. Kompiliere und starte zunächst dieses funktionierende Beispiel:

cd /home/labex/project/postcards
gcc -std=c11 -Wall -Wextra base.c -o base
./base > original.ppm

Wechsle zu Postcard Preview, klicke auf Refresh files, wähle original.ppm und klicke auf Open postcard. Der Status 5 × 3 und die farbigen Zeilen zeigen die Ausgangsausrichtung. Kehre zu Workspace zurück und lies das neue Gerüst:

cat art.h
cat card.c

Lasse art.h, base.c und cli.h unverändert; bearbeite card.c. Das bereitgestellte main nimmt zwei Argumente entgegen: ROTATE ist genau 0 (ursprünglich) oder 1 (im Uhrzeigersinn), SCALE eine ganze Zahl von 1 bis 4. Der Hilfscode in cli.h speichert sie in den ganzzahligen Variablen rotation und scale. parse_options liefert nur für unterstützte Argumente wahr zurück; andernfalls beendet sich main mit Status 2, bevor ein Bild geschrieben wird. Die Deklarationen argc und argv gehören zum bereitgestellten C-Einstiegspunkt; eine zeigerbasierte Argumentverarbeitung musst du hier nicht implementieren.

Eine Drehung im Uhrzeigersinn vertauscht Breite und Höhe. Verfolge den linken oberen Quellpixel (0,0): Er wandert nach rechts oben im gedrehten Bild, an (HEIGHT - 1,0). Die Ausgabeschleife fragt umgekehrt: Welchen Quellpixel soll sie für das Ziel (x,y) lesen? Ohne Vergrößerung lautet die Antwort source_x = y und source_y = HEIGHT - 1 - x. Wenn du die Zielspalte von HEIGHT - 1 abziehst, kehrst du die Reihenfolge der Quellzeilen um. WIDTH - 1 wäre hier für ein Rechteck falsch.

Jede Koordinatenfunktion erhält x und y als ganzzahlige Parameter und gibt einen ganzzahligen Index zurück. Ist rotation null, liefern die Funktionen die unveränderten Koordinaten. Der vorgegebene Ausdruck rotation ? HEIGHT : WIDTH bedeutet: „Wähle HEIGHT, wenn rotation ungleich null ist, andernfalls WIDTH.“

Ersetze card.c durch diese vollständige erste Version. Wie im ersten Lab kopiert das Here-Document mit dem in Anführungszeichen gesetzten Begrenzer den C-Text wörtlich bis zur letzten EOF-Zeile. Diese Version unterstützt Faktor 1; im nächsten Schritt kommen die Faktoren 2 bis 4 hinzu.

cat > card.c <<'EOF'
#include <stdio.h>
#include "art.h"
#include "cli.h"

int source_x(int x, int y) {
    if (rotation) return y;
    return x;
}
int source_y(int x, int y) {
    if (rotation) return HEIGHT - 1 - x;
    return y;
}
int main(int argc, char **argv) {
    if (!parse_options(argc, argv)) return 2;
    int out_width = (rotation ? HEIGHT : WIDTH) * scale;
    int out_height = (rotation ? WIDTH : HEIGHT) * scale;
    printf("P3\n%d %d\n255\n", out_width, out_height);
    for (int y = 0; y < out_height; y++) {
        for (int x = 0; x < out_width; x++) {
            int c = pattern[source_y(x, y)][source_x(x, y)] - '0';
            printf("%d %d %d\n", palette[c][0], palette[c][1], palette[c][2]);
        }
    }
    return 0;
}
EOF

Kompiliere sie. Erzeuge die ursprüngliche Ausrichtung und die Drehung im Uhrzeigersinn mit Faktor 1; jede Ausgabe bekommt einen eigenen Namen:

gcc -std=c11 -Wall -Wextra card.c -o card
./card 0 1 > original.ppm
./card 1 1 > rotated.ppm

Prüfe die gedrehte Datei:

python3 /home/labex/.local/share/pixel-postcards/inspect.py rotated.ppm
3 x 5 pixels; 15 RGB triples; max value 255

Wechsle zu Postcard Preview, klicke auf Refresh files, wähle rotated.ppm und klicke auf Open postcard. Der frühere dunkle Streifen links sollte jetzt die obere Zeile bilden. Die Maße allein beweisen keine Drehung: Vergleiche auch die farbigen Pixel. Wirkt das Ergebnis gespiegelt, prüfe die Umkehrung HEIGHT - 1 - x, kompiliere erneut und erzeuge die Datei noch einmal.

Jeden Pixel vergrößern und ungültige Optionen ablehnen

In diesem Schritt vergrößerst du die gedrehte Karte, indem jeder Quellpixel in einen gleich großen Block kopiert wird.

Beginne in Workspace. Bei Faktor 2 lesen die Zielspalten 0 und 1 beide Quellspalte 0; Spalten 2 und 3 lesen Quellspalte 1. Ganzzahldivision bildet genau diese Gruppen: 0 / 2 und 1 / 2 ergeben beide 0. Teile die Zielkoordinaten zuerst durch scale, um die Vergrößerung rückgängig zu machen. Danach wählt dieselbe umgekehrte Drehformel die Quellposition. So entsteht ein im Uhrzeigersinn gedrehtes und um einen positiven ganzzahligen Faktor vergrößertes Bild, ohne gemischte oder interpolierte Farben.

Im gedrehten Fall gibt source_x den Wert y / scale und source_y den Wert HEIGHT - 1 - x / scale zurück. Im ursprünglichen Fall sind es x / scale und y / scale. Die vorgegebene Hauptschleife multipliziert die Ausgabemaße bereits mit scale. Ersetze die Datei durch die vollständige Version:

cat > card.c <<'EOF'
#include <stdio.h>
#include "art.h"
#include "cli.h"

int source_x(int x, int y) {
    if (rotation) return y / scale;
    return x / scale;
}
int source_y(int x, int y) {
    if (rotation) return HEIGHT - 1 - x / scale;
    return y / scale;
}
int main(int argc, char **argv) {
    if (!parse_options(argc, argv)) return 2;
    int out_width = (rotation ? HEIGHT : WIDTH) * scale;
    int out_height = (rotation ? WIDTH : HEIGHT) * scale;
    printf("P3\n%d %d\n255\n", out_width, out_height);
    for (int y = 0; y < out_height; y++) {
        for (int x = 0; x < out_width; x++) {
            int c = pattern[source_y(x, y)][source_x(x, y)] - '0';
            printf("%d %d %d\n", palette[c][0], palette[c][1], palette[c][2]);
        }
    }
    return 0;
}
EOF

Kompiliere, erneuere mit dieser ausführbaren Datei die ursprüngliche und die gedrehte Ausgabe und erzeuge die zweifache Vergrößerung der gedrehten Karte:

gcc -std=c11 -Wall -Wextra card.c -o card
./card 0 1 > original.ppm
./card 1 1 > rotated.ppm
./card 1 2 > enlarged.ppm
python3 /home/labex/.local/share/pixel-postcards/inspect.py enlarged.ppm
6 x 10 pixels; 60 RGB triples; max value 255

Jeder der ursprünglich 15 Pixel wird zu einem Block von 2 mal 2: insgesamt 60 Pixel. Wechsle zu Postcard Preview, aktualisiere die Dateiliste, wähle enlarged.ppm und klicke auf Open postcard. Richtung und Proportionen entsprechen rotated.ppm. Die Vorschau passt beide Bilder an den Bildschirm an; lies deshalb die Maßangabe, um ihre tatsächlichen Bildabmessungen zu unterscheiden. Lade die vergrößerte Datei mit Download PPM herunter.

Der Screenshot zeigt die Drehung im Uhrzeigersinn mit gespeicherten 6 × 10 Pixeln. Der dunkle Streifen liegt nun oben; die Proportionen entsprechen der gedrehten Datei.

Die gedrehte und vergrößerte Karte in Postcard Preview

Kehre zu Workspace zurück. Faktor null würde kein sinnvolles Bild erzeugen. Der nächste Befehl ist ein absichtlicher Fehlerfall. Verwende einen neuen Dateinamen, weil > die Datei öffnet und leert, bevor das Programm seine Optionen ablehnen kann. echo $? zeigt unmittelbar den Exit-Status des vorherigen Befehls: 0 steht für Erfolg, 2 ist der Argumentfehler dieses Programms.

./card 1 0 > rejected.ppm
echo $?

Du solltest Scale must be an integer from 1 to 4 und danach 2 sehen. Prüfe die abgelehnte Datei mit dem bereits erklärten Decoder:

python3 /home/labex/.local/share/pixel-postcards/inspect.py rejected.ppm

Auch dieser Befehl scheitert absichtlich mit Cannot read postcard: Expected a P3 header. Es gibt keine Pixel zum Dekodieren. Das unterscheidet sich von einer fehlenden Datei: Die Shell hat vor dem C-Programmstart eine leere Datei angelegt. Aktualisiere in Postcard Preview die Dateiliste und öffne rejected.ppm, damit der Fehler statt des vorherigen Bildes erscheint. Öffne danach wieder enlarged.ppm, um zur normalen Ansicht zurückzukehren. Die drei korrekten Ausgabedateien bleiben erhalten.

Behalte card.c, die ausführbare Datei card, die bereitgestellten Dateien art.h und cli.h sowie original.ppm, rotated.ppm und enlarged.ppm. Der Generator muss beide Drehwerte und alle Faktoren von 1 bis 4 für bereitgestellte Muster mit bis zu 16 Spalten und 16 Zeilen unterstützen; die Maße oder Farben dieses Bildes dürfen nicht fest einprogrammiert sein. Gleichwertige Schleifen und Koordinatenausdrücke sind zulässig. Ungültige Argumente müssen mit einer Fehlermeldung scheitern, ohne einen Bildkopf auszugeben. Der bereitgestellte Parser übernimmt dies bereits.

Zusammenfassung

Du hast einen Pixel bei der Drehung im Uhrzeigersinn verfolgt, die Vergrößerung durch Ganzzahldivision rückgängig gemacht und beide Abbildungen in zwei C-Funktionen kombiniert. Du hast ursprüngliche, gedrehte und vergrößerte Bilder erzeugt und die Ablehnung von Argumenten von fehlerhaften oder fehlenden Dateien unterschieden.