Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshaling a Python PIL Image using SWIG

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.

like image 990
Chris Eberle Avatar asked Jun 25 '11 22:06

Chris Eberle


2 Answers

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.

like image 77
Mark Tolonen Avatar answered Nov 07 '22 13:11

Mark Tolonen


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 );
}
like image 38
Samuel Avatar answered Nov 07 '22 13:11

Samuel