PythonとCとの連携(ctypes活用)～画像ファイルの生成～

## はじめに

Qiitaへの投稿、久しぶりになりました。

私は、最近(一昨年ぐらい?)、Pythonを少し覚えて、まだまだ初心者ですが、業務で使用するツール的プログラムをちょこちょこと作成しています。

で、私は、**C**のプログラマー歴は一応長いので、

* Cで書いた方が早い、とか、
* Pythonの変数って実際にはどうメモリ上に配置されているんだろう？

などと、よく考えることがありました。そして、今回、Pythonに、**ctypes**というライブラリがあって、結構、便利に使えそうだと気づきましたので、ちょっと試してみました。それをこの場にシェアしたいと思います。

## 画像ファイルの生成

今回、垂直カラーバーを一例に、画像ファイルを生成するプログラムを書いてみました。カラーバーのイメージを作っているのは、C言語による関数**get_color_bar_image**で、それをPythonプログラムから呼び出すことで、その画像を取り扱っています。

### C側

```c
unsigned char *get_color_bar_image(unsigned int *pHeight, unsigned int *pWidth)
{
  *pWidth = IMG_WIDTH;
  *pHeight = IMG_HEIGHT;

  return generate_color_bar_image(IMG_WIDTH, IMG_HEIGHT, NUM_OF_COLOR_BARS, colors_of_color_bars, kFalse, kRGB_Order);
}
```

公開用の関数**get_color_bar_image**は、さらにサブ関数**generate_color_bar_image**を呼び出す形としていて、このサブ関数内で、実際のカラーバー生成の処理を記述しています。

```c
static unsigned char *generate_color_bar_image(int width, int height, int num_of_color_bars, void *table, int pixel_interleaved, int color_order)
{
  int	i, j, k;
  unsigned char	*red, *green, *blue, *image;
  unsigned char *color_bar_table = (unsigned char *)table;
  
  image = (unsigned char *)malloc(width * height * IMG_COLOR_FACTOR);

  if (!pixel_interleaved) {	// 面順次
    red = image;
    green = red + width * height;
    blue = green + width * height;
  } else if (color_order == kRGB_Order) {	// 点順次
    red = image;
    green = image + 1;
    blue = image + 2;
  } else {
    blue = image;
    green = image + 1;
    red = image + 2;
  }

  for (j = 0; j < height; j++) {
    for (k = 0; k < num_of_color_bars; k++) {
      for (i = 0; i < width / num_of_color_bars; i++) {
	*red = *color_bar_table;
	*green = *(color_bar_table + 1);
	*blue = *(color_bar_table + 2);
	if (!pixel_interleaved) {	// 面順次
	  red++;
	  green++;
	  blue++;
	} else {	// 点順次
	  red += 3;
	  green += 3;
	  blue += 3;
	}
      }
      color_bar_table += IMG_COLOR_FACTOR;
    }
    color_bar_table = (unsigned char *)table;
  }

  return image;
}
```

### Python側

関数**get_color_bar_image**が、共有ライブラリファイルが**libcolorbar.so**の中で定義されているとして、

```test.py
lib = ctypes.cdll.LoadLibrary("./libcolorbar.so")

lib.get_color_bar_image.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
lib.get_color_bar_image.restype = ctypes.c_void_p
```

と記述することで、関数**get_color_bar_image**の引数や返り値の型を明文化しています。そして、実際に、関数**get_color_bar_image**を使用するときは、

```test.py
    refHeight = ctypes.c_int32()
    refWidth = ctypes.c_int32()
    img_pointer = lib.get_color_bar_image(refHeight, refWidth)
```

という感じになります。

通常、C言語で関数**get_color_bar_image**を呼び出す場合は、引数や返り値の型を合わせて使うことになるわけですが、Pythonから使う場合は、上記のように、それらのインタフェース情報を指定することで、Pythonの世界で扱えるようになる、という風に言えるかと思います。

```py
        width = refWidth.value
        height = refHeight.value

        # ポインタからデータをバイト配列に変換
        buffer_size = width * height

        # ctypes.string_atを使用してポインタからデータを取得
        red_data = ctypes.string_at(img_pointer, buffer_size)
        green_data = ctypes.string_at(img_pointer + buffer_size, buffer_size)
        blue_data = ctypes.string_at(img_pointer + 2 * buffer_size, buffer_size)
```

そして、lib.get_color_bar_imageで得られたimg_pointerを用いて、ctypes.string_atを用いて、

* REDデータを変数red_dataに
* GREENデータを変数green_dataに
* BLUEデータを変数blue_dataに、

それぞれ、取得してやって、ここまでくれば、Pythonで画像を扱うことになれている人は、扱えますね。
あとは、Pythonの**Pillow**ライブラリの**frombytes**と**merge**を使って、一つの画像に統合して、動的に、ユーザに出力したい画像ファイルフォーマットの拡張子を指定させて、**save**を使えば簡単に画像ファイルが生成できてしまいます。

### C言語での画像ファイルの生成

C言語でも、BMPファイル、JPEGファイル、TIFFファイル、を生成する例を記述してみていますので、ご参考になれば、と思います。

BMPファイル生成は、自分でヘッダを用意して、画像の縦横サイズやデータ長をそのヘッダに埋め込みつつファイル化しています。

JPEGは[jpeglib](https://ja.wikipedia.org/wiki/Libjpeg)を、TIFFは[libtiff](https://ja.wikipedia.org/wiki/LibTIFF)を、それぞれ利用しています。

## 本サンプルプログラムの置き場所

https://github.com/MakotoA67/Python/tree/main/ctypes

現時点では、**Raspberry Pi 5**と**NVIDIA Jetson Orin Nano**にて動作を確認しています。

## リンク集

誰でもググるとすぐ見つかるリンクですが、一応、この場にて紹介させて下さい。

https://qiita.com/nabion/items/594fb3316583130a636e
https://qiita.com/laddge/items/fa15b180d206e4175af8

私も、これらの皆さんの記事は一応読んだ上で、今回、PythonとCとの連携を試したり、この記事を書いたりしています。

## おわりに

以上、PythonとCとを連携させたサンプルプログラムの紹介でした。

自分で作れるはっきりとした処理内容があって、それは**C**で記述しつつ、**Python**からは、その**C**での処理をお手軽に再利用しつつ、例えば、今回のように、よく知られたファイルフォーマットに変換して可視化する、みたいに、Pythonの環境にお任せしてさくっとやりたいことを実現する、そんなときに、この**Python**と**C**との連携は活躍しそうです

もう少し、いろいろと説明入れてみようと思っていたのですが、、、このレベルでの公開になってしまいました。

ちなみに、最近の若い人は、ネット上のサンプルプログラムを適当に再利用したり、今ならば、**ChatGPT**君にプログラム作ってもらったり、などとできますよね？私も時々お世話になっています。ですが、そういう便利な世の中になっても、なんでも丸投げしていると、知見が蓄積できないかと思いますので、自分のこだわる領域だけでも、自分で作ってみるのがいいんじゃないかな、と思っています。

以上、**Python**と**C**とで連携してやりたいことを実現する例として、画像ファイルの生成の実現例を紹介してみました。