Filter

Harvard Hof in Graustufen

Aufgabe

Die vielleicht einfachste Art, ein Bild darzustellen, ist ein Raster aus Pixeln (d. h. Punkten), von denen jeder eine andere Farbe haben kann. Für Schwarz-Weiß-Bilder benötigen wir daher 1 Bit pro Pixel, so dass 0 für Schwarz und 1 für Weiß stehen könnte, wie in der folgenden Abbildung.

eine einfache Bitmap

In diesem Sinne ist ein Bild also nur ein Raster aus Bits - eine sogenannte Bitmap. Für buntere Bilder benötigt man einfach mehr Bits pro Pixel. Ein Dateiformat (wie BMP, JPEG oder PNG), das “24-Bit Farbtiefe” unterstützt, verwendet 24 Bits pro Pixel. (BMP unterstützt 1-, 4-, 8-, 16-, 24- und 32-Bit Farbtiefe.)

Eine 24-Bit BMP verwendet 8 Bits zur Angabe des Rotanteils eines Pixels, 8 Bits zur Angabe des Grünanteils eines Pixels und 8 Bits zur Angabe des Blauanteils eines Pixels. Wenn Sie schon einmal von RGB-Farben gehört haben, wissen Sie spätestens jetzt, was sich dahinter verbirgt: Rot, Grün, Blau.

Wenn die R-, G- und B-Werte eines Pixels in einem BMP, sagen wir, 0xff, 0x00 und 0x00 in Hexadezimal sind, ist dieser Pixel komplett rot, da 0xff (auch bekannt als 255 in Dezimal) “viel Rot” bedeutet, während 0x00 und 0x00 “kein Grün” bzw. “kein Blau” bedeuten.

In dieser Aufgabe werden wir die R-, G- und B-Werte einzelner Pixel manipulieren, um eigene Bildfilter zu erstellen, die wir auf BMP-Dateien anwenden können.

ℹ️
Beachten Sie die Anweisungen in den folgenden Abschnitten.

Demo

Aufgabenmaterial

Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen.

Aufgabenmaterial herunterladen

Öffnen Sie VS Code entsprechend Ihrem Setup.

Öffnen Sie Ihr Terminalfenster und führen Sie dann cd aus. Die Eingabeaufforderung Ihres Terminalfensters sollte wie folgt aussehen:

$

Geben Sie dann

wget https://inf.zone/download/exercises/04/filter.zip

ein und führen Sie den Befehl mit der Eingabetaste aus, um eine ZIP-Datei namens filter.zip in den aktuellen Ordner herunterzuladen. Achten Sie darauf, dass Sie das Leerzeichen zwischen wget und der folgenden URL nicht übersehen, und auch kein anderes Zeichen!

Führen Sie jetzt

unzip filter.zip

aus, um das ZIP-Archiv in einen Ordner namens filter zu extrahieren. Sie brauchen die ZIP-Datei nicht mehr, also können Sie

rm filter.zip

ausführen. Antworten Sie mit “y”, gefolgt von der Eingabetaste, um die heruntergeladene ZIP-Datei zu entfernen.

Führen Sie dann

cd filter

aus, um in dieses Verzeichnis zu wechseln. Ihre Eingabeaufforderung sollte nun wie folgt aussehen:

filter/ $

Wenn alles wie beschrieben funktioniert hat, sollten Sie nach dem Ausführen von

ls

mehrere Dateien sehen: bmp.h, filter.c, helpers.h, helpers.c, und Makefile. Sie sollten auch einen Ordner images/ mit vier BMP-Dateien sehen. Wenn diese Dateien nicht angezeigt werden, führen Sie die gleichen Schritte noch einmal durch und schauen Sie, ob Sie herausfinden können, wo Sie einen Fehler gemacht haben!

Hintergrund

Etwas technischer

Wie Sie bereits gelernt haben, ist eine Datei nur eine Folge von Bits, die in irgendeiner Weise angeordnet sind. Eine BMP-Datei ist also im Wesentlichen auch nur eine Folge von Bits, von denen (fast) alle einfach die Farbe eines Pixels repräsentieren. Eine BMP-Datei enthält aber auch einige “Metadaten”, z.B. Informationen wie Höhe und Breite eines Bildes. Diese Metadaten werden am Anfang der Datei in Form von zwei Datenstrukturen gespeichert, die allgemein als “Header” bezeichnet werden - nicht zu verwechseln mit den Header-Dateien von C. (Diese Header haben sich übrigens im Laufe der Zeit weiterentwickelt. Wir verwenden in dieser Aufgabe die neueste Version des BMP-Formats von Microsoft, Version 4.0, die mit Windows 95 eingeführt wurde.)

Der erste dieser Header heißt BITMAPFILEHEADER und ist 14 Bytes lang. (Zur Erinnerung: 1 Byte entspricht 8 Bits.) Der zweite heißt BITMAPINFOHEADER und ist 40 Byte lang. Unmittelbar nach diesen Headern folgt die eigentliche Bitmap: ein Array von Bytes, von denen jedes Tripel die Farbe eines Pixels repräsentiert. BMP speichert diese Tripel jedoch rückwärts (d. h. als BGR). Im Falle einer 24-Bit-BMP mit 8 Bits für Blau, gefolgt von 8 Bits für Grün, gefolgt von 8 Bits für Rot. (Einige BMPs speichern auch die gesamte Bitmap rückwärts, wobei die oberste Zeile eines Bildes am Ende der BMP-Datei steht. Wir haben die BMPs dieser Aufgabe jedoch so gespeichert, wie hier beschrieben, d. h. die oberste Zeile jedes Bitmaps zuerst und die unterste Zeile zuletzt). Mit anderen Worten: Wenn wir den obigen 1-Bit-Smiley in einen 24-Bit-Smiley umwandeln und dabei Schwarz durch Rot ersetzen, würde eine 24-Bit-BMP diese Bitmap wie folgt speichern, wobei 0000ff für Rot und ffffff für Weiß steht (in der Abbildung sind alle Instanzen von 0000ff rot hervorgehoben).

rotes Smiley

Da wir diese Bits von links nach rechts, von oben nach unten in 8 Spalten dargestellt haben, können Sie den roten Smiley tatsächlich sehen, wenn Sie mit etwas Abstand darauf schauen.

Es sei noch daran erinnert, dass eine hexadezimale Ziffer 4 Bits repräsentiert. Dementsprechend wird ffffff (6 Ziffern) im Hexadezimalsystem bei der Umwandlung in das Binärsystem zu 111111111111111111111111 (24 Ziffern bzw. Bits, da 6 * 4 Bits = 24 Bits).

Abschließend ist es wichtig zu wissen, dass eine Bitmap als 2-dimensionales Array von Pixeln repräsentiert werden kann: Das Bild ist ein Array von Zeilen und jede Zeile ist ein Array von Pixeln. Auf diese Weise werden wir in dieser Aufgabe Bitmap-Bilder repräsentieren.

Bild Filter

Was bedeutet es überhaupt, ein Bild zu filtern? Das Filtern eines Bildes kann man sich so vorstellen, dass man die Pixel eines Originalbildes nimmt und jeden Pixel so verändert, dass ein bestimmter Effekt im resultierenden Bild sichtbar wird.

Überblick über den Code

Werfen wir nun einen Blick auf einige der bereitgestellten Dateien, um zu verstehen, was dort bereits vorimplementiert ist.

bmp.h

Öffnen Sie die Datei bmp.h und werfen Sie einen Blick hinein.

Sie finden darin Definitionen der bereits erwähnten Header (BITMAPINFOHEADER und BITMAPFILEHEADER). Außerdem definiert diese Datei BYTE, DWORD, LONG und WORD, Datentypen, die normalerweise in der Welt der Windows-Programmierung vorkommen. Beachten Sie, dass dies nur Aliase für Primitive sind, mit denen Sie (hoffentlich) bereits vertraut sind (siehe Aufgabe Lautstärke). Es scheint, dass BITMAPFILEHEADER und BITMAPINFOHEADER diese Typen verwenden.

Am interessantesten für Sie ist vielleicht, dass diese Datei auch eine struct namens RGBTRIPLE definiert, die drei Bytes “kapselt”: ein blaues, ein grünes und ein rotes (die Reihenfolge, in der wir erwarten, RGB-Tripel tatsächlich auf der Festplatte zu finden, siehe Abschnitt “Etwas Technischer”). Hier taucht auch der bereits zuvor erwähnte Datentyp BYTE auf. Wenn Sie am Anfang nachgeschaut haben, sollten Sie es bereits wissen - hinter dem BYTE steckt der Datentyp uint8_t (d.h. letztlich eben einfach ein Byte). Dieser wurde mit typedef lediglich hinsichtlich seines Namens umdefiniert.

Warum sind diese structs nützlich? Erinnern Sie sich daran, dass eine Datei nur eine Folge von Bytes (oder letztlich Bits) auf der Festplatte ist. Aber diese Bytes sind in der Regel so geordnet, dass die ersten Bytes für etwas stehen, die nächsten Bytes für etwas anderes, und so weiter. Es gibt “Dateiformate”, weil die Welt standardisiert hat, welche Bytes was bedeuten. Jetzt könnten wir einfach eine Datei von der Festplatte in den Arbeitsspeicher als ein großes Array von Bytes einlesen. Und wir könnten uns einfach merken, dass das Byte an array[i] für eine Sache steht, während das Byte an array[j] für eine andere steht. Aber warum sollten wir nicht einigen dieser Bytes Namen geben, damit wir sie leichter aus dem Speicher abrufen können? Genau das ermöglicht uns die struct in bmp.h. Anstatt eine Datei als eine lange Folge von Bytes zu betrachten, können wir sie stattdessen als eine Folge von structs` betrachten.

filter.c

Öffnen wir nun die Datei filter.c. Diese Datei wurde bereits für Sie geschrieben, aber es gibt ein paar wichtige Punkte, die Sie hier verstehen sollten.

Beachten Sie zunächst die Definition von filters in Zeile 10. Diese Zeichenkette teilt dem Programm mit, welche Befehlszeilenargumente für das Programm zulässig sind: b, g, r und s. Jedes dieser Argumente gibt einen anderen Filter an, den wir auf unsere Bilder anwenden können: Weichzeichnen, Graustufen, Spiegelung und Sepia.

Die nächsten Zeilen ab Zeile 34 öffnen eine Bilddatei, stellen sicher, dass es sich tatsächlich um eine BMP-Datei handelt, und lesen alle Pixelinformationen in ein 2D-Array namens image.

Scrollen Sie nach unten zur Anweisung switch, die in Zeile 101 beginnt. Beachten Sie, dass je nachdem, welchen filter wir gewählt haben, eine andere Funktion aufgerufen wird: Wenn der Benutzer den Filter b wählt, ruft das Programm die Funktion blur auf; wenn g, dann wird grayscale aufgerufen; wenn r, dann wird reflect aufgerufen; und wenn s, dann wird sepia aufgerufen. Beachten Sie auch, dass jede dieser Funktionen die Höhe des Bildes, die Breite des Bildes und das 2D-Array der Pixel als Argumente erhält.

Dies sind die Funktionen, die Sie (bald!) implementieren werden. Wie Sie sich vielleicht vorstellen können, besteht das Ziel jeder dieser Funktionen darin, das 2D-Pixel-Array (namens image) so zu bearbeiten, dass der gewünschte Filter auf das Bild angewendet wird.

Die restlichen Zeilen des Programms nehmen das überarbeitete image und schreiben es in eine neue Bilddatei.

helpers.h

Werfen Sie als nächstes einen Blick auf helpers.h. Diese Datei ist recht kurz und enthält nur die Funktionsprototypen für die Funktionen, die Sie zuvor gesehen haben.

Hier ist zu beachten, dass jede Funktion ein 2D-Array namens image als Argument nimmt, wobei image ein Array mit height Zeilen ist, und jede Zeile selbst ein weiteres Array mit width RGBTRIPLEs ist. Wenn also image das ganze Bild repräsentiert, dann repräsentiert image[0] die erste Zeile, und image[0][0] repräsentiert den Pixel in der oberen linken Ecke des Bildes.

helpers.c

Öffnen Sie nun helpers.c. Hier werden die in helpers.h deklarierten Funktionen implementiert. Diese Implementierungen fehlen aber noch! Dies ist Ihre Aufgabe.

Makefile

Zum Schluss wollen wir uns noch Makefile ansehen. Diese Datei legt fest, was passieren soll, wenn wir einen Terminalbefehl wie make filter ausführen. Während Programme, die Sie vorher geschrieben haben, auf eine einzige Datei beschränkt waren, nutzt filter nun mehrere Dateien: filter.c und helpers.c. Also müssen wir make sagen, wie es diese Datei kompilieren soll.

Versuchen Sie, filter selbst zu kompilieren, indem Sie in Ihrem Terminal folgendes ausführen:

make filter

Anschließend können Sie das Programm durch den folgenden Befehl starten:

./filter -g images/yard.bmp out.bmp

Dieser nimmt das Bild images/yard.bmp und erzeugt ein neues Bild namens out.bmp, nachdem die Pixel durch die Funktion grayscale gelaufen sind. Die Funktion grayscale ändert allerdings noch nichts, so dass das Ausgabebild genauso aussehen sollte wie das Originalbild.

Spezifikation

Implementieren Sie die Funktionen in helpers.c so, dass ein Benutzer Graustufen-, Sepia-, Spiegelungs- oder Unschärfefilter auf seine Bilder anwenden kann.

  • Die Funktion grayscale sollte ein Bild in eine Schwarz-Weiß-Version desselben Bildes umwandeln.
  • Die Funktion sepia sollte ein Bild in eine Sepia-Version desselben Bildes umwandeln.
  • Die Funktion reflect sollte ein Bild nehmen und es horizontal spiegeln.
  • Die Funktion blursollte ein Bild in eine unscharfe Version desselben Bildes umwandeln.

Sie sollten keine der Funktionssignaturen und auch keine anderen Dateien außer helpers.c ändern. Überdenken Sie stattdessen Ihr Vorgehen, falls dieses nur mit einer Änderung an einer anderen Stelle im Code funktioniert.

Details zur Umsetzung

Für die Bearbeitung dieser Aufgabe ist es nicht erforderlich, filter.c vollständig verstanden zu haben. Letztendlich müssen Sie nur die Funktionen für die einzelnen Filter implementieren. Es ist jedoch wichtig verstanden zu haben, welche Argumente den bereits angelegten Filterfunktionen übergeben werden und wie eine Bitmap und somit das übergebene 2-D-Array image des eigens definierten Typs RGBTRIPLE (welches letztlich eine Bitmap repräsentiert) aufgebaut ist.

Die Filter können unabhängig von einander implementiert werden. Beginnen sich jedoch am besten in der angegebenen Reihenfolge. Lesen Sie die entsprechenden Informationen zur Implementierung des jeweiligen Filters. Dort finden Sie auch eine kleine Einstiegshilfe.

Implementierung von grayscale

Ein gängiger Filter ist der “Graustufen”-Filter, mit dem man ein Bild in Schwarz-Weiß umwandeln kann. Wie funktioniert das?

Wenn die Rot-, Grün- und Blauwerte alle auf 0x00 (hexadezimal für 0) gesetzt sind, ist der Pixel schwarz. Und wenn alle Werte auf 0xff (hexadezimal für 255) gesetzt sind, dann ist der Pixel weiß. Solange die Rot-, Grün- und Blauwerte alle gleich sind, ergeben sich unterschiedliche Grautöne entlang des Schwarz-Weiß-Spektrums, wobei höhere Werte für hellere Farbtöne (näher an Weiß) und niedrigere Werte für dunklere Farbtöne (näher an Schwarz) stehen.

Um also einen Pixel in Graustufen umzuwandeln, müssen wir nur dafür sorgen, dass die Rot-, Grün- und Blauwerte alle denselben Wert haben. Aber woher wissen wir, welchen Wert wir ihnen geben sollen? Nun, wenn die ursprünglichen Rot-, Grün- und Blauwerte alle ziemlich hoch waren, dann sollte auch der neue Wert ziemlich hoch sein. Und wenn die ursprünglichen Werte alle niedrig waren, dann sollte auch der neue Wert niedrig sein.

Um sicherzustellen, dass jeder Pixel des neuen Bildes immer noch die gleiche allgemeine Helligkeit oder Dunkelheit wie das alte Bild hat, können wir den Durchschnitt der Rot-, Grün- und Blauwerte nehmen, um zu bestimmen, welchen Grauton der neue Pixel haben soll.

Wenn Sie dies auf jeden Pixel des Bildes anwenden, wird das Bild in Graustufen umgewandelt.

Hilfestellung

Wandeln Sie das beschriebene Vorgehen in Pseudocode um, um das Problem in kleinere Schritte zu unterteilen.

void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels

        // Take average of red, green, and blue

        // Update pixel values
}

Wie können Sie nun über alle Pixel iterieren? Erinnern Sie sich, dass die Pixel des Bildes in dem zweidimensionalen Array image gespeichert sind. Um über ein zweidimensionales Array zu iterieren, brauchen Sie zwei Schleifen, die ineinander verschachtelt sind.

void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Take average of red, green, and blue

            // Update pixel values
        }
    }
}

Nun können Sie mit image[i][j] auf jeden einzelnen Pixel des Bildes zugreifen. Aber wie kann man den Durchschnitt der roten, grünen und blauen Elemente ermitteln? Erinnern Sie sich daran, dass jedes Element von image ein RGBTRIPLE ist, welches die in bmp.h definierte struct zur Darstellung eines Pixels ist. Es gilt die übliche Syntax für den Zugriff auf Elemente eines struct, wobei image[i][j].rgbtRed Ihnen Zugriff auf den Rotwert des RGBTRIPLE gibt, image[i][j].rgbtGreen den Zugriff auf den Grünwert, und so weiter.

Wenn Sie den Durchschnitt berechnen, denken Sie daran, dass die Werte der Komponenten rgbtRed, rgbtGreen und rgbtBlue eines Pixels allesamt Ganzzahlen sind. Stellen Sie also sicher, dass Sie mit round alle Fließkommazahlen auf die nächste Ganzzahl runden, bevor Sie sie einem Pixelwert zuordnen! Und warum sollten Sie die Summe dieser ganzen Zahlen durch 3.0 und nicht durch 3 teilen?

Nachdem Sie aus dem Mittelwert der Rot-, Grün- und Blauwerte des Pixels eine entsprechende Graustufenfarbe berechnet haben, können Sie die Rot-, Grün- und Blauwerte des Pixels aktualisieren. Inzwischen sind Sie bereits mit der Syntax für die Zuweisung vertraut!

Implementierung von sepia

Ein “Sepia”-Filter gibt dem ganzen Bild einen leicht rötlich-braunen Farbton und verleiht ihm dadurch einen altmodischen Charakter.

Ein Bild kann in Sepia umgewandelt werden, indem für jedes Pixel neue Rot-, Grün- und Blauwerte berechnet werden, die auf den ursprünglichen Werten dieser drei Werte basieren.

Es gibt eine Reihe von Algorithmen für die Umwandlung eines Bildes in Sepia, aber für diese Aufgabe werden wir den folgenden Algorithmus verwenden. Für jeden Pixel sollten die Sepia-Farbwerte auf der Grundlage der ursprünglichen Farbwerte wie folgt berechnet werden:

sepiaRed = .393 * originalRed + .769 * originalGreen + .189 * originalBlue
sepiaGreen = .349 * originalRed + .686 * originalGreen + .168 * originalBlue
sepiaBlue = .272 * originalRed + .534 * originalGreen + .131 * originalBlue

Es kann natürlich vorkommen, dass das Ergebnis einer dieser Formeln keine ganze Zahl ist. Daher muss jeder Wert auf die nächste ganze Zahl gerundet werden. Es ist auch möglich, dass das Ergebnis der Formel eine Zahl größer als 255 ist, dem Maximalwert für einen 8-Bit-Farbwert. In diesem Fall sollte das Ergebnis auf 255 begrenzt werden. Dadurch wird sichergestellt, dass die resultierenden Rot-, Grün- und Blauwerte ganze Zahlen zwischen 0 und 255 (einschließlich) sind.

Hilfestellung

Wandeln Sie das beschriebene Vorgehen in Pseudocode um, um das Problem in kleinere Schritte zu unterteilen. Erinnern Sie sich an die Verwendung von verschachtelten for-Schleifen, um über jeden Pixel zu iterieren.

void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Compute sepia values

            // Update pixel with sepia values
        }
    }
}

Für die Berechnung der sepia-Werte können Sie die vorgegebene Formel verwenden, aber dabei gibt es ein paar Haken. Insbesondere müssen Sie…

  • das Ergebnis jeder Berechnung auf die nächstliegende ganze Zahl runden.
  • sicherstellen, dass der resultierende Wert nicht größer als 255 ist.

Inwiefern könnte eine Funktion, die den kleineren Wert von zwei Ganzzahlen zurückgibt, bei der Implementierung von sepia nützlich sein, wenn man sicherstellen muss, dass der Wert einer Farbe nicht größer als 255 ist? Sie können auch, müssen aber nicht, eine eigene Hilfsfunktion schreiben, die genau das tut!

Implementierung von reflect

Einige Filter können auch Pixel verschieben. Die Spiegelung eines Bildes zum Beispiel ist ein Filter, der das Bild so aussehen lässt, als würde man das Originalbild vor einen Spiegel halten. Alle Pixel auf der linken Seite des Bildes sollten also auf der rechten Seite landen und umgekehrt.

Beachten Sie, dass alle ursprünglichen Pixel des Originalbildes auch im gespiegelten Bild vorhanden sind, nur dass die meisten dieser Pixel an einer anderen Stelle im Bild angeordnet sind.

Hilfestellung

In der Funktion reflect müssen Sie also die Werte von Pixeln auf gegenüberliegenden Seiten einer Reihe vertauschen. Überlegen Sie, wie Sie vorgehen könnten, indem Sie etwas Pseudocode schreiben:

void reflect(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Swap pixels
        }
    }
}

Erinnern Sie sich daran, wie wir in der Vorlesung das Vertauschen von zwei Werten mit einer temporären Variablen implementiert haben. Es ist nicht nötig, eine separate Funktion für das Vertauschen zu verwenden, es sei denn, Sie möchten das!

Denken Sie dabei auch noch einmal über beiden verschachtelten for-Schleifen nach. Die äußere for-Schleife iteriert über jede Zeile, während die innere for-Schleife über jeden Pixel in dieser Zeile iteriert. Muss wirklich jedes Pixel einer Zeile durchlaufen werden, um eine Zeile erfolgreich zu spiegeln?

Implementierung von blur

Es gibt verschiedene Möglichkeiten, ein Bild unscharf oder “weicher” zu machen. Für diese Aufgabe verwenden wir den “Box Blur”, bei dem jedes Pixel genommen wird und für jeden Farbwert ein neuer Wert berechnet wird, indem die Farbwerte der benachbarten Pixel gemittelt werden.

Betrachten Sie das folgende Pixelraster, in dem wir jeden Pixel nummeriert haben.

ein Raster aus Pixeln

Der neue Wert jedes Pixels wäre der Mittelwert der Werte aller Pixel, die sich innerhalb einer Zeile und Spalte des ursprünglichen Pixels befinden, d.h. einen 3x3-Rahmen bilden. Zum Beispiel würde jeder der Farbwerte für Pixel 6 durch Mittelwertbildung der ursprünglichen Farbwerte der Pixel 1, 2, 3, 5, 6, 7, 9, 10 und 11 berechnet werden (beachten Sie, dass Pixel 6 selbst in den Mittelwert einbezogen wird).

ein Raster aus Pixeln mit 3x3-Rahmen um Pixel 6

Analog würden die Farbwerte für Pixel 11 durch Mittelwertbildung der Farbwerte der Pixel 6, 7, 8, 10, 11, 12, 14, 15 und 16 berechnet werden.

Für ein Pixel am Rand oder in der Ecke, wie z. B. Pixel 15, wird weiterhin nach allen Pixeln innerhalb einer Zeile und Spalte gesucht: in diesem Fall nach den Pixeln 10, 11, 12, 14, 15 und 16.

Hilfestellung

Bei der Implementierung der Unschärfe-Funktion stoßen Sie womöglich auf folgendes Problem: Die Unschärfe eines Pixels kann die Unschärfe eines anderen Pixels beeinflussen. Daher ist es am besten, eine Kopie von image zu erstellen, indem man ein neues, zweidimensionales Array mit Code wie RGBTRIPLE copy[height][width]; deklariert. Kopieren Sie dann die Werte von image in copy, mit verschachtelten for-Schleifen, Pixel für Pixel, etwa so:

void blur(int height, int width, RGBTRIPLE image[height][width])
{
    // Create a copy of image
    RGBTRIPLE copy[height][width];
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            copy[i][j] = image[i][j];
        }
    }
}

Jetzt können Sie die Werte der Pixel von copy auslesen, die Berechnungen durchführen und dann die Ergebnisse in die Pixelwerte von image schreiben!

Testen

Kompilieren Sie filter mit:

make filter

Anschließend können Sie das Programm z.B. durch den folgenden Befehl starten:

./filter -g images/yard.bmp out.bmp

Dieser nimmt das Bild images/yard.bmp und erzeugt ein neues Bild namens out.bmp, nachdem die Pixel durch die Funktion grayscale gelaufen sind.

Testen Sie unbedingt alle Ihre Filter an den mitgelieferten Bitmap-Beispieldateien! Wie oben erwähnt, können Sie b, r und s statt g verwenden, um auch die anderen Filter zu testen.

Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

check50 -l cs50/problems/2024/x/filter/less

Style

Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50 zu analysieren:

style50 helpers.c