見出し画像

CS50 2023 - Week4 Memory


概要

Week4では、メモリについて学びます。
講義の主な内容は、ポインター、セグメンテーション、動的メモリ割り当て、スタック、ヒープ、バッファオーバーフロー、ファイルI/O、画像です。


Lab 4


Week4のLabでは、以下の2つの中から1つを選んで提出します。

  • Smiley

  • Volume

どちらも難易度は同じです。
本ページでは、Smileyを取り上げます。

Smiley

この課題では、画像の仕組みについて学びます。
helpers.c内にあるcolorize関数を完成させることが目的です。

以下は私が提出したコードです。

#include "helpers.h"

void colorize(int height, int width, RGBTRIPLE image[height][width])
{
    // Selected colours
    BYTE chosenRed = 0x00;
    BYTE chosenGreen = 0xff;
    BYTE chosenBlue = 0x00;

    // Loop through the image
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // When a black pixel is found
            if (image[i][j].rgbtRed == 0x00 && image[i][j].rgbtGreen == 0x00 && image[i][j].rgbtBlue == 0x00)
            {
                // Change the black pixel to the selected colour
                image[i][j].rgbtRed = chosenRed;
                image[i][j].rgbtGreen = chosenGreen;
                image[i][j].rgbtBlue = chosenBlue;
            }
        }
    }
}

Problem Set 4

Week4のProblem Setでは、最初に以下の2つの中から1つを選んで提出します。

  • Filter(less comfortable)

  • Filter(more comfortable)

次に以下の2つの中から1つを選んで提出します。

  • Recover

  • Reverse

本ページでは、Filter(less comfortable)Recoverを取り上げます。


Filter(less comfortable)

BMP画像にフィルタを適用するプログラムを作成します。

以下のフィルタを実装します:

  • グレースケール

  • セピア

  • 反転

  • ぼかし

LabのページやWalkthroughをよく確認しながら、作業を進めることをおすすめします。

以下は私が提出したコードです。

#include "helpers.h"
#include <math.h>

// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            RGBTRIPLE pixel = image[i][j];
            int average = round((pixel.rgbtRed + pixel.rgbtGreen + pixel.rgbtBlue) / 3.0);
            image[i][j].rgbtRed = image[i][j].rgbtGreen = image[i][j].rgbtBlue = average;
        }
    }
}

// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            RGBTRIPLE pixel = image[i][j];
            int originalRed = pixel.rgbtRed;
            int originalGreen = pixel.rgbtGreen;
            int originalBlue = pixel.rgbtBlue;

            int sepiaRed = round(.393 * originalRed + .769 * originalGreen + .189 * originalBlue);
            int sepiaGreen = round(.349 * originalRed + .686 * originalGreen + .168 * originalBlue);
            int sepiaBlue = round(.272 * originalRed + .534 * originalGreen + .131 * originalBlue);

            // Cap the color values at 255
            image[i][j].rgbtRed = sepiaRed <= 255 ? sepiaRed : 255;
            image[i][j].rgbtGreen = sepiaGreen <= 255 ? sepiaGreen : 255;
            image[i][j].rgbtBlue = sepiaBlue <= 255 ? sepiaBlue : 255;
        }
    }
}

// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width / 2; j++)
        {
            RGBTRIPLE temp = image[i][j];
            image[i][j] = image[i][width - j - 1];
            image[i][width - j - 1] = temp;
        }
    }
}

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE temp[height][width];

    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            temp[i][j] = image[i][j];
        }
    }

    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int totalRed = 0;
            int totalGreen = 0;
            int totalBlue = 0;
            int counter = 0;

            // Loop through the 3x3 grid around the pixel (including the pixel itself)
            for (int di = -1; di <= 1; di++)
            {
                for (int dj = -1; dj <= 1; dj++)
                {
                    // Calculate the position of the neighboring pixel
                    int ni = i + di;
                    int nj = j + dj;

                    // Check if the neighboring pixel is within the bounds of the image
                    if (ni >= 0 && ni < height && nj >= 0 && nj < width)
                    {
                        totalRed += temp[ni][nj].rgbtRed;
                        totalGreen += temp[ni][nj].rgbtGreen;
                        totalBlue += temp[ni][nj].rgbtBlue;

                        // Increment the count of pixels
                        counter++;
                    }
                }
            }

            // Calculate the average color values and assign them to the pixel in the original image
            image[i][j].rgbtRed = round(totalRed / (float) counter);
            image[i][j].rgbtGreen = round(totalGreen / (float) counter);
            image[i][j].rgbtBlue = round(totalBlue / (float) counter);
        }
    }
}

Recover

フォレンジックイメージからJPEGを復元するプログラムを作成します。

フォレンジックイメージとは、犯罪捜査やセキュリティ調査の際に、オリジナルのデータストレージを変更することなく内容を詳細に調査するために取得される完全なデータのコピーのことを指します。

この課題では、メモリカードから消去されたJPEGファイルの回復を目指します。メモリカードのデータはcard.rawというファイル名で保存されていて、それがフォレンジックイメージとして利用されます。

以下は私が提出したコードです。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef uint8_t BYTE;

int main(int argc, char *argv[])
{
    // Check usage
    if (argc != 2)
    {
        printf("Usage: ./recover image\n");
        return 1;
    }

    // Open memory card
    FILE *file = fopen(argv[1], "r");
    if (!file)
    {
        printf("File could not be opened.\n");
        return 1;
    }

    FILE *img = NULL;

    BYTE buffer[512];
    char filename[8];
    int counter = 0;

    while (fread(buffer, sizeof(BYTE), 512, file) == 512)
    {
        // checks if start of img in buffer
        if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
        {
            // if start of img and first image
            // then begins writing a new image
            if (counter == 0)
            {
                sprintf(filename, "%03i.jpg", counter);
                img = fopen(filename, "w");
                fwrite(&buffer, sizeof(BYTE), 512, img);
                counter += 1;
            }
            // if start of img but not first image
            // then closes the image and begins writing new image
            else if (counter > 0)
            {
                fclose(img);
                sprintf(filename, "%03i.jpg", counter);
                img = fopen(filename, "w");
                fwrite(&buffer, sizeof(BYTE), 512, img);
                counter += 1;
            }
        }
        // if not start of new img
        // then it keeps on writing the image
        else if (counter > 0)
        {
            fwrite(&buffer, sizeof(BYTE), 512, img);
        }
    }

    // Close file
    fclose(file);
    if (img != NULL)
    {
        fclose(img);
    }

    return 0;
}

少し解説を加えます。

入力チェック

    if (argc != 2)
    {
        printf("Usage: ./recover image\n");
        return 1;
    }

ユーザーが正しい数の引数を提供しているかを確認します。正しくない場合はエラーメッセージを表示して終了します。

メモリカードを開く

FILE *file = fopen(argv[1], "r");

与えられたフォレンジックイメージ(メモリカードのデータ)を読み込むために開きます。

画像の復元

BYTE buffer[512];

メモリカードのデータを512バイトごとに読み込むためのバッファを定義しています。

while (fread(buffer, sizeof(BYTE), 512, file) == 512)

512バイト単位でフォレンジックイメージからデータを読み取ります。JPEGファイルのシグネチャ(開始バイト)が見つかった場合、新しいJPEGファイルを作成し、そのファイルにデータを書き込みます。

すべてのJPEGファイルの復元が完了したら、フォレンジックイメージを閉じます。


さいごに

メモリについて学びつつ、画像の仕組みについても学べるWeek4でした。
私はRecoverがお気に入りです。少し苦戦しましたが、課題に取り組んでいるときはとても楽しかったです。
CS50の旅も既に半分を過ぎました。引き続き、頑張っていきましょう!


この記事が気に入ったらサポートをしてみませんか?