C言語でオーバーライドしてみた！！！


# はじめに
> Method overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. ―[Method Overrideing, Wikipedia](https://w.wiki/A5EF)
>
> （抄訳）OOPにおいて関数のオーバーライドとは、ある親クラス配下の関数の実装を子クラスが提供すること、またその機能を指す。

オーバーライド（英：Override）といえばオブジェクト指向の概念じゃないか、C言語ではできないはず、そう思われてこの記事を開かれたことかと思います。

確かにその通り、しかし実際にできるのです。ああ、ネット民も笑覧あれ！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420671/2a14ed4d-2057-6d47-e87c-993ed1fbbfe0.png)
大川ぶくぶ『ポプテピピック』一巻より

# どうやって？

最近テトの勢いすごくないですか？ライアーダンサーにメズマライザーに…オーバーライドなんか特に！
<iframe width="560" height="315" src="https://www.youtube.com/embed/LLjfal8jCYI?si=EOxGDT7xGs_FtQqh" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

*僕は[ニコ動](https://www.nicovideo.jp/watch/sm43083649)の活動を応援しています。*

茶番は以上です。ということで **吉田夜世「オーバーライド」をOpneCVでAAにし、C言語でアニメーションにしてみたい** と思います。

# 手順

0. ~~動画をローカルに落とす~~
0. 一定時間で区切って画像にする
0. AAに変換
0. C言語で再生

:::note warn
Youtube、ニコ動から動画を落とす行為は、利用規約で禁じられています。
著作権的にもだいぶグレーです。営利なら特に。
その辺は十分注意してくださいね。
:::


ちなみに以下のようなファイル構造になっています。ソースを読みたい読者は適宜参照ください。
```
.
|-- asciiart.py
|-- runner.py
|-- pirnt.c
|-- dist/*
|-- frames/*
`-- video.mp4

2 directories, 7 files
```

<details><summary>asciiart.py（このファイルの該当部分を適宜切り抜いて話します。）</summary>

```Python
import math
from pathlib import Path

import cv2


class AsciiArt:
    def __init__(self, video_path: str, frames_folder: str, AAs_folder: str):
        self.__video_path = Path(video_path)
        self.__frames_folder = Path(frames_folder)
        self.__AAs_folder = Path(AAs_folder)

    def split_into_frame(self, fps=1):
        cap = cv2.VideoCapture(str(self.__video_path))
        frame_rate = int(cap.get(cv2.CAP_PROP_FPS) / fps)

        frame_cnt = 0
        while True:
            ret, frame = cap.read()

            if not ret:
                break

            if frame_cnt % frame_rate == 0:
                idx = str(math.floor(frame_cnt / frame_rate)).zfill(3)
                filename = f"frame_{idx}.jpg"
                cv2.imwrite(f"{self.__frames_folder}/{filename}", frame)

            frame_cnt += 1

        cap.release()

    def to_ascii(self, img_path: Path):
        img = cv2.resize(cv2.imread(str(img_path)), (100, 60))

        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        ascii_chars = [
            "@",
            "#",
            "S",
            "%",
            "?",
            "*",
            "+",
            ";",
            ":",
            ",",
            ".",
        ]

        ascii_art: list[str] = []

        for row in gray:
            row_chars = [
                ascii_chars[int(px / 255 * (len(ascii_chars) - 1))] for px in row
            ]

            ascii_art.append("".join(row_chars))

        self.__output_AA(img_path, ascii_art)

    def __output_AA(self, img_path: Path, ascii_art: list[str]):
        aa_path = Path(f"{self.__AAs_folder}/{img_path.stem}.txt")

        with open(aa_path, mode="w") as f:
            for line in ascii_art:
                f.write(f"{line}\n")
```

</details>

動画の編集…というか加工は勉強もかねて[OpenCV](https://opencv.org/)でやってみます。
言語はC++とかPythonとかJavaとかが使えるみたいですが、今回はPythonで行きます。
PythonもOpenCVも初めて使うのでおかしな箇所があればご指摘願います。


## 一定時間で区切って画像に切り出そう
```Python
def split_into_frame(self, fps=1):
    # Read the video:
    cap = cv2.VideoCapture(str(self.__video_path))
    frame_rate = int(cap.get(cv2.CAP_PROP_FPS) / fps)

    # Loop through the video and save frames:
    frame_cnt = 0
    while True:
        ret, frame = cap.read()

        if not ret:
            break

        if frame_cnt % frame_rate == 0:
            idx = str(math.floor(frame_cnt / frame_rate)).zfill(3)
            filename = f"frame_{idx}.jpg"
            cv2.imwrite(f"{self.__frames_folder}/{filename}", frame)

        frame_cnt += 1

    cap.release()
```


大まかな流れはコメントの通りです。
変数`fps`は任意の引数値により動的に決定されますが、指定されなかった場合には規定値1が使用されます。
また、`while loop`内で決定されている出力ファイル名`frame_[num].jpg`について、numは3桁の0埋めがなされており、後でファイルを参照するときに参照しやすいようにしています。

フォーマットは今回JPEGを使っていますが、ほかにも以下の形式が使えるようです。

https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html#ga288b8b3da0892bd651fce07b3bbd3a56

## 画像をAAに変換しよう
```Python
def to_ascii(self, img_path: Path):
    img = cv2.resize(cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE), (100, 60))

    ascii_chars = [使いたい文字]

    ascii_art: list[str] = []

    for row in img:
        row_chars = [
            ascii_chars[int(px / 255 * (len(ascii_chars) - 1))] for px in row
        ]

        ascii_art.append("".join(row_chars))

    self.__output_AA(img_path, ascii_art)

def __output_AA(self, img_path: Path, ascii_art: list[str]):
    aa_path = Path(f"{self.__AAs_folder}/{img_path.stem}.txt")

    with open(aa_path, mode="w") as f:
        for line in ascii_art:
            f.write(f"{line}\n")

```
画像を読み込むとき、`cv2.imread()`関数の第二引数に`cv2.IMREAD_GRAYSCALE`を指定するとモノクロにしてくれるようです。
画像を読み込んだとき帰ってくる値は、vscodeによれば`MatLike`なるオブジェクトなんですが、実態は多次元の`ndarray`なため、ループで回せます。

https://note.nkmk.me/python-opencv-imread-imwrite/

このプログラムでは、一段目のforが行を取ってきて、二つ目の内包表現がpxに分解、これを任意の文字に変換します。

ファイルへの出力については専用の関数に切り出しています。

---

実行するスクリプトを書いて、動かしてみます。

```Python
from convert import AsciiArt
import pathlib

aa = AsciiArt("video.mp4", "frames", "dist")
aa.split_into_frame(fps=3)

for f in pathlib.Path("frames").glob("*.jpg"):
    aa.to_ascii(f)
```

結構時間がかかりました。動画の長さにもよりますが、筆者環境では10秒以上はかかったかな。

するとインスタンス化に際して指定した通り、`frames`と`dist`フォルダができます。`frames`は画像が入っている、一時的につくったフォルダなので、もう不要です。

ほしいのは`dist`フォルダのtxtファイル。一つ開いてみると

<details><summary>長いので省略</summary>

```
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,??????:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,??%%%%%?::*SS%S?:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%,,,,,+S%%%%%%%%%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%%%%%%%%%%%%%%%%%%*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,S%%%%%%%%%%%%%%%%%%%%S,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%%SSSSSSSSSSSSS%%%%%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,SSSSSSSSSSSSSSS##S%S%%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,+SSSSSSSSSSSSSSSSSSSSS#S%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,SSSSSSSSS#S%%%%%%#SSSSSSS#%%%%S,,,,:??+,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%SSSSSSSSS%%%%%%%%%%%%%S#SSS#%%%%,,??;+??????,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,SSSSSSSSS#%%%%%%%%%%%%%%%%%%##S%%%%?%???????%?,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,+*+:,,:#SSSSSSSSS#%%%%%%%%%%%%%%%%%%%%S%%%?SSS##SS%???,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,???%%?#SSSSSSSS#%%%%???????????%%%%%%%S#S%%S%S%SS%??%?,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,?*%SS%SSSSSSSSS%%%???????????%?????????%%%S%#SS%??????,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,S??%SS%SSSSSSS%??????????????,??????????S%%S%????%???%,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,:???%S#S%#SS#????????????????,:????????????????%??????,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,*??%???SS%%%??????:??????????:;+%??%?????%?%%?????????;,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,*????????????????+,??????????,,,;????????????????????,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,;?%??????????????,,,?????????,,:?SS#@??????????????#S??,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,S???????%%??????,,:*????????;,S#%S?SS#???%?%????S%%??S,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,?SSS??????%??????,:,,%???????:%#S**#,%#??????#S%SSS????,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,*?%%%S#%??%??????,,;;,*??????,,?#SSS*,S#S??*%%SSS??%???,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,%????SSS%S%??????SS+%SS??????,,?#S%?,,??;,,?%%???%????,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,???%?????%??????#S,SSS*;,S?%?,,#??%;+*??+,:????????S%S,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,%%?????S???????SS,,?SSS#,,,*,:,,,,:+*+??*,,+S????%%???,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,+?%SSSSS%%?????S.,S%#?S,,,,,,,,,,::%????:,,#???%S??%%:,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,+??S??SS%%??????S,,S?+,,,,,,,,,,,,,S????%:,,**?%??%??*,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,+?S%%%S#???%??S??%:;:,,,,,,,,,,,,,:?;???%,,,,???????%:S:,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,:???S%SS%S*;;:,??*++*,,,,,,;,,,,,,,,????*,,,?:%??%SS???:,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,?%???????%,,,,???*?::,,,,,,,,,,,,:?*:??;:,,,;;::;S?%%%?%,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,:???????%%,,,%S?????;,,,,,,,,%?,,,,,??:,,,,,,,,%??%%?%???:,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,?*%S?????;,,,,,,????:,,,,,;????+,,,:,,,,,,,,,,,,:???%S?%+%;,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,???%????,,,,,,,:%?%?%+,,,?????:,,,,,;:*%,,,,,,,,,??*:?%?,?:,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,+??????%:,,,,,,,;??,,,,,,,,,,,,,,,,?,,::,,,,,,,,,,,,,,:,,%,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,*????S??:,,,,,,,??:;,,,,,,,,,,,,%,;;:,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,??????,,,,,,,,,??S,,+;,,,,,,,,?,;,,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,?SS%:,,,,,,,,,,%??,,,:S#,,,,,?,,,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,?%%;,,,,,,,,,,,,,?,:S%%%+**?++,?,,,,,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,?:,,,,,,,,,,,,,,,:*,,,,,++++++,,,,:,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,?%,,,,,,,,,,,,,,,,,,,,,,++++++,?,,,,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,++++++*,,,,,,,+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,++++++:,,,,,,,*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,*S,,,,,,,,,,,,,,,??%*++++++:,,,,,,?*%?;,,,,:,,,,,+,:S,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,:,:+#%,,,,,,,:+%%??*?SS%*%?++%,,,,+*%%?S***??#%S%,,%+:###,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,S%?++;#,,,,%*****S?*##S??S***,,,,,,,SS*******++***:%;:?S,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,#;?,,,+;+***S**;;**%**?SSS#SS#,,SS%+******%+*****?+*:,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,;*+*,,,%*++++#**+#*S;**%?*++;+,,+%?*;?****#+*******;*++,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,+S++,,,:?**??**#+**S+#******,,,,,,**+S***?+#+****;*+:#+,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,*%?+:,,,:*******+S****#******,,,,,,,#;****S?S+***;;**:,:,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,++,,,,;*******?************?,,,,,,;?******%+**;;;++%,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;;+*******************,,,,,,,:*********;;;;;;;,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,?;;;;?*%***************?,,,,,,,********;;;;;;;;S,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;;;;;++?***??****?******:+,,:,+,S*****;;;;;;;;;;,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,:;;;;;;;;**?******????**?**,,:,?:,?***;;+;;;;;;;;*,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,+;;;;;;;;;;*??****?*?*****%,;;,;,,,**+;;;;;;;;;;;;,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,;;;;;;;;;;;;;+**************,,,,,,,,,+;;;;;;;;;;*;;#,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,*;+;;;;;;;;;;;;**************,,,,,,,,,;;;;;;;;;;;;;;,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,?;;;;;;;;;;;;;;;;*************+,,,,,,,,,;;;;;;;;;;;;;,,,,,,,,,,,,,,,,,,,,,
```

</details>

いい感じですね。

## C言語で再生

Q. Pythonでよくね？
A. C使わないとタイトル詐欺なんですよね

```C
#include <stdio.h>
#include <unistd.h>

#define FILES 415
#define fps 3

int main(void)
{

    for (int i = 0; i <= FILES; i++)
    {
        // clear screen
        printf("%c[2J", 27);
        FILE *fp;
        char fname[21];
        sprintf(fname, "./dist/frame_%03d.txt", i);

        fp = fopen(fname, "r");
        if (fp == NULL)
        {
            printf("FAILED: %s can't open!\n", fname);
            return -1;
        }

        int chr;
        while ((chr = fgetc(fp)) != EOF)
        {
            putchar(chr);
        }
        printf("%s open\n", fname);

        // 1s = 1000000
        usleep(1000000 / fps);
        fclose(fp);
    }

    return 0;
}
```
しくみは割と単純で、ファイルを読み込み、表示して、一定時間止め、ファイルを閉じて、画面をクリアする、というのをファイルの枚数分繰り返しているだけです。

# [実際にやってみた](https://youtu.be/5KdoDP4OtOQ?si=P14GNb-CJJbl2h0Z)

https://youtu.be/wX4yroSwAYY

チカチカする…何というか改良の余地がありそうな感じですね。

# 後記
画像の読み込みからAA生成までほぼPythonなんでパッケージもできそうですね。

## 謝辞
吉田夜世さま、重音テトさまには深く感謝申し上げます。
ご助言いただいた諸先輩方同胞諸君にも御礼申し上げます。