2

Python 3.x バッファー インターフェイスを介して画像ピクセル情報 (32 ビット RGBA) のバッファーを公開しようとしています。かなり遊んだ後、私はこれを次のように機能させることができました:

int Image_get_buffer(PyObject* self, Py_buffer* view, int flags)
{
    int img_len;
    void* img_bytes;

    // Do my image fetch magic
    get_image_pixel_data(self, &img_bytes, &img_len);

    // Let python fill my buffer
    PyBuffer_FillInfo(view, self, img_bytes, img_len, 0, flags); 
}

そしてPythonでは、次のように遊ぶことができます:

mv = memoryview(image)
print(mv[0]) # prints b'\x00'
mv[0] = b'\xFF' # set the first pixels red component to full
mx[0:4] = b'\xFF\xFF\xFF\xFF' # set the first pixel to white

そして、それは見事に機能します。ただし、個々のバイトではなく、完全なピクセル値 (int、4 バイト) で作業できれば素晴らしいので、バッファ フェッチを次のように変更しました。

int Image_get_buffer(PyObject* self, Py_buffer* view, int flags)
{
    int img_len;
    void* img_bytes;

    // Do my image fetch magic
    get_image_pixel_data(self, &img_bytes, &img_len);

    // Fill my buffer manually (derived from the PyBuffer_FillInfo source)
    Py_INCREF(self);
    view->readonly = 0;
    view->obj = self;
    view->buf = img_bytes;
    view->itemsize = 4;
    view->ndim = 1;
    view->len = img_len;
    view->suboffsets = NULL;

    view->format = NULL;
    if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
        view->format = "I";

    view->shape = NULL;
    if ((flags & PyBUF_ND) == PyBUF_ND)
    {
        Py_ssize_t shape[] = { (int)(img_len/4) };
        view->shape = shape;
    }

    view->strides = NULL;
    if((flags & PyBUF_STRIDED) == PyBUF_STRIDED)
    {
        Py_ssize_t strides[] = { 4 };
        view->strides = strides;
    }

    return 0;
}

これは実際にデータを返し、正しく読み取ることができますが、値を代入しようとすると失敗します!

mv = memoryview(image)
print(mv[0]) # prints b'\x00\x00\x00\x00'
mv[0] = 0xFFFFFFFF # ERROR (1)
mv[0] = b'\xFF\xFF\xFF\xFF' # ERROR! (2)
mv[0] = mv[0] # ERROR?!? (3)

ケース 1 の場合、エラーで が通知されますが'int' does not support the buffer interface、これは残念で少し混乱します (結局、バッファー形式が "I" であることを指定しました) が、対処できます。mismatching item sizes for "my.Image" and "bytes"ただし、2 と 3 の場合は非常に奇妙になりますmy.Image

私が渡すデータは明らかにその要素から得られるものと同じサイズであるため、これは私にとって非常に混乱します。itemsize が 1 より大きい場合、バッファは単純に代入を許可しないように見えます。もちろん、このインターフェイスのドキュメントは非常にまばらで、Python コードを熟読しても実際には使用例がまったく得られないため、かなり行き詰まっています。「itemsize > 1 の場合、バッファは本質的に役に立たなくなる」というドキュメントの一部が欠けているのでしょうか、何か間違ったことをしているのに見えないのでしょうか、それとも Python のバグですか? (3.1.1に対するテスト)

この(確かに高度な)問題についてあなたが与えることができる洞察に感謝します!

4

1 に答える 1

1

関数 memory_ass_sub の Python コード (Objects の memoryobject.c 内) でこれを見つけました。

/* XXX should we allow assignment of different item sizes
   as long as the byte length is the same?
   (e.g. assign 2 shorts to a 4-byte slice) */
if (srcview.itemsize != view->itemsize) {
    PyErr_Format(PyExc_TypeError,
        "mismatching item sizes for \"%.200s\" and \"%.200s\"", 
        view->obj->ob_type->tp_name, srcview.obj->ob_type->tp_name);
    goto _error;
}

これが、後の 2 つのエラーの原因です。mv[0] の itemsize でも、それ自体と等しくないようです。

アップデート

これが私が起こっていると思うことです。mv で何かを割り当てようとすると、Objects/memoryobject.c で memory_ass_sub が呼び出されますが、その関数は入力として PyObject しか取りません。このオブジェクトは、mv[0] の場合は既にバッファー (および必要なバッファー!) ですが、PyObject_GetBuffer 関数を使用して内部でバッファーに変更されます。私の推測では、この関数はオブジェクトを受け取り、それが既にバッファーであるかどうかに関係なく、itemsize=1 の単純なバッファーにします。そのため、アイテムのサイズが一致しない場合でも

mv[0] = mv[0]

最初の割り当ての問題は、

mv[0] = 0xFFFFFFFF

int をバッファーとして使用できるかどうかを確認することから (私が思うに) 発生します。現在、私が理解していることからはセットアップされていません。

つまり、バッファ システムは現在、1 より大きいサイズのアイテムを処理することができません。それほど遠くないようには見えませんが、ユーザー側でもう少し作業が必要になるでしょう。うまく動作するようになったら、変更をメインの Python ディストリビューションに提出する必要があります。

別のアップデート

最初に mv[0] を割り当てようとしたときのエラー コードは、PyObject_CheckBuffer が呼び出されたときに int が PyObject_CheckBuffer に失敗したことに起因します。どうやら、システムはバッファ可能なオブジェクトからのコピーのみを処理します。これも変更する必要があるようです。

結論

現在、Python バッファ システムは、ご想像のとおり、itemsize > 1 のアイテムを処理できません。また、int などのバッファリング不可能なオブジェクトからバッファへの代入を処理することもできません。

于 2010-02-19T16:02:02.320 に答える