mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 15:37:55 +03:00 
			
		
		
		
	Merge pull request #994 from homm/add-transpose
Add transpose and cache aware rotation
This commit is contained in:
		
						commit
						cfbe49f124
					
				|  | @ -150,6 +150,7 @@ FLIP_TOP_BOTTOM = 1 | ||||||
| ROTATE_90 = 2 | ROTATE_90 = 2 | ||||||
| ROTATE_180 = 3 | ROTATE_180 = 3 | ||||||
| ROTATE_270 = 4 | ROTATE_270 = 4 | ||||||
|  | TRANSPOSE = 5 | ||||||
| 
 | 
 | ||||||
| # transforms | # transforms | ||||||
| AFFINE = 0 | AFFINE = 0 | ||||||
|  | @ -1920,13 +1921,13 @@ class Image: | ||||||
| 
 | 
 | ||||||
|         :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`, 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. |         :returns: Returns a flipped or rotated copy of this image. | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         self.load() |         self.load() | ||||||
|         im = self.im.transpose(method) |         return self._new(self.im.transpose(method)) | ||||||
|         return self._new(im) |  | ||||||
| 
 | 
 | ||||||
|     def effect_spread(self, distance): |     def effect_spread(self, distance): | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  | @ -1,30 +1,114 @@ | ||||||
| from helper import unittest, PillowTestCase, hopper | from helper import unittest, PillowTestCase, hopper | ||||||
| 
 | 
 | ||||||
| from PIL import Image | from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, | ||||||
| 
 |     ROTATE_270, TRANSPOSE) | ||||||
| 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 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestImageTranspose(PillowTestCase): | 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) |             x, y = im.size | ||||||
|         im.transpose(FLIP_TOP_BOTTOM) |             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) |         for mode in ("L", "RGB"): | ||||||
|         im.transpose(ROTATE_180) |             transpose(mode) | ||||||
|         im.transpose(ROTATE_270) | 
 | ||||||
|  |     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): |     def test_roundtrip(self): | ||||||
| 
 |         im = self.hopper['L'] | ||||||
|         im = hopper() |  | ||||||
| 
 | 
 | ||||||
|         def transpose(first, second): |         def transpose(first, second): | ||||||
|             return im.transpose(first).transpose(second) |             return im.transpose(first).transpose(second) | ||||||
|  | @ -33,12 +117,13 @@ class TestImageTranspose(PillowTestCase): | ||||||
|             im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) |             im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) | ||||||
|         self.assert_image_equal( |         self.assert_image_equal( | ||||||
|             im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) |             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_90, ROTATE_270)) | ||||||
|         self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) |         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__': | if __name__ == '__main__': | ||||||
|     unittest.main() |     unittest.main() | ||||||
| 
 |  | ||||||
| # End of file |  | ||||||
|  |  | ||||||
|  | @ -1750,6 +1750,7 @@ _transpose(ImagingObject* self, PyObject* args) | ||||||
|         break; |         break; | ||||||
|     case 2: /* rotate 90 */ |     case 2: /* rotate 90 */ | ||||||
|     case 4: /* rotate 270 */ |     case 4: /* rotate 270 */ | ||||||
|  |     case 5: /* transpose */ | ||||||
|         imOut = ImagingNew(imIn->mode, imIn->ysize, imIn->xsize); |         imOut = ImagingNew(imIn->mode, imIn->ysize, imIn->xsize); | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|  | @ -1774,6 +1775,9 @@ _transpose(ImagingObject* self, PyObject* args) | ||||||
|         case 4: |         case 4: | ||||||
|             (void) ImagingRotate270(imOut, imIn); |             (void) ImagingRotate270(imOut, imIn); | ||||||
|             break; |             break; | ||||||
|  |         case 5: | ||||||
|  |             (void) ImagingTranspose(imOut, imIn); | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     return PyImagingNew(imOut); |     return PyImagingNew(imOut); | ||||||
|  |  | ||||||
|  | @ -30,6 +30,13 @@ | ||||||
| /* Undef if you don't need resampling filters */ | /* Undef if you don't need resampling filters */ | ||||||
| #define WITH_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 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))) | ||||||
| 
 | 
 | ||||||
|  | @ -98,7 +105,7 @@ Imaging | ||||||
| ImagingRotate90(Imaging imOut, Imaging imIn) | ImagingRotate90(Imaging imOut, Imaging imIn) | ||||||
| { | { | ||||||
|     ImagingSectionCookie cookie; |     ImagingSectionCookie cookie; | ||||||
|     int x, y, xr; |     int x, y, xx, yy, xr, xxsize, yysize; | ||||||
| 
 | 
 | ||||||
|     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(); | ||||||
|  | @ -107,11 +114,18 @@ ImagingRotate90(Imaging imOut, Imaging imIn) | ||||||
| 
 | 
 | ||||||
|     ImagingCopyInfo(imOut, imIn); |     ImagingCopyInfo(imOut, imIn); | ||||||
| 
 | 
 | ||||||
| #define	ROTATE_90(image)\ | #define ROTATE_90(image) \ | ||||||
|     for (y = 0; y < imIn->ysize; y++) {\ |     for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ | ||||||
| 	xr = imIn->xsize-1;\ |         for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ | ||||||
| 	for (x = 0; x < imIn->xsize; x++, xr--)\ |             yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ | ||||||
| 	    imOut->image[xr][y] = imIn->image[y][x];\ |             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); |     ImagingSectionEnter(&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 | Imaging | ||||||
| ImagingRotate180(Imaging imOut, Imaging imIn) | ImagingRotate180(Imaging imOut, Imaging imIn) | ||||||
| { | { | ||||||
|  | @ -166,7 +217,7 @@ Imaging | ||||||
| ImagingRotate270(Imaging imOut, Imaging imIn) | ImagingRotate270(Imaging imOut, Imaging imIn) | ||||||
| { | { | ||||||
|     ImagingSectionCookie cookie; |     ImagingSectionCookie cookie; | ||||||
|     int x, y, yr; |     int x, y, xx, yy, yr, xxsize, yysize; | ||||||
| 
 | 
 | ||||||
|     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(); | ||||||
|  | @ -175,12 +226,19 @@ ImagingRotate270(Imaging imOut, Imaging imIn) | ||||||
| 
 | 
 | ||||||
|     ImagingCopyInfo(imOut, imIn); |     ImagingCopyInfo(imOut, imIn); | ||||||
| 
 | 
 | ||||||
|     yr = imIn->ysize - 1; | #define ROTATE_270(image) \ | ||||||
| 
 |     for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ | ||||||
| #define	ROTATE_270(image)\ |         for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ | ||||||
|     for (y = 0; y < imIn->ysize; y++, yr--)\ |             yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ | ||||||
| 	for (x = 0; x < imIn->xsize; x++)\ |             xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ | ||||||
| 	    imOut->image[x][y] = imIn->image[yr][x]; |             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); |     ImagingSectionEnter(&cookie); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -292,6 +292,7 @@ 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 imOut, Imaging imIn, int filter); | ||||||
|  | extern Imaging ImagingTranspose(Imaging imOut, 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); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user