From 7e8a6c61f8550095a8c171678c955dc08d0d0976 Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Sat, 17 Dec 2016 18:50:50 +0100 Subject: [PATCH 01/28] Use pathlib2 for Path objects on Python < 3.4 The pathlib backport module is no longer maintained. The development has moved to the pathlib2 module instead. Quoting from the pathlib's README: "Attention: this backport module isn't maintained anymore. If you want to report issues or contribute patches, please consider the pathlib2 project instead." Other projects have already switched to pathlib2, most notably IPython and its dependencies. --- PIL/Image.py | 27 +++++++++++++++------------ Tests/test_image.py | 6 ++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index c086dfcd7..93278ec4d 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -132,6 +132,16 @@ try: except ImportError: HAS_CFFI = False +try: + from pathlib import Path + HAS_PATHLIB = True +except ImportError: + try: + from pathlib2 import Path + HAS_PATHLIB = True + except ImportError: + HAS_PATHLIB = False + def isImageType(t): """ @@ -1652,11 +1662,9 @@ class Image(object): if isPath(fp): filename = fp open_fp = True - elif sys.version_info >= (3, 4): - from pathlib import Path - if isinstance(fp, Path): - filename = str(fp) - open_fp = True + elif HAS_PATHLIB and isinstance(fp, Path): + filename = str(fp) + open_fp = True if not filename and hasattr(fp, "name") and isPath(fp.name): # only set the name for metadata purposes filename = fp.name @@ -2267,13 +2275,8 @@ def open(fp, mode="r"): filename = "" if isPath(fp): filename = fp - else: - try: - from pathlib import Path - if isinstance(fp, Path): - filename = str(fp.resolve()) - except ImportError: - pass + elif HAS_PATHLIB and isinstance(fp, Path): + filename = str(fp.resolve()) if filename: fp = builtins.open(filename, "rb") diff --git a/Tests/test_image.py b/Tests/test_image.py index ef9aa16af..7c62b3297 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -2,7 +2,6 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image import os -import sys class TestImage(PillowTestCase): @@ -50,10 +49,9 @@ class TestImage(PillowTestCase): im = io.BytesIO(b'') self.assertRaises(IOError, lambda: Image.open(im)) - @unittest.skipIf(sys.version_info < (3, 4), - "pathlib only available in Python 3.4 or later") + @unittest.skipUnless(Image.HAS_PATHLIB, "requires pathlib/pathlib2") def test_pathlib(self): - from pathlib import Path + from PIL.Image import Path im = Image.open(Path("Tests/images/hopper.jpg")) self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) From c59529d28d5c543b11a0842cf40ef8ad98c437c8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 1 Sep 2017 08:37:05 +0300 Subject: [PATCH 02/28] use PRECISION_BITS constant in ImagingAlphaComposite use UINT32 instead of UINT16 (a bit faster on x86) --- libImaging/AlphaComposite.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/libImaging/AlphaComposite.c b/libImaging/AlphaComposite.c index 00d28f956..81b621123 100644 --- a/libImaging/AlphaComposite.c +++ b/libImaging/AlphaComposite.c @@ -11,6 +11,7 @@ #include "Imaging.h" +#define PRECISION_BITS 7 typedef struct { @@ -49,13 +50,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) ImagingCopyInfo(imOut, imDst); for (y = 0; y < imDst->ysize; y++) { - rgba8* dst = (rgba8*) imDst->image[y]; rgba8* src = (rgba8*) imSrc->image[y]; rgba8* out = (rgba8*) imOut->image[y]; for (x = 0; x < imDst->xsize; x ++) { - if (src->a == 0) { // Copy 4 bytes at once. *out = *dst; @@ -64,25 +63,20 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) // Each variable has extra meaningful bits. // Divisions are rounded. - // This code uses trick from Paste.c: - // (a + (2 << (n-1)) - 1) / ((2 << n)-1) - // almost equivalent to: - // tmp = a + (2 << (n-1)), ((tmp >> n) + tmp) >> n - UINT32 tmpr, tmpg, tmpb; - UINT16 blend = dst->a * (255 - src->a); - UINT16 outa255 = src->a * 255 + blend; + UINT32 blend = dst->a * (255 - src->a); + UINT32 outa255 = src->a * 255 + blend; // There we use 7 bits for precision. // We could use more, but we go beyond 32 bits. - UINT16 coef1 = src->a * 255 * 255 * 128 / outa255; - UINT16 coef2 = 255 * 128 - coef1; + UINT32 coef1 = src->a * 255 * 255 * (1<r * coef1 + dst->r * coef2 + (0x80 << 7); - out->r = SHIFTFORDIV255(tmpr) >> 7; - tmpg = src->g * coef1 + dst->g * coef2 + (0x80 << 7); - out->g = SHIFTFORDIV255(tmpg) >> 7; - tmpb = src->b * coef1 + dst->b * coef2 + (0x80 << 7); - out->b = SHIFTFORDIV255(tmpb) >> 7; + tmpr = src->r * coef1 + dst->r * coef2; + tmpg = src->g * coef1 + dst->g * coef2; + tmpb = src->b * coef1 + dst->b * coef2; + out->r = SHIFTFORDIV255(tmpr + (0x80<> PRECISION_BITS; + out->g = SHIFTFORDIV255(tmpg + (0x80<> PRECISION_BITS; + out->b = SHIFTFORDIV255(tmpb + (0x80<> PRECISION_BITS; out->a = SHIFTFORDIV255(outa255 + 0x80); } From d4b2fa7348740edd0ea806c135904d94b4694e23 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 1 Sep 2017 13:32:37 +0300 Subject: [PATCH 03/28] Fix erroneous condition which was never true --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index d9660dce7..5293cdf08 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1426,7 +1426,7 @@ class Image(object): box = dest + (dest[0] + overlay.width, dest[1] + overlay.height) # destination image. don't copy if we're using the whole image. - if dest == (0,0) + self.size: + if box == (0,0) + self.size: background = self else: background = self.crop(box) From 62ece989dc2fcd3536f1f6fa5b9561402634131b Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 1 Sep 2017 13:36:25 +0300 Subject: [PATCH 04/28] Accept lists as well --- PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 5293cdf08..864c2bde5 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1400,9 +1400,9 @@ class Image(object): Performance Note: Not currently implemented in-place in the core layer. """ - if not isinstance(source, tuple): + if not isinstance(source, (list, tuple)): raise ValueError("Source must be a tuple") - if not isinstance(dest, tuple): + if not isinstance(dest, (list, tuple)): raise ValueError("Destination must be a tuple") if not len(source) in (2, 4): raise ValueError("Source must be a 2 or 4-tuple") From 67459795dbae03432ab0b2126a32387b5c5c1275 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 01:07:38 +0300 Subject: [PATCH 05/28] 2 times faster ImagingRotate180 --- libImaging/Geometry.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index 2b3b1d5ef..c1b7029db 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -32,18 +32,16 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) for (y = 0; y < imIn->ysize; y++) { UINT8* in = (UINT8*) imIn->image8[y]; UINT8* out = (UINT8*) imOut->image8[y]; - x = 0; xr = imIn->xsize-1; - for (; x < imIn->xsize; x++, xr--) + for (x = 0; x < imIn->xsize; x++, xr--) out[xr] = in[x]; } } else { for (y = 0; y < imIn->ysize; y++) { UINT32* in = (UINT32*) imIn->image32[y]; UINT32* out = (UINT32*) imOut->image32[y]; - x = 0; xr = imIn->xsize-1; - for (; x < imIn->xsize; x++, xr--) + for (x = 0; x < imIn->xsize; x++, xr--) out[xr] = in[x]; } } @@ -175,25 +173,29 @@ ImagingRotate180(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#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];\ - } - ImagingSectionEnter(&cookie); yr = imIn->ysize-1; - if (imIn->image8) - ROTATE_180(image8) - else - ROTATE_180(image32) + if (imIn->image8) { + for (y = 0; y < imIn->ysize; y++, yr--) { + UINT8* in = (UINT8*) imIn->image8[y]; + UINT8* out = (UINT8*) imOut->image8[yr]; + xr = imIn->xsize-1; + for (x = 0; x < imIn->xsize; x++, xr--) + out[xr] = in[x]; + } + } else { + for (y = 0; y < imIn->ysize; y++, yr--) { + UINT32* in = (UINT32*) imIn->image32[y]; + UINT32* out = (UINT32*) imOut->image32[yr]; + xr = imIn->xsize-1; + for (x = 0; x < imIn->xsize; x++, xr--) + out[xr] = in[x]; + } + } ImagingSectionLeave(&cookie); -#undef ROTATE_180 - return imOut; } From a2a2d8d71618ce47945b018e6561f703cc43a41c Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 01:47:47 +0300 Subject: [PATCH 06/28] Implement ImagingTransposeRotate180 --- PIL/Image.py | 1 + _imaging.c | 4 ++++ libImaging/Geometry.c | 44 +++++++++++++++++++++++++++++++++++++++++++ libImaging/Imaging.h | 3 ++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index a6447d45f..664380e82 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -150,6 +150,7 @@ ROTATE_90 = 2 ROTATE_180 = 3 ROTATE_270 = 4 TRANSPOSE = 5 +TRANSPOSE_ROTATE_180 = 6 # transforms AFFINE = 0 diff --git a/_imaging.c b/_imaging.c index 60a97aa19..90fa61e59 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1674,6 +1674,7 @@ _transpose(ImagingObject* self, PyObject* args) case 2: /* rotate 90 */ case 4: /* rotate 270 */ case 5: /* transpose */ + case 6: /* transpose and rotate 180 */ imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); break; default: @@ -1701,6 +1702,9 @@ _transpose(ImagingObject* self, PyObject* args) case 5: (void) ImagingTranspose(imOut, imIn); break; + case 6: + (void) ImagingTransposeRotate180(imOut, imIn); + break; } return PyImagingNew(imOut); diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index c1b7029db..6d9af669d 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -160,6 +160,50 @@ ImagingTranspose(Imaging imOut, Imaging imIn) } +Imaging +ImagingTransposeRotate180(Imaging imOut, Imaging imIn) +{ + ImagingSectionCookie cookie; + int x, y, xx, yy, xxsize, yysize; + int xr, yr; + + 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 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++) { \ + yr = imIn->ysize - 1 - yy; \ + xr = imIn->xsize - 1 - x; \ + for (xx = x; xx < xxsize; xx++, xr--) { \ + imOut->image[xr][yr] = imIn->image[yy][xx]; \ + } \ + } \ + } \ + } + + ImagingSectionEnter(&cookie); + + if (imIn->image8) + TRANSPOSE(image8) + else + TRANSPOSE(image32) + + ImagingSectionLeave(&cookie); + +#undef TRANSPOSE + + return imOut; +} + + Imaging ImagingRotate180(Imaging imOut, Imaging imIn) { diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index fa7ed368d..dbb9823a0 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -291,8 +291,9 @@ extern Imaging ImagingRankFilter(Imaging im, int size, int rank); extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate180(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 ImagingTransposeRotate180(Imaging imOut, Imaging imIn); +extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]); extern Imaging ImagingTransform( Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1, double *a, int filter, int fill); From b8789e681514853e34aa372678cd4183a5e811cf Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 02:44:28 +0300 Subject: [PATCH 07/28] Change geometry chunk size --- libImaging/Geometry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index 6d9af669d..c490937da 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -5,7 +5,7 @@ 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 ROTATE_CHUNK 16 #define COORD(v) ((v) < 0.0 ? -1 : ((int)(v))) #define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v))) From fd297fe588451d7faed4c6768045bcef2c5d66b3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 17:01:38 +0300 Subject: [PATCH 08/28] 3-level transpospose --- libImaging/Geometry.c | 94 +++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index c490937da..306dd8ed4 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -5,7 +5,8 @@ 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 16 +#define ROTATE_CHUNK 512 +#define ROTATE_SMALL_CHUNK 8 #define COORD(v) ((v) < 0.0 ? -1 : ((int)(v))) #define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v))) @@ -82,6 +83,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, xr, xxsize, yysize; + int xxx, yyy, xxxsize, yyysize; if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) return (Imaging) ImagingError_ModeError(); @@ -90,15 +92,22 @@ ImagingRotate90(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#define ROTATE_90(image) \ +#define ROTATE_90(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++) { \ - xr = imIn->xsize - 1 - x; \ - for (xx = x; xx < xxsize; xx++, xr--) { \ - imOut->image[xr][yy] = imIn->image[yy][xx]; \ + 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; \ + 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]; \ + } \ + } \ } \ } \ } \ @@ -107,9 +116,9 @@ ImagingRotate90(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) - ROTATE_90(image8) + ROTATE_90(UINT8, image8) else - ROTATE_90(image32) + ROTATE_90(INT32, image32) ImagingSectionLeave(&cookie); @@ -124,6 +133,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, xxsize, yysize; + int xxx, yyy, xxxsize, yyysize; if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) return (Imaging) ImagingError_ModeError(); @@ -132,14 +142,21 @@ ImagingTranspose(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#define TRANSPOSE(image) \ +#define TRANSPOSE(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++) { \ - for (xx = x; xx < xxsize; xx++) { \ - imOut->image[xx][yy] = imIn->image[yy][xx]; \ + 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; \ + for (yyy = yy; yyy < yyysize; yyy++) { \ + INT* in = imIn->image[yyy]; \ + for (xxx = xx; xxx < xxxsize; xxx++) { \ + imOut->image[xxx][yyy] = in[xxx]; \ + } \ + } \ } \ } \ } \ @@ -148,9 +165,9 @@ ImagingTranspose(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) - TRANSPOSE(image8) + TRANSPOSE(UINT8, image8) else - TRANSPOSE(image32) + TRANSPOSE(INT32, image32) ImagingSectionLeave(&cookie); @@ -164,8 +181,8 @@ Imaging ImagingTransposeRotate180(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; - int x, y, xx, yy, xxsize, yysize; - int xr, yr; + 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(); @@ -174,16 +191,23 @@ ImagingTransposeRotate180(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#define TRANSPOSE(image) \ +#define TRANSPOSE(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++) { \ - yr = imIn->ysize - 1 - yy; \ - xr = imIn->xsize - 1 - x; \ - for (xx = x; xx < xxsize; xx++, xr--) { \ - imOut->image[xr][yr] = imIn->image[yy][xx]; \ + 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]; \ + } \ + } \ } \ } \ } \ @@ -192,9 +216,9 @@ ImagingTransposeRotate180(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) - TRANSPOSE(image8) + TRANSPOSE(UINT8, image8) else - TRANSPOSE(image32) + TRANSPOSE(INT32, image32) ImagingSectionLeave(&cookie); @@ -249,6 +273,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, yr, xxsize, yysize; + int xxx, yyy, xxxsize, yyysize; if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) return (Imaging) ImagingError_ModeError(); @@ -257,15 +282,22 @@ ImagingRotate270(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#define ROTATE_270(image) \ +#define ROTATE_270(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; \ - 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]; \ + 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]; \ + for (xxx = xx; xxx < xxxsize; xxx++) { \ + imOut->image[xxx][yr] = in[xxx]; \ + } \ + } \ } \ } \ } \ @@ -274,9 +306,9 @@ ImagingRotate270(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) - ROTATE_270(image8) + ROTATE_270(UINT8, image8) else - ROTATE_270(image32) + ROTATE_270(INT32, image32) ImagingSectionLeave(&cookie); From 29515f59bb49ec95a8ba1f25a034db553ff93616 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 22:58:22 +0300 Subject: [PATCH 09/28] rename TRANSPOSE_ROTATE_180 to TRANSVERSE --- PIL/Image.py | 2 +- _imaging.c | 4 ++-- libImaging/Geometry.c | 10 +++++----- libImaging/Imaging.h | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 664380e82..455b9860e 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -150,7 +150,7 @@ ROTATE_90 = 2 ROTATE_180 = 3 ROTATE_270 = 4 TRANSPOSE = 5 -TRANSPOSE_ROTATE_180 = 6 +TRANSVERSE = 6 # transforms AFFINE = 0 diff --git a/_imaging.c b/_imaging.c index 90fa61e59..89e883dee 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1674,7 +1674,7 @@ _transpose(ImagingObject* self, PyObject* args) case 2: /* rotate 90 */ case 4: /* rotate 270 */ case 5: /* transpose */ - case 6: /* transpose and rotate 180 */ + case 6: /* transverse */ imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); break; default: @@ -1703,7 +1703,7 @@ _transpose(ImagingObject* self, PyObject* args) (void) ImagingTranspose(imOut, imIn); break; case 6: - (void) ImagingTransposeRotate180(imOut, imIn); + (void) ImagingTransverse(imOut, imIn); break; } diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index 306dd8ed4..95d33dd04 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -178,7 +178,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) Imaging -ImagingTransposeRotate180(Imaging imOut, Imaging imIn) +ImagingTransverse(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr, yr, xx, yy, xxsize, yysize; @@ -191,7 +191,7 @@ ImagingTransposeRotate180(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); -#define TRANSPOSE(INT, image) \ +#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; \ @@ -216,13 +216,13 @@ ImagingTransposeRotate180(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) - TRANSPOSE(UINT8, image8) + TRANSVERSE(UINT8, image8) else - TRANSPOSE(INT32, image32) + TRANSVERSE(INT32, image32) ImagingSectionLeave(&cookie); -#undef TRANSPOSE +#undef TRANSVERSE return imOut; } diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index dbb9823a0..5577fd10e 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -292,7 +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 ImagingTranspose(Imaging imOut, Imaging imIn); -extern Imaging ImagingTransposeRotate180(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( Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1, From b6b3b004d8e2132b628635586260a9b275dc2864 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2017 23:00:35 +0300 Subject: [PATCH 10/28] tests for transverse, add to docs --- PIL/Image.py | 4 ++-- Tests/test_image_transpose.py | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 455b9860e..8d4a99bbf 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2171,8 +2171,8 @@ class Image(object): :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`, :py:attr:`PIL.Image.ROTATE_270` or - :py:attr:`PIL.Image.TRANSPOSE`. + :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`, + :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index e13fc8605..a6b1191db 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -2,7 +2,7 @@ import helper from helper import unittest, PillowTestCase from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, - ROTATE_270, TRANSPOSE) + ROTATE_270, TRANSPOSE, TRANSVERSE) class TestImageTranspose(PillowTestCase): @@ -108,6 +108,22 @@ class TestImageTranspose(PillowTestCase): for mode in ("L", "RGB"): 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): im = self.hopper['L'] @@ -124,6 +140,12 @@ class TestImageTranspose(PillowTestCase): im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM)) self.assert_image_equal( 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__': From bc13a9d011c3384683a55e5b3e0c497bf003e84c Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 12 Sep 2017 14:07:33 +0300 Subject: [PATCH 11/28] Use macros for FLIP_LEFT_RIGHT and ROTATE_180 --- libImaging/Geometry.c | 54 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index 95d33dd04..759056ae0 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -27,28 +27,27 @@ ImagingFlipLeftRight(Imaging imOut, Imaging 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); if (imIn->image8) { - for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image8[y]; - UINT8* out = (UINT8*) imOut->image8[y]; - xr = imIn->xsize-1; - for (x = 0; x < imIn->xsize; x++, xr--) - out[xr] = in[x]; - } + FLIP_LEFT_RIGHT(UINT8, image8) } else { - for (y = 0; y < imIn->ysize; y++) { - UINT32* in = (UINT32*) imIn->image32[y]; - UINT32* out = (UINT32*) imOut->image32[y]; - xr = imIn->xsize-1; - for (x = 0; x < imIn->xsize; x++, xr--) - out[xr] = in[x]; - } + FLIP_LEFT_RIGHT(INT32, image32) } ImagingSectionLeave(&cookie); +#undef FLIP_LEFT_RIGHT + return imOut; } @@ -241,29 +240,28 @@ ImagingRotate180(Imaging imOut, Imaging imIn) ImagingCopyInfo(imOut, imIn); +#define ROTATE_180(INT, image) \ + for (y = 0; y < imIn->ysize; y++, yr--) { \ + INT* in = imIn->image[y]; \ + INT* out = imOut->image[yr]; \ + xr = imIn->xsize-1; \ + for (x = 0; x < imIn->xsize; x++, xr--) \ + out[xr] = in[x]; \ + } + ImagingSectionEnter(&cookie); yr = imIn->ysize-1; if (imIn->image8) { - for (y = 0; y < imIn->ysize; y++, yr--) { - UINT8* in = (UINT8*) imIn->image8[y]; - UINT8* out = (UINT8*) imOut->image8[yr]; - xr = imIn->xsize-1; - for (x = 0; x < imIn->xsize; x++, xr--) - out[xr] = in[x]; - } + ROTATE_180(UINT8, image8) } else { - for (y = 0; y < imIn->ysize; y++, yr--) { - UINT32* in = (UINT32*) imIn->image32[y]; - UINT32* out = (UINT32*) imOut->image32[yr]; - xr = imIn->xsize-1; - for (x = 0; x < imIn->xsize; x++, xr--) - out[xr] = in[x]; - } + ROTATE_180(INT32, image32) } ImagingSectionLeave(&cookie); +#undef ROTATE_180 + return imOut; } From d5f069472d76728ccc7a0b3f35a783efdce38d69 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 14 Sep 2017 01:48:27 +0300 Subject: [PATCH 12/28] Deprecate ImageOps undocumented functions --- PIL/ImageOps.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 3681109c1..447d48aae 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -21,6 +21,7 @@ from . import Image from ._util import isStringType import operator import functools +import warnings # @@ -437,6 +438,13 @@ def solarize(image, threshold=128): def gaussian_blur(im, radius=None): """ PIL_usm.gblur(im, [radius])""" + warnings.warn( + 'PIL.ImageOps.gaussian_blur is deprecated. ' + 'Use PIL.ImageFilter.GaussianBlur instead. ' + 'This function will be removed in a future version.', + DeprecationWarning + ) + if radius is None: radius = 5.0 @@ -444,12 +452,30 @@ def gaussian_blur(im, radius=None): return im.im.gaussian_blur(radius) -gblur = gaussian_blur + +def gblur(im, radius=None): + """ PIL_usm.gblur(im, [radius])""" + + warnings.warn( + 'PIL.ImageOps.gblur is deprecated. ' + 'Use PIL.ImageFilter.GaussianBlur instead. ' + 'This function will be removed in a future version.', + DeprecationWarning + ) + + return gaussian_blur(im, radius) def unsharp_mask(im, radius=None, percent=None, threshold=None): """ PIL_usm.usm(im, [radius, percent, threshold])""" + warnings.warn( + 'PIL.ImageOps.unsharp_mask is deprecated. ' + 'Use PIL.ImageFilter.UnsharpMask instead. ' + 'This function will be removed in a future version.', + DeprecationWarning + ) + if radius is None: radius = 5.0 if percent is None: @@ -461,7 +487,18 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None): return im.im.unsharp_mask(radius, percent, threshold) -usm = unsharp_mask + +def usm(im, radius=None, percent=None, threshold=None): + """ PIL_usm.usm(im, [radius, percent, threshold])""" + + warnings.warn( + 'PIL.ImageOps.usm is deprecated. ' + 'Use PIL.ImageFilter.UnsharpMask instead. ' + 'This function will be removed in a future version.', + DeprecationWarning + ) + + return unsharp_mask(im, radius, percent, threshold) def box_blur(image, radius): @@ -478,6 +515,13 @@ def box_blur(image, radius): in each direction, i.e. 9 pixels in total. :return: An image. """ + warnings.warn( + 'PIL.ImageOps.box_blur is deprecated. ' + 'Use PIL.ImageFilter.BoxBlur instead. ' + 'This function will be removed in a future version.', + DeprecationWarning + ) + image.load() return image._new(image.im.box_blur(radius)) From 6900a7707fb0ea81a1251ba6799f52ac82503e1e Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 14 Sep 2017 01:59:25 +0300 Subject: [PATCH 13/28] ImageFilter.BoxBlur --- PIL/ImageFilter.py | 20 ++++++++++++++++++++ Tests/test_box_blur.py | 14 -------------- Tests/test_image_filter.py | 14 +++++++------- docs/reference/ImageFilter.rst | 1 + 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/PIL/ImageFilter.py b/PIL/ImageFilter.py index c89225484..100fea8bd 100644 --- a/PIL/ImageFilter.py +++ b/PIL/ImageFilter.py @@ -160,6 +160,26 @@ class GaussianBlur(MultibandFilter): return image.gaussian_blur(self.radius) +class BoxBlur(MultibandFilter): + """Blurs the image by setting each pixel to the average value of the pixels + in a square box extending radius pixels in each direction. + Supports float radius of arbitrary size. Uses an optimized implementation + which runs in linear time relative to the size of the image + for any radius value. + + :param radius: Size of the box in one direction. Radius 0 does not blur, + returns an identical image. Radius 1 takes 1 pixel + in each direction, i.e. 9 pixels in total. + """ + name = "BoxBlur" + + def __init__(self, radius): + self.radius = radius + + def filter(self, image): + return image.box_blur(self.radius) + + class UnsharpMask(MultibandFilter): """Unsharp mask filter. diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index d99847740..622b842d0 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -13,20 +13,6 @@ sample.putdata(sum([ ], [])) -class ImageMock(object): - def __init__(self): - self.im = self - - def load(self): - pass - - def _new(self, im): - return im - - def box_blur(self, radius, n): - return radius, n - - class TestBoxBlurApi(PillowTestCase): def test_imageops_box_blur(self): diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 8a38b2979..3636a73f7 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,7 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image -from PIL import ImageFilter +from PIL import Image, ImageFilter class TestImageFilter(PillowTestCase): @@ -9,10 +8,11 @@ class TestImageFilter(PillowTestCase): def test_sanity(self): def filter(filter): - im = hopper("L") - out = im.filter(filter) - self.assertEqual(out.mode, im.mode) - self.assertEqual(out.size, im.size) + for mode in ["L", "RGB", "CMYK"]: + im = hopper(mode) + out = im.filter(filter) + self.assertEqual(out.mode, im.mode) + self.assertEqual(out.size, im.size) filter(ImageFilter.BLUR) filter(ImageFilter.CONTOUR) @@ -28,9 +28,9 @@ class TestImageFilter(PillowTestCase): filter(ImageFilter.MedianFilter) filter(ImageFilter.MinFilter) filter(ImageFilter.ModeFilter) - filter(ImageFilter.Kernel((3, 3), list(range(9)))) filter(ImageFilter.GaussianBlur) filter(ImageFilter.GaussianBlur(5)) + filter(ImageFilter.BoxBlur(5)) filter(ImageFilter.UnsharpMask) filter(ImageFilter.UnsharpMask(10)) diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index e89fafbcf..bc1868667 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -38,6 +38,7 @@ image enhancement filters: * **SHARPEN** .. autoclass:: PIL.ImageFilter.GaussianBlur +.. autoclass:: PIL.ImageFilter.BoxBlur .. autoclass:: PIL.ImageFilter.UnsharpMask .. autoclass:: PIL.ImageFilter.Kernel .. autoclass:: PIL.ImageFilter.RankFilter From 8b1a3cc80c6d86f17819313361f94bfdfb4e16e8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 14 Sep 2017 02:49:05 +0300 Subject: [PATCH 14/28] DeprecationWarning tests --- Tests/test_imageops_usm.py | 56 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index eed8ff754..cd7dcae5f 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -12,15 +12,25 @@ class TestImageOpsUsm(PillowTestCase): def test_ops_api(self): - i = ImageOps.gaussian_blur(im, 2.0) + i = self.assert_warning(DeprecationWarning, + ImageOps.gaussian_blur, im, 2.0) self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) - # i.save("blur.bmp") - i = ImageOps.unsharp_mask(im, 2.0, 125, 8) + i = self.assert_warning(DeprecationWarning, + ImageOps.gblur, im, 2.0) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + + i = self.assert_warning(DeprecationWarning, + ImageOps.unsharp_mask, im, 2.0, 125, 8) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + + i = self.assert_warning(DeprecationWarning, + ImageOps.usm, im, 2.0, 125, 8) self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) - # i.save("usm.bmp") def test_filter_api(self): @@ -36,38 +46,38 @@ class TestImageOpsUsm(PillowTestCase): def test_usm_formats(self): - usm = ImageOps.unsharp_mask - self.assertRaises(ValueError, usm, im.convert("1")) - usm(im.convert("L")) - self.assertRaises(ValueError, usm, im.convert("I")) - self.assertRaises(ValueError, usm, im.convert("F")) - usm(im.convert("RGB")) - usm(im.convert("RGBA")) - usm(im.convert("CMYK")) - self.assertRaises(ValueError, usm, im.convert("YCbCr")) + usm = ImageFilter.UnsharpMask + self.assertRaises(ValueError, im.convert("1").filter, usm) + im.convert("L").filter(usm) + self.assertRaises(ValueError, im.convert("I").filter, usm) + self.assertRaises(ValueError, im.convert("F").filter, usm) + im.convert("RGB").filter(usm) + im.convert("RGBA").filter(usm) + im.convert("CMYK").filter(usm) + self.assertRaises(ValueError, im.convert("YCbCr").filter, usm) def test_blur_formats(self): - blur = ImageOps.gaussian_blur - self.assertRaises(ValueError, blur, im.convert("1")) + blur = ImageFilter.GaussianBlur + self.assertRaises(ValueError, im.convert("1").filter, blur) blur(im.convert("L")) - self.assertRaises(ValueError, blur, im.convert("I")) - self.assertRaises(ValueError, blur, im.convert("F")) - blur(im.convert("RGB")) - blur(im.convert("RGBA")) - blur(im.convert("CMYK")) - self.assertRaises(ValueError, blur, im.convert("YCbCr")) + self.assertRaises(ValueError, im.convert("I").filter, blur) + self.assertRaises(ValueError, im.convert("F").filter, blur) + im.convert("RGB").filter(blur) + im.convert("RGBA").filter(blur) + im.convert("CMYK").filter(blur) + self.assertRaises(ValueError, im.convert("YCbCr").filter, blur) def test_usm_accuracy(self): src = snakes.convert('RGB') - i = src._new(ImageOps.unsharp_mask(src, 5, 1024, 0)) + i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) # Image should not be changed because it have only 0 and 255 levels. self.assertEqual(i.tobytes(), src.tobytes()) def test_blur_accuracy(self): - i = snakes._new(ImageOps.gaussian_blur(snakes, .4)) + i = snakes.filter(ImageFilter.GaussianBlur(.4)) # These pixels surrounded with pixels with 255 intensity. # They must be very close to 255. for x, y, c in [(1, 0, 1), (2, 0, 1), (7, 8, 1), (8, 8, 1), (2, 9, 1), From 1f4699ca5b0f1a159789af1ca19578fb0d59a00c Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Tue, 5 Sep 2017 16:19:18 +0000 Subject: [PATCH 15/28] Specific fix for issue #2278, save fails with PhotoshopInfo tag --- PIL/TiffImagePlugin.py | 6 ++++-- PIL/TiffTags.py | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index bd66f4a7a..8dea356cc 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1011,8 +1011,10 @@ class TiffImageFile(ImageFile.ImageFile): args = rawmode, "" if JPEGTABLES in self.tag_v2: # Hack to handle abbreviated JPEG headers - # FIXME This will fail with more than one value - self.tile_prefix, = self.tag_v2[JPEGTABLES] + # Definition of JPEGTABLES is that the count + # is the number of bytes in the tables datastream + # so, it should always be 1 in our tag info + self.tile_prefix = self.tag_v2[JPEGTABLES] elif compression == "packbits": args = rawmode elif compression == "tiff_lzw": diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index 731d0ec8b..6393bb48b 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -142,6 +142,8 @@ TAGS_V2 = { 341: ("SMaxSampleValue", DOUBLE, 0), 342: ("TransferRange", SHORT, 6), + 347: ("JPEGTables", UNDEFINED, 1), + # obsolete JPEG tags 512: ("JPEGProc", SHORT, 1), 513: ("JPEGInterchangeFormat", LONG, 1), @@ -158,7 +160,10 @@ TAGS_V2 = { 531: ("YCbCrPositioning", SHORT, 1), 532: ("ReferenceBlackWhite", LONG, 0), + 700: ('XMP', BYTE, 1), + 33432: ("Copyright", ASCII, 1), + 34377: ('PhotoshopInfo', BYTE, 1), # FIXME add more tags here 34665: ("ExifIFD", SHORT, 1), From 488691bda4b92711aa3b96eebe074febc3375633 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 8 Sep 2017 18:15:45 +0000 Subject: [PATCH 16/28] General fix for issue #2278, #2006, ValueError with out of spec metadata --- PIL/TiffImagePlugin.py | 23 ++++++++++++++++++++--- PIL/TiffTags.py | 2 +- Tests/test_file_tiff_metadata.py | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 8dea356cc..e6ecabc1d 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -550,11 +550,28 @@ class ImageFileDirectory_v2(collections.MutableMapping): dest = self._tags_v1 if legacy_api else self._tags_v2 - if info.length == 1: - if legacy_api and self.tagtype[tag] in [5, 10]: + # Three branches: + # Spec'd length == 1, Actual length 1, store as element + # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. + # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. + # Don't mess with the legacy api, since it's frozen. + if ((info.length == 1) or + (info.length is None and len(values) == 1 and not legacy_api)): + # Don't mess with the legacy api, since it's frozen. + if legacy_api and self.tagtype[tag] in [5, 10]: # rationals values = values, - dest[tag], = values + try: + dest[tag], = values + except ValueError: + # We've got a builtin tag with 1 expected entry + warnings.warn( + "Metadata Warning, tag %s had too many entries: %s, expected 1" % ( + tag, len(values))) + dest[tag] = values[0] + else: + # Spec'd length > 1 or undefined + # Unspec'd, and length > 1 dest[tag] = values def __delitem__(self, tag): diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index 6393bb48b..e46cb02c1 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -23,7 +23,7 @@ from collections import namedtuple class TagInfo(namedtuple("_TagInfo", "value name type length enum")): __slots__ = [] - def __new__(cls, value=None, name="unknown", type=None, length=0, enum=None): + def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): return super(TagInfo, cls).__new__( cls, value, name, type, length, enum or {}) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9d11e32ef..3818d77a1 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -202,8 +202,8 @@ class TestFileTiffMetadata(PillowTestCase): im.save(out, tiffinfo=info, compression='raw') reloaded = Image.open(out) - self.assertEqual(0, reloaded.tag_v2[41988][0].numerator) - self.assertEqual(0, reloaded.tag_v2[41988][0].denominator) + self.assertEqual(0, reloaded.tag_v2[41988].numerator) + self.assertEqual(0, reloaded.tag_v2[41988].denominator) def test_expty_values(self): data = io.BytesIO( From 83c57a80637b630cc21a7a068e453bd0d790875f Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 8 Sep 2017 18:20:48 +0000 Subject: [PATCH 17/28] Specific change for issue #2006, ImageJ Metadata counts are incorrect in TiffTags --- PIL/TiffTags.py | 4 ++-- Tests/test_file_tiff_metadata.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index e46cb02c1..eba88ef8d 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -193,8 +193,8 @@ TAGS_V2 = { 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), 50780: ("BestQualityScale", RATIONAL, 1), - 50838: ("ImageJMetaDataByteCounts", LONG, 1), - 50839: ("ImageJMetaData", UNDEFINED, 1) + 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one + 50839: ("ImageJMetaData", UNDEFINED, 1) # see Issue #2006 } # Legacy Tags structure diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 3818d77a1..90fad52c2 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -56,7 +56,7 @@ class TestFileTiffMetadata(PillowTestCase): loaded = Image.open(f) self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], len(bindata)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),)) self.assertEqual(loaded.tag[ImageJMetaData], bindata) self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata) From 843f8b2a6b57a50df24ec34def06f926317b6a44 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 14 Sep 2017 16:11:56 +0000 Subject: [PATCH 18/28] Test for ImageJMetaDataByteCounts, #2006 --- Tests/test_file_tiff_metadata.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 90fad52c2..2360d4fa9 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -69,6 +69,16 @@ class TestFileTiffMetadata(PillowTestCase): loaded_double = loaded.tag[tag_ids['YawAngle']][0] self.assertAlmostEqual(loaded_double, doubledata) + # check with 2 element ImageJMetaDataByteCounts, issue #2006 + + info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8) + img.save(f, tiffinfo=info) + loaded = Image.open(f) + + self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) + + def test_read_metadata(self): img = Image.open('Tests/images/hopper_g4.tif') From 56e490fe7c11620569060ea2bb8eda0b164bd753 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 14 Sep 2017 16:25:11 +0000 Subject: [PATCH 19/28] Test for issue #2278 --- Tests/images/issue_2278.tif | Bin 0 -> 38668 bytes Tests/test_file_tiff_metadata.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 Tests/images/issue_2278.tif diff --git a/Tests/images/issue_2278.tif b/Tests/images/issue_2278.tif new file mode 100644 index 0000000000000000000000000000000000000000..adac046dba073285f945446d5ad557894da82c9b GIT binary patch literal 38668 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 v&}*RAK(B#b1HA@%4fGo5HPCCI*Fdj Date: Thu, 14 Sep 2017 20:16:14 +0100 Subject: [PATCH 20/28] Release notes for metadata changes --- docs/releasenotes/4.3.0.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index 4708eeb29..6213402e6 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -40,6 +40,22 @@ This release contains several performance improvements: using a recent version of libjpeg-turbo. +TIFF Metadata Changes +===================== + +* TIFF tags with unknown type/quantity now default to being bare + values if they are 1 element, where previously they would be a + single element tuple. This is only with the new api, not the legacy + api. This normalizes the handling of fields, so that the metadata + with inferred or image specified counts are handled the same as + metadata with count specified in the TIFF spec. +* The ``PhotoshopInfo``, ``XMP``, and ``JPEGTables`` tags now have a + defined type (bytes) and a count of 1. +* The ``ImageJMetaDataByteCounts`` tag now has an arbitrary number of + items, as there can be multiple items, one for UTF-8, and one for + UTF-16. + + Core Image API Changes ====================== From 60f44c83df4b8697116129c3e0a45f36f1cb90bd Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 15 Sep 2017 00:55:19 +0300 Subject: [PATCH 21/28] Add to release notes --- docs/releasenotes/4.3.0.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index 4708eeb29..6e3a40176 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -4,13 +4,19 @@ Get One Channel From Image ========================== -New method :py:meth:`PIL.Image.Image.getchannel` added. +New method :py:meth:`PIL.Image.Image.getchannel` is added. It returns single channel by index or name. For example, ``image.getchannel("A")`` will return alpha channel as separate image. ``getchannel`` should work up to 6 times faster than ``image.split()[0]`` in previous Pillow versions. +Box Blur +======== + +New filter :py:class:`PIL.ImageFilter.BoxBlur` is added. + + Partial Resampling ================== From a6ed03c475ba5fa645f5e81de1f20f0692a1dc61 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 16 Sep 2017 21:52:28 +0300 Subject: [PATCH 22/28] Export only required properties in unsafe_ptrs --- PIL/PyAccess.py | 3 +-- _imaging.c | 15 ++------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 09ce86af4..620efc4ff 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -49,8 +49,7 @@ class PyAccess(object): self.image8 = ffi.cast('unsigned char **', vals['image8']) self.image32 = ffi.cast('int **', vals['image32']) self.image = ffi.cast('unsigned char **', vals['image']) - self.xsize = vals['xsize'] - self.ysize = vals['ysize'] + self.xsize, self.ysize = img.im.size # Keep pointer to im object to prevent dereferencing. self._im = img.im diff --git a/_imaging.c b/_imaging.c index d0777c73a..3c1124151 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3138,21 +3138,10 @@ _getattr_ptr(ImagingObject* self, void* closure) static PyObject* _getattr_unsafe_ptrs(ImagingObject* self, void* closure) { - return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", - "mode", self->image->mode, - "type", self->image->type, - "depth", self->image->depth, - "bands", self->image->bands, - "xsize", self->image->xsize, - "ysize", self->image->ysize, - "palette", self->image->palette, + return Py_BuildValue("(sn)(sn)(sn)", "image8", self->image->image8, "image32", self->image->image32, - "image", self->image->image, - "block", self->image->block, - "pixelsize", self->image->pixelsize, - "linesize", self->image->linesize, - "destroy", self->image->destroy + "image", self->image->image ); }; From 3852df109c699faf3cd85b1dfb687fa3ab7d163f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 17 Sep 2017 11:44:39 +1000 Subject: [PATCH 23/28] Updated freetype to 2.8.1 --- winbuild/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 87d485568..310a58eb8 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -34,9 +34,9 @@ libs = { 'dir': 'tiff-4.0.8', }, 'freetype': { - 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.8.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.8.tar.gz', - 'dir': 'freetype-2.8', + 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.8.1.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.8.1.tar.gz', + 'dir': 'freetype-2.8.1', }, 'lcms': { 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', From 269b50e7a8276769697a52e435af5536fa569897 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 17 Sep 2017 13:36:32 +1000 Subject: [PATCH 24/28] Fixed bug where player script was skipping the first image --- Scripts/player.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Scripts/player.py b/Scripts/player.py index dcf7d9307..6e6e21d20 100755 --- a/Scripts/player.py +++ b/Scripts/player.py @@ -22,13 +22,10 @@ from PIL import Image, ImageTk class UI(tkinter.Label): def __init__(self, master, im): - if isinstance(im, list): + self.im = im + if isinstance(self.im, list): # list of images - self.im = im[1:] - im = self.im[0] - else: - # sequence - self.im = im + im = self.im.pop(0) if im.mode == "1": self.image = ImageTk.BitmapImage(im, foreground="white") From 03e5c63398d3625d2a314a637c183201919bf89b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 19 Sep 2017 11:09:55 +0100 Subject: [PATCH 25/28] Updage CHANGES.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f9586184e..a2308a422 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,24 @@ Changelog (Pillow) 4.3.0 (unreleased) ------------------ +- Export only required properties in unsafe_ptrs #2740 + [homm] + +- Alpha composite fixes #2709 + [homm] + +- Faster Transpose operations, added 'Transverse' option #2730 + [homm] + +- Deprecate ImageOps undocumented functions gaussian_blur, gblur, unsharp_mask, usm and box_blur in favor of ImageFilter implementations #2735 + [homm] + +- Dependencies: Updated freetype to 2.8.1 #2741 + [radarhere] + +- Bug: Player skipped first image #2742 + [radarhere] + - Faster filter operations for Kernel, Gaussian, and Unsharp Mask filters #2679 [homm] From bcdc23c3bab2651e68b8f5247be29cca8695e56e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 19 Sep 2017 11:12:57 +0100 Subject: [PATCH 26/28] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a2308a422..91cae6f9c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 4.3.0 (unreleased) ------------------ +- Use pathlib2 for Path objects on Python < 3.4 #2291 + [asergi] + - Export only required properties in unsafe_ptrs #2740 [homm] From c12b9fb4dc8b23aaa329a4b989a0c9e12de8bc27 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Tue, 19 Sep 2017 10:35:14 +0000 Subject: [PATCH 27/28] added test for metadata count warning --- Tests/test_file_tiff_metadata.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9c29c7cd3..bb5768046 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -239,5 +239,18 @@ class TestFileTiffMetadata(PillowTestCase): reloaded = Image.open(out) self.assertIsInstance(reloaded.tag_v2[34377], bytes) + def test_too_many_entries(self): + ifd = TiffImagePlugin.ImageFileDirectory_v2() + + # 277: ("SamplesPerPixel", SHORT, 1), + ifd._tagdata[277] = struct.pack('hh', 4,4) + ifd.tagtype[277] = TiffTags.SHORT + + try: + self.assert_warning(UserWarning, lambda: ifd[277]) + except ValueError: + self.fail("Invalid Metadata count should not cause a Value Error.") + + if __name__ == '__main__': unittest.main() From bbf1c3a52c446650ce2ba126205363d8503a4e77 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 19 Sep 2017 14:27:25 +0100 Subject: [PATCH 28/28] Updage CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 91cae6f9c..e26794af6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 4.3.0 (unreleased) ------------------ +- Fix ValueError in Exif/Tiff IFD #2719 + [wiredfool] + - Use pathlib2 for Path objects on Python < 3.4 #2291 [asergi]