はじめに
Qiitaへの投稿、久しぶりになりました。
私は、最近(一昨年ぐらい?)、Pythonを少し覚えて、まだまだ初心者ですが、業務で使用するツール的プログラムをちょこちょこと作成しています。
で、私は、Cのプログラマー歴は一応長いので、
- Cで書いた方が早い、とか、
- Pythonの変数って実際にはどうメモリ上に配置されているんだろう?
などと、よく考えることがありました。そして、今回、Pythonに、ctypesというライブラリがあって、結構、便利に使えそうだと気づきましたので、ちょっと試してみました。それをこの場にシェアしたいと思います。
画像ファイルの生成
今回、垂直カラーバーを一例に、画像ファイルを生成するプログラムを書いてみました。カラーバーのイメージを作っているのは、C言語による関数get_color_bar_imageで、それをPythonプログラムから呼び出すことで、その画像を取り扱っています。
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を呼び出す形としていて、このサブ関数内で、実際のカラーバー生成の処理を記述しています。
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の中で定義されているとして、
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を使用するときは、
refHeight = ctypes.c_int32()
refWidth = ctypes.c_int32()
img_pointer = lib.get_color_bar_image(refHeight, refWidth)
という感じになります。
通常、C言語で関数get_color_bar_imageを呼び出す場合は、引数や返り値の型を合わせて使うことになるわけですが、Pythonから使う場合は、上記のように、それらのインタフェース情報を指定することで、Pythonの世界で扱えるようになる、という風に言えるかと思います。
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を、TIFFはlibtiffを、それぞれ利用しています。
本サンプルプログラムの置き場所
現時点では、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とで連携してやりたいことを実現する例として、画像ファイルの生成の実現例を紹介してみました。