ライブラリ無しで画像生成

簡単そうだけど、OpenCVを使ったりbmpファイルのバイナリをいじったりと、地味にめんどくさい画像生成。ここではコンソールに文字を書くか、テキストファイルを書き込む機能さえあればどんな言語でも作れる画像ファイルを紹介する。CでもPythonでもなんでも。なんならScratchでも。
さっさと画像を生成して結果をチェックしたいときなんかにお勧め。
# PPM画像ファイル
Netpbmという団体が開発している画像フォーマット。これはバイナリを書くのではなく、ASCII文字だけで表現するからコンソールさえあれば作れちゃう。

https://en.wikipedia.org/wiki/Netpbm#File_formats

## 書き方
#はコメント。
```
P3 #PPMファイルであることを示す
2 3 #横2ピクセル、縦3ピクセル
255 #R,G,Bそれぞれ0から255までで示す
#ここから下、左上から右下に、改行じゃなくてスペースだけでもいいっぽい。
0 255 255
255 0 0
255 0 255
0 255 0
255 0 0
0 0 0
```
ピクセルはこの順番で、R,G,Bの順でスペース、改行で区切りながら書く。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610196/eb88ff75-0b3d-6aa8-6cbe-9add506c8d56.png)

### コードでの実装例
超適当、例えばimage.pyというpythonコードがあったとして、
```
print("P3")
print("2 3")
print("255")
print("0 255 255")
print("255 0 0")
print("255 0 255")
print("0 255 0")
print("255 0 0")
print("0 0 0")
```
こうあったとしよう。

## コンソールでファイルの書き込み
```
py image.py > image.ppm
```
```
> file.txt
```
これを語尾につけることで、テキストファイルにコンソールに出力された内容を書き込むことができる。これはプログラミング言語関係なく、ターミナル共通の機能。
これで同じフォルダ上にimage.ppmというファイルが生成される。メモ帳で中身を確認するといいだろう。

:::note alert
PowerShellで実行する際、そのままだとUTF-16で書き込まれてしまうので正常に読み込めるファイルが生成されない。
:::
コマンドプロンプトで実行するか、以下の記事を参考にpowershellでUTF-8で出力されるようにしよう。
もちろん、プログラミング言語にあるファイル書き込み機能を使っても問題ない。
https://zenn.dev/koh1nigeeee/articles/854c04fafa9c01

## ファイル読み取り
生成したはいいけど、画像として読み取る方法が今のところない。フォトで読み取れるわけもなく。
実際古いフォーマットだから読み取れるソフトはあまり多くない。でも頼りになる無料ソフト二つ、[FFmpeg](https://www.ffmpeg.org/)と[GIMP](https://www.gimp.org/)が対応しているから安心。
GIMPは重くて本末転倒なので割愛、FFmpegで読み込む方法を紹介する。
一応FFmpegのインストール方法すら分からない人用にこんな記事を置いていく。

https://note.com/xx_freezer_xx/n/n2c76640a9045

画像を確認するにはFFplayを使う。簡単だね。
```
ffplay -i image.ppm -vf "scale='300:-1':flags=neighbor"
```
小さい画像でもぼやけることなく画像をくっきり確認するためのフラグがついている。ウィンドウは長さ300にアップスケールされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610196/a6286ffc-5d07-b64d-d7ea-1770015b921a.png)
こんな風に見えるはず。
### ファイル変換
PNGにでも変換しよう。超簡単だね。
```
ffmpeg -i image.ppm image.png
```

## 透明度を追加
PPM+PGM(グレースケール画像)の組み合わせで画像に透明度を追加する。そのまま読み込める画像ではなくなっちゃうけどね。
同じくPGMもNetPBMの画像ファイルの一種。大まかな流れは同じ。
```
P2 # PGMファイルであることを示す
2 3
255 #グレースケール、明るさ0~255
0 #同じく改行じゃなくてスペースだけでもいいっぽい。
50
50
100
200
0
```
これもFFplayで確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610196/b3d69fe6-d49d-cf4c-d5ad-81a7180252f8.png)
PPMファイルに色を記録し、PGMファイルに透明度をグレースケールとして記録する。そして、次のFFmpegコマンドを実行して透明度付きのPNGファイルを生成する。
```
ffmpeg -i color.ppm -i alpha.pgm -filter_complex "[0:v][1:v]alphamerge" out.png
```
実行して、拡大してほかの画像の上に重ねてみた。左の物体がそれ。いいね。
![aha.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610196/516683f1-639d-5819-2f34-7b3f5ea806d0.png)

## 補足、実装例
こんな風に？
<details><summary>C++での実装例</summary>

```cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

void alpha(string *output, int Width, int Height);
void color(string *output, int Width, int Height);
int main()
{
    const int width = 200;
    const int height = 200;

    string col;
    string alp;
    //ポインタでからの文字列を渡してfstreamで書き出し
    color(&col, width, height);
    alpha(&alp, width, height);
    ofstream colorfile("color.ppm");
    colorfile << col;
    colorfile.close();
    ofstream alphafile("alpha.pgm");
    alphafile << alp;
    alphafile.close();
    return 0;
}
//PPMファイルの文字列を生成
void color(string *output, int Width, int Height)
{
    *output = "P3\n" + to_string(Width) + " " + to_string(Height) + "\n255";
    for (int i = 0; i < Height; i++)
    {
        for (int ii = 0; ii < Width; ii++)
        {
            *output += "\n" + to_string(i % 256) + " " + to_string(ii % 256) + " " + to_string((i + 128) % 256);
        }
    }
}
//PGMファイルの文字列を生成
void alpha(string *output, int Width, int Height)
{
    *output = "P2\n" + to_string(Width) + " " + to_string(Height) + "\n255\n";
    for (int i = 0; i < Height; i++)
    {
        for (int ii = 0; ii < Width; ii++)
        {
            *output += to_string((i + ii) % 256) + "\n";
        }
        *output += "\n";
    }
}
```

</details>

こんな画像になった。qiitaにアップロードしたら透明度が失われてしまう。。。
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610196/967331b6-4ab7-e9bd-c156-7053d2f456d8.png)

libavutilとかを使えばコードだけで完結しそうだけどそこまでしなくてもいいでしょう。