Merge pull request #2730 from uploadcare/fast-geometry

Faster Transposition
This commit is contained in:
wiredfool 2017-09-19 10:58:47 +01:00 committed by GitHub
commit 7541755aa5
5 changed files with 154 additions and 50 deletions

View File

@ -150,6 +150,7 @@ ROTATE_90 = 2
ROTATE_180 = 3 ROTATE_180 = 3
ROTATE_270 = 4 ROTATE_270 = 4
TRANSPOSE = 5 TRANSPOSE = 5
TRANSVERSE = 6
# transforms # transforms
AFFINE = 0 AFFINE = 0
@ -2173,8 +2174,8 @@ class Image(object):
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`, :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`, :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`,
:py:attr:`PIL.Image.TRANSPOSE`. :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`.
:returns: Returns a flipped or rotated copy of this image. :returns: Returns a flipped or rotated copy of this image.
""" """

View File

@ -2,7 +2,7 @@ import helper
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
ROTATE_270, TRANSPOSE) ROTATE_270, TRANSPOSE, TRANSVERSE)
class TestImageTranspose(PillowTestCase): class TestImageTranspose(PillowTestCase):
@ -108,6 +108,22 @@ class TestImageTranspose(PillowTestCase):
for mode in ("L", "RGB"): for mode in ("L", "RGB"):
transpose(mode) transpose(mode)
def test_tranverse(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(TRANSVERSE)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size[::-1])
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((y-2, 1)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"):
transpose(mode)
def test_roundtrip(self): def test_roundtrip(self):
im = self.hopper['L'] im = self.hopper['L']
@ -124,6 +140,12 @@ class TestImageTranspose(PillowTestCase):
im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM)) im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT)) im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1678,6 +1678,7 @@ _transpose(ImagingObject* self, PyObject* args)
case 2: /* rotate 90 */ case 2: /* rotate 90 */
case 4: /* rotate 270 */ case 4: /* rotate 270 */
case 5: /* transpose */ case 5: /* transpose */
case 6: /* transverse */
imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
break; break;
default: default:
@ -1705,6 +1706,9 @@ _transpose(ImagingObject* self, PyObject* args)
case 5: case 5:
(void) ImagingTranspose(imOut, imIn); (void) ImagingTranspose(imOut, imIn);
break; break;
case 6:
(void) ImagingTransverse(imOut, imIn);
break;
} }
return PyImagingNew(imOut); return PyImagingNew(imOut);

View File

@ -5,7 +5,8 @@
Rotating in chunks that fit in the cache can speed up rotation Rotating in chunks that fit in the cache can speed up rotation
8x on a modern CPU. A chunk size of 128 requires only 65k and is large enough 8x on a modern CPU. A chunk size of 128 requires only 65k and is large enough
that the overhead from the extra loops are not apparent. */ that the overhead from the extra loops are not apparent. */
#define ROTATE_CHUNK 128 #define ROTATE_CHUNK 512
#define ROTATE_SMALL_CHUNK 8
#define COORD(v) ((v) < 0.0 ? -1 : ((int)(v))) #define COORD(v) ((v) < 0.0 ? -1 : ((int)(v)))
#define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v))) #define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v)))
@ -26,30 +27,27 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define FLIP_LEFT_RIGHT(INT, image) \
for (y = 0; y < imIn->ysize; y++) { \
INT* in = imIn->image[y]; \
INT* out = imOut->image[y]; \
xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \
}
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) { if (imIn->image8) {
for (y = 0; y < imIn->ysize; y++) { FLIP_LEFT_RIGHT(UINT8, image8)
UINT8* in = (UINT8*) imIn->image8[y];
UINT8* out = (UINT8*) imOut->image8[y];
x = 0;
xr = imIn->xsize-1;
for (; x < imIn->xsize; x++, xr--)
out[xr] = in[x];
}
} else { } else {
for (y = 0; y < imIn->ysize; y++) { FLIP_LEFT_RIGHT(INT32, image32)
UINT32* in = (UINT32*) imIn->image32[y];
UINT32* out = (UINT32*) imOut->image32[y];
x = 0;
xr = imIn->xsize-1;
for (; x < imIn->xsize; x++, xr--)
out[xr] = in[x];
}
} }
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
#undef FLIP_LEFT_RIGHT
return imOut; return imOut;
} }
@ -84,6 +82,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, xr, xxsize, yysize; int x, y, xx, yy, xr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -92,15 +91,22 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_90(image) \ #define ROTATE_90(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
xr = imIn->xsize - 1 - x; \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++, xr--) { \ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
imOut->image[xr][yy] = imIn->image[yy][xx]; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yyy] = in[xxx]; \
} \
} \
} \ } \
} \ } \
} \ } \
@ -109,9 +115,9 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
ROTATE_90(image8) ROTATE_90(UINT8, image8)
else else
ROTATE_90(image32) ROTATE_90(INT32, image32)
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -126,6 +132,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, xxsize, yysize; int x, y, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -134,14 +141,21 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define TRANSPOSE(image) \ #define TRANSPOSE(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++) { \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
imOut->image[xx][yy] = imIn->image[yy][xx]; \ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yyy] = in[xxx]; \
} \
} \
} \ } \
} \ } \
} \ } \
@ -150,9 +164,9 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
TRANSPOSE(image8) TRANSPOSE(UINT8, image8)
else else
TRANSPOSE(image32) TRANSPOSE(INT32, image32)
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -162,6 +176,57 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
} }
Imaging
ImagingTransverse(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xr, yr, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize)
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
#define TRANSVERSE(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yr] = in[xxx]; \
} \
} \
} \
} \
} \
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
TRANSVERSE(UINT8, image8)
else
TRANSVERSE(INT32, image32)
ImagingSectionLeave(&cookie);
#undef TRANSVERSE
return imOut;
}
Imaging Imaging
ImagingRotate180(Imaging imOut, Imaging imIn) ImagingRotate180(Imaging imOut, Imaging imIn)
{ {
@ -175,20 +240,23 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_180(image)\ #define ROTATE_180(INT, image) \
for (y = 0; y < imIn->ysize; y++, yr--) {\ for (y = 0; y < imIn->ysize; y++, yr--) { \
xr = imIn->xsize-1;\ INT* in = imIn->image[y]; \
for (x = 0; x < imIn->xsize; x++, xr--)\ INT* out = imOut->image[yr]; \
imOut->image[y][x] = imIn->image[yr][xr];\ xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \
} }
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
yr = imIn->ysize-1; yr = imIn->ysize-1;
if (imIn->image8) if (imIn->image8) {
ROTATE_180(image8) ROTATE_180(UINT8, image8)
else } else {
ROTATE_180(image32) ROTATE_180(INT32, image32)
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -203,6 +271,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, yr, xxsize, yysize; int x, y, xx, yy, yr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -211,15 +280,22 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_270(image) \ #define ROTATE_270(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - y; \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (yy = y; yy < yysize; yy++, yr--) { \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++) { \ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
imOut->image[xx][yr] = imIn->image[yy][xx]; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yr] = in[xxx]; \
} \
} \
} \ } \
} \ } \
} \ } \
@ -228,9 +304,9 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
ROTATE_270(image8) ROTATE_270(UINT8, image8)
else else
ROTATE_270(image32) ROTATE_270(INT32, image32)
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);

View File

@ -289,8 +289,9 @@ extern Imaging ImagingRankFilter(Imaging im, int size, int rank);
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 ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]);
extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn); extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn);
extern Imaging ImagingTransverse(Imaging imOut, Imaging imIn);
extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]);
extern Imaging ImagingTransform( extern Imaging ImagingTransform(
Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1, Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1,
double *a, int filter, int fill); double *a, int filter, int fill);