The benchmark test was a transformation from rectangular to polar co-ordinates. Input image was 3328×1664 JPEG (~480kB), output was 3327×3327 JPEG (all methods produced ~790kB).
Python
I implemented it purely in Python first (using the Python Imaging Library PIL.Image module), but it took ages for higher resolution images.
from PIL import Image
def RectToPolar(oldImg):
w, h = oldImg.size
oldPixels = oldImg.load()
...
diameter = 2 * h - 1
newImg = Image.new("RGB", (diameter, diameter), bg)
newPixels = newImg.load()
...
for x in range(diameter):
for y in range(diameter):
...
newPixels[x, y] = oldPixels[s, (h-1)-r]
return newImg
original = Image.open("../Home.jpg")
warped = RectToPolar(original)
warped.save("warped.jpg")
Python and C++ (Boost)
I decided to try using Boost’s Python library, so I could write the actual processing code in C++ for improved performance. I originally tried loading the image in Python and passing the pixel data to and from C++ as a string. I expected to cop a performance hit doing this, but the Python libraries for loading an image (with pixel access) are really nice and simple, so I gave it a shot.
class ImageMapper
{
public:
ImageMapper(std::string pixelStr, int width, int height);
...
std::string rectToPolar()
{
std::string result;
for (int y = max; y >= min; y--)
{
for (int x = min; x <= max; x++)
{
...
result += pixel(u, v);
}
}
return result;
}
};
BOOST_PYTHON_MODULE(imagemap)
{
class_<ImageMapper>("ImageMapper", init<std::string, int, int>())
.def("pixel", &ImageMapper::pixel)
.def("rectToPolar", &ImageMapper::rectToPolar)
;
}
from PIL import Image
import imagemap
img1 = Image.open("../Home.jpg")
w, h = img1.size
mapper = imagemap.ImageMapper(img1.tostring(), w, h)
data = mapper.rectToPolar()
img2 = Image.fromstring("RGB", (3327, 3327), data)
img2.save("warped.jpg")
To compile against the Boost Python libraries:
g++ -Wall -O3 imagemap.cpp -shared -o imagemap.so -I/usr/include -I/usr/include/python2.5 -L/usr/lib -lpython2.5 -lboost_python
C++ and GTK/gtkmm
Finally, I tried loading, processing and saving the image purely in C++, using GTK and gtkmm (GTK wrapped up in C++).
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
...
class ImageMapper
{
private:
GdkPixbuf *original_;
GdkPixbuf *warped_;
static guchar *get_pixel(GdkPixbuf *pixbuf, int x, int y)
{
return gdk_pixbuf_get_pixels(pixbuf) +
y * gdk_pixbuf_get_rowstride(pixbuf) +
x * gdk_pixbuf_get_n_channels(pixbuf);
}
public:
ImageMapper(const char *filename)
{
GError *error = NULL;
original_ = gdk_pixbuf_new_from_file(filename, &error);
if (original_ == NULL)
throw runtime_error("Could not load image");
}
...
void rectToPolar()
{
warped_ = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8, diameter, diameter);
if (warped_ == NULL)
throw runtime_error("Unable to create output image");
for (int j = 0; j < diameter; j++)
{
for (int i = 0; i < diameter; i++)
{
memcpy(
get_pixel(warped_, i, j),
get_pixel(original_, u, v),
channels * sizeof(guchar));
}
}
}
void transform(const char *filename, const char *type)
{
rectToPolar();
GError *error = NULL;
gboolean result = gdk_pixbuf_save(warped_, filename, type,
&error, "quality", "75", NULL);
if (!result)
throw runtime_error("Could not save image");
}
};
int main(int argc, char *argv[])
{
gtk_init (&argc, &argv);
ImageMapper mapper = ImageMapper("../Home.jpg");
mapper.transform("warped.jpg", "jpeg");
return 0;
}
To compile in Linux, it was just a matter of:
g++ -Wall -O3 warpgtk.cpp -o warpgtk `pkg-config --cflags --libs gtk+-2.0`
There was just a few differences with gtkmm, namely…
Glib::RefPtr<Gdk::Pixbuf> original_;
Glib::RefPtr<Gdk::Pixbuf> warped_;
static guchar *get_pixel(Glib::RefPtr<Gdk::Pixbuf> &pixbuf, int x, int y)
{
return pixbuf->get_pixels() +
y * pixbuf->get_rowstride() +
x * pixbuf->get_n_channels();
}
original_ = Gdk::Pixbuf::create_from_file(filename);
warped_ = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, diameter, diameter);
And compiling: (might need to grab a few repository packages)
g++ -Wall -O3 warpgtkmm.cpp -o warpgtkmm `pkg-config gtkmm-2.4 --cflags --libs`
The Results
- 3.04s; C++ and GTK
- 3.53s; C++ and gtkmm
- 8.89s; C++ with Python data via Boost
- 32.9s; Python
So I’ll probably go with either GTK or gtkmm. The slight speed penalty is probably worth it for the C++iness.
Source of the article