Marshaling a Python PIL Image using SWIG

时光总嘲笑我的痴心妄想 提交于 2019-12-04 21:25:00

问题


I've got a library that takes in a very simple C image structure:

// Represents a one-channel 8-bit image
typedef struct simple_image_t {
    uint32 rows;
    uint32 cols;
    uint8 *imgdata;
} simple_image;

I didn't create this library, nor this structure, so I can't change it. I'm responsible for wrapping this library for python using SWIG. The Python wrapper needs to be able to take in a PIL Image and convert it into this structure. Here's how I'm doing it right now (using a SWIG %inline%):

// Allows python to easily create and initialize this structure
simple_image* py_make_simple_image(uint32 width, uint32 height)
{
    simple_image* img = new simple_image();
    img->rows = height;
    img->cols = width;
    img->imgdata = new uint8[height * width];

    return img;
}

// Allows python to set a particular pixel value
void py_set_simple_image(simple_image* img, uint32 pos, uint8 val)
{
    img->imgdata[pos] = val;
}

And then on the python wrapper side here's how things look right now:

# Make sure it's an 8-bit image
if pil_image.mode != "L":
    pil_image = pil_image.convert("L")

# Create the simple image structure
(width, height) = pil_image.size
img = swig_wrapper.py_make_simple_image(width, height)

try:
    # Copy the image data into the simple image structure
    pos = 0
    for pixel in pil_image.getdata():
        swig_wrapper.py_set_simple_image(img, pos, pixel)
        pos += 1

    # Call some library method that accepts a simple_image*
    return swig_wrapper.some_image_method(img)

finally:
    # Clean up the simple image structure
    swig_wrapper.py_destroy_simple_image(img)

Amazingly this works, however as you may have guessed it's incredibly slow when working with even moderately large images. I know with SWIG the proper way to do things is to use a typemap, however that would mean digging in to the C API of PIL, and I just didn't have time to do that at the moment.

What are my options in terms of speed? Are there quicker ways of marshaling the pixel data from a PIL image to this simple image structure? Has someone already done this and my Google skills are just that bad? Am I just boned and soon will need to learn the internals of PIL?

Thanks.


回答1:


PIL's Image.tostring() returns a string of the exact data you need for imgdata. The typemap I used is fairly simple, but not perfect, which I'll note below. Here is the sample code I created on Windows that worked for me:

sample.h

typedef unsigned int uint32;
typedef unsigned char uint8;

typedef struct simple_image_t {
    uint32 rows;
    uint32 cols;
    uint8 *imgdata;
} simple_image;

#ifdef SAMPLE_EXPORT
#   define SAMPLE_API __declspec(dllexport)
#else
#   define SAMPLE_API __declspec(dllimport)
#endif

SAMPLE_API void some_func(const simple_image* si);

sample.c

#include <stdio.h>

#define SAMPLE_EXPORT
#include "sample.h"

void some_func(const simple_image* si)
{
    uint32 i,j;

    printf(
        "rows = %d\n"
        "cols = %d\n",
        si->rows,si->cols);

    /* Dump a simple map of the image data */
    for(i = 0; i < si->rows; i++)
    {
        for(j = 0; j < si->cols; j++)
        {
            if(si->imgdata[i * si->rows + j] < 0x80)
                printf(" ");
            else
                printf("*");
        }
        printf("\n");
    }
}

sample.i

%module sample

%begin %{
#pragma warning(disable:4100 4127 4706)
%}

%{
#include "sample.h"
%}

%include <windows.i>

%typemap(in) uint8* (char* buffer, Py_ssize_t length) {
    PyString_AsStringAndSize($input,&buffer,&length);
    $1 = (uint8*)buffer;
}

%include "sample.h"

makefile

all: _sample.pyd

sample.dll: sample.c sample.h
    cl /nologo /W4 /LD /MD sample.c

sample_wrap.c: sample.i
    @echo sample.i
    swig -python sample.i

_sample.pyd: sample_wrap.c sample.dll
    cl /nologo /W4 /LD /MD /Fe_sample.pyd sample_wrap.c /Ic:\Python27\include -link /LIBPATH:c:\Python27\libs python27.lib sample.lib

example.py

from PIL import Image
import sample

im = Image.open('sample.gif')
im = im.convert('L')
si = sample.simple_image()
si.rows,si.cols = im.size
s = im.tostring() # Must keep a reference 
si.imgdata = s
sample.some_func(si)

With this quick example I haven't determined how the typemap should correctly increment the reference count of the string object. Note that the above code could crash if the following code were used:

si.imgdata = im.tostring()

The current typemap's PyString_AsStringAndSize returns a direct pointer to the PyString object's buffer, but doesn't increment the reference count for the object. It can be garbage collected before some_func executes (and was for me, crashing Python). Assigning to s keeps a reference to the string and prevents problems. The typemap should copy the buffer, but you were looking for speed so this hack may be what you want.




回答2:


May be you could convert the image to a char array using the array module, and then, from swig, memcpy the data to your C array.

import array
imagar = array.array('B', pil_image.getdata())
(mem, length) = imagar.buffer_info()
swig_wrapper.py_copy(img, mem, length)

being py_copy something like:

void py_copy(simple_image* img, uint32 mem, uint32 length) {
   memcpy((void*)img->imgdata ,(void*)mem, length );
}



回答3:


How about using ctypes? It allows you to have direct access to c structures, so no need to create a Python equivalent of the struct, and you should also be able to do a memcpy (which would be faster than copying pixel by pixel).



来源:https://stackoverflow.com/questions/6480862/marshaling-a-python-pil-image-using-swig

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!