Merge pull request #977 from homm/fast-stretch

Imaging.stretch optimization
This commit is contained in:
wiredfool 2014-11-09 10:47:46 -08:00
commit d781d56aa5
6 changed files with 270 additions and 207 deletions

View File

@ -1,6 +1,7 @@
""" """
Tests for ImagingCore.stretch functionality. Tests for ImagingCore.stretch functionality.
""" """
from itertools import permutations
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
@ -12,6 +13,9 @@ im = Image.open("Tests/images/hopper.ppm").copy()
class TestImagingStretch(PillowTestCase): class TestImagingStretch(PillowTestCase):
def stretch(self, im, size, f):
return im._new(im.im.stretch(size, f))
def test_modes(self): def test_modes(self):
self.assertRaises(ValueError, im.convert("1").im.stretch, self.assertRaises(ValueError, im.convert("1").im.stretch,
(15, 12), Image.ANTIALIAS) (15, 12), Image.ANTIALIAS)
@ -39,6 +43,42 @@ class TestImagingStretch(PillowTestCase):
self.assertEqual(r.mode, "RGB") self.assertEqual(r.mode, "RGB")
self.assertEqual(r.size, (764, 414)) self.assertEqual(r.size, (764, 414))
def test_endianness(self):
# Make an image with one colored pixel, in one channel.
# When stretched, that channel should be the same as a GS image.
# Other channels should be unaffected.
# The R and A channels should not swap, which is indicitive of
# an endianness issues.
samples = {
'blank': Image.new('L', (2, 2), 0),
'filled': Image.new('L', (2, 2), 255),
'dirty': Image.new('L', (2, 2), 0),
}
samples['dirty'].putpixel((1, 1), 128)
for f in [Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]:
# samples resized with current filter
resized = dict(
(name, self.stretch(ch, (4, 4), f))
for name, ch in samples.items()
)
for mode, channels_set in [
('RGB', ('blank', 'filled', 'dirty')),
('RGBA', ('blank', 'blank', 'filled', 'dirty')),
('LA', ('filled', 'dirty')),
]:
for channels in set(permutations(channels_set)):
# compile image from different channels permutations
im = Image.merge(mode, [samples[ch] for ch in channels])
stretched = self.stretch(im, (4, 4), f)
for i, ch in enumerate(stretched.split()):
# check what resized channel in image is the same
# as separately resized channel
self.assert_image_equal(ch, resized[channels[i]])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1612,46 +1612,20 @@ im_setmode(ImagingObject* self, PyObject* args)
static PyObject* static PyObject*
_stretch(ImagingObject* self, PyObject* args) _stretch(ImagingObject* self, PyObject* args)
{ {
Imaging imIn; Imaging imIn, imOut;
Imaging imTemp;
Imaging imOut;
int xsize, ysize; int xsize, ysize;
int filter = IMAGING_TRANSFORM_NEAREST; int filter = IMAGING_TRANSFORM_NEAREST;
if (!PyArg_ParseTuple(args, "(ii)|i", &xsize, &ysize, &filter)) if (!PyArg_ParseTuple(args, "(ii)|i", &xsize, &ysize, &filter))
return NULL; return NULL;
imIn = self->image; imIn = self->image;
/* two-pass resize: minimize size of intermediate image */ imOut = ImagingStretch(imIn, xsize, ysize, filter);
if ((Py_ssize_t) imIn->xsize * ysize < (Py_ssize_t) xsize * imIn->ysize) if ( ! imOut) {
imTemp = ImagingNew(imIn->mode, imIn->xsize, ysize);
else
imTemp = ImagingNew(imIn->mode, xsize, imIn->ysize);
if (!imTemp)
return NULL;
/* first pass */
if (!ImagingStretch(imTemp, imIn, filter)) {
ImagingDelete(imTemp);
return NULL; return NULL;
} }
imOut = ImagingNew(imIn->mode, xsize, ysize);
if (!imOut) {
ImagingDelete(imTemp);
return NULL;
}
/* second pass */
if (!ImagingStretch(imOut, imTemp, filter)) {
ImagingDelete(imOut);
ImagingDelete(imTemp);
return NULL;
}
ImagingDelete(imTemp);
return PyImagingNew(imOut); return PyImagingNew(imOut);
} }

View File

@ -78,25 +78,44 @@ static inline float bicubic_filter(float x)
static struct filter BICUBIC = { bicubic_filter, 2.0 }; static struct filter BICUBIC = { bicubic_filter, 2.0 };
Imaging
ImagingStretch(Imaging imOut, Imaging imIn, int filter)
{
/* FIXME: this is a quick and straightforward translation from a
python prototype. might need some further C-ification... */
static inline UINT8 clip8(float in)
{
int out = (int) in;
if (out >= 255)
return 255;
if (out <= 0)
return 0;
return (UINT8) out;
}
/* This is work around bug in GCC prior 4.9 in 64 bit mode.
GCC generates code with partial dependency which 3 times slower.
See: http://stackoverflow.com/a/26588074/253146 */
#if defined(__x86_64__) && defined(__SSE__) && \
! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900)
static float __attribute__((always_inline)) i2f(int v) {
float x;
__asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) );
return x;
}
#else
static float inline i2f(int v) { return (float) v; }
#endif
Imaging
ImagingStretchHorizontal(Imaging imIn, int xsize, int filter)
{
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
Imaging imOut;
struct filter *filterp; struct filter *filterp;
float support, scale, filterscale; float support, scale, filterscale;
float center, ww, ss, ymin, ymax, xmin, xmax; float center, ww, ss, ss0, ss1, ss2, ss3;
int xx, yy, x, y, b; int xx, yy, x, kmax, xmin, xmax;
float *k; int *xbounds;
float *k, *kk;
/* check modes */
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0)
return (Imaging) ImagingError_ModeError();
/* check filter */ /* check filter */
switch (filter) { switch (filter) {
@ -118,14 +137,8 @@ ImagingStretch(Imaging imOut, Imaging imIn, int filter)
); );
} }
if (imIn->ysize == imOut->ysize) { /* prepare for horizontal stretch */
/* prepare for horizontal stretch */ filterscale = scale = (float) imIn->xsize / xsize;
filterscale = scale = (float) imIn->xsize / imOut->xsize;
} else if (imIn->xsize == imOut->xsize) {
/* prepare for vertical stretch */
filterscale = scale = (float) imIn->ysize / imOut->ysize;
} else
return (Imaging) ImagingError_Mismatch();
/* determine support size (length of resampling filter) */ /* determine support size (length of resampling filter) */
support = filterp->support; support = filterp->support;
@ -136,172 +149,186 @@ ImagingStretch(Imaging imOut, Imaging imIn, int filter)
support = support * filterscale; support = support * filterscale;
/* maximum number of coofs */
kmax = (int) ceil(support) * 2 + 1;
/* coefficient buffer (with rounding safety margin) */ /* coefficient buffer (with rounding safety margin) */
k = malloc(((int) support * 2 + 10) * sizeof(float)); kk = malloc(xsize * kmax * sizeof(float));
if (!k) if ( ! kk)
return (Imaging) ImagingError_MemoryError(); return (Imaging) ImagingError_MemoryError();
xbounds = malloc(xsize * 2 * sizeof(int));
if ( ! xbounds) {
free(kk);
return (Imaging) ImagingError_MemoryError();
}
for (xx = 0; xx < xsize; xx++) {
k = &kk[xx * kmax];
center = (xx + 0.5) * scale;
ww = 0.0;
ss = 1.0 / filterscale;
xmin = (int) floor(center - support);
if (xmin < 0)
xmin = 0;
xmax = (int) ceil(center + support);
if (xmax > imIn->xsize)
xmax = imIn->xsize;
for (x = xmin; x < xmax; x++) {
float w = filterp->filter((x - center + 0.5) * ss) * ss;
k[x - xmin] = w;
ww += w;
}
for (x = 0; x < xmax - xmin; x++) {
if (ww != 0.0)
k[x] /= ww;
}
xbounds[xx * 2 + 0] = xmin;
xbounds[xx * 2 + 1] = xmax;
}
imOut = ImagingNew(imIn->mode, xsize, imIn->ysize);
if ( ! imOut) {
free(kk);
free(xbounds);
return NULL;
}
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->xsize == imOut->xsize) { /* horizontal stretch */
/* vertical stretch */ for (yy = 0; yy < imOut->ysize; yy++) {
for (yy = 0; yy < imOut->ysize; yy++) { if (imIn->image8) {
center = (yy + 0.5) * scale; /* 8-bit grayscale */
ww = 0.0; for (xx = 0; xx < xsize; xx++) {
ss = 1.0 / filterscale; xmin = xbounds[xx * 2 + 0];
/* calculate filter weights */ xmax = xbounds[xx * 2 + 1];
ymin = floor(center - support); k = &kk[xx * kmax];
if (ymin < 0.0) ss = 0.5;
ymin = 0.0; for (x = xmin; x < xmax; x++)
ymax = ceil(center + support); ss += i2f(imIn->image8[yy][x]) * k[x - xmin];
if (ymax > (float) imIn->ysize) imOut->image8[yy][xx] = clip8(ss);
ymax = (float) imIn->ysize;
for (y = (int) ymin; y < (int) ymax; y++) {
float w = filterp->filter((y - center + 0.5) * ss) * ss;
k[y - (int) ymin] = w;
ww = ww + w;
} }
if (ww == 0.0) } else
ww = 1.0; switch(imIn->type) {
else case IMAGING_TYPE_UINT8:
ww = 1.0 / ww; /* n-bit grayscale */
if (imIn->image8) { if (imIn->bands == 2) {
/* 8-bit grayscale */ for (xx = 0; xx < xsize; xx++) {
for (xx = 0; xx < imOut->xsize; xx++) { xmin = xbounds[xx * 2 + 0];
ss = 0.0; xmax = xbounds[xx * 2 + 1];
for (y = (int) ymin; y < (int) ymax; y++) k = &kk[xx * kmax];
ss = ss + imIn->image8[y][xx] * k[y - (int) ymin]; ss0 = ss1 = 0.5;
ss = ss * ww + 0.5; for (x = xmin; x < xmax; x++) {
if (ss < 0.5) ss0 += i2f((UINT8) imIn->image[yy][x*4 + 0]) * k[x - xmin];
imOut->image8[yy][xx] = 0; ss1 += i2f((UINT8) imIn->image[yy][x*4 + 3]) * k[x - xmin];
else if (ss >= 255.0)
imOut->image8[yy][xx] = 255;
else
imOut->image8[yy][xx] = (UINT8) ss;
}
} else
switch(imIn->type) {
case IMAGING_TYPE_UINT8:
/* n-bit grayscale */
for (xx = 0; xx < imOut->xsize*4; xx++) {
/* FIXME: skip over unused pixels */
ss = 0.0;
for (y = (int) ymin; y < (int) ymax; y++)
ss = ss + (UINT8) imIn->image[y][xx] * k[y-(int) ymin];
ss = ss * ww + 0.5;
if (ss < 0.5)
imOut->image[yy][xx] = (UINT8) 0;
else if (ss >= 255.0)
imOut->image[yy][xx] = (UINT8) 255;
else
imOut->image[yy][xx] = (UINT8) ss;
}
break;
case IMAGING_TYPE_INT32:
/* 32-bit integer */
for (xx = 0; xx < imOut->xsize; xx++) {
ss = 0.0;
for (y = (int) ymin; y < (int) ymax; y++)
ss = ss + IMAGING_PIXEL_I(imIn, xx, y) * k[y - (int) ymin];
IMAGING_PIXEL_I(imOut, xx, yy) = (int) ss * ww;
}
break;
case IMAGING_TYPE_FLOAT32:
/* 32-bit float */
for (xx = 0; xx < imOut->xsize; xx++) {
ss = 0.0;
for (y = (int) ymin; y < (int) ymax; y++)
ss = ss + IMAGING_PIXEL_F(imIn, xx, y) * k[y - (int) ymin];
IMAGING_PIXEL_F(imOut, xx, yy) = ss * ww;
}
break;
default:
ImagingSectionLeave(&cookie);
return (Imaging) ImagingError_ModeError();
}
}
} else {
/* horizontal stretch */
for (xx = 0; xx < imOut->xsize; xx++) {
center = (xx + 0.5) * scale;
ww = 0.0;
ss = 1.0 / filterscale;
xmin = floor(center - support);
if (xmin < 0.0)
xmin = 0.0;
xmax = ceil(center + support);
if (xmax > (float) imIn->xsize)
xmax = (float) imIn->xsize;
for (x = (int) xmin; x < (int) xmax; x++) {
float w = filterp->filter((x - center + 0.5) * ss) * ss;
k[x - (int) xmin] = w;
ww = ww + w;
}
if (ww == 0.0)
ww = 1.0;
else
ww = 1.0 / ww;
if (imIn->image8) {
/* 8-bit grayscale */
for (yy = 0; yy < imOut->ysize; yy++) {
ss = 0.0;
for (x = (int) xmin; x < (int) xmax; x++)
ss = ss + imIn->image8[yy][x] * k[x - (int) xmin];
ss = ss * ww + 0.5;
if (ss < 0.5)
imOut->image8[yy][xx] = (UINT8) 0;
else if (ss >= 255.0)
imOut->image8[yy][xx] = (UINT8) 255;
else
imOut->image8[yy][xx] = (UINT8) ss;
}
} else
switch(imIn->type) {
case IMAGING_TYPE_UINT8:
/* n-bit grayscale */
for (yy = 0; yy < imOut->ysize; yy++) {
for (b = 0; b < imIn->bands; b++) {
if (imIn->bands == 2 && b)
b = 3; /* hack to deal with LA images */
ss = 0.0;
for (x = (int) xmin; x < (int) xmax; x++)
ss = ss + (UINT8) imIn->image[yy][x*4+b] * k[x - (int) xmin];
ss = ss * ww + 0.5;
if (ss < 0.5)
imOut->image[yy][xx*4+b] = (UINT8) 0;
else if (ss >= 255.0)
imOut->image[yy][xx*4+b] = (UINT8) 255;
else
imOut->image[yy][xx*4+b] = (UINT8) ss;
} }
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 3] = clip8(ss1);
} }
break; } else if (imIn->bands == 3) {
case IMAGING_TYPE_INT32: for (xx = 0; xx < xsize; xx++) {
/* 32-bit integer */ xmin = xbounds[xx * 2 + 0];
for (yy = 0; yy < imOut->ysize; yy++) { xmax = xbounds[xx * 2 + 1];
ss = 0.0; k = &kk[xx * kmax];
for (x = (int) xmin; x < (int) xmax; x++) ss0 = ss1 = ss2 = 0.5;
ss = ss + IMAGING_PIXEL_I(imIn, x, yy) * k[x - (int) xmin]; for (x = xmin; x < xmax; x++) {
IMAGING_PIXEL_I(imOut, xx, yy) = (int) ss * ww; ss0 += i2f((UINT8) imIn->image[yy][x*4 + 0]) * k[x - xmin];
ss1 += i2f((UINT8) imIn->image[yy][x*4 + 1]) * k[x - xmin];
ss2 += i2f((UINT8) imIn->image[yy][x*4 + 2]) * k[x - xmin];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 1] = clip8(ss1);
imOut->image[yy][xx*4 + 2] = clip8(ss2);
} }
break; } else {
case IMAGING_TYPE_FLOAT32: for (xx = 0; xx < xsize; xx++) {
/* 32-bit float */ xmin = xbounds[xx * 2 + 0];
for (yy = 0; yy < imOut->ysize; yy++) { xmax = xbounds[xx * 2 + 1];
ss = 0.0; k = &kk[xx * kmax];
for (x = (int) xmin; x < (int) xmax; x++) ss0 = ss1 = ss2 = ss3 = 0.5;
ss = ss + IMAGING_PIXEL_F(imIn, x, yy) * k[x - (int) xmin]; for (x = xmin; x < xmax; x++) {
IMAGING_PIXEL_F(imOut, xx, yy) = ss * ww; ss0 += i2f((UINT8) imIn->image[yy][x*4 + 0]) * k[x - xmin];
ss1 += i2f((UINT8) imIn->image[yy][x*4 + 1]) * k[x - xmin];
ss2 += i2f((UINT8) imIn->image[yy][x*4 + 2]) * k[x - xmin];
ss3 += i2f((UINT8) imIn->image[yy][x*4 + 3]) * k[x - xmin];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 1] = clip8(ss1);
imOut->image[yy][xx*4 + 2] = clip8(ss2);
imOut->image[yy][xx*4 + 3] = clip8(ss3);
} }
break;
default:
ImagingSectionLeave(&cookie);
return (Imaging) ImagingError_ModeError();
} }
} break;
case IMAGING_TYPE_INT32:
/* 32-bit integer */
for (xx = 0; xx < xsize; xx++) {
xmin = xbounds[xx * 2 + 0];
xmax = xbounds[xx * 2 + 1];
k = &kk[xx * kmax];
ss = 0.0;
for (x = xmin; x < xmax; x++)
ss += i2f(IMAGING_PIXEL_I(imIn, x, yy)) * k[x - xmin];
IMAGING_PIXEL_I(imOut, xx, yy) = (int) ss;
}
break;
case IMAGING_TYPE_FLOAT32:
/* 32-bit float */
for (xx = 0; xx < xsize; xx++) {
xmin = xbounds[xx * 2 + 0];
xmax = xbounds[xx * 2 + 1];
k = &kk[xx * kmax];
ss = 0.0;
for (x = xmin; x < xmax; x++)
ss += IMAGING_PIXEL_F(imIn, x, yy) * k[x - xmin];
IMAGING_PIXEL_F(imOut, xx, yy) = ss;
}
break;
default:
ImagingSectionLeave(&cookie);
ImagingDelete(imOut);
free(kk);
free(xbounds);
return (Imaging) ImagingError_ModeError();
}
} }
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
free(kk);
free(xbounds);
return imOut;
}
free(k);
Imaging
ImagingStretch(Imaging imIn, int xsize, int ysize, int filter)
{
Imaging imTemp1, imTemp2, imTemp3;
Imaging imOut;
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0)
return (Imaging) ImagingError_ModeError();
/* two-pass resize, first pass */
imTemp1 = ImagingStretchHorizontal(imIn, xsize, filter);
if ( ! imTemp1)
return NULL;
/* transpose image once */
imTemp2 = ImagingTransposeToNew(imTemp1);
ImagingDelete(imTemp1);
if ( ! imTemp2)
return NULL;
/* second pass */
imTemp3 = ImagingStretchHorizontal(imTemp2, ysize, filter);
ImagingDelete(imTemp2);
if ( ! imTemp3)
return NULL;
/* transpose result */
imOut = ImagingTransposeToNew(imTemp3);
ImagingDelete(imTemp3);
if ( ! imOut)
return NULL;
return imOut; return imOut;
} }

View File

@ -178,6 +178,21 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
} }
Imaging
ImagingTransposeToNew(Imaging imIn)
{
Imaging imTemp = ImagingNew(imIn->mode, imIn->ysize, imIn->xsize);
if ( ! imTemp)
return NULL;
if ( ! ImagingTranspose(imTemp, imIn)) {
ImagingDelete(imTemp);
return NULL;
}
return imTemp;
}
Imaging Imaging
ImagingRotate180(Imaging imOut, Imaging imIn) ImagingRotate180(Imaging imOut, Imaging imIn)
{ {

View File

@ -72,3 +72,9 @@
#ifdef _MSC_VER #ifdef _MSC_VER
typedef signed __int64 int64_t; typedef signed __int64 int64_t;
#endif #endif
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#endif

View File

@ -291,8 +291,9 @@ extern Imaging ImagingRotate(
extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn);
extern Imaging ImagingStretch(Imaging imOut, Imaging imIn, int filter); extern Imaging ImagingStretch(Imaging imIn, int xsize, int ysize, int filter);
extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn); extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn);
extern Imaging ImagingTransposeToNew(Imaging imIn);
extern Imaging ImagingTransformPerspective( extern Imaging ImagingTransformPerspective(
Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1, Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1,
double a[8], int filter, int fill); double a[8], int filter, int fill);