Pergunta

Estou tentando expor um buffer de informações sobre pixels de imagem (RGBA de 32 bits) através da interface buffer Python 3.x. Depois de brincar bastante, consegui fazer isso funcionar assim:

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); 
}

E em Python, eu posso brincar com isso assim:

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

E isso funciona esplendidamente. No entanto, seria ótimo se eu pudesse trabalhar com o valor completo do pixel (int, 4 byte) em vez de bytes individuais, então modifiquei o buffer buscando assim:

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;
}

Na verdade, isso retorna os dados e eu posso lê -los corretamente, mas qualquer tentativa de atribuir um valor a ele agora falha!

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)

No caso 1, o erro me informa que 'int' does not support the buffer interface, o que é uma pena e um pouco confuso (especifiquei que o formato do buffer era "eu", afinal), mas posso lidar com isso. No caso 2 e 3, as coisas ficam realmente estranhas: ambos os casos me gime uma leitura do TypeError mismatching item sizes for "my.Image" and "bytes" (Onde my.Image é, obviamente, meu tipo de imagem)

Isso é muito confuso para mim, já que os dados que estou passando são obviamente do mesmo tamanho do que eu ganho desse elemento. Parece que os buffers simplesmente param de permitir a atribuição se o tamanho da itens for maior que 1. É claro que a documentação para essa interface é realmente escassa e examinando o código Python realmente não fornece exemplos de uso, por isso estou bastante preso. Estou perdendo algum pouco de documentação que afirma que "os buffers se tornam essencialmente inúteis quando itens size> 1", estou fazendo algo errado que não consigo ver, ou isso é um bug no python? (Teste contra 3.1.1)

Obrigado por qualquer insight que você possa dar sobre esse problema (reconhecidamente avançado)!

Foi útil?

Solução

Encontrei isso no código Python (em MemoryObject.c em objetos) na função memória_ass_sub:

/* 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;
}

Essa é a fonte dos dois últimos erros. Parece que o tamanho itens para mesmo MV [0] ainda não é igual a si mesmo.

Atualizar

Aqui está o que eu acho que está acontecendo. Quando você tenta atribuir algo no MV, ele chama Memory_ASS_SUB em Objects/MemoryObject.c, mas essa função leva apenas um pyobject como entrada. Este objeto é então alterado para um buffer dentro usando a função pyobject_getbuffer, embora no caso de MV [0] já seja um buffer (e o buffer que você deseja!). Meu palpite é que essa função pega o objeto e a transforma em um buffer simples de itense = 1, independentemente de já ser um buffer ou não. É por isso que você obtém os tamanhos de itens incompatíveis, mesmo para

mv[0] = mv[0]

O problema com a primeira tarefa,

mv [0] = 0xffffffffffffff

As hastes (eu acho) de verificar se o INT puder ser usado como um buffer, que atualmente não é configurado pelo que eu entendo.

Em outras palavras, o sistema de buffer não é capaz de lidar com tamanhos de itens maiores de 1. Não parece que está tão longe, mas levaria um pouco mais de trabalho do seu lado. Se você funcionar, provavelmente deve enviar as alterações de volta à distribuição principal do Python.

Outra atualização

O código de erro da sua primeira tentativa de atribuir o MV [0] decorre do int que falha no pyobject_checkBuffer quando pyobject_checkbuffer é chamado. Aparentemente, o sistema lida apenas com cópias de objetos bufferáveis. Parece que também deve ser alterado.

Conclusão

Atualmente, o sistema de buffer Python não pode lidar com itens com itense> 1, como você adivinhou. Além disso, ele não pode lidar com atribuições para um buffer de objetos não adquiríveis, como o INTS.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top