Merge pull request #994 from homm/add-transpose

Add transpose and cache aware rotation
This commit is contained in:
wiredfool 2014-11-07 13:58:18 -08:00
commit cfbe49f124
5 changed files with 275 additions and 126 deletions

View File

@ -150,6 +150,7 @@ FLIP_TOP_BOTTOM = 1
ROTATE_90 = 2
ROTATE_180 = 3
ROTATE_270 = 4
TRANSPOSE = 5
# transforms
AFFINE = 0
@ -1920,13 +1921,13 @@ class Image:
: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.ROTATE_180`, or :py:attr:`PIL.Image.ROTATE_270`.
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
:py:attr:`PIL.Image.TRANSPOSE`.
:returns: Returns a flipped or rotated copy of this image.
"""
self.load()
im = self.im.transpose(method)
return self._new(im)
return self._new(self.im.transpose(method))
def effect_spread(self, distance):
"""

View File

@ -1,30 +1,114 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
FLIP_LEFT_RIGHT = Image.FLIP_LEFT_RIGHT
FLIP_TOP_BOTTOM = Image.FLIP_TOP_BOTTOM
ROTATE_90 = Image.ROTATE_90
ROTATE_180 = Image.ROTATE_180
ROTATE_270 = Image.ROTATE_270
from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
ROTATE_270, TRANSPOSE)
class TestImageTranspose(PillowTestCase):
def test_sanity(self):
hopper = {
'L': hopper('L').crop((0, 0, 121, 127)).copy(),
'RGB': hopper('RGB').crop((0, 0, 121, 127)).copy(),
}
im = hopper()
def test_flip_left_right(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(FLIP_LEFT_RIGHT)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size)
im.transpose(FLIP_LEFT_RIGHT)
im.transpose(FLIP_TOP_BOTTOM)
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((x-2, 1)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
im.transpose(ROTATE_90)
im.transpose(ROTATE_180)
im.transpose(ROTATE_270)
for mode in ("L", "RGB"):
transpose(mode)
def test_flip_top_bottom(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(FLIP_TOP_BOTTOM)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size)
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, y-2)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
for mode in ("L", "RGB"):
transpose(mode)
def test_rotate_90(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(ROTATE_90)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size[::-1])
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1)))
for mode in ("L", "RGB"):
transpose(mode)
def test_rotate_180(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(ROTATE_180)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size)
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, y-2)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"):
transpose(mode)
def test_rotate_270(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(ROTATE_270)
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, 1)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2)))
for mode in ("L", "RGB"):
transpose(mode)
def test_transpose(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(TRANSPOSE)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size[::-1])
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2)))
for mode in ("L", "RGB"):
transpose(mode)
def test_roundtrip(self):
im = hopper()
im = self.hopper['L']
def transpose(first, second):
return im.transpose(first).transpose(second)
@ -33,12 +117,13 @@ class TestImageTranspose(PillowTestCase):
im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
self.assert_image_equal(
im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1750,6 +1750,7 @@ _transpose(ImagingObject* self, PyObject* args)
break;
case 2: /* rotate 90 */
case 4: /* rotate 270 */
case 5: /* transpose */
imOut = ImagingNew(imIn->mode, imIn->ysize, imIn->xsize);
break;
default:
@ -1774,6 +1775,9 @@ _transpose(ImagingObject* self, PyObject* args)
case 4:
(void) ImagingRotate270(imOut, imIn);
break;
case 5:
(void) ImagingTranspose(imOut, imIn);
break;
}
return PyImagingNew(imOut);

View File

@ -30,11 +30,18 @@
/* Undef if you don't need resampling filters */
#define WITH_FILTERS
/* For large images rotation is an inefficient operation in terms of CPU cache.
One row in the source image affects each column in destination.
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
that the overhead from the extra loops are not apparent. */
#define ROTATE_CHUNK 128
#define COORD(v) ((v) < 0.0 ? -1 : ((int)(v)))
#define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v)))
/* -------------------------------------------------------------------- */
/* Transpose operations */
/* Transpose operations */
Imaging
ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
@ -43,25 +50,25 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
int x, y, xr;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize)
return (Imaging) ImagingError_Mismatch();
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
#define FLIP_HORIZ(image)\
#define FLIP_HORIZ(image)\
for (y = 0; y < imIn->ysize; y++) {\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[y][x] = imIn->image[y][xr];\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[y][x] = imIn->image[y][xr];\
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
FLIP_HORIZ(image8)
FLIP_HORIZ(image8)
else
FLIP_HORIZ(image32)
FLIP_HORIZ(image32)
ImagingSectionLeave(&cookie);
@ -76,9 +83,9 @@ ImagingFlipTopBottom(Imaging imOut, Imaging imIn)
int y, yr;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize)
return (Imaging) ImagingError_Mismatch();
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
@ -86,7 +93,7 @@ ImagingFlipTopBottom(Imaging imOut, Imaging imIn)
yr = imIn->ysize-1;
for (y = 0; y < imIn->ysize; y++, yr--)
memcpy(imOut->image[yr], imIn->image[y], imIn->linesize);
memcpy(imOut->image[yr], imIn->image[y], imIn->linesize);
ImagingSectionLeave(&cookie);
@ -98,28 +105,35 @@ Imaging
ImagingRotate90(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xr;
int x, y, xx, yy, xr, xxsize, yysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize)
return (Imaging) ImagingError_Mismatch();
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
#define ROTATE_90(image)\
for (y = 0; y < imIn->ysize; y++) {\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[xr][y] = imIn->image[y][x];\
#define ROTATE_90(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++) { \
xr = imIn->xsize - 1 - x; \
for (xx = x; xx < xxsize; xx++, xr--) { \
imOut->image[xr][yy] = imIn->image[yy][xx]; \
} \
} \
} \
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
ROTATE_90(image8)
ROTATE_90(image8)
else
ROTATE_90(image32)
ROTATE_90(image32)
ImagingSectionLeave(&cookie);
@ -127,6 +141,43 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
}
Imaging
ImagingTranspose(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xx, yy, xxsize, yysize;
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();
#define TRANSPOSE(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++) { \
for (xx = x; xx < xxsize; xx++) { \
imOut->image[xx][yy] = imIn->image[yy][xx]; \
} \
} \
} \
}
ImagingCopyInfo(imOut, imIn);
ImagingSectionEnter(&cookie);
if (imIn->image8)
TRANSPOSE(image8)
else
TRANSPOSE(image32)
ImagingSectionLeave(&cookie);
return imOut;
}
Imaging
ImagingRotate180(Imaging imOut, Imaging imIn)
{
@ -134,27 +185,27 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
int x, y, xr, yr;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize)
return (Imaging) ImagingError_Mismatch();
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
yr = imIn->ysize-1;
#define ROTATE_180(image)\
#define ROTATE_180(image)\
for (y = 0; y < imIn->ysize; y++, yr--) {\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[y][x] = imIn->image[yr][xr];\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[y][x] = imIn->image[yr][xr];\
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
ROTATE_180(image8)
ROTATE_180(image8)
else
ROTATE_180(image32)
ROTATE_180(image32)
ImagingSectionLeave(&cookie);
@ -166,28 +217,35 @@ Imaging
ImagingRotate270(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, yr;
int x, y, xx, yy, yr, xxsize, yysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize)
return (Imaging) ImagingError_Mismatch();
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
yr = imIn->ysize - 1;
#define ROTATE_270(image)\
for (y = 0; y < imIn->ysize; y++, yr--)\
for (x = 0; x < imIn->xsize; x++)\
imOut->image[x][y] = imIn->image[yr][x];
#define ROTATE_270(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; \
yr = imIn->ysize - 1 - y; \
for (yy = y; yy < yysize; yy++, yr--) { \
for (xx = x; xx < xxsize; xx++) { \
imOut->image[xx][yr] = imIn->image[yy][xx]; \
} \
} \
} \
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
ROTATE_270(image8)
ROTATE_270(image8)
else
ROTATE_270(image32)
ROTATE_270(image32)
ImagingSectionLeave(&cookie);
@ -196,7 +254,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
/* -------------------------------------------------------------------- */
/* Transforms */
/* Transforms */
/* transform primitives (ImagingTransformMap) */
@ -597,7 +655,7 @@ ImagingTransform(
double xx, yy;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
ImagingCopyInfo(imOut, imIn);
@ -613,15 +671,15 @@ ImagingTransform(
y1 = imOut->ysize;
for (y = y0; y < y1; y++) {
out = imOut->image[y] + x0*imOut->pixelsize;
for (x = x0; x < x1; x++) {
if (!transform(&xx, &yy, x-x0, y-y0, transform_data) ||
out = imOut->image[y] + x0*imOut->pixelsize;
for (x = x0; x < x1; x++) {
if (!transform(&xx, &yy, x-x0, y-y0, transform_data) ||
!filter(out, imIn, xx, yy, filter_data)) {
if (fill)
memset(out, 0, imOut->pixelsize);
}
out += imOut->pixelsize;
}
}
}
ImagingSectionLeave(&cookie);
@ -644,7 +702,7 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn,
int *xintab;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
ImagingCopyInfo(imOut, imIn);
@ -659,8 +717,8 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn,
xintab = (int*) malloc(imOut->xsize * sizeof(int));
if (!xintab) {
ImagingDelete(imOut);
return (Imaging) ImagingError_MemoryError();
ImagingDelete(imOut);
return (Imaging) ImagingError_MemoryError();
}
xo = a[0];
@ -671,29 +729,29 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn,
/* Pretabulate horizontal pixel positions */
for (x = x0; x < x1; x++) {
xin = COORD(xo);
if (xin >= 0 && xin < (int) imIn->xsize) {
xmax = x+1;
if (x < xmin)
xmin = x;
xintab[x] = xin;
}
xo += a[1];
xin = COORD(xo);
if (xin >= 0 && xin < (int) imIn->xsize) {
xmax = x+1;
if (x < xmin)
xmin = x;
xintab[x] = xin;
}
xo += a[1];
}
#define AFFINE_SCALE(pixel, image)\
#define AFFINE_SCALE(pixel, image)\
for (y = y0; y < y1; y++) {\
int yi = COORD(yo);\
pixel *in, *out;\
out = imOut->image[y];\
int yi = COORD(yo);\
pixel *in, *out;\
out = imOut->image[y];\
if (fill && x1 > x0)\
memset(out+x0, 0, (x1-x0)*sizeof(pixel));\
if (yi >= 0 && yi < imIn->ysize) {\
in = imIn->image[yi];\
for (x = xmin; x < xmax; x++)\
out[x] = in[xintab[x]];\
}\
yo += a[5];\
if (yi >= 0 && yi < imIn->ysize) {\
in = imIn->image[yi];\
for (x = xmin; x < xmax; x++)\
out[x] = in[xintab[x]];\
}\
yo += a[5];\
}
ImagingSectionEnter(&cookie);
@ -743,32 +801,32 @@ affine_fixed(Imaging imOut, Imaging imIn,
a0 = FIX(a[0]); a1 = FIX(a[1]); a2 = FIX(a[2]);
a3 = FIX(a[3]); a4 = FIX(a[4]); a5 = FIX(a[5]);
#define AFFINE_TRANSFORM_FIXED(pixel, image)\
#define AFFINE_TRANSFORM_FIXED(pixel, image)\
for (y = y0; y < y1; y++) {\
pixel *out;\
xx = a0;\
yy = a3;\
out = imOut->image[y];\
pixel *out;\
xx = a0;\
yy = a3;\
out = imOut->image[y];\
if (fill && x1 > x0)\
memset(out+x0, 0, (x1-x0)*sizeof(pixel));\
for (x = x0; x < x1; x++, out++) {\
xin = xx >> 16;\
if (xin >= 0 && xin < xsize) {\
yin = yy >> 16;\
if (yin >= 0 && yin < ysize)\
xin = xx >> 16;\
if (xin >= 0 && xin < xsize) {\
yin = yy >> 16;\
if (yin >= 0 && yin < ysize)\
*out = imIn->image[yin][xin];\
}\
xx += a1;\
yy += a4;\
}\
a0 += a2;\
a3 += a5;\
xx += a1;\
yy += a4;\
}\
a0 += a2;\
a3 += a5;\
}
if (imIn->image8)
AFFINE_TRANSFORM_FIXED(UINT8, image8)
AFFINE_TRANSFORM_FIXED(UINT8, image8)
else
AFFINE_TRANSFORM_FIXED(INT32, image32)
AFFINE_TRANSFORM_FIXED(INT32, image32)
return imOut;
}
@ -801,11 +859,11 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn,
}
if (a[2] == 0 && a[4] == 0)
/* Scaling */
return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill);
/* Scaling */
return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill);
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
return (Imaging) ImagingError_ModeError();
if (x0 < 0)
x0 = 0;
@ -835,34 +893,34 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn,
xo = a[0];
yo = a[3];
#define AFFINE_TRANSFORM(pixel, image)\
#define AFFINE_TRANSFORM(pixel, image)\
for (y = y0; y < y1; y++) {\
pixel *out;\
xx = xo;\
yy = yo;\
out = imOut->image[y];\
pixel *out;\
xx = xo;\
yy = yo;\
out = imOut->image[y];\
if (fill && x1 > x0)\
memset(out+x0, 0, (x1-x0)*sizeof(pixel));\
for (x = x0; x < x1; x++, out++) {\
xin = COORD(xx);\
if (xin >= 0 && xin < xsize) {\
yin = COORD(yy);\
if (yin >= 0 && yin < ysize)\
xin = COORD(xx);\
if (xin >= 0 && xin < xsize) {\
yin = COORD(yy);\
if (yin >= 0 && yin < ysize)\
*out = imIn->image[yin][xin];\
}\
xx += a[1];\
yy += a[4];\
}\
xo += a[2];\
yo += a[5];\
xx += a[1];\
yy += a[4];\
}\
xo += a[2];\
yo += a[5];\
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
AFFINE_TRANSFORM(UINT8, image8)
AFFINE_TRANSFORM(UINT8, image8)
else
AFFINE_TRANSFORM(INT32, image32)
AFFINE_TRANSFORM(INT32, image32)
ImagingSectionLeave(&cookie);
@ -912,7 +970,7 @@ ImagingResize(Imaging imOut, Imaging imIn, int filterid)
double a[6];
if (imOut->xsize == imIn->xsize && imOut->ysize == imIn->ysize)
return ImagingCopy2(imOut, imIn);
return ImagingCopy2(imOut, imIn);
memset(a, 0, sizeof a);
a[1] = (double) imIn->xsize / imOut->xsize;

View File

@ -292,6 +292,7 @@ extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn);
extern Imaging ImagingStretch(Imaging imOut, Imaging imIn, int filter);
extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn);
extern Imaging ImagingTransformPerspective(
Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1,
double a[8], int filter, int fill);