From 36286927994173c8da577493201e5b01a6cbfd83 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 17 Jun 2011 14:50:14 +0100 Subject: [PATCH] add experimental ctypes Python binding start hacking on a new Python interface --- .gitignore | 1 + TODO | 6 +--- python/README | 11 ++++++++ python/finalize.py | 41 +++++++++++++++++++++++++++ python/test.py | 14 ++++++++++ python/vipsimage.py | 50 +++++++++++++++++++++++++++++++++ python/vipsobject.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 python/README create mode 100644 python/finalize.py create mode 100755 python/test.py create mode 100644 python/vipsimage.py create mode 100644 python/vipsobject.py diff --git a/.gitignore b/.gitignore index 7344c73e..d5ae75fc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ TAGS *.lo *.la *.pc +*.pyc .deps .libs aclocal.m4 diff --git a/TODO b/TODO index 6dc81085..ad3f0ebf 100644 --- a/TODO +++ b/TODO @@ -69,10 +69,6 @@ - some way for nip2 to get the vips bin area - - some way for nip2 to get EXEEXT - - - wrap a basic set of Magick filters - - need vips_image_invalidate_area() @@ -93,7 +89,7 @@ - get rid of a lot of the command-line programs, who wants to write a man page for batch_image_convert etc yuk -- can we make man pages for the API as well? probably not from googling a bit +- can we make man pages for the API as well? probably not from googling a bit diff --git a/python/README b/python/README new file mode 100644 index 00000000..67994184 --- /dev/null +++ b/python/README @@ -0,0 +1,11 @@ +Experimental Python binding using ctypes + +The current Python binding uses swig to wrap the C++ interface. This causes +problems on Windows, since compiling all of the DLLs correctly to work with +the various python binaries is painful, and makes the bainding very large. + +The idea here is to use ctypes to make a binding that's just Python and which +directly wraps the C API. The new vips8 C API is much more wrapper-friendly, +plus we can reuse parts of the gobject binding, so this should be possible. + + diff --git a/python/finalize.py b/python/finalize.py new file mode 100644 index 00000000..6103648d --- /dev/null +++ b/python/finalize.py @@ -0,0 +1,41 @@ +#!/usr/bin/python + +# Finalization with weakrefs +# This is designed for avoiding __del__. + + +import sys +import traceback +import logging +import weakref + +__author__ = "Benjamin Peterson " + +class OwnerRef(weakref.ref): + """A simple weakref.ref subclass, so attributes can be added.""" + pass + +def _run_finalizer(ref): + """Internal weakref callback to run finalizers""" + del _finalize_refs[id(ref)] + finalizer = ref.finalizer + item = ref.item + try: + finalizer(item) + except Exception as e: + logging.debug("Exception %s running %s", repr(e), format(finalizer)) + traceback.print_exc() + +_finalize_refs = {} + +def track(owner, item, finalizer): + """Register an object for finalization. + + ``owner`` is the the object which is responsible for ``item``. + ``finalizer`` will be called with ``item`` as its only argument when + ``owner`` is destroyed by the garbage collector. + """ + ref = OwnerRef(owner, _run_finalizer) + ref.item = item + ref.finalizer = finalizer + _finalize_refs[id(ref)] = ref diff --git a/python/test.py b/python/test.py new file mode 100755 index 00000000..9e8a5d93 --- /dev/null +++ b/python/test.py @@ -0,0 +1,14 @@ +#!/usr/bin/python + +import logging + +import vipsimage + +logging.basicConfig(level = logging.DEBUG) + +# should work +a = vipsimage.VipsImage('/home/john/pics/healthygirl.jpg') +a.write('x.png') + +# should raise an error +a = vipsimage.VipsImage('banana') diff --git a/python/vipsimage.py b/python/vipsimage.py new file mode 100644 index 00000000..804b3e30 --- /dev/null +++ b/python/vipsimage.py @@ -0,0 +1,50 @@ +#!/usr/bin/python + +"""This module wraps up libvips in a less awful interface. + +Author: J.Cupitt +GNU LESSER GENERAL PUBLIC LICENSE +""" + +import logging +import ctypes + +import vipsobject + +libvips = vipsobject.libvips + +vips_image_new = libvips.vips_image_new +vips_image_new.restype = vipsobject.check_pointer_return + +vips_image_new_from_file = libvips.vips_image_new_from_file +vips_image_new_from_file.restype = vipsobject.check_pointer_return +vips_image_new_from_file.argtypes = [ctypes.c_char_p] + +vips_image_new_mode = libvips.vips_image_new_mode +vips_image_new_mode.restype = vipsobject.check_pointer_return +vips_image_new_mode.argtypes = [ctypes.c_char_p, ctypes.c_char_p] + +vips_image_write = libvips.vips_image_write +vips_image_write.restype = vipsobject.check_int_return +vips_image_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + +class VipsImage(vipsobject.VipsObject): + """Manipulate a libvips image.""" + + def __init__(self, filename = None, mode = None): + logging.debug('vipsimage: init') + + vipsobject.VipsObject.__init__(self) + + if filename == None and mode == None: + self.vipsobject = vips_image_new() + elif filename != None and mode == None: + self.vipsobject = vips_image_new_from_file(filename) + else: + self.vipsobject = vips_image_new_mode(filename, mode) + + def write(self, filename): + vips_image_write(self.vipsobject, filename) + + + diff --git a/python/vipsobject.py b/python/vipsobject.py new file mode 100644 index 00000000..480ee4fa --- /dev/null +++ b/python/vipsobject.py @@ -0,0 +1,66 @@ +#!/usr/bin/python + +"""This module wraps up libvips in a less awful interface. + +Wrap VipsObject. + +Author: J.Cupitt +GNU LESSER GENERAL PUBLIC LICENSE +""" + +import logging +import sys +import ctypes + +import finalize + +# .15 is 7.25+ with the new vips8 API +libvips = ctypes.CDLL('libvips.so.15') +libvips.vips_init(sys.argv[0]) + +class Error(Exception): + + """An error from libvips. + + message -- a high-level description of the error + detail -- a string with some detailed diagnostics + """ + + def __init__(self, message): + self.message = message + self.detail = vips_error_buffer() + libvips.vips_error_clear() + + logging.debug('vipsobject: Error %s %s', self.message, self.detail) + + def __str__(self): + return '%s - %s' %(self.message, self.detail) + +def check_int_return(value): + if value != 0: + raise Error('Error calling vips function.') + return value + +def check_pointer_return(value): + if value == None: + raise Error('Error calling vips function.') + return value + +vips_error_buffer = libvips.vips_error_buffer +vips_error_buffer.restype = ctypes.c_char_p + +class VipsObject: + """Abstract base class for libvips.""" + + def unref_vips(self): + if self.vipsobject != None: + libvips.g_object_unref(self.vipsobject) + self.vipsobject = None + + def __init__(self): + logging.debug('vipsobject: init') + + self.vipsobject = None + finalize.track(self, self, self.unref_vips) + +