Skip to content

Commit 609af32

Browse files
committed
Add extension module to unpremultiply and swizzle pixels
Avoid the multiple passes over the data necessary when doing the conversion through PIL.
1 parent 6786074 commit 609af32

File tree

3 files changed

+140
-12
lines changed

3 files changed

+140
-12
lines changed

openslide/_convert.c

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* openslide-python - Python bindings for the OpenSlide library
3+
*
4+
* Copyright (c) 2015 Carnegie Mellon University
5+
*
6+
* This library is free software; you can redistribute it and/or modify it
7+
* under the terms of version 2.1 of the GNU Lesser General Public License
8+
* as published by the Free Software Foundation.
9+
*
10+
* This library is distributed in the hope that it will be useful, but
11+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13+
* License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this library; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#include <Python.h>
21+
22+
typedef unsigned char u8;
23+
24+
#ifdef WORDS_BIGENDIAN
25+
#define CA 0
26+
#define CR 1
27+
#define CG 2
28+
#define CB 3
29+
#else
30+
#define CB 0
31+
#define CG 1
32+
#define CR 2
33+
#define CA 3
34+
#endif
35+
36+
static void
37+
argb2rgba(u8 *buf, Py_ssize_t len)
38+
{
39+
Py_ssize_t cur;
40+
41+
for (cur = 0; cur < len; cur += 4) {
42+
u8 a = buf[cur + CA];
43+
u8 r = buf[cur + CR];
44+
u8 g = buf[cur + CG];
45+
u8 b = buf[cur + CB];
46+
if (a != 0 && a != 255) {
47+
r = r * 255 / a;
48+
g = g * 255 / a;
49+
b = b * 255 / a;
50+
}
51+
buf[cur + 0] = r;
52+
buf[cur + 1] = g;
53+
buf[cur + 2] = b;
54+
buf[cur + 3] = a;
55+
}
56+
}
57+
58+
// Takes one argument: a contiguous buffer object. Modifies it in place.
59+
static PyObject *
60+
_convert_argb2rgba(PyObject *self, PyObject *args)
61+
{
62+
PyObject *ret = NULL;
63+
Py_buffer view;
64+
65+
if (!PyArg_ParseTuple(args, "s*", &view))
66+
return NULL;
67+
if (!PyBuffer_IsContiguous(&view, 'A')) {
68+
PyErr_SetString(PyExc_ValueError, "Argument is not contiguous");
69+
goto DONE;
70+
}
71+
if (view.readonly) {
72+
PyErr_SetString(PyExc_ValueError, "Argument is not writable");
73+
goto DONE;
74+
}
75+
if (view.len % 4) {
76+
PyErr_SetString(PyExc_ValueError, "Argument has invalid size");
77+
goto DONE;
78+
}
79+
80+
Py_BEGIN_ALLOW_THREADS
81+
argb2rgba(view.buf, view.len);
82+
Py_END_ALLOW_THREADS
83+
84+
Py_INCREF(Py_None);
85+
ret = Py_None;
86+
87+
DONE:
88+
PyBuffer_Release(&view);
89+
return ret;
90+
}
91+
92+
static PyMethodDef ConvertMethods[] = {
93+
{"argb2rgba", _convert_argb2rgba, METH_VARARGS,
94+
"Convert aRGB to RGBA in place."},
95+
{NULL, NULL, 0, NULL}
96+
};
97+
98+
#if PY_MAJOR_VERSION >= 3
99+
static struct PyModuleDef convertmodule = {
100+
PyModuleDef_HEAD_INIT,
101+
"_convert",
102+
NULL,
103+
0,
104+
ConvertMethods
105+
};
106+
107+
PyMODINIT_FUNC
108+
PyInit__convert(void)
109+
{
110+
return PyModule_Create(&convertmodule);
111+
}
112+
#else
113+
PyMODINIT_FUNC
114+
init_convert(void)
115+
{
116+
Py_InitModule("_convert", ConvertMethods);
117+
}
118+
#endif

openslide/lowlevel.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@
5454
else:
5555
_lib = cdll.LoadLibrary('libopenslide.so.0')
5656

57+
try:
58+
from . import _convert
59+
def _load_image(buf, size):
60+
'''buf must be a mutable buffer.'''
61+
_convert.argb2rgba(buf)
62+
return PIL.Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
63+
except ImportError:
64+
def _load_image(buf, size):
65+
'''buf can be a string, but should be a ctypes buffer to avoid an
66+
extra copy in the caller.'''
67+
# First reorder the bytes in a pixel from native-endian aRGB to
68+
# big-endian RGBa to work around limitations in RGBa loader
69+
rawmode = (sys.byteorder == 'little') and 'BGRA' or 'ARGB'
70+
buf = PIL.Image.frombuffer('RGBA', size, buf, 'raw', rawmode, 0,
71+
1).tostring()
72+
# Now load the image as RGBA, undoing premultiplication
73+
return PIL.Image.frombuffer('RGBA', size, buf, 'raw', 'RGBa', 0, 1)
74+
5775
class OpenSlideError(Exception):
5876
"""An error produced by the OpenSlide library.
5977
@@ -161,17 +179,6 @@ def _func(name, restype, argtypes, errcheck=_check_error):
161179
func.errcheck = errcheck
162180
return func
163181

164-
def _load_image(buf, size):
165-
'''buf can be a string, but should be a ctypes buffer to avoid an extra
166-
copy in the caller.'''
167-
# First reorder the bytes in a pixel from native-endian aRGB to
168-
# big-endian RGBa to work around limitations in RGBa loader
169-
rawmode = (sys.byteorder == 'little') and 'BGRA' or 'ARGB'
170-
buf = PIL.Image.frombuffer('RGBA', size, buf, 'raw', rawmode, 0,
171-
1).tostring()
172-
# Now load the image as RGBA, undoing premultiplication
173-
return PIL.Image.frombuffer('RGBA', size, buf, 'raw', 'RGBa', 0, 1)
174-
175182
try:
176183
detect_vendor = _func('openslide_detect_vendor', c_char_p, [_utf8_p],
177184
_check_string)

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from setuptools import setup
2+
from setuptools import setup, Extension
33

44
# Load version string
55
_verfile = os.path.join(os.path.dirname(__file__), 'openslide', '_version.py')
@@ -12,6 +12,9 @@
1212
packages=[
1313
'openslide',
1414
],
15+
ext_modules=[
16+
Extension('openslide._convert', ['openslide/_convert.c']),
17+
],
1518
maintainer='OpenSlide project',
1619
maintainer_email='[email protected]',
1720
description='Python interface to OpenSlide',

0 commit comments

Comments
 (0)