From 69ff457f2e22994848eed7373ac83e25f05a0052 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 07:05:03 -0700 Subject: [PATCH 001/267] release notes, docs --- docs/handbook/image-file-formats.rst | 8 +++++++- docs/releasenotes/3.4.0.rst | 29 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 61c7ed607..f03bb6bb8 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -540,6 +540,11 @@ Saving Tiff Images The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: +**save_all** + If true, Pillow will save all frames of the image to a multiframe tiff document. + + .. versionadded:: 3.4.0 + **tiffinfo** A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` object or dict object containing tiff tags and values. The TIFF field type is @@ -659,9 +664,10 @@ DDS DDS is a popular container texture format used in video games and natively supported by DirectX. -Currently, only DXT1 and DXT5 pixel formats are supported and only in ``RGBA`` +Currently, DXT1, DXT3, and DXT5 pixel formats are supported and only in ``RGBA`` mode. +.. versionadded:: 3.4.0 DXT3 FLI, FLC ^^^^^^^^ diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index ef4db3322..a6512fc12 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -14,6 +14,20 @@ two times shorter window than ``BILINEAR``. It can be used for image reduction providing the image downscaling quality comparable to ``BICUBIC``. Both new filters don't show good quality for the image upscaling. +Deprecation Warning when Saving JPEGs +===================================== + +JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 +silently drops the alpha channel. With this release Pillow will now +issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode +image as a JPEG. This will become an error in Pillow 3.7. + +New DDS Decoders +================ + +Pillow can now decode DXT3 images, as well as the previously support +DXT1 and DXT5 formats. All three formats are now decoded in C code for +better performance. Append images to GIF ==================== @@ -26,3 +40,18 @@ Note that the ``append_images`` argument is only used if ``save_all`` is also in effect, e.g.:: im.save(out, save_all=True, append_images=[im1, im2, ...]) + +Save multiple frame TIFF +======================== + +Multiple frames can now be saved in a TIFF file by using the ``save_all`` option. +e.g.:: + + im.save("filename.tiff", format="TIFF", save_all=True) + +Image.core.open_ppm removed +=========================== + +The nominally private/debugging function ``Image.core.open_ppm`` has +been removed. If you were using this function, please use +``Image.open`` instead. From 5d8a0be45aad78c5a22c8d099118ee26ef8144af Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 25 Sep 2016 10:44:22 +0100 Subject: [PATCH 002/267] Memory error in Storage.c when accepting negative image size arguments --- Tests/images/negative_size.ppm | Bin 0 -> 19 bytes Tests/test_file_ppm.py | 12 ++++++++++++ libImaging/Storage.c | 4 ++++ 3 files changed, 16 insertions(+) create mode 100755 Tests/images/negative_size.ppm diff --git a/Tests/images/negative_size.ppm b/Tests/images/negative_size.ppm new file mode 100755 index 0000000000000000000000000000000000000000..257b8c29c8ead2324a99166bfb6af1ed1fb0e5a5 GIT binary patch literal 19 UcmWGAGd5B%Hno5NAV-l404Om7!T Date: Thu, 29 Sep 2016 07:05:00 -0700 Subject: [PATCH 003/267] Map.c overflow fixes --- Tests/images/l2rgb_read.bmp | Bin 0 -> 57 bytes Tests/test_map.py | 25 +++++++++++++++++++++++++ map.c | 10 ++++++++++ 3 files changed, 35 insertions(+) create mode 100644 Tests/images/l2rgb_read.bmp create mode 100644 Tests/test_map.py diff --git a/Tests/images/l2rgb_read.bmp b/Tests/images/l2rgb_read.bmp new file mode 100644 index 0000000000000000000000000000000000000000..838e3226b07aa7214876e6fed83681b61c743a68 GIT binary patch literal 57 kcmZ?rHGqNt|LZjv7#M(D1_Ln70wlqFm SIZE_MAX bytes in the image or if + # the file encodes an offset that makes + # (offset + size(bytes)) > SIZE_MAX + + # Note that this image triggers the decompression bomb warning: + max_pixels = Image.MAX_IMAGE_PIXELS + Image.MAX_IMAGE_PIXELS = None + + # This image hits the offset test. + im = Image.open('Tests/images/l2rgb_read.bmp') + with self.assertRaises((ValueError, MemoryError)): + im.load() + + Image.MAX_IMAGE_PIXELS = max_pixels + + +if __name__ == '__main__': + unittest.main() diff --git a/map.c b/map.c index 7309a7bd7..3637ee86a 100644 --- a/map.c +++ b/map.c @@ -342,8 +342,18 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) stride = xsize * 4; } + if (ysize > INT_MAX / stride) { + PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize"); + return NULL; + } + size = (Py_ssize_t) ysize * stride; + if (offset > SIZE_MAX - size) { + PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset"); + return NULL; + } + /* check buffer size */ if (PyImaging_GetBuffer(target, &view) < 0) return NULL; From 0f2d6e0cc5ec216c9f740eb119934ccd52d1d757 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 29 Sep 2016 07:37:01 -0700 Subject: [PATCH 004/267] Changes, Release Notes for 3.3.2 --- CHANGES.rst | 9 +++++++++ docs/releasenotes/3.3.2.rst | 40 +++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 3 files changed, 50 insertions(+) create mode 100644 docs/releasenotes/3.3.2.rst diff --git a/CHANGES.rst b/CHANGES.rst index 8cd9f2d1f..a9265f999 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -124,6 +124,15 @@ Changelog (Pillow) - Retain a reference to core image object in PyAccess #2009 [homm] +3.3.2 (2016-10-03) +------------------ + +- Fix negative image sizes in Storage.c #2105 + [wiredfool] + +- Fix integer overflow in map.c #2105 + [wiredfool] + 3.3.1 (2016-08-18) ------------------ diff --git a/docs/releasenotes/3.3.2.rst b/docs/releasenotes/3.3.2.rst new file mode 100644 index 000000000..141413093 --- /dev/null +++ b/docs/releasenotes/3.3.2.rst @@ -0,0 +1,40 @@ + +3.3.2 +===== + +Integer overflow in Map.c +------------------------- + +Pillow prior to 3.3.2 may experience integer overflow errors in map.c +when reading specially crafted image files. This may lead to memory +disclosure or corruption. + +Specifically, when parameters from the image are passed into +``Image.core.map_buffer``, the size of the image was calculated with +``xsize``*``ysize``*``bytes_per_pixel``. This will overflow if the +result is larger than SIZE_MAX. This is possible on a 32-bit system. + +Furthermore this ``size`` value was added to a potentially attacker +provided ``offset`` value and compared to the size of the buffer +without checking for overflow or negative values. + +These values were then used for creating pointers, at which point +Pillow could read the memory and include it in other images. The image +was marked readonly, so Pillow would not ordinarily write to that +memory without duplicating the image first. + +This issue was found by Cris Neckar at Divergent Security. + +Sign Extension in Storage.c +--------------------------- + +Pillow prior to 3.3.2 and PIL 1.1.7 (at least) do not check for +negative image sizes in ``ImagingNew`` in ``Storage.c``. A negative +image size can lead to a smaller allocation than expected, leading to +arbitrary writes. + +This issue was found by Cris Neckar at Divergent Security. + + + + diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 2ffe37980..8c484af44 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -7,6 +7,7 @@ Release Notes :maxdepth: 2 3.4.0 + 3.3.2 3.3.0 3.2.0 3.1.2 From c0d2d2a912c7e70bce8f5b9054f60703b179006d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 02:54:23 -0700 Subject: [PATCH 005/267] whitespace: mixed 8ch tabs + spaces -> spaces --- libImaging/File.c | 134 +++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/libImaging/File.c b/libImaging/File.c index ac9ec3be1..b669ab665 100644 --- a/libImaging/File.c +++ b/libImaging/File.c @@ -30,26 +30,26 @@ ImagingOpenPPM(const char* infile) Imaging im; if (!infile) - return ImagingError_ValueError(NULL); + return ImagingError_ValueError(NULL); fp = fopen(infile, "rb"); if (!fp) - return ImagingError_IOError(); + return ImagingError_IOError(); /* PPM magic */ if (fgetc(fp) != 'P') - goto error; + goto error; switch (fgetc(fp)) { case '4': /* FIXME: 1-bit images are not yet supported */ - goto error; + goto error; case '5': - mode = "L"; - break; + mode = "L"; + break; case '6': - mode = "RGB"; - break; + mode = "RGB"; + break; default: - goto error; + goto error; } i = 0; @@ -59,66 +59,66 @@ ImagingOpenPPM(const char* infile) while (i < 3) { - /* Ignore optional comment fields */ - while (c == '\n') { - c = fgetc(fp); - if (c == '#') { - do { - c = fgetc(fp); - if (c == EOF) - goto error; - } while (c != '\n'); - c = fgetc(fp); - } - } + /* Ignore optional comment fields */ + while (c == '\n') { + c = fgetc(fp); + if (c == '#') { + do { + c = fgetc(fp); + if (c == EOF) + goto error; + } while (c != '\n'); + c = fgetc(fp); + } + } - /* Skip forward to next value */ - while (isspace(c)) - c = fgetc(fp); + /* Skip forward to next value */ + while (isspace(c)) + c = fgetc(fp); - /* And parse it */ - v = 0; - while (isdigit(c)) { - v = v * 10 + (c - '0'); - c = fgetc(fp); - } + /* And parse it */ + v = 0; + while (isdigit(c)) { + v = v * 10 + (c - '0'); + c = fgetc(fp); + } - if (c == EOF) - goto error; + if (c == EOF) + goto error; - switch (i++) { - case 0: - x = v; - break; - case 1: - y = v; - break; - case 2: - max = v; - break; - } + switch (i++) { + case 0: + x = v; + break; + case 1: + y = v; + break; + case 2: + max = v; + break; + } } im = ImagingNew(mode, x, y); if (!im) - return NULL; + return NULL; /* if (max != 255) ... FIXME: does anyone ever use this feature? */ if (strcmp(im->mode, "L") == 0) { - /* PPM "L" */ - for (y = 0; y < im->ysize; y++) - if (fread(im->image[y], im->xsize, 1, fp) != 1) - goto error; + /* PPM "L" */ + for (y = 0; y < im->ysize; y++) + if (fread(im->image[y], im->xsize, 1, fp) != 1) + goto error; } else { - /* PPM "RGB" or PyPPM mode */ - for (y = 0; y < im->ysize; y++) - for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) - if (fread(im->image[y]+i, im->bands, 1, fp) != 1) - goto error; + /* PPM "RGB" or PyPPM mode */ + for (y = 0; y < im->ysize; y++) + for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) + if (fread(im->image[y]+i, im->bands, 1, fp) != 1) + goto error; } fclose(fp); @@ -140,16 +140,16 @@ ImagingSaveRaw(Imaging im, FILE* fp) /* @PIL227: FIXME: for mode "1", map != 0 to 255 */ - /* PGM "L" */ - for (y = 0; y < im->ysize; y++) - fwrite(im->image[y], 1, im->xsize, fp); + /* PGM "L" */ + for (y = 0; y < im->ysize; y++) + fwrite(im->image[y], 1, im->xsize, fp); } else { - /* PPM "RGB" or other internal format */ - for (y = 0; y < im->ysize; y++) - for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) - fwrite(im->image[y]+i, 1, im->bands, fp); + /* PPM "RGB" or other internal format */ + for (y = 0; y < im->ysize; y++) + for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) + fwrite(im->image[y]+i, 1, im->bands, fp); } @@ -163,24 +163,24 @@ ImagingSavePPM(Imaging im, const char* outfile) FILE* fp; if (!im) { - (void) ImagingError_ValueError(NULL); - return 0; + (void) ImagingError_ValueError(NULL); + return 0; } fp = fopen(outfile, "wb"); if (!fp) { - (void) ImagingError_IOError(); - return 0; + (void) ImagingError_IOError(); + return 0; } if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { - /* Write "PGM" */ - fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize); + /* Write "PGM" */ + fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize); } else if (strcmp(im->mode, "RGB") == 0) { /* Write "PPM" */ fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); } else { - (void) ImagingError_ModeError(); + (void) ImagingError_ModeError(); return 0; } From 1a43da7a8bda884a597f3a1623364f4719d21c14 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 03:09:54 -0700 Subject: [PATCH 006/267] Removed 'Debugging' open_ppm call that didn't check file provided parameters for sanity --- PIL/EpsImagePlugin.py | 5 +- PIL/IptcImagePlugin.py | 11 ++--- PIL/JpegImagePlugin.py | 4 +- PIL/PpmImagePlugin.py | 5 -- _imaging.c | 14 ------ libImaging/File.c | 110 ----------------------------------------- 6 files changed, 9 insertions(+), 140 deletions(-) diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 9a668125a..77a7e7e1c 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -145,7 +145,8 @@ def Ghostscript(tile, size, fp, scale=1): status = gs.wait() if status: raise IOError("gs failed (status %d)" % status) - im = Image.core.open_ppm(outfile) + im = Image.open(outfile) + im.load() finally: try: os.unlink(outfile) @@ -154,7 +155,7 @@ def Ghostscript(tile, size, fp, scale=1): except OSError: pass - return im + return im.im.copy() class PSFile(object): diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index ed3bf81b1..1de17cbba 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -168,14 +168,9 @@ class IptcImageFile(ImageFile.ImageFile): o.close() try: - try: - # fast - self.im = Image.core.open_ppm(outfile) - except: - # slightly slower - im = Image.open(outfile) - im.load() - self.im = im.im + _im = Image.open(outfile) + _im.load() + self.im = _im.im finally: try: os.unlink(outfile) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index e216fa06d..ef229e611 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -377,7 +377,9 @@ class JpegImageFile(ImageFile.ImageFile): raise ValueError("Invalid Filename") try: - self.im = Image.core.open_ppm(path) + _im = Image.open(path) + _im.load() + self.im = _im.im finally: try: os.unlink(path) diff --git a/PIL/PpmImagePlugin.py b/PIL/PpmImagePlugin.py index 68073cace..adaf8384c 100644 --- a/PIL/PpmImagePlugin.py +++ b/PIL/PpmImagePlugin.py @@ -123,11 +123,6 @@ class PpmImageFile(ImageFile.ImageFile): self.fp.tell(), (rawmode, 0, 1))] - # ALTERNATIVE: load via builtin debug function - # self.im = Image.core.open_ppm(self.filename) - # self.mode = self.im.mode - # self.size = self.im.size - # # -------------------------------------------------------------------- diff --git a/_imaging.c b/_imaging.c index ef5d346ba..47334f184 100644 --- a/_imaging.c +++ b/_imaging.c @@ -686,17 +686,6 @@ _radial_gradient(PyObject* self, PyObject* args) return PyImagingNew(ImagingFillRadialGradient(mode)); } -static PyObject* -_open_ppm(PyObject* self, PyObject* args) -{ - char* filename; - - if (!PyArg_ParseTuple(args, "s", &filename)) - return NULL; - - return PyImagingNew(ImagingOpenPPM(filename)); -} - static PyObject* _alpha_composite(ImagingObject* self, PyObject* args) { @@ -3424,9 +3413,6 @@ static PyMethodDef functions[] = { {"crc32", (PyCFunction)_crc32, 1}, {"getcodecstatus", (PyCFunction)_getcodecstatus, 1}, - /* Debugging stuff */ - {"open_ppm", (PyCFunction)_open_ppm, 1}, - /* Special effects (experimental) */ #ifdef WITH_EFFECTS {"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, 1}, diff --git a/libImaging/File.c b/libImaging/File.c index b669ab665..d67bcabde 100644 --- a/libImaging/File.c +++ b/libImaging/File.c @@ -20,116 +20,6 @@ #include -Imaging -ImagingOpenPPM(const char* infile) -{ - FILE* fp; - int i, c, v; - char* mode; - int x, y, max; - Imaging im; - - if (!infile) - return ImagingError_ValueError(NULL); - - fp = fopen(infile, "rb"); - if (!fp) - return ImagingError_IOError(); - - /* PPM magic */ - if (fgetc(fp) != 'P') - goto error; - switch (fgetc(fp)) { - case '4': /* FIXME: 1-bit images are not yet supported */ - goto error; - case '5': - mode = "L"; - break; - case '6': - mode = "RGB"; - break; - default: - goto error; - } - - i = 0; - c = fgetc(fp); - - x = y = max = 0; - - while (i < 3) { - - /* Ignore optional comment fields */ - while (c == '\n') { - c = fgetc(fp); - if (c == '#') { - do { - c = fgetc(fp); - if (c == EOF) - goto error; - } while (c != '\n'); - c = fgetc(fp); - } - } - - /* Skip forward to next value */ - while (isspace(c)) - c = fgetc(fp); - - /* And parse it */ - v = 0; - while (isdigit(c)) { - v = v * 10 + (c - '0'); - c = fgetc(fp); - } - - if (c == EOF) - goto error; - - switch (i++) { - case 0: - x = v; - break; - case 1: - y = v; - break; - case 2: - max = v; - break; - } - } - - im = ImagingNew(mode, x, y); - if (!im) - return NULL; - - /* if (max != 255) ... FIXME: does anyone ever use this feature? */ - - if (strcmp(im->mode, "L") == 0) { - - /* PPM "L" */ - for (y = 0; y < im->ysize; y++) - if (fread(im->image[y], im->xsize, 1, fp) != 1) - goto error; - - } else { - - /* PPM "RGB" or PyPPM mode */ - for (y = 0; y < im->ysize; y++) - for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) - if (fread(im->image[y]+i, im->bands, 1, fp) != 1) - goto error; - } - - fclose(fp); - - return im; - -error: - fclose(fp); - return ImagingError_IOError(); -} - int ImagingSaveRaw(Imaging im, FILE* fp) From 445451c0b9347b50e0f603db33f196e207de470d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 03:38:15 -0700 Subject: [PATCH 007/267] Added common check for size tuple errors --- PIL/Image.py | 22 ++++++++++++++++++++++ Tests/test_image.py | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index f20640c19..bbfee34b6 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1985,6 +1985,22 @@ def _wedge(): return Image()._new(core.wedge("L")) +def _check_size(size): + """ + Common check to enforce type and sanity check on size tuples + + :param size: Should be a 2 tuple of (width, height) + :returns: True, or raises a ValueError + """ + + if not isinstance(size, tuple): + raise ValueError("Size must be a tuple") + if len(size) != 2: + raise ValueError("Size must be a tuple of length 2") + if size[0] <= 0 or size[1] <= 0: + raise ValueError("Width and Height must be > 0") + + return True def new(mode, size, color=0): """ @@ -2002,6 +2018,8 @@ def new(mode, size, color=0): :returns: An :py:class:`~PIL.Image.Image` object. """ + _check_size(size) + if color is None: # don't initialize return Image()._new(core.new(mode, size)) @@ -2039,6 +2057,8 @@ def frombytes(mode, size, data, decoder_name="raw", *args): :returns: An :py:class:`~PIL.Image.Image` object. """ + _check_size(size) + # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): args = args[0] @@ -2091,6 +2111,8 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): .. versionadded:: 1.1.4 """ + _check_size(size) + # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): args = args[0] diff --git a/Tests/test_image.py b/Tests/test_image.py index b3fbd508d..1a67d79c8 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -237,5 +237,17 @@ class TestImage(PillowTestCase): im3 = Image.open('Tests/images/effect_spread.png') self.assert_image_similar(im2, im3, 110) + def test_check_size(self): + # Checking that the _check_size function throws value errors when we want it to. + with self.assertRaises(ValueError): + Image.new('RGB', 0) # not a tuple + with self.assertRaises(ValueError): + Image.new('RGB', (0,)) # Tuple too short + with self.assertRaises(ValueError): + Image.new('RGB', (0,0)) # w,h <= 0 + + self.assertTrue(Image.new('RGB', (1,1))) + + if __name__ == '__main__': unittest.main() From b3ad80a2bd81e98b3f16c2201749f3e45dd73426 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 03:50:25 -0700 Subject: [PATCH 008/267] Image.core.open_ppm has been removed. Test the Storage.c fix with an alternate method. Assert that the ordinary opener rejects the negative size in the PPM file --- Tests/test_file_ppm.py | 14 ++++++-------- Tests/test_image.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 9284d422a..817a62393 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -45,15 +45,13 @@ class TestFilePpm(PillowTestCase): def test_neg_ppm(self): - """test_neg_ppm - - Storage.c accepted negative values for xsize, ysize. - open_ppm is a core debugging item that doesn't check any parameters for - sanity. - """ + # Storage.c accepted negative values for xsize, ysize. the + # internal open_ppm function didn't check for sanity but it + # has been removed. The default opener doesn't accept negative + # sizes. - with self.assertRaises(ValueError): - Image.core.open_ppm('Tests/images/negative_size.ppm') + with self.assertRaises(IOError): + Image.open('Tests/images/negative_size.ppm') if __name__ == '__main__': diff --git a/Tests/test_image.py b/Tests/test_image.py index 1a67d79c8..8657bd2e6 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -247,6 +247,18 @@ class TestImage(PillowTestCase): Image.new('RGB', (0,0)) # w,h <= 0 self.assertTrue(Image.new('RGB', (1,1))) + + def test_storage_neg(self): + # Storage.c accepted negative values for xsize, ysize. Was + # test_neg_ppm, but the core function for that has been + # removed Calling directly into core to test the error in + # Storage.c, rather than the size check above + + with self.assertRaises(ValueError): + Image.core.fill('RGB', (2,-2), (0,0,0)) + + + if __name__ == '__main__': From aa8cfce94c9ba054068baf870f82a0608a5395ae Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 07:33:29 -0700 Subject: [PATCH 009/267] IOError is also a valid error here --- Tests/test_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_map.py b/Tests/test_map.py index 235bfadbc..d9b66e965 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -15,7 +15,7 @@ class TestMap(PillowTestCase): # This image hits the offset test. im = Image.open('Tests/images/l2rgb_read.bmp') - with self.assertRaises((ValueError, MemoryError)): + with self.assertRaises((ValueError, MemoryError, IOError)): im.load() Image.MAX_IMAGE_PIXELS = max_pixels From 22ff3f435879683b9d7896c0a2e15d053205a1ca Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 07:42:48 -0700 Subject: [PATCH 010/267] Vulnerable map function is not called on windows --- PIL/ImageFile.py | 2 +- Tests/test_map.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index a66452478..55cb701a1 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -154,7 +154,7 @@ class ImageFile(Image.Image): if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES: try: if hasattr(Image.core, "map"): - # use built-in mapper + # use built-in mapper WIN32 only self.map = Image.core.map(self.filename) self.map.seek(o) self.im = self.map.readimage( diff --git a/Tests/test_map.py b/Tests/test_map.py index d9b66e965..14bd835a2 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,7 +1,10 @@ from helper import PillowTestCase, unittest +import sys from PIL import Image + +@unittest.skipIf(sys.platform.startswith('win32'), "Win32 does not call map_buffer") class TestMap(PillowTestCase): def test_overflow(self): # There is the potential to overflow comparisons in map.c From 5683f033fdaf8cc7318b4bea12c6251786b26159 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 15:59:12 +0100 Subject: [PATCH 011/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a9265f999..e69522244 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Removed Image.core.open_ppm, added negative image size checks in Image.py. #2146 + [wiredfool] + - Windows build: fetch dependencies from pillow-depends #2095 [hugovk] From ac5a212cee478f7f1448080d47c2c9daa29e297f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 3 Oct 2016 07:57:25 -0700 Subject: [PATCH 012/267] 3.4.0 Release Version bump --- CHANGES.rst | 2 +- PIL/__init__.py | 2 +- _imaging.c | 2 +- appveyor.yml | 2 +- setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e69522244..b76e82bf4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog (Pillow) ================== -3.4.0 (unreleased) +3.4.0 (2016-10-03) ------------------ - Removed Image.core.open_ppm, added negative image size checks in Image.py. #2146 diff --git a/PIL/__init__.py b/PIL/__init__.py index 561a13a67..f1f413d4d 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '3.4.0.dev0' # Pillow +PILLOW_VERSION = '3.4.0' # Pillow __version__ = PILLOW_VERSION diff --git a/_imaging.c b/_imaging.c index 47334f184..8a69aafd6 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "3.4.0.dev0" +#define PILLOW_VERSION "3.4.0" #include "Python.h" diff --git a/appveyor.yml b/appveyor.yml index 826c4c1a5..7e0674d80 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.4.pre.{build} +version: 3.4.0.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% diff --git a/setup.py b/setup.py index 024780d3b..685097f9e 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '3.4.0.dev0' +PILLOW_VERSION = '3.4.0' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From 6d2b7626f3657e8da0d707ec87845d752909d9d4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 3 Oct 2016 18:17:20 +0300 Subject: [PATCH 013/267] Revert "Work around pycparser issue failing our builds" --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ac51aa4e8..586ac7fcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,6 @@ python: - nightly install: - - "travis_retry pip install pycparser!=2.14" # TEMPORARY, WORKAROUND FOR https://github.com/eliben/pycparser/issues/147 - "travis_retry sudo apt-get update" - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" - "travis_retry pip install cffi" From c5e111e6b8b9633bce046a988c9f6e49f2358627 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 4 Oct 2016 03:06:35 +0300 Subject: [PATCH 014/267] allow lists as arguments for Image.new --- PIL/Image.py | 6 +++--- Tests/test_image.py | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index bbfee34b6..a23a7d92c 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1020,7 +1020,7 @@ class Image(object): 4-tuple defining the left, upper, right, and lower pixel coordinate. - Note: Prior to Pillow 3.4.0, this was a lazy operation. + Note: Prior to Pillow 3.4.0, this was a lazy operation. :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. :rtype: :py:class:`~PIL.Image.Image` @@ -1993,7 +1993,7 @@ def _check_size(size): :returns: True, or raises a ValueError """ - if not isinstance(size, tuple): + if not isinstance(size, (list, tuple)): raise ValueError("Size must be a tuple") if len(size) != 2: raise ValueError("Size must be a tuple of length 2") @@ -2019,7 +2019,7 @@ def new(mode, size, color=0): """ _check_size(size) - + if color is None: # don't initialize return Image()._new(core.new(mode, size)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 8657bd2e6..ce7c928b9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -238,7 +238,7 @@ class TestImage(PillowTestCase): self.assert_image_similar(im2, im3, 110) def test_check_size(self): - # Checking that the _check_size function throws value errors when we want it to. + # Checking that the _check_size function throws value errors when we want it to. with self.assertRaises(ValueError): Image.new('RGB', 0) # not a tuple with self.assertRaises(ValueError): @@ -247,19 +247,21 @@ class TestImage(PillowTestCase): Image.new('RGB', (0,0)) # w,h <= 0 self.assertTrue(Image.new('RGB', (1,1))) + # Should pass lists too + self.assertTrue(Image.new('RGB', [1,1])) def test_storage_neg(self): # Storage.c accepted negative values for xsize, ysize. Was # test_neg_ppm, but the core function for that has been # removed Calling directly into core to test the error in # Storage.c, rather than the size check above - + with self.assertRaises(ValueError): Image.core.fill('RGB', (2,-2), (0,0,0)) - + if __name__ == '__main__': unittest.main() From 923f0bb9b3fc935c609a72d85372238b55fcca37 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 4 Oct 2016 03:11:53 +0300 Subject: [PATCH 015/267] improve test --- Tests/test_image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index ce7c928b9..6b24a988b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -248,7 +248,8 @@ class TestImage(PillowTestCase): self.assertTrue(Image.new('RGB', (1,1))) # Should pass lists too - self.assertTrue(Image.new('RGB', [1,1])) + i = Image.new('RGB', [1,1]) + self.assertEqual(type(i.size), tuple) def test_storage_neg(self): # Storage.c accepted negative values for xsize, ysize. Was From 38f2b0e9d1a6ef814acb0ef855296b31040c9b83 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 4 Oct 2016 07:59:02 +0100 Subject: [PATCH 016/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b76e82bf4..688c6e0af 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ Changelog (Pillow) ================== +3.4.1 (2016-10-0?) +------------------ + +- Allow lists as arguments for Image.new() #2149 + [homm] + + 3.4.0 (2016-10-03) ------------------ From 35e1237b7eebdf06a4fe14cd60f9dfea24396d1c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 4 Oct 2016 00:18:33 -0700 Subject: [PATCH 017/267] 3.5.0.dev0 Dev Version bump --- PIL/__init__.py | 2 +- _imaging.c | 2 +- appveyor.yml | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index f1f413d4d..be672e825 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '3.4.0' # Pillow +PILLOW_VERSION = '3.5.0.dev0' # Pillow __version__ = PILLOW_VERSION diff --git a/_imaging.c b/_imaging.c index 8a69aafd6..3c109b88c 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "3.4.0" +#define PILLOW_VERSION "3.5.0.dev0" #include "Python.h" diff --git a/appveyor.yml b/appveyor.yml index 7e0674d80..2b58eeda3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.4.0.{build} +version: 3.5.pre.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% diff --git a/setup.py b/setup.py index 685097f9e..f9ae061b8 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '3.4.0' +PILLOW_VERSION = '3.5.0.dev0' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From d6193e2cfa30fd812b3ce7fa5d88ced6ff656f61 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 4 Oct 2016 15:14:23 +0100 Subject: [PATCH 018/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 688c6e0af..e66c6fc4e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,12 +1,14 @@ Changelog (Pillow) ================== -3.4.1 (2016-10-0?) +3.4.1 (2016-10-04) ------------------ - Allow lists as arguments for Image.new() #2149 [homm] - + +- Fix fix for map.c overflow #2151 (also in 3.3.3) + [wiredfool] 3.4.0 (2016-10-03) ------------------ From 7d6f4104a114d106cd9502e040a78e793fb72393 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 4 Oct 2016 14:40:22 +0100 Subject: [PATCH 019/267] Map.c check should be against PY_SSIZE_T_MAX (#2151) --- map.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map.c b/map.c index 3637ee86a..75f463440 100644 --- a/map.c +++ b/map.c @@ -349,7 +349,7 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) size = (Py_ssize_t) ysize * stride; - if (offset > SIZE_MAX - size) { + if (offset > PY_SSIZE_T_MAX - size) { PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset"); return NULL; } From 628f4ac5aea5f2741361ef258c79f68e23996a99 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Oct 2016 19:33:35 +1100 Subject: [PATCH 020/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e66c6fc4e..d3471b45f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ Changelog (Pillow) - Allow lists as arguments for Image.new() #2149 [homm] - + - Fix fix for map.c overflow #2151 (also in 3.3.3) [wiredfool] @@ -39,10 +39,10 @@ Changelog (Pillow) - Force reloading palette when using mmap in ImageFile. #2139 [lambdafu] - + - Fix "invalid escape sequence" warning in Python 3.6 #2136 [timgraham] - + - Update documentation about drafts #2137 [radarhere] @@ -51,10 +51,10 @@ Changelog (Pillow) - Fixed typos #2128 #2142 [radarhere] - + - Renamed references to OS X to macOS #2125 2130 [radarhere] - + - Use truth value when checking for progressive and optimize option on save #2115, #2129 [radarhere] @@ -64,7 +64,7 @@ Changelog (Pillow) - Added append_images parameter to GIF saving #2103 [radarhere] -- Speedup paste with masks up to 80% #2015 +- Speedup paste with masks up to 80% #2015 [homm] - Rewrite DDS decoders in C, add DXT3 and BC7 decoders #2068 @@ -136,12 +136,18 @@ Changelog (Pillow) - Retain a reference to core image object in PyAccess #2009 [homm] +3.3.3 (2016-10-04) +------------------ + +- Fix fix for map.c overflow #2151 + [wiredfool] + 3.3.2 (2016-10-03) ------------------ - Fix negative image sizes in Storage.c #2105 [wiredfool] - + - Fix integer overflow in map.c #2105 [wiredfool] From f59b708f3a7e128543e1567afb0828ab7afd8fd6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Oct 2016 20:43:54 +1100 Subject: [PATCH 021/267] Fixed typo --- PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageGrab.py b/PIL/ImageGrab.py index 55fb014c4..03283f3d6 100644 --- a/PIL/ImageGrab.py +++ b/PIL/ImageGrab.py @@ -41,7 +41,7 @@ def grab(bbox=None): size, data = grabber() im = Image.frombytes( "RGB", size, data, - # RGB, 32-bit line padding, origo in lower left corner + # RGB, 32-bit line padding, origin lower left corner "raw", "BGR", (size[0]*3 + 3) & -4, -1 ) if bbox: From 00eb6d6b3718c104829c1fc1d3bc92f4ec3b5f36 Mon Sep 17 00:00:00 2001 From: David McInnis Date: Sun, 16 Oct 2016 20:20:58 -0700 Subject: [PATCH 022/267] added arch linux support --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 35b4e54fd..60172db27 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -365,6 +365,8 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Gentoo Linux |Yes | 2.7,3.2 | 2.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ +| Arch Linux |Yes | 2.7,3.5 | 3.4.1 |x86,x86-64 | ++----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | FreeBSD 10.2 |Yes | 2.7,3.4 | 3.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Windows 7 Pro |Yes | 2.7,3.2,3.3 | 2.2.1 |x86-64 | From ab2ac2f7f96bd59b2a8e85bf4412dcf5250fe60f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 17 Oct 2016 09:04:20 +0100 Subject: [PATCH 023/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d3471b45f..5f1e9deb4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog (Pillow) ================== +3.5.0 (unreleased) +------------------ + +- Update compatibility matrix + [daavve] + 3.4.1 (2016-10-04) ------------------ From e9f2794786722f9751797f34f3f559c33324b1d1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 17 Oct 2016 09:12:19 +0100 Subject: [PATCH 024/267] Update Compatibility Matrix [ci skip] --- docs/installation.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 60172db27..4ee43a990 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -336,7 +336,7 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Supported**|**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Mac OS X 10.11 El Capitan |Yes | 2.7,3.3,3.4,3.5 | 3.3.0 |x86-64 | +| Mac OS X 10.11 El Capitan |Yes | 2.7,3.3,3.4,3.5 | 3.4.1 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Mac OS X 10.10 Yosemite |Yes | 2.7,3.3,3.4 | 3.0.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ @@ -352,10 +352,10 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Ubuntu Linux 10.04 LTS |Yes | 2.6 | 2.3.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Ubuntu Linux 12.04 LTS |Yes | 2.6,2.7,3.2,3.3,3.4,3.5 | 3.1.0 |x86,x86-64 | -| | | PyPy2.4,PyPy3,v2.3 | | | +| Ubuntu Linux 12.04 LTS |Yes | 2.6,2.7,3.2,3.3,3.4,3.5 | 3.4.1 (CI target) |x86,x86-64 | +| | | PyPy5.3.1,PyPy3 v2.4.0 | | | | | | | | | -| | | 2.7,3.2 | 2.6.1 |ppc | +| | | 2.7,3.2 | 3.4.1 |ppc | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Ubuntu Linux 14.04 LTS |Yes | 2.7,3.4 | 3.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ @@ -369,11 +369,11 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | FreeBSD 10.2 |Yes | 2.7,3.4 | 3.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Windows 7 Pro |Yes | 2.7,3.2,3.3 | 2.2.1 |x86-64 | +| Windows 7 Pro |Yes | 2.7,3.2,3.3 | 3.4.1 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Windows Server 2008 R2 Enterprise|Yes | 3.3 | |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Windows Server 2012 R2 |Yes | 2.7,3.3,3.4 | 3.0.0 |x86-64 | +| Windows Server 2012 R2 |Yes | 2.7,3.3,3.4 | 3.4.1 (CI target) |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Windows 8 Pro |Yes | 2.6,2.7,3.2,3.3,3.4a3 | 2.2.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ From f3879b9f9ff7d5b3acb9a0a9faa9ef58142c22aa Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 17 Oct 2016 09:12:54 +0100 Subject: [PATCH 025/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5f1e9deb4..00583c7d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) ------------------ - Update compatibility matrix - [daavve] + [daavve, wiredfool] 3.4.1 (2016-10-04) ------------------ From 5a359fbf287d6962def2d0d482d2570625c90bf6 Mon Sep 17 00:00:00 2001 From: Alexander Karpinsky Date: Mon, 17 Oct 2016 11:24:40 +0300 Subject: [PATCH 026/267] Fix coefficients calculation (#2162) Fix coefficients calculation * test for regression * detailed comments what is going on prevent setting the `k[-1]` item * more readable --- Tests/test_image_resample.py | 11 +++++++++++ libImaging/Resample.c | 11 ++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 3c8afd8ea..bd763addb 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -336,6 +336,17 @@ class CoreResampleCoefficientsTest(PillowTestCase): self.assertEqual(test_color // 2, px[2, 0]) # print '\r>', size, test_color // 2, px[2, 0] + def test_nonzero_coefficients(self): + # regression test for the wrong coefficients calculation + # due to bug https://github.com/python-pillow/Pillow/issues/2161 + im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff)) + histogram = im.resize((256, 256), Image.BICUBIC).histogram() + + self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) # first channel + self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) # second channel + self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) # third channel + self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) # fourth channel + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Resample.c b/libImaging/Resample.c index 5f202bc3f..770f5f611 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -176,18 +176,23 @@ precompute_coeffs(int inSize, int outSize, struct filter *filterp, k = &kk[xx * kmax]; for (x = 0; x < xmax; x++) { double w = filterp->filter((x + xmin - center + 0.5) * ss); + k[x] = w; + ww += w; + + // We can skip extreme coefficients if they are zeroes. if (w == 0) { + // Skip from the start. if (x == 0) { + // At next loop `x` will be 0. x -= 1; + // But `w` will not be 0, because it based on `xmin`. xmin += 1; xmax -= 1; } else if (x == xmax - 1) { + // Truncate the last coefficient for current `xx`. xmax -= 1; } - continue; } - k[x] = w; - ww += w; } for (x = 0; x < xmax; x++) { if (ww != 0.0) From eb3b9618cdbd4d47df9e6bcdc5abb5ce27424f24 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 17 Oct 2016 01:31:05 -0700 Subject: [PATCH 027/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 00583c7d5..988ab88e0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,14 @@ Changelog (Pillow) - Update compatibility matrix [daavve, wiredfool] + +3.4.2 (2016-10-18) +------------------ + +- Fix Resample coefficient calculation #2161 + [homm] + + 3.4.1 (2016-10-04) ------------------ From 74f751037f806edd8cd506fd95e33c6bfbee1cf9 Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Mon, 17 Oct 2016 11:45:54 -0400 Subject: [PATCH 028/267] Divide floats to eliminate deprecation warning. When running python 2.7 with the `-3` flag the following warning occurs > .../PIL/Image.py:48: DeprecationWarning: classic int division MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3) Simply changing the 4 and 3 to be floats instead eliminates the warning and, because the result is cast, the resulting `int` stays the same for python 2 and 3. --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index a23a7d92c..fa743c602 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -46,7 +46,7 @@ class _imaging_not_installed(object): # Limit to around a quarter gigabyte for a 24 bit (3 bpp) image -MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3) +MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3) try: # give Tk a chance to set up the environment, in case we're From 7992d2a65ae4d3917aeae7c1e9ee5140befbadcf Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 22 Oct 2016 10:55:50 -0700 Subject: [PATCH 029/267] Fix BytesWarning discovered while running tests Discovered using the command: python -b -m nose -vx Tests/test_*.py --- PIL/GifImagePlugin.py | 2 +- PIL/ImtImagePlugin.py | 2 +- PIL/PngImagePlugin.py | 10 +++++----- Tests/test_file_gif.py | 2 +- Tests/test_file_tiff.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index df17f830b..8b2dfac0a 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -696,7 +696,7 @@ def getheader(im, palette=None, info=None): version = b"89a" break else: - if im.info.get("version") == "89a": + if im.info.get("version") == b"89a": version = b"89a" header = [ diff --git a/PIL/ImtImagePlugin.py b/PIL/ImtImagePlugin.py index 63e892483..e95f7aeba 100644 --- a/PIL/ImtImagePlugin.py +++ b/PIL/ImtImagePlugin.py @@ -69,7 +69,7 @@ class ImtImageFile(ImageFile.ImageFile): s = s + self.fp.readline() if len(s) == 1 or len(s) > 100: break - if s[0] == b"*": + if s[0] == ord(b"*"): continue # comment m = field.match(s) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 4975d5598..a5f4c3f42 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -132,7 +132,7 @@ class ChunkStream(object): def call(self, cid, pos, length): "Call the appropriate chunk handler" - logger.debug("STREAM %s %s %s", cid, pos, length) + logger.debug("STREAM %r %s %s", cid, pos, length) return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length) def crc(self, cid, data): @@ -148,10 +148,10 @@ class ChunkStream(object): crc1 = Image.core.crc32(data, Image.core.crc32(cid)) crc2 = i16(self.fp.read(2)), i16(self.fp.read(2)) if crc1 != crc2: - raise SyntaxError("broken PNG file (bad header checksum in %s)" + raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) except struct.error: - raise SyntaxError("broken PNG file (incomplete checksum in %s)" + raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid) def crc_skip(self, cid, data): @@ -309,7 +309,7 @@ class PngStream(ChunkStream): # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) i = s.find(b"\0") - logger.debug("iCCP profile name %s", s[:i]) + logger.debug("iCCP profile name %r", s[:i]) logger.debug("Compression method %s", i8(s[i])) comp_method = i8(s[i]) if comp_method != 0: @@ -539,7 +539,7 @@ class PngImageFile(ImageFile.ImageFile): except EOFError: break except AttributeError: - logger.debug("%s %s %s (unknown)", cid, pos, length) + logger.debug("%r %s %s (unknown)", cid, pos, length) s = ImageFile._safe_read(self.fp, length) self.png.crc(cid, s) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index dbe4f34fd..83318366b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -338,7 +338,7 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info["version"], b"GIF87a") # Test that a GIF89a image is also saved in that format - im.info["version"] = "GIF89a" + im.info["version"] = b"GIF89a" im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ad7fad7c8..9913860ad 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -119,7 +119,7 @@ class TestFileTiff(PillowTestCase): self.assertRaises(SyntaxError, lambda: TiffImagePlugin.TiffImageFile(invalid_file)) - TiffImagePlugin.PREFIXES.append("\xff\xd8\xff\xe0") + TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0") self.assertRaises(SyntaxError, lambda: TiffImagePlugin.TiffImageFile(invalid_file)) TiffImagePlugin.PREFIXES.pop() From 0be156936a1a4218124200f7429da2330c959dd6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 23 Oct 2016 12:44:23 +1100 Subject: [PATCH 030/267] Updated setup url to https --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f9ae061b8..482d7ed91 100644 --- a/setup.py +++ b/setup.py @@ -723,7 +723,7 @@ setup(name=NAME, long_description=_read('README.rst').decode('utf-8'), author='Alex Clark (Fork Author)', author_email='aclark@aclark.net', - url='http://python-pillow.org', + url='https://python-pillow.org', classifiers=[ "Development Status :: 6 - Mature", "Topic :: Multimedia :: Graphics", From 47e2798895d24d9af4b165f0404e348ff111b773 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 25 Oct 2016 10:08:40 +0100 Subject: [PATCH 031/267] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 988ab88e0..a601b9c77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix BytesWarnings #2172 + [jdufresne] + +- Use Integer division to eliminate deprecation warning. #2168 + [mastermatt] + - Update compatibility matrix [daavve, wiredfool] From 13429c8c363016791e5681d0a7e645911e5aed3a Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 25 Oct 2016 15:23:07 +0300 Subject: [PATCH 032/267] Fix too-short title underlines and malformed table https://travis-ci.org/python-pillow/Pillow/jobs/170394881#L3453 --- docs/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 4ee43a990..2ada37c03 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -59,7 +59,7 @@ or:: macOS Installation -^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ We provide binaries for macOS for each of the supported Python versions in the wheel format. These include support for all optional libraries @@ -231,7 +231,7 @@ or using pip:: Building on macOS -^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ The Xcode command line tools are required to compile portions of Pillow. The tools are installed by running ``xcode-select --install`` @@ -373,7 +373,7 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Windows Server 2008 R2 Enterprise|Yes | 3.3 | |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Windows Server 2012 R2 |Yes | 2.7,3.3,3.4 | 3.4.1 (CI target) |x86,x86-64 | +| Windows Server 2012 R2 |Yes | 2.7,3.3,3.4 | 3.4.1 (CI target) |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Windows 8 Pro |Yes | 2.6,2.7,3.2,3.3,3.4a3 | 2.2.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ From 7dbb1a5c99be3b2e036ef4d4f44bce6294286962 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 25 Oct 2016 15:24:19 +0300 Subject: [PATCH 033/267] Fix too-short title underline https://travis-ci.org/python-pillow/Pillow/jobs/170394881#L3517 --- docs/reference/ImageGrab.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 8711c761d..39aaef6bc 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -2,7 +2,7 @@ .. py:currentmodule:: PIL.ImageGrab :py:mod:`ImageGrab` Module (macOS and Windows only) -================================================== +=================================================== The :py:mod:`ImageGrab` module can be used to copy the contents of the screen or the clipboard to a PIL image memory. From 9a301236d90fa3f44c673ec271e4851e1bd40e2e Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 25 Oct 2016 15:33:54 +0300 Subject: [PATCH 034/267] Reinstate tests on nightly --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 586ac7fcd..2ffd18a5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,12 +49,12 @@ install: - pushd depends && ./install_imagequant.sh && popd script: - - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi + - coverage erase - python setup.py clean - CFLAGS="-coverage" python setup.py build_ext --inplace - - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi + - coverage run --append --include=PIL/* selftest.py + - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py - pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd # Docs From abc4d55f08a61be8e1fb7815b56423c0dff268c1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 25 Oct 2016 16:03:51 +0100 Subject: [PATCH 035/267] Update Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a601b9c77..2b36a3b0e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix Doc Formatting, too-short title underlines and malformed table #2175 + [hugovk] + - Fix BytesWarnings #2172 [jdufresne] From efa94a78e6ad868221f30e05d9dee2735cd4a7ba Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Wed, 26 Oct 2016 19:41:40 +0100 Subject: [PATCH 036/267] Move ICO out of the list of read-only file formats Fixes #2179. --- docs/handbook/image-file-formats.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index f03bb6bb8..135fccdd8 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -149,6 +149,19 @@ sets the following :py:attr:`~PIL.Image.Image.info` property: ask for ``(512, 512, 2)``, the final value of :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``). +ICO +^^^ + +ICO is used to store icons on Windows. The largest available icon is read. + +The :py:meth:`~PIL.Image.Image.save` method supports the following options: + +**sizes** + A list of sizes including in this ico file; these are a 2-tuple, + ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48), + (64, 64), (128, 128), (255, 255)]``. Any size is bigger then the original + size or 255 will be ignored. + IM ^^ @@ -730,19 +743,6 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following Transparency color index. This key is omitted if the image is not transparent. -ICO -^^^ - -ICO is used to store icons on Windows. The largest available icon is read. - -The :py:meth:`~PIL.Image.Image.save` method supports the following options: - -**sizes** - A list of sizes including in this ico file; these are a 2-tuple, - ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48), - (64, 64), (128, 128), (255, 255)]``. Any size is bigger then the original - size or 255 will be ignored. - IMT ^^^ From 6d38015474b86ae0fbb7ceed2025dfa6b7aedd06 Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Thu, 27 Oct 2016 06:29:32 +0100 Subject: [PATCH 037/267] Fix typo in description of ICO --- docs/handbook/image-file-formats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 135fccdd8..78840e720 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -159,7 +159,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **sizes** A list of sizes including in this ico file; these are a 2-tuple, ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48), - (64, 64), (128, 128), (255, 255)]``. Any size is bigger then the original + (64, 64), (128, 128), (255, 255)]``. Any sizes bigger then the original size or 255 will be ignored. IM From 42d4b23ed76f88ac9d2c31edce4c7c8c21689fb2 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 27 Oct 2016 13:54:05 +0100 Subject: [PATCH 038/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2b36a3b0e..7aacb5042 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,10 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ -- Fix Doc Formatting, too-short title underlines and malformed table #2175 +- Doc: Move ICO out of the list of read-only file formats #2180 + [alexwlchan] + +- Doc: Fix formatting, too-short title underlines and malformed table #2175 [hugovk] - Fix BytesWarnings #2172 @@ -13,7 +16,7 @@ Changelog (Pillow) - Use Integer division to eliminate deprecation warning. #2168 [mastermatt] -- Update compatibility matrix +- Doc: Update compatibility matrix [daavve, wiredfool] From 8582144e0e99dd6dd6d509170e14bb8f06cec990 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 27 Oct 2016 13:57:11 -0700 Subject: [PATCH 039/267] Fix for issue #1370, Segfault using QImages due to not retaining the data --- PIL/ImageQt.py | 8 ++++-- Tests/test_image_toqimage.py | 48 +++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 3f7ae2518..2ce89e7ad 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -157,7 +157,6 @@ def _toqclass_helper(im): else: raise ValueError("unsupported image mode %r" % im.mode) - # must keep a reference, or Qt will crash! __data = data or align8to32(im.tobytes(), im.size[0], im.mode) return { 'data': __data, 'im': im, 'format': format, 'colortable': colortable @@ -175,8 +174,13 @@ if qt_is_installed: string or a PyQt string object). """ im_data = _toqclass_helper(im) + # must keep a reference, or Qt will crash! + # All QImage constructors that take data operate on an existing + # buffer, so this buffer has to hang on for the life of the image. + # Fixes https://github.com/python-pillow/Pillow/issues/1370 + self.__data = im_data['data'] QImage.__init__(self, - im_data['data'], im_data['im'].size[0], + self.__data, im_data['im'].size[0], im_data['im'].size[1], im_data['format']) if im_data['colortable']: self.setColorTable(im_data['colortable']) diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index 0dd9751d3..ccab08799 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -5,7 +5,17 @@ from PIL import ImageQt if ImageQt.qt_is_installed: - from PIL.ImageQt import QImage + from PIL.ImageQt import QImage, QPixmap + + try: + from PyQt5 import QtGui + except (ImportError, RuntimeError): + try: + from PyQt4 import QtGui + except (ImportError, RuntimeError): + from PySide import QtGui + + class TestToQImage(PillowQtTestCase, PillowTestCase): @@ -23,5 +33,41 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): data.save(tempfile) + def test_segfault(self): + PillowQtTestCase.setUp(self) + + app = QtGui.QApplication([]) + ex = Example() + + +if ImageQt.qt_is_installed: + class Example(QtGui.QWidget): + + def __init__(self): + super(Example, self).__init__() + + img = hopper().resize((1000,1000)) + + qimage = ImageQt.ImageQt(img) + + pixmap1 = QtGui.QPixmap.fromImage(qimage) + + hbox = QtGui.QHBoxLayout(self) + + lbl = QtGui.QLabel(self) + # Segfault in the problem + lbl.setPixmap(pixmap1.copy()) + + + + +def main(): + app = QtGui.QApplication(sys.argv) + ex = Example() + sys.exit(app.exec_()) + + + + if __name__ == '__main__': unittest.main() From 6380f8da034ba3cb45e175ae64790243640dd888 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 28 Oct 2016 19:59:40 +1100 Subject: [PATCH 040/267] Fixed typo [ci skip] --- docs/handbook/image-file-formats.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 78840e720..11486c76b 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -159,7 +159,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **sizes** A list of sizes including in this ico file; these are a 2-tuple, ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48), - (64, 64), (128, 128), (255, 255)]``. Any sizes bigger then the original + (64, 64), (128, 128), (255, 255)]``. Any sizes bigger than the original size or 255 will be ignored. IM @@ -555,7 +555,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **save_all** If true, Pillow will save all frames of the image to a multiframe tiff document. - + .. versionadded:: 3.4.0 **tiffinfo** From 23c1c2732a60867803a8bc65befd9c4d18c00a60 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 28 Oct 2016 02:00:12 -0700 Subject: [PATCH 041/267] added a display for the QT tests --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 586ac7fcd..bc4a2289e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,11 @@ install: # libimagequant - pushd depends && ./install_imagequant.sh && popd +before_script: +# QT needs a display for some of the tests, and it's only run on the system site packages install + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + script: - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi - python setup.py clean From f09d7d986352ef143e9b3028f67453c47c8f6479 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 28 Oct 2016 02:44:28 -0700 Subject: [PATCH 042/267] t --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bc4a2289e..b14ff9af1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ install: - pushd depends && ./install_imagequant.sh && popd before_script: -# QT needs a display for some of the tests, and it's only run on the system site packages install +# Qt needs a display for some of the tests, and it's only run on the system site packages install - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" From e44bb42ae98663b9196562100a1aa990bebebcde Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 30 Oct 2016 17:43:32 -0700 Subject: [PATCH 043/267] Replace type() equality checks with isinstance --- PIL/Image.py | 2 +- PIL/ImageDraw.py | 4 ++-- PIL/TiffImagePlugin.py | 8 ++++---- PIL/features.py | 2 +- Tests/test_features.py | 4 ++-- Tests/test_file_png.py | 2 +- Tests/test_file_tiff_metadata.py | 11 +++++------ Tests/test_image.py | 2 +- Tests/test_imagemath.py | 2 +- Tests/test_olefileio.py | 3 +-- setup.py | 2 +- 11 files changed, 20 insertions(+), 22 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index fa743c602..b83c43d8a 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -879,7 +879,7 @@ class Image(object): trns_im = Image()._new(core.new(self.mode, (1, 1))) if self.mode == 'P': trns_im.putpalette(self.palette) - if type(t) == tuple: + if isinstance(t, tuple): try: t = trns_im.palette.getcolor(t) except: diff --git a/PIL/ImageDraw.py b/PIL/ImageDraw.py index 5dfb5929d..720403920 100644 --- a/PIL/ImageDraw.py +++ b/PIL/ImageDraw.py @@ -208,12 +208,12 @@ class ImageDraw(object): def _multiline_check(self, text): """Draw text.""" - split_character = "\n" if isinstance(text, type("")) else b"\n" + split_character = "\n" if isinstance(text, str) else b"\n" return split_character in text def _multiline_split(self, text): - split_character = "\n" if isinstance(text, type("")) else b"\n" + split_character = "\n" if isinstance(text, str) else b"\n" return text.split(split_character) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index fefed6b30..3af38832d 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -278,12 +278,12 @@ class IFDRational(Rational): self._numerator = value self._val = float(1) - if type(value) == Fraction: + if isinstance(value, Fraction): self._numerator = value.numerator self._denominator = value.denominator self._val = value - if type(value) == IFDRational: + if isinstance(value, IFDRational): self._denominator = value.denominator self._numerator = value.numerator self._val = value._val @@ -294,7 +294,7 @@ class IFDRational(Rational): return elif denominator == 1: - if sys.hexversion < 0x2070000 and type(value) == float: + if sys.hexversion < 0x2070000 and isinstance(value, float): # python 2.6 is different. self._val = Fraction.from_float(value) else: @@ -1056,7 +1056,7 @@ class TiffImageFile(ImageFile.ImageFile): # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. fp = False - + if fp: args[2] = fp diff --git a/PIL/features.py b/PIL/features.py index fd87f094f..134d85abf 100644 --- a/PIL/features.py +++ b/PIL/features.py @@ -17,7 +17,7 @@ def check_module(feature): module = modules[feature] method_to_call = None - if type(module) is tuple: + if isinstance(module, tuple): module, method_to_call = module try: diff --git a/Tests/test_features.py b/Tests/test_features.py index 7861693b2..b9afe9b1d 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -13,8 +13,8 @@ class TestFeatures(PillowTestCase): self.assertTrue(features.check_codec(feature) in [True, False]) def test_supported_features(self): - self.assertTrue(type(features.get_supported_modules()) is list) - self.assertTrue(type(features.get_supported_codecs()) is list) + self.assertIsInstance(features.get_supported_modules(), list) + self.assertIsInstance(features.get_supported_codecs(), list) def test_unsupported_codec(self): # Arrange diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a96422fa7..c10fb89fc 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -392,7 +392,7 @@ class TestFilePng(PillowTestCase): info = PngImagePlugin.PngInfo() info.add_text("Text", "Ascii") im = roundtrip(im, pnginfo=info) - self.assertEqual(type(im.info["Text"]), str) + self.assertIsInstance(im.info["Text"], str) def test_unicode_text(self): # Check preservation of non-ASCII characters on Python3 diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index f40ec0982..7af03a675 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -123,10 +123,9 @@ class TestFileTiffMetadata(PillowTestCase): reloaded = loaded.tag_v2.named() for k, v in original.items(): - if type(v) == IFDRational: + if isinstance(v, IFDRational): original[k] = IFDRational(*_limit_rational(v, 2**31)) - if type(v) == tuple and \ - type(v[0]) == IFDRational: + if isinstance(v, tuple) and isinstance(v[0], IFDRational): original[k] = tuple([IFDRational( *_limit_rational(elt, 2**31)) for elt in v]) @@ -136,8 +135,8 @@ class TestFileTiffMetadata(PillowTestCase): for tag, value in reloaded.items(): if tag in ignored: continue - if (type(original[tag]) == tuple - and type(original[tag][0]) == IFDRational): + if (isinstance(original[tag], tuple) + and isinstance(original[tag][0], IFDRational)): # Need to compare element by element in the tuple, # not comparing tuples of object references self.assert_deep_equal(original[tag], @@ -175,7 +174,7 @@ class TestFileTiffMetadata(PillowTestCase): im.save(out) reloaded = Image.open(out) - self.assert_(type(im.info['icc_profile']) is not tuple) + self.assertNotIsInstance(im.info['icc_profile'], tuple) self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile']) def test_iccprofile_binary(self): diff --git a/Tests/test_image.py b/Tests/test_image.py index 6b24a988b..717d4dbfb 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -249,7 +249,7 @@ class TestImage(PillowTestCase): self.assertTrue(Image.new('RGB', (1,1))) # Should pass lists too i = Image.new('RGB', [1,1]) - self.assertEqual(type(i.size), tuple) + self.assertIsInstance(i.size, tuple) def test_storage_neg(self): # Storage.c accepted negative values for xsize, ysize. Was diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index a5295c4f9..4f99eda93 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -9,7 +9,7 @@ def pixel(im): if hasattr(im, "im"): return "%s %r" % (im.mode, im.getpixel((0, 0))) else: - if isinstance(im, type(0)): + if isinstance(im, int): return int(im) # hack to deal with booleans print(im) diff --git a/Tests/test_olefileio.py b/Tests/test_olefileio.py index 28c39afb5..cb0496db4 100644 --- a/Tests/test_olefileio.py +++ b/Tests/test_olefileio.py @@ -106,9 +106,8 @@ class TestOleFileIo(PillowTestCase): mtime = root_entry.getmtime() # Assert - self.assertIsInstance(ctime, type(None)) + self.assertIsNone(ctime) self.assertIsInstance(mtime, datetime.datetime) - self.assertEqual(ctime, None) self.assertEqual(mtime.year, 2014) ole.close() diff --git a/setup.py b/setup.py index 482d7ed91..1275f4fd1 100644 --- a/setup.py +++ b/setup.py @@ -189,7 +189,7 @@ class pil_build_ext(build_ext): for root in (JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT, FREETYPE_ROOT, LCMS_ROOT, IMAGEQUANT_ROOT): - if isinstance(root, type(())): + if isinstance(root, tuple): lib_root, include_root = root else: lib_root = include_root = root From 02b5ce0479e37d9ce9ebe7bd942ab4d326e7aab5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 31 Oct 2016 11:09:40 -0400 Subject: [PATCH 044/267] Remove redundant space in PIL.Image ImportError message --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index fa743c602..92b6164e8 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -67,7 +67,7 @@ try: from PIL import _imaging as core if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): raise ImportError("The _imaging extension was built for another " - " version of Pillow or PIL") + "version of Pillow or PIL") except ImportError as v: core = _imaging_not_installed() From 792df283d97d38e99cc73132e7350a7f87c1cbd1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 31 Oct 2016 11:27:49 -0400 Subject: [PATCH 045/267] Fix "invalid escape sequence" bytestring warnings in Python 3.6 --- PIL/GimpPaletteFile.py | 2 +- PIL/PngImagePlugin.py | 2 +- PIL/XbmImagePlugin.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/GimpPaletteFile.py b/PIL/GimpPaletteFile.py index 4bf3ca36a..e4b4641e5 100644 --- a/PIL/GimpPaletteFile.py +++ b/PIL/GimpPaletteFile.py @@ -41,7 +41,7 @@ class GimpPaletteFile(object): if not s: break # skip fields and comment lines - if re.match(b"\w+:|#", s): + if re.match(br"\w+:|#", s): continue if len(s) > 100: raise SyntaxError("bad palette file") diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index a5f4c3f42..4d6f9d7ff 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -48,7 +48,7 @@ i8 = _binary.i8 i16 = _binary.i16be i32 = _binary.i32be -is_cid = re.compile(b"\w\w\w\w").match +is_cid = re.compile(br"\w\w\w\w").match _MAGIC = b"\211PNG\r\n\032\n" diff --git a/PIL/XbmImagePlugin.py b/PIL/XbmImagePlugin.py index bca882866..d0b0e47ab 100644 --- a/PIL/XbmImagePlugin.py +++ b/PIL/XbmImagePlugin.py @@ -26,7 +26,7 @@ __version__ = "0.6" # XBM header xbm_head = re.compile( - b"\s*#define[ \t]+.*_width[ \t]+(?P[0-9]+)[\r\n]+" + br"\s*#define[ \t]+.*_width[ \t]+(?P[0-9]+)[\r\n]+" b"#define[ \t]+.*_height[ \t]+(?P[0-9]+)[\r\n]+" b"(?P" b"#define[ \t]+[^_]*_x_hot[ \t]+(?P[0-9]+)[\r\n]+" From a33939f5c30309475e72ac4a28d0f2e95c18695d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 1 Nov 2016 06:31:47 -0700 Subject: [PATCH 046/267] Remove unused, open files at top level of tests. The data read from the file was unused. The files remained opened and were never explicitly closed. Fixes some instances of warnings during tests: "ResourceWarning: unclosed file ..." --- Tests/test_file_fli.py | 1 - Tests/test_file_icns.py | 1 - Tests/test_file_ico.py | 1 - Tests/test_file_png.py | 1 - Tests/test_file_ppm.py | 1 - Tests/test_file_psd.py | 1 - 6 files changed, 6 deletions(-) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 5ce4839bc..a49301de1 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -6,7 +6,6 @@ from PIL import Image, FliImagePlugin # created as an export of a palette image from Gimp2.6 # save as...-> hopper.fli, default options. test_file = "Tests/images/hopper.fli" -data = open(test_file, "rb").read() class TestFileFli(PillowTestCase): diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 7332db964..92fe136f2 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -6,7 +6,6 @@ import sys # sample icon file TEST_FILE = "Tests/images/pillow.icns" -data = open(TEST_FILE, "rb").read() enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 806cff66f..94b8c44e3 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -5,7 +5,6 @@ from PIL import Image, IcoImagePlugin # sample ppm stream TEST_ICO_FILE = "Tests/images/hopper.ico" -TEST_DATA = open(TEST_ICO_FILE, "rb").read() class TestFileIco(PillowTestCase): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a96422fa7..54250d15a 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -12,7 +12,6 @@ codecs = dir(Image.core) # sample png stream TEST_PNG_FILE = "Tests/images/hopper.png" -TEST_DATA = open(TEST_PNG_FILE, "rb").read() # stuff to create inline PNG images diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 817a62393..e7428f88d 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -4,7 +4,6 @@ from PIL import Image # sample ppm stream test_file = "Tests/images/hopper.ppm" -data = open(test_file, "rb").read() class TestFilePpm(PillowTestCase): diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 6bf34cf78..2b4825734 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -4,7 +4,6 @@ from PIL import Image, PsdImagePlugin # sample ppm stream test_file = "Tests/images/hopper.psd" -data = open(test_file, "rb").read() class TestImagePsd(PillowTestCase): From 5aeb0ed9727a8c51c98d11788c0135c54e88b1b7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 1 Nov 2016 19:10:30 +0200 Subject: [PATCH 047/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7aacb5042..eeb4db7fc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Tests: Remove unused, open files at top level of tests #2188 + [jdufresne] + +- Replace type() equality checks with isinstance #2184 + [jdufresne] + - Doc: Move ICO out of the list of read-only file formats #2180 [alexwlchan] From 66de02685f947024a340588fac9f5f2ac35882b0 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Fri, 4 Nov 2016 16:37:49 +0100 Subject: [PATCH 048/267] Update info.icc_profile when using libtiff reader. --- PIL/TiffImagePlugin.py | 7 ++++--- Tests/test_file_libtiff.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 3af38832d..69a216017 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1010,9 +1010,6 @@ class TiffImageFile(ImageFile.ImageFile): # Section 14: Differencing Predictor self.decoderconfig = (self.tag_v2[PREDICTOR],) - if ICCPROFILE in self.tag_v2: - self.info['icc_profile'] = self.tag_v2[ICCPROFILE] - return args def load(self): @@ -1285,6 +1282,10 @@ class TiffImageFile(ImageFile.ImageFile): print("- unsupported data organization") raise SyntaxError("unknown data organization") + # Fix up info. + if ICCPROFILE in self.tag_v2: + self.info['icc_profile'] = self.tag_v2[ICCPROFILE] + # fixup palette descriptor if self.mode == "P": diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6e40d4b37..229c1e830 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -521,8 +521,16 @@ class TestFileLibTiff(LibTiffTestCase): except: self.fail("Should not get permission error here") - - + def test_read_icc(self): + with Image.open("Tests/images/hopper.iccprofile.tif") as img: + icc = img.info.get('icc_profile') + self.assertNotEqual(icc, None) + TiffImagePlugin.READ_LIBTIFF = True + with Image.open("Tests/images/hopper.iccprofile.tif") as img: + icc_libtiff = img.info.get('icc_profile') + self.assertNotEqual(icc_libtiff, None) + TiffImagePlugin.READ_LIBTIFF = False + self.assertEqual(icc, icc_libtiff) if __name__ == '__main__': unittest.main() From a51dc7dcaf70b56538664bb7d8ead2c68a855434 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Nov 2016 13:32:02 +1100 Subject: [PATCH 049/267] Unified different GIF optimize conditions --- PIL/GifImagePlugin.py | 152 +++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 77 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 8b2dfac0a..3e387a670 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -434,18 +434,16 @@ def _get_local_header(fp, im, offset, flags): # optimize the block away if transparent color is not used transparent_color_exists = True - if _get_optimize(im, im.encoderinfo): - used_palette_colors = _get_used_palette_colors(im) - + used_palette_colors = _get_optimize(im, im.encoderinfo) + if used_palette_colors is not None: # adjust the transparency index after optimize - if len(used_palette_colors) < 256: - for i in range(len(used_palette_colors)): - if used_palette_colors[i] == transparency: - transparency = i - transparent_color_exists = True - break - else: - transparent_color_exists = False + for i in range(len(used_palette_colors)): + if used_palette_colors[i] == transparency: + transparency = i + transparent_color_exists = True + break + else: + transparent_color_exists = False if "duration" in im.encoderinfo: duration = int(im.encoderinfo["duration"] / 10) @@ -552,9 +550,28 @@ def _save_netpbm(im, fp, filename): # -------------------------------------------------------------------- # GIF utilities -def _get_optimize(im, info): - return im.mode in ("P", "L") and info and info.get("optimize", 0) +# Force optimization so that we can test performance against +# cases where it took lots of memory and time previously. +_FORCE_OPTIMIZE = False +def _get_optimize(im, info): + if im.mode in ("P", "L") and info and info.get("optimize", 0): + # Potentially expensive operation. + + # The palette saves 3 bytes per color not used, but palette + # lengths are restricted to 3*(2**N) bytes. Max saving would + # be 768 -> 6 bytes if we went all the way down to 2 colors. + # * If we're over 128 colors, we can't save any space. + # * If there aren't any holes, it's not worth collapsing. + # * If we have a 'large' image, the palette is in the noise. + + # create the new palette if not every color is used + used_palette_colors = _get_used_palette_colors(im) + if _FORCE_OPTIMIZE or im.mode == 'L' or \ + (len(used_palette_colors) <= 128 and + max(used_palette_colors) > len(used_palette_colors) and + im.width * im.height < 512 * 512): + return used_palette_colors def _get_used_palette_colors(im): used_palette_colors = [] @@ -586,10 +603,6 @@ def _get_header_palette(palette_bytes): palette_bytes += o8(0) * 3 * actual_target_size_diff return palette_bytes -# Force optimization so that we can test performance against -# cases where it took lots of memory and time previously. -_FORCE_OPTIMIZE = False - def _get_palette_bytes(im, palette, info): if im.mode == "P": if palette and isinstance(palette, bytes): @@ -602,79 +615,64 @@ def _get_palette_bytes(im, palette, info): else: source_palette = bytearray([i//3 for i in range(768)]) - used_palette_colors = palette_bytes = None + palette_bytes = None - if _get_optimize(im, info): - used_palette_colors = _get_used_palette_colors(im) + used_palette_colors = _get_optimize(im, info) + if used_palette_colors is not None: + palette_bytes = b"" + new_positions = [0]*256 - # Potentially expensive operation. + # pick only the used colors from the palette + for i, oldPosition in enumerate(used_palette_colors): + palette_bytes += source_palette[oldPosition*3:oldPosition*3+3] + new_positions[oldPosition] = i - # The palette saves 3 bytes per color not used, but palette - # lengths are restricted to 3*(2**N) bytes. Max saving would - # be 768 -> 6 bytes if we went all the way down to 2 colors. - # * If we're over 128 colors, we can't save any space. - # * If there aren't any holes, it's not worth collapsing. - # * If we have a 'large' image, the palette is in the noise. + # replace the palette color id of all pixel with the new id - # create the new palette if not every color is used - if _FORCE_OPTIMIZE or im.mode == 'L' or \ - (len(used_palette_colors) <= 128 and - max(used_palette_colors) > len(used_palette_colors) and - im.width * im.height < 512 * 512): - palette_bytes = b"" - new_positions = [0]*256 + # Palette images are [0..255], mapped through a 1 or 3 + # byte/color map. We need to remap the whole image + # from palette 1 to palette 2. New_positions is + # an array of indexes into palette 1. Palette 2 is + # palette 1 with any holes removed. - # pick only the used colors from the palette - for i, oldPosition in enumerate(used_palette_colors): - palette_bytes += source_palette[oldPosition*3:oldPosition*3+3] - new_positions[oldPosition] = i + # We're going to leverage the convert mechanism to use the + # C code to remap the image from palette 1 to palette 2, + # by forcing the source image into 'L' mode and adding a + # mapping 'L' mode palette, then converting back to 'L' + # sans palette thus converting the image bytes, then + # assigning the optimized RGB palette. - # replace the palette color id of all pixel with the new id + # perf reference, 9500x4000 gif, w/~135 colors + # 14 sec prepatch, 1 sec postpatch with optimization forced. - # Palette images are [0..255], mapped through a 1 or 3 - # byte/color map. We need to remap the whole image - # from palette 1 to palette 2. New_positions is - # an array of indexes into palette 1. Palette 2 is - # palette 1 with any holes removed. + mapping_palette = bytearray(new_positions) - # We're going to leverage the convert mechanism to use the - # C code to remap the image from palette 1 to palette 2, - # by forcing the source image into 'L' mode and adding a - # mapping 'L' mode palette, then converting back to 'L' - # sans palette thus converting the image bytes, then - # assigning the optimized RGB palette. + m_im = im.copy() + m_im.mode = 'P' - # perf reference, 9500x4000 gif, w/~135 colors - # 14 sec prepatch, 1 sec postpatch with optimization forced. + m_im.palette = ImagePalette.ImagePalette("RGB", + palette=mapping_palette*3, + size=768) + #possibly set palette dirty, then + #m_im.putpalette(mapping_palette, 'L') # converts to 'P' + # or just force it. + # UNDONE -- this is part of the general issue with palettes + m_im.im.putpalette(*m_im.palette.getdata()) - mapping_palette = bytearray(new_positions) + m_im = m_im.convert('L') - m_im = im.copy() - m_im.mode = 'P' + # Internally, we require 768 bytes for a palette. + new_palette_bytes = (palette_bytes + + (768 - len(palette_bytes)) * b'\x00') + m_im.putpalette(new_palette_bytes) + m_im.palette = ImagePalette.ImagePalette("RGB", + palette=palette_bytes, + size=len(palette_bytes)) - m_im.palette = ImagePalette.ImagePalette("RGB", - palette=mapping_palette*3, - size=768) - #possibly set palette dirty, then - #m_im.putpalette(mapping_palette, 'L') # converts to 'P' - # or just force it. - # UNDONE -- this is part of the general issue with palettes - m_im.im.putpalette(*m_im.palette.getdata()) - - m_im = m_im.convert('L') - - # Internally, we require 768 bytes for a palette. - new_palette_bytes = (palette_bytes + - (768 - len(palette_bytes)) * b'\x00') - m_im.putpalette(new_palette_bytes) - m_im.palette = ImagePalette.ImagePalette("RGB", - palette=palette_bytes, - size=len(palette_bytes)) + # oh gawd, this is modifying the image in place so I can pass by ref. + # REFACTOR SOONEST + im.frombytes(m_im.tobytes()) - # oh gawd, this is modifying the image in place so I can pass by ref. - # REFACTOR SOONEST - im.frombytes(m_im.tobytes()) - if not palette_bytes: palette_bytes = source_palette From caf53b05ec1d8fdb7b19eb2df6d64a56bcb4c916 Mon Sep 17 00:00:00 2001 From: Clement Skau Date: Mon, 22 Aug 2016 19:47:49 +0900 Subject: [PATCH 050/267] Fixes TIFFImagePlugin ICC color profile saving. In the TIFF code saving icc_profile is conditional on tag_v2 being set which doesn't make sense to me. I believe this is merely an indentation typo. I've been trying to save TIFFs with im.info['icc_profile'] set and compression=raw, but unfortunately this results in TIFFs without ICC color profiles. With the attached patch TIFFs with said conditions will be saved with the profile set in im.info['icc_profile']. Note: There are a number of different conditions that need to be met for code to succeed in saving with the profile since it branches between using libtiff and ImageFile._save(..), and the libtiff code does not currently save the ICC color profile. For instance setting compression=tiff_lzw will result in using libtiff and no profile will be saved. --- PIL/TiffImagePlugin.py | 8 ++++---- Tests/test_file_tiff.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 3af38832d..4aae05032 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1366,10 +1366,10 @@ def _save(im, fp, filename): ifd[key] = im.tag_v2[key] ifd.tagtype[key] = im.tag_v2.tagtype[key] - # preserve ICC profile (should also work when saving other formats - # which support profiles as TIFF) -- 2008-06-06 Florian Hoech - if "icc_profile" in im.info: - ifd[ICCPROFILE] = im.info["icc_profile"] + # preserve ICC profile (should also work when saving other formats + # which support profiles as TIFF) -- 2008-06-06 Florian Hoech + if "icc_profile" in im.info: + ifd[ICCPROFILE] = im.info["icc_profile"] for key, name in [(IMAGEDESCRIPTION, "description"), (X_RESOLUTION, "resolution"), diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 9913860ad..76fe8f930 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -499,5 +499,21 @@ class TestFileTiff(PillowTestCase): with Image.open(mp) as im: self.assertEqual(im.n_frames, 3) + def test_saving_icc_profile(self): + # Tests saving TIFF with icc_profile set. + # At the time of writing this will only work for non-compressed tiffs + # as libtiff does not support embedded ICC profiles, ImageFile._save(..) + # however does. + im = Image.new('RGB', (1, 1)) + im.info['icc_profile'] = 'Dummy value' + + # Try save-load round trip to make sure both handle icc_profile. + tmpfile = self.tempfile('temp.tif') + im.save(tmpfile, 'TIFF', compression='raw') + reloaded = Image.open(tmpfile) + + self.assertEqual(b'Dummy value', reloaded.info['icc_profile']) + + if __name__ == '__main__': unittest.main() From 2ba2763f0bd0a7c0d860a14404b04adc40b8d453 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Nov 2016 19:09:14 +1100 Subject: [PATCH 051/267] Replaced range(len()) with enumerate --- PIL/GifImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 3e387a670..fc96da9f4 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -437,8 +437,8 @@ def _get_local_header(fp, im, offset, flags): used_palette_colors = _get_optimize(im, im.encoderinfo) if used_palette_colors is not None: # adjust the transparency index after optimize - for i in range(len(used_palette_colors)): - if used_palette_colors[i] == transparency: + for i, palette_color in enumerate(used_palette_colors): + if palette_color == transparency: transparency = i transparent_color_exists = True break From 8fc90fe4fac8365b6bc68250c3ad680d774cb5f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 6 Nov 2016 12:38:59 +1100 Subject: [PATCH 052/267] Replaced range(len()) --- PIL/ImageMorph.py | 6 +++--- PIL/PalmImagePlugin.py | 9 ++++----- PIL/TiffImagePlugin.py | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 4847594f6..748ecdb61 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -156,10 +156,10 @@ class LutBuilder(object): # print '--' # compile the patterns into regular expressions for speed - for i in range(len(patterns)): - p = patterns[i][0].replace('.', 'X').replace('X', '[01]') + for i, pattern in enumerate(patterns): + p = pattern[0].replace('.', 'X').replace('X', '[01]') p = re.compile(p) - patterns[i] = (p, patterns[i][1]) + patterns[i] = (p, pattern[1]) # Step through table and find patterns that match. # Note that all the patterns are searched. The last one diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py index 4f415ff7c..d02839bdf 100644 --- a/PIL/PalmImagePlugin.py +++ b/PIL/PalmImagePlugin.py @@ -80,13 +80,12 @@ _Palm8BitColormapValues = ( # so build a prototype image to be used for palette resampling def build_prototype_image(): - image = Image.new("L", (1, len(_Palm8BitColormapValues),)) + image = Image.new("L", (1, len(_Palm8BitColormapValues))) image.putdata(list(range(len(_Palm8BitColormapValues)))) palettedata = () - for i in range(len(_Palm8BitColormapValues)): - palettedata = palettedata + _Palm8BitColormapValues[i] - for i in range(256 - len(_Palm8BitColormapValues)): - palettedata = palettedata + (0, 0, 0) + for colormapValue in _Palm8BitColormapValues: + palettedata += colormapValue + palettedata += (0, 0, 0)*(256 - len(_Palm8BitColormapValues)) image.putpalette(palettedata) return image diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 3af38832d..52e04654c 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1246,12 +1246,12 @@ class TiffImageFile(ImageFile.ImageFile): a = None else: - for i in range(len(offsets)): + for i, offset in enumerate(offsets): a = self._decoder(rawmode, l, i) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), - offsets[i], a)) + offset, a)) if DEBUG: print("tiles: ", self.tile) y = y + h From f041188050a056913b27c27542fed9ccf7c6cb7d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 6 Nov 2016 08:58:45 -0800 Subject: [PATCH 053/267] Replace try/except/fail pattern with TestCase.assertRaises() Replace pattern with the builtin support for asserting exceptions. --- Tests/check_j2k_overflow.py | 7 +------ Tests/check_libtiff_segfault.py | 6 +----- Tests/test_file_jpeg2k.py | 11 ++--------- Tests/test_image_access.py | 5 +---- Tests/test_image_resample.py | 15 +++------------ Tests/test_imagepath.py | 12 +++--------- 6 files changed, 11 insertions(+), 45 deletions(-) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 474b49948..bec4ea694 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -7,13 +7,8 @@ class TestJ2kEncodeOverflow(PillowTestCase): im = Image.new('RGBA', (1024, 131584)) target = self.tempfile('temp.jpc') - try: + with self.assertRaises(IOError): im.save(target) - self.assertTrue(False, "Expected IOError, save succeeded?") - except IOError as err: - self.assertTrue(True, "IOError is expected") - except Exception as err: - self.assertTrue(False, "Expected IOError, got %s" % type(err)) if __name__ == '__main__': unittest.main() diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index c2e01dd55..6611648a5 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -10,13 +10,9 @@ class TestLibtiffSegfault(PillowTestCase): libtiff >= 4.0.0 """ - try: + with self.assertRaises(IOError): im = Image.open(TEST_FILE) im.load() - except IOError: - self.assertTrue(True, "Got expected IOError") - except Exception: - self.fail("Should have returned IOError") if __name__ == '__main__': diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 0f3522a3b..8aeb7ecae 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -174,15 +174,8 @@ class TestFileJpeg2k(PillowTestCase): def test_unbound_local(self): # prepatch, a malformed jp2 file could cause an UnboundLocalError # exception. - try: - jp2 = Image.open('Tests/images/unbound_variable.jp2') - self.assertTrue(False, 'Expecting an exception') - except SyntaxError as err: - self.assertTrue(True, 'Expecting a syntax error') - except IOError as err: - self.assertTrue(True, 'Expecting an IO error') - except UnboundLocalError as err: - self.assertTrue(False, "Prepatch error") + with self.assertRaises(IOError): + Image.open('Tests/images/unbound_variable.jp2') if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 21295ac82..0f8d2a654 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -192,11 +192,8 @@ class TestCffi(AccessTest): # Attempt to set the value on a read-only image access = PyAccess.new(im, True) - try: + with self.assertRaises(ValueError): access[(0, 0)] = color - except ValueError: - return - self.fail("Putpixel did not fail on a read-only image") def test_set_vs_c(self): rgb = hopper('RGB') diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index bd763addb..6fcdd84b9 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -8,12 +8,9 @@ class TestImagingResampleVulnerability(PillowTestCase): im = hopper('L') xsize = 0x100000008 // 4 ysize = 1000 # unimportant - try: + with self.assertRaises(MemoryError): # any resampling filter will do here im.im.resize((xsize, ysize), Image.BILINEAR) - self.fail("Resize should raise MemoryError on invalid xsize") - except MemoryError: - self.assertTrue(True, "Should raise MemoryError") def test_invalid_size(self): im = hopper() @@ -21,17 +18,11 @@ class TestImagingResampleVulnerability(PillowTestCase): im.resize((100, 100)) self.assertTrue(True, "Should not Crash") - try: + with self.assertRaises(ValueError): im.resize((-100, 100)) - self.fail("Resize should raise a value error on x negative size") - except ValueError: - self.assertTrue(True, "Should raise ValueError") - try: + with self.assertRaises(ValueError): im.resize((100, -100)) - self.fail("Resize should raise a value error on y negative size") - except ValueError: - self.assertTrue(True, "Should raise ValueError") class TestImagingCoreResampleAccuracy(PillowTestCase): diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index d5ec5dfaa..14cc4d14b 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -63,7 +63,9 @@ class TestImagePath(PillowTestCase): self.assertEqual(list(p), [(0.0, 1.0)]) def test_overflow_segfault(self): - try: + # Some Pythons fail getting the argument as an integer, and it falls + # through to the sequence. Seeing this on 32-bit Windows. + with self.assertRaises((TypeError, MemoryError)): # post patch, this fails with a memory error x = evil() @@ -74,14 +76,6 @@ class TestImagePath(PillowTestCase): x[i] = "0"*16 else: x[i] = b'0'*16 - except TypeError as msg: - # Some pythons fail getting the argument as an integer, and - # it falls through to the sequence. Seeing this on 32bit windows. - self.assertTrue(True, "Sequence required") - except MemoryError as msg: - self.assertTrue(msg) - except: - self.assertTrue(False, "Should have received a memory error") class evil: From e768e7fa4560c230baf4bc4ae1cb40dce6e0b0bf Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Nov 2016 09:42:09 +0200 Subject: [PATCH 054/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eeb4db7fc..c4887a4df 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Tests: Replace try/except/fail pattern with TestCase.assertRaises() #2200 + [jdufresne] + - Tests: Remove unused, open files at top level of tests #2188 [jdufresne] From e2e4d180f925a07fca32bdcbb6ff981455d9fb3f Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 8 Nov 2016 17:54:23 -0800 Subject: [PATCH 055/267] Close file in setup.py after finished reading Fixes a "ResourceWarning: unclosed file" during tests. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1275f4fd1..0f4d5fb38 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,8 @@ def _lib_include(root): def _read(file): - return open(file, 'rb').read() + with open(file, 'rb') as fp: + return fp.read() try: From 92272f81954e2ce38a81071ba9072dd98bd89ee3 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Wed, 9 Nov 2016 08:58:12 -0800 Subject: [PATCH 056/267] FIX: search for tkinter first in builtins Python compiled from Python.org source builds the tkinter module as a built-in module, not an external module, as is the case for the packaged builds of Debian etc: >>> Tkinter.tkinter This breaks the current algorithm for searching for tkinter symbols, which loaded the external module .so file to get the symbols. Try searching in the main program namespace for the tkinter symbols, before looking for the extermal module .so file. Thanks to github user ettaka for reporting : see https://github.com/matplotlib/matplotlib/issues/7428 --- Tk/tkImaging.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Tk/tkImaging.c b/Tk/tkImaging.c index 5999a140a..6c612cfe9 100644 --- a/Tk/tkImaging.c +++ b/Tk/tkImaging.c @@ -438,10 +438,19 @@ int load_tkinter_funcs(void) */ int ret = -1; - void *tkinter_lib; + void *main_program, *tkinter_lib; char *tkinter_libname; PyObject *pModule = NULL, *pString = NULL; + /* Try loading from the main program namespace first */ + main_program = dlopen(NULL, RTLD_LAZY); + if (_func_loader(main_program) == 0) { + return 0; + } + /* Clear exception triggered when we didn't find symbols above */ + PyErr_Clear(); + + /* Now try finding the tkinter compiled module */ pModule = PyImport_ImportModule(TKINTER_FINDER); if (pModule == NULL) { goto exit; From 993969790bd0b1ba880e98da65e86ac699ae8f22 Mon Sep 17 00:00:00 2001 From: Rok Garbas Date: Mon, 25 Jul 2016 00:02:11 +0200 Subject: [PATCH 057/267] optionaly use pkg-config (when present) to detect *_ROOTs * only run pkg-config when building exttensions * print debug messages when using pkg-config * silance error from pkg-config by default * first search for libtiff-5 then libtiff-4 --- setup.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1275f4fd1..f2fe286da 100644 --- a/setup.py +++ b/setup.py @@ -98,6 +98,11 @@ def _lib_include(root): # map root to (root/lib, root/include) return os.path.join(root, "lib"), os.path.join(root, "include") +def _cmd_exists(cmd): + return any( + os.access(os.path.join(path, cmd), os.X_OK) + for path in os.environ["PATH"].split(os.pathsep) + ) def _read(file): return open(file, 'rb').read() @@ -120,6 +125,20 @@ FREETYPE_ROOT = None LCMS_ROOT = None +def _pkg_config(name): + try: + command = [ + 'pkg-config', + '--libs-only-L', name, + '--cflags-only-I', name, + ] + if not DEBUG: + command.append('--silence-errors') + libs = subprocess.check_output(command).decode('utf8').split(' ') + return libs[1][2:].strip(), libs[0][2:].strip() + except: + pass + class pil_build_ext(build_ext): class feature: features = ['zlib', 'jpeg', 'tiff', 'freetype', 'lcms', 'webp', @@ -184,15 +203,37 @@ class pil_build_ext(build_ext): _add_directory(include_dirs, "libImaging") + pkg_config = None + if _cmd_exists('pkg-config'): + pkg_config = _pkg_config + # # add configured kits + for root_name, lib_name in dict(JPEG_ROOT="libjpeg", + JPEG2K_ROOT="libopenjp2", + TIFF_ROOT=("libtiff-5", "libtiff-4"), + ZLIB_ROOT="zlib", + FREETYPE_ROOT="freetype2", + LCMS_ROOT="lcms2", + IMAGEQUANT_ROOT="libimagequant" + ).items(): + root = globals()[root_name] + if root is None and pkg_config: + if isinstance(lib_name, tuple): + for lib_name2 in lib_name: + _dbg('Looking for `%s` using pkg-config.' % lib_name2) + root = pkg_config(lib_name2) + if root: + break + else: + _dbg('Looking for `%s` using pkg-config.' % lib_name) + root = pkg_config(lib_name) - for root in (JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT, - FREETYPE_ROOT, LCMS_ROOT, IMAGEQUANT_ROOT): if isinstance(root, tuple): lib_root, include_root = root else: lib_root = include_root = root + _add_directory(library_dirs, lib_root) _add_directory(include_dirs, include_root) From abf1211eaa64a53ee1ea50334f45400820274f75 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 11 Nov 2016 16:55:50 +0000 Subject: [PATCH 058/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c4887a4df..1ca81f002 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Search for tkinter first in builtins #2210 + [matthew-brett] + - Tests: Replace try/except/fail pattern with TestCase.assertRaises() #2200 [jdufresne] From 6a795dd168d0ebe70893b33bbeac2fcdaff8f4c1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 11 Nov 2016 16:58:24 +0000 Subject: [PATCH 059/267] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 1ca81f002..9ad5a46e8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Setup: optionally use pkg-config (when present) to detect dependencies #2074 + [garbas] + - Search for tkinter first in builtins #2210 [matthew-brett] From c9a69660b14b2271e6eb056265260e3336dbfa71 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 11 Nov 2016 17:01:12 +0000 Subject: [PATCH 060/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9ad5a46e8..cb7e85677 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix for ImageQt Segfault, fixes #1370 #2182 + [wiredfool] + +- Setup: Close file in setup.py after finished reading #2208 + [jdufresne] + - Setup: optionally use pkg-config (when present) to detect dependencies #2074 [garbas] From 3ba7fec8059465715923aa6233e29f041b364b2d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 11 Nov 2016 17:04:46 +0000 Subject: [PATCH 061/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cb7e85677..42769a91f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Replaced range(len()) #2197 + [radarhere] + - Fix for ImageQt Segfault, fixes #1370 #2182 [wiredfool] From 015e8cc266753839d9f8887af0e6c90885c9b679 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 19 Oct 2016 19:07:08 +0100 Subject: [PATCH 062/267] raise custom exceptions when required/requested items are not found --- setup.py | 102 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/setup.py b/setup.py index 5d75884d4..2febe8195 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,8 @@ _LIB_IMAGING = ( DEBUG = False +class DependencyException(Exception): pass +class DefaultDependencyException(Exception): pass def _dbg(s, tp=None): if DEBUG: @@ -558,13 +560,9 @@ class pil_build_ext(build_ext): for f in feature: if not getattr(feature, f) and feature.require(f): if f in ('jpeg', 'zlib'): - raise ValueError( - '%s is required unless explicitly disabled' - ' using --disable-%s, aborting' % (f, f)) - raise ValueError( - '--enable-%s requested but %s not found, aborting.' % - (f, f)) - + raise DefaultDependencyException(f) + raise DependencyException(f) + # # core library @@ -758,38 +756,60 @@ class pil_build_ext(build_ext): def debug_build(): return hasattr(sys, 'gettotalrefcount') +try: + setup(name=NAME, + version=PILLOW_VERSION, + description='Python Imaging Library (Fork)', + long_description=_read('README.rst').decode('utf-8'), + author='Alex Clark (Fork Author)', + author_email='aclark@aclark.net', + url='http://python-pillow.org', + classifiers=[ + "Development Status :: 6 - Mature", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", + "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + "Topic :: Multimedia :: Graphics :: Viewers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + cmdclass={"build_ext": pil_build_ext}, + ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + include_package_data=True, + packages=find_packages(), + scripts=glob.glob("Scripts/*.py"), + test_suite='nose.collector', + keywords=["Imaging", ], + license='Standard PIL License', + zip_safe=not debug_build(), ) +except DefaultDependencyException as err: + msg = """ + +The headers or library files could not be found for %s, +a required dependency when compiling Pillow from source. + +Please see the install instructions at: + http://pillow.readthedocs.io/en/latest/installation.html + +""" % (str(err)) + sys.stderr.write(msg) + raise DefaultDependencyException(msg) +except DependencyException as err: + msg = """ + +The headers or library files could not be found for %s, +which was requested by the option flag --enable-%s + +""" % (str(err), str(err)) + sys.stderr.write(msg) + raise DependencyException(msg) -setup(name=NAME, - version=PILLOW_VERSION, - description='Python Imaging Library (Fork)', - long_description=_read('README.rst').decode('utf-8'), - author='Alex Clark (Fork Author)', - author_email='aclark@aclark.net', - url='https://python-pillow.org', - classifiers=[ - "Development Status :: 6 - Mature", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], - include_package_data=True, - packages=find_packages(), - scripts=glob.glob("Scripts/*.py"), - test_suite='nose.collector', - keywords=["Imaging", ], - license='Standard PIL License', - zip_safe=not debug_build(), ) From 8b596600af3046620c3707bdddf6275966c89834 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 11 Nov 2016 09:12:07 -0800 Subject: [PATCH 063/267] renamed Default->Required dependency exception --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 2febe8195..b14b860ba 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ _LIB_IMAGING = ( DEBUG = False class DependencyException(Exception): pass -class DefaultDependencyException(Exception): pass +class RequiredDependencyException(Exception): pass def _dbg(s, tp=None): if DEBUG: @@ -560,7 +560,7 @@ class pil_build_ext(build_ext): for f in feature: if not getattr(feature, f) and feature.require(f): if f in ('jpeg', 'zlib'): - raise DefaultDependencyException(f) + raise RequiredDependencyException(f) raise DependencyException(f) # @@ -791,7 +791,7 @@ try: keywords=["Imaging", ], license='Standard PIL License', zip_safe=not debug_build(), ) -except DefaultDependencyException as err: +except RequiredDependencyException as err: msg = """ The headers or library files could not be found for %s, @@ -802,7 +802,7 @@ Please see the install instructions at: """ % (str(err)) sys.stderr.write(msg) - raise DefaultDependencyException(msg) + raise RequiredDependencyException(msg) except DependencyException as err: msg = """ From 6bc8fdd34275669af9be4bc062a58b6b9af0e49c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 11 Nov 2016 16:59:32 -0800 Subject: [PATCH 064/267] Close file after reading in ImagePalette.load() Fixes some "ResourceWarning: unclosed file ..." when running tests with warnings enabled. --- PIL/ImagePalette.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 3b60068bc..8bf09385c 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -197,23 +197,23 @@ def load(filename): # FIXME: supports GIMP gradients only - fp = open(filename, "rb") + with open(filename, "rb") as fp: - for paletteHandler in [ - GimpPaletteFile.GimpPaletteFile, - GimpGradientFile.GimpGradientFile, - PaletteFile.PaletteFile - ]: - try: - fp.seek(0) - lut = paletteHandler(fp).getpalette() - if lut: - break - except (SyntaxError, ValueError): - # import traceback - # traceback.print_exc() - pass - else: - raise IOError("cannot load palette") + for paletteHandler in [ + GimpPaletteFile.GimpPaletteFile, + GimpGradientFile.GimpGradientFile, + PaletteFile.PaletteFile + ]: + try: + fp.seek(0) + lut = paletteHandler(fp).getpalette() + if lut: + break + except (SyntaxError, ValueError): + # import traceback + # traceback.print_exc() + pass + else: + raise IOError("cannot load palette") return lut # data, rawmode From 05b5382a605c39cdef887ca398852f7197e0a6f1 Mon Sep 17 00:00:00 2001 From: Jake Merdich Date: Mon, 14 Nov 2016 00:31:23 -0500 Subject: [PATCH 065/267] Add support for another type of BMP bitfield There's an example file [here](https://dl.dropboxusercontent.com/u/11688373/Gompei%20(1).bmp), though I don't have the rights to commit it here. --- PIL/BmpImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index eccd29923..a922d8ff8 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -136,12 +136,13 @@ class BmpImageFile(ImageFile.ImageFile): # ----------------- Process BMP with Bitfields compression (not palette) if file_info['compression'] == self.BITFIELDS: SUPPORTED = { - 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)], + 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0) ], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] } MASK_MODES = { (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", + (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (24, (0xff0000, 0xff00, 0xff)): "BGR", From 3dcef86fe55cdc73dfe2de312ea60663c82f87e2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Nov 2016 21:12:25 +1100 Subject: [PATCH 066/267] Added missing top-level test code --- Tests/test_image.py | 3 --- Tests/test_image_paste.py | 6 +++++- Tests/test_scipy.py | 6 +++++- Tests/test_tiff_ifdrational.py | 5 ++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 717d4dbfb..a625a429b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -261,8 +261,5 @@ class TestImage(PillowTestCase): Image.core.fill('RGB', (2,-2), (0,0,0)) - - - if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index f3fe9a2af..0d3d18858 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -1,4 +1,4 @@ -from helper import PillowTestCase, cached_property +from helper import unittest, PillowTestCase, cached_property from PIL import Image @@ -250,3 +250,7 @@ class TestImagingPaste(PillowTestCase): (48, 15, 15, 47), (126, 63, 255, 63) ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/test_scipy.py b/Tests/test_scipy.py index dda49e707..8be16c518 100644 --- a/Tests/test_scipy.py +++ b/Tests/test_scipy.py @@ -1,4 +1,4 @@ -from helper import PillowTestCase +from helper import unittest, PillowTestCase try: import numpy as np @@ -41,3 +41,7 @@ class Test_scipy_resize(PillowTestCase): assert_equal(im2, res) assert_equal(im3, res) assert_equal(im4, res) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index dd3ad1b3d..7e2c908b5 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,6 +1,6 @@ from __future__ import print_function -from helper import PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper from PIL import TiffImagePlugin, Image from PIL.TiffImagePlugin import IFDRational @@ -60,3 +60,6 @@ class Test_IFDRational(PillowTestCase): reloaded = Image.open(out) self.assertEqual(float(IFDRational(301, 1)), float(reloaded.tag_v2[282])) + +if __name__ == '__main__': + unittest.main() From da1c2ca703399c36a33d51c8eb3632222ea8f924 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Nov 2016 13:19:35 +0000 Subject: [PATCH 067/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 42769a91f..34e7a3e2b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Added missing top-level test __main__ #2222 + [radarhere] + - Replaced range(len()) #2197 [radarhere] From e63b97ea428de96ea464be738cfe84f0e4c230d1 Mon Sep 17 00:00:00 2001 From: Jake Merdich Date: Mon, 14 Nov 2016 09:45:46 -0500 Subject: [PATCH 068/267] Add testing for bmp 'questionable' files --- Tests/images/bmp/q/rgb32bf-xbgr.bmp | Bin 0 -> 32650 bytes Tests/test_bmp_reference.py | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Tests/images/bmp/q/rgb32bf-xbgr.bmp diff --git a/Tests/images/bmp/q/rgb32bf-xbgr.bmp b/Tests/images/bmp/q/rgb32bf-xbgr.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d80495e7e9dc08688a5a543957d89378d4c0d6d2 GIT binary patch literal 32650 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 Date: Mon, 14 Nov 2016 10:26:57 -0500 Subject: [PATCH 069/267] Update test image for xbgr bmp files --- Tests/images/bmp/q/rgb32bf-xbgr.bmp | Bin 32650 -> 32650 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Tests/images/bmp/q/rgb32bf-xbgr.bmp b/Tests/images/bmp/q/rgb32bf-xbgr.bmp index d80495e7e9dc08688a5a543957d89378d4c0d6d2..c6c05e1480ccc9f14d0d2a58e569e239a99abbc6 100644 GIT binary patch literal 32650 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 literal 32650 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 Date: Mon, 14 Nov 2016 07:47:02 -0800 Subject: [PATCH 070/267] Removed the handles_eof flag in the decoder, as there are no users of it --- PIL/ImageFile.py | 2 +- decode.c | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index 55cb701a1..207dcfd00 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -210,7 +210,7 @@ class ImageFile(Image.Image): else: raise IOError("image file is truncated") - if not s and not decoder.handles_eof: # truncated jpeg + if not s: # truncated jpeg self.tile = [] # JpegDecode needs to clean things up here either way diff --git a/decode.c b/decode.c index f700747e1..91de1075e 100644 --- a/decode.c +++ b/decode.c @@ -52,7 +52,6 @@ typedef struct { struct ImagingCodecStateInstance state; Imaging im; PyObject* lock; - int handles_eof; int pulls_fd; } ImagingDecoderObject; @@ -95,9 +94,6 @@ PyImaging_DecoderNew(int contextsize) /* Initialize the cleanup function pointer */ decoder->cleanup = NULL; - /* Most decoders don't want to handle EOF themselves */ - decoder->handles_eof = 0; - /* set if the decoder needs to pull data from the fd, instead of having it pushed */ decoder->pulls_fd = 0; @@ -239,12 +235,6 @@ _setfd(ImagingDecoderObject* decoder, PyObject* args) } -static PyObject * -_get_handles_eof(ImagingDecoderObject *decoder) -{ - return PyBool_FromLong(decoder->handles_eof); -} - static PyObject * _get_pulls_fd(ImagingDecoderObject *decoder) { @@ -260,9 +250,6 @@ static struct PyMethodDef methods[] = { }; static struct PyGetSetDef getseters[] = { - {"handles_eof", (getter)_get_handles_eof, NULL, - "True if this decoder expects to handle EOF itself.", - NULL}, {"pulls_fd", (getter)_get_pulls_fd, NULL, "True if this decoder expects to pull from self.fd itself.", NULL}, @@ -918,7 +905,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) if (decoder == NULL) return NULL; - decoder->handles_eof = 1; decoder->pulls_fd = 1; decoder->decode = ImagingJpeg2KDecode; decoder->cleanup = ImagingJpeg2KDecodeCleanup; From 28fdac2c1d704e3dece35f86e1d707add59defcd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Nov 2016 16:14:12 +0000 Subject: [PATCH 071/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 34e7a3e2b..6366ab2bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Add support for another BMP bitfield #2221 + [jmerdich] + - Added missing top-level test __main__ #2222 [radarhere] From c3fe5d43137aa83acd5c8345cd997152b4a54ad9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Nov 2016 19:55:56 +1100 Subject: [PATCH 072/267] Changed behaviour of default box argument for paste method to match docs --- PIL/Image.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 000757594..655aa3d07 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1307,8 +1307,7 @@ class Image(object): box = None if box is None: - # cover all of self - box = (0, 0) + self.size + box = (0, 0) if len(box) == 2: # upper left corner given; get size from image or mask @@ -1321,7 +1320,7 @@ class Image(object): raise ValueError( "cannot determine region size; use 4-item box" ) - box = box + (box[0]+size[0], box[1]+size[1]) + box += (box[0]+size[0], box[1]+size[1]) if isStringType(im): from PIL import ImageColor From b6a4d9bc94f86b61682d5b0538f944eb4b5c3344 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Nov 2016 21:16:04 +1100 Subject: [PATCH 073/267] Added test --- Tests/test_image_paste.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 0d3d18858..4450fd028 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -251,6 +251,13 @@ class TestImagingPaste(PillowTestCase): (126, 63, 255, 63) ]) + def test_different_sizes(self): + im = Image.new('RGB', (100, 100)) + im2 = Image.new('RGB', (50, 50)) + + im.copy().paste(im2) + im.copy().paste(im2, (0,0)) + if __name__ == '__main__': unittest.main() From 5a820fb7c08383663870d2e5ab0e453e1e05202c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Nov 2016 21:58:51 +0000 Subject: [PATCH 074/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6366ab2bc..841e6b90d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Changed behaviour of default box argument for paste method to match docs #2211 + [radarhere] + - Add support for another BMP bitfield #2221 [jmerdich] From e4d5eed3b2ac02b29d610aeb86750fc242049810 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Nov 2016 22:11:54 +0000 Subject: [PATCH 075/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 841e6b90d..66bcff213 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Close file after reading in ImagePalette.load() #2215 + [jdufresne] + - Changed behaviour of default box argument for paste method to match docs #2211 [radarhere] From ffa5bc27268f89cb726c557d194378cd7a6a8832 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 14 Nov 2016 17:48:54 -0800 Subject: [PATCH 076/267] Use generator expressions instead of list comprehension Avoids unnecessary temporary lists in memory. --- PIL/GifImagePlugin.py | 2 +- PIL/ImageMorph.py | 4 ++-- PIL/JpegImagePlugin.py | 2 +- PIL/TiffImagePlugin.py | 2 +- Tests/helper.py | 2 +- Tests/test_file_gif.py | 2 +- Tests/test_font_pcf.py | 2 +- Tests/test_imagemorph.py | 4 ++-- setup.py | 8 +++----- 9 files changed, 13 insertions(+), 15 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 8b2dfac0a..2775a00f1 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -600,7 +600,7 @@ def _get_palette_bytes(im, palette, info): if palette and isinstance(palette, bytes): source_palette = palette[:768] else: - source_palette = bytearray([i//3 for i in range(768)]) + source_palette = bytearray(i//3 for i in range(768)) used_palette_colors = palette_bytes = None diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 748ecdb61..0bbfbb42b 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -78,7 +78,7 @@ class LutBuilder(object): def build_default_lut(self): symbols = [0, 1] m = 1 << 4 # pos of current pixel - self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)]) + self.lut = bytearray(symbols[(i & m) > 0] for i in range(LUT_SIZE)) def get_lut(self): return self.lut @@ -88,7 +88,7 @@ class LutBuilder(object): string permuted according to the permutation list. """ assert(len(permutation) == 9) - return ''.join([pattern[p] for p in permutation]) + return ''.join(pattern[p] for p in permutation) def _pattern_permute(self, basic_pattern, options, basic_result): """pattern_permute takes a basic pattern and its result and clones diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index ef229e611..6d4e588c0 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -408,7 +408,7 @@ def _fixup_dict(src_dict): except: pass return value - return dict([(k, _fixup(v)) for k, v in src_dict.items()]) + return dict((k, _fixup(v)) for k, v in src_dict.items()) def _getexif(self): diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 52e04654c..d442c3766 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -132,7 +132,7 @@ COMPRESSION_INFO = { 34677: "tiff_sgilog24", } -COMPRESSION_INFO_REV = dict([(v, k) for (k, v) in COMPRESSION_INFO.items()]) +COMPRESSION_INFO_REV = dict((v, k) for (k, v) in COMPRESSION_INFO.items()) OPEN_INFO = { # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, diff --git a/Tests/helper.py b/Tests/helper.py index 9f1501249..5cfd7678f 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -49,7 +49,7 @@ class PillowTestCase(unittest.TestCase): len(a), len(b), msg or "got length %s, expected %s" % (len(a), len(b))) self.assertTrue( - all([x == y for x, y in zip(a, b)]), + all(x == y for x, y in zip(a, b)), msg or "got %s, expected %s" % (a, b)) except: self.assertEqual(a, b, msg) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 83318366b..1672a14f0 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -60,7 +60,7 @@ class TestFileGif(PillowTestCase): def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range im = Image.frombytes('P', (colors,colors), - bytes(bytearray(list(range(256-colors,256))*colors))) + bytes(bytearray(range(256-colors,256))*colors)) im = im.resize((size,size)) outfile = BytesIO() im.save(outfile, 'GIF') diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 6afa9d476..25c5d7001 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -55,7 +55,7 @@ class TestFontPcf(PillowTestCase): self.assert_image_equal(image, compare) def test_high_characters(self): - message = "".join([chr(i+1) for i in range(140, 232)]) + message = "".join(chr(i+1) for i in range(140, 232)) self._test_high_characters(message) # accept bytes instances in Py3. if bytes is not str: diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 22372b78d..ea54417a2 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -25,8 +25,8 @@ class MorphTests(PillowTestCase): chars = '.1' width, height = im.size return '\n'.join( - [''.join([chars[im.getpixel((c, r)) > 0] for c in range(width)]) - for r in range(height)]) + ''.join(chars[im.getpixel((c, r)) > 0] for c in range(width)) + for r in range(height)) def string_to_img(self, image_string): """Turn a string image representation into a binary image""" diff --git a/setup.py b/setup.py index 5d75884d4..4f82363c6 100644 --- a/setup.py +++ b/setup.py @@ -405,8 +405,7 @@ class pil_build_ext(build_ext): best_path = None for name in os.listdir(program_files): if name.startswith('OpenJPEG '): - version = tuple([int(x) for x in name[9:].strip().split( - '.')]) + version = tuple(int(x) for x in name[9:].strip().split('.')) if version > best_version: best_version = version best_path = os.path.join(program_files, name) @@ -466,7 +465,7 @@ class pil_build_ext(build_ext): os.path.isfile(os.path.join(directory, name, 'openjpeg.h')): _dbg('Found openjpeg.h in %s/%s', (directory, name)) - version = tuple([int(x) for x in name[9:].split('.')]) + version = tuple(int(x) for x in name[9:].split('.')) if best_version is None or version > best_version: best_version = version best_path = os.path.join(directory, name) @@ -479,8 +478,7 @@ class pil_build_ext(build_ext): # include path _add_directory(self.compiler.include_dirs, best_path, 0) feature.jpeg2000 = 'openjp2' - feature.openjpeg_version = '.'.join([str(x) for x in - best_version]) + feature.openjpeg_version = '.'.join(str(x) for x in best_version) if feature.want('imagequant'): _dbg('Looking for imagequant') From 25ac9a20e4f068d72e4cf910a14ee9b41c2093ff Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 14 Nov 2016 21:14:04 -0800 Subject: [PATCH 077/267] Use a context manager in FontFile.save() to ensure file is always closed --- PIL/FontFile.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/PIL/FontFile.py b/PIL/FontFile.py index 1ccfaa3c3..d86f9b3ab 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -100,14 +100,13 @@ class FontFile(object): self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG") # font metrics - fp = open(os.path.splitext(filename)[0] + ".pil", "wb") - fp.write(b"PILfont\n") - fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! - fp.write(b"DATA\n") - for id in range(256): - m = self.metrics[id] - if not m: - puti16(fp, [0] * 10) - else: - puti16(fp, m[0] + m[1] + m[2]) - fp.close() + with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: + fp.write(b"PILfont\n") + fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! + fp.write(b"DATA\n") + for id in range(256): + m = self.metrics[id] + if not m: + puti16(fp, [0] * 10) + else: + puti16(fp, m[0] + m[1] + m[2]) From 38fd77ca7dc4786ddafba064bcce789bed41f216 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 15 Nov 2016 20:13:25 +1100 Subject: [PATCH 078/267] Fixed bug in saving to fp-objects in Python >= 3.4 --- PIL/Image.py | 2 +- Tests/test_image.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 655aa3d07..368186f81 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1659,7 +1659,7 @@ class Image(object): if isinstance(fp, Path): filename = str(fp) open_fp = True - elif hasattr(fp, "name") and isPath(fp.name): + if not filename and hasattr(fp, "name") and isPath(fp.name): # only set the name for metadata purposes filename = fp.name diff --git a/Tests/test_image.py b/Tests/test_image.py index a625a429b..ef9aa16af 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -63,6 +63,18 @@ class TestImage(PillowTestCase): os.remove(temp_file) im.save(Path(temp_file)) + def test_fp_name(self): + temp_file = self.tempfile("temp.jpg") + + class FP(object): + def write(a, b): + pass + fp = FP() + fp.name = temp_file + + im = hopper() + im.save(fp) + def test_tempfile(self): # see #1460, pathlib support breaks tempfile.TemporaryFile on py27 # Will error out on save on 3.0.0 From 17d38d09050ea6a546fcb3ddfce189c2b82784fe Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 Nov 2016 11:00:44 +0200 Subject: [PATCH 079/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 66bcff213..f7d208944 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Use generator expressions instead of list comprehension #2225 + [jdufresne] + - Close file after reading in ImagePalette.load() #2215 [jdufresne] From 85ec6eb251a354fa6e3a4e1f2b37a0fa94f962d7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 17 Nov 2016 06:21:50 -0800 Subject: [PATCH 080/267] Close file after finished reading in ImageFont._load_pilfont() Fixes some "ResourceWarning: unclosed file ..." when running tests with warnings enabled. --- PIL/ImageFont.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 6bd48dd6e..49494b33f 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -62,23 +62,22 @@ class ImageFont(object): def _load_pilfont(self, filename): - fp = open(filename, "rb") - - for ext in (".png", ".gif", ".pbm"): - try: - fullname = os.path.splitext(filename)[0] + ext - image = Image.open(fullname) - except: - pass + with open(filename, "rb") as fp: + for ext in (".png", ".gif", ".pbm"): + try: + fullname = os.path.splitext(filename)[0] + ext + image = Image.open(fullname) + except: + pass + else: + if image and image.mode in ("1", "L"): + break else: - if image and image.mode in ("1", "L"): - break - else: - raise IOError("cannot find glyph data file") + raise IOError("cannot find glyph data file") - self.file = fullname + self.file = fullname - return self._load_pilfont_data(fp, image) + return self._load_pilfont_data(fp, image) def _load_pilfont_data(self, file, image): From 8758866f47bcd1a33a5e517f3dfd834b917843ff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 10:45:33 +1100 Subject: [PATCH 081/267] Fixed typo --- PIL/SpiderImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index 07f623c7b..08726f90c 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -58,7 +58,7 @@ iforms = [1, 3, -11, -12, -21, -22] # There is no magic number to identify Spider files, so just check a # series of header locations to see if they have reasonable values. -# Returns no.of bytes in the header, if it is a valid Spider header, +# Returns no. of bytes in the header, if it is a valid Spider header, # otherwise returns 0 def isSpiderHeader(t): From 5d37103d01718030c31f1a0d23c43422ed8a1a9a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 10:55:08 +1100 Subject: [PATCH 082/267] Corrected unfinished sentence --- PIL/FtexImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/FtexImagePlugin.py b/PIL/FtexImagePlugin.py index 9dab83621..4fa462f04 100644 --- a/PIL/FtexImagePlugin.py +++ b/PIL/FtexImagePlugin.py @@ -13,7 +13,7 @@ packed custom format called FTEX. This file format uses file extensions FTC and * FTC files are compressed textures (using standard texture compression). * FTU files are not compressed. Texture File Format -The FTC and FTU texture files both use the same format, called. This +The FTC and FTU texture files both use the same format. This has the following structure: {header} {format_directory} From 5cfbba5f5f56aa17889a248c91e2cf612ed57deb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 17:26:51 +1100 Subject: [PATCH 083/267] Updated Tk Tcl to 8.6.6 --- winbuild/config.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 1b5d5e380..1b5b93210 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -62,17 +62,17 @@ libs = { 'version': '8.5.19', }, 'tcl-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.5/tcl865-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tcl865-src.zip', - 'hash': 'md5:932e360acb40ec760ebeed659bc893de', + 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.6/tcl866-src.zip', + 'filename': PILLOW_DEPENDS_DIR + 'tcl866-src.zip', + 'hash': 'md5:45dae95abc12a5f8c29dca5baf169a13', 'dir': '', }, 'tk-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.5/tk865-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tk865-src.zip', - 'hash': 'md5:f2f5802a5a3b1f70b906e6930db12089', + 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.6/tk866-src.zip', + 'filename': PILLOW_DEPENDS_DIR + 'tk866-src.zip', + 'hash': 'md5:5004cc0ed2ab820406a36a0c0553b917', 'dir': '', - 'version': '8.6.5', + 'version': '8.6.6', }, 'webp': { 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.5.0.tar.gz', From 7ca183743f366beb302a69ccb48fe9503e2ece26 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 17:29:59 +1100 Subject: [PATCH 084/267] Updated libwebp to 0.5.1 --- depends/install_webp.sh | 10 +++++----- winbuild/config.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 08507ede6..356c6e552 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,14 +1,14 @@ #!/bin/bash # install webp -if [ ! -f libwebp-0.5.0.tar.gz ]; then - wget -O 'libwebp-0.5.0.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/libwebp-0.5.0.tar.gz?raw=true' +if [ ! -f libwebp-0.5.1.tar.gz ]; then + wget -O 'libwebp-0.5.1.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/libwebp-0.5.1.tar.gz?raw=true' fi -rm -r libwebp-0.5.0 -tar -xvzf libwebp-0.5.0.tar.gz +rm -r libwebp-0.5.1 +tar -xvzf libwebp-0.5.1.tar.gz -pushd libwebp-0.5.0 +pushd libwebp-0.5.1 ./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install diff --git a/winbuild/config.py b/winbuild/config.py index 1b5d5e380..7790caf4a 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -75,10 +75,10 @@ libs = { 'version': '8.6.5', }, 'webp': { - 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.5.0.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'libwebp-0.5.0.tar.gz', - 'hash': 'sha1:d3de815b272fcf88fc4f2dc1ab65d176bcb8df68', - 'dir': 'libwebp-0.5.0', + 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.5.1.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'libwebp-0.5.1.tar.gz', + 'hash': 'sha1:66efb2213015ad3460bef64b4fb218fdc10ce83f', + 'dir': 'libwebp-0.5.1', }, 'openjpeg': { 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.0/openjpeg-2.1.0.tar.gz', From dc0d4f8db6dc19d7e0e1a63f94d2749649594ddb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 18:22:53 +1100 Subject: [PATCH 085/267] Updated openjpeg to 2.1.2 --- depends/install_openjpeg.sh | 10 +++++----- winbuild/config.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index fb7d3e9e4..8d5f5c010 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -2,16 +2,16 @@ # install openjpeg -if [ ! -f openjpeg-2.1.0.tar.gz ]; then - wget -O 'openjpeg-2.1.0.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/openjpeg-2.1.0.tar.gz?raw=true' +if [ ! -f openjpeg-2.1.2.tar.gz ]; then + wget -O 'openjpeg-2.1.2.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/openjpeg-2.1.2.tar.gz?raw=true' fi -rm -r openjpeg-2.1.0 -tar -xvzf openjpeg-2.1.0.tar.gz +rm -r openjpeg-2.1.2 +tar -xvzf openjpeg-2.1.2.tar.gz -pushd openjpeg-2.1.0 +pushd openjpeg-2.1.2 cmake -DCMAKE_INSTALL_PREFIX=/usr . && make -j4 && sudo make -j4 install diff --git a/winbuild/config.py b/winbuild/config.py index 1b5d5e380..1a295acef 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -81,10 +81,10 @@ libs = { 'dir': 'libwebp-0.5.0', }, 'openjpeg': { - 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.0/openjpeg-2.1.0.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'openjpeg-2.1.0.tar.gz', - 'hash': 'md5:f6419fcc233df84f9a81eb36633c6db6', - 'dir': 'openjpeg-2.1.0', + 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.2/openjpeg-2.1.2.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'openjpeg-2.1.2.tar.gz', + 'hash': 'md5:40a7bfdcc66280b3c1402a0eb1a27624', + 'dir': 'openjpeg-2.1.2', }, } From 5df2851f85778fb707fd57b565e57a2d634f9745 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Nov 2016 17:39:30 -0800 Subject: [PATCH 086/267] Fixed literal expansion, allowed run length > line length --- libImaging/SunRleDecode.c | 122 ++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/libImaging/SunRleDecode.c b/libImaging/SunRleDecode.c index 6c240e400..9c126755f 100644 --- a/libImaging/SunRleDecode.c +++ b/libImaging/SunRleDecode.c @@ -7,7 +7,7 @@ * decoder for SUN RLE data. * * history: - * 97-01-04 fl Created + * 97-01-04 fl Created * * Copyright (c) Fredrik Lundh 1997. * Copyright (c) Secret Labs AB 1997. @@ -24,88 +24,106 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { int n; UINT8* ptr; + UINT8 extra_data = 0; + UINT8 extra_bytes = 0; + int ct_bytes = 0; ptr = buf; for (;;) { - if (bytes < 1) - return ptr - buf; + if (bytes < 1) + return ptr - buf; - if (ptr[0] == 0x80) { + if (ptr[0] == 0x80) { - if (bytes < 2) - break; + if (bytes < 2) + break; - n = ptr[1]; + n = ptr[1]; - if (n == 0) { - /* Literal 0x80 (2 bytes) */ - n = 1; + if (n == 0) { - state->buffer[state->x] = 0x80; + /* Literal 0x80 (2 bytes) */ + n = 1; + ct_bytes += n; - ptr += 2; - bytes -= 2; + state->buffer[state->x] = 0x80; - } else { + ptr += 2; + bytes -= 2; - /* Run (3 bytes) */ - if (bytes < 3) - break; + } else { - if (state->x + n > state->bytes) { - /* FIXME: is this correct? */ - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } + /* Run (3 bytes) */ + if (bytes < 3) + break; - memset(state->buffer + state->x, ptr[2], n); + ct_bytes += n; - ptr += 3; - bytes -= 3; + if (state->x + n > state->bytes) { + extra_bytes = n; /* full value */ + n = state->bytes - state->x; + extra_bytes -= n; + extra_data = ptr[2]; + } - } + memset(state->buffer + state->x, ptr[2], n); - } else { + ptr += 3; + bytes -= 3; - /* Literal (1+n bytes block) */ - n = ptr[0]; + } - if (bytes < 1 + n) - break; + } else { - if (state->x + n > state->bytes) { - /* FIXME: is this correct? */ - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } + /* Literal byte */ + n = 1; + ct_bytes += n; - memcpy(state->buffer + state->x, ptr + 1, n); + state->buffer[state->x] = ptr[0]; - ptr += 1 + n; - bytes -= 1 + n; + ptr += 1; + bytes -= 1; - } + } - state->x += n; + for (;;) { + state->x += n; + + if (state->x >= state->bytes) { - if (state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle((UINT8*) im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, state->buffer, + state->xsize); - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->x = 0; - state->x = 0; + if (++state->y >= state->ysize) { + /* End of file (errcode = 0) */ + printf("%d", ct_bytes); + return -1; + } + } + + if (extra_bytes == 0) { + break; + } - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } + if (state->x > 0) { + break; // assert + } + if (extra_bytes >= state->bytes) { + n = state->bytes; + } else { + n = extra_bytes; + } + memset(state->buffer + state->x, extra_data, n); + extra_bytes -= n; + } } return ptr - buf; From e43c91cf1c956022fac9c830032d5b3b03f1e6a7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Nov 2016 19:12:30 -0800 Subject: [PATCH 087/267] Working 1 bit sun_rle raster file --- Tests/images/sunraster.im1 | Bin 0 -> 2106 bytes Tests/images/sunraster.im1.png | Bin 0 -> 1774 bytes Tests/test_file_sun.py | 6 ++++++ libImaging/SunRleDecode.c | 26 +++++++++++++++++++++----- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 Tests/images/sunraster.im1 create mode 100644 Tests/images/sunraster.im1.png diff --git a/Tests/images/sunraster.im1 b/Tests/images/sunraster.im1 new file mode 100644 index 0000000000000000000000000000000000000000..82c92bca923100c719a6dc039579e06953133ef5 GIT binary patch literal 2106 zcmaKt&2ig63`SRCW_VNyJf;ktWyuBPm}`2-EKA5GEu;>Wq7rn}L1X$o>~c*iwmcp{ z1n_<(ZZO4>0e-a|4->9@z_%}{p|Nl(gb$m_zgxF1&@r1mEyoa2sOydy` z0Y_q|E2^8OJ8CmcZ^Yzj8jZnuE>>A`_=f7Mc!1&onqu6VVqaiHRBAbO$%#`dP7`*T zfuqbs;v0$_Z>YXtUc!CB@PJ(x3rGrTwsDD~5+u3wP^#n)J;Et9Xq}*2Sy8hzc=B!7;u{Dc3G~dx>tYqs?@-oH1W`)O|;HniD_J+J}YRXXB0AmAFIj$hXJw zhVF5kBM>6-@*IL!oOr`3zU49h4ZBBRx%Aa$-zGro4mH=IdY%jwN#jn4nO=GN??4;_ zflf9~ItVr9Vq~%{nY~-Ym+qrGN; zpxi?zD@JoJ8Jt{jnaa9Qas|q6v@h5^sK)kbVXGIRcDkOO9o0VL?N}ieCC^K@D9xcn z36>^!QSzd+vZC%v3Ab^h-62=kUD=~iuF`tN+`vPu-ZfV5n_-zS%`T|uF|2~;r&RaU z=S;47Peqy2XLHUhmuk1xq1M*kAA8YZ>rP~O@?~6}N!>~odtR2foxoJD(}~ymleP8~ z;x_oWXFe`i9Qt`BmiLhUAzm5$kqYu+64dn=VNZhIxu5Wn(%TX_j9L?=R4nfxk z9d15fnUWlcvV6*VGM}j2k#rHiA^w%|L^y+cjCZ_Upf*S%o5(jYA@2Oz%M)UdEX2;E pFn6dv3Mta1KhZ^z!6_4ktvDqfn%L>>4el<_KH*Qk?$2G{(>vU5R*+)?6&i5#KlkidUC`^|!k8lU1NO>2e&;{8r#aTM;F3BCv zOn^hcZ7c!C=YPoIaE9dA1n7QNnb&{~*nkb#fDPDy4fuZpPJ<@U1YCN}l+FTg7wSkj15Ka$h0?q?|IH&Rd^oo z`J@S)IvAz(d^LFoJx>fFgb+ONIjDSBlW`uj9OpZ5>a-lk-u~EY0*wQHQ2~B88OKq{ zao*FpXD!EhR~RgGLfE@P5S)Lz00hpWsxKs!OS?0Mq z;XDBNjn7*HzW|s5=5Ug{kCD}b<91U@I*ppbWpT2RP4 zMJ*^ajx&e_g}gy5y&_-@Vo4zkBl9f2b2uB5K-X+s5TRU^!GSKlW`o0HdCKZ4g9S{cCu(`6w=L7)7)lv!k3;-8b_#2fbUnjaWinVP`JBN+xwDzj< z1OVS8)#y@yMKN+$cXAnzV&tw?cy;c4F3lX6`x*~P+X0NnXPdm2Y!u{ve7D1JhIJ7odQDSu$jYohG)5r+rt@(DL@HO}7pE-uO^qDk_tMCy>#~UsXJ`-@bCTAa3 zuod>1M3D`A$UJvpo1AOKU@Oec3LiM8%V$3DIo+kqAH%6jhD1IvGb?o0F#@u1ek0pmW&~MB0h)|rd(SD zy!30=76DVuWTG)lHIs=H;yA86Q3g!(e#wBrt~^mRhA?imoMj3VgD6voVO2T{6|Ng> zg$mdG@skbh=I#T%;GVGpT`?H z@MRuw-+cTLC!E@=m!Q;?l>Cr~nK$_Cy7fjf;m-0f^W5 z7Y*PLd$tC!k3CxhI6HCC0Cuq>Y5;A#t!My^vL6C2Dh5=5uhB`J3NUs=6<~bPrvmIE z&sG82%+Ku4XanO<2k35#`A!u^(Lo2ezeR^THF>;+n>$rF+~U`rD(r6o@lF+Xw+O2P zw8buffer[state->x] = 0x80; @@ -60,7 +58,27 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (bytes < 3) break; - ct_bytes += n; + /* from (http://www.fileformat.info/format/sunraster/egff.htm) + + For example, a run of 100 pixels with the value of + 0Ah would encode as the values 80h 64h 0Ah. A + single pixel value of 80h would encode as the + values 80h 00h. The four unencoded bytes 12345678h + would be stored in the RLE stream as 12h 34h 56h + 78h. 100 pixels, n=100, not 100 pixels, n=99. + + But Wait! There's More! + (http://www.fileformat.info/format/sunraster/spec/598a59c4fac64c52897585d390d86360/view.htm) + + If the first byte is 0x80, and the second byte is + not zero, the record is three bytes long. The + second byte is a count and the third byte is a + value. Output (count+1) pixels of that value. + + 2 specs, same site, but Imagemagick and GIMP seem + to agree on the second one. + */ + n += 1; if (state->x + n > state->bytes) { extra_bytes = n; /* full value */ @@ -80,7 +98,6 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* Literal byte */ n = 1; - ct_bytes += n; state->buffer[state->x] = ptr[0]; @@ -103,7 +120,6 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (++state->y >= state->ysize) { /* End of file (errcode = 0) */ - printf("%d", ct_bytes); return -1; } } From 318ff7d332ed47fbb25ec5f2c5dc805b7bce2765 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Nov 2016 19:43:43 -0800 Subject: [PATCH 088/267] fixed support for hopper.ras, and other RGB sun raster files --- PIL/SunImagePlugin.py | 37 ++++++++++++++++++++++++++++++------- Tests/test_file_sun.py | 6 +++--- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py index af63144f2..07aa37eeb 100644 --- a/PIL/SunImagePlugin.py +++ b/PIL/SunImagePlugin.py @@ -48,17 +48,20 @@ class SunImageFile(ImageFile.ImageFile): self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) + file_type = i32(s[20:24]) + if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: - self.mode, rawmode = "RGB", "BGR" + if file_type == 3: + self.mode, rawmode = "RGB", "RGB" + else: + self.mode, rawmode = "RGB", "BGR" else: raise SyntaxError("unsupported mode") - compression = i32(s[20:24]) - if i32(s[24:28]) != 0: length = i32(s[28:32]) offset = offset + length @@ -68,11 +71,31 @@ class SunImageFile(ImageFile.ImageFile): stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3) - if compression == 1: - self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] - elif compression == 2: - self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] + # file type: Type is the version (or flavor) of the bitmap + # file. The following values are typically found in the Type + # field: + # 0000h Old + # 0001h Standard + # 0002h Byte-encoded + # 0003h RGB format + # 0004h TIFF format + # 0005h IFF format + # FFFFh Experimental + # Old and standard are the same, except for the length tag. + # byte-encoded is run-length-encoded + # RGB looks similar to standard, but RGB byte order + # TIFF and IFF mean that they were converted from T/IFF + # Experimental means that it's something else. + # (http://www.fileformat.info/format/sunraster/egff.htm) + + if file_type in (0, 1, 3, 4, 5): + self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] + elif file_type == 2: + self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] + else: + raise SyntaxError('Unsupported Sun Raster file type') + # # registry diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 55afced16..d0ff87b84 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper from PIL import Image, SunImagePlugin @@ -16,12 +16,12 @@ class TestFileSun(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) + self.assert_image_similar(im, hopper(), 5) # visually verified + invalid_file = "Tests/images/flower.jpg" self.assertRaises(SyntaxError, lambda: SunImagePlugin.SunImageFile(invalid_file)) - - def test_im1(self): im = Image.open('Tests/images/sunraster.im1') target = Image.open('Tests/images/sunraster.im1.png') From 8f6b7b258b11fc99ffaa7084ab200b175e14b627 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 22 Nov 2016 04:00:49 +0300 Subject: [PATCH 089/267] use minimal scale for jpeg downscaling --- PIL/JpegImagePlugin.py | 2 +- Tests/test_image_draft.py | 70 +++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 6d4e588c0..d44b345fd 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -349,7 +349,7 @@ class JpegImageFile(ImageFile.ImageFile): a = mode, "" if size: - scale = max(self.size[0] // size[0], self.size[1] // size[1]) + scale = min(self.size[0] // size[0], self.size[1] // size[1]) for s in [8, 4, 2, 1]: if scale >= s: break diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 0a8cc023c..409d70598 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -2,35 +2,63 @@ from helper import unittest, PillowTestCase, fromstring, tostring from PIL import Image -CODECS = dir(Image.core) -FILENAME = "Tests/images/hopper.jpg" -DATA = tostring(Image.open(FILENAME).resize((512, 512)), "JPEG") - - -def draft(mode, size): - im = fromstring(DATA) - im.draft(mode, size) - return im - class TestImageDraft(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in CODECS or "jpeg_decoder" not in CODECS: + codecs = dir(Image.core) + if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: self.skipTest("jpeg support not available") + def draft_roundtrip(self, in_mode, in_size, req_mode, req_size): + im = Image.new(in_mode, in_size) + data = tostring(im, 'JPEG') + im = fromstring(data) + im.draft(req_mode, req_size) + return im + def test_size(self): - self.assertEqual(draft("RGB", (512, 512)).size, (512, 512)) - self.assertEqual(draft("RGB", (256, 256)).size, (256, 256)) - self.assertEqual(draft("RGB", (128, 128)).size, (128, 128)) - self.assertEqual(draft("RGB", (64, 64)).size, (64, 64)) - self.assertEqual(draft("RGB", (32, 32)).size, (64, 64)) + for in_size, req_size, out_size in [ + ((435, 361), (2048, 2048), (435, 361)), # bigger + ((435, 361), (435, 361), (435, 361)), # same + + # large requested width + ((435, 361), (218, 128), (435, 361)), # almost 2x + ((435, 361), (217, 128), (218, 181)), # more than 2x + ((435, 361), (109, 64), (218, 181)), # almost 4x + ((435, 361), (108, 64), (109, 91)), # more than 4x + ((435, 361), (55, 32), (109, 91)), # almost 8x + ((435, 361), (54, 32), (55, 46)), # more than 8x + ((435, 361), (27, 16), (55, 46)), # more than 16x + + # and vice versa + ((435, 361), (128, 181), (435, 361)), # almost 2x + ((435, 361), (128, 180), (218, 181)), # more than 2x + ((435, 361), (64, 91), (218, 181)), # almost 4x + ((435, 361), (64, 90), (109, 91)), # more than 4x + ((435, 361), (32, 46), (109, 91)), # almost 8x + ((435, 361), (32, 45), (55, 46)), # more than 8x + ((435, 361), (16, 22), (55, 46)), # more than 16x + ]: + im = self.draft_roundtrip('L', in_size, None, req_size) + self.assertEqual(im.size, out_size) def test_mode(self): - self.assertEqual(draft("1", (512, 512)).mode, "RGB") - self.assertEqual(draft("L", (512, 512)).mode, "L") - self.assertEqual(draft("RGB", (512, 512)).mode, "RGB") - self.assertEqual(draft("YCbCr", (512, 512)).mode, "YCbCr") + for in_mode, req_mode, out_mode in [ + ("RGB", "1", "RGB"), + ("RGB", "L", "L"), + ("RGB", "RGB", "RGB"), + ("RGB", "YCbCr", "YCbCr"), + ("L", "1", "L"), + ("L", "L", "L"), + ("L", "RGB", "L"), + ("L", "YCbCr", "L"), + ("CMYK", "1", "CMYK"), + ("CMYK", "L", "CMYK"), + ("CMYK", "RGB", "CMYK"), + ("CMYK", "YCbCr", "CMYK"), + ]: + im = self.draft_roundtrip(in_mode, (64, 64), req_mode, None) + self.assertEqual(im.mode, out_mode) if __name__ == '__main__': From 55fca4857ce97a6e97abcf4eee2228b4046488b8 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 22 Nov 2016 04:28:04 +0300 Subject: [PATCH 090/267] protect .draft() from second call --- PIL/JpegImagePlugin.py | 4 ++++ Tests/test_image_draft.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index d44b345fd..2728fb9f8 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -341,6 +341,10 @@ class JpegImageFile(ImageFile.ImageFile): if len(self.tile) != 1: return + # Protect from second call + if self.decoderconfig: + return + d, e, o, a = self.tile[0] scale = 0 diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 409d70598..12f5e0e9f 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -20,6 +20,9 @@ class TestImageDraft(PillowTestCase): for in_size, req_size, out_size in [ ((435, 361), (2048, 2048), (435, 361)), # bigger ((435, 361), (435, 361), (435, 361)), # same + ((128, 128), (64, 64), (64, 64)), + ((128, 128), (32, 32), (32, 32)), + ((128, 128), (16, 16), (16, 16)), # large requested width ((435, 361), (218, 128), (435, 361)), # almost 2x @@ -40,6 +43,7 @@ class TestImageDraft(PillowTestCase): ((435, 361), (16, 22), (55, 46)), # more than 16x ]: im = self.draft_roundtrip('L', in_size, None, req_size) + im.load() self.assertEqual(im.size, out_size) def test_mode(self): @@ -58,8 +62,13 @@ class TestImageDraft(PillowTestCase): ("CMYK", "YCbCr", "CMYK"), ]: im = self.draft_roundtrip(in_mode, (64, 64), req_mode, None) + im.load() self.assertEqual(im.mode, out_mode) + def test_several_drafts(self): + im = self.draft_roundtrip('L', (128, 128), None, (64, 64)) + im.draft(None, (64, 64)) + im.load() if __name__ == '__main__': unittest.main() From 372b1abe6924c1274d8b1e6f1b38ba8aec0dde41 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 02:46:54 -0800 Subject: [PATCH 091/267] expand tile element names, don't attempt to mmap if args has < 3 elements --- PIL/ImageFile.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index 55cb701a1..94f705b3c 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -150,15 +150,16 @@ class ImageFile(Image.Image): if use_mmap: # try memory mapping - d, e, o, a = self.tile[0] - if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES: + decoder_name, extents, offset, args = self.tile[0] + if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \ + and args[0] in Image._MAPMODES: try: if hasattr(Image.core, "map"): # use built-in mapper WIN32 only self.map = Image.core.map(self.filename) - self.map.seek(o) + self.map.seek(offset) self.im = self.map.readimage( - self.mode, self.size, a[1], a[2] + self.mode, self.size, args[1], args[2] ) else: # use mmap, if possible @@ -167,7 +168,7 @@ class ImageFile(Image.Image): size = os.path.getsize(self.filename) self.map = mmap.mmap(fp.fileno(), size, access=mmap.ACCESS_READ) self.im = Image.core.map_buffer( - self.map, self.size, d, e, o, a + self.map, self.size, decoder_name, extents, offset, args ) readonly = 1 # After trashing self.im, we might need to reload the palette data. From 816c74ac815eba5a949f2cefaed045d7842c4c19 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 02:50:39 -0800 Subject: [PATCH 092/267] Fix SunImagePlugin. SunImagePlugin now loads all the images here: https://samples.libav.org/image-samples/sunrast/ without LOAD_TRUNCATED_IMAGES set, verified visually. Prior to this commit: Could not open 32bpp.ras Could not open 4bpp.ras Could not open gray.ras Could not open lena-1bit-rle.sun Could not open lena-24bit-rle.sun Could not open lena-8bit-raw.sun Could not open lena-8bit-rle.sun Could not open MARBLES.SUN --- PIL/SunImagePlugin.py | 50 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py index 07aa37eeb..059bcb655 100644 --- a/PIL/SunImagePlugin.py +++ b/PIL/SunImagePlugin.py @@ -38,6 +38,21 @@ class SunImageFile(ImageFile.ImageFile): def _open(self): + # The Sun Raster file header is 32 bytes in length and has the following format: + + # typedef struct _SunRaster + # { + # DWORD MagicNumber; /* Magic (identification) number */ + # DWORD Width; /* Width of image in pixels */ + # DWORD Height; /* Height of image in pixels */ + # DWORD Depth; /* Number of bits per pixel */ + # DWORD Length; /* Size of image data in bytes */ + # DWORD Type; /* Type of raster file */ + # DWORD ColorMapType; /* Type of color map */ + # DWORD ColorMapLength; /* Size of the color map in bytes */ + # } SUNRASTER; + + # HEAD s = self.fp.read(32) if i32(s) != 0x59a66a95: @@ -48,10 +63,15 @@ class SunImageFile(ImageFile.ImageFile): self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) + data_length = i32(s[16:20]) # unreliable, ignore. file_type = i32(s[20:24]) + palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary + palette_length = i32(s[28:32]) if depth == 1: self.mode, rawmode = "1", "1;I" + elif depth == 4: + self.mode, rawmode = "L", "L;4" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: @@ -59,17 +79,31 @@ class SunImageFile(ImageFile.ImageFile): self.mode, rawmode = "RGB", "RGB" else: self.mode, rawmode = "RGB", "BGR" + elif depth == 32: + if file_type == 3: + self.mode, rawmode = 'RGB', 'RGBX' + else: + self.mode, rawmode = 'RGB', 'BGRX' else: - raise SyntaxError("unsupported mode") + raise SyntaxError("Unsupported Mode/Bit Depth") - if i32(s[24:28]) != 0: - length = i32(s[28:32]) - offset = offset + length - self.palette = ImagePalette.raw("RGB;L", self.fp.read(length)) + + + if palette_length: + if palette_length > 1024: + raise SyntaxError("Unsupported Color Palette Length") + + if palette_type != 1: + raise SyntaxError("Unsupported Palette Type") + + offset = offset + palette_length + self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": - self.mode = rawmode = "P" - - stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3) + self.mode = "P" + rawmode = rawmode.replace('L', 'P') + + # 16 bit boundaries on stride + stride = ((self.size[0] * depth + 15) // 16) * 2 # file type: Type is the version (or flavor) of the bitmap # file. The following values are typically found in the Type From dbe9f85c7d8f760756ebf8129195470700e63e3d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 7 Nov 2016 04:33:46 -0800 Subject: [PATCH 093/267] Drop support for Python 2.6 * Drop unittest2 requirement * Use set literals * Use dict/set comprehension * Use str.format() automatic numbering --- .travis.yml | 7 ++----- PIL/BmpImagePlugin.py | 2 +- PIL/IcoImagePlugin.py | 2 +- PIL/JpegImagePlugin.py | 4 ++-- PIL/TiffImagePlugin.py | 16 ++++++---------- PIL/TiffTags.py | 14 +++++++------- Tests/README.rst | 4 ---- Tests/helper.py | 6 +----- Tests/test_file_libtiff.py | 6 +++--- Tests/test_file_tiff_metadata.py | 2 +- Tests/test_image_getim.py | 9 +-------- Tests/test_image_resample.py | 6 +++--- Tests/test_image_resize.py | 6 +++--- Tests/test_image_toqimage.py | 2 +- Tests/test_image_toqpixmap.py | 2 +- Tests/test_imagewin_pointers.py | 6 +----- _imaging.c | 4 ---- docs/installation.rst | 4 +++- py3.h | 2 +- setup.py | 3 +-- tox.ini | 2 +- winbuild/build.py | 3 --- winbuild/get_pythons.py | 2 +- 23 files changed, 41 insertions(+), 73 deletions(-) diff --git a/.travis.yml b/.travis.yml index 15bec68cc..e5d5a6b4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ python: - "pypy3" - 3.5 - 2.7 - - 2.6 - "2.7_with_system_site_packages" # For PyQt4 - 3.2 - 3.3 @@ -24,10 +23,8 @@ install: - "travis_retry pip install cffi" - "travis_retry pip install nose" - "travis_retry pip install check-manifest" - # Pyroma tests sometimes hang on PyPy and Python 2.6; skip for those - - if [ $TRAVIS_PYTHON_VERSION != "pypy" && $TRAVIS_PYTHON_VERSION != "2.6" ]; then travis_retry pip install pyroma; fi - - - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi + # Pyroma tests sometimes hang on PyPy; skip + - if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi # Coverage 4.0 doesn't support Python 3.2 - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index a922d8ff8..b04981af9 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -73,7 +73,7 @@ class BmpImageFile(ImageFile.ImageFile): read, seek = self.fp.read, self.fp.seek if header: seek(header) - file_info = dict() + file_info = {} file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) file_info['direction'] = -1 # --------------------- If requested, read header at a specific position diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index a01aed376..b278a85bf 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -139,7 +139,7 @@ class IcoFile(object): """ Get a list of all available icon sizes and color depths. """ - return set((h['width'], h['height']) for h in self.entry) + return {(h['width'], h['height']) for h in self.entry} def getimage(self, size, bpp=False): """ diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 6d4e588c0..92316a65a 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -408,7 +408,7 @@ def _fixup_dict(src_dict): except: pass return value - return dict((k, _fixup(v)) for k, v in src_dict.items()) + return {k: _fixup(v) for k, v in src_dict.items()} def _getexif(self): @@ -488,7 +488,7 @@ def _getmp(self): rawmpentries = mp[0xB002] for entrynum in range(0, quant): unpackedentry = unpack_from( - '{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16) + '{}LLLHH'.format(endianness), rawmpentries, entrynum * 16) labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') mpentry = dict(zip(labels, unpackedentry)) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index d442c3766..f1860e4aa 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -132,7 +132,7 @@ COMPRESSION_INFO = { 34677: "tiff_sgilog24", } -COMPRESSION_INFO_REV = dict((v, k) for (k, v) in COMPRESSION_INFO.items()) +COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()} OPEN_INFO = { # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, @@ -294,11 +294,7 @@ class IFDRational(Rational): return elif denominator == 1: - if sys.hexversion < 0x2070000 and isinstance(value, float): - # python 2.6 is different. - self._val = Fraction.from_float(value) - else: - self._val = Fraction(value) + self._val = Fraction(value) else: self._val = Fraction(value, denominator) @@ -592,7 +588,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): TYPES[idx] = name size = struct.calcsize("=" + fmt) _load_dispatch[idx] = size, lambda self, data, legacy_api=True: ( - self._unpack("{0}{1}".format(len(data) // size, fmt), data)) + self._unpack("{}{}".format(len(data) // size, fmt), data)) _write_dispatch[idx] = lambda self, *values: ( b"".join(self._pack(fmt, value) for value in values)) @@ -624,7 +620,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): @_register_loader(5, 8) def load_rational(self, data, legacy_api=True): - vals = self._unpack("{0}L".format(len(data) // 4), data) + vals = self._unpack("{}L".format(len(data) // 4), data) combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b) return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @@ -644,7 +640,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): @_register_loader(10, 8) def load_signed_rational(self, data, legacy_api=True): - vals = self._unpack("{0}l".format(len(data) // 4), data) + vals = self._unpack("{}l".format(len(data) // 4), data) combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b) return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @@ -1518,7 +1514,7 @@ class AppendingTiffWriter: # JPEGQTables = 519 # JPEGDCTables = 520 # JPEGACTables = 521 - Tags = set((273, 288, 324, 519, 520, 521)) + Tags = {273, 288, 324, 519, 520, 521} def __init__(self, fn, new=False): if hasattr(fn, 'read'): diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index edb28b9ec..6644b237e 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -418,13 +418,13 @@ TYPES = {} # some of these are not in our TAGS_V2 dict and were included from tiff.h -LIBTIFF_CORE = set([255, 256, 257, 258, 259, 262, 263, 266, 274, 277, - 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, - 296, 297, 321, 320, 338, 32995, 322, 323, 32998, - 32996, 339, 32997, 330, 531, 530, 301, 532, 333, - # as above - 269 # this has been in our tests forever, and works - ]) +LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277, + 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, + 296, 297, 321, 320, 338, 32995, 322, 323, 32998, + 32996, 339, 32997, 330, 531, 530, 301, 532, 333, + # as above + 269 # this has been in our tests forever, and works + } LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes diff --git a/Tests/README.rst b/Tests/README.rst index a7212cb3d..0985e0f26 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -10,10 +10,6 @@ Install:: pip install coverage nose -If you're using Python 2.6, there's one additional dependency:: - - pip install unittest2 - Execution --------- diff --git a/Tests/helper.py b/Tests/helper.py index 5cfd7678f..7b8bdcce4 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,11 +5,7 @@ from __future__ import print_function import sys import tempfile import os - -if sys.version_info[:2] <= (2, 6): - import unittest2 as unittest -else: - import unittest +import unittest class PillowTestCase(unittest.TestCase): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6e40d4b37..2d1b33154 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -180,9 +180,9 @@ class TestFileLibTiff(LibTiffTestCase): # Get the list of the ones that we should be able to write - core_items = dict((tag, info) for tag, info in [(s, TiffTags.lookup(s)) for s - in TiffTags.LIBTIFF_CORE] - if info.type is not None) + core_items = {tag: info for tag, info in ((s, TiffTags.lookup(s)) for s + in TiffTags.LIBTIFF_CORE) + if info.type is not None} # Exclude ones that have special meaning # that we're already testing them diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 7af03a675..c75487f9b 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -8,7 +8,7 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import _limit_rational, IFDRational -tag_ids = dict((info.name, info.value) for info in TiffTags.TAGS_V2.values()) +tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()} class TestFileTiffMetadata(PillowTestCase): diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index c67118b58..bc562de5a 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,5 +1,4 @@ from helper import unittest, PillowTestCase, hopper, py3 -import sys class TestImageGetIm(PillowTestCase): @@ -11,13 +10,7 @@ class TestImageGetIm(PillowTestCase): if py3: self.assertIn("PyCapsule", type_repr) - if sys.hexversion < 0x2070000: - # py2.6 x64, windows - target_types = (int, long) - else: - target_types = (int) - - self.assertIsInstance(im.im.id, target_types) + self.assertIsInstance(im.im.id, int) if __name__ == '__main__': diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 6fcdd84b9..1336c1009 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -232,10 +232,10 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): def run_levels_case(self, i): px = i.load() for y in range(i.size[1]): - used_colors = set(px[x, y][0] for x in range(i.size[0])) + used_colors = {px[x, y][0] for x in range(i.size[0])} self.assertEqual(256, len(used_colors), 'All colors should present in resized image. ' - 'Only {0} on {1} line.'.format(len(used_colors), y)) + 'Only {} on {} line.'.format(len(used_colors), y)) @unittest.skip("current implementation isn't precise enough") def test_levels_rgba(self): @@ -270,7 +270,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): for y in range(i.size[1]): for x in range(i.size[0]): if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: - message = 'pixel at ({0}, {1}) is differ:\n{2}\n{3}'\ + message = 'pixel at ({}, {}) is differ:\n{}\n{}'\ .format(x, y, px[x, y], clean_pixel) self.assertEqual(px[x, y][:3], clean_pixel, message) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 73f8091ed..e0af7bb6b 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -69,10 +69,10 @@ class TestImagingCoreResize(PillowTestCase): for f in [Image.LINEAR, Image.BOX, Image.BILINEAR, Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: # samples resized with current filter - references = dict( - (name, self.resize(ch, (4, 4), f)) + references = { + name: self.resize(ch, (4, 4), f) for name, ch in samples.items() - ) + } for mode, channels_set in [ ('RGB', ('blank', 'filled', 'dirty')), diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index ccab08799..4a318c5c4 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -29,7 +29,7 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): self.assertFalse(data.isNull()) # Test saving the file - tempfile = self.tempfile('temp_{0}.png'.format(mode)) + tempfile = self.tempfile('temp_{}.png'.format(mode)) data.save(tempfile) diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_image_toqpixmap.py index 0bed9c747..c6555d7ff 100644 --- a/Tests/test_image_toqpixmap.py +++ b/Tests/test_image_toqpixmap.py @@ -19,7 +19,7 @@ class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): self.assertFalse(data.isNull()) # Test saving the file - tempfile = self.tempfile('temp_{0}.png'.format(mode)) + tempfile = self.tempfile('temp_{}.png'.format(mode)) data.save(tempfile) diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index f37c67eac..7178d8cb8 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -75,11 +75,7 @@ if sys.platform.startswith('win32'): memcpy(bp, ctypes.byref(bf), ctypes.sizeof(bf)) memcpy(bp + ctypes.sizeof(bf), ctypes.byref(bi), bi.biSize) memcpy(bp + bf.bfOffBits, pixels, bi.biSizeImage) - try: - return bytearray(buf) - except ValueError: - # py2.6 - return buffer(buf)[:] + return bytearray(buf) class TestImageWinPointers(PillowTestCase): def test_pointer(self): diff --git a/_imaging.c b/_imaging.c index 3c109b88c..e2320d862 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3073,11 +3073,7 @@ _getattr_id(ImagingObject* self, void* closure) static PyObject* _getattr_ptr(ImagingObject* self, void* closure) { -#if PY_VERSION_HEX >= 0x02070000 return PyCapsule_New(self->image, IMAGING_MAGIC, NULL); -#else - return PyCObject_FromVoidPtrAndDesc(self->image, IMAGING_MAGIC, NULL); -#endif } static PyObject* diff --git a/docs/installation.rst b/docs/installation.rst index 2ada37c03..4cccf303e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,7 +15,9 @@ Notes .. note:: Pillow < 2.0.0 supports Python versions 2.4, 2.5, 2.6, 2.7. -.. note:: Pillow >= 2.0.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 +.. note:: Pillow >= 2.0.0 < 3.5.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 + +.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.2, 3.3, 3.4, 3.5 Basic Installation ------------------ diff --git a/py3.h b/py3.h index a2a1e264f..310583845 100644 --- a/py3.h +++ b/py3.h @@ -1,5 +1,5 @@ /* - Python3 definition file to consistently map the code to Python 2.6 or + Python3 definition file to consistently map the code to Python 2 or Python 3. PyInt and PyLong were merged into PyLong in Python 3, so all PyInt functions diff --git a/setup.py b/setup.py index 553494afb..dbb9e046c 100644 --- a/setup.py +++ b/setup.py @@ -147,7 +147,7 @@ class pil_build_ext(build_ext): features = ['zlib', 'jpeg', 'tiff', 'freetype', 'lcms', 'webp', 'webpmux', 'jpeg2000', 'imagequant'] - required = set(['jpeg', 'zlib']) + required = {'jpeg', 'zlib'} def __init__(self): for f in self.features: @@ -770,7 +770,6 @@ try: "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Viewers", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", diff --git a/tox.ini b/tox.ini index 507af757a..809183b9a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, py35 +envlist = py27, py32, py33, py34, py35 [testenv] commands = diff --git a/winbuild/build.py b/winbuild/build.py index aacf13174..487977adb 100644 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -18,9 +18,6 @@ def setup_vms(): % (py, arch, VIRT_BASE, py, arch)) ret.append("%s%s%s\Scripts\pip.exe install nose" % (VIRT_BASE, py, arch)) - if py == '26': - ret.append("%s%s%s\Scripts\pip.exe install unittest2" % - (VIRT_BASE, py, arch)) return "\n".join(ret) diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index 57c866daa..5abd836fb 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -2,7 +2,7 @@ from fetch import fetch import os if __name__ == '__main__': - for version in ['2.6.6', '2.7.10', '3.2.5', '3.3.5', '3.4.3']: + for version in ['2.7.10', '3.2.5', '3.3.5', '3.4.3']: for platform in ['', '.amd64']: for extension in ['', '.asc']: fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' From 85cf6d31401cc1763c0f5405663054a09071385d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 7 Nov 2016 04:35:23 -0800 Subject: [PATCH 094/267] Drop support for Python 3.2 --- .travis.yml | 5 +---- docs/installation.rst | 2 +- setup.py | 16 +--------------- tox.ini | 2 +- winbuild/build.rst | 2 +- winbuild/get_pythons.py | 2 +- 6 files changed, 6 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index e5d5a6b4d..952714b5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ python: - 3.5 - 2.7 - "2.7_with_system_site_packages" # For PyQt4 - - 3.2 - 3.3 - 3.4 - nightly @@ -26,9 +25,7 @@ install: # Pyroma tests sometimes hang on PyPy; skip - if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi - # Coverage 4.0 doesn't support Python 3.2 - - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi + - "travis_retry pip install coverage" # docs only on python 2.7 - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then travis_retry pip install -r requirements.txt ; fi diff --git a/docs/installation.rst b/docs/installation.rst index 4cccf303e..2724886bb 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -17,7 +17,7 @@ Notes .. note:: Pillow >= 2.0.0 < 3.5.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 -.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.2, 3.3, 3.4, 3.5 +.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.3, 3.4, 3.5 Basic Installation ------------------ diff --git a/setup.py b/setup.py index dbb9e046c..808ec728a 100644 --- a/setup.py +++ b/setup.py @@ -74,20 +74,7 @@ def _find_include_file(self, include): def _find_library_file(self, library): - # Fix for 3.2.x <3.2.4, 3.3.0, shared lib extension is the python shared - # lib extension, not the system shared lib extension: e.g. .cpython-33.so - # vs .so. See Python bug http://bugs.python.org/16754 - if 'cpython' in self.compiler.shared_lib_extension: - _dbg('stripping cpython from shared library extension %s', - self.compiler.shared_lib_extension) - existing = self.compiler.shared_lib_extension - self.compiler.shared_lib_extension = "." + existing.split('.')[-1] - ret = self.compiler.find_library_file(self.compiler.library_dirs, - library) - self.compiler.shared_lib_extension = existing - else: - ret = self.compiler.find_library_file(self.compiler.library_dirs, - library) + ret = self.compiler.find_library_file(self.compiler.library_dirs, library) if ret: _dbg('Found library %s at %s', (library, ret)) else: @@ -772,7 +759,6 @@ try: "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", diff --git a/tox.ini b/tox.ini index 809183b9a..4d63febd0 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py32, py33, py34, py35 +envlist = py27, py33, py34, py35 [testenv] commands = diff --git a/winbuild/build.rst b/winbuild/build.rst index e4c2293a9..86c4cf4c7 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -31,7 +31,7 @@ The build routines expect Python to be installed at C:\PythonXX for Download Python 3.4, install it, and add it to the path. This is the Python that we will use to bootstrap the build process. (The download -routines are using 3.2+ features, and installing 3.4 gives us pip and +routines are using 3 features, and installing 3.4 gives us pip and virtualenv as well, reducing the number of packages that we need to install.) diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index 5abd836fb..481283df3 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -2,7 +2,7 @@ from fetch import fetch import os if __name__ == '__main__': - for version in ['2.7.10', '3.2.5', '3.3.5', '3.4.3']: + for version in ['2.7.10', '3.3.5', '3.4.3']: for platform in ['', '.amd64']: for extension in ['', '.asc']: fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' From d1b25850f0387868bfe6b7ec41ba2ea9e87416fa Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 12:39:03 +0000 Subject: [PATCH 095/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f7d208944..aadbb014c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,24 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186 + [timgraham] + +- Removed support for Python 2.6 and Python 3.2 #2192 + [jdufresne] + +- Setup: Raise custom exceptions when required/requested dependencies are not found #2213 + [wiredfool] + +- Use a context manager in FontFile.save() to ensure file is always closed #2226 + [jdufresne] + +- Fixed bug in saving to fp-objects in Python >= 3.4 #2227 + [radarhere] + +- Use a context manager in ImageFont._load_pilfont() to ensure file is always closed #2232 + [jdufresne] + - Use generator expressions instead of list comprehension #2225 [jdufresne] From 846bc32941d2585e8cdae28a53e6c3a5093b717f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 12:48:36 +0000 Subject: [PATCH 096/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aadbb014c..a85f0e773 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Updated dependency scripts to use Webp 0.5.1, OpenJpeg 2.1.2, and TclTk 8.6.6 #2235, #2236, #2237 + [radarhere] + - Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186 [timgraham] From 82caa13a02f277f42883d887c1561d18c4f87726 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 06:47:33 -0800 Subject: [PATCH 097/267] Additional test images for sun raster files --- .travis.yml | 4 ++++ Tests/test_file_sun.py | 20 ++++++++++++++++++++ depends/install_extra_test_images.sh | 11 +++++++++++ 3 files changed, 35 insertions(+) create mode 100755 depends/install_extra_test_images.sh diff --git a/.travis.yml b/.travis.yml index 15bec68cc..df5bc8ac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,10 @@ install: # libimagequant - pushd depends && ./install_imagequant.sh && popd + + # extra test images + - pushd depends && ./install_extra_test_images.sh && popd + before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index d0ff87b84..deac32394 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -2,6 +2,9 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image, SunImagePlugin +import os + +EXTRA_DIR = 'Tests/images/sunraster' class TestFileSun(PillowTestCase): @@ -26,6 +29,23 @@ class TestFileSun(PillowTestCase): im = Image.open('Tests/images/sunraster.im1') target = Image.open('Tests/images/sunraster.im1.png') self.assert_image_equal(im, target) + + + @unittest.skipIf(not os.path.exists(EXTRA_DIR), + "Extra image files not installed") + def test_others(self): + files = (os.path.join(EXTRA_DIR, f) for f in + os.listdir(EXTRA_DIR) if os.path.splitext(f)[1] + in ('.sun', '.SUN', '.ras')) + for path in files: + print (path) + with Image.open(path) as im: + im.load() + self.assertIsInstance(im, SunImagePlugin.SunImageFile) + target_path = "%s.png" % os.path.splitext(path)[0] + #im.save(target_file) + with Image.open(target_path) as target: + self.assert_image_equal(im, target) if __name__ == '__main__': unittest.main() diff --git a/depends/install_extra_test_images.sh b/depends/install_extra_test_images.sh new file mode 100755 index 000000000..00ed219d6 --- /dev/null +++ b/depends/install_extra_test_images.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# install extra test images + +if [ ! -f test_images.tar.gz ]; then + wget -O 'test_images.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/test_images.tar.gz?raw=true' +fi + +rm -r test_images +tar -xvzf test_images.tar.gz + +cp -r test_images/* ../Tests/images From 2e178d7dbbead6fba2d9f8b6a31b66b0c9328877 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 15:23:41 +0000 Subject: [PATCH 098/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a85f0e773..3aa00eb02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Use minimal scale for jpeg drafts #2240 + [homm] + - Updated dependency scripts to use Webp 0.5.1, OpenJpeg 2.1.2, and TclTk 8.6.6 #2235, #2236, #2237 [radarhere] From e0cbef34fb069ef2d9044dce8c263a9a3d3742ef Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Nov 2016 07:19:39 -0800 Subject: [PATCH 099/267] Installed extra test files on Appveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 2b58eeda3..9ff661224 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,7 @@ install: - git clone https://github.com/python-pillow/pillow-depends.git c:\pillow-depends - xcopy c:\pillow-depends\*.zip c:\pillow\winbuild\ - xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\ +- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images - cd c:\pillow\winbuild\ - c:\python34\python.exe c:\pillow\winbuild\build_dep.py - c:\pillow\winbuild\build_deps.cmd From 52b1f46b12433bcc4a4c02396b0875f800866787 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 5 Nov 2016 11:11:30 -0700 Subject: [PATCH 100/267] Deprecate vendored version of olefile Python package in favor of upstream Pillow now requires the olefile Python package through setup.py. This removes Pillow's maintenance of this library by instead relying on and reusing the upstream version. No longer need to regularly update the vendored package and docs. olefile bug fixes and features can go directly upstream. During travis tests, now installs Pillow package before tests; this will also install all dependencies (currently, only olefile). --- .travis.yml | 2 + PIL/FpxImagePlugin.py | 11 +- PIL/MicImagePlugin.py | 7 +- PIL/OleFileIO-README.md | 180 --- PIL/OleFileIO.py | 2307 +--------------------------------- Tests/test_olefileio.py | 147 --- docs/reference/OleFileIO.rst | 364 ------ docs/reference/index.rst | 1 - setup.py | 1 + 9 files changed, 22 insertions(+), 2998 deletions(-) delete mode 100644 PIL/OleFileIO-README.md delete mode 100644 Tests/test_olefileio.py delete mode 100644 docs/reference/OleFileIO.rst diff --git a/.travis.yml b/.travis.yml index 952714b5f..7225fc825 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,8 @@ install: # libimagequant - pushd depends && ./install_imagequant.sh && popd + - travis_retry pip install -e . + before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install - "export DISPLAY=:99.0" diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index a4a9098a7..861f4bf01 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -16,11 +16,14 @@ # -from PIL import Image, ImageFile -from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO +from PIL import Image, ImageFile, _binary + +import olefile __version__ = "0.1" +i32 = _binary.i32le +i8 = _binary.i8 # we map from colour field tuples to (mode, rawmode) descriptors MODES = { @@ -42,7 +45,7 @@ MODES = { # -------------------------------------------------------------------- def _accept(prefix): - return prefix[:8] == MAGIC + return prefix[:8] == olefile.MAGIC ## @@ -59,7 +62,7 @@ class FpxImageFile(ImageFile.ImageFile): # to be a FlashPix file try: - self.ole = OleFileIO(self.fp) + self.ole = olefile.OleFileIO(self.fp) except IOError: raise SyntaxError("not an FPX file; invalid OLE file") diff --git a/PIL/MicImagePlugin.py b/PIL/MicImagePlugin.py index 3c912442b..125e297ac 100644 --- a/PIL/MicImagePlugin.py +++ b/PIL/MicImagePlugin.py @@ -18,7 +18,8 @@ from PIL import Image, TiffImagePlugin -from PIL.OleFileIO import MAGIC, OleFileIO + +import olefile __version__ = "0.1" @@ -28,7 +29,7 @@ __version__ = "0.1" def _accept(prefix): - return prefix[:8] == MAGIC + return prefix[:8] == olefile.MAGIC ## @@ -45,7 +46,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): # to be a Microsoft Image Composer file try: - self.ole = OleFileIO(self.fp) + self.ole = olefile.OleFileIO(self.fp) except IOError: raise SyntaxError("not an MIC file; invalid OLE file") diff --git a/PIL/OleFileIO-README.md b/PIL/OleFileIO-README.md deleted file mode 100644 index 35e028265..000000000 --- a/PIL/OleFileIO-README.md +++ /dev/null @@ -1,180 +0,0 @@ -olefile (formerly OleFileIO_PL) -=============================== - -[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write -[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format) -(also called Structured Storage, Compound File Binary Format or Compound Document File Format), -such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer -and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files, -etc. - - -**Quick links:** [Home page](http://www.decalage.info/olefile) - -[Download/Install](https://bitbucket.org/decalage/olefileio_pl/wiki/Install) - -[Documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) - -[Report Issues/Suggestions/Questions](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open) - -[Contact the author](http://decalage.info/contact) - -[Repository](https://bitbucket.org/decalage/olefileio_pl) - -[Updates on Twitter](https://twitter.com/decalage2) - - -News ----- - -Follow all updates and news on Twitter: - -- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8 - instead of Latin-1), fixed bug in listdir with empty storages. -- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for - python 3, added support for Jython (Niko Ehrenfeuchter) -- 2014-10-01 v0.40: renamed OleFileIO_PL to olefile, added initial write support for streams >4K, updated doc and - license, improved the setup script. -- 2014-07-27 v0.31: fixed support for large files with 4K sectors, thanks to Niko Ehrenfeuchter, Martijn Berger and - Dave Jones. Added test scripts from Pillow (by hugovk). Fixed setup for Python 3 (Martin Panter) -- 2014-02-04 v0.30: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work. -- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed - parsing of direntry timestamps -- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed - [issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole) -- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved - getproperties to convert timestamps to Python datetime -- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based - on OleFileIO_PL -- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object) -- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method) -- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking -- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs. -- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str. -- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug) -- see changelog in source code for more info. - -Download/Install ----------------- - -If you have pip or setuptools installed (pip is included in Python 2.7.9+), you may simply run **pip install olefile** -or **easy_install olefile** for the first installation. - -To update olefile, run **pip install -U olefile**. - -Otherwise, see https://bitbucket.org/decalage/olefileio_pl/wiki/Install - -Features --------- - -- Parse, read and write any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, - PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, - Zeiss AxioVision ZVI files, Olympus FluoView OIB files, etc -- List all the streams and storages contained in an OLE file -- Open streams as files -- Parse and read property streams, containing metadata of the file -- Portable, pure Python module, no dependency - -olefile can be used as an independent package or with PIL/Pillow. - -olefile is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data (especially -for security purposes such as malware analysis and forensics), then please also check my -[python-oletools](http://www.decalage.info/python/oletools), which are built upon olefile and provide a higher-level interface. - - -History -------- - -olefile is based on the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent -Python Imaging Library, created and maintained by Fredrik Lundh. The olefile API is still compatible with PIL, but -since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust -design. From 2005 to 2014 the project was called OleFileIO_PL, and in 2014 I changed its name to olefile to celebrate -its 9 years and its new write features. - -As far as I know, olefile is the most complete and robust Python implementation to read MS OLE2 files, portable on -several operating systems. (please tell me if you know other similar Python modules) - -Since 2014 olefile/OleFileIO_PL has been integrated into [Pillow](http://python-pillow.org), the friendly fork -of PIL. olefile will continue to be improved as a separate project, and new versions will be merged into Pillow -regularly. - - -Main improvements over the original version of OleFileIO in PIL: ----------------------------------------------------------------- - -- Compatible with Python 3.x and 2.6+ -- Many bug fixes -- Support for files larger than 6.8MB -- Support for 64 bits platforms and big-endian CPUs -- Robust: many checks to detect malformed files -- Runtime option to choose if malformed files should be parsed or raise exceptions -- Improved API -- Metadata extraction, stream/storage timestamps (e.g. for document forensics) -- Can open file-like objects -- Added setup.py and install.bat to ease installation -- More convenient slash-based syntax for stream paths -- Write features - -Documentation -------------- - -Please see the [online documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) for more information, -especially the [OLE overview](https://bitbucket.org/decalage/olefileio_pl/wiki/OLE_Overview) and the -[API page](https://bitbucket.org/decalage/olefileio_pl/wiki/API) which describe how to use olefile in Python applications. -A copy of the same documentation is also provided in the doc subfolder of the olefile package. - - -## Real-life examples ## - -A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/). - -See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features olefile. - - -License -------- - -olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec -([http://www.decalage.info](http://www.decalage.info)) - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ----------- - -olefile is based on source code from the OleFileIO module of the Python Imaging Library (PIL) published by Fredrik -Lundh under the following license: - -The Python Imaging Library (PIL) is - - Copyright © 1997-2011 by Secret Labs AB - Copyright © 1995-2011 by Fredrik Lundh - -By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, -understood, and will comply with the following terms and conditions: - -Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and -without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that -copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or -the author not be used in advertising or publicity pertaining to distribution of the software without specific, written -prior permission. - -SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR -CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS -SOFTWARE. diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index 1998e3c10..c4e23b86b 100755 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -1,2305 +1,14 @@ #!/usr/bin/env python -# olefile (formerly OleFileIO_PL) version 0.42 2015-01-25 -# -# Module to read/write Microsoft OLE2 files (also called Structured Storage or -# Microsoft Compound Document File Format), such as Microsoft Office 97-2003 -# documents, Image Composer and FlashPix files, Outlook messages, ... -# This version is compatible with Python 2.6+ and 3.x -# -# Project website: http://www.decalage.info/olefile -# -# olefile is copyright (c) 2005-2015 Philippe Lagadec (http://www.decalage.info) -# -# olefile is based on the OleFileIO module from the PIL library v1.1.6 -# See: http://www.pythonware.com/products/pil/index.htm -# -# The Python Imaging Library (PIL) is -# Copyright (c) 1997-2005 by Secret Labs AB -# Copyright (c) 1995-2005 by Fredrik Lundh -# -# See source code and LICENSE.txt for information on usage and redistribution. +import warnings +warnings.warn( + 'PIL.OleFileIO is deprecated. Use the olefile Python package ' + 'instead. This module will be removed in a future version.', + DeprecationWarning +) -# Since OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported -# This import enables print() as a function rather than a keyword -# (main requirement to be compatible with Python 3.x) -# The comment on the line below should be printed on Python 2.5 or older: -from __future__ import print_function # This version of olefile requires Python 2.6+ or 3.x. - - -__author__ = "Philippe Lagadec" -__date__ = "2015-01-25" -__version__ = '0.42b' - -#--- LICENSE ------------------------------------------------------------------ - -# olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec -# (http://www.decalage.info) -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# ---------- -# PIL License: -# -# olefile is based on source code from the OleFileIO module of the Python -# Imaging Library (PIL) published by Fredrik Lundh under the following license: - -# The Python Imaging Library (PIL) is -# Copyright (c) 1997-2005 by Secret Labs AB -# Copyright (c) 1995-2005 by Fredrik Lundh -# -# By obtaining, using, and/or copying this software and/or its associated -# documentation, you agree that you have read, understood, and will comply with -# the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and its -# associated documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appears in all copies, and that both -# that copyright notice and this permission notice appear in supporting -# documentation, and that the name of Secret Labs AB or the author(s) not be used -# in advertising or publicity pertaining to distribution of the software -# without specific, written prior permission. -# -# SECRET LABS AB AND THE AUTHORS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS -# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. -# IN NO EVENT SHALL SECRET LABS AB OR THE AUTHORS BE LIABLE FOR ANY SPECIAL, -# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -#----------------------------------------------------------------------------- -# CHANGELOG: (only olefile/OleFileIO_PL changes compared to PIL 1.1.6) -# 2005-05-11 v0.10 PL: - a few fixes for Python 2.4 compatibility -# (all changes flagged with [PL]) -# 2006-02-22 v0.11 PL: - a few fixes for some Office 2003 documents which raise -# exceptions in _OleStream.__init__() -# 2006-06-09 v0.12 PL: - fixes for files above 6.8MB (DIFAT in loadfat) -# - added some constants -# - added header values checks -# - added some docstrings -# - getsect: bugfix in case sectors >512 bytes -# - getsect: added conformity checks -# - DEBUG_MODE constant to activate debug display -# 2007-09-04 v0.13 PL: - improved/translated (lots of) comments -# - updated license -# - converted tabs to 4 spaces -# 2007-11-19 v0.14 PL: - added OleFileIO._raise_defect() to adapt sensitivity -# - improved _unicode() to use Python 2.x unicode support -# - fixed bug in _OleDirectoryEntry -# 2007-11-25 v0.15 PL: - added safety checks to detect FAT loops -# - fixed _OleStream which didn't check stream size -# - added/improved many docstrings and comments -# - moved helper functions _unicode and _clsid out of -# OleFileIO class -# - improved OleFileIO._find() to add Unix path syntax -# - OleFileIO._find() is now case-insensitive -# - added get_type() and get_rootentry_name() -# - rewritten loaddirectory and _OleDirectoryEntry -# 2007-11-27 v0.16 PL: - added _OleDirectoryEntry.kids_dict -# - added detection of duplicate filenames in storages -# - added detection of duplicate references to streams -# - added get_size() and exists() to _OleDirectoryEntry -# - added isOleFile to check header before parsing -# - added __all__ list to control public keywords in pydoc -# 2007-12-04 v0.17 PL: - added _load_direntry to fix a bug in loaddirectory -# - improved _unicode(), added workarounds for Python <2.3 -# - added set_debug_mode and -d option to set debug mode -# - fixed bugs in OleFileIO.open and _OleDirectoryEntry -# - added safety check in main for large or binary -# properties -# - allow size>0 for storages for some implementations -# 2007-12-05 v0.18 PL: - fixed several bugs in handling of FAT, MiniFAT and -# streams -# - added option '-c' in main to check all streams -# 2009-12-10 v0.19 PL: - bugfix for 32 bit arrays on 64 bits platforms -# (thanks to Ben G. and Martijn for reporting the bug) -# 2009-12-11 v0.20 PL: - bugfix in OleFileIO.open when filename is not plain str -# 2010-01-22 v0.21 PL: - added support for big-endian CPUs such as PowerPC Macs -# 2012-02-16 v0.22 PL: - fixed bug in getproperties, patch by chuckleberryfinn -# (https://bitbucket.org/decalage/olefileio_pl/issue/7) -# - added close method to OleFileIO (fixed issue #2) -# 2012-07-25 v0.23 PL: - added support for file-like objects (patch by mete0r_kr) -# 2013-05-05 v0.24 PL: - getproperties: added conversion from filetime to python -# datetime -# - main: displays properties with date format -# - new class OleMetadata to parse standard properties -# - added get_metadata method -# 2013-05-07 v0.24 PL: - a few improvements in OleMetadata -# 2013-05-24 v0.25 PL: - getproperties: option to not convert some timestamps -# - OleMetaData: total_edit_time is now a number of seconds, -# not a timestamp -# - getproperties: added support for VT_BOOL, VT_INT, V_UINT -# - getproperties: filter out null chars from strings -# - getproperties: raise non-fatal defects instead of -# exceptions when properties cannot be parsed properly -# 2013-05-27 PL: - getproperties: improved exception handling -# - _raise_defect: added option to set exception type -# - all non-fatal issues are now recorded, and displayed -# when run as a script -# 2013-07-11 v0.26 PL: - added methods to get modification and creation times -# of a directory entry or a storage/stream -# - fixed parsing of direntry timestamps -# 2013-07-24 PL: - new options in listdir to list storages and/or streams -# 2014-02-04 v0.30 PL: - upgraded code to support Python 3.x by Martin Panter -# - several fixes for Python 2.6 (xrange, MAGIC) -# - reused i32 from Pillow's _binary -# 2014-07-18 v0.31 - preliminary support for 4K sectors -# 2014-07-27 v0.31 PL: - a few improvements in OleFileIO.open (header parsing) -# - Fixed loadfat for large files with 4K sectors (issue #3) -# 2014-07-30 v0.32 PL: - added write_sect to write sectors to disk -# - added write_mode option to OleFileIO.__init__ and open -# 2014-07-31 PL: - fixed padding in write_sect for Python 3, added checks -# - added write_stream to write a stream to disk -# 2014-09-26 v0.40 PL: - renamed OleFileIO_PL to olefile -# 2014-11-09 NE: - added support for Jython (Niko Ehrenfeuchter) -# 2014-11-13 v0.41 PL: - improved isOleFile and OleFileIO.open to support OLE -# data in a string buffer and file-like objects. -# 2014-11-21 PL: - updated comments according to Pillow's commits -# 2015-01-24 v0.42 PL: - changed the default path name encoding from Latin-1 -# to UTF-8 on Python 2.x (Unicode on Python 3.x) -# - added path_encoding option to override the default -# - fixed a bug in _list when a storage is empty - -#----------------------------------------------------------------------------- -# TODO (for version 1.0): -# + get rid of print statements, to simplify Python 2.x and 3.x support -# + add is_stream and is_storage -# + remove leading and trailing slashes where a path is used -# + add functions path_list2str and path_str2list -# + fix how all the methods handle unicode str and/or bytes as arguments -# + add path attrib to _OleDirEntry, set it once and for all in init or -# append_kids (then listdir/_list can be simplified) -# - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ... -# - add underscore to each private method, to avoid their display in -# pydoc/epydoc documentation - Remove it for classes to be documented -# - replace all raised exceptions with _raise_defect (at least in OleFileIO) -# - merge code from _OleStream and OleFileIO.getsect to read sectors -# (maybe add a class for FAT and MiniFAT ?) -# - add method to check all streams (follow sectors chains without storing all -# stream in memory, and report anomalies) -# - use _OleDirectoryEntry.kids_dict to improve _find and _list ? -# - fix Unicode names handling (find some way to stay compatible with Py1.5.2) -# => if possible avoid converting names to Latin-1 -# - review DIFAT code: fix handling of DIFSECT blocks in FAT (not stop) -# - rewrite OleFileIO.getproperties -# - improve docstrings to show more sample uses -# - see also original notes and FIXME below -# - remove all obsolete FIXMEs -# - OleMetadata: fix version attrib according to -# http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx - -# IDEAS: -# - in OleFileIO._open and _OleStream, use size=None instead of 0x7FFFFFFF for -# streams with unknown size -# - use arrays of int instead of long integers for FAT/MiniFAT, to improve -# performance and reduce memory usage ? (possible issue with values >2^31) -# - provide tests with unittest (may need write support to create samples) -# - move all debug code (and maybe dump methods) to a separate module, with -# a class which inherits OleFileIO ? -# - fix docstrings to follow epydoc format -# - add support for big endian byte order ? -# - create a simple OLE explorer with wxPython - -# FUTURE EVOLUTIONS to add write support: -# see issue #6 on Bitbucket: -# https://bitbucket.org/decalage/olefileio_pl/issue/6/improve-olefileio_pl-to-write-ole-files - -#----------------------------------------------------------------------------- -# NOTES from PIL 1.1.6: - -# History: -# 1997-01-20 fl Created -# 1997-01-22 fl Fixed 64-bit portability quirk -# 2003-09-09 fl Fixed typo in OleFileIO.loadfat (noted by Daniel Haertle) -# 2004-02-29 fl Changed long hex constants to signed integers -# -# Notes: -# FIXME: sort out sign problem (eliminate long hex constants) -# FIXME: change filename to use "a/b/c" instead of ["a", "b", "c"] -# FIXME: provide a glob mechanism function (using fnmatchcase) -# -# Literature: -# -# "FlashPix Format Specification, Appendix A", Kodak and Microsoft, -# September 1996. -# -# Quotes: -# -# "If this document and functionality of the Software conflict, -# the actual functionality of the Software represents the correct -# functionality" -- Microsoft, in the OLE format specification - -#------------------------------------------------------------------------------ - - -import io +import olefile import sys -import struct -import array -import os.path -import datetime -#=== COMPATIBILITY WORKAROUNDS ================================================ - -# [PL] Define explicitly the public API to avoid private objects in pydoc: -#TODO: add more -# __all__ = ['OleFileIO', 'isOleFile', 'MAGIC'] - -# For Python 3.x, need to redefine long as int: -if str is not bytes: - long = int - -# Need to make sure we use xrange both on Python 2 and 3.x: -try: - # on Python 2 we need xrange: - iterrange = xrange -except: - # no xrange, for Python 3 it was renamed as range: - iterrange = range - -# [PL] workaround to fix an issue with array item size on 64 bits systems: -if array.array('L').itemsize == 4: - # on 32 bits platforms, long integers in an array are 32 bits: - UINT32 = 'L' -elif array.array('I').itemsize == 4: - # on 64 bits platforms, integers in an array are 32 bits: - UINT32 = 'I' -elif array.array('i').itemsize == 4: - # On 64 bit Jython, signed integers ('i') are the only way to store our 32 - # bit values in an array in a *somewhat* reasonable way, as the otherwise - # perfectly suited 'H' (unsigned int, 32 bits) results in a completely - # unusable behaviour. This is most likely caused by the fact that Java - # doesn't have unsigned values, and thus Jython's "array" implementation, - # which is based on "jarray", doesn't have them either. - # NOTE: to trick Jython into converting the values it would normally - # interpret as "signed" into "unsigned", a binary-and operation with - # 0xFFFFFFFF can be used. This way it is possible to use the same comparing - # operations on all platforms / implementations. The corresponding code - # lines are flagged with a 'JYTHON-WORKAROUND' tag below. - UINT32 = 'i' -else: - raise ValueError('Need to fix a bug with 32 bit arrays, please contact author...') - - -# [PL] These workarounds were inspired from the Path module -# (see http://www.jorendorff.com/articles/python/path/) -try: - basestring -except NameError: - basestring = str - -# [PL] Experimental setting: if True, OLE filenames will be kept in Unicode -# if False (default PIL behaviour), all filenames are converted to Latin-1. -KEEP_UNICODE_NAMES = True - -if sys.version_info[0] < 3: - # On Python 2.x, the default encoding for path names is UTF-8: - DEFAULT_PATH_ENCODING = 'utf-8' -else: - # On Python 3.x, the default encoding for path names is Unicode (None): - DEFAULT_PATH_ENCODING = None - - -#=== DEBUGGING =============================================================== - -#TODO: replace this by proper logging - -# [PL] DEBUG display mode: False by default, use set_debug_mode() or "-d" on -# command line to change it. -DEBUG_MODE = False - - -def debug_print(msg): - print(msg) - - -def debug_pass(msg): - pass - - -debug = debug_pass - - -def set_debug_mode(debug_mode): - """ - Set debug mode on or off, to control display of debugging messages. - :param mode: True or False - """ - global DEBUG_MODE, debug - DEBUG_MODE = debug_mode - if debug_mode: - debug = debug_print - else: - debug = debug_pass - - -#=== CONSTANTS =============================================================== - -# magic bytes that should be at the beginning of every OLE file: -MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' - -# [PL]: added constants for Sector IDs (from AAF specifications) -MAXREGSECT = 0xFFFFFFFA # (-6) maximum SECT -DIFSECT = 0xFFFFFFFC # (-4) denotes a DIFAT sector in a FAT -FATSECT = 0xFFFFFFFD # (-3) denotes a FAT sector in a FAT -ENDOFCHAIN = 0xFFFFFFFE # (-2) end of a virtual stream chain -FREESECT = 0xFFFFFFFF # (-1) unallocated sector - -# [PL]: added constants for Directory Entry IDs (from AAF specifications) -MAXREGSID = 0xFFFFFFFA # (-6) maximum directory entry ID -NOSTREAM = 0xFFFFFFFF # (-1) unallocated directory entry - -# [PL] object types in storage (from AAF specifications) -STGTY_EMPTY = 0 # empty directory entry (according to OpenOffice.org doc) -STGTY_STORAGE = 1 # element is a storage object -STGTY_STREAM = 2 # element is a stream object -STGTY_LOCKBYTES = 3 # element is an ILockBytes object -STGTY_PROPERTY = 4 # element is an IPropertyStorage object -STGTY_ROOT = 5 # element is a root storage - - -# -# -------------------------------------------------------------------- -# property types - -VT_EMPTY = 0; VT_NULL = 1; VT_I2 = 2; VT_I4 = 3; VT_R4 = 4; VT_R8 = 5; VT_CY = 6; -VT_DATE = 7; VT_BSTR = 8; VT_DISPATCH = 9; VT_ERROR = 10; VT_BOOL = 11; -VT_VARIANT = 12; VT_UNKNOWN = 13; VT_DECIMAL = 14; VT_I1 = 16; VT_UI1 = 17; -VT_UI2 = 18; VT_UI4 = 19; VT_I8 = 20; VT_UI8 = 21; VT_INT = 22; VT_UINT = 23; -VT_VOID = 24; VT_HRESULT = 25; VT_PTR = 26; VT_SAFEARRAY = 27; VT_CARRAY = 28; -VT_USERDEFINED = 29; VT_LPSTR = 30; VT_LPWSTR = 31; VT_FILETIME = 64; -VT_BLOB = 65; VT_STREAM = 66; VT_STORAGE = 67; VT_STREAMED_OBJECT = 68; -VT_STORED_OBJECT = 69; VT_BLOB_OBJECT = 70; VT_CF = 71; VT_CLSID = 72; -VT_VECTOR = 0x1000; - -# map property id to name (for debugging purposes) - -VT = {} -for keyword, var in list(vars().items()): - if keyword[:3] == "VT_": - VT[var] = keyword - -# -# -------------------------------------------------------------------- -# Some common document types (root.clsid fields) - -WORD_CLSID = "00020900-0000-0000-C000-000000000046" -#TODO: check Excel, PPT, ... - -# [PL]: Defect levels to classify parsing errors - see OleFileIO._raise_defect() -DEFECT_UNSURE = 10 # a case which looks weird, but not sure it's a defect -DEFECT_POTENTIAL = 20 # a potential defect -DEFECT_INCORRECT = 30 # an error according to specifications, but parsing - # can go on -DEFECT_FATAL = 40 # an error which cannot be ignored, parsing is - # impossible - -# Minimal size of an empty OLE file, with 512-bytes sectors = 1536 bytes -# (this is used in isOleFile and OleFile.open) -MINIMAL_OLEFILE_SIZE = 1536 - -# [PL] add useful constants to __all__: -# for key in list(vars().keys()): -# if key.startswith('STGTY_') or key.startswith('DEFECT_'): -# __all__.append(key) - - -#=== FUNCTIONS =============================================================== - -def isOleFile(filename): - """ - Test if a file is an OLE container (according to the magic bytes in its header). - - :param filename: string-like or file-like object, OLE file to parse - - - if filename is a string smaller than 1536 bytes, it is the path - of the file to open. (bytes or unicode string) - - if filename is a string longer than 1535 bytes, it is parsed - as the content of an OLE file in memory. (bytes type only) - - if filename is a file-like object (with read and seek methods), - it is parsed as-is. - - :returns: True if OLE, False otherwise. - """ - # check if filename is a string-like or file-like object: - if hasattr(filename, 'read'): - # file-like object: use it directly - header = filename.read(len(MAGIC)) - # just in case, seek back to start of file: - filename.seek(0) - elif isinstance(filename, bytes) and len(filename) >= MINIMAL_OLEFILE_SIZE: - # filename is a bytes string containing the OLE file to be parsed: - header = filename[:len(MAGIC)] - else: - # string-like object: filename of file on disk - header = open(filename, 'rb').read(len(MAGIC)) - if header == MAGIC: - return True - else: - return False - - -if bytes is str: - # version for Python 2.x - def i8(c): - return ord(c) -else: - # version for Python 3.x - def i8(c): - return c if c.__class__ is int else c[0] - - -#TODO: replace i16 and i32 with more readable struct.unpack equivalent? - -def i16(c, o = 0): - """ - Converts a 2-bytes (16 bits) string to an integer. - - c: string containing bytes to convert - o: offset of bytes to convert in string - """ - return struct.unpack(" len(fat): - raise IOError('malformed OLE document, stream too large') - # optimization(?): data is first a list of strings, and join() is called - # at the end to concatenate all in one string. - # (this may not be really useful with recent Python versions) - data = [] - # if size is zero, then first sector index should be ENDOFCHAIN: - if size == 0 and sect != ENDOFCHAIN: - debug('size == 0 and sect != ENDOFCHAIN:') - raise IOError('incorrect OLE sector index for empty stream') - # [PL] A fixed-length for loop is used instead of an undefined while - # loop to avoid DoS attacks: - for i in range(nb_sectors): - # Sector index may be ENDOFCHAIN, but only if size was unknown - if sect == ENDOFCHAIN: - if unknown_size: - break - else: - # else this means that the stream is smaller than declared: - debug('sect=ENDOFCHAIN before expected size') - raise IOError('incomplete OLE stream') - # sector index should be within FAT: - if sect < 0 or sect >= len(fat): - debug('sect=%d (%X) / len(fat)=%d' % (sect, sect, len(fat))) - debug('i=%d / nb_sectors=%d' % (i, nb_sectors)) -## tmp_data = b"".join(data) -## f = open('test_debug.bin', 'wb') -## f.write(tmp_data) -## f.close() -## debug('data read so far: %d bytes' % len(tmp_data)) - raise IOError('incorrect OLE FAT, sector index out of range') - #TODO: merge this code with OleFileIO.getsect() ? - #TODO: check if this works with 4K sectors: - try: - fp.seek(offset + sectorsize * sect) - except: - debug('sect=%d, seek=%d, filesize=%d' % - (sect, offset+sectorsize*sect, filesize)) - raise IOError('OLE sector index out of range') - sector_data = fp.read(sectorsize) - # [PL] check if there was enough data: - # Note: if sector is the last of the file, sometimes it is not a - # complete sector (of 512 or 4K), so we may read less than - # sectorsize. - if len(sector_data) != sectorsize and sect != (len(fat)-1): - debug('sect=%d / len(fat)=%d, seek=%d / filesize=%d, len read=%d' % - (sect, len(fat), offset+sectorsize*sect, filesize, len(sector_data))) - debug('seek+len(read)=%d' % (offset+sectorsize*sect+len(sector_data))) - raise IOError('incomplete OLE sector') - data.append(sector_data) - # jump to next sector in the FAT: - try: - sect = fat[sect] & 0xFFFFFFFF # JYTHON-WORKAROUND - except IndexError: - # [PL] if pointer is out of the FAT an exception is raised - raise IOError('incorrect OLE FAT, sector index out of range') - # [PL] Last sector should be a "end of chain" marker: - if sect != ENDOFCHAIN: - raise IOError('incorrect last sector index in OLE stream') - data = b"".join(data) - # Data is truncated to the actual stream size: - if len(data) >= size: - data = data[:size] - # actual stream size is stored for future use: - self.size = size - elif unknown_size: - # actual stream size was not known, now we know the size of read - # data: - self.size = len(data) - else: - # read data is less than expected: - debug('len(data)=%d, size=%d' % (len(data), size)) - raise IOError('OLE stream size is less than declared') - # when all data is read in memory, BytesIO constructor is called - io.BytesIO.__init__(self, data) - # Then the _OleStream object can be used as a read-only file object. - - -#--- _OleDirectoryEntry ------------------------------------------------------- - -class _OleDirectoryEntry(object): - - """ - OLE2 Directory Entry - """ - # [PL] parsing code moved from OleFileIO.loaddirectory - - # struct to parse directory entries: - # <: little-endian byte order, standard sizes - # (note: this should guarantee that Q returns a 64 bits int) - # 64s: string containing entry name in unicode (max 31 chars) + null char - # H: uint16, number of bytes used in name buffer, including null = (len+1)*2 - # B: uint8, dir entry type (between 0 and 5) - # B: uint8, color: 0=black, 1=red - # I: uint32, index of left child node in the red-black tree, NOSTREAM if none - # I: uint32, index of right child node in the red-black tree, NOSTREAM if none - # I: uint32, index of child root node if it is a storage, else NOSTREAM - # 16s: CLSID, unique identifier (only used if it is a storage) - # I: uint32, user flags - # Q (was 8s): uint64, creation timestamp or zero - # Q (was 8s): uint64, modification timestamp or zero - # I: uint32, SID of first sector if stream or ministream, SID of 1st sector - # of stream containing ministreams if root entry, 0 otherwise - # I: uint32, total stream size in bytes if stream (low 32 bits), 0 otherwise - # I: uint32, total stream size in bytes if stream (high 32 bits), 0 otherwise - STRUCT_DIRENTRY = '<64sHBBIII16sIQQIII' - # size of a directory entry: 128 bytes - DIRENTRY_SIZE = 128 - assert struct.calcsize(STRUCT_DIRENTRY) == DIRENTRY_SIZE - - def __init__(self, entry, sid, olefile): - """ - Constructor for an _OleDirectoryEntry object. - Parses a 128-bytes entry from the OLE Directory stream. - - :param entry : string (must be 128 bytes long) - :param sid : index of this directory entry in the OLE file directory - :param olefile: OleFileIO containing this directory entry - """ - self.sid = sid - # ref to olefile is stored for future use - self.olefile = olefile - # kids is a list of children entries, if this entry is a storage: - # (list of _OleDirectoryEntry objects) - self.kids = [] - # kids_dict is a dictionary of children entries, indexed by their - # name in lowercase: used to quickly find an entry, and to detect - # duplicates - self.kids_dict = {} - # flag used to detect if the entry is referenced more than once in - # directory: - self.used = False - # decode DirEntry - ( - name, - namelength, - self.entry_type, - self.color, - self.sid_left, - self.sid_right, - self.sid_child, - clsid, - self.dwUserFlags, - self.createTime, - self.modifyTime, - self.isectStart, - sizeLow, - sizeHigh - ) = struct.unpack(_OleDirectoryEntry.STRUCT_DIRENTRY, entry) - if self.entry_type not in [STGTY_ROOT, STGTY_STORAGE, STGTY_STREAM, STGTY_EMPTY]: - olefile.raise_defect(DEFECT_INCORRECT, 'unhandled OLE storage type') - # only first directory entry can (and should) be root: - if self.entry_type == STGTY_ROOT and sid != 0: - olefile.raise_defect(DEFECT_INCORRECT, 'duplicate OLE root entry') - if sid == 0 and self.entry_type != STGTY_ROOT: - olefile.raise_defect(DEFECT_INCORRECT, 'incorrect OLE root entry') - #debug (struct.unpack(fmt_entry, entry[:len_entry])) - # name should be at most 31 unicode characters + null character, - # so 64 bytes in total (31*2 + 2): - if namelength > 64: - olefile.raise_defect(DEFECT_INCORRECT, 'incorrect DirEntry name length') - # if exception not raised, namelength is set to the maximum value: - namelength = 64 - # only characters without ending null char are kept: - name = name[:(namelength-2)] - #TODO: check if the name is actually followed by a null unicode character ([MS-CFB] 2.6.1) - #TODO: check if the name does not contain forbidden characters: - # [MS-CFB] 2.6.1: "The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'." - # name is converted from UTF-16LE to the path encoding specified in the OleFileIO: - self.name = olefile._decode_utf16_str(name) - - debug('DirEntry SID=%d: %s' % (self.sid, repr(self.name))) - debug(' - type: %d' % self.entry_type) - debug(' - sect: %d' % self.isectStart) - debug(' - SID left: %d, right: %d, child: %d' % (self.sid_left, - self.sid_right, self.sid_child)) - - # sizeHigh is only used for 4K sectors, it should be zero for 512 bytes - # sectors, BUT apparently some implementations set it as 0xFFFFFFFF, 1 - # or some other value so it cannot be raised as a defect in general: - if olefile.sectorsize == 512: - if sizeHigh != 0 and sizeHigh != 0xFFFFFFFF: - debug('sectorsize=%d, sizeLow=%d, sizeHigh=%d (%X)' % - (olefile.sectorsize, sizeLow, sizeHigh, sizeHigh)) - olefile.raise_defect(DEFECT_UNSURE, 'incorrect OLE stream size') - self.size = sizeLow - else: - self.size = sizeLow + (long(sizeHigh) << 32) - debug(' - size: %d (sizeLow=%d, sizeHigh=%d)' % (self.size, sizeLow, sizeHigh)) - - self.clsid = _clsid(clsid) - # a storage should have a null size, BUT some implementations such as - # Word 8 for Mac seem to allow non-null values => Potential defect: - if self.entry_type == STGTY_STORAGE and self.size != 0: - olefile.raise_defect(DEFECT_POTENTIAL, 'OLE storage with size>0') - # check if stream is not already referenced elsewhere: - if self.entry_type in (STGTY_ROOT, STGTY_STREAM) and self.size > 0: - if self.size < olefile.minisectorcutoff \ - and self.entry_type == STGTY_STREAM: # only streams can be in MiniFAT - # ministream object - minifat = True - else: - minifat = False - olefile._check_duplicate_stream(self.isectStart, minifat) - - def build_storage_tree(self): - """ - Read and build the red-black tree attached to this _OleDirectoryEntry - object, if it is a storage. - Note that this method builds a tree of all subentries, so it should - only be called for the root object once. - """ - debug('build_storage_tree: SID=%d - %s - sid_child=%d' - % (self.sid, repr(self.name), self.sid_child)) - if self.sid_child != NOSTREAM: - # if child SID is not NOSTREAM, then this entry is a storage. - # Let's walk through the tree of children to fill the kids list: - self.append_kids(self.sid_child) - - # Note from OpenOffice documentation: the safest way is to - # recreate the tree because some implementations may store broken - # red-black trees... - - # in the OLE file, entries are sorted on (length, name). - # for convenience, we sort them on name instead: - # (see rich comparison methods in this class) - self.kids.sort() - - def append_kids(self, child_sid): - """ - Walk through red-black tree of children of this directory entry to add - all of them to the kids list. (recursive method) - - :param child_sid : index of child directory entry to use, or None when called - first time for the root. (only used during recursion) - """ - # [PL] this method was added to use simple recursion instead of a complex - # algorithm. - # if this is not a storage or a leaf of the tree, nothing to do: - if child_sid == NOSTREAM: - return - # check if child SID is in the proper range: - if child_sid < 0 or child_sid >= len(self.olefile.direntries): - self.olefile.raise_defect(DEFECT_FATAL, 'OLE DirEntry index out of range') - # get child direntry: - child = self.olefile._load_direntry(child_sid) #direntries[child_sid] - debug('append_kids: child_sid=%d - %s - sid_left=%d, sid_right=%d, sid_child=%d' - % (child.sid, repr(child.name), child.sid_left, child.sid_right, child.sid_child)) - # the directory entries are organized as a red-black tree. - # (cf. Wikipedia for details) - # First walk through left side of the tree: - self.append_kids(child.sid_left) - # Check if its name is not already used (case-insensitive): - name_lower = child.name.lower() - if name_lower in self.kids_dict: - self.olefile.raise_defect(DEFECT_INCORRECT, - "Duplicate filename in OLE storage") - # Then the child_sid _OleDirectoryEntry object is appended to the - # kids list and dictionary: - self.kids.append(child) - self.kids_dict[name_lower] = child - # Check if kid was not already referenced in a storage: - if child.used: - self.olefile.raise_defect(DEFECT_INCORRECT, - 'OLE Entry referenced more than once') - child.used = True - # Finally walk through right side of the tree: - self.append_kids(child.sid_right) - # Afterwards build kid's own tree if it's also a storage: - child.build_storage_tree() - - def __eq__(self, other): - "Compare entries by name" - return self.name == other.name - - def __lt__(self, other): - "Compare entries by name" - return self.name < other.name - - def __ne__(self, other): - return not self.__eq__(other) - - def __le__(self, other): - return self.__eq__(other) or self.__lt__(other) - - # Reflected __lt__() and __le__() will be used for __gt__() and __ge__() - - #TODO: replace by the same function as MS implementation ? - # (order by name length first, then case-insensitive order) - - def dump(self, tab = 0): - "Dump this entry, and all its subentries (for debug purposes only)" - TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)", - "(property)", "(root)"] - print(" "*tab + repr(self.name), TYPES[self.entry_type], end=' ') - if self.entry_type in (STGTY_STREAM, STGTY_ROOT): - print(self.size, "bytes", end=' ') - print() - if self.entry_type in (STGTY_STORAGE, STGTY_ROOT) and self.clsid: - print(" "*tab + "{%s}" % self.clsid) - - for kid in self.kids: - kid.dump(tab + 2) - - def getmtime(self): - """ - Return modification time of a directory entry. - - :returns: None if modification time is null, a python datetime object - otherwise (UTC timezone) - - new in version 0.26 - """ - if self.modifyTime == 0: - return None - return filetime2datetime(self.modifyTime) - - def getctime(self): - """ - Return creation time of a directory entry. - - :returns: None if modification time is null, a python datetime object - otherwise (UTC timezone) - - new in version 0.26 - """ - if self.createTime == 0: - return None - return filetime2datetime(self.createTime) - - -#--- OleFileIO ---------------------------------------------------------------- - -class OleFileIO(object): - """ - OLE container object - - This class encapsulates the interface to an OLE 2 structured - storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and - :py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to - access the contents of this file. - - Object names are given as a list of strings, one for each subentry - level. The root entry should be omitted. For example, the following - code extracts all image streams from a Microsoft Image Composer file:: - - ole = OleFileIO("fan.mic") - - for entry in ole.listdir(): - if entry[1:2] == "Image": - fin = ole.openstream(entry) - fout = open(entry[0:1], "wb") - while True: - s = fin.read(8192) - if not s: - break - fout.write(s) - - You can use the viewer application provided with the Python Imaging - Library to view the resulting files (which happens to be standard - TIFF files). - """ - - def __init__(self, filename=None, raise_defects=DEFECT_FATAL, - write_mode=False, debug=False, path_encoding=DEFAULT_PATH_ENCODING): - """ - Constructor for the OleFileIO class. - - :param filename: file to open. - - - if filename is a string smaller than 1536 bytes, it is the path - of the file to open. (bytes or unicode string) - - if filename is a string longer than 1535 bytes, it is parsed - as the content of an OLE file in memory. (bytes type only) - - if filename is a file-like object (with read, seek and tell methods), - it is parsed as-is. - - :param raise_defects: minimal level for defects to be raised as exceptions. - (use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a - security-oriented application, see source code for details) - - :param write_mode: bool, if True the file is opened in read/write mode instead - of read-only by default. - - :param debug: bool, set debug mode - - :param path_encoding: None or str, name of the codec to use for path - names (streams and storages), or None for Unicode. - Unicode by default on Python 3+, UTF-8 on Python 2.x. - (new in olefile 0.42, was hardcoded to Latin-1 until olefile v0.41) - """ - set_debug_mode(debug) - # minimal level for defects to be raised as exceptions: - self._raise_defects_level = raise_defects - # list of defects/issues not raised as exceptions: - # tuples of (exception type, message) - self.parsing_issues = [] - self.write_mode = write_mode - self.path_encoding = path_encoding - self._filesize = None - self.fp = None - if filename: - self.open(filename, write_mode=write_mode) - - def raise_defect(self, defect_level, message, exception_type=IOError): - """ - This method should be called for any defect found during file parsing. - It may raise an IOError exception according to the minimal level chosen - for the OleFileIO object. - - :param defect_level: defect level, possible values are: - - - DEFECT_UNSURE : a case which looks weird, but not sure it's a defect - - DEFECT_POTENTIAL : a potential defect - - DEFECT_INCORRECT : an error according to specifications, but parsing can go on - - DEFECT_FATAL : an error which cannot be ignored, parsing is impossible - - :param message: string describing the defect, used with raised exception. - :param exception_type: exception class to be raised, IOError by default - """ - # added by [PL] - if defect_level >= self._raise_defects_level: - raise exception_type(message) - else: - # just record the issue, no exception raised: - self.parsing_issues.append((exception_type, message)) - - def _decode_utf16_str(self, utf16_str, errors='replace'): - """ - Decode a string encoded in UTF-16 LE format, as found in the OLE - directory or in property streams. Return a string encoded - according to the path_encoding specified for the OleFileIO object. - - :param utf16_str: bytes string encoded in UTF-16 LE format - :param errors: str, see python documentation for str.decode() - :return: str, encoded according to path_encoding - """ - unicode_str = utf16_str.decode('UTF-16LE', errors) - if self.path_encoding: - # an encoding has been specified for path names: - return unicode_str.encode(self.path_encoding, errors) - else: - # path_encoding=None, return the Unicode string as-is: - return unicode_str - - def open(self, filename, write_mode=False): - """ - Open an OLE2 file in read-only or read/write mode. - Read and parse the header, FAT and directory. - - :param filename: string-like or file-like object, OLE file to parse - - - if filename is a string smaller than 1536 bytes, it is the path - of the file to open. (bytes or unicode string) - - if filename is a string longer than 1535 bytes, it is parsed - as the content of an OLE file in memory. (bytes type only) - - if filename is a file-like object (with read, seek and tell methods), - it is parsed as-is. - - :param write_mode: bool, if True the file is opened in read/write mode instead - of read-only by default. (ignored if filename is not a path) - """ - self.write_mode = write_mode - # [PL] check if filename is a string-like or file-like object: - # (it is better to check for a read() method) - if hasattr(filename, 'read'): - #TODO: also check seek and tell methods? - # file-like object: use it directly - self.fp = filename - elif isinstance(filename, bytes) and len(filename) >= MINIMAL_OLEFILE_SIZE: - # filename is a bytes string containing the OLE file to be parsed: - # convert it to BytesIO - self.fp = io.BytesIO(filename) - else: - # string-like object: filename of file on disk - if self.write_mode: - # open file in mode 'read with update, binary' - # According to https://docs.python.org/2/library/functions.html#open - # 'w' would truncate the file, 'a' may only append on some Unixes - mode = 'r+b' - else: - # read-only mode by default - mode = 'rb' - self.fp = open(filename, mode) - # obtain the filesize by using seek and tell, which should work on most - # file-like objects: - #TODO: do it above, using getsize with filename when possible? - #TODO: fix code to fail with clear exception when filesize cannot be obtained - filesize = 0 - self.fp.seek(0, os.SEEK_END) - try: - filesize = self.fp.tell() - finally: - self.fp.seek(0) - self._filesize = filesize - - # lists of streams in FAT and MiniFAT, to detect duplicate references - # (list of indexes of first sectors of each stream) - self._used_streams_fat = [] - self._used_streams_minifat = [] - - header = self.fp.read(512) - - if len(header) != 512 or header[:8] != MAGIC: - self.raise_defect(DEFECT_FATAL, "not an OLE2 structured storage file") - - # [PL] header structure according to AAF specifications: - ##Header - ##struct StructuredStorageHeader { // [offset from start (bytes), length (bytes)] - ##BYTE _abSig[8]; // [00H,08] {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, - ## // 0x1a, 0xe1} for current version - ##CLSID _clsid; // [08H,16] reserved must be zero (WriteClassStg/ - ## // GetClassFile uses root directory class id) - ##USHORT _uMinorVersion; // [18H,02] minor version of the format: 33 is - ## // written by reference implementation - ##USHORT _uDllVersion; // [1AH,02] major version of the dll/format: 3 for - ## // 512-byte sectors, 4 for 4 KB sectors - ##USHORT _uByteOrder; // [1CH,02] 0xFFFE: indicates Intel byte-ordering - ##USHORT _uSectorShift; // [1EH,02] size of sectors in power-of-two; - ## // typically 9 indicating 512-byte sectors - ##USHORT _uMiniSectorShift; // [20H,02] size of mini-sectors in power-of-two; - ## // typically 6 indicating 64-byte mini-sectors - ##USHORT _usReserved; // [22H,02] reserved, must be zero - ##ULONG _ulReserved1; // [24H,04] reserved, must be zero - ##FSINDEX _csectDir; // [28H,04] must be zero for 512-byte sectors, - ## // number of SECTs in directory chain for 4 KB - ## // sectors - ##FSINDEX _csectFat; // [2CH,04] number of SECTs in the FAT chain - ##SECT _sectDirStart; // [30H,04] first SECT in the directory chain - ##DFSIGNATURE _signature; // [34H,04] signature used for transactions; must - ## // be zero. The reference implementation - ## // does not support transactions - ##ULONG _ulMiniSectorCutoff; // [38H,04] maximum size for a mini stream; - ## // typically 4096 bytes - ##SECT _sectMiniFatStart; // [3CH,04] first SECT in the MiniFAT chain - ##FSINDEX _csectMiniFat; // [40H,04] number of SECTs in the MiniFAT chain - ##SECT _sectDifStart; // [44H,04] first SECT in the DIFAT chain - ##FSINDEX _csectDif; // [48H,04] number of SECTs in the DIFAT chain - ##SECT _sectFat[109]; // [4CH,436] the SECTs of first 109 FAT sectors - ##}; - - # [PL] header decoding: - # '<' indicates little-endian byte ordering for Intel (cf. struct module help) - fmt_header = '<8s16sHHHHHHLLLLLLLLLL' - header_size = struct.calcsize(fmt_header) - debug("fmt_header size = %d, +FAT = %d" % (header_size, header_size + 109*4)) - header1 = header[:header_size] - ( - self.Sig, - self.clsid, - self.MinorVersion, - self.DllVersion, - self.ByteOrder, - self.SectorShift, - self.MiniSectorShift, - self.Reserved, self.Reserved1, - self.csectDir, - self.csectFat, - self.sectDirStart, - self.signature, - self.MiniSectorCutoff, - self.MiniFatStart, - self.csectMiniFat, - self.sectDifStart, - self.csectDif - ) = struct.unpack(fmt_header, header1) - debug(struct.unpack(fmt_header, header1)) - - if self.Sig != MAGIC: - # OLE signature should always be present - self.raise_defect(DEFECT_FATAL, "incorrect OLE signature") - if self.clsid != bytearray(16): - # according to AAF specs, CLSID should always be zero - self.raise_defect(DEFECT_INCORRECT, "incorrect CLSID in OLE header") - debug("MinorVersion = %d" % self.MinorVersion) - debug("DllVersion = %d" % self.DllVersion) - if self.DllVersion not in [3, 4]: - # version 3: usual format, 512 bytes per sector - # version 4: large format, 4K per sector - self.raise_defect(DEFECT_INCORRECT, "incorrect DllVersion in OLE header") - debug("ByteOrder = %X" % self.ByteOrder) - if self.ByteOrder != 0xFFFE: - # For now only common little-endian documents are handled correctly - self.raise_defect(DEFECT_FATAL, "incorrect ByteOrder in OLE header") - # TODO: add big-endian support for documents created on Mac ? - # But according to [MS-CFB] ? v20140502, ByteOrder MUST be 0xFFFE. - self.SectorSize = 2**self.SectorShift - debug("SectorSize = %d" % self.SectorSize) - if self.SectorSize not in [512, 4096]: - self.raise_defect(DEFECT_INCORRECT, "incorrect SectorSize in OLE header") - if (self.DllVersion == 3 and self.SectorSize != 512) \ - or (self.DllVersion == 4 and self.SectorSize != 4096): - self.raise_defect(DEFECT_INCORRECT, "SectorSize does not match DllVersion in OLE header") - self.MiniSectorSize = 2**self.MiniSectorShift - debug("MiniSectorSize = %d" % self.MiniSectorSize) - if self.MiniSectorSize not in [64]: - self.raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorSize in OLE header") - if self.Reserved != 0 or self.Reserved1 != 0: - self.raise_defect(DEFECT_INCORRECT, "incorrect OLE header (non-null reserved bytes)") - debug("csectDir = %d" % self.csectDir) - # Number of directory sectors (only allowed if DllVersion != 3) - if self.SectorSize == 512 and self.csectDir != 0: - self.raise_defect(DEFECT_INCORRECT, "incorrect csectDir in OLE header") - debug("csectFat = %d" % self.csectFat) - # csectFat = number of FAT sectors in the file - debug("sectDirStart = %X" % self.sectDirStart) - # sectDirStart = 1st sector containing the directory - debug("signature = %d" % self.signature) - # Signature should be zero, BUT some implementations do not follow this - # rule => only a potential defect: - # (according to MS-CFB, may be != 0 for applications supporting file - # transactions) - if self.signature != 0: - self.raise_defect(DEFECT_POTENTIAL, "incorrect OLE header (signature>0)") - debug("MiniSectorCutoff = %d" % self.MiniSectorCutoff) - # MS-CFB: This integer field MUST be set to 0x00001000. This field - # specifies the maximum size of a user-defined data stream allocated - # from the mini FAT and mini stream, and that cutoff is 4096 bytes. - # Any user-defined data stream larger than or equal to this cutoff size - # must be allocated as normal sectors from the FAT. - if self.MiniSectorCutoff != 0x1000: - self.raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorCutoff in OLE header") - debug("MiniFatStart = %X" % self.MiniFatStart) - debug("csectMiniFat = %d" % self.csectMiniFat) - debug("sectDifStart = %X" % self.sectDifStart) - debug("csectDif = %d" % self.csectDif) - - # calculate the number of sectors in the file - # (-1 because header doesn't count) - self.nb_sect = ((filesize + self.SectorSize-1) // self.SectorSize) - 1 - debug("Number of sectors in the file: %d" % self.nb_sect) - #TODO: change this test, because an OLE file MAY contain other data - # after the last sector. - - # file clsid - self.clsid = _clsid(header[8:24]) - - #TODO: remove redundant attributes, and fix the code which uses them? - self.sectorsize = self.SectorSize #1 << i16(header, 30) - self.minisectorsize = self.MiniSectorSize #1 << i16(header, 32) - self.minisectorcutoff = self.MiniSectorCutoff # i32(header, 56) - - # check known streams for duplicate references (these are always in FAT, - # never in MiniFAT): - self._check_duplicate_stream(self.sectDirStart) - # check MiniFAT only if it is not empty: - if self.csectMiniFat: - self._check_duplicate_stream(self.MiniFatStart) - # check DIFAT only if it is not empty: - if self.csectDif: - self._check_duplicate_stream(self.sectDifStart) - - # Load file allocation tables - self.loadfat(header) - # Load directory. This sets both the direntries list (ordered by sid) - # and the root (ordered by hierarchy) members. - self.loaddirectory(self.sectDirStart)#i32(header, 48)) - self.ministream = None - self.minifatsect = self.MiniFatStart #i32(header, 60) - - def close(self): - """ - close the OLE file, to release the file object - """ - self.fp.close() - - def _check_duplicate_stream(self, first_sect, minifat=False): - """ - Checks if a stream has not been already referenced elsewhere. - This method should only be called once for each known stream, and only - if stream size is not null. - - :param first_sect: int, index of first sector of the stream in FAT - :param minifat: bool, if True, stream is located in the MiniFAT, else in the FAT - """ - if minifat: - debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect) - used_streams = self._used_streams_minifat - else: - debug('_check_duplicate_stream: sect=%d in FAT' % first_sect) - # some values can be safely ignored (not a real stream): - if first_sect in (DIFSECT, FATSECT, ENDOFCHAIN, FREESECT): - return - used_streams = self._used_streams_fat - #TODO: would it be more efficient using a dict or hash values, instead - # of a list of long ? - if first_sect in used_streams: - self.raise_defect(DEFECT_INCORRECT, 'Stream referenced twice') - else: - used_streams.append(first_sect) - - def dumpfat(self, fat, firstindex=0): - "Displays a part of FAT in human-readable form for debugging purpose" - # [PL] added only for debug - if not DEBUG_MODE: - return - # dictionary to convert special FAT values in human-readable strings - VPL = 8 # values per line (8+1 * 8+1 = 81) - fatnames = { - FREESECT: "..free..", - ENDOFCHAIN: "[ END. ]", - FATSECT: "FATSECT ", - DIFSECT: "DIFSECT " - } - nbsect = len(fat) - nlines = (nbsect+VPL-1)//VPL - print("index", end=" ") - for i in range(VPL): - print("%8X" % i, end=" ") - print() - for l in range(nlines): - index = l*VPL - print("%8X:" % (firstindex+index), end=" ") - for i in range(index, index+VPL): - if i >= nbsect: - break - sect = fat[i] - aux = sect & 0xFFFFFFFF # JYTHON-WORKAROUND - if aux in fatnames: - name = fatnames[aux] - else: - if sect == i+1: - name = " --->" - else: - name = "%8X" % sect - print(name, end=" ") - print() - - def dumpsect(self, sector, firstindex=0): - "Displays a sector in a human-readable form, for debugging purpose." - if not DEBUG_MODE: - return - VPL = 8 # number of values per line (8+1 * 8+1 = 81) - tab = array.array(UINT32, sector) - if sys.byteorder == 'big': - tab.byteswap() - nbsect = len(tab) - nlines = (nbsect+VPL-1)//VPL - print("index", end=" ") - for i in range(VPL): - print("%8X" % i, end=" ") - print() - for l in range(nlines): - index = l*VPL - print("%8X:" % (firstindex+index), end=" ") - for i in range(index, index+VPL): - if i >= nbsect: - break - sect = tab[i] - name = "%8X" % sect - print(name, end=" ") - print() - - def sect2array(self, sect): - """ - convert a sector to an array of 32 bits unsigned integers, - swapping bytes on big endian CPUs such as PowerPC (old Macs) - """ - a = array.array(UINT32, sect) - # if CPU is big endian, swap bytes: - if sys.byteorder == 'big': - a.byteswap() - return a - - def loadfat_sect(self, sect): - """ - Adds the indexes of the given sector to the FAT - - :param sect: string containing the first FAT sector, or array of long integers - :returns: index of last FAT sector. - """ - # a FAT sector is an array of ulong integers. - if isinstance(sect, array.array): - # if sect is already an array it is directly used - fat1 = sect - else: - # if it's a raw sector, it is parsed in an array - fat1 = self.sect2array(sect) - self.dumpsect(sect) - # The FAT is a sector chain starting at the first index of itself. - for isect in fat1: - isect = isect & 0xFFFFFFFF # JYTHON-WORKAROUND - debug("isect = %X" % isect) - if isect == ENDOFCHAIN or isect == FREESECT: - # the end of the sector chain has been reached - debug("found end of sector chain") - break - # read the FAT sector - s = self.getsect(isect) - # parse it as an array of 32 bits integers, and add it to the - # global FAT array - nextfat = self.sect2array(s) - self.fat = self.fat + nextfat - return isect - - def loadfat(self, header): - """ - Load the FAT table. - """ - # The 1st sector of the file contains sector numbers for the first 109 - # FAT sectors, right after the header which is 76 bytes long. - # (always 109, whatever the sector size: 512 bytes = 76+4*109) - # Additional sectors are described by DIF blocks - - sect = header[76:512] - debug("len(sect)=%d, so %d integers" % (len(sect), len(sect)//4)) - #fat = [] - # [PL] FAT is an array of 32 bits unsigned ints, it's more effective - # to use an array than a list in Python. - # It's initialized as empty first: - self.fat = array.array(UINT32) - self.loadfat_sect(sect) - #self.dumpfat(self.fat) -## for i in range(0, len(sect), 4): -## ix = i32(sect, i) -## # [PL] if ix == -2 or ix == -1: # ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: -## if ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: -## break -## s = self.getsect(ix) -## #fat = fat + [i32(s, i) for i in range(0, len(s), 4)] -## fat = fat + array.array(UINT32, s) - if self.csectDif != 0: - # [PL] There's a DIFAT because file is larger than 6.8MB - # some checks just in case: - if self.csectFat <= 109: - # there must be at least 109 blocks in header and the rest in - # DIFAT, so number of sectors must be >109. - self.raise_defect(DEFECT_INCORRECT, 'incorrect DIFAT, not enough sectors') - if self.sectDifStart >= self.nb_sect: - # initial DIFAT block index must be valid - self.raise_defect(DEFECT_FATAL, 'incorrect DIFAT, first index out of range') - debug("DIFAT analysis...") - # We compute the necessary number of DIFAT sectors : - # Number of pointers per DIFAT sector = (sectorsize/4)-1 - # (-1 because the last pointer is the next DIFAT sector number) - nb_difat_sectors = (self.sectorsize//4)-1 - # (if 512 bytes: each DIFAT sector = 127 pointers + 1 towards next DIFAT sector) - nb_difat = (self.csectFat-109 + nb_difat_sectors-1)//nb_difat_sectors - debug("nb_difat = %d" % nb_difat) - if self.csectDif != nb_difat: - raise IOError('incorrect DIFAT') - isect_difat = self.sectDifStart - for i in iterrange(nb_difat): - debug("DIFAT block %d, sector %X" % (i, isect_difat)) - #TODO: check if corresponding FAT SID = DIFSECT - sector_difat = self.getsect(isect_difat) - difat = self.sect2array(sector_difat) - self.dumpsect(sector_difat) - self.loadfat_sect(difat[:nb_difat_sectors]) - # last DIFAT pointer is next DIFAT sector: - isect_difat = difat[nb_difat_sectors] - debug("next DIFAT sector: %X" % isect_difat) - # checks: - if isect_difat not in [ENDOFCHAIN, FREESECT]: - # last DIFAT pointer value must be ENDOFCHAIN or FREESECT - raise IOError('incorrect end of DIFAT') -## if len(self.fat) != self.csectFat: -## # FAT should contain csectFat blocks -## print("FAT length: %d instead of %d" % (len(self.fat), self.csectFat)) -## raise IOError('incorrect DIFAT') - # since FAT is read from fixed-size sectors, it may contain more values - # than the actual number of sectors in the file. - # Keep only the relevant sector indexes: - if len(self.fat) > self.nb_sect: - debug('len(fat)=%d, shrunk to nb_sect=%d' % (len(self.fat), self.nb_sect)) - self.fat = self.fat[:self.nb_sect] - debug('\nFAT:') - self.dumpfat(self.fat) - - def loadminifat(self): - """ - Load the MiniFAT table. - """ - # MiniFAT is stored in a standard sub-stream, pointed to by a header - # field. - # NOTE: there are two sizes to take into account for this stream: - # 1) Stream size is calculated according to the number of sectors - # declared in the OLE header. This allocated stream may be more than - # needed to store the actual sector indexes. - # (self.csectMiniFat is the number of sectors of size self.SectorSize) - stream_size = self.csectMiniFat * self.SectorSize - # 2) Actually used size is calculated by dividing the MiniStream size - # (given by root entry size) by the size of mini sectors, *4 for - # 32 bits indexes: - nb_minisectors = (self.root.size + self.MiniSectorSize-1) // self.MiniSectorSize - used_size = nb_minisectors * 4 - debug('loadminifat(): minifatsect=%d, nb FAT sectors=%d, used_size=%d, stream_size=%d, nb MiniSectors=%d' % - (self.minifatsect, self.csectMiniFat, used_size, stream_size, nb_minisectors)) - if used_size > stream_size: - # This is not really a problem, but may indicate a wrong implementation: - self.raise_defect(DEFECT_INCORRECT, 'OLE MiniStream is larger than MiniFAT') - # In any case, first read stream_size: - s = self._open(self.minifatsect, stream_size, force_FAT=True).read() - # [PL] Old code replaced by an array: - # self.minifat = [i32(s, i) for i in range(0, len(s), 4)] - self.minifat = self.sect2array(s) - # Then shrink the array to used size, to avoid indexes out of MiniStream: - debug('MiniFAT shrunk from %d to %d sectors' % (len(self.minifat), nb_minisectors)) - self.minifat = self.minifat[:nb_minisectors] - debug('loadminifat(): len=%d' % len(self.minifat)) - debug('\nMiniFAT:') - self.dumpfat(self.minifat) - - def getsect(self, sect): - """ - Read given sector from file on disk. - - :param sect: int, sector index - :returns: a string containing the sector data. - """ - # From [MS-CFB]: A sector number can be converted into a byte offset - # into the file by using the following formula: - # (sector number + 1) x Sector Size. - # This implies that sector #0 of the file begins at byte offset Sector - # Size, not at 0. - - # [PL] the original code in PIL was wrong when sectors are 4KB instead of - # 512 bytes: - # self.fp.seek(512 + self.sectorsize * sect) - # [PL]: added safety checks: - # print("getsect(%X)" % sect) - try: - self.fp.seek(self.sectorsize * (sect+1)) - except: - debug('getsect(): sect=%X, seek=%d, filesize=%d' % - (sect, self.sectorsize*(sect+1), self._filesize)) - self.raise_defect(DEFECT_FATAL, 'OLE sector index out of range') - sector = self.fp.read(self.sectorsize) - if len(sector) != self.sectorsize: - debug('getsect(): sect=%X, read=%d, sectorsize=%d' % - (sect, len(sector), self.sectorsize)) - self.raise_defect(DEFECT_FATAL, 'incomplete OLE sector') - return sector - - def write_sect(self, sect, data, padding=b'\x00'): - """ - Write given sector to file on disk. - - :param sect: int, sector index - :param data: bytes, sector data - :param padding: single byte, padding character if data < sector size - """ - if not isinstance(data, bytes): - raise TypeError("write_sect: data must be a bytes string") - if not isinstance(padding, bytes) or len(padding) != 1: - raise TypeError("write_sect: padding must be a bytes string of 1 char") - #TODO: we could allow padding=None for no padding at all - try: - self.fp.seek(self.sectorsize * (sect+1)) - except: - debug('write_sect(): sect=%X, seek=%d, filesize=%d' % - (sect, self.sectorsize*(sect+1), self._filesize)) - self.raise_defect(DEFECT_FATAL, 'OLE sector index out of range') - if len(data) < self.sectorsize: - # add padding - data += padding * (self.sectorsize - len(data)) - elif len(data) < self.sectorsize: - raise ValueError("Data is larger than sector size") - self.fp.write(data) - - def loaddirectory(self, sect): - """ - Load the directory. - - :param sect: sector index of directory stream. - """ - # The directory is stored in a standard - # substream, independent of its size. - - # open directory stream as a read-only file: - # (stream size is not known in advance) - self.directory_fp = self._open(sect) - - # [PL] to detect malformed documents and avoid DoS attacks, the maximum - # number of directory entries can be calculated: - max_entries = self.directory_fp.size // 128 - debug('loaddirectory: size=%d, max_entries=%d' % - (self.directory_fp.size, max_entries)) - - # Create list of directory entries - # self.direntries = [] - # We start with a list of "None" object - self.direntries = [None] * max_entries -## for sid in iterrange(max_entries): -## entry = fp.read(128) -## if not entry: -## break -## self.direntries.append(_OleDirectoryEntry(entry, sid, self)) - # load root entry: - root_entry = self._load_direntry(0) - # Root entry is the first entry: - self.root = self.direntries[0] - # read and build all storage trees, starting from the root: - self.root.build_storage_tree() - - def _load_direntry(self, sid): - """ - Load a directory entry from the directory. - This method should only be called once for each storage/stream when - loading the directory. - - :param sid: index of storage/stream in the directory. - :returns: a _OleDirectoryEntry object - - :exception IOError: if the entry has always been referenced. - """ - # check if SID is OK: - if sid < 0 or sid >= len(self.direntries): - self.raise_defect(DEFECT_FATAL, "OLE directory index out of range") - # check if entry was already referenced: - if self.direntries[sid] is not None: - self.raise_defect(DEFECT_INCORRECT, - "double reference for OLE stream/storage") - # if exception not raised, return the object - return self.direntries[sid] - self.directory_fp.seek(sid * 128) - entry = self.directory_fp.read(128) - self.direntries[sid] = _OleDirectoryEntry(entry, sid, self) - return self.direntries[sid] - - def dumpdirectory(self): - """ - Dump directory (for debugging only) - """ - self.root.dump() - - def _open(self, start, size = 0x7FFFFFFF, force_FAT=False): - """ - Open a stream, either in FAT or MiniFAT according to its size. - (openstream helper) - - :param start: index of first sector - :param size: size of stream (or nothing if size is unknown) - :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT - according to size. If True, it will always be opened in FAT. - """ - debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' % - (start, size, str(force_FAT))) - # stream size is compared to the MiniSectorCutoff threshold: - if size < self.minisectorcutoff and not force_FAT: - # ministream object - if not self.ministream: - # load MiniFAT if it wasn't already done: - self.loadminifat() - # The first sector index of the miniFAT stream is stored in the - # root directory entry: - size_ministream = self.root.size - debug('Opening MiniStream: sect=%d, size=%d' % - (self.root.isectStart, size_ministream)) - self.ministream = self._open(self.root.isectStart, - size_ministream, force_FAT=True) - return _OleStream(fp=self.ministream, sect=start, size=size, - offset=0, sectorsize=self.minisectorsize, - fat=self.minifat, filesize=self.ministream.size) - else: - # standard stream - return _OleStream(fp=self.fp, sect=start, size=size, - offset=self.sectorsize, - sectorsize=self.sectorsize, fat=self.fat, - filesize=self._filesize) - - def _list(self, files, prefix, node, streams=True, storages=False): - """ - listdir helper - - :param files: list of files to fill in - :param prefix: current location in storage tree (list of names) - :param node: current node (_OleDirectoryEntry object) - :param streams: bool, include streams if True (True by default) - new in v0.26 - :param storages: bool, include storages if True (False by default) - new in v0.26 - (note: the root storage is never included) - """ - prefix = prefix + [node.name] - for entry in node.kids: - if entry.entry_type == STGTY_STORAGE: - # this is a storage - if storages: - # add it to the list - files.append(prefix[1:] + [entry.name]) - # check its kids - self._list(files, prefix, entry, streams, storages) - elif entry.entry_type == STGTY_STREAM: - # this is a stream - if streams: - # add it to the list - files.append(prefix[1:] + [entry.name]) - else: - self.raise_defect(DEFECT_INCORRECT, 'The directory tree contains an entry which is not a stream nor a storage.') - - def listdir(self, streams=True, storages=False): - """ - Return a list of streams and/or storages stored in this file - - :param streams: bool, include streams if True (True by default) - new in v0.26 - :param storages: bool, include storages if True (False by default) - new in v0.26 - (note: the root storage is never included) - :returns: list of stream and/or storage paths - """ - files = [] - self._list(files, [], self.root, streams, storages) - return files - - def _find(self, filename): - """ - Returns directory entry of given filename. (openstream helper) - Note: this method is case-insensitive. - - :param filename: path of stream in storage tree (except root entry), either: - - - a string using Unix path syntax, for example: - 'storage_1/storage_1.2/stream' - - or a list of storage filenames, path to the desired stream/storage. - Example: ['storage_1', 'storage_1.2', 'stream'] - - :returns: sid of requested filename - :exception IOError: if file not found - """ - - # if filename is a string instead of a list, split it on slashes to - # convert to a list: - if isinstance(filename, basestring): - filename = filename.split('/') - # walk across storage tree, following given path: - node = self.root - for name in filename: - for kid in node.kids: - if kid.name.lower() == name.lower(): - break - else: - raise IOError("file not found") - node = kid - return node.sid - - def openstream(self, filename): - """ - Open a stream as a read-only file object (BytesIO). - Note: filename is case-insensitive. - - :param filename: path of stream in storage tree (except root entry), either: - - - a string using Unix path syntax, for example: - 'storage_1/storage_1.2/stream' - - or a list of storage filenames, path to the desired stream/storage. - Example: ['storage_1', 'storage_1.2', 'stream'] - - :returns: file object (read-only) - :exception IOError: if filename not found, or if this is not a stream. - """ - sid = self._find(filename) - entry = self.direntries[sid] - if entry.entry_type != STGTY_STREAM: - raise IOError("this file is not a stream") - return self._open(entry.isectStart, entry.size) - - def write_stream(self, stream_name, data): - """ - Write a stream to disk. For now, it is only possible to replace an - existing stream by data of the same size. - - :param stream_name: path of stream in storage tree (except root entry), either: - - - a string using Unix path syntax, for example: - 'storage_1/storage_1.2/stream' - - or a list of storage filenames, path to the desired stream/storage. - Example: ['storage_1', 'storage_1.2', 'stream'] - - :param data: bytes, data to be written, must be the same size as the original - stream. - """ - if not isinstance(data, bytes): - raise TypeError("write_stream: data must be a bytes string") - sid = self._find(stream_name) - entry = self.direntries[sid] - if entry.entry_type != STGTY_STREAM: - raise IOError("this is not a stream") - size = entry.size - if size != len(data): - raise ValueError("write_stream: data must be the same size as the existing stream") - if size < self.minisectorcutoff: - raise NotImplementedError("Writing a stream in MiniFAT is not implemented yet") - sect = entry.isectStart - # number of sectors to write - nb_sectors = (size + (self.sectorsize-1)) // self.sectorsize - debug('nb_sectors = %d' % nb_sectors) - for i in range(nb_sectors): - # try: - # self.fp.seek(offset + self.sectorsize * sect) - # except: - # debug('sect=%d, seek=%d' % - # (sect, offset+self.sectorsize*sect)) - # raise IOError('OLE sector index out of range') - # extract one sector from data, the last one being smaller: - if i < (nb_sectors-1): - data_sector = data[i*self.sectorsize:(i+1)*self.sectorsize] - #TODO: comment this if it works - assert(len(data_sector) == self.sectorsize) - else: - data_sector = data[i*self.sectorsize:] - # TODO: comment this if it works - debug('write_stream: size=%d sectorsize=%d data_sector=%d size%%sectorsize=%d' - % (size, self.sectorsize, len(data_sector), size % self.sectorsize)) - assert(len(data_sector) % self.sectorsize == size % self.sectorsize) - self.write_sect(sect, data_sector) -# self.fp.write(data_sector) - # jump to next sector in the FAT: - try: - sect = self.fat[sect] - except IndexError: - # [PL] if pointer is out of the FAT an exception is raised - raise IOError('incorrect OLE FAT, sector index out of range') - # [PL] Last sector should be a "end of chain" marker: - if sect != ENDOFCHAIN: - raise IOError('incorrect last sector index in OLE stream') - - def get_type(self, filename): - """ - Test if given filename exists as a stream or a storage in the OLE - container, and return its type. - - :param filename: path of stream in storage tree. (see openstream for syntax) - :returns: False if object does not exist, its entry type (>0) otherwise: - - - STGTY_STREAM: a stream - - STGTY_STORAGE: a storage - - STGTY_ROOT: the root entry - """ - try: - sid = self._find(filename) - entry = self.direntries[sid] - return entry.entry_type - except: - return False - - def getmtime(self, filename): - """ - Return modification time of a stream/storage. - - :param filename: path of stream/storage in storage tree. (see openstream for - syntax) - :returns: None if modification time is null, a python datetime object - otherwise (UTC timezone) - - new in version 0.26 - """ - sid = self._find(filename) - entry = self.direntries[sid] - return entry.getmtime() - - def getctime(self, filename): - """ - Return creation time of a stream/storage. - - :param filename: path of stream/storage in storage tree. (see openstream for - syntax) - :returns: None if creation time is null, a python datetime object - otherwise (UTC timezone) - - new in version 0.26 - """ - sid = self._find(filename) - entry = self.direntries[sid] - return entry.getctime() - - def exists(self, filename): - """ - Test if given filename exists as a stream or a storage in the OLE - container. - Note: filename is case-insensitive. - - :param filename: path of stream in storage tree. (see openstream for syntax) - :returns: True if object exist, else False. - """ - try: - sid = self._find(filename) - return True - except: - return False - - def get_size(self, filename): - """ - Return size of a stream in the OLE container, in bytes. - - :param filename: path of stream in storage tree (see openstream for syntax) - :returns: size in bytes (long integer) - :exception IOError: if file not found - :exception TypeError: if this is not a stream. - """ - sid = self._find(filename) - entry = self.direntries[sid] - if entry.entry_type != STGTY_STREAM: - #TODO: Should it return zero instead of raising an exception ? - raise TypeError('object is not an OLE stream') - return entry.size - - def get_rootentry_name(self): - """ - Return root entry name. Should usually be 'Root Entry' or 'R' in most - implementations. - """ - return self.root.name - - def getproperties(self, filename, convert_time=False, no_conversion=None): - """ - Return properties described in substream. - - :param filename: path of stream in storage tree (see openstream for syntax) - :param convert_time: bool, if True timestamps will be converted to Python datetime - :param no_conversion: None or list of int, timestamps not to be converted - (for example total editing time is not a real timestamp) - - :returns: a dictionary of values indexed by id (integer) - """ - # REFERENCE: [MS-OLEPS] https://msdn.microsoft.com/en-us/library/dd942421.aspx - # make sure no_conversion is a list, just to simplify code below: - if no_conversion is None: - no_conversion = [] - # stream path as a string to report exceptions: - streampath = filename - if not isinstance(streampath, str): - streampath = '/'.join(streampath) - - fp = self.openstream(filename) - - data = {} - - try: - # header - s = fp.read(28) - clsid = _clsid(s[8:24]) - - # format id - s = fp.read(20) - fmtid = _clsid(s[:16]) - fp.seek(i32(s, 16)) - - # get section - s = b"****" + fp.read(i32(fp.read(4))-4) - # number of properties: - num_props = i32(s, 4) - except BaseException as exc: - # catch exception while parsing property header, and only raise - # a DEFECT_INCORRECT then return an empty dict, because this is not - # a fatal error when parsing the whole file - msg = 'Error while parsing properties header in stream %s: %s' % ( - repr(streampath), exc) - self.raise_defect(DEFECT_INCORRECT, msg, type(exc)) - return data - - for i in range(num_props): - try: - id = 0 # just in case of an exception - id = i32(s, 8+i*8) - offset = i32(s, 12+i*8) - type = i32(s, offset) - - debug('property id=%d: type=%d offset=%X' % (id, type, offset)) - - # test for common types first (should perhaps use - # a dictionary instead?) - - if type == VT_I2: # 16-bit signed integer - value = i16(s, offset+4) - if value >= 32768: - value = value - 65536 - elif type == VT_UI2: # 2-byte unsigned integer - value = i16(s, offset+4) - elif type in (VT_I4, VT_INT, VT_ERROR): - # VT_I4: 32-bit signed integer - # VT_ERROR: HRESULT, similar to 32-bit signed integer, - # see http://msdn.microsoft.com/en-us/library/cc230330.aspx - value = i32(s, offset+4) - elif type in (VT_UI4, VT_UINT): # 4-byte unsigned integer - value = i32(s, offset+4) # FIXME - elif type in (VT_BSTR, VT_LPSTR): - # CodePageString, see http://msdn.microsoft.com/en-us/library/dd942354.aspx - # size is a 32 bits integer, including the null terminator, and - # possibly trailing or embedded null chars - #TODO: if codepage is unicode, the string should be converted as such - count = i32(s, offset+4) - value = s[offset+8:offset+8+count-1] - # remove all null chars: - value = value.replace(b'\x00', b'') - elif type == VT_BLOB: - # binary large object (BLOB) - # see http://msdn.microsoft.com/en-us/library/dd942282.aspx - count = i32(s, offset+4) - value = s[offset+8:offset+8+count] - elif type == VT_LPWSTR: - # UnicodeString - # see http://msdn.microsoft.com/en-us/library/dd942313.aspx - # "the string should NOT contain embedded or additional trailing - # null characters." - count = i32(s, offset+4) - value = self._decode_utf16_str(s[offset+8:offset+8+count*2]) - elif type == VT_FILETIME: - value = long(i32(s, offset+4)) + (long(i32(s, offset+8)) << 32) - # FILETIME is a 64-bit int: "number of 100ns periods - # since Jan 1,1601". - if convert_time and id not in no_conversion: - debug('Converting property #%d to python datetime, value=%d=%fs' - % (id, value, float(value) / 10000000)) - # convert FILETIME to Python datetime.datetime - # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/ - _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0) - debug('timedelta days=%d' % (value//(10*1000000*3600*24))) - value = _FILETIME_null_date + datetime.timedelta(microseconds=value//10) - else: - # legacy code kept for backward compatibility: returns a - # number of seconds since Jan 1,1601 - value = value // 10000000 # seconds - elif type == VT_UI1: # 1-byte unsigned integer - value = i8(s[offset+4]) - elif type == VT_CLSID: - value = _clsid(s[offset+4:offset+20]) - elif type == VT_CF: - # PropertyIdentifier or ClipboardData?? - # see http://msdn.microsoft.com/en-us/library/dd941945.aspx - count = i32(s, offset+4) - value = s[offset+8:offset+8+count] - elif type == VT_BOOL: - # VARIANT_BOOL, 16 bits bool, 0x0000=Fals, 0xFFFF=True - # see http://msdn.microsoft.com/en-us/library/cc237864.aspx - value = bool(i16(s, offset+4)) - else: - value = None # everything else yields "None" - debug('property id=%d: type=%d not implemented in parser yet' % (id, type)) - - # missing: VT_EMPTY, VT_NULL, VT_R4, VT_R8, VT_CY, VT_DATE, - # VT_DECIMAL, VT_I1, VT_I8, VT_UI8, - # see http://msdn.microsoft.com/en-us/library/dd942033.aspx - - # FIXME: add support for VT_VECTOR - # VT_VECTOR is a 32 uint giving the number of items, followed by - # the items in sequence. The VT_VECTOR value is combined with the - # type of items, e.g. VT_VECTOR|VT_BSTR - # see http://msdn.microsoft.com/en-us/library/dd942011.aspx - - # print("%08x" % id, repr(value), end=" ") - # print("(%s)" % VT[i32(s, offset) & 0xFFF]) - - data[id] = value - except BaseException as exc: - # catch exception while parsing each property, and only raise - # a DEFECT_INCORRECT, because parsing can go on - msg = 'Error while parsing property id %d in stream %s: %s' % ( - id, repr(streampath), exc) - self.raise_defect(DEFECT_INCORRECT, msg, type(exc)) - - return data - - def get_metadata(self): - """ - Parse standard properties streams, return an OleMetadata object - containing all the available metadata. - (also stored in the metadata attribute of the OleFileIO object) - - new in version 0.25 - """ - self.metadata = OleMetadata() - self.metadata.parse_properties(self) - return self.metadata - -# -# -------------------------------------------------------------------- -# This script can be used to dump the directory of any OLE2 structured -# storage file. - -if __name__ == "__main__": - - # [PL] display quick usage info if launched from command-line - if len(sys.argv) <= 1: - print('olefile version %s %s - %s' % (__version__, __date__, __author__)) - print( -""" -Launched from the command line, this script parses OLE files and prints info. - -Usage: olefile.py [-d] [-c] [file2 ...] - -Options: --d : debug mode (displays a lot of debug information, for developers only) --c : check all streams (for debugging purposes) - -For more information, see http://www.decalage.info/olefile -""") - sys.exit() - - check_streams = False - for filename in sys.argv[1:]: - # try: - # OPTIONS: - if filename == '-d': - # option to switch debug mode on: - set_debug_mode(True) - continue - if filename == '-c': - # option to switch check streams mode on: - check_streams = True - continue - - ole = OleFileIO(filename)#, raise_defects=DEFECT_INCORRECT) - print("-" * 68) - print(filename) - print("-" * 68) - ole.dumpdirectory() - for streamname in ole.listdir(): - if streamname[-1][0] == "\005": - print(streamname, ": properties") - props = ole.getproperties(streamname, convert_time=True) - props = sorted(props.items()) - for k, v in props: - # [PL]: avoid to display too large or binary values: - if isinstance(v, (basestring, bytes)): - if len(v) > 50: - v = v[:50] - if isinstance(v, bytes): - # quick and dirty binary check: - for c in (1, 2, 3, 4, 5, 6, 7, 11, 12, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31): - if c in bytearray(v): - v = '(binary data)' - break - print(" ", k, v) - - if check_streams: - # Read all streams to check if there are errors: - print('\nChecking streams...') - for streamname in ole.listdir(): - # print name using repr() to convert binary chars to \xNN: - print('-', repr('/'.join(streamname)), '-', end=' ') - st_type = ole.get_type(streamname) - if st_type == STGTY_STREAM: - print('size %d' % ole.get_size(streamname)) - # just try to read stream in memory: - ole.openstream(streamname) - else: - print('NOT a stream : type=%d' % st_type) - print() - -# for streamname in ole.listdir(): -# # print name using repr() to convert binary chars to \xNN: -# print('-', repr('/'.join(streamname)),'-', end=' ') -# print(ole.getmtime(streamname)) -# print() - - print('Modification/Creation times of all directory entries:') - for entry in ole.direntries: - if entry is not None: - print('- %s: mtime=%s ctime=%s' % (entry.name, - entry.getmtime(), entry.getctime())) - print() - - # parse and display metadata: - meta = ole.get_metadata() - meta.dump() - print() - # [PL] Test a few new methods: - root = ole.get_rootentry_name() - print('Root entry name: "%s"' % root) - if ole.exists('worddocument'): - print("This is a Word document.") - print("type of stream 'WordDocument':", ole.get_type('worddocument')) - print("size :", ole.get_size('worddocument')) - if ole.exists('macros/vba'): - print("This document may contain VBA macros.") - - # print parsing issues: - print('\nNon-fatal issues raised during parsing:') - if ole.parsing_issues: - for exctype, msg in ole.parsing_issues: - print('- %s: %s' % (exctype.__name__, msg)) - else: - print('None') -## except IOError as v: -## print("***", "cannot read", file, "-", v) - -# this code was developed while listening to The Wedding Present "Sea Monsters" +sys.modules[__name__] = olefile diff --git a/Tests/test_olefileio.py b/Tests/test_olefileio.py deleted file mode 100644 index cb0496db4..000000000 --- a/Tests/test_olefileio.py +++ /dev/null @@ -1,147 +0,0 @@ -from helper import unittest, PillowTestCase - -import datetime - -import PIL.OleFileIO as OleFileIO - - -class TestOleFileIo(PillowTestCase): - - def test_isOleFile(self): - ole_file = "Tests/images/test-ole-file.doc" - - self.assertTrue(OleFileIO.isOleFile(ole_file)) - with open(ole_file, 'rb') as fp: - self.assertTrue(OleFileIO.isOleFile(fp)) - self.assertTrue(OleFileIO.isOleFile(fp.read())) - - non_ole_file = "Tests/images/flower.jpg" - - self.assertFalse(OleFileIO.isOleFile(non_ole_file)) - with open(non_ole_file, 'rb') as fp: - self.assertFalse(OleFileIO.isOleFile(fp)) - self.assertFalse(OleFileIO.isOleFile(fp.read())) - - def test_exists_worddocument(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - exists = ole.exists('worddocument') - - # Assert - self.assertTrue(exists) - ole.close() - - def test_exists_no_vba_macros(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - exists = ole.exists('macros/vba') - - # Assert - self.assertFalse(exists) - ole.close() - - def test_get_type(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - entry_type = ole.get_type('worddocument') - - # Assert - self.assertEqual(entry_type, OleFileIO.STGTY_STREAM) - ole.close() - - def test_get_size(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - size = ole.get_size('worddocument') - - # Assert - self.assertGreater(size, 0) - ole.close() - - def test_get_rootentry_name(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - root = ole.get_rootentry_name() - - # Assert - self.assertEqual(root, "Root Entry") - ole.close() - - def test_meta(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - meta = ole.get_metadata() - - # Assert - self.assertEqual(meta.author, b"Laurence Ipsum") - self.assertEqual(meta.num_pages, 1) - ole.close() - - def test_gettimes(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - root_entry = ole.direntries[0] - - # Act - ctime = root_entry.getctime() - mtime = root_entry.getmtime() - - # Assert - self.assertIsNone(ctime) - self.assertIsInstance(mtime, datetime.datetime) - self.assertEqual(mtime.year, 2014) - ole.close() - - def test_listdir(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - - # Act - dirlist = ole.listdir() - - # Assert - self.assertIn(['WordDocument'], dirlist) - ole.close() - - def test_debug(self): - # Arrange - ole_file = "Tests/images/test-ole-file.doc" - ole = OleFileIO.OleFileIO(ole_file) - meta = ole.get_metadata() - - # Act - OleFileIO.set_debug_mode(True) - ole.dumpdirectory() - meta.dump() - - OleFileIO.set_debug_mode(False) - ole.dumpdirectory() - meta.dump() - - # Assert - # No assert, just check they run ok - ole.close() - - -if __name__ == '__main__': - unittest.main() diff --git a/docs/reference/OleFileIO.rst b/docs/reference/OleFileIO.rst deleted file mode 100644 index 791cb5ff3..000000000 --- a/docs/reference/OleFileIO.rst +++ /dev/null @@ -1,364 +0,0 @@ -.. py:module:: PIL.OleFileIO -.. py:currentmodule:: PIL.OleFileIO - -:py:mod:`OleFileIO` Module -=========================== - -The :py:mod:`OleFileIO` module reads Microsoft OLE2 files (also called -Structured Storage or Microsoft Compound Document File Format), such -as Microsoft Office documents, Image Composer and FlashPix files, and -Outlook messages. - -This module is the `OleFileIO\_PL`_ project by Philippe Lagadec, v0.42, -merged back into Pillow. - -.. _OleFileIO\_PL: http://www.decalage.info/python/olefileio - -How to use this module ----------------------- - -For more information, see also the file **PIL/OleFileIO.py**, sample -code at the end of the module itself, and docstrings within the code. - -About the structure of OLE files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -An OLE file can be seen as a mini file system or a Zip archive: It -contains **streams** of data that look like files embedded within the -OLE file. Each stream has a name. For example, the main stream of a MS -Word document containing its text is named "WordDocument". - -An OLE file can also contain **storages**. A storage is a folder that -contains streams or other storages. For example, a MS Word document with -VBA macros has a storage called "Macros". - -Special streams can contain **properties**. A property is a specific -value that can be used to store information such as the metadata of a -document (title, author, creation date, etc). Property stream names -usually start with the character '05'. - -For example, a typical MS Word document may look like this: - -:: - - \x05DocumentSummaryInformation (stream) - \x05SummaryInformation (stream) - WordDocument (stream) - Macros (storage) - PROJECT (stream) - PROJECTwm (stream) - VBA (storage) - Module1 (stream) - ThisDocument (stream) - _VBA_PROJECT (stream) - dir (stream) - ObjectPool (storage) - -Test if a file is an OLE container -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use isOleFile to check if the first bytes of the file contain the Magic -for OLE files, before opening it. isOleFile returns True if it is an OLE -file, False otherwise. - -.. code-block:: python - - assert OleFileIO.isOleFile('myfile.doc') - -Open an OLE file from disk -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Create an OleFileIO object with the file path as parameter: - -.. code-block:: python - - ole = OleFileIO.OleFileIO('myfile.doc') - -Open an OLE file from a file-like object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is useful if the file is not on disk, e.g. already stored in a -string or as a file-like object. - -.. code-block:: python - - ole = OleFileIO.OleFileIO(f) - -For example the code below reads a file into a string, then uses BytesIO -to turn it into a file-like object. - -.. code-block:: python - - data = open('myfile.doc', 'rb').read() - f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x - ole = OleFileIO.OleFileIO(f) - -How to handle malformed OLE files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, the parser is configured to be as robust and permissive as -possible, allowing to parse most malformed OLE files. Only fatal errors -will raise an exception. It is possible to tell the parser to be more -strict in order to raise exceptions for files that do not fully conform -to the OLE specifications, using the raise\_defect option: - -.. code-block:: python - - ole = OleFileIO.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT) - -When the parsing is done, the list of non-fatal issues detected is -available as a list in the parsing\_issues attribute of the OleFileIO -object: - -.. code-block:: python - - print('Non-fatal issues raised during parsing:') - if ole.parsing_issues: - for exctype, msg in ole.parsing_issues: - print('- %s: %s' % (exctype.__name__, msg)) - else: - print('None') - -Syntax for stream and storage path -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Two different syntaxes are allowed for methods that need or return the -path of streams and storages: - -1) Either a **list of strings** including all the storages from the root - up to the stream/storage name. For example a stream called - "WordDocument" at the root will have ['WordDocument'] as full path. A - stream called "ThisDocument" located in the storage "Macros/VBA" will - be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax - from PIL. While hard to read and not very convenient, this syntax - works in all cases. - -2) Or a **single string with slashes** to separate storage and stream - names (similar to the Unix path syntax). The previous examples would - be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is - easier, but may fail if a stream or storage name contains a slash. - -Both are case-insensitive. - -Switching between the two is easy: - -.. code-block:: python - - slash_path = '/'.join(list_path) - list_path = slash_path.split('/') - -Get the list of streams -~~~~~~~~~~~~~~~~~~~~~~~ - -listdir() returns a list of all the streams contained in the OLE file, -including those stored in storages. Each stream is listed itself as a -list, as described above. - -.. code-block:: python - - print(ole.listdir()) - -Sample result: - -.. code-block:: python - - [['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation'] - , ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA', - 'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT'] - , ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']] - -As an option it is possible to choose if storages should also be listed, -with or without streams: - -.. code-block:: python - - ole.listdir (streams=False, storages=True) - -Test if known streams/storages exist: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -exists(path) checks if a given stream or storage exists in the OLE file. - -.. code-block:: python - - if ole.exists('worddocument'): - print("This is a Word document.") - if ole.exists('macros/vba'): - print("This document seems to contain VBA macros.") - -Read data from a stream -~~~~~~~~~~~~~~~~~~~~~~~ - -openstream(path) opens a stream as a file-like object. - -The following example extracts the "Pictures" stream from a PPT file: - -.. code-block:: python - - pics = ole.openstream('Pictures') - data = pics.read() - - -Get information about a stream/storage -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Several methods can provide the size, type and timestamps of a given -stream/storage: - -get\_size(path) returns the size of a stream in bytes: - -.. code-block:: python - - s = ole.get_size('WordDocument') - -get\_type(path) returns the type of a stream/storage, as one of the -following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a -storage, STGTY\_ROOT for the root entry, and False for a non existing -path. - -.. code-block:: python - - t = ole.get_type('WordDocument') - -get\_ctime(path) and get\_mtime(path) return the creation and -modification timestamps of a stream/storage, as a Python datetime object -with UTC timezone. Please note that these timestamps are only present if -the application that created the OLE file explicitly stored them, which -is rarely the case. When not present, these methods return None. - -.. code-block:: python - - c = ole.get_ctime('WordDocument') - m = ole.get_mtime('WordDocument') - -The root storage is a special case: You can get its creation and -modification timestamps using the OleFileIO.root attribute: - -.. code-block:: python - - c = ole.root.getctime() - m = ole.root.getmtime() - -Extract metadata -~~~~~~~~~~~~~~~~ - -get\_metadata() will check if standard property streams exist, parse all -the properties they contain, and return an OleMetadata object with the -found properties as attributes. - -.. code-block:: python - - meta = ole.get_metadata() - print('Author:', meta.author) - print('Title:', meta.title) - print('Creation date:', meta.create_time) - # print all metadata: - meta.dump() - -Available attributes include: - -:: - - codepage, title, subject, author, keywords, comments, template, - last_saved_by, revision_number, total_edit_time, last_printed, create_time, - last_saved_time, num_pages, num_words, num_chars, thumbnail, - creating_application, security, codepage_doc, category, presentation_target, - bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips, - scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty, - chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed, - version, dig_sig, content_type, content_status, language, doc_version - -See the source code of the OleMetadata class for more information. - -Parse a property stream -~~~~~~~~~~~~~~~~~~~~~~~ - -get\_properties(path) can be used to parse any property stream that is -not handled by get\_metadata. It returns a dictionary indexed by -integers. Each integer is the index of the property, pointing to its -value. For example in the standard property stream -'05SummaryInformation', the document title is property #2, and the -subject is #3. - -.. code-block:: python - - p = ole.getproperties('specialprops') - -By default as in the original PIL version, timestamp properties are -converted into a number of seconds since Jan 1,1601. With the option -convert\_time, you can obtain more convenient Python datetime objects -(UTC timezone). If some time properties should not be converted (such as -total editing time in '05SummaryInformation'), the list of indexes can -be passed as no\_conversion: - -.. code-block:: python - - p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10]) - -Close the OLE file -~~~~~~~~~~~~~~~~~~ - -Unless your application is a simple script that terminates after -processing an OLE file, do not forget to close each OleFileIO object -after parsing to close the file on disk. - -.. code-block:: python - - ole.close() - -Use OleFileIO as a script -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -OleFileIO can also be used as a script from the command-line to -display the structure of an OLE file and its metadata, for example: - -:: - - PIL/OleFileIO.py myfile.doc - -You can use the option -c to check that all streams can be read fully, -and -d to generate very verbose debugging information. - -How to contribute ------------------ - -The code is available in `a Mercurial repository on -bitbucket `_. You may use -it to submit enhancements or to report any issue. - -If you would like to help us improve this module, or simply provide -feedback, please `contact me `_. You can -help in many ways: - -- test this module on different platforms / Python versions -- find and report bugs -- improve documentation, code samples, docstrings -- write unittest test cases -- provide tricky malformed files - -How to report bugs ------------------- - -To report a bug, for example a normal file which is not parsed -correctly, please use the `issue reporting -page `_, -or if you prefer to do it privately, use this `contact -form `_. Please provide all the -information about the context and how to reproduce the bug. - -If possible please join the debugging output of OleFileIO. For this, -launch the following command : - -:: - - PIL/OleFileIO.py -d -c file >debug.txt - - -Classes and Methods -------------------- - -.. automodule:: PIL.OleFileIO - :members: - :undoc-members: - :show-inheritance: - :noindex: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 555bd2a57..940b4b9b6 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -27,7 +27,6 @@ Reference ImageWin ExifTags TiffTags - OleFileIO PSDraw PixelAccess PyAccess diff --git a/setup.py b/setup.py index 808ec728a..d49a1ec59 100644 --- a/setup.py +++ b/setup.py @@ -770,6 +770,7 @@ try: include_package_data=True, packages=find_packages(), scripts=glob.glob("Scripts/*.py"), + install_requires=['olefile'], test_suite='nose.collector', keywords=["Imaging", ], license='Standard PIL License', From 5773fd8396a74ca86e713225df2bc483940c76ff Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 23 Nov 2016 14:41:43 +0300 Subject: [PATCH 101/267] =?UTF-8?q?=5Fmakeself=20=E2=86=92=20=5Fnew?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 368186f81..4fd2dcfc7 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -997,7 +997,7 @@ class Image(object): "only RGB or L mode images can be quantized to a palette" ) im = self.im.convert("P", 1, palette.im) - return self._makeself(im) + return self._new(im) return self._new(self.im.quantize(colors, method, kmeans)) From 212508b3f24daffb23c1171716496eb78dc84b70 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Nov 2016 05:33:02 -0800 Subject: [PATCH 102/267] Review comments --- PIL/SunImagePlugin.py | 4 +--- Tests/test_file_sun.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py index 059bcb655..c3e2bc402 100644 --- a/PIL/SunImagePlugin.py +++ b/PIL/SunImagePlugin.py @@ -85,9 +85,7 @@ class SunImageFile(ImageFile.ImageFile): else: self.mode, rawmode = 'RGB', 'BGRX' else: - raise SyntaxError("Unsupported Mode/Bit Depth") - - + raise SyntaxError("Unsupported Mode/Bit Depth") if palette_length: if palette_length > 1024: diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index deac32394..40eb73898 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -38,7 +38,6 @@ class TestFileSun(PillowTestCase): os.listdir(EXTRA_DIR) if os.path.splitext(f)[1] in ('.sun', '.SUN', '.ras')) for path in files: - print (path) with Image.open(path) as im: im.load() self.assertIsInstance(im, SunImagePlugin.SunImageFile) From 37698d8395c30fe1383487c1d14b74fb45aa2101 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 23 Nov 2016 16:10:56 +0200 Subject: [PATCH 103/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3aa00eb02..3e29eac3f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- SunImagePlugin fixes #2241 + [wiredfool] + - Use minimal scale for jpeg drafts #2240 [homm] From 76b1eb242ef54aaa3e3b50ba07051f7534bc0438 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Nov 2016 06:14:06 -0800 Subject: [PATCH 104/267] Fix for issue #2206 --- libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/TiffDecode.c b/libImaging/TiffDecode.c index 8793f2b34..f292da388 100644 --- a/libImaging/TiffDecode.c +++ b/libImaging/TiffDecode.c @@ -58,7 +58,7 @@ tsize_t _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { tdata_t new; tsize_t newsize=state->size; while (newsize < (size + state->size)) { - if (newsize > (tsize_t)SIZE_MAX - 64*1024){ + if (newsize > INT_MAX - 64*1024){ return 0; } newsize += 64*1024; From ce3432f5f42c86eef9823cd780be3c1b4a165bb3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Nov 2016 06:24:40 -0800 Subject: [PATCH 105/267] Test for issue #2206 --- Tests/test_file_libtiff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 2d1b33154..bc2149dd6 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -39,6 +39,8 @@ class LibTiffTestCase(PillowTestCase): out = self.tempfile("temp.png") im.save(out) + out_bytes = io.BytesIO() + im.save(out_bytes, format='tiff', compression='group4') class TestFileLibTiff(LibTiffTestCase): From 14734b1d971f76d195cd8de6cae2de12970b33fb Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 24 Nov 2016 03:03:31 +0300 Subject: [PATCH 106/267] remove _makeself deprecated method --- PIL/Image.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 4fd2dcfc7..b808aa2e5 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -530,8 +530,6 @@ class Image(object): new.info = self.info.copy() return new - _makeself = _new # compatibility - # Context Manager Support def __enter__(self): return self From 9947794ccd9f1fb53c0b8846642d7991bb730733 Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 24 Nov 2016 03:08:57 +0300 Subject: [PATCH 107/267] fix spelling error --- Tests/test_image_resample.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 1336c1009..fb439579c 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -255,7 +255,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) - def make_dity_case(self, mode, clean_pixel, dirty_pixel): + def make_dirty_case(self, mode, clean_pixel, dirty_pixel): i = Image.new(mode, (64, 64), dirty_pixel) px = i.load() xdiv4 = i.size[0] // 4 @@ -265,7 +265,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): px[x + xdiv4, y + ydiv4] = clean_pixel return i - def run_dity_case(self, i, clean_pixel): + def run_dirty_case(self, i, clean_pixel): px = i.load() for y in range(i.size[1]): for x in range(i.size[0]): @@ -275,20 +275,20 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): self.assertEqual(px[x, y][:3], clean_pixel, message) def test_dirty_pixels_rgba(self): - case = self.make_dity_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) - self.run_dity_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) - self.run_dity_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) - self.run_dity_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) - self.run_dity_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) - self.run_dity_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) + case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) def test_dirty_pixels_la(self): - case = self.make_dity_case('LA', (255, 128), (0, 0)) - self.run_dity_case(case.resize((20, 20), Image.BOX), (255,)) - self.run_dity_case(case.resize((20, 20), Image.BILINEAR), (255,)) - self.run_dity_case(case.resize((20, 20), Image.HAMMING), (255,)) - self.run_dity_case(case.resize((20, 20), Image.BICUBIC), (255,)) - self.run_dity_case(case.resize((20, 20), Image.LANCZOS), (255,)) + case = self.make_dirty_case('LA', (255, 128), (0, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BOX), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) class CoreResamplePassesTest(PillowTestCase): From d4784bffb2d1f398b1eb16b1fe09874b13f78cea Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 24 Nov 2016 03:30:36 +0300 Subject: [PATCH 108/267] return copy of the image if size matches --- PIL/Image.py | 2 +- Tests/test_image_resample.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 368186f81..3f8a49e6f 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1544,7 +1544,7 @@ class Image(object): size = tuple(size) if self.size == size: - return self._new(self.im) + return self.copy() if self.mode in ("1", "P"): resample = NEAREST diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index fb439579c..ebc1ac6e4 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -24,6 +24,15 @@ class TestImagingResampleVulnerability(PillowTestCase): with self.assertRaises(ValueError): im.resize((100, -100)) + def test_modify_after_resizing(self): + im = hopper('RGB') + # get copy with same size + copy = im.resize(im.size) + # some in-place operation + copy.paste('black', (0, 0, im.width // 2, im.height // 2)) + # image should be different + self.assertNotEqual(im.tobytes(), copy.tobytes()) + class TestImagingCoreResampleAccuracy(PillowTestCase): def make_case(self, mode, size, color): From bcb6d606a2210b44dac62f7f6d56dead2c9b84e6 Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 24 Nov 2016 04:06:18 +0300 Subject: [PATCH 109/267] fix typo! --- Tests/test_image_resize.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index e0af7bb6b..7db409659 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -39,14 +39,14 @@ class TestImagingCoreResize(PillowTestCase): self.assertEqual(r.im.bands, im.im.bands) def test_reduce_filters(self): - for f in [Image.LINEAR, Image.BOX, Image.BILINEAR, Image.HAMMING, + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: r = self.resize(hopper("RGB"), (15, 12), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (15, 12)) def test_enlarge_filters(self): - for f in [Image.LINEAR, Image.BOX, Image.BILINEAR, Image.HAMMING, + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: r = self.resize(hopper("RGB"), (212, 195), f) self.assertEqual(r.mode, "RGB") @@ -66,7 +66,7 @@ class TestImagingCoreResize(PillowTestCase): } samples['dirty'].putpixel((1, 1), 128) - for f in [Image.LINEAR, Image.BOX, Image.BILINEAR, Image.HAMMING, + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: # samples resized with current filter references = { From 06895b6fa4db8da510c8161a15e57b43e5e7af58 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 24 Nov 2016 08:37:52 +0200 Subject: [PATCH 110/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3e29eac3f..0af77f439 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Use Image._new() instead of _makeself() #2248 + [homm] + - SunImagePlugin fixes #2241 [wiredfool] From 378dbb2f9f059ef555598c533fe87e7382b454c3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Nov 2016 10:57:51 +1100 Subject: [PATCH 111/267] Improved description of method parameter --- PIL/ImageOps.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 8580ec5fb..182f3c3d2 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -206,7 +206,8 @@ def deform(image, deformer, resample=Image.BILINEAR): :param image: The image to deform. :param deformer: A deformer object. Any object that implements a **getmesh** method can be used. - :param resample: What resampling filter to use. + :param resample: An optional resampling filter. Same values possible as + in the PIL.Image.transform function. :return: An image. """ return image.transform( From 4ed31e8ef78f7e8aa31823ed5599c71037361b2f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2016 11:19:43 +1100 Subject: [PATCH 112/267] Updated comments to use print as a function --- PIL/BdfFontFile.py | 4 ++-- PIL/CurImagePlugin.py | 16 ++++++++-------- PIL/FontFile.py | 2 +- PIL/FpxImagePlugin.py | 4 ++-- PIL/Image.py | 2 +- PIL/ImageMorph.py | 4 ++-- PIL/IptcImagePlugin.py | 2 +- PIL/JpegImagePlugin.py | 2 +- PIL/SpiderImagePlugin.py | 2 +- PIL/TiffImagePlugin.py | 6 +++--- PIL/WmfImagePlugin.py | 2 +- Tests/make_hash.py | 4 ++-- Tests/test_bmp_reference.py | 4 ++-- Tests/test_format_hsv.py | 24 ++++++++++++------------ Tests/test_image_resample.py | 6 +++--- Tests/test_locale.py | 6 +++--- Tests/test_numpy.py | 2 +- 17 files changed, 46 insertions(+), 46 deletions(-) diff --git a/PIL/BdfFontFile.py b/PIL/BdfFontFile.py index e6cc22f91..d663b55f9 100644 --- a/PIL/BdfFontFile.py +++ b/PIL/BdfFontFile.py @@ -119,9 +119,9 @@ class BdfFontFile(FontFile.FontFile): # fontname = ";".join(font[1:]) - # print "#", fontname + # print("#", fontname) # for i in comments: - # print "#", i + # print("#", i) while True: c = bdf_char(fp) diff --git a/PIL/CurImagePlugin.py b/PIL/CurImagePlugin.py index 4db4c4073..39b3fd9a3 100644 --- a/PIL/CurImagePlugin.py +++ b/PIL/CurImagePlugin.py @@ -58,14 +58,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): m = s elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]): m = s - # print "width", i8(s[0]) - # print "height", i8(s[1]) - # print "colors", i8(s[2]) - # print "reserved", i8(s[3]) - # print "hotspot x", i16(s[4:]) - # print "hotspot y", i16(s[6:]) - # print "bytes", i32(s[8:]) - # print "offset", i32(s[12:]) + # print("width", i8(s[0])) + # print("height", i8(s[1])) + # print("colors", i8(s[2])) + # print("reserved", i8(s[3])) + # print("hotspot x", i16(s[4:])) + # print("hotspot y", i16(s[6:])) + # print("bytes", i32(s[8:])) + # print("offset", i32(s[12:])) if not m: raise TypeError("No cursors were found") diff --git a/PIL/FontFile.py b/PIL/FontFile.py index d86f9b3ab..c8860a905 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -88,7 +88,7 @@ class FontFile(object): x = xx s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0 self.bitmap.paste(im.crop(src), s) - # print chr(i), dst, s + # print(chr(i), dst, s) self.metrics[i] = d, dst, s def save(self, filename): diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index a4a9098a7..6595a08bb 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -112,7 +112,7 @@ class FpxImageFile(ImageFile.ImageFile): if id in prop: self.jpeg[i] = prop[id] - # print len(self.jpeg), "tables loaded" + # print(len(self.jpeg), "tables loaded") self._open_subimage(1, self.maxid) @@ -141,7 +141,7 @@ class FpxImageFile(ImageFile.ImageFile): offset = i32(s, 28) length = i32(s, 32) - # print size, self.mode, self.rawmode + # print(size, self.mode, self.rawmode) if size != self.size: raise IOError("subimage mismatch") diff --git a/PIL/Image.py b/PIL/Image.py index cabc10743..c086dfcd7 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2161,7 +2161,7 @@ def fromarray(obj, mode=None): typekey = (1, 1) + shape[2:], arr['typestr'] mode, rawmode = _fromarray_typemap[typekey] except KeyError: - # print typekey + # print(typekey) raise TypeError("Cannot handle this data type") else: rawmode = mode diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 0bbfbb42b..2f7d762c4 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -152,8 +152,8 @@ class LutBuilder(object): # # Debugging # for p,r in patterns: -# print p,r -# print '--' +# print(p,r) +# print('--') # compile the patterns into regular expressions for speed for i, pattern in enumerate(patterns): diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 1de17cbba..319e6abfe 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -107,7 +107,7 @@ class IptcImageFile(ImageFile.ImageFile): else: self.info[tag] = tagdata - # print tag, self.info[tag] + # print(tag, self.info[tag]) # mode layers = i8(self.info[(3, 60)][0]) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 221bf6495..360c4c9d8 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -316,7 +316,7 @@ class JpegImageFile(ImageFile.ImageFile): if i in MARKER: name, description, handler = MARKER[i] - # print hex(i), name, description + # print(hex(i), name, description) if handler is not None: handler(self, i) if i == 0xFFDA: # start of scan diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index 08726f90c..5c4d1bb3b 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -75,7 +75,7 @@ def isSpiderHeader(t): labrec = int(h[13]) # no. records in file header labbyt = int(h[22]) # total no. of bytes in header lenbyt = int(h[23]) # record length in bytes - # print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt) + # print("labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)) if labbyt != (labrec * lenbyt): return 0 # looks like a valid header diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index f1860e4aa..956bdc137 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -338,7 +338,7 @@ class IFDRational(Rational): 'rfloordiv','mod','rmod', 'pow','rpow', 'pos', 'neg', 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'nonzero', 'ceil', 'floor', 'round'] - print "\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a) + print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) """ __add__ = _delegate('__add__') @@ -800,7 +800,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ifd = ImageFileDirectory_v1() ifd[key] = 'Some Data' ifd.tagtype[key] = 2 - print ifd[key] + print(ifd[key]) ('Some Data',) Also contains a dictionary of tag types as read from the tiff image file, @@ -1197,7 +1197,7 @@ class TiffImageFile(ImageFile.ImageFile): "tiff_sgilog24", "tiff_raw_16"]: # if DEBUG: - # print "Activating g4 compression for whole file" + # print("Activating g4 compression for whole file") # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py index 9416035c0..e1391c2bb 100644 --- a/PIL/WmfImagePlugin.py +++ b/PIL/WmfImagePlugin.py @@ -111,7 +111,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): self.info["dpi"] = 72 - # print self.mode, self.size, self.info + # print(self.mode, self.size, self.info) # sanity check (standard metafile header) if s[22:26] != b"\x01\x00\t\x00": diff --git a/Tests/make_hash.py b/Tests/make_hash.py index 6d700addf..4412f65be 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -51,10 +51,10 @@ for i0 in range(65556): print() -# print check(min_size, min_start) +# print(check(min_size, min_start)) print("#define ACCESS_TABLE_SIZE", min_size) print("#define ACCESS_TABLE_HASH", min_start) # for m in modes: -# print m, "=>", hash(m, min_start) % min_size +# print(m, "=>", hash(m, min_start) % min_size) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 0f98d8374..3b20d4a43 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -21,7 +21,7 @@ class TestBmpReference(PillowTestCase): im.load() except Exception: # as msg: pass - # print ("Bad Image %s: %s" %(f,msg)) + # print("Bad Image %s: %s" %(f,msg)) def test_questionable(self): """ These shouldn't crash/dos, but it's not well defined that these @@ -46,7 +46,7 @@ class TestBmpReference(PillowTestCase): except Exception: # as msg: if os.path.basename(f) in supported: raise - # print ("Bad Image %s: %s" %(f,msg)) + # print("Bad Image %s: %s" %(f,msg)) def test_good(self): """ These should all work. There's a set of target files in the diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index e32f1c047..eea29b3f1 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -47,9 +47,9 @@ class TestFormatHSV(PillowTestCase): img = Image.merge('RGB', (r, g, b)) - # print (("%d, %d -> "% (int(1.75*px),int(.25*px))) + \ - # "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px))) - # print (("%d, %d -> "% (int(.75*px),int(.25*px))) + \ + # print(("%d, %d -> "% (int(1.75*px),int(.25*px))) + \ + # "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px))) + # print(("%d, %d -> "% (int(.75*px),int(.25*px))) + \ # "(%s, %s, %s)"%img.getpixel((.75*px, .25*px))) return img @@ -95,8 +95,8 @@ class TestFormatHSV(PillowTestCase): im = src.convert('HSV') comparable = self.to_hsv_colorsys(src) - # print (im.getpixel((448, 64))) - # print (comparable.getpixel((448, 64))) + # print(im.getpixel((448, 64))) + # print(comparable.getpixel((448, 64))) # print(im.split()[0].histogram()) # print(comparable.split()[0].histogram()) @@ -111,15 +111,15 @@ class TestFormatHSV(PillowTestCase): self.assert_image_similar(im.split()[2], comparable.split()[2], 1, "Value conversion is wrong") - # print (im.getpixel((192, 64))) + # print(im.getpixel((192, 64))) comparable = src im = im.convert('RGB') # im.split()[0].show() # comparable.split()[0].show() - # print (im.getpixel((192, 64))) - # print (comparable.getpixel((192, 64))) + # print(im.getpixel((192, 64))) + # print(comparable.getpixel((192, 64))) self.assert_image_similar(im.split()[0], comparable.split()[0], 3, "R conversion is wrong") @@ -132,8 +132,8 @@ class TestFormatHSV(PillowTestCase): im = hopper('RGB').convert('HSV') comparable = self.to_hsv_colorsys(hopper('RGB')) -# print ([ord(x) for x in im.split()[0].tobytes()[:80]]) -# print ([ord(x) for x in comparable.split()[0].tobytes()[:80]]) +# print([ord(x) for x in im.split()[0].tobytes()[:80]]) +# print([ord(x) for x in comparable.split()[0].tobytes()[:80]]) # print(im.split()[0].histogram()) # print(comparable.split()[0].histogram()) @@ -153,8 +153,8 @@ class TestFormatHSV(PillowTestCase): # print(converted.split()[1].histogram()) # print(target.split()[1].histogram()) - # print ([ord(x) for x in target.split()[1].tobytes()[:80]]) - # print ([ord(x) for x in converted.split()[1].tobytes()[:80]]) + # print([ord(x) for x in target.split()[1].tobytes()[:80]]) + # print([ord(x) for x in converted.split()[1].tobytes()[:80]]) self.assert_image_similar(converted.split()[0], comparable.split()[0], 3, "R conversion is wrong") diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index ebc1ac6e4..ac58eafa0 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -323,10 +323,10 @@ class CoreResamplePassesTest(PillowTestCase): class CoreResampleCoefficientsTest(PillowTestCase): def test_reduce(self): test_color = 254 - # print '' + # print() for size in range(400000, 400010, 2): - # print '\r', size, + # print(size) i = Image.new('L', (size, 1), 0) draw = ImageDraw.Draw(i) draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) @@ -334,7 +334,7 @@ class CoreResampleCoefficientsTest(PillowTestCase): px = i.resize((5, i.size[1]), Image.BICUBIC).load() if px[2, 0] != test_color // 2: self.assertEqual(test_color // 2, px[2, 0]) - # print '\r>', size, test_color // 2, px[2, 0] + # print('>', size, test_color // 2, px[2, 0]) def test_nonzero_coefficients(self): # regression test for the wrong coefficients calculation diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 3f6ce0ade..3fa7c3e67 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -8,10 +8,10 @@ import locale # on windows, in polish locale: # import locale -# print locale.setlocale(locale.LC_ALL, 'polish') +# print(locale.setlocale(locale.LC_ALL, 'polish')) # import string -# print len(string.whitespace) -# print ord(string.whitespace[6]) +# print(len(string.whitespace)) +# print(ord(string.whitespace[6])) # Polish_Poland.1250 # 7 diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 370b8c1f9..02ae5e50b 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -54,7 +54,7 @@ class TestNumpy(PillowTestCase): i = Image.fromarray(a) if list(i.split()[0].getdata()) != list(range(100)): print("data mismatch for", dtype) - # print dtype, list(i.getdata()) + # print(dtype, list(i.getdata())) return i # Check supported 1-bit integer formats From 67be3a9eda142270eec8b92801bc883a0ac704d5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Nov 2016 11:05:56 +1100 Subject: [PATCH 113/267] Added future print_function imports --- PIL/BdfFontFile.py | 2 ++ PIL/CurImagePlugin.py | 1 + PIL/FontFile.py | 2 ++ PIL/FpxImagePlugin.py | 1 + PIL/ImageMorph.py | 2 ++ PIL/JpegImagePlugin.py | 2 ++ PIL/WmfImagePlugin.py | 2 ++ Tests/test_bmp_reference.py | 1 + Tests/test_image_resample.py | 1 + Tests/test_locale.py | 1 + 10 files changed, 15 insertions(+) diff --git a/PIL/BdfFontFile.py b/PIL/BdfFontFile.py index d663b55f9..b02c44fd2 100644 --- a/PIL/BdfFontFile.py +++ b/PIL/BdfFontFile.py @@ -17,6 +17,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function + from PIL import Image from PIL import FontFile diff --git a/PIL/CurImagePlugin.py b/PIL/CurImagePlugin.py index 39b3fd9a3..3a9a10e01 100644 --- a/PIL/CurImagePlugin.py +++ b/PIL/CurImagePlugin.py @@ -16,6 +16,7 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function from PIL import Image, BmpImagePlugin, _binary diff --git a/PIL/FontFile.py b/PIL/FontFile.py index c8860a905..807984a8c 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -14,6 +14,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function + import os from PIL import Image, _binary diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index 6595a08bb..59240718f 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -15,6 +15,7 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function from PIL import Image, ImageFile from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 2f7d762c4..3ad708291 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -5,6 +5,8 @@ # # Copyright (c) 2014 Dov Grobgeld +from __future__ import print_function + from PIL import Image from PIL import _imagingmorph import re diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 360c4c9d8..1e2504888 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -32,6 +32,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function + import array import struct import io diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py index e1391c2bb..ff4819a66 100644 --- a/PIL/WmfImagePlugin.py +++ b/PIL/WmfImagePlugin.py @@ -15,6 +15,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import print_function + from PIL import Image, ImageFile, _binary __version__ = "0.2" diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 3b20d4a43..fa4571e60 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,3 +1,4 @@ +from __future__ import print_function from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index ac58eafa0..7ae687e2f 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,3 +1,4 @@ +from __future__ import print_function from helper import unittest, PillowTestCase, hopper from PIL import Image, ImageDraw, ImageMode diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 3fa7c3e67..142753791 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,3 +1,4 @@ +from __future__ import print_function from helper import unittest, PillowTestCase from PIL import Image From 87de178e0b285956dc1ad262ca487357ced47b6b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Nov 2016 11:29:20 +1100 Subject: [PATCH 114/267] Added context manager when opening files in WalImageFile --- PIL/WalImageFile.py | 51 +++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py index b0b1e684a..fac1dc1a8 100644 --- a/PIL/WalImageFile.py +++ b/PIL/WalImageFile.py @@ -47,33 +47,34 @@ def open(filename): # FIXME: modify to return a WalImageFile instance instead of # plain Image object ? + def imopen(fp): + # read header fields + header = fp.read(32+24+32+12) + size = i32(header, 32), i32(header, 36) + offset = i32(header, 40) + + # load pixel data + fp.seek(offset) + + im = Image.frombytes("P", size, fp.read(size[0] * size[1])) + im.putpalette(quake2palette) + + im.format = "WAL" + im.format_description = "Quake2 Texture" + + # strings are null-terminated + im.info["name"] = header[:32].split(b"\0", 1)[0] + next_name = header[56:56+32].split(b"\0", 1)[0] + if next_name: + im.info["next_name"] = next_name + + return im + if hasattr(filename, "read"): - fp = filename + return imopen(filename) else: - fp = builtins.open(filename, "rb") - - # read header fields - header = fp.read(32+24+32+12) - size = i32(header, 32), i32(header, 36) - offset = i32(header, 40) - - # load pixel data - fp.seek(offset) - - im = Image.frombytes("P", size, fp.read(size[0] * size[1])) - im.putpalette(quake2palette) - - im.format = "WAL" - im.format_description = "Quake2 Texture" - - # strings are null-terminated - im.info["name"] = header[:32].split(b"\0", 1)[0] - next_name = header[56:56+32].split(b"\0", 1)[0] - if next_name: - im.info["next_name"] = next_name - - return im - + with builtins.open(filename, "rb") as fp: + return imopen(fp) quake2palette = ( # default palette taken from piffo 0.93 by Hans Häggström From 14cfec0b49c4fd1e69e2c50bca6558e1363d3467 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Nov 2016 11:50:56 +1100 Subject: [PATCH 115/267] Added decompression bomb check to WalImageFile --- PIL/WalImageFile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py index fac1dc1a8..1167fa739 100644 --- a/PIL/WalImageFile.py +++ b/PIL/WalImageFile.py @@ -56,6 +56,7 @@ def open(filename): # load pixel data fp.seek(offset) + Image._decompression_bomb_check(size) im = Image.frombytes("P", size, fp.read(size[0] * size[1])) im.putpalette(quake2palette) From 0116c9240efd6e6ba12f357b81a8c5290d64474c Mon Sep 17 00:00:00 2001 From: glexey Date: Sun, 27 Nov 2016 08:03:51 -0800 Subject: [PATCH 116/267] EMF: support negative bounding box coordinates (#2249) * EMF: support negative bounding box coordinates Similar to placeable WMF, bounding box coordinates should be interpreted as signed integer, otherwise opening EMF file with negative (x0,y0) fails. * Basic load tests for WMF and EMF formats * WMF/WMF tests: just test open(), not load() Not sure why load() fails on Debian build. Well, at least we can test open(). * WMF/EMF: Unpack signed integers using unpack() * WMF/EMF: Compare to reference PNG rendering * EMF/WMF comparison: use assert_image_similar() * Use similarity epsilon 0.5 for WMF, as vector rendering looks different across Windows platforms * Trigger rebuild --- PIL/WmfImagePlugin.py | 25 +++++++++++-------------- PIL/_binary.py | 22 ++++++++++++++++++++-- Tests/images/drawing.emf | Bin 0 -> 876 bytes Tests/images/drawing.wmf | Bin 0 -> 610 bytes Tests/images/drawing_emf_ref.png | Bin 0 -> 20304 bytes Tests/images/drawing_wmf_ref.png | Bin 0 -> 531 bytes Tests/test_file_wmf.py | 31 +++++++++++++++++++++++++++++++ 7 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 Tests/images/drawing.emf create mode 100644 Tests/images/drawing.wmf create mode 100644 Tests/images/drawing_emf_ref.png create mode 100644 Tests/images/drawing_wmf_ref.png create mode 100644 Tests/test_file_wmf.py diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py index 9416035c0..1dbd9f964 100644 --- a/PIL/WmfImagePlugin.py +++ b/PIL/WmfImagePlugin.py @@ -14,6 +14,10 @@ # # See the README file for information on usage and redistribution. # +# WMF/EMF reference documentation: +# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf +# http://wvware.sourceforge.net/caolan/index.html +# http://wvware.sourceforge.net/caolan/ora-wmf.html from PIL import Image, ImageFile, _binary @@ -56,16 +60,9 @@ if hasattr(Image.core, "drawwmf"): # -------------------------------------------------------------------- word = _binary.i16le - - -def short(c, o=0): - v = word(c, o) - if v >= 32768: - v -= 65536 - return v - +short = _binary.si16le dword = _binary.i32le - +_long = _binary.si32le # # -------------------------------------------------------------------- @@ -121,13 +118,13 @@ class WmfStubImageFile(ImageFile.StubImageFile): # enhanced metafile # get bounding box - x0 = dword(s, 8) - y0 = dword(s, 12) - x1 = dword(s, 16) - y1 = dword(s, 20) + x0 = _long(s, 8) + y0 = _long(s, 12) + x1 = _long(s, 16) + y1 = _long(s, 20) # get frame (in 0.01 millimeter units) - frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36) + frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36) # normalize size to 72 dots per inch size = x1 - x0, y1 - y0 diff --git a/PIL/_binary.py b/PIL/_binary.py index 1cbe59dea..9760b86cd 100644 --- a/PIL/_binary.py +++ b/PIL/_binary.py @@ -31,23 +31,41 @@ else: # TODO: replace with more readable struct.unpack equivalent def i16le(c, o=0): """ - Converts a 2-bytes (16 bits) string to an integer. + Converts a 2-bytes (16 bits) string to an unsigned integer. c: string containing bytes to convert o: offset of bytes to convert in string """ return unpack("H", c[o:o+2])[0] diff --git a/Tests/images/drawing.emf b/Tests/images/drawing.emf new file mode 100644 index 0000000000000000000000000000000000000000..ef751cd51a4d542c949802f6ca0d6824cf0ba8bb GIT binary patch literal 876 zcmaiz-AV#c6ouEM{g4&?Q9%$xB^8LxPo`E9K?N0EdDT^jnMf*P7tT%Ki}Wr%LYLn3 zEVkBgjzNKK_|D$5&)WN(nRCVg@JTQn4#mSHAgq;BfN-zwuK@?m1hBLxb~m*4NO&^Ju{(Msvg=y9S_t&d}ITJi!cK3K`;(aM?t>WYQlbri{ddB7qUSVaG)H^)Y-kPm{@DBnRtU< zNF!S%isuPGXFR9(kPXQcNt53_rFkR;qW*tu+lhPHy!u$P{Zw~C)H_K|$qd;fizMWo zc&A8L5tA rUx_*8&y3ea(V1AY@0Vsm--hM=eG{|I=sehCl-IXhAUaQL`ft7g7R6_K literal 0 HcmV?d00001 diff --git a/Tests/images/drawing.wmf b/Tests/images/drawing.wmf new file mode 100644 index 0000000000000000000000000000000000000000..d9cfda453d9fcb68f45c5b7aa775599a859c374f GIT binary patch literal 610 zcmYLH%SvNG5baZ4)k(;V;%a6QT@1s35MOcQ+7O9rSNa3_h(L&|fPx<&xR@_6pc^HS z-Rx$S#ccBlB8oBydaCcuOgG(Kb^27*>ALsN?_ZZ8k7GNwvAX&52%#22+(8HyF7mB$ z|0K?Ft1b>YIigBV2m+F`mEf0-ZQ^Tb)iU2P#b@pnt{>?OGdNb|`b&mq@KN~o&-XeT z%hs)VmKRw|cT!ISd9;qqY(>U4 z$H;DQVrN*l9-g&}iEiOk8))kiM5~y_3a(-m{kVj^*ubl7;a<8JNDl{ch84NNob#5} zIXXlKa)>+G#*3^nVwsV(oMFmSbQ$})ilJ_^(jlI-$I9naa7!g~TvOG|4ybTIrT0|) jDov_y@fF?=@6iK$y6AY}Yw07O5B%x{zVP{Pgt&2AcSk6g38WpiJo+RtU7cC$58n$PK@HX-1#`jxHt@L3xjDUdPH zLhU8pw^D~9{lfq@A%8=}VdI&IuP5LjC%s##Etg~($Tyfo^{K5>!k^A$NKX_K=(yL1 zJYLuzNU1O8p?UI{hV(BYq|yMKIfhqNVNXLD(rf_p6n>3cdFizVnhXT=Gdi_KMt{;m z(9tyOYn%d#w>>7Eu~AqQhJl4G1Ao_`#k}15X>zFMGbV`KSpx3s5T4@2_Vq zvFLp6`)=5XCZxX?#y28)X;N$D{3{cZMzK&iNYx>H)YEcKs@}Dz8@Dh-mXi#4avN zxcOXH_HWMfdJaT zz*!DBOiu|K>3;Qmia)YEB4dR6b!fKs`E8!zw?x|%MtTz9gc$Oa9=T!VK=Lmz>m}PY z8XpMe#*|Gcip76k}J~>LJ$#3U$t})oCr$>R5bj(|L22kOy2QYfYCN znvWhNT+B`NNA2L`9jAl)*IMXEc8k8OpTd!DhEe+uC3X&#KGb;faf$=VYNU{OZaMl> z=OgmDhD4rhW}Bx5Q{^k48fVQ?#)x@B3qu~7b!~&E=m;Wqa#{7R2XtCbF!h)24oU9f0K{BbbZxRfdMuYQj0|$)Hsqm? zXMUW>PiBN~U+@Pk03i&mYM92$bULpB)|6*E+WfQBQ`z;<$nVI4Vh zK5l~;h2MW_z5FOs=?2|vq5C0pUjs81?L9iw09J00r$SCZxCK5vbEVg`tpgS&Ca(I;{nLh2beM z05HqI;}XoXf%vuYN8K~nOs5%IOsPAJst0`PfZ}Xi*?6W>2%;_p66b-PPsL5^ z<>$2Y!73ebfcVMUAFTpjSLc)?`IspjxfzgW0QqlVd@(pxPrxFr$PZdlA0r2;6%g49 zM3iTUltrnGP*5yx%}U`3=Zksu`+(kr++@aGNL4dHcIAG+nO0255036%d$~V)wWwcZ zVFN1o_Uz3NB(X8$pwiG=7F!;E94DOBN=fZO%Mv4|KiUvN3M1;Yn8<4OHRO1QlHRP1 zW1u1!Vwuwjj`d0x?Jgq<$uEz7%hWSq@+ueed}!Vs6>&=fwSEhS~F&9`%_b(r2TQg4j1uzx>MtUk>#By0L z@&S4SklN%tg5$Y-g?6Mp4-t>rzvw0-)Brc?nF{4^l3q#d9wrweN%YKd19V^>LQR~#D~0`Kz=>sXZKuS z+r8?}z|1}T`YbLhfv`y^D`%k5V6gmPQn~zkIYLj#8cRI!D7J&@!?Vm;c>j0SBfqk3*yD&0Vt z<;xR@sqqYSVD#U*H>JGvgJpyy#|yL|9nVQ-bbGOxEQO6B;+GCMy)tW;&_m1%&yIkh z+kw{#IHfazPnD+ub6jN9r0@)V7E0ee@>6VGt&DNZc@PK5QP3;q#Jy~0-QH1pt=sz` z$r-P=64o`T3LxH|1;X;-!;%@BbJ)x!U7el!6hb;9`K*tmJtrCuv#mnP^Ep&Ts1{=V zU2x#Dc7q0@r{k_#=-LBa4Dd|3Gt*x*EhG93&G3oD2I6fdBDndd#y$Qw_gCh=IpPm!Eagx=$FhX7nKu z*h$Foyt$ZHjJ=FVXE^YUBgy)jCjHj57=%D!Wx-^9RR`z+-ALGKzNbREp*siJrH`R-CxK_u3pUBJrpdZ$N;)lk)%&LID^oX`D}(j z659oMqcL7CC+28(XD7jDJw76HvB8fR4@dO5M((sU!cM4vnE5I8AAr`vT{gg_1h|x8 zF3VmBH%ma^^c)aSI7jd9Z&3{t-xd@kz1yCxS)!O~_oP1|YQY>{3Ign#H_!hj7!{egl!T1cqOChmtV6%o#|+cjiRH zn(}u{h&W)%x>UV^RP12FP$k1}Br!(pWyeo3#R3&MPvS&-vE%!cpYOSNg@>^ZUV$kSl!Om1{V4WrmXJj!Ct&j74uGZKZHEa zEH&)(x9516IO(?!yNqAU1zLPVjo|16DY-mn;g&bxW77iS~fiq`ZBc(cJ zc6;(~;6}=6`A?NCgy)TC840h`AjE}H`E#-=M=yimvNr?-IqDjkJ5azmg|X!vXAVpt zs!^D1N=AjeuYgmB<3Voe%-L5@sWfqi&aMhMbL&*Yr#N8?hqyiM5u7&zSMAt`NA(A1 z!@3`|YcfK}jyU{C7bKq#4eUv1RW<38$z@%onL9gkUI9M`OxEcIVLg={|6AgOqmyJ1 zE7s)Qi36Nzoz2QbSl#t7u2)$TVfa6dZ1f zy`p1`GiOdCCB#s585R5yg0wf?)t=xX8qS=|J5=zhdAQ5OI?R>UYj~DpyOvxav>E^DBTSZk5w0t>9|kGJNfeH-b3 zkZ``+0^K#d+I(3CBOhBY-f(f9m*O<2pW@F!QZ#@ z_AJ}YE8Y*YU?3?!@vhzL^*m}ZZ!~s6p{X0!m~~B}El7*Q$T;5O>Z#)%7}+0?H3VsM z=nh9`PDvwm+zod;-&G-jp_|hkzz!Bp$7!9c$*7PR3(`K)9TtF5PgUXIMDH^zAPuHn zqdUesbGVIE)vKP%J#%rV2Jh^cj0)8ZG0#R93xy)8NJS#oV-i-m59psAOj=ri+aXys zN$lr(SkWDaOj)q(I%0-9W>k@|0QJzr9rY=utX+V?#~r$W5zqBlOLt_MvMNHz1S{Ne z3p%p69*c2@(k!XmN6d4fnLm3$@TRaW%(3&Resks|)>GD=Sj=f>n_|0)399Vti2jFk zL3iZX>d=CvSGZNE#muI#OBl|aL-mx1mQUoN7ym4nETna0 zXO&bA%j8CS_`X$S$5=sH8;xIMcS7}>Agye5&%n5gV?a?e#u_j1sIc~62hjTAgWF#T zA?ZZ`x&~SVqtuKwu&Gj?QVV#e@cqe#BLD=8Y7jWxVO`l5PSY zS30iGl6X{j&R_@dXkY{9v_r+j_3$B@@Rnccn0};~V1+N41giI_YdB^mNV`b$y7Yp$ z&_I<=!HTPzsh8hhkoKDHFnK|ItEZZ1pVh8(+zzW&`wMfwKOw>X8W)O0BTYOY`WBYn zfObdxQFEJM@<3}2Hq1#=Q;!W|-b);q=v*~pdqW$)~_ zIvGMT>+kJ6?G6=9$8UZ8L9K%ggg>-t{}1-o%jXHwj31W)z0%se%-~|e%Fzho*hd=q z@q|0OdB8;oPvm6x&KepEu~(vJAcU@%x0AL@H$Q2%lzW{?_w=nMmmQNiWa32NbLi-h z6Cq^xkwzGP$Kw{$<<-?uV%`sOy(Q3?TTQluh$FB*rDL6)(m;njv{#1TJa#J+UN|G@ z6or=M$|6k1ZoGs^)*omf)-+JG@k*!YSr`Zk$I}G5UJ!T_0Z1KuK`gjKiAUlgD6O+n zAai}f3k*bL9n8FTOg38Gh&S~J7mKFSLVu_t@%tHI*UPx~cjSVe(Q+_>NGuvb1FWnf zuN{$%#w$vs$Tam3ibck6EC8x-SdzhZ?saw|&Dyb8!EN6GE+H|_Clm5y_lD63*`^+* zVo?N5$)JkVgjW(k&8;RmM`ZJtTjTLN*^SvIWZTk7o^LXK=);-p-|2AUTREp)2F1`q zE2J&M28NJYHZ6d^ds%tSX0d2=)KmyJ{lf<{+w)|5XbID0rq{uHXnvbS>1Pjd7bMy1 zfp3m`uqJ0H0ntCDX;2?%+pgg~2qyoatr!{299%--jXltA4Kc$u3hK=Z+1;Njr8Z)xtX!W z3eA{JAe2gjy`rZ;wkME=bF?3^riIv2PvsSjhv-0h-hQkG%60B(47oqPqI4EJ_4WJp z^KD?GPCB`ojA^7!(pkZ}_H6e4VujqaAGj9k`_QI(kXYn?!oHpwUQYox^fXtdtXM@;=he;hIs`i?@b85J=ucGBx6HadAy>AF&b!KaQPbYT zA_+jS1ozYs8X$ou+xO;JSe&7(j0#xGWl3rJux+4NM|-}sGA;34JZ#+|G|wvO-7rC@ zfm+5#A*3yxniLrg1Y~9aKs&3+Pr+p406alR|FJ9nnkTzOJGxLmYTBA$8s>I2xhI(H zr-t#dgLJyQAr17w=Z_z{zmf89I0g;W+CUX3Xl+xDCin5yAvkTo;G+UT=meU-PiBKc z19VAiKvX-k`cSc=JZ30>@!3T-x*?>6T7fNlFOdkAa_-_$gPWtGa_-r@gjtRIcpFIzhr$ST`ae zqcz}812u-uU3@mvJSvw7=-3yUbKk?#^Vo??fUb1QgSA?dj@S_z zMkDbtg^*!!o9#MB&52|lzaq{WH{i%X$TdLrATSYdoh zMl8iU;VOdcx05^O5KiG}=%V+=a@oZECy=+$*lKoeFd1)i4SxINxP2^Yq_(s;YC#L8 z@`2Sp*p+t&jj|gpXm>EShDOK0MglfpDmrmAp;vZN%j_|Uv_A${o*=zK z$jN2aSa0id(yBe#1C>XyD3_{@>XwL`ww9XWt}lCcdQD|tteJwl4)}9}EClUK?cJfF zvYYD&@3rSkA7eQ7^cRp`zgG;^hOWf{3#<$T?cz@aZWIn}Q0 zi?E7)4lU)3g2?;oNMjLWce#Gy(%Bw0RRuTsKk>z5Q(6}UJzx2Rq9Y7F8ldZYVjX={ zz^mcR2;c0RLyw2Aoln+$Ttynw5v26ia9uXTH|I|rhY98j3LA=Le)7MuI>Ryzs8>zY z+6C~N&#(56-63e#_>Z+t__61T@Ng6fsSp1=r(myL3~(w9FEc(J>wNuua_*-Uq_Mq~ z7PfHdbWw7_LRy^U+rdo}wu{x`NG5JD+wGx7Vds;()`pO3PxNz7bh=QCCj}JT&^xA0 zexAA|UUl`b1rLuygWg-keG}BuhE3nyp!n<6c?>V**FFn9Cj0&(52jD_oG=-);2pPh z4MtJ!-|DmG^i4r6oN|Ef4i7)CC{k;V&C`8!Jd_=|4W6S8w}*5F(9e1j za`A2`X`Dzcj)UjIWuKM0vx{g!G=uDeQsa6B!P%ipr-T;7X!^^-W3sY#fR~O+OMEi# z4_O%>8mM!|x&O$K&kmt1>jxbb=XhfgrNgG3`zZ-+!Plt!X ziBWZzM?|ncIAD;Wdt3Joi(rqh3B>B27r4A_#vX9)DHt`2$GPIeC?gXq}?sm9);r&6@l z(d6^7dO__}Jot`yzlik-t3gB~XrXBH)ogmLiHL;S_kz`+Q`)!d6F6%Ysg17;OH16` z=T9nT0!Em{)4gaF3}82~iIv_{)I4rKOj>^}lnk28qoonLWmah!&?y4d!r=peE?XBh zs&-6uxS*Ehg$Y^pc|hw{lKHsTjq4Ar$~<;$1$k>Sp1Dy$$~_vusiv;%%h1*2q~-8X z${zQ&mVK8Rx0Qym(?3oV58wVhGVCVEsKl7r=bjukJ}Nu+9XF833J!AMuS)M?+tZA* zvai8doYk+&yhpgRUw{1_COg>T)K@PTy4sI!vRTUhkc+j;X$U|4w1<1YgLV`pw@W>s z{HEDo7N6y|He=`G9e=dr@-0E(qtgSSCEcERix#Nkz~*(c+Cd>nR7^T_po(*Uo`-Ev z3}a8H80m`kQ(d=8xXa@$;h|7}bBWhJT26VYX|F<*HbIfob~Pv_3JR-7U}uy1tf{5R zD>wHM=jdj_BV^oU_4T~2ps?a}e+(ftR6~G9qOdbeqO(Ha&9^UopHMe{xZ<&E+kRV& zV+E~mTKDBYo}m!^iCw{!>q3w35L|6#b+zV-%IzcSoKtv=2%8n!q*YRHo6k*60F zD~hf<4Yb69<&>+*`KcFA6BdN+fANvK;O#_A$j=mYFf1E`#>Hf_-GRl5!}QNzFQ!vt=&#Y~$oFH_VF!7zxexq`%h zi9PCwt$X-=Ltq`X?;cgx6Bn9}EiD-nNrS9azW{ZJx39{aD;8~EieCYQR#frJU@zHrjqy?^zSFCZE!162>e1r! z?S5ff92NC!$t#PBs1l-=Ovo~}_aH5-KI_|qacD{3d~1KIp10flec7*f4aA)()tlzr zS8%hwEQAIU_WSIKT^!yRv2KOg9|}JgE3B^K8C7~R^H6qRudiM&Rva1A(lXSr|s?ZG((<$@dk( z${d48^_$CDhu>v;w#hJ+1N}c2{v4IsvFj@@3hde?G&}ZPCxd^pJ1V=!+e;HXn=@>+ znzlUkyjWD5MzcGn4m*`VtFH3r=wjHm`r5RM!hs29J6Cf;{1RF$dFlg!okWQVO4(`` z>p0auta2rZ;nq5o$^vJ_z>E$_xGk*H4dI@)d_mG;?w+eH` z$5EJsmCm}sm(>n$toDVMFDBY}_sS6@u)Di_CWb5RmC}#1lH729n+80pPw4eRdYcC2 z*w&r+*y~Q}WOX3xV|%nZ)7WoWMMjiFU^JzEuPObfv%N34;5qkaAoYfxa;-~UfqE+Z z83HM3!>T5)CS4Nm12`OQ^g5k`To!1WgcT(>`z0s*q@-ieBhLS2c*aM&_NVj}ixMJ6 zV^X}jUe|fNiu{}P7k(7{FY~H>KvDJi>O=L^`jhy&vYxZgZ*xmF`(S+|D~DdU2E(5+ zpaY~gDuunO{pp{*UZqYn%5iWGH1NlRbF@8He|`L&c?oWy0KuPWT0J|W3u-;LJ)bNH zee2MJSrYd?l_}h}^6~GVVcq8TKOw7AisZ!3ojd)0G^)+De)F3i##8m$^=J0`m&EVF z*pH&6bf&QjkFd9^IgBG1-e7;tng7E2b(||;oW4El)#GG!;&J|uqCSVsk=JcoFdtFt z&BdaW$@ujMp~+1ac+vSkD)oPjS^KrK+Re zdsTP89lzx3-bwynC_j@{#AVrLPPYx_A-VFSSaJLIZNJI%^j5okrzL*;6Y>|ED+Swn zuI14wSUGOfkLX{s@qdXTG=>bCcQqSFIqID(o(HkJy1O60kHpWEq3Wll_7BX&zYM|d z?DuH<-YMrziDL11dUfAc+neMk1e1@xtfFTjPA}o!HCEo*snxynt6F@8Yl7P=b*{R= ztEcY#!^ZE5@?pJU_O4VmrRg#)VLGgcCB4Ik)ajbVbgE_CJbUZ!h zt?rGR%F_b7s=@Rmi*FU-@%3s2=FiTb^QU?OZ%v6Le$LhvV$q$6c^DkuGl4%+`$Ojx zPUD@iQ>Z5Wk`BUWK8EHB3H4bhZ0NQ`@#id{}_j4pxl)~>- zz}bI=c)YLZ)c|_a;(AIH%Kl{;5WVp8jM&q@qGB&N#DjIi_1+<+g^>@3wctlQb5)ev zFXGi#`U>n+6X^9-YJVq_e@(y+M`itUvhj?l+L4p=bA8pT?guMEHDk`pDi5r{0I>h} z%j{Q9t#&wYk6O{rr>$rB3)>FYy#gsL3Y#_`!g6)!_4dN@anIg^G{4OmV~u;?L4U?$ z-8n^7zttF_*5~hky^7RVKISe6iNl*H((Je)XMSCNZJ8hX=-J;~ZcH7$Uf2RxoG_q$ z!Fh#$T#)l)Y-4#=J5`=E!vzKc?EzDe#wQ?Vh)8*RxODtiboQP*)1~vws)&!-T6S z`rQew_A1`&Lu*wvStso6#e0r{GZ%_`GT>KBo#EBcP?ogpL6 z%Ubru(JPvwdy(Y1W9?WJvDa9ue~FUcoplA?n4Z|tnQY>!9JcKaU9 z^@`%OF|>7HBjvb2h&wL)TzhR2i;}jDhbK}hb9FzpEW+%Dp}jdF@topFWCX_ecxIJ- zVfACYDKDJ3At~>dL@mEKX<(;EiK3`6V)K?74DK%ZWvAkP-RdVbo5Fi~Q{4>}x3!91 zzftO!jrxV>U3z)0KRU^-y?5yr5W|jKMu)hA+E0UDwI|Q{teXi}Z?Pal%)~jX=ocIb0?kPB+fbmR)ELCdF;r{Ho{)Sz4;xT!^j0f> zpJ^mH+zP|Yo8ghYMh!F5LHx14gnR$UXe_h-)O}@y0Io#A;Y*O)w8ObaN74R>R{!~s zs^d=0SIehP<8s&xIoxYM|HxkZN%9xCkvBf8r%@qm+o4C#s0;U|0!0+{5cZAdjKvB` z40g)ft)6>2(hc&mU5gcoy0rN}!Ox;mc#@Is->5h2sCpN*TN2$iXtr2%+YnD=%4xG< z3wPQu{4;RXjm)$3_BpyTVBp5Na_;>Sd@PL=9&*@eeTw13U0%i>xuy=R3T6F(Gh)%p z;WVMpGeGDhCRG3U;e6doOO90XD+A>ro(20)+%xwZIFJf_*1Y23p^07O4X1(PB2{;=Xh0FsbKma*m}`b zp7ntdzD-)E=$YZ_0OxPq3$m{VFv=E!vvz8mTAeczxA(xAfK5K`=(gI(o@Zr_V=nFfHw{gl=5oi?bIOC92YO&IK78J=&9DxSv7EEQ&bpUv2Yg!K{ zBVXpH`i7s68!7(ivya>Q0Za0ynSfWxPnc22k_y2CMeWD>=j0v*%94J<&_waHC>N@LG7D z|Gc){9|g59@x-#5Ob#mf2q+6Nich>*ZqYdP@r*R>t({M<%+FeWN&sE_@ouE?JoN^` z!vT*M<$JOlU1-WDJlPk8tFVpZO4DsPEWf#Ms)MzKqEF2PJJBAZIGC&0GwmGl0HdH; zyW#AMr;D||#sMt;h1R+v!{K1%NjNPFygM2%V3(2B?FB1nN9MDc<6id1&+yBDrqDEzx<33V?bXkTxFWyr+~uRN z$9dlLX$t>D?-mO+6T{5mFfJ_-^9vuPCbY?qckztNnne4bk>W=?fYLsZdLDeY7x*=t z&5rtMPM!Tz*_&K0;_=w`ex>6+NuU|*s#)a? zr{Hw9j;`_%>5H3AFUE;S`7DisaxmT2E@&W`pKs^D(rs?+P1pC}Vf$=@vvyhl>uMhn zL)^qZh4-vgj-;81AXJB*KYY?Ex9EzSRzA=<6^19cQ9;&rd_lY56h8ECtF7`L0Ehlp za8mY>T70l=pq{qCMQ6CO0{0z{%5DYHem1A5c!URgyh|h2+!8~5^G2$maVKbwZWQcy$&``G~^BO(Ca3pwXrb_VyAtnP0)n!t|NG)ChJ*xda=`wD~?o-&q%NJdGP$2VL|3~LH58x+sNTduKsMQgF zK=2BOdWdc|Qe!$UgHEN=Q-UVln=in0Eh!Mb(voGdYvIxh9S=h5tXDIFNoKGTNYYfERNN&@H4kf*wrgO)@#RjYtt&0|$dS@LLlR2l9MQ%5*6k%GH`Tn7^@yKmE=>cen#hX#_uJu`SFw{s)A;W~-h!dV6s zzUC8eiowHSi*4TpEGV0&(`+UPWsV;TtFFR^uZYD{k6Ck6&eV(PG0c zZdQ(3gcL3}P7hBJ*#mT%;r^RZ)HJZN67KyU3nu@rrlknGLuoV|0m)QGZsdWZvP%DB zc*o(Amv*y;pimoYQo6t{q0EELo{D!SrC#`6k@?xVc=2;cF+Q`VSTTpz$<3n97CnKT z^&ckC!#Z2v3j5`1op;1P$$(k}nNYyHE4a>qgzNnCX*?fkivUu)vo-eo@RXyY`i77pO*SyK3f-cnI%aVf9LA#- z&dDleEv^B+1Ah@*TB;W7%wPrv$>h zo=Vw;Rgo;zw$K(7PNF+5+Yyj2Az7d}M!mIo{!AEM-zetYprcFr zd;1PcxY4||3LE^u(1eT~-0N3qk9|5UAtQr(ormL2Xqy!algqmy>;Jse#fNsk^;~Hp z)#S}?hME-YH^8=$iiXlJh!r!lwDw|RFfBu;i*`O#IstKf*uI6%1=6rY>bMKs2c}Wo zwDa}h+A!uRZA~DCH^6;jOeVPI><<58q9F1aEUG^u5zk{>Mo!M@1Yq<1vogbZal~L=n zmb>!)4kkH#9KM!;vpvR&d0k~6AeDsvl|z`^p?DR}cr9M0O@2Hgb4bHxPnYR?_=dHd>8+XWaOgwIWe&0~3lP)9+PUQ87@Fbo`S6u?W!FQH3L+{QV=lb zS{%EWFu$;2{TMtU?Oq#s#v-9SS7rbO5wOY|W`>I#l4-~3G*2kE7W3fRG?>Me2Y06p zo_segNCqjqo-Azu6+$(I#+SEGF#Ghn?=q;q*$TxRE|{s&x4of(kHyWdhOU z%Fd^!B2Rbic)w!e70x+QBk1sC=hHb+d!}|g%*iLQ!C^H`G|gn^LzNc9gQs@9b}_L6 zCmN|w0-+70?C1{AEfhBl$1yCuJPCR{*!iVq&eIK;Fb(m}?glv_-o7PHaK@sljLbNY zy@Jc}i>BudSJv`+s*QRG3RXJSX~!#y33?KdJ~YkhTOLAwd~OYqDH$A;fGdFhahf&+ z4CtaAZ&XasIZt{ffv5se=$yx!q8;x(Ddu7Do95;>0W}L@jX5rI&ZHOq_E4A<0U@Ar zG~>X1SmlsxNE9;%p49=_pdN8Y#XHP@fSz{zrDCEJs#Rd(cGHf(3yo~tm@q8C1*!7MT8RSeRV2ZaRXv)l&L&T7aLqgZZ>&F2h{_Afaql*_$B33y&HlDD zCWHa^;)e4&4wMn17au+}N+H^&!=-J=EQ;M&j9uEKa(po%S|!n&dkxGSF64zLLP6mE z*~5~*Ldezc)nOfgI=>RA_k&wxhLp4jDi*zK0=8j zT;e@0Q4VUQ_&QiKIH8n638D}x7?Jc@lW`wTuFS=bdH5y{{y8pYLmclgLCjoXP+TR`U&bHNhs%h4yC(D0cEl7uzh<0YHMNg7|zo_Tt>7} zSo9B^AN##sdBiq&5ZJ!93Ce&G>d%2!;`kld2NQW${FH|Mb8ZdxY76chm?+o<3&1|4 z(4&=-kH(r?3{~D6fYE)-C_GD_bp#mkCJ%~&`doC}Q;P|;jMA-8-wKtE%hP`NZrAVy zA-e;vZ}IZAVj#!}(rK_4Jo%v`Kp6Mu9KW%c*U^}Ez8@z3w>ByCYNe!02LQ1)FCYeY zn!2Hy8`jRQJRdmuP{{@~l2}{P=y`;k38m3&IQ3oD3a)$wy{!WICt~>* zLrM2z*hp@Il5?b7biEwC0WF4(2@|#x7A!Q+;nU%7g*vediV40ZHp$>Q<>>kyN-6~V zOD8`x=tCTU?-dA5VYnTY4HC2qgf(B2%tQ5AGcxeECxnaP!w&T+IxPHM3io5cvgL#X z)xb9rl2^Zav_JA1%u~y+Zr3K!WI(->Gfq9u`}@B4U2}gUg7+bzBTz^`2oy57tRqK& z0t5|ugg#(ufA}7OP&k|?6pDF1 z*Wyg0psn0AkC4I)CpQAqG_Aaqk$*n|L1~oG1=uML>9Epa$9DHoV7KzP1a0dM`_dN? zHT%9)YHb}d2x+f;xL>NzB2vl-`6Td0Wq3`GLv5xm%&wd^S1^=^1Dlne&1HSe0}7D4 z@W(QQQ-O4NmJZ7Y3S>TF(rk2AZkPEHycxaKQ1VOzY z<3-W=eT5ljbKaL_6Z z2X3<6=ZAl1OFsiQSMNV7^+GtqZ*H7?=PFXc!U#FP*zTWX&)Pfm$|vxKj%1)S=>m_K z^}MSl!t|Rlx&-ws>7rNGpKtBreLfjJ1k9;|1tFSpx6L2ER<)~t*<8~bxmy{^q44nD z?rr|hCY=0h$BOlueK_b1=<#^r663Dtf9YO5@%7+?t4M-J$UW(CX2hYE;`v`wH=8DD zvQWQZ5*5$An7aAh+G8ibJzMgfjjTXlZKvE!#@}^Vv*KNg5ugg-MZM?Vk<{e(efrZ5}?ew>$j`Ne#T@!6f;PmeSB2Ef51^Sb>9kF$qTKRKSRM>w~kqBj4L z(GeSxY{OWmZ^0=IJWBNpFj%(XZNwk$s)+*->@*_Dz;ztgcm5nS2nM4s8^yBSpv#it zW8`G-LGiJdzGxtPoLzQ2pu;Mx4EFDVy`|Oma^ttfaNq@C)>8)M_Ze{p;V%#&qhmWV z4t|*rcRE3&eRryouWzzJcy2-6NgmfjpH5~-@kYE#U@{w9TcrDx$B;S#lw@*ssPXAIM1R*TJ#pFRNpGX!axvr|p~a#V`^Jufok0MDe-}FO z_ki#}F4zwK{k&QA^O{F5Snols`j*gtb0PEpwp04!*^WWln5p*r^MhYd+ETXq&V)>T ztVhY<6&Qv8Maxwh4sTDe@+zgpwpZV#F;-tj_+LQNzP^q-YqjHFv~Ikn%yd8HDvA3) Dw?19& literal 0 HcmV?d00001 diff --git a/Tests/images/drawing_wmf_ref.png b/Tests/images/drawing_wmf_ref.png new file mode 100644 index 0000000000000000000000000000000000000000..207160de0d33ec60eb038a5bc7988c6bc28ddc70 GIT binary patch literal 531 zcmV+u0_^>XP)Y0005nNklTSrsyoowwgOkt~`U?tyz;LPi1#*)}+gm*e1-HMENz_ zkXe%=zhIj)YcAz6wo$VtE&Kgpt#LL}oXr$xGo_<{ z&_5OdrVv8mgAkTOI^@n~inE#GY^Kn$1qdNb00{3`=-SqBvq|P$zRx0ax6PIxd-yq* z#nblmZDZ)dW&OhnZyUg%h0E?YG~V_%gB8B78fR?}BR@35hkTm>3O}nF@wQFNBW7F< zxlwuCjFiu|Ir*g-iC<}kqE$F`@^_PCWhXf6IWu!Gj|8vX5;_7BUZ V=6D`xo3j7_002ovPDHLkV1n2=1BU Date: Sun, 27 Nov 2016 16:07:37 +0000 Subject: [PATCH 117/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0af77f439..aa1d8f355 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- EMF: support negative bounding box coordinates #2249 + [glexey] + +- Close file if opened in WalImageFile #2216 + [radarhere] + - Use Image._new() instead of _makeself() #2248 [homm] From 6145a7216be04d3dbad764cfd61d55b94d7870af Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 27 Nov 2016 16:29:05 +0000 Subject: [PATCH 118/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aa1d8f355..8762f4db8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Doc: Improved description of ImageOps.deform resample parameter #2256 + [radarhere] + - EMF: support negative bounding box coordinates #2249 [glexey] From 01392fcb2e72e839e0f38cf3e28dd990b989e858 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Nov 2016 19:35:53 +0000 Subject: [PATCH 119/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8762f4db8..7d26efb5d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix TIFFImagePlugin ICC color profile saving. #2087 + [cskau] + - Doc: Improved description of ImageOps.deform resample parameter #2256 [radarhere] From 4d6c2d5601956fca2984f7378ab1d156886479aa Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Nov 2016 14:37:50 +0200 Subject: [PATCH 120/267] Test saving 256x256 icons --- Tests/test_file_ico.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 94b8c44e3..dc5c041b2 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -48,6 +48,19 @@ class TestFileIco(PillowTestCase): self.assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) + def test_save_256x256(self): + """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" + # Arrange + im = Image.open("Tests/images/hopper_256x256.ico") + outfile = self.tempfile("temp_saved_hopper_256x256.ico") + + # Act + im.save(outfile) + im_saved = Image.open(outfile) + + # Assert + self.assertEqual(im_saved.size, (256, 256)) + if __name__ == '__main__': unittest.main() From 4061c3836c4435171c70c53eda6d1dd2264f133a Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Nov 2016 14:38:04 +0200 Subject: [PATCH 121/267] Created with IrfanView --- Tests/images/hopper_256x256.ico | Bin 0 -> 204862 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper_256x256.ico diff --git a/Tests/images/hopper_256x256.ico b/Tests/images/hopper_256x256.ico new file mode 100644 index 0000000000000000000000000000000000000000..2c08b1f3cf7179379929016b53cb635bf78174aa GIT binary patch literal 204862 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≻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 Date: Wed, 30 Nov 2016 14:50:53 +0200 Subject: [PATCH 122/267] Fix saving 256x256 icons --- PIL/IcoImagePlugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index b278a85bf..c48465620 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -44,7 +44,7 @@ def _save(im, fp, filename): fp.write(_MAGIC) # (2+2) sizes = im.encoderinfo.get("sizes", [(16, 16), (24, 24), (32, 32), (48, 48), - (64, 64), (128, 128), (255, 255)]) + (64, 64), (128, 128), (256, 256)]) width, height = im.size filter(lambda x: False if (x[0] > width or x[1] > height or x[0] > 255 or x[1] > 255) else True, sizes) @@ -52,8 +52,9 @@ def _save(im, fp, filename): offset = fp.tell() + len(sizes)*16 for size in sizes: width, height = size - fp.write(struct.pack("B", width)) # bWidth(1) - fp.write(struct.pack("B", height)) # bHeight(1) + # 0 means 256 + fp.write(struct.pack("B", width if width < 256 else 0)) # bWidth(1) + fp.write(struct.pack("B", height if height < 256 else 0)) # bHeight(1) fp.write(b"\0") # bColorCount(1) fp.write(b"\0") # bReserved(1) fp.write(b"\0\0") # wPlanes(2) From 43fc9c9b597b11796452ff6d243b1544ace36d62 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Nov 2016 14:51:30 +0200 Subject: [PATCH 123/267] flake8 --- PIL/IcoImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index c48465620..9d4ebb6e8 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -216,13 +216,13 @@ class IcoFile(object): total_bytes = int((w * im.size[1]) / 8) self.buf.seek(and_mask_offset) - maskData = self.buf.read(total_bytes) + mask_data = self.buf.read(total_bytes) # convert raw data to image mask = Image.frombuffer( '1', # 1 bpp im.size, # (w, h) - maskData, # source chars + mask_data, # source chars 'raw', # raw decoder ('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed ) @@ -279,6 +279,7 @@ class IcoImageFile(ImageFile.ImageFile): # # -------------------------------------------------------------------- + Image.register_open(IcoImageFile.format, IcoImageFile, _accept) Image.register_save(IcoImageFile.format, _save) Image.register_extension(IcoImageFile.format, ".ico") From b341898c7f53d5cc776bd601961500c95e0c54a9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Nov 2016 16:00:14 +0200 Subject: [PATCH 124/267] Allow 256x256 sizes --- PIL/IcoImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 9d4ebb6e8..5dbf31f9f 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -47,7 +47,7 @@ def _save(im, fp, filename): (64, 64), (128, 128), (256, 256)]) width, height = im.size filter(lambda x: False if (x[0] > width or x[1] > height or - x[0] > 255 or x[1] > 255) else True, sizes) + x[0] > 256 or x[1] > 256) else True, sizes) fp.write(struct.pack(" Date: Wed, 30 Nov 2016 16:36:04 +0200 Subject: [PATCH 125/267] Test case for #2266 --- Tests/test_file_ico.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index dc5c041b2..3904340f3 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -61,6 +61,24 @@ class TestFileIco(PillowTestCase): # Assert self.assertEqual(im_saved.size, (256, 256)) + def test_only_save_relevant_sizes(self): + """Issue #2266 https://github.com/python-pillow/Pillow/issues/2266 + Should save in 16x16, 24x24, 32x32, 48x48 sizes + and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes + """ + # Arrange + im = Image.open("Tests/images/python.ico") # 16x16, 32x32, 48x48 + outfile = self.tempfile("temp_saved_python.ico") + + # Act + im.save(outfile) + im_saved = Image.open(outfile) + + # Assert + self.assertEqual( + im_saved.info['sizes'], + set([(16, 16), (24, 24), (32, 32), (48, 48)])) + if __name__ == '__main__': unittest.main() From 880464f46c231e4e1c74abf61613af1306be1128 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Nov 2016 16:41:43 +0200 Subject: [PATCH 126/267] Only save relevant sizes --- PIL/IcoImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 5dbf31f9f..195bbe42e 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -46,8 +46,9 @@ def _save(im, fp, filename): [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]) width, height = im.size - filter(lambda x: False if (x[0] > width or x[1] > height or - x[0] > 256 or x[1] > 256) else True, sizes) + sizes = filter(lambda x: False if (x[0] > width or x[1] > height or + x[0] > 256 or x[1] > 256) else True, + sizes) fp.write(struct.pack(" Date: Wed, 30 Nov 2016 16:50:44 +0200 Subject: [PATCH 127/267] Python 3 compatibility --- PIL/IcoImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 195bbe42e..3436ae84f 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -49,6 +49,7 @@ def _save(im, fp, filename): sizes = filter(lambda x: False if (x[0] > width or x[1] > height or x[0] > 256 or x[1] > 256) else True, sizes) + sizes = list(sizes) fp.write(struct.pack(" Date: Mon, 14 Nov 2016 07:00:17 -0800 Subject: [PATCH 128/267] design docs for file closing --- docs/reference/index.rst | 1 + docs/reference/internal_design.rst | 7 ++ docs/reference/open_files.rst | 124 +++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 docs/reference/internal_design.rst create mode 100644 docs/reference/open_files.rst diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 555bd2a57..1dffcc9e2 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -33,3 +33,4 @@ Reference PyAccess ../PIL plugins + internal_design diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst new file mode 100644 index 000000000..4672d1886 --- /dev/null +++ b/docs/reference/internal_design.rst @@ -0,0 +1,7 @@ +Internal Reference Docs +======================= + +.. toctree:: + :maxdepth: 2 + + open_files diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst new file mode 100644 index 000000000..76130fbca --- /dev/null +++ b/docs/reference/open_files.rst @@ -0,0 +1,124 @@ +File Handling in Pillow +======================= + +When opening a file as an image, Pillow requires a filename, +pathlib.Path object, or a file-like object. Pillow uses the filename +or Path to open a file, so for the rest of this article, they will all +be treated as a file-like object. + +The first four of these items are equivalent, the last is dangerous +and may fail:: + + from PIL import Image + import io + import pathlib + + im = Image.open('test.jpg') + + im2 = Image.open(pathlib.Path('test.jpg')) + + f = open('test.jpg', 'rb') + im3 = Image.open(f) + + with open('test.jpg', 'rb') as f: + im4 = Image.open(io.BytesIO(f.read())) + + # Dangerous FAIL: + with open('test.jpg', 'rb') as f: + im5 = Image.open(f) + im5.load() # FAILS, closed file + +The documentation specifies that the file will be closed after the +``Image.Image.load()`` method is called. This is an aspirational +specification rather than an accurate reflection of the state of the +code. + +Pillow cannot in general close and reopen a file, so any access to +that file needs to be prior to the close. + +Issues +------ + +The current open file handling is inconsistent at best: + +* Most of the image plugins do not close the input file. +* Multi-frame images behave badly when seeking through the file, as + it's legal to seek backward in the file until the last image is + read, and then it's not. +* Using the file context manager to provide a file-like object to + Pillow is dangerous unless the context of the image is limited to + the context of the file. + +Image Lifecycle +--------------- + +* ``Image.open()`` called. Path-like objects are opened as a + file. Metadata is read from the open file. The file is left open for + further usage. + +* ``Image.Image.load()`` when the pixel data from the image is + required, ``load()`` is called. The current frame is read into + memory. The image can now be used independently of the underlying + image file. + +* ``Image.Image.seek()`` in the case of multi-frame images + (e.g. multipage TIFF and animated GIF) the image file left open so + that seek can load the appropriate frame. When the last frame is + read, the image file is closed, and no more seeks can occur. + +* ``Image.Image.close()`` Closes the file pointer and destroys the + core image object. This is used in the Pillow context manager + support. e.g.:: + + with Image.open('test.jpg') as img: + ... # image operations here. + + +The lifecycle of a single frame image is relatively simple. The file +must remain open until the ``load()`` or ``close()`` function is +called. + +Multi-frame images are more complicated. The ``load()`` method is not +a terminal method, so it should not close the underlying file. The +current behavior of ``seek()`` closing the underlying file on +accessing the last frame is presumably a heuristic for closing the +file after iterating through the entire sequence. In general, Pillow +does not know if there are going to be any requests for additional +data until the caller has explicitly closed the image. + + +Complications +------------- + +* TiffImagePlugin has some code to pass the underlying file descriptor + into libtiff (if working on an actual file). Since libtiff closes + the file descriptor internally, it is duplicated prior to passing it + into libtiff. + +* ``decoder.handles_eof`` This slightly misnamed flag indicates that + the decoder wants to be called with a 0 length buffer when reads are + done. Despite the comments in ``ImageFile.load()``, the only decoder + that actually uses this flag is the Jpeg2K decoder. The use of this + flag in Jpeg2K predated the change to the decoder that added the + pulls_fd flag, and is therefore not used. + +* I don't think that there's any way to make this safe without + changing the lazy loading:: + + # Dangerous FAIL: + with open('test.jpg', 'rb') as f: + im5 = Image.open(f) + im5.load() # FAILS, closed file + + +Proposed File Handling +---------------------- + +* ``Image.Image.load()`` should close the image file, unless there are + multiple frames. + +* ``Image.Image.seek()`` should never close the image file. + +* Users of the library should call ``Image.Image.close()`` on any + multi-frame image to ensure that the underlying file is closed. + From cf85e8f1c760d338ba63d985d739b7e985787fed Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Nov 2016 07:26:52 -0800 Subject: [PATCH 129/267] design docs for file closing --- docs/reference/open_files.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index 76130fbca..143eb7209 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -64,7 +64,8 @@ Image Lifecycle * ``Image.Image.seek()`` in the case of multi-frame images (e.g. multipage TIFF and animated GIF) the image file left open so that seek can load the appropriate frame. When the last frame is - read, the image file is closed, and no more seeks can occur. + read, the image file is closed (at least in some image plugins), and + no more seeks can occur. * ``Image.Image.close()`` Closes the file pointer and destroys the core image object. This is used in the Pillow context manager From 711e95e3612b7feb1dceae5d6bf776d136304063 Mon Sep 17 00:00:00 2001 From: Chris Hogan Date: Thu, 1 Dec 2016 11:10:03 -0600 Subject: [PATCH 130/267] Fix bug in test_idf_rational_save A boolean wrapped in parentheses is still a boolean, not a tuple. The comma makes this an actual tuple so it can be iterated on in the for loop. --- Tests/test_tiff_ifdrational.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 7e2c908b5..ffd4f9eac 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -47,7 +47,7 @@ class Test_IFDRational(PillowTestCase): def test_ifd_rational_save(self): methods = (True, False) if 'libtiff_encoder' not in dir(Image.core): - methods = (False) + methods = (False,) for libtiff in methods: TiffImagePlugin.WRITE_LIBTIFF = libtiff From 937199e54556dae5882e3d2a65d804365a8cca67 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 1 Dec 2016 09:11:04 -0800 Subject: [PATCH 131/267] document limits --- docs/reference/internal_design.rst | 1 + docs/reference/limits.rst | 41 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 docs/reference/limits.rst diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst index 4672d1886..a8d6e2284 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -5,3 +5,4 @@ Internal Reference Docs :maxdepth: 2 open_files + limits diff --git a/docs/reference/limits.rst b/docs/reference/limits.rst new file mode 100644 index 000000000..6f81c9e65 --- /dev/null +++ b/docs/reference/limits.rst @@ -0,0 +1,41 @@ +Limits +------ + +This page is documentation to the various fundamental size limits in +the Pillow implementation. + +Internal Limits +=============== + +* Image sizes cannot be negative. These are checked both in + ``Storage.c`` and ``Image.py`` + +* Image sizes may be 0. (At least, prior to 3.4) + +* Maximum pixel dimensions are limited to INT32, or 2^31 by the sizes + in the image header. + +* Individual allocations are limited to 2GB in ``Storage.c`` + +* The 2GB allocation puts an upper limit to the xsize of the image of + either 2^31 for 'L' or 2^29 for 'RGB' + +* Individual memory mapped segments are limited to 2GB in map.c based + on the overflow checks. This requires that any memory mapped image + is smaller than 2GB, as calculated by ``y*stride`` (so 2Gpx for 'L' + images, and .5Gpx for 'RGB' + +* Any call to internal python size functions for buffers or strings + are currently returned as int32, not py_ssize_t. This limits the + maximum buffer to 2GB for operations like frombytes and frombuffer. + +* This also limits the size of buffers converted using a + decoder. (decode.c:127) + +Format Size Limits +================== + +* ICO: Max size is 256x256 + +* Webp: 16383x16383 (underlying library size limit: + https://developers.google.com/speed/webp/docs/api) From 132412fdf39f5a79e570d703d19691b485db5cca Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 1 Dec 2016 17:19:36 +0000 Subject: [PATCH 132/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7d26efb5d..fa8b07d44 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- ICO: Only save relevant sizes #2267 + [hugovk] + +- ICO: Allow saving .ico files of 256x256 instead of 255x255 #2265 + [hugovk] + - Fix TIFFImagePlugin ICC color profile saving. #2087 [cskau] From e4623c425e64dd2fa97fe02db96fa37256ef8197 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 2 Dec 2016 09:01:00 +1100 Subject: [PATCH 133/267] Fixed typo --- Tk/_tkmini.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tk/_tkmini.h b/Tk/_tkmini.h index 348425e41..adc470532 100644 --- a/Tk/_tkmini.h +++ b/Tk/_tkmini.h @@ -59,7 +59,7 @@ /* * Unless otherwise noted, these definitions are stable from Tcl / Tk 8.4 - * through Tck / Tk master as of 21 May 2016 + * through Tcl / Tk master as of 21 May 2016 */ #ifdef __cplusplus From 15347b6703ea804f68de4fd2762462ced45b2cff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 2 Dec 2016 10:27:38 +1100 Subject: [PATCH 134/267] Fixed typo [ci skip] --- docs/reference/TiffTags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/TiffTags.rst b/docs/reference/TiffTags.rst index 9518461dd..3b261625a 100644 --- a/docs/reference/TiffTags.rst +++ b/docs/reference/TiffTags.rst @@ -4,7 +4,7 @@ :py:mod:`TiffTags` Module ========================= -The :py:mod:`TiffTags` module exposes many of the stantard TIFF +The :py:mod:`TiffTags` module exposes many of the standard TIFF metadata tag numbers, names, and type information. .. method:: lookup(tag) From d48dabfd9714b77a7fdd2e9648797ae52fd1af40 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 13:39:24 +0000 Subject: [PATCH 135/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fa8b07d44..8687c0fc8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Test: Fix bug in test_ifd_rational_save when libtiff is not available #2270 + [ChristopherHogan] + - ICO: Only save relevant sizes #2267 [hugovk] From 8e7dd8ef1edaddc881da2da063df37d4383601ae Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 13:45:26 +0000 Subject: [PATCH 136/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8687c0fc8..22cd34ca7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Tiff: Update info.icc_profile when using libtiff reader. #2193 + [lambdafu] + - Test: Fix bug in test_ifd_rational_save when libtiff is not available #2270 [ChristopherHogan] From b3f8b5fa7d6acac15b57692e73a76de5cbf0edcd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 14:07:49 +0000 Subject: [PATCH 137/267] tabs->spaces --- libImaging/Pack.c | 422 +++++++++++++++++++++++----------------------- 1 file changed, 211 insertions(+), 211 deletions(-) diff --git a/libImaging/Pack.c b/libImaging/Pack.c index 8a0fcc004..c768bc27d 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -1,4 +1,4 @@ -/* + /* * The Python Imaging Library. * $Id$ * @@ -28,16 +28,16 @@ #include "Imaging.h" -#define R 0 -#define G 1 -#define B 2 -#define X 3 -#define A 3 +#define R 0 +#define G 1 +#define B 2 +#define X 3 +#define A 3 -#define C 0 -#define M 1 -#define Y 2 -#define K 3 +#define C 0 +#define M 1 +#define Y 2 +#define K 3 /* byte swapping macros */ @@ -83,16 +83,16 @@ pack1(UINT8* out, const UINT8* in, int pixels) /* bilevel (black is 0) */ b = 0; m = 128; for (i = 0; i < pixels; i++) { - if (in[i] != 0) - b |= m; - m >>= 1; - if (m == 0) { - *out++ = b; - b = 0; m = 128; - } + if (in[i] != 0) + b |= m; + m >>= 1; + if (m == 0) { + *out++ = b; + b = 0; m = 128; + } } if (m != 128) - *out++ = b; + *out++ = b; } static void @@ -102,16 +102,16 @@ pack1I(UINT8* out, const UINT8* in, int pixels) /* bilevel (black is 1) */ b = 0; m = 128; for (i = 0; i < pixels; i++) { - if (in[i] == 0) - b |= m; - m >>= 1; - if (m == 0) { - *out++ = b; - b = 0; m = 128; - } + if (in[i] == 0) + b |= m; + m >>= 1; + if (m == 0) { + *out++ = b; + b = 0; m = 128; + } } if (m != 128) - *out++ = b; + *out++ = b; } static void @@ -121,16 +121,16 @@ pack1R(UINT8* out, const UINT8* in, int pixels) /* bilevel, lsb first (black is 0) */ b = 0; m = 1; for (i = 0; i < pixels; i++) { - if (in[i] != 0) - b |= m; - m <<= 1; - if (m == 256){ - *out++ = b; - b = 0; m = 1; - } + if (in[i] != 0) + b |= m; + m <<= 1; + if (m == 256){ + *out++ = b; + b = 0; m = 1; + } } if (m != 1) - *out++ = b; + *out++ = b; } static void @@ -140,16 +140,16 @@ pack1IR(UINT8* out, const UINT8* in, int pixels) /* bilevel, lsb first (black is 1) */ b = 0; m = 1; for (i = 0; i < pixels; i++) { - if (in[i] == 0) - b |= m; - m <<= 1; - if (m == 256){ - *out++ = b; - b = 0; m = 1; - } + if (in[i] == 0) + b |= m; + m <<= 1; + if (m == 256){ + *out++ = b; + b = 0; m = 1; + } } if (m != 1) - *out++ = b; + *out++ = b; } static void @@ -158,44 +158,44 @@ pack1L(UINT8* out, const UINT8* in, int pixels) int i; /* bilevel, stored as bytes */ for (i = 0; i < pixels; i++) - out[i] = (in[i] != 0) ? 255 : 0; + out[i] = (in[i] != 0) ? 255 : 0; } static void packP4(UINT8* out, const UINT8* in, int pixels) { while (pixels >= 2) { - *out++ = (in[0] << 4) | - (in[1] & 15); - in += 2; pixels -= 2; + *out++ = (in[0] << 4) | + (in[1] & 15); + in += 2; pixels -= 2; } if (pixels) - out[0] = (in[0] << 4); + out[0] = (in[0] << 4); } static void packP2(UINT8* out, const UINT8* in, int pixels) { while (pixels >= 4) { - *out++ = (in[0] << 6) | - ((in[1] & 3) << 4) | - ((in[2] & 3) << 2) | - (in[3] & 3); - in += 4; pixels -= 4; + *out++ = (in[0] << 6) | + ((in[1] & 3) << 4) | + ((in[2] & 3) << 2) | + (in[3] & 3); + in += 4; pixels -= 4; } switch (pixels) { case 3: - out[0] = (in[0] << 6) | - ((in[1] & 3) << 4) | - ((in[2] & 3) << 2); - break; + out[0] = (in[0] << 6) | + ((in[1] & 3) << 4) | + ((in[2] & 3) << 2); + break; case 2: - out[0] = (in[0] << 6) | - ((in[1] & 3) << 4); + out[0] = (in[0] << 6) | + ((in[1] & 3) << 4); case 1: - out[0] = (in[0] << 6); + out[0] = (in[0] << 6); } } @@ -205,9 +205,9 @@ packLA(UINT8* out, const UINT8* in, int pixels) int i; /* LA, pixel interleaved */ for (i = 0; i < pixels; i++) { - out[0] = in[R]; - out[1] = in[A]; - out += 2; in += 4; + out[0] = in[R]; + out[1] = in[A]; + out += 2; in += 4; } } @@ -217,9 +217,9 @@ packLAL(UINT8* out, const UINT8* in, int pixels) int i; /* LA, line interleaved */ for (i = 0; i < pixels; i++) { - out[i] = in[R]; - out[i+pixels] = in[A]; - in += 4; + out[i] = in[R]; + out[i+pixels] = in[A]; + in += 4; } } @@ -229,10 +229,10 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) int i; /* RGB triplets */ for (i = 0; i < pixels; i++) { - out[0] = in[R]; - out[1] = in[G]; - out[2] = in[B]; - out += 3; in += 4; + out[0] = in[R]; + out[1] = in[G]; + out[2] = in[B]; + out += 3; in += 4; } } @@ -242,11 +242,11 @@ ImagingPackXRGB(UINT8* out, const UINT8* in, int pixels) int i; /* XRGB, triplets with left padding */ for (i = 0; i < pixels; i++) { - out[0] = 0; - out[1] = in[R]; - out[2] = in[G]; - out[3] = in[B]; - out += 4; in += 4; + out[0] = 0; + out[1] = in[R]; + out[2] = in[G]; + out[3] = in[B]; + out += 4; in += 4; } } @@ -256,10 +256,10 @@ ImagingPackBGR(UINT8* out, const UINT8* in, int pixels) int i; /* RGB, reversed bytes */ for (i = 0; i < pixels; i++) { - out[0] = in[B]; - out[1] = in[G]; - out[2] = in[R]; - out += 3; in += 4; + out[0] = in[B]; + out[1] = in[G]; + out[2] = in[R]; + out += 3; in += 4; } } @@ -269,11 +269,11 @@ ImagingPackBGRX(UINT8* out, const UINT8* in, int pixels) int i; /* BGRX, reversed bytes with right padding */ for (i = 0; i < pixels; i++) { - out[0] = in[B]; - out[1] = in[G]; - out[2] = in[R]; - out[3] = 0; - out += 4; in += 4; + out[0] = in[B]; + out[1] = in[G]; + out[2] = in[R]; + out[3] = 0; + out += 4; in += 4; } } @@ -283,11 +283,11 @@ ImagingPackXBGR(UINT8* out, const UINT8* in, int pixels) int i; /* XBGR, reversed bytes with left padding */ for (i = 0; i < pixels; i++) { - out[0] = 0; - out[1] = in[B]; - out[2] = in[G]; - out[3] = in[R]; - out += 4; in += 4; + out[0] = 0; + out[1] = in[B]; + out[2] = in[G]; + out[3] = in[R]; + out += 4; in += 4; } } @@ -297,11 +297,11 @@ ImagingPackBGRA(UINT8* out, const UINT8* in, int pixels) int i; /* BGRX, reversed bytes with right padding */ for (i = 0; i < pixels; i++) { - out[0] = in[B]; - out[1] = in[G]; - out[2] = in[R]; - out[3] = in[A]; - out += 4; in += 4; + out[0] = in[B]; + out[1] = in[G]; + out[2] = in[R]; + out[3] = in[A]; + out += 4; in += 4; } } @@ -311,11 +311,11 @@ ImagingPackABGR(UINT8* out, const UINT8* in, int pixels) int i; /* XBGR, reversed bytes with left padding */ for (i = 0; i < pixels; i++) { - out[0] = in[A]; - out[1] = in[B]; - out[2] = in[G]; - out[3] = in[R]; - out += 4; in += 4; + out[0] = in[A]; + out[1] = in[B]; + out[2] = in[G]; + out[3] = in[R]; + out += 4; in += 4; } } @@ -340,10 +340,10 @@ packRGBL(UINT8* out, const UINT8* in, int pixels) int i; /* RGB, line interleaved */ for (i = 0; i < pixels; i++) { - out[i] = in[R]; - out[i+pixels] = in[G]; - out[i+pixels+pixels] = in[B]; - in += 4; + out[i] = in[R]; + out[i+pixels] = in[G]; + out[i+pixels+pixels] = in[B]; + in += 4; } } @@ -353,11 +353,11 @@ packRGBXL(UINT8* out, const UINT8* in, int pixels) int i; /* RGBX, line interleaved */ for (i = 0; i < pixels; i++) { - out[i] = in[R]; - out[i+pixels] = in[G]; - out[i+pixels+pixels] = in[B]; - out[i+pixels+pixels+pixels] = in[X]; - in += 4; + out[i] = in[R]; + out[i+pixels] = in[G]; + out[i+pixels+pixels] = in[B]; + out[i+pixels+pixels+pixels] = in[X]; + in += 4; } } @@ -376,7 +376,7 @@ packI16B(UINT8* out, const UINT8* in_, int pixels) else tmp_ = in[0]; C16B; - out += 2; in++; + out += 2; in++; } } @@ -386,7 +386,7 @@ packI16N_I16B(UINT8* out, const UINT8* in, int pixels){ UINT8* tmp = (UINT8*) in; for (i = 0; i < pixels; i++) { C16B; - out += 2; tmp += 2; + out += 2; tmp += 2; } } @@ -396,7 +396,7 @@ packI16N_I16(UINT8* out, const UINT8* in, int pixels){ UINT8* tmp = (UINT8*) in; for (i = 0; i < pixels; i++) { C16L; - out += 2; tmp += 2; + out += 2; tmp += 2; } } @@ -408,7 +408,7 @@ packI32S(UINT8* out, const UINT8* in, int pixels) UINT8* tmp = (UINT8*) in; for (i = 0; i < pixels; i++) { C32L; - out += 4; tmp += 4; + out += 4; tmp += 4; } } @@ -418,10 +418,10 @@ ImagingPackLAB(UINT8* out, const UINT8* in, int pixels) int i; /* LAB triplets */ for (i = 0; i < pixels; i++) { - out[0] = in[0]; - out[1] = in[1] ^ 128; /* signed in outside world */ - out[2] = in[2] ^ 128; - out += 3; in += 4; + out[0] = in[0]; + out[1] = in[1] ^ 128; /* signed in outside world */ + out[2] = in[2] ^ 128; + out += 3; in += 4; } } @@ -459,7 +459,7 @@ copy4I(UINT8* out, const UINT8* in, int pixels) /* RGBA, CMYK quadruples, inverted */ int i; for (i = 0; i < pixels*4; i++) - out[i] = ~in[i]; + out[i] = ~in[i]; } static void @@ -467,7 +467,7 @@ band0(UINT8* out, const UINT8* in, int pixels) { int i; for (i = 0; i < pixels; i++, in += 4) - out[i] = in[0]; + out[i] = in[0]; } static void @@ -475,7 +475,7 @@ band1(UINT8* out, const UINT8* in, int pixels) { int i; for (i = 0; i < pixels; i++, in += 4) - out[i] = in[1]; + out[i] = in[1]; } static void @@ -483,7 +483,7 @@ band2(UINT8* out, const UINT8* in, int pixels) { int i; for (i = 0; i < pixels; i++, in += 4) - out[i] = in[2]; + out[i] = in[2]; } static void @@ -491,7 +491,7 @@ band3(UINT8* out, const UINT8* in, int pixels) { int i; for (i = 0; i < pixels; i++, in += 4) - out[i] = in[3]; + out[i] = in[3]; } static struct { @@ -502,122 +502,122 @@ static struct { } packers[] = { /* bilevel */ - {"1", "1", 1, pack1}, - {"1", "1;I", 1, pack1I}, - {"1", "1;R", 1, pack1R}, - {"1", "1;IR", 1, pack1IR}, - {"1", "L", 8, pack1L}, + {"1", "1", 1, pack1}, + {"1", "1;I", 1, pack1I}, + {"1", "1;R", 1, pack1R}, + {"1", "1;IR", 1, pack1IR}, + {"1", "L", 8, pack1L}, /* greyscale */ - {"L", "L", 8, copy1}, + {"L", "L", 8, copy1}, /* greyscale w. alpha */ - {"LA", "LA", 16, packLA}, - {"LA", "LA;L", 16, packLAL}, + {"LA", "LA", 16, packLA}, + {"LA", "LA;L", 16, packLAL}, /* palette */ - {"P", "P;1", 1, pack1}, - {"P", "P;2", 2, packP2}, - {"P", "P;4", 4, packP4}, - {"P", "P", 8, copy1}, + {"P", "P;1", 1, pack1}, + {"P", "P;2", 2, packP2}, + {"P", "P;4", 4, packP4}, + {"P", "P", 8, copy1}, /* palette w. alpha */ - {"PA", "PA", 16, packLA}, - {"PA", "PA;L", 16, packLAL}, + {"PA", "PA", 16, packLA}, + {"PA", "PA;L", 16, packLAL}, /* true colour */ - {"RGB", "RGB", 24, ImagingPackRGB}, - {"RGB", "RGBX", 32, copy4}, - {"RGB", "XRGB", 32, ImagingPackXRGB}, - {"RGB", "BGR", 24, ImagingPackBGR}, - {"RGB", "BGRX", 32, ImagingPackBGRX}, - {"RGB", "XBGR", 32, ImagingPackXBGR}, - {"RGB", "RGB;L", 24, packRGBL}, - {"RGB", "R", 8, band0}, - {"RGB", "G", 8, band1}, - {"RGB", "B", 8, band2}, + {"RGB", "RGB", 24, ImagingPackRGB}, + {"RGB", "RGBX", 32, copy4}, + {"RGB", "XRGB", 32, ImagingPackXRGB}, + {"RGB", "BGR", 24, ImagingPackBGR}, + {"RGB", "BGRX", 32, ImagingPackBGRX}, + {"RGB", "XBGR", 32, ImagingPackXBGR}, + {"RGB", "RGB;L", 24, packRGBL}, + {"RGB", "R", 8, band0}, + {"RGB", "G", 8, band1}, + {"RGB", "B", 8, band2}, /* true colour w. alpha */ - {"RGBA", "RGBA", 32, copy4}, - {"RGBA", "RGBA;L", 32, packRGBXL}, - {"RGBA", "RGB", 24, ImagingPackRGB}, - {"RGBA", "BGR", 24, ImagingPackBGR}, - {"RGBA", "BGRA", 32, ImagingPackBGRA}, - {"RGBA", "ABGR", 32, ImagingPackABGR}, - {"RGBA", "BGRa", 32, ImagingPackBGRa}, - {"RGBA", "R", 8, band0}, - {"RGBA", "G", 8, band1}, - {"RGBA", "B", 8, band2}, - {"RGBA", "A", 8, band3}, + {"RGBA", "RGBA", 32, copy4}, + {"RGBA", "RGBA;L", 32, packRGBXL}, + {"RGBA", "RGB", 24, ImagingPackRGB}, + {"RGBA", "BGR", 24, ImagingPackBGR}, + {"RGBA", "BGRA", 32, ImagingPackBGRA}, + {"RGBA", "ABGR", 32, ImagingPackABGR}, + {"RGBA", "BGRa", 32, ImagingPackBGRa}, + {"RGBA", "R", 8, band0}, + {"RGBA", "G", 8, band1}, + {"RGBA", "B", 8, band2}, + {"RGBA", "A", 8, band3}, /* true colour w. alpha premultiplied */ - {"RGBa", "RGBa", 32, copy4}, - {"RGBa", "BGRa", 32, ImagingPackBGRA}, - {"RGBa", "aBGR", 32, ImagingPackABGR}, + {"RGBa", "RGBa", 32, copy4}, + {"RGBa", "BGRa", 32, ImagingPackBGRA}, + {"RGBa", "aBGR", 32, ImagingPackABGR}, /* true colour w. padding */ - {"RGBX", "RGBX", 32, copy4}, - {"RGBX", "RGBX;L", 32, packRGBXL}, - {"RGBX", "RGB", 32, ImagingPackRGB}, - {"RGBX", "BGR", 32, ImagingPackBGR}, - {"RGBX", "BGRX", 32, ImagingPackBGRX}, - {"RGBX", "XBGR", 32, ImagingPackXBGR}, - {"RGBX", "R", 8, band0}, - {"RGBX", "G", 8, band1}, - {"RGBX", "B", 8, band2}, - {"RGBX", "X", 8, band3}, + {"RGBX", "RGBX", 32, copy4}, + {"RGBX", "RGBX;L", 32, packRGBXL}, + {"RGBX", "RGB", 32, ImagingPackRGB}, + {"RGBX", "BGR", 32, ImagingPackBGR}, + {"RGBX", "BGRX", 32, ImagingPackBGRX}, + {"RGBX", "XBGR", 32, ImagingPackXBGR}, + {"RGBX", "R", 8, band0}, + {"RGBX", "G", 8, band1}, + {"RGBX", "B", 8, band2}, + {"RGBX", "X", 8, band3}, /* colour separation */ - {"CMYK", "CMYK", 32, copy4}, - {"CMYK", "CMYK;I", 32, copy4I}, - {"CMYK", "CMYK;L", 32, packRGBXL}, - {"CMYK", "C", 8, band0}, - {"CMYK", "M", 8, band1}, - {"CMYK", "Y", 8, band2}, - {"CMYK", "K", 8, band3}, + {"CMYK", "CMYK", 32, copy4}, + {"CMYK", "CMYK;I", 32, copy4I}, + {"CMYK", "CMYK;L", 32, packRGBXL}, + {"CMYK", "C", 8, band0}, + {"CMYK", "M", 8, band1}, + {"CMYK", "Y", 8, band2}, + {"CMYK", "K", 8, band3}, /* video (YCbCr) */ - {"YCbCr", "YCbCr", 24, ImagingPackRGB}, - {"YCbCr", "YCbCr;L", 24, packRGBL}, - {"YCbCr", "YCbCrX", 32, copy4}, - {"YCbCr", "YCbCrK", 32, copy4}, - {"YCbCr", "Y", 8, band0}, - {"YCbCr", "Cb", 8, band1}, - {"YCbCr", "Cr", 8, band2}, + {"YCbCr", "YCbCr", 24, ImagingPackRGB}, + {"YCbCr", "YCbCr;L", 24, packRGBL}, + {"YCbCr", "YCbCrX", 32, copy4}, + {"YCbCr", "YCbCrK", 32, copy4}, + {"YCbCr", "Y", 8, band0}, + {"YCbCr", "Cb", 8, band1}, + {"YCbCr", "Cr", 8, band2}, /* LAB Color */ - {"LAB", "LAB", 24, ImagingPackLAB}, - {"LAB", "L", 8, band0}, - {"LAB", "A", 8, band1}, - {"LAB", "B", 8, band2}, + {"LAB", "LAB", 24, ImagingPackLAB}, + {"LAB", "L", 8, band0}, + {"LAB", "A", 8, band1}, + {"LAB", "B", 8, band2}, /* HSV */ - {"HSV", "HSV", 24, ImagingPackRGB}, - {"HSV", "H", 8, band0}, - {"HSV", "S", 8, band1}, - {"HSV", "V", 8, band2}, + {"HSV", "HSV", 24, ImagingPackRGB}, + {"HSV", "H", 8, band0}, + {"HSV", "S", 8, band1}, + {"HSV", "V", 8, band2}, /* integer */ - {"I", "I", 32, copy4}, - {"I", "I;16B", 16, packI16B}, - {"I", "I;32S", 32, packI32S}, - {"I", "I;32NS", 32, copy4}, + {"I", "I", 32, copy4}, + {"I", "I;16B", 16, packI16B}, + {"I", "I;32S", 32, packI32S}, + {"I", "I;32NS", 32, copy4}, /* floating point */ - {"F", "F", 32, copy4}, - {"F", "F;32F", 32, packI32S}, - {"F", "F;32NF", 32, copy4}, + {"F", "F", 32, copy4}, + {"F", "F;32F", 32, packI32S}, + {"F", "F;32NF", 32, copy4}, /* storage modes */ - {"I;16", "I;16", 16, copy2}, - {"I;16B", "I;16B", 16, copy2}, - {"I;16L", "I;16L", 16, copy2}, - {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. - {"I;16L", "I;16N", 16, packI16N_I16}, - {"I;16B", "I;16N", 16, packI16N_I16B}, - {"BGR;15", "BGR;15", 16, copy2}, - {"BGR;16", "BGR;16", 16, copy2}, - {"BGR;24", "BGR;24", 24, copy3}, + {"I;16", "I;16", 16, copy2}, + {"I;16B", "I;16B", 16, copy2}, + {"I;16L", "I;16L", 16, copy2}, + {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. + {"I;16L", "I;16N", 16, packI16N_I16}, + {"I;16B", "I;16N", 16, packI16N_I16B}, + {"BGR;15", "BGR;15", 16, copy2}, + {"BGR;16", "BGR;16", 16, copy2}, + {"BGR;24", "BGR;24", 24, copy3}, {NULL} /* sentinel */ }; @@ -630,11 +630,11 @@ ImagingFindPacker(const char* mode, const char* rawmode, int* bits_out) /* find a suitable pixel packer */ for (i = 0; packers[i].rawmode; i++) - if (strcmp(packers[i].mode, mode) == 0 && + if (strcmp(packers[i].mode, mode) == 0 && strcmp(packers[i].rawmode, rawmode) == 0) { - if (bits_out) - *bits_out = packers[i].bits; - return packers[i].pack; - } + if (bits_out) + *bits_out = packers[i].bits; + return packers[i].pack; + } return NULL; } From 88c43b61b7d5339292be31bcd70c0f707a3a76ae Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 14:08:59 +0000 Subject: [PATCH 138/267] Fix for #2258, 2 bit palette images corrupted --- Tests/test_imagepalette.py | 15 ++++++++++++++- libImaging/Pack.c | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index e26c242b0..b8e74d99e 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import ImagePalette +from PIL import ImagePalette, Image ImagePalette = ImagePalette.ImagePalette @@ -125,6 +125,19 @@ class TestImagePalette(PillowTestCase): self.assertEqual(rawmode, "RGB") self.assertEqual(data_in, data_out) + def test_2bit_palette(self): + # issue #2258, 2 bit palettes are corrupted. + outfile = self.tempfile('temp.png') + + rgb = b'\x00' * 2 + b'\x01' * 2 + b'\x02' * 2 + img = Image.frombytes('P', (6, 1), rgb) + img.putpalette('\xFF\x00\x00' '\x00\xFF\x00' '\x00\x00\xFF') # RGB + img.save(outfile, format='PNG') + + reloaded = Image.open(outfile) + + self.assert_image_equal(img, reloaded) + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Pack.c b/libImaging/Pack.c index c768bc27d..621936351 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -194,6 +194,7 @@ packP2(UINT8* out, const UINT8* in, int pixels) case 2: out[0] = (in[0] << 6) | ((in[1] & 3) << 4); + break; case 1: out[0] = (in[0] << 6); } From 4b57345683891de2c1e83a07a14e516caf68bd2c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 14:45:05 +0000 Subject: [PATCH 139/267] Refactor random image --- Tests/test_file_jpeg.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 1b34b42c6..7cbb96c2e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,7 +1,6 @@ from helper import unittest, PillowTestCase, hopper, py3 from helper import djpeg_available, cjpeg_available -import random from io import BytesIO import os @@ -29,6 +28,15 @@ class TestFileJpeg(PillowTestCase): im.bytes = test_bytes # for testing only return im + def gen_random_image(self, size, mode='RGB'): + """ Generates a very hard to compress file + :param size: tuple + :param mode: optional image mode + + """ + return Image.frombytes(mode, size, + os.urandom(size[0]*size[1]*len(mode))) + def test_sanity(self): # internal version number @@ -159,12 +167,7 @@ class TestFileJpeg(PillowTestCase): def test_progressive_large_buffer_highest_quality(self): f = self.tempfile('temp.jpg') - if py3: - a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3)) - else: - a = b''.join(chr(random.randint(0, 255)) for _ in range( - 256 * 256 * 3)) - im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1) + im = self.gen_random_image((255,255)) # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) @@ -436,14 +439,7 @@ class TestFileJpeg(PillowTestCase): self.assertEqual(tag_ids['RelatedImageLength'], 0x1002) def test_MAXBLOCK_scaling(self): - def gen_random_image(size): - """ Generates a very hard to compress file - :param size: tuple - """ - return Image.frombytes('RGB', - size, os.urandom(size[0]*size[1] * 3)) - - im = gen_random_image((512, 512)) + im = self.gen_random_image((512, 512)) f = self.tempfile("temp.jpeg") im.save(f, quality=100, optimize=True) From 7e2bd28a7cbc7fd7a01fc5bcef7956279ed99d85 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 14:45:45 +0000 Subject: [PATCH 140/267] Fix for issue 2272, CMYK images miss the heuristic for the maxblock buffer scaling --- PIL/JpegImagePlugin.py | 5 ++++- Tests/test_file_jpeg.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 221bf6495..4daf67aad 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -716,8 +716,11 @@ def _save(im, fp, filename): # https://github.com/matthewwithanm/django-imagekit/issues/50 bufsize = 0 if optimize or progressive: + # CMYK can be bigger + if im.mode == 'CMYK': + bufsize = 4 * im.size[0] * im.size[1] # keep sets quality to 0, but the actual value may be high. - if quality >= 95 or quality == 0: + elif quality >= 95 or quality == 0: bufsize = 2 * im.size[0] * im.size[1] else: bufsize = im.size[0] * im.size[1] diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 7cbb96c2e..b703598c5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -171,6 +171,12 @@ class TestFileJpeg(PillowTestCase): # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) + def test_progressive_cmyk_buffer(self): + # Issue 2272, quality 90 cmyk image is tripping the large buffer bug. + f = BytesIO() + im = self.gen_random_image((256,256), 'CMYK') + im.save(f, format='JPEG', progressive=True, quality=94) + def test_large_exif(self): # https://github.com/python-pillow/Pillow/issues/148 f = self.tempfile('temp.jpg') From d983aead74fc9d2ece607020009ddccee5e34417 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 14:49:44 +0000 Subject: [PATCH 141/267] Py3 bytes --- Tests/test_imagepalette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index b8e74d99e..f80fa34c8 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -131,7 +131,7 @@ class TestImagePalette(PillowTestCase): rgb = b'\x00' * 2 + b'\x01' * 2 + b'\x02' * 2 img = Image.frombytes('P', (6, 1), rgb) - img.putpalette('\xFF\x00\x00' '\x00\xFF\x00' '\x00\x00\xFF') # RGB + img.putpalette(b'\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF') # RGB img.save(outfile, format='PNG') reloaded = Image.open(outfile) From b1017b29030170234b36df343415bc3517a6154b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 4 Dec 2016 12:33:08 +0200 Subject: [PATCH 142/267] Update CHANGES.rst [CI skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 22cd34ca7..8321dc293 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Fix for 2-bit palette corruption #2274 + [pdknsk, wiredfool] + - Tiff: Update info.icc_profile when using libtiff reader. #2193 [lambdafu] - + - Test: Fix bug in test_ifd_rational_save when libtiff is not available #2270 [ChristopherHogan] From 194ebd4e0a3f63161768f9bcbdac4df050c65a70 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 4 Dec 2016 18:41:39 +0300 Subject: [PATCH 143/267] remove depreciated internal "stretch" method --- _imaging.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/_imaging.c b/_imaging.c index e2320d862..b72aa552a 100644 --- a/_imaging.c +++ b/_imaging.c @@ -2977,9 +2977,6 @@ static struct PyMethodDef methods[] = { {"rankfilter", (PyCFunction)_rankfilter, 1}, #endif {"resize", (PyCFunction)_resize, 1}, - // There were two methods for image resize before. - // Starting from Pillow 2.7.0 stretch is depreciated. - {"stretch", (PyCFunction)_resize, 1}, {"transpose", (PyCFunction)_transpose, 1}, {"transform2", (PyCFunction)_transform2, 1}, From 9c38253733196b77d7eb0151e347dfeeeb80605b Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 4 Dec 2016 18:59:53 +0300 Subject: [PATCH 144/267] set executable flag on setup.py and add shebang --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) mode change 100644 => 100755 setup.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 808ec728a..72dc11baa --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # > pyroma . # ------------------------------ # Checking . @@ -89,7 +90,7 @@ def _lib_include(root): def _cmd_exists(cmd): return any( - os.access(os.path.join(path, cmd), os.X_OK) + os.access(os.path.join(path, cmd), os.X_OK) for path in os.environ["PATH"].split(os.pathsep) ) @@ -547,7 +548,7 @@ class pil_build_ext(build_ext): if f in ('jpeg', 'zlib'): raise RequiredDependencyException(f) raise DependencyException(f) - + # # core library From 87840374638a9deb2c9f539edb60a7fe8d9a1fc0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 4 Dec 2016 22:00:51 +0200 Subject: [PATCH 145/267] Update [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8321dc293..07c775ee5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Update Maxblock heuristic #2275 + [wiredfool] + - Fix for 2-bit palette corruption #2274 [pdknsk, wiredfool] From aa247dbb60ffb0689c2500e24686636d073c2562 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 3 Dec 2016 13:37:31 +0000 Subject: [PATCH 146/267] Moving tests requiring libtiff to test_file_libtiff --- Tests/test_file_libtiff.py | 45 ++++++++++++++++++++++++++++++++++++++ Tests/test_file_tiff.py | 44 ------------------------------------- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 3a8c0b1c2..95a4d27c2 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -532,5 +532,50 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False self.assertEqual(icc, icc_libtiff) + def test_multipage_compression(self): + im = Image.open('Tests/images/compression.tif') + + im.seek(0) + self.assertEqual(im._compression, 'tiff_ccitt') + self.assertEqual(im.size, (10, 10)) + + im.seek(1) + self.assertEqual(im._compression, 'packbits') + self.assertEqual(im.size, (10, 10)) + im.load() + + im.seek(0) + self.assertEqual(im._compression, 'tiff_ccitt') + self.assertEqual(im.size, (10, 10)) + im.load() + + def test_save_tiff_with_jpegtables(self): + # Arrange + outfile = self.tempfile("temp.tif") + + # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif + # Contains JPEGTables (347) tag + infile = "Tests/images/hopper_jpg.tif" + im = Image.open(infile) + + # Act / Assert + # Should not raise UnicodeDecodeError or anything else + im.save(outfile) + + def test_page_number_x_0(self): + # Issue 973 + # Test TIFF with tag 297 (Page Number) having value of 0 0. + # The first number is the current page number. + # The second is the total number of pages, zero means not available. + outfile = self.tempfile("temp.tif") + # Created by printing a page in Chrome to PDF, then: + # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif + # -dNOPAUSE /tmp/test.pdf -c quit + infile = "Tests/images/total-pages-zero.tif" + im = Image.open(infile) + # Should not divide by zero + im.save(outfile) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 76fe8f930..bf19947a1 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -382,20 +382,6 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im2.mode, "L") self.assert_image_equal(im, im2) - def test_page_number_x_0(self): - # Issue 973 - # Test TIFF with tag 297 (Page Number) having value of 0 0. - # The first number is the current page number. - # The second is the total number of pages, zero means not available. - outfile = self.tempfile("temp.tif") - # Created by printing a page in Chrome to PDF, then: - # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif - # -dNOPAUSE /tmp/test.pdf -c quit - infile = "Tests/images/total-pages-zero.tif" - im = Image.open(infile) - # Should not divide by zero - im.save(outfile) - def test_with_underscores(self): kwargs = {'resolution_unit': 'inch', 'x_resolution': 72, @@ -432,36 +418,6 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.tag_v2[X_RESOLUTION], 36) self.assertEqual(im.tag_v2[Y_RESOLUTION], 72) - def test_multipage_compression(self): - im = Image.open('Tests/images/compression.tif') - - im.seek(0) - self.assertEqual(im._compression, 'tiff_ccitt') - self.assertEqual(im.size, (10, 10)) - - im.seek(1) - self.assertEqual(im._compression, 'packbits') - self.assertEqual(im.size, (10, 10)) - im.load() - - im.seek(0) - self.assertEqual(im._compression, 'tiff_ccitt') - self.assertEqual(im.size, (10, 10)) - im.load() - - def test_save_tiff_with_jpegtables(self): - # Arrange - outfile = self.tempfile("temp.tif") - - # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif - # Contains JPEGTables (347) tag - infile = "Tests/images/hopper_jpg.tif" - im = Image.open(infile) - - # Act / Assert - # Should not raise UnicodeDecodeError or anything else - im.save(outfile) - def test_lzw(self): # Act im = Image.open("Tests/images/hopper_lzw.tif") From 8967a20ad70f9af67f2aa89f22dca54dbbbd1754 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 7 Dec 2016 03:07:20 +0300 Subject: [PATCH 147/267] test new assert_image_similar implementation --- Tests/helper.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Tests/helper.py b/Tests/helper.py index 7b8bdcce4..f42de8bb2 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -7,6 +7,18 @@ import tempfile import os import unittest +from PIL import Image, ImageMath + + +def convert_to_comparable(a, b): + new_a, new_b = a, b + if a.mode == 'P': + new_a = Image.new('L', a.size) + new_b = Image.new('L', b.size) + new_a.putdata(a.getdata()) + new_b.putdata(b.getdata()) + return new_a, new_b + class PillowTestCase(unittest.TestCase): @@ -88,6 +100,16 @@ class PillowTestCase(unittest.TestCase): except: for abyte, bbyte in zip(a.tobytes(), b.tobytes()): diff += abs(abyte-bbyte) + + a, b = convert_to_comparable(a, b) + + new_diff = 0 + for ach, bch in zip(a.split(), b.split()): + chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert('L') + new_diff += sum(i * num for i, num in enumerate(chdiff.histogram())) + + self.assertEqual(diff, new_diff) + ave_diff = float(diff)/(a.size[0]*a.size[1]) self.assertGreaterEqual( epsilon, ave_diff, From 52c8e2050ed50c5861db447ab89bdcb3ab8ccfc0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 7 Dec 2016 03:09:13 +0300 Subject: [PATCH 148/267] replace test "1"-mode image --- Tests/images/hopper.msp | Bin 2080 -> 2080 bytes Tests/test_file_msp.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/images/hopper.msp b/Tests/images/hopper.msp index 91d9a147ff0a7d2a27f89c66405e9011a464d359..18215f1aff7d85bf066a4f12cb16542c48ad18c2 100644 GIT binary patch literal 2080 zcmXX{Z)_Y#6@N3gPS!S2_e|{%AT{gb9O6x(wogYQJZzV4i1T{$k~L$&V3 zm1xpXwRmj&!Sxp$%L`H@#L4(1-)uHDsC!kNq&^@>C46zLtP}YX$Bl#(91I7t+fWjl zw+DNto%zkY_j`Zl&D-S5r%wnFK>q&==iy^KTN}bxB>%0)Q>l3PPfNsmCfVa~K08%3 z3-C^>Hp<=thno&>i%mBHC?a|4f-+UebWJ-PKuwI7*T21-Zg1xqDIukU=tz9}Po8$# z+T$q=2#K*DE&jXMp2|#56#$qf&fouzvNc<1@8o7I1$6umAOEMi;~3M$cH}TuuZthCi7B8faB!x10Z(z4Nyqqd#rufl4DE~HHym9ic@pPJZ zz4f_+x>$gr*Pq&O+uBoyc8;%ij8_kST1{+C&lQZ>D|^dj@p99T&B=ewIOeQ#bpN$e zGpT=wV{d-lN~fJn^rU!Iz%RF+tbMJmCtfsOc&=O>htH>4I>pq9Oo3~AU%V}J%+h_} zG0EfFo{wM43(Uei%*qtz@8|G95E#`#%M^_wk1N_a0g8qiX-r@o6u346Aq2@&rc^-) zp&+8P4b$i7lTAV(BpUFvIaLG-wG&g2kEi9Vg83^mAUg6%DY>QrOM=s-{PHD_UeF;q(6R!QRve}LL)6TR2D4$qd z{K@Jr~b=I$4xX^gKhLg8P#Y9J<*Gxuh zxcrz3rw(M1UJpWkI7i`nPptP8w~bJs>v)@*GMe4XHEE285iv36oegp_6z8N%u=BjH z_v*^6Y$a!xG*ID>hxz7;YomsX^{YdJ%)UEx_r;pL8UyXm*~Q8y55I3o*OIybc7f;a z4PKQ&&bNI$4VrUL@@@MnM}h!U4KCaMv4u;BJkC|D_q+bA8~A-$Bkl(lRI+X6*`r*UUgdf@;EQ?sh|AE)P`95w3+?-(}r&rsS-Ry-4 zua8T&EWpoYJbirAsww2Da(RJnIcH1gyye?BRJat>XV=`mmcS0AM|67TgT$FZAD#7( zGAi`120gBcQ}2KIqkYz#FP$8>eV^NypCcRck~{dG%d>LTgIN6+tvR{&B+L6OG=Y_W z!Z+QVoAG3TM*yO!B{03-x6xVd<^};^BH)?Tl>=4@UAhR$+<|}>IZtN;>7%|*8+lK+ z%s*{le14X|WWoORDQLLZP^zFspbA}{`3?S^3qvpm^~IM^s2c$1opWM?L^g==g@=#l^Cs>!8Y!S}z*@ zY=2q9)G_~BxDCVNsZj<3dr%%2t`EbP1vU}}d>x3cYe!=d6;vd&=P97W-^5iMAoi6x zJYawqfB@480b@xhh&33;cT$Bz^WnBYFNjKT)_fauRioH1RqS{=d|?j5!3g&+u~07B zxPxRCV|CuY{_W05$z!{-Cfu4 zW@2sKfKV+uJOhjgQ0fKaj&4s^#j#aVmu+>fJ3_Nqo z0(ezPwn+b#@x4pXv9L*KMu&cR^LdC4P^dE$h+|jfO#)~h!>K*v+b?}I50P>Ws0S(S w2?`n;WuUTfNrf?tREgvf>5Ksm;YX)l;2xfX0Q^vd)8D~}5{{4nCIrO(3pB#RqW}N^ literal 2080 zcmXX{Z)jWB7609*XXn{2^vP|^=oo(1WELy)+QBwPH8!W5a}-@UlSkr0KD5&0 zg>`MRlGxX#Ep{2q(WDdt1y4v|UHLUx@zV!8)M}f-=qD%ZbAp^WPN0x^WYI>vq?v2& zys|6ly?f62oxk_oqwq_kF9-;*|9`x_w?g0k>g7Frvp?O=M1tPel0-MpnA_gIkSWEA znC{Xi%~QznSdMoGvh9E>!pF|3nPPMxo8y4iH&xwu{)*Mp!=q9nVnFowJ@^;5a@{?_ zhz`aA=BvwJlzJl3Y^Dg9x;S(DMfHB6*mHo#0ud-vpWOOyeJ2;nmU=>aC{FS7JB@lH zcE+-@nP;GPz4?cYo1ZnL><$*KAB2TEb91CoAHNbQMKgR~GvQAw%a@P-J!n~c;M8M# z46%q`|Mv0Ca(7Q;@6K}@{ZsWl59)pQvvb8z;o`1pRlL;xyV&5r^SM|dx3BZkv3%s7 z*uVH}!m@Hv|55QPf%on=*1y|*Fjxxx@abxO3Qt6yJRmYJM2lSCb?BNf0G0<2NFLOi zZylc%RE6Ux$OzT%Eba(~Nds!A6e{tcs-F~4b->7Q-W`OBOL-6p!L5SKX%IrFME1if zMN8b6(FK{KDj=2%<)TmsNi;#Iy0M&!@IciBGXSf|qa~e8g^?RjXB8gH$Rhd;2!)3# z^9V_qi>aP{R@o|71P_ru>FL#un;-7f1ZBfH+I5)a#*>%p zqrgP(q10T~BDpL*+}85_GuZceO64BsdOaZCV(afF+5(Zr1QoJV><@q<2p_p7qdia6o0zyX1LzN<>M+>GhHM!9H+S*!*rxOlm1H$Ay zaW$Q`Oh7AFMChT&_xJewLQ@vyV6@G#}RJ=oU8%=iS9cFB}*BX3Gud^|f^R zeYrMF_B1GEZg&r^mN`$_WKaE8ZFOPl?NpK&+*VCIwe$;l|Bc>?laLPAncjQj?knf5 zT$-oiZqn4SDl5{hgeo?P`K*DZ>sx;tTYL8nm-Dz?6@eSC1&Zan{2lVndET5z%@xojl?D;0rL7=5qmiUJa3ZL7_ zRchCct(cvX(zX*0chB2pO^&_a}p4Wlx86DSo;yP02hROQoq|m(v_tByh#|ig_TRNH-x@jV`o) z?h6&3oJXZ1%L#nY6PWWQ&3ESUl$&oJQ%f8ncX1| zZQRd)$q-+y+5RI@1HN1=cM5tN)r4(Q27R&^y-1C~(+)cV_(MZ*pFSpP%(wn229c$(|yDySFsC>hMD?f#{7(M z*gK~a7hj{F%4jlue?`z1&%h6zMi+W75M>$!)lD*p-PfRz9Wh}L+Ut!ue5KHN3*KgO z_lg3NsGScrsOF5;`m#=ZMc-eC=La1ZOiI(lc-@#Hb+oNORq4IFA?T@~)$>QIahu@N zn$dchycqc7_$+im{I^EDXr`1#RRg7uHp+*VuK}|khJmIPt+!7F`!Y;i8r01rM(5ZH z{9|+|F_RAHg4kDnnc)je`&%cevQ>O%eZ|0C`rkWf0!<_M(TQ!S6$OvUg*nZa#}PXY zGYR8GdG5H`w1PmTrs5csZ}3tVCPx$~bcupG!ZJpkH|s?OZHD=ObN%B%v{$_{>0}v~ vIoDN%c7g~hkxCEUH*4x%Wz?`~_K~Phz$6729)j+U@)2!>qEZc8JWcjLt-ix9 diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index f7c518379..e174d0061 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -31,7 +31,7 @@ class TestFileMsp(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) - self.assert_image_similar(im, hopper("1"), 4) + self.assert_image_equal(im, hopper("1"), 4) def test_cannot_save_wrong_mode(self): # Arrange From ba92896a53e4f86fa2e03eeb022676ef32a3752f Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 7 Dec 2016 04:39:36 +0300 Subject: [PATCH 149/267] remove old version --- Tests/helper.py | 15 ++------------- Tests/test_imagecms.py | 6 +++--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index f42de8bb2..3cf51185b 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -92,23 +92,12 @@ class PillowTestCase(unittest.TestCase): a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size)) - diff = 0 - try: - ord(b'0') - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(ord(abyte)-ord(bbyte)) - except: - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(abyte-bbyte) - a, b = convert_to_comparable(a, b) - new_diff = 0 + diff = 0 for ach, bch in zip(a.split(), b.split()): chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert('L') - new_diff += sum(i * num for i, num in enumerate(chdiff.histogram())) - - self.assertEqual(diff, new_diff) + diff += sum(i * num for i, num in enumerate(chdiff.histogram())) ave_diff = float(diff)/(a.size[0]*a.size[1]) self.assertGreaterEqual( diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index e1a3e0af5..136590667 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -205,7 +205,7 @@ class TestImageCms(PillowTestCase): target = Image.open('Tests/images/hopper.Lab.tif') - self.assert_image_similar(i, target, 30) + self.assert_image_similar(i, target, 3.5) def test_lab_srgb(self): psRGB = ImageCms.createProfile("sRGB") @@ -326,12 +326,12 @@ class TestImageCms(PillowTestCase): prepatch, these would segfault, postpatch they should emit a typeerror """ - + with self.assertRaises(TypeError): ImageCms.ImageCmsProfile(0).tobytes() with self.assertRaises(TypeError): ImageCms.ImageCmsProfile(1).tobytes() - + if __name__ == '__main__': unittest.main() From 9ee1f58215d29913e3a1578522ab793fab3ca50c Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 7 Dec 2016 05:16:10 +0300 Subject: [PATCH 150/267] fix I;16 mode --- Tests/helper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/helper.py b/Tests/helper.py index 3cf51185b..c5ec253ca 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -17,6 +17,9 @@ def convert_to_comparable(a, b): new_b = Image.new('L', b.size) new_a.putdata(a.getdata()) new_b.putdata(b.getdata()) + elif a.mode == 'I;16': + new_a = a.convert('I') + new_b = b.convert('I') return new_a, new_b From badbff1b99a939c05b3651db7e4aea11181e5bd7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 9 Dec 2016 11:26:22 +0200 Subject: [PATCH 151/267] [CI skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 07c775ee5..ca66a89eb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,15 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Tiff: Fix for writing Tiff to BytesIO using libtiff #2263 + [wiredfool] + +- Doc: Design docs #2269 + [wiredfool] + +- Test: Move tests requiring libtiff to test_file_libtiff #2273 + [wiredfool] + - Update Maxblock heuristic #2275 [wiredfool] From 57bab081b9f74e9397fdef32f9be3a1f642e5928 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 11 Dec 2016 12:30:11 +1100 Subject: [PATCH 152/267] Set executable flag on selftest.py and added shebang line --- selftest.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 selftest.py diff --git a/selftest.py b/selftest.py old mode 100644 new mode 100755 index 7829bae5b..067db4d79 --- a/selftest.py +++ b/selftest.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # minimal sanity check from __future__ import print_function From acf68c835c93ba144f83198306aa7e6082a43f43 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 Dec 2016 15:16:43 +0200 Subject: [PATCH 153/267] Increase epsilon for FreeType 2.7 --- Tests/test_imagefont.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index de89ac929..5207dce38 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -125,7 +125,9 @@ try: target = 'Tests/images/rectangle_surrounding_text.png' target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, 2.5) def test_render_multiline(self): im = Image.new(mode='RGB', size=(300, 100)) @@ -144,7 +146,7 @@ try: # some versions of freetype have different horizontal spacing. # setting a tight epsilon, I'm showing the original test failure # at epsilon = ~38. - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 6.2) def test_render_multiline_text(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -158,7 +160,8 @@ try: target = 'Tests/images/multiline_text.png' target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, 6.2) # Test that text() can pass on additional arguments # to multiline_text() @@ -178,7 +181,8 @@ try: target = 'Tests/images/multiline_text'+ext+'.png' target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, 6.2) def test_unknown_align(self): im = Image.new(mode='RGB', size=(300, 100)) @@ -227,7 +231,8 @@ try: target = 'Tests/images/multiline_text_spacing.png' target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, 6.2) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) From 4b6de04882d501aa1dd32f4316010e636aa9a3a1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 13 Dec 2016 20:05:38 +0000 Subject: [PATCH 154/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ca66a89eb..81060e2a2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,24 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Updated comments to use print as a function #2234 + [radarhere] + +- Set executable flag on selftest.py, setup.py and added shebang line #2282, #2277 + [radarhere, homm] + +- Test: Increase epsilon for FreeType 2.7 as rendering is slightly different. #2286 + [hugovk] + +- Test: Faster assert_image_similar #2279 + [homm] + +- Removed depreciated internal "stretch" method #2276 + [homm] + +- Removed the handles_eof flag in decode.c #2223 + [wiredfool] + - Tiff: Fix for writing Tiff to BytesIO using libtiff #2263 [wiredfool] From 3f372ef54a7efde311b9bf51cbba8d6248c7cde5 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 13 Dec 2016 20:13:00 +0000 Subject: [PATCH 155/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 81060e2a2..6108600ff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Remove vendored version of olefile Python package in favor of upstream #2199 + [jdufresne] + - Updated comments to use print as a function #2234 [radarhere] From 574c0a4f5be456a27ad5d8c4dad30a945d607b6e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Dec 2016 22:16:33 +1100 Subject: [PATCH 156/267] Updated freetype to 2.7 --- winbuild/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 344c0be35..4690d2a2e 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -37,10 +37,10 @@ libs = { 'dir': 'tiff-4.0.6', }, 'freetype': { - 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.5.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.6.5.tar.gz', - 'hash': 'md5:31b2276515d9ee1c7f37d9c9f4f3145a', - 'dir': 'freetype-2.6.5', + 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.7.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.7.tar.gz', + 'hash': 'md5:337139e5c7c5bd645fe130608e0fa8b5', + 'dir': 'freetype-2.7', }, 'lcms': { 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', From 980785952f697e9267304c3bc0213903fc15109c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 19 Dec 2016 14:15:06 -0800 Subject: [PATCH 157/267] Makefile entries for debug symbols, clean build, and highlight errors --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 493364cd8..60f4aa421 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,13 @@ install: python setup.py install python selftest.py --installed +debug: +# make a debug version if we don't have a -dbg python. Leaves in symbols +# for our stuff, kills optimization, and redirects to dev null so we +# see any build failures. + make clean > /dev/null + CFLAGS='-g -O0' python setup.py build_ext install > /dev/null + install-req: pip install -r requirements.txt From 01e31bfd84ad70264b6b5a903c88020f0d2cf671 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 19 Dec 2016 14:16:06 -0800 Subject: [PATCH 158/267] Workaround for 'PyPy does not yet implement the new buffer interface' bug --- _imaging.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_imaging.c b/_imaging.c index e2320d862..a82aa493c 100644 --- a/_imaging.c +++ b/_imaging.c @@ -251,7 +251,9 @@ int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view) /* Use new buffer protocol if available (mmap doesn't support this in 2.7, go figure) */ if (PyObject_CheckBuffer(buffer)) { - return PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE); + int success = PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE); + if (!success) { return success; } + PyErr_Clear(); } /* Pretend we support the new protocol; PyBuffer_Release happily ignores From c994f86684009205efaff6e693ee1d47ef5a53f2 Mon Sep 17 00:00:00 2001 From: Fahad Al-Saidi Date: Thu, 15 Dec 2016 09:33:03 +0400 Subject: [PATCH 159/267] switch to ubuntu trusty 14.04 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0d0b76146..732647555 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ python: - 3.4 - nightly +dist: trusty + install: - "travis_retry sudo apt-get update" - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" From 954fc5287777bee35c6d987472f44d29cd43925b Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 21 Dec 2016 08:53:10 +0200 Subject: [PATCH 160/267] [CI skip] --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6108600ff..22ff283fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- PyPy: Buffer interface workaround #2294 + [wiredfool] + +- Test: Switch to Ubuntu Trusty 14.04 on Travis CI #2294 + - Remove vendored version of olefile Python package in favor of upstream #2199 [jdufresne] From 50408568cb592b6779ab00fde2543b82827f7ceb Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 21 Dec 2016 21:55:45 +0200 Subject: [PATCH 161/267] Don't pip install -e . --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b88dc5ff2..2b3b49d99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ python: - nightly dist: trusty - + install: - "travis_retry sudo apt-get update" - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" @@ -43,12 +43,10 @@ install: # libimagequant - pushd depends && ./install_imagequant.sh && popd - - # extra test images - - pushd depends && ./install_extra_test_images.sh && popd - - - travis_retry pip install -e . + # extra test images + - pushd depends && ./install_extra_test_images.sh && popd + before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install From a8c6df563e56d3f62a98c16d85c46d9c0ee1eedc Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 21 Dec 2016 22:01:55 +0200 Subject: [PATCH 162/267] pip install olefile --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2b3b49d99..4e655f81c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ install: - "travis_retry pip install cffi" - "travis_retry pip install nose" - "travis_retry pip install check-manifest" + - "travis_retry pip install olefile" # Pyroma tests sometimes hang on PyPy; skip - if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi From e2c2251cabb1fbd52903ccf247f054eb5cfe615a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 22 Dec 2016 19:40:06 +1100 Subject: [PATCH 163/267] Updated libwebp to 0.5.2 --- depends/install_webp.sh | 10 +++++----- winbuild/config.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 356c6e552..a1a28b524 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,14 +1,14 @@ #!/bin/bash # install webp -if [ ! -f libwebp-0.5.1.tar.gz ]; then - wget -O 'libwebp-0.5.1.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/libwebp-0.5.1.tar.gz?raw=true' +if [ ! -f libwebp-0.5.2.tar.gz ]; then + wget -O 'libwebp-0.5.2.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/libwebp-0.5.2.tar.gz?raw=true' fi -rm -r libwebp-0.5.1 -tar -xvzf libwebp-0.5.1.tar.gz +rm -r libwebp-0.5.2 +tar -xvzf libwebp-0.5.2.tar.gz -pushd libwebp-0.5.1 +pushd libwebp-0.5.2 ./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install diff --git a/winbuild/config.py b/winbuild/config.py index 344c0be35..18fd82995 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -75,10 +75,10 @@ libs = { 'version': '8.6.6', }, 'webp': { - 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.5.1.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'libwebp-0.5.1.tar.gz', - 'hash': 'sha1:66efb2213015ad3460bef64b4fb218fdc10ce83f', - 'dir': 'libwebp-0.5.1', + 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.5.2.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'libwebp-0.5.2.tar.gz', + 'hash': 'sha1:c3adfa47f96a3909fb05e41636fdcbe3826edfbd', + 'dir': 'libwebp-0.5.2', }, 'openjpeg': { 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.2/openjpeg-2.1.2.tar.gz', From 0cfd2caa0c03c54d3d89d9a9a127dcc0b0199a5e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 24 Dec 2016 14:40:05 +0200 Subject: [PATCH 164/267] [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 22ff283fe..673098e1d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Update libwebp to 0.5.2 #2302 + [radarhere] + - PyPy: Buffer interface workaround #2294 [wiredfool] From a7b6b197eb6794cada7147fdc6933f1e765e1091 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 25 Dec 2016 00:33:17 +1100 Subject: [PATCH 165/267] Merged two changelog entries [ci skip] --- CHANGES.rst | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 673098e1d..ed7cf92da 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,6 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ -- Update libwebp to 0.5.2 #2302 - [radarhere] - - PyPy: Buffer interface workaround #2294 [wiredfool] @@ -29,7 +26,7 @@ Changelog (Pillow) - Removed depreciated internal "stretch" method #2276 [homm] - + - Removed the handles_eof flag in decode.c #2223 [wiredfool] @@ -52,20 +49,20 @@ Changelog (Pillow) [lambdafu] - Test: Fix bug in test_ifd_rational_save when libtiff is not available #2270 - [ChristopherHogan] + [ChristopherHogan] - ICO: Only save relevant sizes #2267 [hugovk] - + - ICO: Allow saving .ico files of 256x256 instead of 255x255 #2265 [hugovk] - Fix TIFFImagePlugin ICC color profile saving. #2087 [cskau] - + - Doc: Improved description of ImageOps.deform resample parameter #2256 [radarhere] - + - EMF: support negative bounding box coordinates #2249 [glexey] @@ -81,7 +78,7 @@ Changelog (Pillow) - Use minimal scale for jpeg drafts #2240 [homm] -- Updated dependency scripts to use Webp 0.5.1, OpenJpeg 2.1.2, and TclTk 8.6.6 #2235, #2236, #2237 +- Updated dependency scripts to use Webp 0.5.2, OpenJpeg 2.1.2, and TclTk 8.6.6 #2235, #2236, #2237, #2302 [radarhere] - Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186 @@ -101,10 +98,10 @@ Changelog (Pillow) - Use a context manager in ImageFont._load_pilfont() to ensure file is always closed #2232 [jdufresne] - + - Use generator expressions instead of list comprehension #2225 [jdufresne] - + - Close file after reading in ImagePalette.load() #2215 [jdufresne] @@ -130,7 +127,7 @@ Changelog (Pillow) [garbas] - Search for tkinter first in builtins #2210 - [matthew-brett] + [matthew-brett] - Tests: Replace try/except/fail pattern with TestCase.assertRaises() #2200 [jdufresne] @@ -143,7 +140,7 @@ Changelog (Pillow) - Doc: Move ICO out of the list of read-only file formats #2180 [alexwlchan] - + - Doc: Fix formatting, too-short title underlines and malformed table #2175 [hugovk] From d764a4c985b715c063a246bcb1fa8571e8ebb9a7 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 Dec 2016 19:16:11 +0200 Subject: [PATCH 166/267] Refactor to avoid repeated version number --- depends/download-and-extract.sh | 12 ++++++++++++ depends/install_openjpeg.sh | 13 +++---------- depends/install_webp.sh | 9 +++------ 3 files changed, 18 insertions(+), 16 deletions(-) create mode 100755 depends/download-and-extract.sh diff --git a/depends/download-and-extract.sh b/depends/download-and-extract.sh new file mode 100755 index 000000000..9f82877db --- /dev/null +++ b/depends/download-and-extract.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Usage: ./download-and-extract.sh something.tar.gz https://example.com/something.tar.gz + +archive=$1 +url=$2 + +if [ ! -f $archive.tar.gz ]; then + wget -O $archive.tar.gz $url +fi + +rm -r $archive +tar -xvzf $archive.tar.gz diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 8d5f5c010..d307a127e 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,19 +1,12 @@ #!/bin/bash # install openjpeg +archive=openjpeg-2.1.2 -if [ ! -f openjpeg-2.1.2.tar.gz ]; then - wget -O 'openjpeg-2.1.2.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/openjpeg-2.1.2.tar.gz?raw=true' +./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true -fi - -rm -r openjpeg-2.1.2 -tar -xvzf openjpeg-2.1.2.tar.gz - - -pushd openjpeg-2.1.2 +pushd $archive cmake -DCMAKE_INSTALL_PREFIX=/usr . && make -j4 && sudo make -j4 install popd - diff --git a/depends/install_webp.sh b/depends/install_webp.sh index a1a28b524..8bb664116 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,14 +1,11 @@ #!/bin/bash # install webp -if [ ! -f libwebp-0.5.2.tar.gz ]; then - wget -O 'libwebp-0.5.2.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/libwebp-0.5.2.tar.gz?raw=true' -fi +archive=libwebp-0.5.2 -rm -r libwebp-0.5.2 -tar -xvzf libwebp-0.5.2.tar.gz +./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true -pushd libwebp-0.5.2 +pushd $archive ./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install From a927a7d4e0011ecdeec5cf9b3574dfb8a49a4062 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 Dec 2016 19:31:45 +0200 Subject: [PATCH 167/267] Install libimagequant from pillow-depends --- depends/install_imagequant.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index dd497dc3c..8ddf7c991 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,9 +1,11 @@ #!/bin/bash # install libimagequant -git clone -b 2.6.0 https://github.com/pornel/pngquant +archive=pngquant-2.6.0 -pushd pngquant +./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true + +pushd $archive make -C lib shared sudo cp lib/libimagequant.so* /usr/lib/ From 9e6cbbe0e6c1d6d7537d2a91b13651da39f4fbce Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 Dec 2016 22:19:00 +0200 Subject: [PATCH 168/267] Use effective URLs to avoid resolving twice --- depends/install_imagequant.sh | 2 +- depends/install_openjpeg.sh | 2 +- depends/install_webp.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 8ddf7c991..c7774d5cb 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -3,7 +3,7 @@ archive=pngquant-2.6.0 -./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz pushd $archive diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index d307a127e..cac86dbbb 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -3,7 +3,7 @@ archive=openjpeg-2.1.2 -./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz pushd $archive diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 8bb664116..8f700901c 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -3,7 +3,7 @@ archive=libwebp-0.5.2 -./download-and-extract.sh $archive https://github.com/python-pillow/pillow-depends/blob/master/$archive.tar.gz?raw=true +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz pushd $archive From ee1143095bc00160f57fa40b3d83e41af9c8ef86 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 25 Dec 2016 11:18:32 +0200 Subject: [PATCH 169/267] [CI skip] --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ed7cf92da..f762c56c8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -78,9 +78,9 @@ Changelog (Pillow) - Use minimal scale for jpeg drafts #2240 [homm] -- Updated dependency scripts to use Webp 0.5.2, OpenJpeg 2.1.2, and TclTk 8.6.6 #2235, #2236, #2237, #2302 +- Updated dependency scripts to use FreeType 2.7, OpenJpeg 2.1.2, WebP 0.5.2 and Tcl/Tk 8.6.6 #2235, #2236, #2237, #2290, #2302 [radarhere] - + - Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186 [timgraham] From 8524c18329f28f5b912ec8ffb3c3f66abdbc428c Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sun, 25 Dec 2016 11:56:12 +0100 Subject: [PATCH 170/267] Remove executable bit from OleFileIO.py --- PIL/OleFileIO.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 PIL/OleFileIO.py diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py old mode 100755 new mode 100644 From 8c2a857c2edc2b04f68416e6030bf5302d488229 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 25 Dec 2016 22:14:29 +1100 Subject: [PATCH 171/267] Removed shebang line from OleFileIO --- PIL/OleFileIO.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index c4e23b86b..2d6aeb85c 100644 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import warnings warnings.warn( From 3089dd18d814d80e062d191be5a3829fa544157e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 26 Dec 2016 12:15:23 +1100 Subject: [PATCH 172/267] Updated copyright year --- LICENSE | 2 +- docs/COPYING | 2 +- docs/conf.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 87743e737..22a4445f9 100644 --- a/LICENSE +++ b/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2016 by Alex Clark and contributors + Copyright © 2017 by Alex Clark and contributors Like PIL, Pillow is licensed under the MIT-like open source PIL Software License: diff --git a/docs/COPYING b/docs/COPYING index 5d10c7364..ee8a7f807 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2016 by Alex Clark and contributors + Copyright © 2017 by Alex Clark and contributors Like PIL, Pillow is licensed under the MIT-like open source PIL Software License: diff --git a/docs/conf.py b/docs/conf.py index f66bea521..33dd6a4f7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,8 +48,8 @@ master_doc = 'index' # General information about the project. project = u'Pillow (PIL Fork)' -copyright = u'1995-2016, Fredrik Lundh and Contributors, Alex Clark and Contributors' -author = u'Fredrik Lundh and Contributors, Alex Clark and Contributors' +copyright = u'1995-2011 Fredrik Lundh, 2010-2017 Alex Clark and Contributors' +author = u'Fredrik Lundh, Alex Clark and Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 9e0d8b20c9529dd4b2043066c2aa07a67c795676 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 26 Dec 2016 13:01:09 +0200 Subject: [PATCH 173/267] [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f762c56c8..e316f7680 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Remove executable bit and shebang from OleFileIO.py #2308 + [jwilk, radarhere] + - PyPy: Buffer interface workaround #2294 [wiredfool] From 5f9f12a208fbb5103b1f73cfb65777cec68647a3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 23 Dec 2016 22:42:21 +0200 Subject: [PATCH 174/267] Python 3.6 is out --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e655f81c..3478d0aa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,12 @@ notifications: python: - "pypy" - "pypy3" - - 3.5 + - 3.6 - 2.7 - "2.7_with_system_site_packages" # For PyQt4 - - 3.3 + - 3.5 - 3.4 + - 3.3 - nightly dist: trusty From d4a6e073679a3b8785d8fb68566336e73a1c45fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Dec 2016 11:38:13 +1100 Subject: [PATCH 175/267] Added documentation for Python 3.6 support --- docs/installation.rst | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 2724886bb..f2b27da42 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -17,7 +17,7 @@ Notes .. note:: Pillow >= 2.0.0 < 3.5.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 -.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.3, 3.4, 3.5 +.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 Basic Installation ------------------ diff --git a/setup.py b/setup.py index c34b8ed47..c35d4357c 100755 --- a/setup.py +++ b/setup.py @@ -763,6 +763,7 @@ try: "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], From 3e2f9478cb3787968b77104c69da0f44df6a4b18 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 25 Dec 2016 22:03:04 +1100 Subject: [PATCH 176/267] Added py36 to tox envlist --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4d63febd0..a4a21e697 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py33, py34, py35 +envlist = py27, py33, py34, py35, py36 [testenv] commands = From 87092ad4f8e55444ec3134ed857e4de5d19cd175 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 02:42:58 -0800 Subject: [PATCH 177/267] can pass list of integer to set different duration for each frame when saving GIF --- PIL/GifImagePlugin.py | 8 ++++++++ Tests/test_file_gif.py | 24 ++++++++++++++++++++++++ docs/handbook/image-file-formats.rst | 4 +++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 2775a00f1..60d6f49e4 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -352,10 +352,18 @@ def _save(im, fp, filename, save_all=False): first_frame = None append_images = im.encoderinfo.get("append_images", []) + if "duration" in im.encoderinfo: + duration = im.encoderinfo["duration"] + else: + duration = None + frame_count = 0 for imSequence in [im]+append_images: for im_frame in ImageSequence.Iterator(imSequence): encoderinfo = im.encoderinfo.copy() im_frame = _convert_mode(im_frame) + if isinstance(duration, list): + encoderinfo["duration"] = duration[frame_count] + frame_count += 1 # To specify duration, add the time in milliseconds to getdata(), # e.g. getdata(im_frame, duration=1000) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 1672a14f0..fdc7d9d55 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -281,6 +281,30 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info['duration'], duration) + def test_multiple_duration(self): + duration_list = [1000, 2000, 3000] + + out = self.tempfile('temp.gif') + im_list = [ + Image.new('L', (100, 100), '#000'), + Image.new('L', (100, 100), '#111'), + Image.new('L', (100, 100), '#222'), + ] + im_list[0].save( + out, + save_all=True, + append_images=im_list[1:], + duration=duration_list + ) + reread = Image.open(out) + + for duration in duration_list: + self.assertEqual(reread.info['duration'], duration) + try: + reread.seek(reread.tell() + 1) + except EOFError: + pass + def test_number_of_loops(self): number_of_loops = 2 diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 11486c76b..36513efd5 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -111,7 +111,9 @@ additional frames when saving, the ``append_images`` parameter works with If present, the ``loop`` parameter can be used to set the number of times the GIF should loop, and the ``duration`` parameter can set the number of -milliseconds between each frame. +milliseconds between each frame. The ``duration`` parameter can be either an +integer or a list of integers. Passing a list to the ``duration``parameter +will set the ``duration`` of each frame respectively. Reading local images ~~~~~~~~~~~~~~~~~~~~ From e530f2a228978413e7b3f08b866b50c109e90510 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 03:04:37 -0800 Subject: [PATCH 178/267] Added tuple option for durations --- PIL/GifImagePlugin.py | 2 +- Tests/test_file_gif.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 60d6f49e4..6cf7a4f22 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -361,7 +361,7 @@ def _save(im, fp, filename, save_all=False): for im_frame in ImageSequence.Iterator(imSequence): encoderinfo = im.encoderinfo.copy() im_frame = _convert_mode(im_frame) - if isinstance(duration, list): + if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] frame_count += 1 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index fdc7d9d55..cfaeb54ed 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -290,6 +290,8 @@ class TestFileGif(PillowTestCase): Image.new('L', (100, 100), '#111'), Image.new('L', (100, 100), '#222'), ] + + #duration as list im_list[0].save( out, save_all=True, @@ -305,6 +307,24 @@ class TestFileGif(PillowTestCase): except EOFError: pass + # duration as tuple + im_list[0].save( + out, + save_all=True, + append_images=im_list[1:], + duration=tuple(duration_list) + ) + reread = Image.open(out) + + for duration in duration_list: + self.assertEqual(reread.info['duration'], duration) + try: + reread.seek(reread.tell() + 1) + except EOFError: + pass + + + def test_number_of_loops(self): number_of_loops = 2 From 978047274255a153046039a9c4b17e7ff0422422 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 11:06:56 +0000 Subject: [PATCH 179/267] Update Changes.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e316f7680..a282dbfee 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,15 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Build: Refactor dependency installation #2305 + [hugovk] + +- Test: Add python 3.6 to travis, tox #2304 + [hugovk] + +- Test: Fix coveralls coverage for Python+C #2300 + [hugovk] + - Remove executable bit and shebang from OleFileIO.py #2308 [jwilk, radarhere] From 55e068d52e20864a33825732ce685ca8f8223df8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 03:09:55 -0800 Subject: [PATCH 180/267] Removed duplicate open duration, clarified, listed all save options, added tuple for duration --- docs/handbook/image-file-formats.rst | 48 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 36513efd5..cfb19a97a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -72,9 +72,6 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following **background** Default background color (a palette color index). -**duration** - Time between frames in an animation (in milliseconds). - **transparency** Transparency color index. This key is omitted if the image is not transparent. @@ -82,9 +79,9 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following **version** Version (either ``GIF87a`` or ``GIF89a``). -**duration** - May not be present. The time to display each frame of the GIF, in - milliseconds. +**duration** + May not be present. The time to display the current frame + of the GIF, in milliseconds. **loop** May not be present. The number of times the GIF should loop. @@ -98,22 +95,37 @@ the file by seeking to the first frame. Random access is not supported. ``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame. -Saving sequences -~~~~~~~~~~~~~~~~ +Saving +~~~~~~ -When calling :py:meth:`~PIL.Image.Image.save`, if a multiframe image is used, -by default only the first frame will be saved. To save all frames, the -``save_all`` parameter must be present and set to ``True``. To append -additional frames when saving, the ``append_images`` parameter works with -``save_all`` to append a list of images containing the extra frames:: +When calling :py:meth:`~PIL.Image.Image.save`, the following options +are available:: im.save(out, save_all=True, append_images=[im1, im2, ...]) -If present, the ``loop`` parameter can be used to set the number of times -the GIF should loop, and the ``duration`` parameter can set the number of -milliseconds between each frame. The ``duration`` parameter can be either an -integer or a list of integers. Passing a list to the ``duration``parameter -will set the ``duration`` of each frame respectively. +**save_all** + If present and true, all frames of the image will be saved. If + not, then only the first frame of a multiframe image will be saved. + +**append_images** + A list of images to append as additional frames. Each of the + images in the list can be single or multiframe images. + +**duration** + The display duration of each frame of the multiframe gif, in + milliseconds. Pass a single integer for a constant duration, or a + list or tuple to set the duration for each frame separately. + +**loop** + Integer number of times the GIF should loop. + +**optimize** + If present and true, attempt to compress the palette by + eliminating unused colors. This is only useful if the palette can + be compressed to the next smaller power of 2 elements. + +**palette** + Use the specified palette for the saved image. Reading local images ~~~~~~~~~~~~~~~~~~~~ From 915270ef6f04ac41f1d29cf33f73b2400f276c93 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 03:30:47 -0800 Subject: [PATCH 181/267] test for issue #2195 --- Tests/test_file_gif.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 83318366b..5d26e71c0 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -362,5 +362,28 @@ class TestFileGif(PillowTestCase): reread = Image.open(out) self.assertEqual(reread.n_frames, 10) + def test_transparent_optimize(self): + # from issue #2195, if the transparent color is incorrectly + # optimized out, gif loses transparency Need a palette that + # isn't using the 0 color, and one that's > 128 items where + # the transparent color is actually the top palette entry to + # trigger the bug. + + from PIL import ImagePalette + + data = bytes(bytearray(range(1,254))) + palette = ImagePalette.ImagePalette("RGB", list(range(256))*3) + + im = Image.new('L', (253,1)) + im.frombytes(data) + im.putpalette(palette) + + out = self.tempfile('temp.gif') + im.save(out, transparency=253) + reloaded = Image.open(out) + + self.assertEqual(reloaded.info['transparency'], 253) + + if __name__ == '__main__': unittest.main() From 2aef31be6744db41e04e7dfb684c89ac5756cba3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 12:02:16 +0000 Subject: [PATCH 182/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a282dbfee..ccf17b064 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Allow passing a list or tuple of individual frame durations when saving a GIF #2298 + [Xdynix] + +- Unified different GIF optimize conditions #2196 + [radarhere] + - Build: Refactor dependency installation #2305 [hugovk] From a06dd59df757d9c9cef0b132c4b123d85dcbe3c0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 28 Dec 2016 09:54:10 +1100 Subject: [PATCH 183/267] Added context managers --- PIL/GifImagePlugin.py | 22 ++++++++++------------ PIL/IcnsImagePlugin.py | 4 ++-- PIL/SpiderImagePlugin.py | 5 ++--- Tests/helper.py | 2 +- Tests/test_file_gif.py | 26 ++++++++++++-------------- Tests/test_file_ppm.py | 9 ++++----- Tests/test_image.py | 10 +++++----- Tests/test_psdraw.py | 10 ++++------ setup.py | 5 ++--- 9 files changed, 42 insertions(+), 51 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 41d6dcc1d..a50af6c02 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -521,22 +521,20 @@ def _save_netpbm(im, fp, filename): import tempfile file = im._dump() - if im.mode != "RGB": - with open(filename, 'wb') as f: - stderr = tempfile.TemporaryFile() - check_call(["ppmtogif", file], stdout=f, stderr=stderr) - else: - with open(filename, 'wb') as f: - + with open(filename, 'wb') as f: + if im.mode != "RGB": + with tempfile.TemporaryFile() as stderr: + check_call(["ppmtogif", file], stdout=f, stderr=stderr) + else: # Pipe ppmquant output into ppmtogif # "ppmquant 256 %s | ppmtogif > %s" % (file, filename) quant_cmd = ["ppmquant", "256", file] togif_cmd = ["ppmtogif"] - stderr = tempfile.TemporaryFile() - quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) - stderr = tempfile.TemporaryFile() - togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, - stderr=stderr) + with tempfile.TemporaryFile() as stderr: + quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) + with tempfile.TemporaryFile() as stderr: + togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, + stdout=f, stderr=stderr) # Allow ppmquant to receive SIGPIPE if ppmtogif exits quant_proc.stdout.close() diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py index d93e0de04..089fc2a44 100644 --- a/PIL/IcnsImagePlugin.py +++ b/PIL/IcnsImagePlugin.py @@ -330,8 +330,8 @@ def _save(im, fp, filename): from subprocess import Popen, PIPE, CalledProcessError convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] - stderr = tempfile.TemporaryFile() - convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr) + with tempfile.TemporaryFile() as stderr: + convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr) convert_proc.stdout.close() diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index 5c4d1bb3b..aa332bf02 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -83,9 +83,8 @@ def isSpiderHeader(t): def isSpiderImage(filename): - fp = open(filename, 'rb') - f = fp.read(92) # read 23 * 4 bytes - fp.close() + with open(filename, 'rb') as fp: + f = fp.read(92) # read 23 * 4 bytes t = struct.unpack('>23f', f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: diff --git a/Tests/helper.py b/Tests/helper.py index c5ec253ca..f4b6b52cf 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -151,7 +151,7 @@ class PillowTestCase(unittest.TestCase): def tempfile(self, template): assert template[:5] in ("temp.", "temp_") - (fd, path) = tempfile.mkstemp(template[4:], template[:4]) + fd, path = tempfile.mkstemp(template[4:], template[:4]) os.close(fd) self.addCleanup(self.delete_tempfile, path) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 94a8ea92c..d987f6851 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -56,7 +56,7 @@ class TestFileGif(PillowTestCase): # 256 color Palette image, posterize to > 128 and < 128 levels # Size bigger and smaller than 512x512 # Check the palette for number of colors allocated. - # Check for correctness after conversion back to RGB + # Check for correctness after conversion back to RGB def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range im = Image.frombytes('P', (colors,colors), @@ -70,7 +70,7 @@ class TestFileGif(PillowTestCase): # check palette length palette_length = max(i+1 for i,v in enumerate(reloaded.histogram()) if v) self.assertEqual(expected_palette_length, palette_length) - + self.assert_image_equal(im.convert('RGB'), reloaded.convert('RGB')) @@ -271,12 +271,11 @@ class TestFileGif(PillowTestCase): duration = 1000 out = self.tempfile('temp.gif') - fp = open(out, "wb") - im = Image.new('L', (100, 100), '#000') - for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, duration=duration): - fp.write(s) - fp.write(b";") - fp.close() + with open(out, "wb") as fp: + im = Image.new('L', (100, 100), '#000') + for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, duration=duration): + fp.write(s) + fp.write(b";") reread = Image.open(out) self.assertEqual(reread.info['duration'], duration) @@ -329,12 +328,11 @@ class TestFileGif(PillowTestCase): number_of_loops = 2 out = self.tempfile('temp.gif') - fp = open(out, "wb") - im = Image.new('L', (100, 100), '#000') - for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, loop=number_of_loops): - fp.write(s) - fp.write(b";") - fp.close() + with open(out, "wb") as fp: + im = Image.new('L', (100, 100), '#000') + for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, loop=number_of_loops): + fp.write(s) + fp.write(b";") reread = Image.open(out) self.assertEqual(reread.info['loop'], number_of_loops) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index e7428f88d..a798466b7 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -36,9 +36,8 @@ class TestFilePpm(PillowTestCase): def test_truncated_file(self): path = self.tempfile('temp.pgm') - f = open(path, 'w') - f.write('P6') - f.close() + with open(path, 'w') as f: + f.write('P6') self.assertRaises(ValueError, lambda: Image.open(path)) @@ -47,8 +46,8 @@ class TestFilePpm(PillowTestCase): # Storage.c accepted negative values for xsize, ysize. the # internal open_ppm function didn't check for sanity but it # has been removed. The default opener doesn't accept negative - # sizes. - + # sizes. + with self.assertRaises(IOError): Image.open('Tests/images/negative_size.ppm') diff --git a/Tests/test_image.py b/Tests/test_image.py index ef9aa16af..8515a030a 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -80,11 +80,11 @@ class TestImage(PillowTestCase): # Will error out on save on 3.0.0 import tempfile im = hopper() - fp = tempfile.TemporaryFile() - im.save(fp, 'JPEG') - fp.seek(0) - reloaded = Image.open(fp) - self.assert_image_similar(im, reloaded, 20) + with tempfile.TemporaryFile() as fp: + im.save(fp, 'JPEG') + fp.seek(0) + reloaded = Image.open(fp) + self.assert_image_similar(im, reloaded, 20) def test_internals(self): diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 31a2de33d..50e8763e2 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -35,12 +35,10 @@ class TestPsDraw(PillowTestCase): # Arrange tempfile = self.tempfile('temp.ps') - fp = open(tempfile, "wb") - - # Act - ps = PSDraw.PSDraw(fp) - self._create_document(ps) - fp.close() + with open(tempfile, "wb") as fp: + # Act + ps = PSDraw.PSDraw(fp) + self._create_document(ps) # Assert # Check non-zero file was created diff --git a/setup.py b/setup.py index c35d4357c..e93514366 100755 --- a/setup.py +++ b/setup.py @@ -728,9 +728,8 @@ class pil_build_ext(build_ext): return try: if ret >> 8 == 0: - fp = open(tmpfile, 'r') - multiarch_path_component = fp.readline().strip() - fp.close() + with open(tmpfile, 'r') as fp: + multiarch_path_component = fp.readline().strip() _add_directory(self.compiler.library_dirs, '/usr/lib/' + multiarch_path_component) _add_directory(self.compiler.include_dirs, From 3585a965e1369f4de026d331b5b4dd98a9a42233 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Dec 2016 10:33:56 +1100 Subject: [PATCH 184/267] Updated pngquant to 2.8.2 --- depends/install_imagequant.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index c7774d5cb..21386557b 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=pngquant-2.6.0 +archive=pngquant-2.8.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz From 1e409e2dc8f2a1c897e81af15e5559eb84f98342 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Dec 2016 10:42:29 +1100 Subject: [PATCH 185/267] Added missing PR number to Changes.rst [ci skip] --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ccf17b064..197aa0dc9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -98,7 +98,7 @@ Changelog (Pillow) - Updated dependency scripts to use FreeType 2.7, OpenJpeg 2.1.2, WebP 0.5.2 and Tcl/Tk 8.6.6 #2235, #2236, #2237, #2290, #2302 [radarhere] - + - Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186 [timgraham] @@ -647,7 +647,7 @@ Changelog (Pillow) - ImageSequence Iterator is now an iterator #1649 [radarhere] -- Updated windows test builds to jpeg9b +- Updated windows test builds to jpeg9b #1673 [radarhere] - Fixed support for .gbr version 1 images, added support for version 2 in GbrImagePlugin #1653 From 8c6bc07124511c4ed38db74337e53e477b2ad6c1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Dec 2016 10:47:05 +1100 Subject: [PATCH 186/267] Updated tested versions of libjpeg [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index f2b27da42..5eb12c69c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -126,8 +126,8 @@ Many of Pillow's features require external libraries: * **libjpeg** provides JPEG functionality. - * Pillow has been tested with libjpeg versions **6b**, **8**, **9**, and - **9a** and libjpeg-turbo version **8**. + * Pillow has been tested with libjpeg versions **6b**, **8**, **9**, **9a**, + and **9b** and libjpeg-turbo version **8**. * Starting with Pillow 3.0.0, libjpeg is required by default, but may be disabled with the ``--disable-jpeg`` flag. From f94183065c99dada8cf40c0b038afdc13d01190f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Dec 2016 20:46:03 +1100 Subject: [PATCH 187/267] Added missing PR numbers to Changes.rst [ci skip] --- CHANGES.rst | 310 +++++++++++++++++++++++++++------------------------- 1 file changed, 161 insertions(+), 149 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 197aa0dc9..bfb669e18 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -461,7 +461,7 @@ Changelog (Pillow) - Fix typos in TIFF tags #1918 [radarhere] -- Skip tests that require libtiff if it is not installed, fixes #1866 +- Skip tests that require libtiff if it is not installed #1893 (fixes #1866) [wiredfool] - Skip test when icc profile is not available, fixes #1887 @@ -488,7 +488,7 @@ Changelog (Pillow) - Combined duplicate code in ImageTk #1856 [radarhere] -- Added --disable-platform-guessing option to setup.py build extension, #1861 +- Added --disable-platform-guessing option to setup.py build extension #1861 [angeloc] - Fixed loading Transparent PNGs with a transparent black color #1840 @@ -554,7 +554,7 @@ Changelog (Pillow) - SpiderImagePlugin: raise an error when seeking in a non-stack file #1794 [radarhere, jmichalon] -- Added Support for 2/4 bpp Tiff Grayscale Images #1789 +- Added support for 2/4 bpp Tiff grayscale images #1789 [zwhfly] - Removed unused variable from selftest #1788 @@ -581,7 +581,7 @@ Changelog (Pillow) - Added __copy__ method to Image #1772 [radarhere] -- Updated dates in PIL license in OleFileIO README #1787 +- Updated dates in PIL license in OleFileIO README #1787 [radarhere] - Corrected Tiff tag names #1786 @@ -605,16 +605,16 @@ Changelog (Pillow) - Documentation changes, URL update, transpose, release checklist [radarhere] -- Fixed saving to nonexistant files specified by pathlib.Path objects, fixes #1747 +- Fixed saving to nonexistant files specified by pathlib.Path objects #1748 (fixes #1747) [radarhere] -- Round Image.crop arguments to the nearest integer, fixes #1744 +- Round Image.crop arguments to the nearest integer #1745 (fixes #1744) [hugovk] -- Fix uninitialized variable warning in _imaging.c:getink, fixes #486 +- Fix uninitialized variable warning in _imaging.c:getink #1663 (fixes #486) [wiredfool] -- Disable multiprocessing install on cygwin, fixes #1690 +- Disable multiprocessing install on cygwin #1700 (fixes #1690) [wiredfool] - Fix the error reported when libz is not found #1764 @@ -629,7 +629,7 @@ Changelog (Pillow) - Fix EXIF tag name typos #1736 [zarlant, radarhere] -- Updated freetype to 2.6.3, Tk/Tcl to 8.6.5 and 8.5.19 +- Updated freetype to 2.6.3, Tk/Tcl to 8.6.5 and 8.5.19 #1725, #1752 [radarhere] - Add a loader for the FTEX format from Independence War 2: Edge of Chaos #1688 @@ -721,7 +721,7 @@ Changelog (Pillow) - Let EditorConfig take care of some basic formatting #1489 [hugovk] -- Restore gpsexif data to the v1 form +- Restore gpsexif data to the v1 form #1619 [wiredfool] - Add /usr/local include and library directories for freebsd #1613 @@ -820,16 +820,16 @@ Changelog (Pillow) - Added some requirements for make release-test #1451 [wiredfool] -- Flatten tiff metadata value SAMPLEFORMAT to initial value, fixes #1466 +- Flatten tiff metadata value SAMPLEFORMAT to initial value #1467 (fixes #1466) [wiredfool] -- Fix handling of pathlib in Image.save. Fixes #1460 +- Fix handling of pathlib in Image.save #1464 (fixes #1460) [wiredfool] - Make tests more robust #1469 [hugovk] -- Use correctly sized pointers for windows handle types. #1458 +- Use correctly sized pointers for windows handle types #1458 [nu744] 3.0.0 (2015-10-01) @@ -886,7 +886,7 @@ Changelog (Pillow) - Fix loading of truncated images with LOAD_TRUNCATED_IMAGES enabled #1366 [homm] -- Documentation update for concepts: bands +- Documentation update for concepts: bands #1406 [merriam] - Add Solaris/SmartOS include and library directories #1356 @@ -895,7 +895,7 @@ Changelog (Pillow) - Improved handling of getink color #1387 [radarhere] -- Disable compiler optimizations for topalette and tobilevel functions for all msvc versions, fixes #1357 +- Disable compiler optimizations for topalette and tobilevel functions for all msvc versions #1402 (fixes #1357) [cgohlke] - Skip ImageFont_bitmap test if _imagingft C module is not installed #1409 @@ -1075,16 +1075,16 @@ Changelog (Pillow) 2.8.0 (2015-04-01) ------------------ -- Fix 32-bit BMP loading (RGBA or RGBX) +- Fix 32-bit BMP loading (RGBA or RGBX) #1125 [artscoop] - Fix UnboundLocalError in ImageFile #1131 [davarisg] -- Re-enable test image caching +- Re-enable test image caching #982 [hugovk, homm] -- Fix: Cannot identify EPS images, fixes #1104 +- Fix: Cannot identify EPS images #1152 (fixes #1104) [hugovk] - Configure setuptools to run nosetests, fixes #729 @@ -1093,7 +1093,7 @@ Changelog (Pillow) - Style/health fixes [radarhere, hugovk] -- Add support for HTTP response objects to Image.open() +- Add support for HTTP response objects to Image.open() #1151 [mfitzp] - Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145 @@ -1105,7 +1105,7 @@ Changelog (Pillow) - Fix ImagingEffectNoise #1128 [hugovk] -- Remove unreachable code +- Remove unreachable code #1126 [hugovk] - Let Python do the endian stuff + tests #1121 @@ -1126,10 +1126,10 @@ Changelog (Pillow) - iPython display hook #1091 [wiredfool] -- Adjust buffer size when quality=keep, fixes #148 (again) +- Adjust buffer size when quality=keep #1079 (fixes #148 again) [wiredfool] -- Fix for corrupted bitmaps embedded in truetype fonts. #1072 +- Fix for corrupted bitmaps embedded in truetype fonts #1072 [jackyyf, wiredfool] 2.7.0 (2015-01-01) @@ -1138,19 +1138,19 @@ Changelog (Pillow) - Split Sane into a separate repo: https://github.com/python-pillow/Sane [hugovk] -- Look for OS X and Linux fonts in common places. #1054 +- Look for OS X and Linux fonts in common places #1054 [charleslaw] - Fix CVE-2014-9601, potential PNG decompression DOS #1060 [wiredfool] -- Use underscores, not spaces, in TIFF tag kwargs. #1044, #1058 +- Use underscores, not spaces, in TIFF tag kwargs #1044, #1058 [anntzer, hugovk] -- Update PSDraw for Python3, add tests. #1055 +- Update PSDraw for Python3, add tests #1055 [hugovk] -- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails. #1029 +- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails #1029 [homm] - Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051 @@ -1162,7 +1162,7 @@ Changelog (Pillow) - The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993 [moriyoshi] -- Use PySide as an alternative to PyQt4/5. +- Use PySide as an alternative to PyQt4/5 #1024 [holg] - Replace affine-based im.resize implementation with convolution-based im.stretch #997 @@ -1186,13 +1186,13 @@ Changelog (Pillow) - Ico save, additional tests #1007 [exherb] -- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003 +- Use PyQt4 if it has already been imported, otherwise prefer PyQt5 #1003 [AurelienBallier] -- Speedup resample implementation up to 2.5 times. #977 +- Speedup resample implementation up to 2.5 times #977 [homm] -- Speed up rotation by using cache aware loops, added transpose to rotations. #994 +- Speed up rotation by using cache aware loops, added transpose to rotations #994 [homm] - Fix Bicubic interpolation #970 @@ -1234,7 +1234,7 @@ Changelog (Pillow) 2.6.0 (2014-10-01) ------------------ -- Relax precision of ImageDraw tests for x86, GimpGradient for PPC +- Relax precision of ImageDraw tests for x86, GimpGradient for PPC #930 [wiredfool] 2.6.0-rc1 (2014-09-29) @@ -1249,7 +1249,7 @@ Changelog (Pillow) - Additional documentation for JPEG info and save options #890 [wiredfool] -- Fix JPEG Encoding memory leak when exif or qtables were specified +- Fix JPEG Encoding memory leak when exif or qtables were specified #921 [wiredfool] - Image.tobytes() and Image.tostring() documentation update #916 #917 @@ -1315,7 +1315,7 @@ Changelog (Pillow) - PyPy performance improvements #821 [wiredfool] -- Added support for reading MPO files +- Added support for reading MPO files #822 [Feneric] - Added support for encoding and decoding iTXt chunks #818 @@ -1333,16 +1333,16 @@ Changelog (Pillow) - Doc cleanup [wiredfool] -- Fix `ImageStat` docs +- Fix `ImageStat` docs #796 [akx] -- Added docs for ExifTags +- Added docs for ExifTags #794 [Wintermute3] - More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util [hugovk] -- Fix return value of FreeTypeFont.textsize() does not include font offsets +- Fix return value of FreeTypeFont.textsize() does not include font offsets #784 [tk0miya] - Fix dispose calculations for animated GIFs #765 @@ -1367,7 +1367,6 @@ Changelog (Pillow) - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport) [Andrew Drake] - 2.5.1 (2014-07-10) ------------------ @@ -1380,10 +1379,10 @@ Changelog (Pillow) 2.5.0 (2014-07-01) ------------------ -- Imagedraw rewrite +- Imagedraw rewrite #737 [terseus, wiredfool] -- Add support for multithreaded test execution +- Add support for multithreaded test execution #755 [wiredfool] - Prevent shell injection #748 @@ -1392,7 +1391,7 @@ Changelog (Pillow) - Support for Resolution in BMP files #734 [gcq] -- Fix error in setup.py for Python 3 +- Fix error in setup.py for Python 3 #744 [matthew-brett] - Pyroma fix and add Python 3.4 to setup metadata #742 @@ -1401,7 +1400,7 @@ Changelog (Pillow) - Top level flake8 fixes #741 [aclark4life] -- Remove obsolete Animated Raster Graphics (ARG) support +- Remove obsolete Animated Raster Graphics (ARG) support #736 [hugovk] - Fix test_imagedraw failures #727 @@ -1416,28 +1415,28 @@ Changelog (Pillow) - Cleanup #654 [dvska, hugovk, wiredfool] -- 16-bit monochrome support for JPEG2000 +- 16-bit monochrome support for JPEG2000 #730 [videan42] - Fixed ImagePalette.save [brightpisces] -- Support JPEG qtables +- Support JPEG qtables #677 [csinchok] - Add binary morphology addon [dov, wiredfool] -- Decompression bomb protection +- Decompression bomb protection #674 [hugovk] -- Put images in a single directory +- Put images in a single directory #708 [hugovk] -- Support OpenJpeg 2.1 - [al45tair] +- Support OpenJpeg 2.1 #681 + [al45tair, wiredfool] -- Remove unistd.h #include for all platforms +- Remove unistd.h #include for all platforms #704 [wiredfool] - Use unittest for tests @@ -1452,19 +1451,19 @@ Changelog (Pillow) - Added tests for Spider files [hugovk] -- Use libtiff to write any compressed tiff files +- Use libtiff to write any compressed tiff files #669 [wiredfool] - Support for pickling Image objects [hugovk] -- Fixed resolution handling for EPS thumbnails +- Fixed resolution handling for EPS thumbnails #619 [eliempje] - Fixed rendering of some binary EPS files (Issue #302) [eliempje] -- Rename variables not to use built-in function names +- Rename variables not to use built-in function names #670 [hugovk] - Ignore junk JPEG markers @@ -1479,19 +1478,19 @@ Changelog (Pillow) - Remove transparency resource after P->RGBA conversion [hugovk] -- Clean up preprocessor cruft for Windows +- Clean up preprocessor cruft for Windows #652 [CounterPillow] -- Adjust Homebrew freetype detection logic +- Adjust Homebrew freetype detection logic #656 [jacknagel] -- Added Image.close, context manager support. +- Added Image.close, context manager support [wiredfool] -- Added support for 16 bit PGM files. +- Added support for 16 bit PGM files [wiredfool] -- Updated OleFileIO to version 0.30 from upstream +- Updated OleFileIO to version 0.30 from upstream #618 [hugovk] - Added support for additional TIFF floating point format @@ -1500,64 +1499,64 @@ Changelog (Pillow) - Have the tempfile use a suffix with a dot [wiredfool] -- Fix variable name used for transparency manipulations +- Fix variable name used for transparency manipulations #604 [nijel] 2.4.0 (2014-04-01) ------------------ -- Indexed Transparency handled for conversions between L, RGB, and P modes. Fixes #510 +- Indexed Transparency handled for conversions between L, RGB, and P modes #574 (fixes #510) [wiredfool] -- Conversions enabled from RGBA->P, Fixes #544 +- Conversions enabled from RGBA->P #574 (fixes #544) [wiredfool] -- Improved icns support +- Improved icns support #565 [al45tair] -- Fix libtiff leaking open files, fixes #580 +- Fix libtiff leaking open files #580 (fixes #526) [wiredfool] -- Fixes for Jpeg encoding in Python 3, fixes #577 +- Fixes for Jpeg encoding in Python 3 #578 (fixes #577) [wiredfool] -- Added support for JPEG 2000 +- Added support for JPEG 2000 #547 [al45tair] -- Add more detailed error messages to Image.py +- Add more detailed error messages to Image.py #566 [larsmans] - Avoid conflicting _expand functions in PIL & MINGW, fixes #538 [aclark4life] -- Merge from Philippe Lagadec’s OleFileIO_PL fork +- Merge from Philippe Lagadec’s OleFileIO_PL fork #512 [vadmium] -- Fix ImageColor.getcolor +- Fix ImageColor.getcolor #534 [homm] -- Make ICO files work with the ImageFile.Parser interface, fixes #522 +- Make ICO files work with the ImageFile.Parser interface #525 (fixes #522) [wiredfool] -- Handle 32bit compiled python on 64bit architecture +- Handle 32bit compiled python on 64bit architecture #521 [choppsv1] -- Fix support for characters >128 using .pcf or .pil fonts in Py3k. Fixes #505 +- Fix support for characters >128 using .pcf or .pil fonts in Py3k #517 (fixes #505) [wiredfool] -- Skip CFFI test earlier if it's not installed +- Skip CFFI test earlier if it's not installed #516 [wiredfool] -- Fixed opening and saving odd sized .pcx files, fixes #523 +- Fixed opening and saving odd sized .pcx files #535 (fixes #523) [wiredfool] - Fixed palette handling when converting from mode P->RGB->P - [d_schmidt] + [d-schmidt] - Fixed saving mode P image as a PNG with transparency = palette color 0 [d-schmidt] -- Improve heuristic used when saving progressive and optimized JPEGs with high quality values +- Improve heuristic used when saving progressive and optimized JPEGs with high quality values #504 [e98cuenc] - Fixed DOS with invalid palette size or invalid image size in BMP file @@ -1569,7 +1568,7 @@ Changelog (Pillow) - Fix segfault in getfont when passed a memory resident font [wiredfool] -- Fix crash on Saving a PNG when icc-profile is None +- Fix crash on Saving a PNG when icc-profile is None #496 [brutasse] - Cffi+Python implementation of the PixelAccess object @@ -1578,13 +1577,13 @@ Changelog (Pillow) - PixelAccess returns unsigned ints for I16 mode [wiredfool] -- Minor patch on booleans + Travis +- Minor patch on booleans + Travis #474 [sciunto] -- Look in multiarch paths in GNU platforms +- Look in multiarch paths in GNU platforms #511 [pinotree] -- Add arch support for pcc64, s390, s390x, armv7l, aarch64 +- Add arch support for pcc64, s390, s390x, armv7l, aarch64 #475 [manisandro] - Add arch support for ppc @@ -1593,7 +1592,7 @@ Changelog (Pillow) - Correctly quote file names for WindowsViewer command [cgohlke] -- Prefer homebrew freetype over X11 freetype (but still allow both) +- Prefer homebrew freetype over X11 freetype (but still allow both) #466 [dmckeone] 2.3.2 (2014-08-13) @@ -1611,76 +1610,76 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ -- Stop leaking filename parameter passed to getfont +- Stop leaking filename parameter passed to getfont #459 [jpharvey] - Report availability of LIBTIFF during setup and selftest [cgohlke] -- Fix msvc build error C1189: "No Target Architecture" +- Fix msvc build error C1189: "No Target Architecture" #460 [cgohlke] - Fix memory leak in font_getsize [wiredfool] -- Correctly prioritize include and library paths +- Correctly prioritize include and library paths #442 [ohanar] -- Image.point fixes for numpy.array and docs +- Image.point fixes for numpy.array and docs #441 [wiredfool] -- Save the transparency header by default for PNGs +- Save the transparency header by default for PNGs #424 [wiredfool] -- Support for PNG tRNS header when converting from RGB->RGBA +- Support for PNG tRNS header when converting from RGB->RGBA #423 [wiredfool] -- PyQT5 Support +- PyQT5 Support #418 [wiredfool] -- Updates for saving color tiffs w/compression using libtiff +- Updates for saving color tiffs w/compression using libtiff #417 [wiredfool] - 2gigapix image fixes and redux [wiredfool] -- Save arbitrary tags in Tiff image files +- Save arbitrary tags in Tiff image files #369 [wiredfool] -- Quote filenames and title before using on command line +- Quote filenames and title before using on command line #398 [tmccombs] -- Fixed Viewer.show to return properly +- Fixed Viewer.show to return properly #399 [tmccombs] - Documentation fixes [wiredfool] -- Fixed memory leak saving images as webp when webpmux is available +- Fixed memory leak saving images as webp when webpmux is available #429 [cezarsa] -- Fix compiling with FreeType 2.5.1 +- Fix compiling with FreeType 2.5.1 #427 [stromnov] -- Adds directories for NetBSD. +- Adds directories for NetBSD #411 [deepy] -- Support RGBA TIFF with missing ExtraSamples tag +- Support RGBA TIFF with missing ExtraSamples tag #393 [cgohlke] -- Lossless WEBP Support +- Lossless WEBP Support #390 [wiredfool] -- Take compression as an option in the save call for tiffs +- Take compression as an option in the save call for tiffs #389 [wiredfool] -- Add support for saving lossless WebP. Just pass 'lossless=True' to save() +- Add support for saving lossless WebP. Just pass 'lossless=True' to save() #386 [liftoff] -- LCMS support upgraded from version 1 to version 2, fixes #343 +- LCMS support upgraded from version 1 to version 2 #380 (fixes #343) [wiredfool] -- Added more raw decoder 16 bit pixel formats +- Added more raw decoder 16 bit pixel formats #379 [svanheulen] - Document remaining Image* modules listed in PIL handbook @@ -1701,34 +1700,34 @@ Changelog (Pillow) - Port PIL Handbook tutorial and appendices [irksep] -- Alpha Premultiplication support for transform and resize +- Alpha Premultiplication support for transform and resize #364 [wiredfool] -- Fixes to make Pypy 2.1.0 work on Ubuntu 12.04/64 +- Fixes to make Pypy 2.1.0 work on Ubuntu 12.04/64 #359 [wiredfool] 2.2.2 (2013-12-11) ------------------ -- Fix #427: compiling with FreeType 2.5.1 +- Fix compiling with FreeType 2.5.1 #427 [stromnov] 2.2.1 (2013-10-02) ------------------ -- Fix #356: Error installing Pillow 2.2.0 on Mac OS X (due to hard dep on brew) +- Error installing Pillow 2.2.0 on Mac OS X (due to hard dep on brew) #357 (fixes #356) [wiredfool] 2.2.0 (2013-10-02) ------------------ -- Fix #254: Bug in image transformations resulting from uninitialized memory +- Bug in image transformations resulting from uninitialized memory #348 (fixes #254) [nikmolnar] -- Fix for encoding of b_whitespace, similar to closed issue #272 +- Fix for encoding of b_whitespace #346 (similar to closed issue #272) [mhogg] -- Fix #273: Add numpy array interface support for 16 and 32 bit integer modes +- Add numpy array interface support for 16 and 32 bit integer modes #347 (fixes #273) [cgohlke] - Partial fix for #290: Add preliminary support for TIFF tags. @@ -1737,91 +1736,93 @@ Changelog (Pillow) - Fix #251 and #326: circumvent classification of pngtest_bad.png as malware [cgohlke] -- Add typedef uint64_t for MSVC. +- Add typedef uint64_t for MSVC #339 [cgohlke] -- Fix #329: setup.py: better support for C_INCLUDE_PATH, LD_RUN_PATH, etc. +- setup.py: better support for C_INCLUDE_PATH, LD_RUN_PATH, etc. #336 (fixes #329) [nu774] -- Fix #328: _imagingcms.c: include windef.h to fix build issue on MSVC +- _imagingcms.c: include windef.h to fix build issue on MSVC #335 (fixes #328) [nu774] -- Automatically discover homebrew include/ and lib/ paths on OS X +- Automatically discover homebrew include/ and lib/ paths on OS X #330 [donspaulding] -- Fix bytes which should be bytearray +- Fix bytes which should be bytearray #325 [manisandro] - Add respective paths for C_INCLUDE_PATH, LD_RUN_PATH (rpath) to build - if specified as environment variables. + if specified as environment variables #324 [seanupton] - Fix #312 + gif optimize improvement [d-schmidt] -- Be more tolerant of tag read failures +- Be more tolerant of tag read failures #320 [ericbuehl] -- Fix #318: Catch truncated zTXt errors. +- Catch truncated zTXt errors #321 (fixes #318) [vytisb] -- Fix IOError when saving progressive JPEGs. +- Fix IOError when saving progressive JPEGs #313 [e98cuenc] -- Add RGBA support to ImageColor +- Add RGBA support to ImageColor #309 [yoavweiss] -- Fix #304: test for `str`, not `"utf-8"`. +- Test for `str`, not `"utf-8"` #306 (fixes #304) [mjpieters] -- Fix missing import os in _util.py. +- Fix missing import os in _util.py #303 [mnowotka] -- Added missing exif tags. +- Added missing exif tags #300 [freyes] -- Fail on all import errors, fixes #298. +- Fail on all import errors #298, #299 (fixes #297) [macfreek, wiredfool] -- Fixed Windows fallback (wasn't using correct file in Windows fonts). +- Fixed Windows fallback (wasn't using correct file in Windows fonts) #295 [lmollea] -- Moved ImageFile and ImageFileIO comments to docstrings. +- Moved ImageFile and ImageFileIO comments to docstrings #293 [freyes] -- Restore compatibility with ISO C. +- Restore compatibility with ISO C #289 [cgohlke] -- Use correct format character for C int type. +- Use correct format character for C int type #288 [cgohlke] -- Allocate enough memory to hold pointers in encode.c. +- Allocate enough memory to hold pointers in encode.c #287 [cgohlke] -- Fix #279, fillorder double shuffling bug when FillOrder ==2 and decoding using libtiff. +- Fillorder double shuffling bug when FillOrder ==2 and decoding using libtiff #284 (fixes #279) [wiredfool] - Moved Image module comments to docstrings. [freyes] -- Add 16-bit TIFF support, fixes #274. +- Add 16-bit TIFF support #277 (fixes #274) [wiredfool] -- Ignore high ascii characters in string.whitespace, fixes #272. +- Ignore high ascii characters in string.whitespace #276 (fixes #272) [wiredfool] -- Added clean/build to tox to make it behave like travis. +- Added clean/build to tox to make it behave like Travis #275 [freyes] -- Adding support for metadata in webp images. +- Adding support for metadata in webp images #271 [heynemann] 2.1.0 (2013-07-02) ------------------ -- Add /usr/bin/env python shebangs to all scripts in /Scripts. +- Add /usr/bin/env python shebangs to all scripts in /Scripts #197 + [mgorny] -- Add several TIFF decoders and encoders. +- Add several TIFF decoders and encoders #268 + [megabuz] - Added support for alpha transparent webp images. @@ -1829,15 +1830,17 @@ Changelog (Pillow) - Adding Python3 basestring compatibility without changing basestring. -- Fix webp encode errors on win-amd64. +- Fix webp encode errors on win-amd64 #259 + [cgohlke] -- Better fix for ZeroDivisionError in ImageOps.fit for image.size height is 1. +- Better fix for ZeroDivisionError in ImageOps.fit for image.size height is 1 #267 + [chrispbailey] - Better support for ICO images. -- Changed PY_VERSION_HEX, fixes #166. +- Changed PY_VERSION_HEX #190 (fixes #166) -- Changes to put everything under the PIL namespace. +- Changes to put everything under the PIL namespace #191 [wiredfool] - Changing StringIO to BytesIO. @@ -1848,35 +1851,44 @@ Changelog (Pillow) - Don't skip 'import site' on initialization when running tests for inplace builds. [cgohlke] -- Enable warnings for test suite. +- Enable warnings for test suite #227 + [wiredfool] -- Fix for ZeroDivisionError in ImageOps.fit for image.size == (1,1) +- Fix for ZeroDivisionError in ImageOps.fit for image.size == (1,1) #255 + [pterk] - Fix for if isinstance(filter, collections.Callable) crash. Python bug #7624 on <2.6.6 -- Fix #193: remove double typedef declaration. +- Remove double typedef declaration #194 (fixes #193) + [evertrol] - Fix msvc compile errors (#230). -- Fix rendered characters have been chipped for some TrueType fonts. +- Fix rendered characters have been chipped for some TrueType fonts + [tk0miya] -- Fix usage of pilfont.py script. +- Fix usage of pilfont.py script #184 + [fabiomcosta] - Fresh start for docs, generated by sphinx-apidoc. - Introduce --enable-x and fail if it is given and x is not available. -- Partial work to add a wrapper for WebPGetFeatures to correctly support #204. +- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204) -- Significant performance improvement of `alpha_composite` function. +- Significant performance improvement of `alpha_composite` function #156 + [homm] -- Support explicitly disabling features via --disable-* options. +- Support explicitly disabling features via --disable-* options #240 + [mgorny] -- Support selftest.py --installed, fixes #263. +- Support selftest.py --installed, fixes #263 -- Transparent WebP Support, #204 +- Transparent WebP Support #220 (fixes #204) + [euangoddard, wiredfool] -- Use PyCapsule for py3.1, fixes #237. +- Use PyCapsule for py3.1 #238 (fixes #237) + [wiredfool] - Workaround for: http://bugs.python.org/issue16754 in 3.2.x < 3.2.4 and 3.3.0. @@ -1890,15 +1902,15 @@ Changelog (Pillow) - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) [fluggo] -- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67) +- Add PyPy support (experimental, please see #67) -- Add WebP support. +- Add WebP support #96 [lqs] - Add Tiff G3/G4 support (experimental) [wiredfool] -- Backport PIL's PNG/Zip improvements. +- Backport PIL's PNG/Zip improvements #95, #97 [olt] - Various 64-bit and Windows fixes. From 95888466c8a66558dd383c235672b9c7dbcda824 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 Dec 2016 22:20:46 +0000 Subject: [PATCH 188/267] Added correctness tests for opening SGI images --- Tests/images/hopper.bw | Bin 7864 -> 16896 bytes Tests/images/hopper.rgb | Bin 52077 -> 49664 bytes Tests/images/hopper.sgi | Bin 0 -> 52077 bytes Tests/images/transparent.sgi | Bin 43896 -> 120512 bytes Tests/test_file_sgi.py | 34 ++++++++++++++++++++-------------- 5 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 Tests/images/hopper.sgi diff --git a/Tests/images/hopper.bw b/Tests/images/hopper.bw index c9dabf64a5ac21508032138fdbd6a6427bc750ec..1503168ab9caff1c119c51a3d79ecd17d456c81a 100644 GIT binary patch literal 16896 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 Q-~{1dAB=;0)Zoeg2Xf8R;{X5v literal 7864 zcmeI0$&XxD6~=GpRlQeL-PLoqhaS=b<(} zp6`5TxaZziQ!h_Vr5n$OFiu$cxC!2zAly zLyjYFLf(&j4EZ$j1%$cHSCQ`^FCafcev14C`5p2X1dQG+asaslIgLDsd>pxed=vQr zg1z29Q|fObZ%00dd=L3mN&~nKUXOeNc@Ft)O4D-)e5T@{f&43_d15jD5Q1;>zf5U?b+Q1~!Y7eu zkYA^?xQ=`ffzRS!Q(6LN=@P>HWqe)!2=XlQkCawUBA-FSaM(}4JHugP^{63}qVDA4s^0SoIh{f6m5azA@DW&zJ z2=QCz*~T)0e;YqcX_Gi^ewaXU9m#-ua-24i+mV}*!^lBoKeC3vH(|pv%~djs49k6c z)wt$1h~_VrtHyLMta(K9WN!tLZP_Ab=_Um3>DF@9{c+?jCTjgHQG*@H7CGP2dMA+{{#N%Da zJCHXcrx9w^@*b|TA?(*7!k380ZHS(UkK!&nx);7|okCsuJ1$Mi^$KnA#X)w z_bufrZWX8$#*j|5VbV6E z_k*(=HjXlMU&MJTygbUhc_tl8wdNn?GRyqixwlRQM4e}lZ7~Iug4l-Y*+jg$OjNQm zr-!JbZ4#(j*+zifPHXu#h|&di^AXfkE7Z{dOXr;kE5_3vPZyakCot5=)L{aNsS4Fw zAYo1o7zY_R??hJO)yp?qP2dsqnABHE5F6@}$Q^Mc6b&NS@bOCQoD)h#iqFXraX)0J zfu=P10N59Cu-G#DI^Sd@#c0xEj}qwSr#;mwu5lHd_RlT$m@&J4-h{vtqZzRt4BN{a z4!;&#CRFecvaHql@qjZXI;z|O542P?!H#Kmlb;N18OJlLHB_^r+_YYg`84oT1bF6n ztLT-89*f|DBEzE~RFpeZDmm|A`apU_j}>TARx`gfx0vQJ*AHSn*e$yHD5yxXvQtwU zN+KV1p7Uh&6A{>6i7r{U1f3mFO|-BFCDRmDZ+8y2MdR z0#c9^(>ffcRyMf508Qz*v@3F`6=!vF(!~hhl)*ZhEmJkycHQ1Y-2sgLTcWw(POG`a z+O)k8kf>1A>!3awU;ugQ{45@?gzH2R(kysvk3GWIPL<_(GJ8#Ekw(vSe+?) zy3S+{9Wmh47;vBPwLHC(6z9C@Fsz_>w-U|LSY-2J2^p+Ag@AAykrRzMzNmO?K~9M} zEv-i#FF>%0!VRZNcok6dcOHt{YM4qu^t62hCss^kQGH2&7%tnI4MiUSO?mH5MABuW zeNHTff^FDJ6_~;y6bg^dNE7B2yY0}%o3tlV64j(gW_Y;*1`Ys}To}@s%{|yP;qO9g zN8u>7>F%nasnsy3eQe%%40RkuAa-+~$I5F_n^PmiQ2b{cWy*Cm$p-ONfL4*3`<`;^ zyy$bu2@i&7thjAB`0`%iXzQ5D;!mT_WhY-YO^Tj}D~SFe<^YE5SBNc4%>(W+LEv?3s^3(k^i;O;DbyvjKf0qZFYRg5n$dN82k#YrCsST~|f4c~{+*E899@MJuz!IqKWd ziWS70MhyWmv@7y$eYBV~C-9{xg0$T&_vcW@{t}~JKQdD?(H2lS=#Kn8pyo^%?AYPb zI2@T`zU^`@M-EU2$cValSuMO2wXG-WvL$Daym33eTUrXCr*4cz0na&7P1FvY?6C3| zJ4+QA!kU&Dj`lGghu=$PEtOpQMVokCohj)kCJG?Lr8|r0VCUiuAENClWO0Bly;b2C z5ls?E+)ortUwa#mqY<1F9aVPnKt)4!Ck&s&B{(K^Iv|MLG)d~^tqcaiui%!iOQrj6gYN^}xfmF8IOhpE-7$kzEj2fXsOuL+Y#w?;K4LMnDPGjZ7^L!PIb78^7|JeZL!A!bxvC)bTrTq{&mbhu z_))MbruN9<(1cHZ=iw#5)>QIarEr42SA^GOq+EO z;mgHpLyRcI0@HHXNzaz4vZgYoiSE`w9G#8FVT68Wv2EQcrPHYtp;5dvBee3oy@U z(^e2i2K3M*duZ0~km{21J9ZW!x?Z&*T{V&GF*JBRzKM!qz7hIU>%Ueo#cFj`2ofc|8l=yG7H($z6i=wAEX7Q#&BcCI}&jxSo(EnY)=altqGfs8+` zgNWV;uO-X1y&XN1KM}o+C^aQC*5NZovrbXdMVf)5qVo5ObAXZk%pVTnCi`uirqzDb zXzTQ21GGTxb&{Cz16ZK`-@+SKKMm>psd9TMW{g?28fARsFgGSv28{1FUXMov{tbyy BY8n6l diff --git a/Tests/images/hopper.rgb b/Tests/images/hopper.rgb index a72fc5b1514d6fbb1a11800cdbecdcaf10833c86..7c6d4ce189dde4c74f3c5e8c3f636730843340df 100644 GIT binary patch delta 17403 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%0A=Kb(XR$)2%d`HGA{p`2h` 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 l^XKZ$)MK{|`wxGIjs} delta 19254 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 ctk;vKcb6u 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 diff --git a/Tests/images/hopper.sgi b/Tests/images/hopper.sgi new file mode 100644 index 0000000000000000000000000000000000000000..a72fc5b1514d6fbb1a11800cdbecdcaf10833c86 GIT binary patch literal 52077 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℞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 UXPQh9zY&8g)WjC8#oV{~H#!+T*8l(j literal 0 HcmV?d00001 diff --git a/Tests/images/transparent.sgi b/Tests/images/transparent.sgi index 482572df5561858412faa3e2980ad57548206cf7..0003cf33f8c74a2e173a6eaf578e8252bb1cd702 100644 GIT binary patch literal 120512 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 literal 43896 zcmeFZcR*Ch(mvdHW`<$NVVHp#a#Y0Znsd%M?3ys=go%uk$VoB^2xi3`5wmNKCERYa$C(vy|L>`2QT|vc!SfmqTIg=18N6=?NtnU$GvyBkj4TRVY zCB$9~LU{+sbHsBeA3~fb5z?|FA+2;k;Nh}|kk)euaTS4{5z^)$A#I}wX@@$upGHUr z=+LnTA)Q(h(%FoVpENpmg93km5{K}g?9Li#BQ z>5pdvvIrR%PskwDVenEyhCsKW{RtU{x(shc$OzC#2SP@{AfwrYjOk6t*xiJTN4+K- zAY@V(LMB7Msq+c>1$CGPelyk)GOGh2ZuNxBIYY=i)Oo=GLKYblviKb#OCJ%k9R69k zosiYhgskx+WF5+H5E9}6n|K8h;)8nlIT5l^ix9~dLS$uxD6SC_2ww#+BqS6z41*34 zuxS+Z-*lXimgf$ksSQ(q0j=4SwA2Psol8LUvvy zWLF*`yB`v=2N>AvNyt7MP#Op^BLn`==m{DNng=Z5eG|wvsRm9oKztBF#1_D!EfGJmC3V7%NeS2B~Q|7=Fs1Iz^ z7xwMf9+-ms0jTSMX5b1s54s9m9Rsd516K;*Y6EZu-Xq+BtEIpdbQul(#=utN41g=h zpJ)kO9VBGRVBo3En_2c}@}J=!4f+Pe*S z8UV@yj_@o4w#?`Uf*&##15dX=(A5Mw@gau~yYkzBP&fW$&=QazC>pd21U~$GpckNz zz!48*2^tAX1)g+40l*Y|A>0E@LAF^jFom`yf@~3Fi6eojA;6Rt=mu~F-q!Phsh+?T zd|`|C?J|L@AYkejV5$=^WdwQ+TwMpQb^=$Zlgn6O3UXS*wyyAB8`QULYhVh`+IxQkkol<3!r_QjmWWjZxOxd(!PcwroBLKm*2)Q4KN7fV z0Ind<+nW&IQH1#0Ay&Z;QutpEodeDTSAm3tj6kfy^YEjDL{0^+V3TOrJ{C5LdkkEy zLab^5T)`&Eroa{amRgNi1sUnvfU6o{3hiy@Am9pq*gXT7!u?)gWN$VwH6Qc{I1+$5 zfChu`UIy%GavxLz`UIQ+cYN^ScLWUqO#`h2$w0s^A9mxzNBkU6ISBfh>Vu${=}gc; zU<&pVz_!B9pgX`6%9{f#=BI!uydyRQT>`FP6U%O(FTmAx;A#tS1%KM2Zg!}r-D}_~ z3%H5^u3%)Rp}>?aNCjLK0$1>z3+&t)@4Lb_u2+GpDBue3wwnk{^#G>qfhl8PN&^Hr zohyK=BH#)(>3RjYIuBgI*FBB{S37|#)Vue7;OY!;^($}%8U69^0g1qs2XHkFm>L00 zA&w0d0#j%g!%&~$z|M$z;HnC^Li`+~2V5-zu5!@_E(fkY0$0(9RqcQ)=sFGfnE{*4 zgdT2b=mTdVR>5xb-vC#)fva@jDiD1j{IhaA`at+&4Rl|}2Cgom4+L+omcZ3J;0k*C z<9$gO`am!Afo|vnyCPQM-4Og1CPA!n0IqPqX&!Key2d7<4;%zs;qiA zM63d?({=+_&4^X~fh+vJGX}WY1ze%7d!)eB7!dT`7XX5vGN4OF7Z7Zd0bMfI5JK4h zB{BjZAqXHT*C7Rh(& z{_Qz^pWgQZ^#u+1Mz9O%_w9Z#u9SxS<_a5pVQyr1NHB;?DzNS zZ&VH~M}MdP{x0KZ+|LCq2Ei|6HE09K1LO(v{>?r7Nhkpm#190&5jjW!3If4*1Q=64 z+lVXWw-iKuw;n{x(efKX{vgVO{+s@`3bYnP-v>qsFiw_zBeZw21Vrn&05lIY;~Sy< zkm;Z)ps}D)AZjaWOKN*+@0K82kU2;P^xwkKANl-W;p#h{zF)s%>brj5u}OcY*y;_U z`cq6%pHrg$raq_l-~CT9LhmU~C@v^QXc=lhiYt1h&**RTImK)r5Pe3kl)lTLyy)}3 z-=0mymHLSi^)2=3cO0z4J;eegiZM#x@gc*#0z`2{F%|OdN}mOTD8^`c4-mxCqH?~=r*)usqWGfsOF$G?3qiE5GeHzv6jOi16}1V~m)e-(stw2%WDfej z#+3nNn1F;J8<0JS#;dlVcA)R^ssrvR9_TZAPwPoz7L85xN{Rmd{k`8lr}+6!-`}C{ z(`Qs?>hHcFY6I$PdZk41LFqfLD9$LRDAD`x@6zADzw^Buz5ctnnu{{uaYg+{W7gVl zM6pEU*Lo1Wrx2<+3a>JG4>U+GJi2Dhk(V&qaY9nd~>Mv&y^|282e~Bvt@HYclfhe9V zzTH!~0+0!aVp|Nd204NpKooZrKOI37e-wL^DCX#uVus?X6NtV;uk<|^5S2suQMr`2 zC5ZC0{zg>y??mfG$@&}7dpi()-w8zP@m;nn?rHt#@04solsC1b^|xy)Tq)6d(Dy0P zd&-a2y$^``p)aT(s3(Z}qc3O>Xgp{VXewwri29E{qnM`GQQxlPaGeFB@3?`!zdsZA z^!XgnA`p#96ekoT^FZ|Z6cBx%@}n3T3z`i21w?nm850I} zaxl>Z;wO=?z1vdK(s%DauzyE#3_D5=r6O42k_aYJ78V(a#`dqhA{OukX11;!yR^4A zHx$YQW&(B5RIbfP_m~6Mo|aVAzWeyLuDT@mUgrLk=#8sp4(lvrp@QQ{>_M`3_x7z@ zckDlM?9iUH#HdL8VIt+QKsY->5gr~&LeaSYjTa*wh^0s<5LkB|HQR0KfY!RgU;*r6 zCg2kRI87u$x<4&UJpZh&QCAgK|F&FtV}F9DTOSK9>O6r&?c2L&`_|O$`wnF6&Dgya zc!^L%qE5JmgolQNgocI$$`uF^|K6+C79umDz_cJmy9-;W_20E;@1EV;Q&ZD-?%2Nbz`nHD2nvvh z$jI>Un55*y=&&HUR3ejs)p+vnyt~N}bu$+V+fVX}Pu=V}+!5%PsoOx9W9I?=d$x79 zvl0nt6~_~W)&SYLR{~W?b92+Xg5!!Yb}T#}OJdVD@7%q6*N)`mr1(t{@u^!=q9Q_B zAxvmQY%1!U6d5R$%H#?uqUXQ)a&*V`4pyk2KQy zOJ)!4Xm4p|YRU)iaYWIgtIvhE=BnUkRnwch$#a}I>c{)icI?`{Yg&zw44l)$T5M*@=Nog>{N+|l57k(z0-L`S?waq^0t1qVx8Zh~N0e?x*KQEBs zXm2GJnVFgx>gwnii!8+WV-F-I5p=73A;j_s zGB@QL8yo3sYw-*%x=nLmx6-Y@lh8~gwr<(AZ@*5CB3pYQsw<-P9YbRH^Y@pl(B>Q7 zRhCt}sc*t9UnT!=&ASbLA4O6!kMG&Fd+*Nl)P%@jiI0!JB6w5swjF!-?bwVs$&L(< z3J(ho3XlZ>%Rw}g_>Us!O#am(Tl)5CX=?+B3XP2o4fJ(&4J`+(ibzTco!iZfC5S>W zH8(Z0a&Qpxg<@+<0mO_(Q|lZ4;0vP)ZG2mn``}4Y<(p3p3<8&-DxkT!a@#0<^;>C& zcWg`FzI)HMgg~FwYrOq7%EO}L6H<0=O$bL&MT}=6Lc>CX* z=FgSNTUV~$ee$fl?&Ft+re-6Ryy4738@x1%9LhbJz9l|!TgJiNaf)?oJiIonTkqlJ z9~d4U91wz@3B5shL|8~ja70{6^5(dd-xoUcZxl+N+qb2irL}#R0i%ZXbTBqB)HinM zJ9+lJX#-lCnlb{mFjQbJ77ENdv~O=k6&OY2OrNmFY*k`?Rl%KWSFT*Y_qeFEva06o zhtFS{(NvpxDtYrQpEl}u4?jA+GbJK2b^p=536Zi5%a<*lH+SCRmFw2HuU_lr>o1Wi z0z<;WLW09Kr5!!8FC{q;quWvBe^)BaZ9v}Bf>7u>dBQ+Dp|M(&++DU&8l7(a2+%tg!H-B+(!=i%k+ zD-DW>KX~Qx>Gb53uPXhoO2tke-K}lQwxd@?Zr+-(>?d7KO%9vQFd=Lf&(OrwP+w1< zF$gm>LZcq-KYqfvaX+v0lW$zRaQ2)9ih7&`+ft}j|NCMcCrs?$#kJj#xss6Zz?mJnY&HucT^5TmXg_rP@J{C1TrQ8N zqi1MhIe$~YEPJ6qEiF=KWcd9$>W+plA8U%TwXQ~8xpDt-UjEaZhmW2r3trUJvsLnv zgyCHE>oHr8pE^n$Br)aW z^=l_nlehdQ#iE@0ju_m-#kp;-3Df8O+{c*3Vy94*cs*wO`FhOmWz>RUSX>HyyuP`){!L|RVa|;!+^d_e3%C;G~*{OZq*4YdV#u3WkLpy+iyRp@p4I4yPEqCEpP z#czp=2$8K_FnPj6_-Fz%F=ad&)bzEAh{(;c-frW5S-j3emU!gI@r?bu)3+b^kIF@H zO>JAYYi%bo(ZlSD&EaUWx3Cz~LCa-<5niJ#IogIM`uYyTC(Iu^q_se3u9g+Sosf9z z&Yj${%IA*}@px1x<*SeN?<;ce+_?X=xUA+=6LhNHJ<&k@e%!LPzOq0JioMn>obe0h zTxz}86B6B)t@V-lESoWNwOsBiOZ)ZGscqYK?Ad?(KPVN>F%XEXEky!DT`jIA^kHvi zIQIP}&Tt#m&RADqVP>G;(|ygnjxHiIGm%X)zUs5-TKrGCWbv$VBL{bPvUh6TWx%*O zOBeoixwsYx0lG#;2HJ?@tQHJcXeH)xS)3$IEo~lGi>IS&AhzOb8qbLio@{1jF1D~# zi`!?vWY^VO50tstH?Q2dbN@*}NyRJWqnw8i?%lbYjmTF3{53b%6c)Uq+PypIMq|qe zk~ntU)Fpo5n?ih+&Gg!T`{s>H+ZQcxgAs*(cO3N4jj)q zclN7x|5I-H+-W1acJ4o_r>UM6hsCrYEPjUx-l1VXE*Gz%!P3$T+tN(Xq37&*Q#zVcuTjm0lgJj` zyiONYe`ayYF*yzzS*9ULj zx+^QMqNKXwnexH??Q?#aFlO|)VZ%pH89sB(nhgtVYC%889F<^|5N zM=HVsp8Kj@Ag6ES(~qiVZB^CN!GiKv&z~rtKPxV+cwM+x5*WAr=;^Gp zr@sO+5(UFT2Do+`G`wf~4jnr%ozlCohz5(p(c)X!*w{PS+t}LTf3fYvfP{Y(gksvJ zB72v%e<;@iSwn%* zjaiiZ&CaPbI1VSxMxlDqdF-Fq;5#Hc}|=B4e*$|szCP+u5G#u?%%GxYlrq7yL9f%(;yt4mYxA$Y-7W< z4Y#os^LquK`JX}~NUDjYZR!3;?$LC#9? zEHC$yifPtSg<%5nvG$`%^)hv=j#`bht{&U&mzI_k7e6b0mY@IZUh(t$%SVkFHZMIT zIW6;Pc?GL7vb?mUMEUF0%KW4IZ{N*4^tD_U-|0@Su5H?OYTMSOOZ$$4M*h@UPlK?u z^z?M~sB^fsp_b+%8}p8#ckijoKnHUh2k7woA{vA%M0&>D%0}yJ2@8wvv!+>pZSV@# z(DZs}Ab*jktmUf$zr1<>EPmwInr{^!dUoyNsRhoR=z6DN=zqn)g}oXHQve5zLhJ}CZb)}mVE~eA2p!0wg%A_uykem zrk0wv5jHjmzBbl2V(W>Ui6#n-AOSYt%EbSsjE1(Qz!ce4k(CYSAEm6!T`bh4h7%cU zaCsqz4^RcXglFscNoLjYWa&U6C6?by%Ib_5@D+`VW z(Xp^I)z#G;;J--3v6Wkyn_IGN0<2B7TM+KIa(0eCD#y2Xw&3&ihi&|QIW71E<%Scf z_Cq8Mit?0=H1`1=ROQ}UYCEPBK6#Q?{4DR(k(+0Z@7E$_;!c3n_2={MFV&0%{>1ek5&m|4@zL%6l@>CJc4GmvDy{jtC zQ&!f0X);&IUae}OHc-klz$icedGXWiw=ZLTJ=~WsS-fcMdyF!;zpYKyl=&2y&KTU zC_X>W&(C8igY)t~efs$EQ$w?^*iBRFug!-oiA#=~Hz%QyE)!^?DhiS7q7LY~_i4`wvIVCkqEs7Ku(yj!qp{EbP+0 zm9tGN8*3{Iq$w?|2$#{@q^qlyu4eIOAq~kQxGoG@nrpLI96c`4{2K-$!eVJgaayGQ zRe57=qJ{E9iL}KnEtTv;KE^+aYRvpTu|7%~%z(8|{y@i2l2BNfAMO`);^~v?w^O5o zeCE$yx=7|TUdqU}%b{Z3hxc`r&!0YjSy_JO@Ws3N<<-@d&(3B%yp~b1Wubb5G*~z~ zwp{J++1oG!OmYD&R9H6em z4>*WT2#2M)jYIZ*;~@9}2fofX4w9dFDoL?sUg*bnxgS4$s;~d_smY8&@JlS#Bv@v+aJhnVtCO!77tgJZi!O{4W*Du|D-&lL|c1dB@ z!95vShqo!;#V>#%2b1#-j*bpv{QXxr*jkAkt?aBZnl`s}Fk&Ni>me~D|H>nR&C}t{ zD0zQpLT4va9ZeR)(_~C645%|viy!e2iS=5rG($NYM)H-#CPpXr&n!%NYCK5UPk1V6 zm6DYguKb{?EdBDO0e`+ULH%ZZs&M0C^?Qj01`r^O+fY_8h!>JN-gkjbb)@GK6e)aByDh??0<$%Qhk> zb33eAAnWL$rLCv0$LPoDwa%tIE-upMX%ANOnAAnUu!7lqQ%2iDtjkvO*z_kJjJZsN ze3NE8hvV>-!+aeRqalChV8T&zkc`(>Nk8N=d4b9rm8$q{lO{$?0nJT~4UNs{D4Lr; zwftJ!oQD_u{CzLpx&A2o(!r2lrY%|_kpxVN4GEC56k)Q|tTU&N?G2DiJ&S0X^h?F9 z3&#%Zy^wu0bw_F4i=}E7i8i)fH~RbcY30<)#n!@FfMppBdx)_iM?Xw^*;z2yW2UFe zTvs!A;A+NVvyDVNHrw37ock9Huuf;LfysboI7efVnu8&m%e(n!4r0Bp+FsRGg?v_G zbXWq5DD@GHnwuJ+u?${9Gj3M>iuVSSnu`}+BNE!zJ#%wj0&41=1 z5D57kzP=Wt$#4Q%42|8o(VAf=wD=Jdfu@>?to;|{cqI+2JO$Pa>R+In!SqvGB^j;$ z`Qr5(SGOxtE?m5H`Eur!3oE8CUFYi)BaJ?iz9}dmC?qsEKqir5og+};k^8Fpb@_8; zaPq6#r%wx>Uca7QP+8-y<`V4S>H%PHJW$QOvMEIT+EnwuIi$a$ve>v46s zdiwf$><4Nd`Ga)LEKNFlud*~Tx3IMQTG8L~I0ha#JyvTXF|K4>>@86Iu+x;S+r3%bFa=Gs9 z!5P?lGKieAv$M795>R@q1PtU;E#8^90&E`%g zO(U0C$nrK$VQE`f8n#gLiTnc}Mi9p5x1xLm`dXS?Mk}66yIn0^eSJ>2n$PVXI>x`@ zW2~X(BjpwAs6wE2AttV>(l79FV|^8x_s7oa-&xT5{Dq5f@`bF+r&ld@_wd-YdD_AK zTVsMkqLa34jtdWv2PEt`xGN(3;?&ZKHMaVYyu-n_3|Y!!A^b`JK8 zLxQ7|mB19P>(`GlH#0RdfTkvVV*^G%+1S)fXyS5(a@nWDW;=hTT)qfc29_e`4_r>F zx$yY{a1jcP^|+c^JZ+v9ysfEcEHpKAR&&YV>vI3hMKVxV72KdyzG-e!sR~3Yd1G_= zJKBt!BEDMW+&ShvbK%g%i+eN9Up}ySfxCNXa`57ml*EYO(CDP()Gbj#ijb7U=Z~a> zCuSVX*y)W_OIQQb9Q66k<0D6&RtJ4$V{60M9i4Bwy6Wm{5Bnn*$yVrGpPT>jUA?LS?O4&+&|FiBmV6&V1`^3x z20Fvh7gK!$FJ0O(Z_)B43DN7S$FRHp4p(?PUvhhoEO&K8m)~YY9v-GT5{j(P` zeEp@DE+x%gym(1M=pz4+sF>JI;i2KtF;Ssdw~tKOw{vr36z;rNuA4H)BmYH7alxaj z2M+Apy>t3kM%LDhO(LyzPl=zmqobLzsi~kx4-;K1N*Nn5hTHKsg1DX$A3H@%u&9Hi znJ$~-T&U)>hamH=t=3*j8J+(fBLNukHPw1UeJv12lfyGWpfl7#>J(`NJ*_`8l62Km zh1V(z^75KgWp5kNpfLFOJOpdL!9jrvCLlR1Atp#35)hz}u3k9ZZOO^I+4pZ=K5_EO^?k?PzVflQLA67n z{xW~RRW^>M#*Aq|w;qhHo&n2{V<5o@CB)Fg1OX25T!-OAvdA7all3eeLkph9UougE zi58corJzuU-T_E2=xJ*)oESETtIzoX7l{mdzfvmmlufGq*Ka;HK<}V><+Fxo%FpPs zrA^D#!X%l@*@ScFjvdeN^Y=@=c-eFQ!i9@GeP(+5Nn~095~{T#Q10OypiszVYv;{b zv`lvFbmp=BIZv-2iM_M(D-Wy*Gd3Z%9WhFoVDDgT!teu~S`nVMjxI}|Wx&>#L0d*I z9)8zDKxVV-4^tK``GziSESbMzAp;9WD*%0$CKvs+rltgFmm+n^Vb~ErVsTSX6r8SiUr2ds^C|$AtxF<0H?lS2K$4)*d-1 zYs`{HK>618_J$@bzCxF!rNv{krMkL|o|e7@6OLHSspxtIORz4&yMxHe(bdI<_17Gv zx-6EKM3c=8L=OhlIgF-^jeeXH@dFMr@p~k>BtbG&8jlDI{%{MV+ zBmV`*As?&F=rFoc>=n>Y($T?&7{n}U2cCIrzEC6gY>*eJ+e?Bu$zTC^poAHgq0={&kr#GH@cx+g? zd}(w}>ZWtd{eWAsv1zxzT4PI>wiu||SUcHGPTPVGKx~LHj0vA`d0H5A2Eu{B1%Q#F zwuCkhkJZaqn$B%o+ls_ui`K2%T7EUh-?3t8$~asthAUUM46aO_T-XG2{D6-nR*%k8 zbn+NwVm=IlBvi$#S5?)ot6o)e5ln?N!Gv={c(=&*y*e@S!Z{9#hk!c@7($D zIjs4I=T1ed@LDu~-u(IVRzZ1B4^J;|Z?zdVs73^=*ZXw3o)=WG1ezq43FUkz=sIo3ud}Ek%inHf0uA9ru3!Ojn7aur&<@}W^nE7%KPfrg`PZ?Bad2Cv_ zO!_z}HsyMD_Wf(oX}{i}5wRapwC~W)%963#)UI7CYvfSu?2Po$0h(wM7B7&iX<$Hu znwAWIYjT4)Y%VgXZEYBFfTe3YTk{tG1`8<~*^fE&CsONr=v<=Ye(fIE%kxW2_xw+@BYMXH?D@PWP8ay(VV%SQ7eM- z?r%PL?|ydn?K9`^Wgl19Q--A?#xku{x9&)G+SqiE_(`n|jaV8jcv?K8PMunhj*o1| zp(8R)ZU9He(W12r%YrSInEdC3rB9GckrZ+BB+s!w@(o(AR9On}-U92sggKs?dHUdi z%tO16oPl;(VSfJpv**m2Hgk@zw|B^)Bag9>DZ|5)?WLgFd9XYKJ(eAKlzsIsD?8xE z*=zS6>{hdpT3T={lUnuYK*N>ge1HFq7)BEf4TNXGPaPw%WH}$&@w9?zQ)8Q1i>=uf zj94ZT8~*3T`w^*OJ$$Itq?%zvPqXT!(g62$&~g*__aj@6!-Ma}4&ks+uFwEwQ~qpeUlVr^kxxOt$1c^t^TL)}03lYDQ8EcyXr% z5DPb2S=&kd{pRZ!XcD4P^Vz=4TX3n0s2#xdLBUG`uzV^C@ z7djwKuK?(}!8eDM9SX?a&%S-@*3H!`)ZtTxbT->E0Wk^6c9Zx^+UZlLw%}sc)II{+ zzQJWT3q3GAE9Yw4h&ACk~ZFZN?0@ISUr9_VEsR^!C%ox35bct%uIuYXg1P(fBca-Tp=9ebU( zr+02XzQ}Es+N+BfqqXjRQC?nBm~)Nln^dn-J>qytSD#`(U}wwk-nw;I&4O(qr7QBG zR`97+ue3uVcKX!ujdNzrnm%LJoCQl(Eb{U7 zk=%du_~Gr#85m4T=~jv0qV=mW!di1HTk}Cc_WgUe?xd-C0EB3Ou~?|5eXGSFV?8}K znrjO-Mo!uhf*duM7=TzvxyY_)U^o?^iPx#t68--g52?-@3?$0)b@L(Nvq}4K%g)a`sZD zSsjt|J*sBB4=B6+2 zDjz+_W<3Z$ZsXX}$yO}D@daaS@`tKlMWp;j1l@B;gJ_%1?O{?K`;IOw5(-pl=?m@K z>+9$+Ko&KYyzIp&wS3$gco^4Otm&G>w+R0QLbnR`?A zW}V79bfB)L=2Y6Y;Q32dtnrdZrDjMNKLt$f>%;Qh>n#uR_gJvlXLWw=hHOBD~eTE)uWpVXP zt=n|dXX!{0wzQ);?FL#P3B)6h)KZFOVu%{;5EmL87#@ogeUY&Vn-dZelhiU4*xXh3 zi9rJILv_xX%!3g&|A7pB>mTZ|N#DNf=o#+v@aXusXoct8p;!-*Hh;lfs|F@lC|{Hp z(F92@j&MdRJG#MpN5Z?9{oB0Yb=E&_~i`6E2TQQOmnOhOtbtwlJF#7a0;10X*RtViNw2PTZVI zLcYpV5j6FEYETDl7QqRS6|ejy_N-^T|XKgINHx$)=W?^#$3u*lqSGKMlZmZRv+UMesAwx$^o<3{d z0=Fp>f1W;L<}bZO+ANDSbW%qXl2a3Q>^^iN^F*x2vUQu{a5ib3Z@{M1jI2oawcg)+ z!uH*>bdArNGuQ9kz4_~DG=u#+AEjGilLZbo;vgf=TiRNo|Dk6X)douVp+KH7Bi?T7 z+P;%Eho@&`jI|jq*VqKfTP=NowM*B*7RU{&)4|$%kQCMIb&pERvc*bpYYUz5gqWzv z*m#`KNs5b$3CE&iY)q&^Ee)aebM2ejk8D+Nlj`mDB+oe${~$@1O*FK1TQG=r|5TD5 z|F885V>wQX*q2@Qp%HFp(J|ngS0$wg@eOa@Vh`Qt>X${ixk^Evw6F;ioma>r1~)gn zzLUPHuNi}?wjw+C^%yXC=%^`3D7#IbIBDvP+4JWOv({vhWs=CmO{rH3Yu^-|&0@~R zU&z@Tfa75sJ=b{$N2l&TyLI8hReruKKPeqDrmgq(^uB%R*vVrV7a!&vPfEP+W@e84 z8#rNVVrJ>!Y|q#Q;Y^``Z%RyC|DD(t9Be~tq1D>K*4EOL&oYfQH`HcfPN>Dz;oG=$ z9nukgP&X2`HjB4M({$14lgEzyEVgigM;v$J%se`T$mqn_P0`W8GFfm~Xn;&krh#1BMJ8F%e_)Nn!OMzm$tTnmjS~KI4xy4*By z9z#13P3?VJT(Nkl`>LKIOY8QoZMaKz?o5fn+0L+t*qErexUh}h5-iwBWiq*3Ej3j2 z?mbSst3FlIEzgIW>++vfeJtF%aK?-=}3QKESYd*HaVg1R_6eqH+u-P1T zR{O2=eF`E-Y zLXvhIKDx!9LG#(|>$h%c`n}Znw1+tlbB@Gq_84Yn=WHv`)7C-XtZR(VYv7!hmAR&= zRQ=6hN3!*AMO#|2;36w4v>(QrWwTw>KKw!Xvm2>e21bT_+t%%|YXTObb4a@8#An-s-M5CSAEPq zceCts$*Jhn+jR|B6w6~S<>xs5STal3z`)4*FC=fFk~Mhdw$3&JU7ijOfc{x!XA;oT z<1!9_>cL0vD__=NG22Yt+aukp`dF^SvW`qy4-Z8& zYeiwUvv0q_OdCDZJuqnTlqLR~V!dZfYNw-*1M;>DURJ_qb#<)jeYdaYR#aD4 z(I)n)>~eHSh{AWhyO%U1e9x{h%-u0oTfSn?#rVzpA2K;x4#jz`w-VbqTk-X<6@|mq zGZyf%n+GFgzLu#ImAn7%kD>aU(pB9{qk3L!4@Zso&ppyk3>i1HN{7^D*!DSJmZ72}7|E=V$-zA%h?V+$P zMvp&IIKXbhbxe-fa1j^dfTp_gDvTSNh44mg!AsgZQoq$bSJG5}exsIZQ$s->J(2Rc z?q$)_LY#bsEAh(Pi}E1?e9p*`*|X96M=r->u@@I^~z=L(FYTxKE7*~xo4b+ zPs@Vu9_{zsFxCX82~G8(vbL6%kwdrsU9gSTl+UBSb0p{fUa)IBEb)zhiyq}+XdhQs zCtGKimQD_KSQ@g>#qJJv+_4j^sRC_v&)$=4*9@2Vzn$J-NCw`SAUU`ogr(Qw3FT-qobK{GH$y z91_K`{gG51fm&S*Z+@h1fN<#rOfxyE$mT}unLzMENUnbMzDa})xhhp{er{1k9X8@M z)mIkMw00339Q_`tFU$o#ynd1Q=wZ(DTABh4`0_Amsx<>u9LW3LeFlu2>m41te&&jZ z^mzA4OXGHiE*LYiv#G9`t-Z}~--ytom#;mpYx?*y`{=1Nhjwns`t|I|1BV_}6~D;c zw$6R!(#4Aw(&MM=w(gBxw`9Yu!fRM#T{*PzeW1*M(wC;8o zW}xjkQrQ>(ne3?5-30$YuLJG62zx3D(hFbu|t_#TphYkO;b zEK)nS>uAeZrQoY(8s2iBm2;<07&&U<%sC6)gVQqhrbb|-A(v^%)q*eMgxu@vyONF< zRNu+ib?m~mGl!3#zwz+t$ve|mR)3jgl}<9r?o`@LX^8It^F|3u>e-qZuH zs0)Ld>)+x784dN!M@3yl)rTf4RebYnEc53o3yWVo%cChvP%(Ws<{gp=)%721(f-g2 z<>Z!qz&IXF@MXplS1uH?C(noX?l)wb@8)!w+xYcKiJlV%y7~If8#TDEJx|NX+@^Eb zliD2F{KPY7?!2xp%P*@fy?^o2tKvu1O${F^i>e-vo~ZOF9PM6=qM8TIo@PYasU)4|eQ+th)|zVt81miM;c8=JNrGJSG8 z8=R1Hv2}LJZ1|YBYo3DvyM+di?Bj^ry4srCyEx;k9AadU`}(X~I&=Kc{=Iq(9P`Vp zrHZYG5ABRo_+YOWPp+04gC*KH^U7258fo;_|A&k*pPC@0TCYr5(f7YwBj#{CN9zFVx zoU2FQM$k^7Q9Zol@-j!Tk#xr1PkqnlWu^BRDB2yzB zO)k7=xNhN$DZ~5q?bWSo*8!s^&R8jnN!k*v@L2C7VPrCF*ka4olGEOQsC$0*%FU-$ zAK#Qdxs`cf=hoEJXt8)WdBs`CqLF%b#0RI@k7lV#lxbi&lu_`zFgnfl?Ns4NP5p6y#|k;wS3K-kpl+wA2@J8|9*Y@4IDCj zu#+yfeR6fDTwqkE@2za#*0s0alee`P$6(x9Q}y`jxhL=6ls-6_eL5$_ZR)gHvl8xI z+Z~y3mrNWt zdf0$IJ$v-%-gDrvQBxPL_g4f-z1Dg8Gm-#I77$HYa&;9BR#a7&Jk80&h`RRE+u~cN z_HRqxlD_BQfiUd9^4ysGYbAQ8H@VkuJv{H%-4dH_{?$rIv2XNG>d%@nu=PLGmut<) ze2-0{pQ>0Tk{8?(#fwsPFvXG65BYFh-fQ%dSc+(VLk|bnV-c{bIEQ)2$_YV3e3?u!~&WdZ^=5d?WjSMdiLl&Z1T9_0|)lQ*UozM>EE|cUrhFfcQI#a?#HI@gLR)i zU0v+b(%H$bmD}N$aA-|M)!oyVa;q>^xqj!_sl#^@XH1)uRLrPS-aNgDFI3!kRC(UR zW4*hJHim6T^g)W*3cT-D=+fHi4_1(MODdx~{XbOc z!K)?JpPOw|fghB)`S0mL9xQFYMaEZN^Qr*Oj6gtrUR*%GIP>gvJpwCE{ajz~3`J~7 zRQDcm=$Ik>`t|KQcwnz?y_i0?`wtvEWT2A{wj<(yC;7e7;?!~W_IB9*W^LJ1cBiK9 zb?Fl&K%Mva&W#88&tDY3$jaD%@Y;jg=JzFc4_>%^=hstfH>`7av)6^8gK>!3SZpaY z2leXRf84zF5^MzC zxX~XD^~YaQDMK5oE!kI6T2@+=_oCue@y)Au^6qBtN>AOscmIKX;aJqih6c}|qfbgo zDyr-1-Z1a?*5seu(8m@>2LE|I(?3B###iK^DN2QFJzF^^;U&ajV*{jd!*=zjiPH!JC zAKGmF{gDQcc&Y0ldBl_?l)fl=SyB1y{6~ zvKM7#+>*em4_|QRkT%}N+RFDfs-V}({P#_D#ShszQE>3Prl$Ahc^DlPRK7<_PF9`R zUF~2=SEgG+_nz=-pMm3k9@f2c*Pgxm_8Ty8U|YT>!%agU%hBsNW?*YeN7uHlPBs=6 z$fLBj>agWd){}z5(sD+X`c`@K>B|S#-&Q=!%P)EJ9@oOB*>_GSmzJILZp*;OI()IM zoj@D?Y#f7+rSMIKNLM>MSPJ?0kR%R$eYNfH>M^3{Prs|j(I3|X2i){@_(&5C##ad6 zT=Q@d@(o-&wRgqW-NcM2%v>Zg=l6c{@=C&z(X3HvBS(##JZK7Vo-C$P38r|;aoXWza=Ia;x|w}<=c^??c7 zPCw1PeKWVB?&Z_VndffZOYQW}Y9i?I;N>@qXq$*_{$LS%r$1UmCh&Tsjql^tmQg2{ zzN*l~SgN$LhJMG4hh+B|_xby)ERbaQT;7Q9Z$5v-$q9Z~Q2yp~V|CFZCMO{O<=Y0@ z*5%E=E)haAE3&Oi=dNA4cI)1wXRqFUhEAR|piAd&J$v`*+kZeWC(Q9MU&on>vsen? z8Hya+c5t;Z6F$o< z#?&?JQC(H;sX=^!l}pFAF0ESGu`EKxX13mWwZ&JHR*d703K}zE;`kB$yLV@LL=K+1 z)I)+!7~gVK$R$YX$t3F)`d)A<6fwe%jgHTF0mI+^U6ruYp7847S4J>JYKBX3m;LY8X4&^0mw7Swh1L zV9<-LSir`|02Kd@01P=X{mGMbRAi-O7zT#AEKLfL*4_#gx3;%8RhAcYyd1dh>K$Fw zKJ?;E*AM$xiv#(UH#`>0NE4A1ngjqDbgCF3Mw3;BHEZeUffJUZ=B})uK$p}0?KD;{ zw1SHN<}|2`WHpVct3Ka2S)IXQgB8OJL3$QbZ~nI-r;DFf-3;6JnKj)e!g}S3WgpF3 zuz2wY^X7iIX6I2SY}L*#G*@>=d%I)yqDfGiBcDD6`5#RAY31#!nMtupsUUsLObMXc z$J!m+i%N#Q$aeaJcBiPky0Y}vl>%NzD=&vaP#M9$nFEFGN7A`|^uG&1BOR_kTnMg~ zbDcD~RS$KxP)Yx}ueVi3=-ndh=o==(eiEcJ#XF(~=SE?^$Af+{lS>}g*v4P9R^mz& z%;Q(pR0}Fg&2DY8(g#32-^L~>hOb9H^y&77i%gGazw1SoT`ShEQLVLf+CJ&kl7&TtF9!CnuXt+!;(caXUzHsbD%InH5d$8 zX?b-H8#PqgY`xiA>;fqfUeTBD7G{Ipk8YD}ZDV7-{F9HY*Q{H;Y~kDmHd_y3>vlK= zf>UP~XZw@3q(O6txS9!6bTro2HT_o0(^ zPTbJ6Yxhg;-MXIpsAF`X=_mbaW zWDMwU7D(grbdh4EL=EK z8$1q@T$%)S?{FNHs1KvdDj`@vqbV@84UCL*X@pA~m59ko%BviH3G#`T57gvv@`B=& zX|d_(p2g~NM4Y&UBGW*hg|~tbmr_tsW9pk2>5+_ql2Wq29e?4xnU*vD$@mN^5i2Ez zcaTJCai(~LhJ}TOM?{3jrX+ap-w2CNwqCW$#@c$-hON7Her~g9?z~Sn?>*^kZ+rZd zor9}~JD}U5S=^|ptc35{@DxXMM|IIf@LeY)CZ(jNWrjHeRA_H^^gAed7nJPiWbYP} zQ&g0n`vZSq;Wh?`x5%8aBFqGB6zwJ#f!^ZzC1H(Y?SIeOLKg+HSNbFvVVTkm290ojDBB|(`H`HNA{v$C+5vt%hemIZSsX-dGNp;oZB z(LxdZpi59`GB|E!l^ELkM#frd1XoT~jjG|z)ZwVJCmE}X$>9P;l~Lswo3Q05gr68* z40YsM*cz%T6eSmx-y3oM>=~AG|Ne-9r+kA$LqfwN!$bVsPwv_%W#eKku_|`W#;*?^ zKeWwy-rTv1*X%qD&&k2nH#90dI4DFkXj~;x@wn_sb$v7GY_(MuUPz1%i-}JJrDsYY z#XbS5JihPSojdm&ISG5kYv7i8tLTU8yk3OLYi|C5pwgtET7Mv@HsSW!S<*WFM+dt) zJK@I*pAGejq8fk=z{hfL?d%_cF>-~j+G>QG+L3#sG=!p_x7AlGkUn(F;Mm*eO#&X2 zhqM50SsnhfAq$6av82w3o-=>W>^TdTtz9wCa{jvAyH?GaH+QCiBAhDn*p&cxtpXkO zipY#mr5Sp9EDff%p1z@u8Y(~3sT!UPT@!5;DXJ0?2DU9VgK4H(xQ|jnBB%iJYIHSl z#WwVtp;j)iTKM;eQYC`qJ%U5Rf&r%q12OT}Yptm^uGaWPs%!I(BR&CkyVtG!_~VtE z_ndU`@Q;X3%}7a3OcITIv%0*rgkM?R)Y8&ae*b=9PPD&Yczi-)>e+ZtAh$?3@8R$E z9d~lZJ=ekh#L+`Om+lo5Jn4S@vah-LXCXC7Atiqia_X;zBwIK9EF_m@Un@J||9YVR z#bBqfr4zs576qfCwYRHP2xUQCT`x!=6n8j&CfWYIr0Y@urs3EoUrRy=mpVd7th)y5pk-8xDFnZJs@M_H>S{l!Rm~ zoi44ZZ>Vpze(OwGD2vW8o2skH0CYfCTLqaV8IWy&xtn7$MO#@?LQD}GC%pMdbFEAP zXjPS?flO76j7|Ww!elbwhp>tN8S?9o7yhFm_u2XdhlNGNCd7n#I~>}+hHCA;a>a_3 z))X7cs(72vzWDBB+=XOcyCVnp@7aCGH8?sk4Xq0@GSah>MWe?(5j-g?FTQc(adTr) zR%&k18Gl!=$i(=h)90gIC=PIiPTIPMMn}eGo{My}J$lGKv$(9dy#4j~%kJ9Glz%0$ z@f6N~Njw8QxxXthhj3eZ`W0ztV=rI4fgV~~2i^>{k{WF5d--bcS#NI#tgi+46B=%- z7ng{s5bn}Da$k|&(3_t63Z61z5;Z+<-@fSU?(FF3dj1+#={DXUx&SKFB3vw}(<2vc z*ni@y#WuSg{SL19?0|dtsnxS)T7vWjUalWq)ez)$-uHOn3+WUIPi1Xzz^iFdbX>J% zRfs?tO&vJAOkFc$hJuuYm<*9bm86RigcK7U80MSm>#|g2P)rXkGC6Y>nz5LS|A$ep zp9%;LjZ92Wjt=CWJg|L@we`y7%a#JWjHO7Gfa^a@KKJGiLzjh7t`^I{2U_!{(v!_os)m1?i3#uk5k}-@)vt}JF znV9Hy(SR?jpbmyr1qOn5D61v{m93)k^#h3jRC?`deR@E^y(3VvuLS-}zObyiK<&{;Hq$Q=n zgh(KW6!D?orR2Tl;^JBh*KJ-oXOfP(EKO2Llfv>|9&{^s9ftvKD3MzK-PqqddHVVK z1cXHeJ00G;f5)1Y*4C6I8J{d&vh?KndshQ@t^V|2RTDfF)^a?6RImp^O7So3kCPirsX{@xtwv~YLcf%YHEA4%Oyp;xk;mz=?vDeSqyO03vZiQ}Ld*6^r z@!`%EVb{f&a3XjqGgx0pG9>HhGH!0q$D3`lU;IP;V5-FJUvpsL6K8HuEXR6N`sHRD+e zxfO56MlwDSrzGog#mzxW6WC zlN!NT^4*>-%Vrs=NlD16v6#%M_Jw!dK4LQ!{$IwwVe9JU=k4Ym5awh1?YH}OY&l?e zeCtZs?vgbz)dRH`95-3-O}sCt;TN0@Idza5b2>ZwLfNzD6?qvc9-;}v)l`%gUOIdA zKEJx8=s{&wetPDmtRUCm?8H+i0BlPV)j8P*oG$#4*M%i`5U|fN`5M?Z^6%BOwN{l! z{IUd+s9JolCE&^(d;*-_*wC}S!QS@H;kQHWLeh${Gb7gTi%8Lnt`-CX$siK<_C^}i z%{pET1CPOhPzk)9pth-r1g-FS6|KW06=pR|+NZuBS@wa}#*SfJPRH8<7m2-FaH|C$ z+U)c7*tyVh`FDF(&$gI4cd;eV1r%v{1sNzj4zWX7mf7@O*E*gF11+d>YHDCQKo1Ir zftj%i5h2geW~(X4D}eaH9#CX`p4a@)UQzMedVuBZvD z|C*RPQ5#H{OkL6Y_V0$je!@E>G{83?Je2$W7dv)uJ&~Au&STs1rAwD=Nqah2m*=z1 zW=C>$cW2}6=u^9n1f=2Yy;0NMPzm5XhepJN`Ub~k zh1vcLmPh#rHzp_VHm~DZN1n&A=}g{zc*S^$ulDZJkTaG9QM(qQ@p zkhZN&h%oH@v3@8M+b#f4bR?^-y6AqKeXzkAXC&FD8A zac7B&%gl_j-??tfj&GtLc2u9UTeoc4@^umA?Yzt0J63H;?HGGem*cu~hjUbRR@Rvt zkDlDkL8q?_(dY@~5AGI}R@78gRMj;$*XJh$2IFEIos=E!=;}-Yz9k%@+{5A$FFbg1 zC+0*@?vv_@>Uw}rnreC1e-d6@fBuK4J$WzTxhA0l0CM6U(}fg7_e%f=2U=x?{>c3I z55FCI)z{YhlDv1mN%;`x@)VysiHR_%7@N1?Y5yoL#?>%qSTkkZl~lZspsM}lU~842 zZCDZ<`3bA&A_XVRT(snqc@`GaXUv^DYu1culO~xkWXO^gc?JvB88oRFP;{U^e(Ki4 z>x`w8*?KytKPKxuQ1qcfM9QmcF!VRL1@5v^BE+dmhYAN9t{pN(awl#Ok|t1h<`WsHRt%WGwHcM7G*kZxBhI)w`rAw_1As2 zS+88V!uDKAVY=g&HXD;VCi?G%?fde8Uox&hXTWQ6o{}XRJMQ8AAB%a_m4b?LK|^!N z>7byHsDz}1l=HD3-mWwp%~AFsRq%~Z4Rdxo^9a;1P;PB)eRcVdzevpucrU5Blj8?* z8+zT>Dr|pFg78IZ^@E$$+VyX3U#wF>Si#+z&sPZ8gozn5{@dBoBcH zj)5v2oE$!MB~3+|oQAeGSUKF;Y=)Ww->6=3$!V9$g^kz!zqMJhc575w zd&M>1ovSD|G3y*-<30AQwb>BgJYJQ0{L3AV;pr(U8St~HS+3bx?_S`7yCuA;3W1>X zZfQ+zZWJ1}C7@Ge+QqcMP)|oE2}c){@dM8L(h2fR11rli3#Y1z5%k5!Tvq9b6B3BE8<`t&XiQ)@w21c{@2XQ|LduQWzSeuOM>c=<+1eAQ zAN`o^vJIj7RW|E)ANqFVs#P1J_`-{>+rHfI9gohJnb`nhrp8EDO;~I4=QRK;H^~G)z>~QJ{kTmq~%VE zd5(I9x2O?%L;6N9+FO)J2n@DX1n#e|7l-E9$4Y2qeODV|5p~^f#zy;^sC*Bo>uFOp z6~-v2suu9j@dv7|u$v|lC#Z?mTIDmrT zD5;X&0KpZ6l4TZBtc0IDG`B3Ysbu=ENmUg&viBSFnq6pIaxTBKu^`Uwo3Fmyvdtwv+4uVm$dMxqZ-bvz zHk*U*7DOKUa_6bgl;rr7%uMv{$^2|xQ z(K17#LnG2J=U&Mko)7r=*xkDp_gw+s?E_p6~q@rwGCYU_gFaYii|!QZYg7LhI)jJ zO)br}GJJm?zd}&k-q|FmXd5C!w$)D4L_#E^fLfNMi6tYdT1+*0Nt!e+7V6R}x-%A< z$icQ~B9cT`1x*UaN5^c2m4%5mnwY8+K8os0&cZ$3PU|OA7#_+X!={MI5+PJM&dd*I z=_*LjG*>tsU8c=|ez=I&ir$4}G$MJw(ZW4q(b($J<;+MQ7rP_h@A(FhF&l(wX{(}b z)_id&Bt4dUaF1=!rANhi+3E2KDVd1wz1usEIeUhkzFu(W)~%uve);u?BRjvd^@z^6 zQgkgVC(hN;i3Z0r*v{>A2?+n|Ybwf0N=r)a=BpE-|B;Wu&{XYOixxtlx?K2OLw% zd+TpxB*bT)zjRJCWc*3L*ql3Me2@X~5zxF7d+dv^b{}#K&CWfOb2^X;mxJmMY#(#8 z4qhX#Oi;rsd2p}b@_(dT4(W9DeWv7ym+xMEx zD?dG@txmYfew7EBW-l~%=o8{llmtSvLfG5U);G{z!*A>pMb*n4roCG&HFQQX(WPsJ zv9vT5Ia};@eZWyuWLqpSQ$(c`O)4B}ky4rR@j{dmSfK2=MmZs4|O(HlG>^3dC}L~)7c>4 z3xShH<@$I}+IpHuJzi`h)5%kfwOJBc;V>P|$?gJP+P9`^>P9oHbQDnrNs|hp)8%H^ zW+(5NH*NaN*|R3=GSxJRa6LmkLwEz|6|ISc6rhf%LJ*TABBYtd#=7cqR2qG0p5$+@y`X>h=wy0-h zr0xF2+k&QA{=>VueM&(F*EACgKm>AhA z4h}+_i@qj-8lellBt{l=z+*3aTWa_KNc!V?HPj_+X#m8geHfm>>-)BoAO?r<(AG0D zH_@SLMX_)PX0i0vCGqO69x&4~m^@>u0b5-LWXxgUkznmB9(_^p_4FyzW=u2I)&#bK z@M5v?T~i%k-+V~Y5KV@f99^7Fgh^`Xu&_l^sgj?ZaM@@9IZTZy`gk4w)o_t6Ug$I6 z?h_Cc7>st?0sh`D$9Hd9Z9~Q0w*K2=$G`vfoA37SkGRFVcP%3+^HO2?6P{>D&vNiB zhB|oU)PVqDxcK7z+NQedM@4xz ze=8!o#k~`e{@)@37yOHeZhFoQqp}krleBK0cba zp2i^D^bS9Gf%<_r{cW{;_;3Ea+Rje&P(z%$s`CX&#W4OfY9$?t(k9%swDt6~CA1=R zQAx^X>3ni3HpOl}(`fRv=@wHA)g+`sP+mJZs26D@-?>JHCWg9N>I}kLo4^5NV5G~U zqD`)b21{?oyh&;_8WAq8s49#5v$z=DY|+xGdIlyYrbhax>3i>y+zGBHniu={(*n@| z)*~p)^UznHLObD~Zr}Ue)@?g>Y~Ss7{_gdhtdy*ah5UCT@gEmID5VOb_@#yUH*eo9 zE*F&Eh_pTG8=aDwm6hO+b0^p#wy+aO!I8?MCyfpDRZj~4Tqu=a3k4MJFG76~*D*$( zFeo6Vy!L%L2$qxA&-;dky6HkLsyOg3U7p4)!fn1g@1+dRo(Z(jZY0s;eie-kRiflU zRVR3App$`X-!eq=cV)38wW4&5&5UtIXc^46`B)DW6Q+~QCYf{Oa3Km-ni&a&41~<% zko8RrC1oZNsiLB(#Q`Ng_}rlo_VQz%2Np5tViHIZOM}sa0y-!KmcE{WkufmD2IS7B zO}x)QZhlT)zEqTYi&|rQ#pGN+6L@^bI#MHF?fiNxdYEqc#`{dctt;t?sb>lwkh_{T z;Zae6U>lzz$bm|(UA&ZctFZ8DlKb8rhdsjMW0TS&-9$dM$9dGNPJnV2*Nuh-UTN03 ze7z&{D*A-y6K1*S3+!DFun`<`_H7W(#0|MQ`~HdI!@S_VbdGI4#Q1dDK&_&A{A zl}I<(*qW=L8*XKVKmr(1loeBe{V#X65`$uN2uZDI7F*xM%*4pZKucLxiYiW(Q-?dI zgA6}KDp*oZeTqxf&~Uw*CEPXWf(@SoSYC=o5R-*iuZHolyo%gYvsGxQWpWpnLXM6q z=0}xh!WjVE40?IbQF8XUcwy)A_4f7+2nq_#t!OIF_OtzF18$_BZTosVSY5x_btL3U z>4Q6$&t#-!-4zYvQXwfw=am-bociTFibO;OskH3?HL-eh>=FXwv z=dIL6ZdF;~`w8PAwKlz-Z21y|#cdY~+j|EFpLKTkv;)@3#nlD|(&1sqCfwyd^e=xU zhrsNc=RzDVWL`~BS&MrWa;i|mXjAlZX)p@tLJ1ixmX3j;p$U4tDxi=T9XHW%R9Xd~ zT%0J%OzqE4*EC$*u1jINf#gMnO`JuKSTzl%j-ei#p>F9@)z6RLq>l}kAb|R#NXkgj zXtEmGXosSwZ(xq%@&6qI4Nd=gi~~+ye*V6m9vcz! z(4oV}9sN>rA3QG2znBm$8pDN87o6ksA3wPD!{rN?a?b=E+q3=i&%gR&%N_?07p%X> zxw593>c*$d&5hWh9^c8k_CAuhNiUB>|BCRj7u`Vrz%ykuVbS$as5ojW~yJ zm;KPKct{Reu93m^Iss2m(f|uP3W2NaA`gC(E#! zYpkNIiB;Fp=5X}Ew(wsMH~h4GzOr+DS1Ki<4>;Yu2JFN%vdZQQV7$5B_VlYPXE`oJ{j=rH+ZzylQQ93^{{``miMpz1cf~FP7BFiwC0l-P=iyAbzj(Ndv%9Ak8hCj7hh62>-pdN}@^C$Q;J8OfWcIDxwCJ>)y!<;q zT)lMV-UZRasRBuU2Cw+~wY-br2X<`3d4M(k`lPp~_vx}G+`_A?>&dhAN+BWT-# zO{W8_9^~x-j_bsD{~g;o6s)izXdktxD0P8v%0w$dO*obkL?jrFsWRxT!ery6B$*L8 z9Hy$Gls*xLR~eit`Z_F)nFkXSPc1Xi(PL9Jf~+I&C4XgZY;I<(%TSf4sr>JwVJm<2 z=q!7C7k4j`F)%3M%ALyzL9l*j*MO+Ri%(j4my=S@<=ri~njIT*TJ&rel&fUoK3)E> zpy>9E#FMCx-~7dneJ5PPGA~>zs%~m(Xl%lM(fIg>Z2!MAZ5<628LG1A(Qbx&ul6F} zg63;|ZP+YY+tB=h((g8aYXAwgo_JlbLtdZoq_DQcR=gsae8*nDBDvK1i|iIqL2+XM zZ<4@zQW&w?x*R4b6_n)wwG~G?g)Swlh_t1izMeLP0m;Atvea-Fx}j{$ZkLpBE6=897@ks-^?4E+%NN9@^0QtC2=~QZA5r#Zc1+kMo1DzAZ}P~9liZSuTXO~ z`WDy_tm=rs-UOdgUgrG(JR0b}fyIWOINFU&7WlGSb%6WE+~B8|rLGc@VW}s;L>cMs&3` zCX*yH|M0OsKq9+&`33s<_)~(y@rQR{bo`a-zNaO**(qt43mz8di5{E8D=B$Ydgt=h zqB6LACHZm3cG-uY&VO79GT0|otzAM|qsOB&A$EHY{EeyVsEeNIF1sx7337p6Va;mSvJZBz$$^O=Hk}31h;GI_J=(X6(Q!_{%p<7Y@vp#FtHpNL5uVg4yI{Uu;-t zq^>NFjS(paU0tfSpT40ksQ6fDt4bFGvzaoXh_%zy0rVeXP!K;%nKWe+*K6~1eM9v7 zG|)E?5upA3=h+d*9Jn4n-fmnEzrettz`zi^b`LAci+}v#!uhLr%F6LP6C&HQz!B$k~ie0VNA{Zic4||8XKEg-FA^$-$3zD+)&~5&$b1Mh*T_vG zyyO?3I8*WL4N1T@EI55~DSI0E7Ns!=h9;9^x*?{lJnOqTsI)iKL4IFb$H2sFGH~ap zwN}6ydf}posJkYcqU~y6Mw#qorVCyeVAiR6u7o~#4gdC&67~?p_I-yA96VwNwm^UX z(3pgT%)Fw9B_(B#%ZSIKM`g4U=o9#)tgffORq(L5vIX2>&5bQBmA6i3UAh0{aaHyG zbk76dL#V%+mO0@qv%vAX5DX=v_QtOolh&$EC4BVPxSoC7F*Gs?a|3+=wyQqgImeCW z(jv0VWF*y?X6u}j(wwZPT27xNS`r;S14EO^=4=K-kuEk3#j9iq6!7>=Q10m(fO?mK z$S6f$#DDsC9uW2Eq~)J&`fBIC!^a(fuMdw&JbV3q>BEPQL{CWVY(h7hj-GaY;p2|s zzOL5#iiY;)$9L}BdsJTjC_l;Lz}|1B{P`q&#B??W6?KuKD`Q6O$#Sav9Opj$<#FPBbzS_0_n4@QKRPyBa8tvJ{l zs!M+a*}$vbw&wcQ=Y!qN4Yig0N4atK-+xc+vHbmE@m5Mq-5!1C=?j=Ud9<`$J2G1q zL&+L1+wBsTaRtqaa?^tk*qBLUsA)u;2551ZP!?RuNmHj=nj7e%3j{I=sG*mcMYu`> z=in<&SJYsDT^8+m{?3;q$x&*nYqc=YZ|4K@6df(I=q=^Fv&vb+EF zi{4hmMIPmN9X$C*BYDYNZ1uZX*7gDn$Kx*=u6eD~rOYA%Bvi~7t=jVS?p<3~&C;NO zI(j-l_^9YYmwP<}1Cwc1mZo|X9ZzkRCewhBn-5o9T3v&qIQK6;IE(OIfwtBw*KRu* zo)jbcw8zCWXY!jzspNs)-#7I7MbFcQx{A`fge{&*kyiWpsXkJM)(8DF3j~cVly=`1 zH1sUUiLza5LZ44~iP5Fu&C|q04~!B+$P7*GvCMj=mR3^?QO~EXg)7kqgp0vKa)N*M z33t&aJW>hn`6f`>0tYqR`|QQYFbc|F5S2eY!ioryG5Fx~{ii%Z8RmNG$eu5ko2xn! zuuUt1s{ji{k81rXR?|&&*;qQZBZ1rTpZ?2Z+@2?VM*4c618+qT^`dXgA-G8QQkd?l liZV1YM}kHW^9eVgPbNDO(!cqCeC@61?;Qy&@&EU){{zzZ;sXEx diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 870e57ed8..af40ac848 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper from PIL import Image, SgiImagePlugin @@ -6,31 +6,37 @@ from PIL import Image, SgiImagePlugin class TestFileSgi(PillowTestCase): def test_rgb(self): - # Arrange # Created with ImageMagick then renamed: - # convert hopper.ppm hopper.sgi + # convert hopper.ppm -compress None sgi:hopper.rgb test_file = "Tests/images/hopper.rgb" - # Act / Assert - self.assertRaises(ValueError, lambda: Image.open(test_file)) + im = Image.open(test_file) + self.assert_image_equal(im, hopper()) def test_l(self): - # Arrange - # Created with ImageMagick then renamed: - # convert hopper.ppm -monochrome hopper.sgi + # Created with ImageMagick + # convert hopper.ppm -monochrome -compress None sgi:hopper.bw test_file = "Tests/images/hopper.bw" - # Act / Assert - self.assertRaises(ValueError, lambda: Image.open(test_file)) + im = Image.open(test_file) + self.assert_image_similar(im, hopper('L'), 2) def test_rgba(self): - # Arrange # Created with ImageMagick: - # convert transparent.png transparent.sgi + # convert transparent.png -compress None transparent.sgi test_file = "Tests/images/transparent.sgi" + + im = Image.open(test_file) + target = Image.open('Tests/images/transparent.png') + self.assert_image_equal(im, target) - # Act / Assert - self.assertRaises(ValueError, lambda: Image.open(test_file)) + def test_rle(self): + # convert hopper.ppm hopper.sgi + # We don't support RLE compression, this should throw a value error + test_file = "Tests/images/hopper.sgi" + + with self.assertRaises(ValueError): + Image.open(test_file) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" From 39c01eb039994e3b0cdf2da38b3de164a54324ea Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 Dec 2016 23:47:47 +0000 Subject: [PATCH 189/267] Update Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index bfb669e18..af46309f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Test: Added correctness tests for opening SGI images #2324 + [wiredfool] + - Allow passing a list or tuple of individual frame durations when saving a GIF #2298 [Xdynix] From 41c97af15c179859472b1cf138b4203bd2393e24 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 Dec 2016 23:54:20 +0000 Subject: [PATCH 190/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index af46309f4..b8c0fa313 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Depends: Updated pngquant to 2.8.2 #2319 + [radarhere] + - Test: Added correctness tests for opening SGI images #2324 [wiredfool] From 597ab45d4d9f0f6af5a164b902317e2692401553 Mon Sep 17 00:00:00 2001 From: Mickael B Date: Sat, 17 Sep 2016 04:03:40 -0400 Subject: [PATCH 191/267] [SGI] Save uncompressed SGI/BW/RGB/RGBA files Save feature added to SgiImagePlugin.py, uncompressed method only --- PIL/SgiImagePlugin.py | 80 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py index d2efd3e25..17b8efd17 100644 --- a/PIL/SgiImagePlugin.py +++ b/PIL/SgiImagePlugin.py @@ -7,9 +7,12 @@ # See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli. # # +# # History: +# 2016-16-10 mb Add save method without compression # 1995-09-10 fl Created # +# Copyright (c) 2016 by Mickael Bonfill. # Copyright (c) 2008 by Karsten Hiddemann. # Copyright (c) 1997 by Secret Labs AB. # Copyright (c) 1995 by Fredrik Lundh. @@ -19,8 +22,10 @@ from PIL import Image, ImageFile, _binary +import struct +import os -__version__ = "0.2" +__version__ = "0.3" i8 = _binary.i8 i16 = _binary.i16be @@ -76,12 +81,83 @@ class SgiImageFile(ImageFile.ImageFile): elif compression == 1: raise ValueError("SGI RLE encoding not supported") + +def _save(im, fp, filename): + if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L": + raise ValueError("Unsupported SGI image mode") + + # Flip the image, since the origin of SGI file is the bottom-left corner + im = im.transpose(Image.FLIP_TOP_BOTTOM) + # Define the file as SGI File Format + magicNumber = 474 + # Run-Length Encoding Compression - Unsupported at this time + rle = 0 + # Byte-per-pixel precision, 1 = 8bits per pixel + bpc = 1 + # Number of dimensions (x,y,z) + dim = 3 + # X Dimension = width / Y Dimension = height + x, y = im.size + if im.mode == "L" and y == 1: + dim = 1 + elif im.mode == "L": + dim = 2 + # Z Dimension: Number of channels + z = len(im.mode) + if dim == 1 or dim == 2: + z = 1 + # Minimum Byte value + pinmin = 0 + # Maximum Byte value (255 = 8bits per pixel) + pinmax = 255 + # Image name (79 characters max) + imgName = os.path.splitext(os.path.basename(filename))[0][0:78] + # Standard representation of pixel in the file + colormap = 0 + channels = [] + for channelIndex in range(0, z): + channelData = list(im.getdata(channelIndex)) + channels.append(channelData) + fp.write(struct.pack('>h', magicNumber)) + fp.write(struct.pack('c', chr(rle))) + fp.write(struct.pack('c', chr(bpc))) + fp.write(struct.pack('>H', dim)) + fp.write(struct.pack('>H', x)) + fp.write(struct.pack('>H', y)) + fp.write(struct.pack('>H', z)) + fp.write(struct.pack('>l', pinmin)) + fp.write(struct.pack('>l', pinmax)) + for i in range(0, 4): + fp.write(struct.pack('c', chr(0))) + for c in imgName: + fp.write(struct.pack('c', c)) + fp.write(struct.pack('c', chr(0))) + if len(imgName) < 78: + charIndex = len(imgName) + for charIndex in range(len(imgName), 79): + fp.write(struct.pack('c', chr(0))) + fp.write(struct.pack('>l', colormap)) + for i in range(0, 404): + fp.write(struct.pack('c', chr(0))) + for zChannel in range(0, z): + dIndex = 0 + for yPos in range(0, y): + for xPos in range(0, x): + fp.write(struct.pack('c', chr(channels[zChannel][dIndex]))) + dIndex += 1 + fp.close() + + # # registry Image.register_open(SgiImageFile.format, SgiImageFile, _accept) - +Image.register_save(SgiImageFile.format, _save) +Image.register_mime(SgiImageFile.format, "image/sgi") +Image.register_mime(SgiImageFile.format, "image/rgb") Image.register_extension(SgiImageFile.format, ".bw") Image.register_extension(SgiImageFile.format, ".rgb") Image.register_extension(SgiImageFile.format, ".rgba") Image.register_extension(SgiImageFile.format, ".sgi") + +# End of file From 3d185ee8579d6cab7660d19cbcad66609c8c19c9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 Dec 2016 22:31:35 +0000 Subject: [PATCH 192/267] added tests for sgi writing --- Tests/test_file_sgi.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index af40ac848..65910b5eb 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -45,6 +45,17 @@ class TestFileSgi(PillowTestCase): lambda: SgiImagePlugin.SgiImageFile(invalid_file)) + def test_write(self): + def roundtrip(img): + out = self.tempfile('temp.sgi') + img.save(out, format='sgi') + reloaded = Image.open(out) + self.assert_image_equal(img, reloaded) + + for mode in ('L', 'RGB', 'RGBA'): + roundtrip(hopper(mode)) + + if __name__ == '__main__': unittest.main() From 31c204eae4e158bbaa6cda4afe7a2f8e2fcabe6e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 Dec 2016 23:10:47 +0000 Subject: [PATCH 193/267] Loop cleanup, python 3 compatibility --- PIL/SgiImagePlugin.py | 47 ++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py index 17b8efd17..d0e293368 100644 --- a/PIL/SgiImagePlugin.py +++ b/PIL/SgiImagePlugin.py @@ -28,6 +28,7 @@ import os __version__ = "0.3" i8 = _binary.i8 +o8 = _binary.o8 i16 = _binary.i16be @@ -110,41 +111,37 @@ def _save(im, fp, filename): pinmin = 0 # Maximum Byte value (255 = 8bits per pixel) pinmax = 255 - # Image name (79 characters max) - imgName = os.path.splitext(os.path.basename(filename))[0][0:78] + # Image name (79 characters max, truncated below in write) + imgName = os.path.splitext(os.path.basename(filename))[0] + if str is not bytes: + imgName = imgName.encode('ascii', 'ignore') # Standard representation of pixel in the file colormap = 0 - channels = [] - for channelIndex in range(0, z): - channelData = list(im.getdata(channelIndex)) - channels.append(channelData) fp.write(struct.pack('>h', magicNumber)) - fp.write(struct.pack('c', chr(rle))) - fp.write(struct.pack('c', chr(bpc))) + fp.write(o8(rle)) + fp.write(o8(bpc)) fp.write(struct.pack('>H', dim)) fp.write(struct.pack('>H', x)) fp.write(struct.pack('>H', y)) fp.write(struct.pack('>H', z)) fp.write(struct.pack('>l', pinmin)) fp.write(struct.pack('>l', pinmax)) - for i in range(0, 4): - fp.write(struct.pack('c', chr(0))) - for c in imgName: - fp.write(struct.pack('c', c)) - fp.write(struct.pack('c', chr(0))) - if len(imgName) < 78: - charIndex = len(imgName) - for charIndex in range(len(imgName), 79): - fp.write(struct.pack('c', chr(0))) + + fp.write(struct.pack('4s', b'')) # dummy + fp.write(struct.pack('79s', imgName)) # truncates to 79 chars + fp.write(struct.pack('s', b'')) # force null byte after imgname fp.write(struct.pack('>l', colormap)) - for i in range(0, 404): - fp.write(struct.pack('c', chr(0))) - for zChannel in range(0, z): - dIndex = 0 - for yPos in range(0, y): - for xPos in range(0, x): - fp.write(struct.pack('c', chr(channels[zChannel][dIndex]))) - dIndex += 1 + + fp.write(struct.pack('404s', b'')) # dummy + + #assert we've got the right number of bands. + if len(im.getbands()) != z: + raise ValueError("incorrect number of bands in SGI write: %s vs %s" % + (z, len(im.getbands()))) + + for channel in im.split(): + fp.write(channel.tobytes()) + fp.close() From 6fd020a465cb7ff5acafb486d36f7b9c1c328758 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 12:33:33 +0000 Subject: [PATCH 194/267] Added save docs for SGI --- docs/handbook/image-file-formats.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index cfb19a97a..21339e75f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -471,6 +471,12 @@ PPM PIL reads and writes PBM, PGM and PPM files containing ``1``, ``L`` or ``RGB`` data. +SGI +^^^ + +Pillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files. + + SPIDER ^^^^^^ @@ -807,10 +813,6 @@ PSD PIL identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0. -SGI -^^^ - -PIL reads uncompressed ``L``, ``RGB``, and ``RGBA`` files. TGA ^^^ From 789ac7aa720cdc81769eddf51cb9729cbb8ad0c1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 12:35:37 +0000 Subject: [PATCH 195/267] Update Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b8c0fa313..d1d74f729 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325 + [jbltx] + - Depends: Updated pngquant to 2.8.2 #2319 [radarhere] From 69bea50810ebc0b4234de4bf461ce5c76f2af72b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Nov 2016 19:25:49 +0000 Subject: [PATCH 196/267] Allow 0 size images, Fixes #2259 --- PIL/Image.py | 4 ++-- Tests/test_image.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index c086dfcd7..03f3973ee 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1994,8 +1994,8 @@ def _check_size(size): raise ValueError("Size must be a tuple") if len(size) != 2: raise ValueError("Size must be a tuple of length 2") - if size[0] <= 0 or size[1] <= 0: - raise ValueError("Width and Height must be > 0") + if size[0] < 0 or size[1] < 0: + raise ValueError("Width and Height must be => 0") return True diff --git a/Tests/test_image.py b/Tests/test_image.py index ef9aa16af..f1457a85b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -256,7 +256,11 @@ class TestImage(PillowTestCase): with self.assertRaises(ValueError): Image.new('RGB', (0,)) # Tuple too short with self.assertRaises(ValueError): - Image.new('RGB', (0,0)) # w,h <= 0 + Image.new('RGB', (-1,-1)) # w,h < 0 + + # this should pass with 0 sized images, #2259 + im = Image.new('L', (0, 0)) + self.assertEqual(im.size, (0, 0)) self.assertTrue(Image.new('RGB', (1,1))) # Should pass lists too From 0a922b962fdc92115a0a9f7deb909acf48afa063 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 27 Dec 2016 04:53:23 -0800 Subject: [PATCH 197/267] tests for basic operations on 0x0 images --- Tests/test_image_access.py | 13 +++++++++++++ Tests/test_image_convert.py | 5 +++++ Tests/test_image_copy.py | 9 +++++++++ Tests/test_image_crop.py | 17 +++++++++++++++++ Tests/test_image_resize.py | 8 ++++++++ 5 files changed, 52 insertions(+) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 0f8d2a654..900f39eb4 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -78,12 +78,25 @@ class TestImageGetPixel(AccessTest): im.getpixel((0, 0)), c, "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c)) + # Check 0 + im = Image.new(mode, (0, 0), None) + with self.assertRaises(IndexError): + im.putpixel((0, 0), c) + with self.assertRaises(IndexError): + im.getpixel((0, 0)) + # check initial color im = Image.new(mode, (1, 1), c) self.assertEqual( im.getpixel((0, 0)), c, "initial color failed for mode %s, color %s " % (mode, c)) + # Check 0 + im = Image.new(mode, (0, 0), c) + with self.assertRaises(IndexError): + im.getpixel((0, 0)) + + def test_basic(self): for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", "P", "PA", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr"): diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 0c98211e7..54ffde10b 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -19,6 +19,11 @@ class TestImageConvert(PillowTestCase): for mode in modes: convert(im, mode) + # Check 0 + im = Image.new(mode, (0,0)) + for mode in modes: + convert(im, mode) + def test_default(self): im = hopper("P") diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index ba53758d5..c50205c9c 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -1,5 +1,7 @@ from helper import unittest, PillowTestCase, hopper +from PIL import Image + import copy @@ -33,5 +35,12 @@ class TestImageCopy(PillowTestCase): self.assertEqual(out.mode, im.mode) self.assertEqual(out.size, croppedSize) + def test_copy_zero(self): + im = Image.new('RGB', (0,0)) + out = im.copy() + self.assertEqual(out.mode, im.mode) + self.assertEqual(out.size, im.size) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index c12e29be4..c887ab0c1 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -83,6 +83,23 @@ class TestImageCrop(PillowTestCase): img = img.crop(extents) img.load() + def test_crop_zero(self): + + im = Image.new('RGB', (0, 0), 'white') + + cropped = im.crop((0, 0, 0, 0)) + self.assertEqual(cropped.size, (0, 0)) + + cropped = im.crop((10, 10, 20, 20)) + self.assertEqual(cropped.size, (10, 10)) + self.assertEqual(cropped.getdata()[0], (0, 0, 0)) + + im = Image.new('RGB', (0, 0)) + + cropped = im.crop((10, 10, 20, 20)) + self.assertEqual(cropped.size, (10, 10)) + self.assertEqual(cropped.getdata()[2], (0, 0, 0)) + if __name__ == '__main__': diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 7db409659..38a60564c 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -89,6 +89,14 @@ class TestImagingCoreResize(PillowTestCase): # as separately resized channel self.assert_image_equal(ch, references[channels[i]]) + def test_enlarge_zero(self): + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, + Image.BICUBIC, Image.LANCZOS]: + r = self.resize(Image.new('RGB', (0,0), "white"), (212, 195), f) + self.assertEqual(r.mode, "RGB") + self.assertEqual(r.size, (212, 195)) + self.assertEqual(r.getdata()[0], (0,0,0)) + class TestImageResize(PillowTestCase): From 7d59183c1d61bbbad8fa5739711eac43c6725a63 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 13:08:00 +0000 Subject: [PATCH 198/267] Zero image size test --- Tests/test_image_rotate.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index e90b9a592..70523a698 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -15,13 +15,19 @@ class TestImageRotate(PillowTestCase): self.assertEqual(out.size, im.size) else: self.assertNotEqual(out.size, im.size) - for mode in "1", "P", "L", "RGB", "I", "F": + + for mode in ("1", "P", "L", "RGB", "I", "F"): im = hopper(mode) rotate(im, mode, 45) - for angle in 0, 90, 180, 270: + + for angle in (0, 90, 180, 270): im = Image.open('Tests/images/test-card.png') rotate(im, im.mode, angle) + for angle in (0, 45, 90, 180, 270): + im = Image.new('RGB',(0,0)) + rotate(im, im.mode, angle) + if __name__ == '__main__': unittest.main() From 20abc9cdfee882a0e90eea05929a3af78d767e8d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 13:31:51 +0000 Subject: [PATCH 199/267] Fix size check on expan for image_rotate --- Tests/test_image_rotate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 70523a698..dfe5a0731 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -13,8 +13,11 @@ class TestImageRotate(PillowTestCase): self.assertEqual(out.mode, mode) if angle % 180 == 0: self.assertEqual(out.size, im.size) + elif im.size == (0, 0): + self.assertEqual(out.size, im.size) else: self.assertNotEqual(out.size, im.size) + for mode in ("1", "P", "L", "RGB", "I", "F"): im = hopper(mode) From dda63c1ada46a0f7e93b45e99f78a6557cde4aad Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 16:35:42 +0000 Subject: [PATCH 200/267] Update Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d1d74f729..90c37d78d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. + [wiredfool] + - SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325 [jbltx] From 5230fee237269a457ce0afc63fe455b929515283 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 17:29:28 +0000 Subject: [PATCH 201/267] Release notes for 4.0.0 --- docs/releasenotes/4.0.0.rst | 46 +++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 47 insertions(+) create mode 100644 docs/releasenotes/4.0.0.rst diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst new file mode 100644 index 000000000..609f8127e --- /dev/null +++ b/docs/releasenotes/4.0.0.rst @@ -0,0 +1,46 @@ +4.0.0 +----- + +Python 2.6 and 3.2 Dropped +========================== + +Pillow 4.0 no longer supports Python 2.6 and 3.2. We will not be +creating binaries, testing, or retaining compatibility with these +releases. This release removes some workarounds for those Python +releases, so the final working version of Pillow on 2.6 or 3.2 is 3.4.2. + +OleFileIO.py +============ + +OleFileIO.py has been removed as a vendored file and is now installed +from the upstream olefile pypi package. All internal dependencies are +redirected to the olefile package. Direct accesses to +``PIL.OlefileIO`` raises a deprecation warning, then patches the +upstream olefile into ``sys.modules`` in its place. + +SGI image save +============== + +It is now possible to save images in modes ``L``, ``RGB``, and +``RGBA`` to the uncompressed SGI image format. + +Zero sized images +================= + +Pillow 3.4.0 removed support for creating images with (0,0) size. This +has been reenabled, restoring pre 3.4 behavior. + +Internal handles_eof flag +========================= + +The ``handles_eof flag`` for decoding images has been removed, as there +were no internal users of the flag. Anyone maintaining image decoders +outside of the Pillow source tree should consider using the cleanup +function pointers instead. + +Image.core.stretch removed +========================== + +The stretch function on the core image object has been removed. This +used to be for enlarging the image, but has been aliased to resize +recently. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 8c484af44..e32bf7e40 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 4.0.0 3.4.0 3.3.2 3.3.0 From eea7c63eb3fe8a1dbc4e8b9760e138ac620e45be Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 17:31:39 +0000 Subject: [PATCH 202/267] Py 3.6 --- docs/releasenotes/4.0.0.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst index 609f8127e..4d21a2e54 100644 --- a/docs/releasenotes/4.0.0.rst +++ b/docs/releasenotes/4.0.0.rst @@ -9,6 +9,11 @@ creating binaries, testing, or retaining compatibility with these releases. This release removes some workarounds for those Python releases, so the final working version of Pillow on 2.6 or 3.2 is 3.4.2. +Support added for Python 3.6 +============================ + +Pillow 4.0 supports Python 3.6. + OleFileIO.py ============ From 5269828d3ab49aaf55c4a5205d127e61eb536cbe Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 17:38:35 +0000 Subject: [PATCH 203/267] Test: Relax WMF test condition, fixes #2323 --- Tests/test_file_wmf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index f08ee249f..9f2f893cb 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -24,7 +24,7 @@ class TestFileWmf(PillowTestCase): # Compare to reference rendering imref = Image.open('Tests/images/drawing_wmf_ref.png') imref.load() - self.assert_image_similar(im, imref, 0.5) + self.assert_image_similar(im, imref, 2.0) if __name__ == '__main__': From f3751a1f3afb956066bfa69accd28e4f58bfbaac Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 18:42:53 +0000 Subject: [PATCH 204/267] Update Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 90c37d78d..5b082c27b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Test: Relax WMF test condition, fixes #2323 + [wiredfool] + - Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. [wiredfool] From 90077b397504c19d1aaf97a9081400130ad8ce46 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Wed, 5 Oct 2016 22:26:26 +0200 Subject: [PATCH 205/267] Add center and translate option to Image.rotate. --- PIL/Image.py | 57 +++++++++++++++++++++++++++++--------- Tests/test_image_rotate.py | 10 +++++-- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 03f3973ee..7e02c49bb 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1555,7 +1555,7 @@ class Image(object): return self._new(self.im.resize(size, resample)) - def rotate(self, angle, resample=NEAREST, expand=0): + def rotate(self, angle, resample=NEAREST, expand=0, center=None, translate=None): """ Returns a rotated copy of this image. This method returns a copy of this image, rotated the given number of degrees counter @@ -1569,10 +1569,14 @@ class Image(object): (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`. + :param center: Optional center of rotation (a 2-tuple). Origin is + the upper left corner. Default is the center of the image. + :param translate: An optional final translation. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the - input image. + input image. Note that the expand flag assumes rotation around + the center and no translation. :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -1588,32 +1592,59 @@ class Image(object): if angle == 270 and expand: return self.transpose(ROTATE_270) + # Calculate the affine matrix. Note that this is the reverse + # transformation (from destination image to source) because we + # want to interpolate the (discrete) destination pixel from + # the local area around the (floating) source pixel. + + # The matrix we actually want (note that it operates from the right): + # (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx) + # (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy) + # (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1) + + # The reverse matrix is thus: + # (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx) + # (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty) + # (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1) + + # In any case, the final translation may be updated at the end to + # compensate for the expand flag. + + w, h = self.size + + if translate is None: + translate = [0, 0] + if center is None: + center = [w / 2.0, h / 2.0] + angle = - math.radians(angle) matrix = [ round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0 - ] - - def transform(x, y, matrix=matrix): + ] + def transform(x, y, matrix): (a, b, c, d, e, f) = matrix return a*x + b*y + c, d*x + e*y + f + matrix[2], matrix[5] = transform(-center[0] - translate[0], -center[1] - translate[1], matrix) + matrix[2] += center[0] + matrix[5] += center[1] - w, h = self.size if expand: # calculate output size xx = [] yy = [] for x, y in ((0, 0), (w, 0), (w, h), (0, h)): - x, y = transform(x, y) + x, y = transform(x, y, matrix) xx.append(x) yy.append(y) - w = int(math.ceil(max(xx)) - math.floor(min(xx))) - h = int(math.ceil(max(yy)) - math.floor(min(yy))) + nw = int(math.ceil(max(xx)) - math.floor(min(xx))) + nh = int(math.ceil(max(yy)) - math.floor(min(yy))) - # adjust center - x, y = transform(w / 2.0, h / 2.0) - matrix[2] = self.size[0] / 2.0 - x - matrix[5] = self.size[1] / 2.0 - y + # We multiply a translation matrix from the right. Because of its + # special form, this is the same as taking the image of the translation vector + # as new translation vector. + matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix) + w, h = nw, nh return self.transform((w, h), AFFINE, matrix, resample) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index dfe5a0731..f2a3c4fa8 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -5,11 +5,11 @@ from PIL import Image class TestImageRotate(PillowTestCase): def test_rotate(self): - def rotate(im, mode, angle): - out = im.rotate(angle) + def rotate(im, mode, angle, center=None, translate=None): + out = im.rotate(angle, center=center, translate=translate) self.assertEqual(out.mode, mode) self.assertEqual(out.size, im.size) # default rotate clips output - out = im.rotate(angle, expand=1) + out = im.rotate(angle, center=center, translate=translate, expand=1) self.assertEqual(out.mode, mode) if angle % 180 == 0: self.assertEqual(out.size, im.size) @@ -31,6 +31,10 @@ class TestImageRotate(PillowTestCase): im = Image.new('RGB',(0,0)) rotate(im, im.mode, angle) + rotate(im, im.mode, 45, center=(0, 0)) + rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) + rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) + if __name__ == '__main__': unittest.main() From cc1ba56c2819fd542d6d750554505cfacde00025 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 19:12:39 +0000 Subject: [PATCH 206/267] Refactor tests --- Tests/test_image_rotate.py | 45 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index f2a3c4fa8..865544350 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -4,36 +4,41 @@ from PIL import Image class TestImageRotate(PillowTestCase): - def test_rotate(self): - def rotate(im, mode, angle, center=None, translate=None): - out = im.rotate(angle, center=center, translate=translate) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) # default rotate clips output - out = im.rotate(angle, center=center, translate=translate, expand=1) - self.assertEqual(out.mode, mode) - if angle % 180 == 0: - self.assertEqual(out.size, im.size) - elif im.size == (0, 0): - self.assertEqual(out.size, im.size) - else: - self.assertNotEqual(out.size, im.size) + def rotate(self, im, mode, angle, center=None, translate=None): + out = im.rotate(angle, center=center, translate=translate) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, im.size) # default rotate clips output + out = im.rotate(angle, center=center, translate=translate, expand=1) + self.assertEqual(out.mode, mode) + if angle % 180 == 0: + self.assertEqual(out.size, im.size) + elif im.size == (0, 0): + self.assertEqual(out.size, im.size) + else: + self.assertNotEqual(out.size, im.size) - + def test_mode(self): for mode in ("1", "P", "L", "RGB", "I", "F"): im = hopper(mode) - rotate(im, mode, 45) + self.rotate(im, mode, 45) + def test_angle(self): for angle in (0, 90, 180, 270): im = Image.open('Tests/images/test-card.png') - rotate(im, im.mode, angle) + self.rotate(im, im.mode, angle) + def test_zero(self): for angle in (0, 45, 90, 180, 270): im = Image.new('RGB',(0,0)) - rotate(im, im.mode, angle) + self.rotate(im, im.mode, angle) - rotate(im, im.mode, 45, center=(0, 0)) - rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) - rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) + def test_center(self): + im = hopper() + self.rotate(im, im.mode, 45, center=(0, 0)) + self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) + self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) + + if __name__ == '__main__': From 5fda1a803aa0337dd42760c7dd45a37de7c768c9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 19:30:26 +0000 Subject: [PATCH 207/267] Added resample target test --- Tests/test_image_rotate.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 865544350..5bf2e15ee 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -32,6 +32,19 @@ class TestImageRotate(PillowTestCase): im = Image.new('RGB',(0,0)) self.rotate(im, im.mode, angle) + def test_resample(self): + # >>> im = Image.open('Tests/images/hopper.ppm') + # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) + # >>> im.save('Tests/images/hopper_45.png') + + target = Image.open('Tests/images/hopper_45.png') + for (resample, epsilon) in ((Image.NEAREST, 10), + (Image.BILINEAR, 5), + (Image.BICUBIC, 0)): + im = hopper() + im = im.rotate(45, resample=resample, expand=True) + self.assert_image_similar(im, target, epsilon) + def test_center(self): im = hopper() self.rotate(im, im.mode, 45, center=(0, 0)) From 7228de14911c6a3dc13c568f81b9a642cb1e5c05 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 31 Dec 2016 19:31:02 +0000 Subject: [PATCH 208/267] test image --- Tests/images/hopper_45.png | Bin 0 -> 36094 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper_45.png diff --git a/Tests/images/hopper_45.png b/Tests/images/hopper_45.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e61428321e0dd4c89b2035e94783c7e1414c52 GIT binary patch literal 36094 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 <7jGYaGJR0BH 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 a;r{`iKf@(5hpVXo0000 Date: Sun, 1 Jan 2017 11:09:06 +0000 Subject: [PATCH 209/267] Added output checked tests for rotate with center and translate --- Tests/test_image_rotate.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 5bf2e15ee..9ee01db44 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -33,6 +33,7 @@ class TestImageRotate(PillowTestCase): self.rotate(im, im.mode, angle) def test_resample(self): + # Target image creation, inspected by eye. # >>> im = Image.open('Tests/images/hopper.ppm') # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) # >>> im.save('Tests/images/hopper_45.png') @@ -45,6 +46,51 @@ class TestImageRotate(PillowTestCase): im = im.rotate(45, resample=resample, expand=True) self.assert_image_similar(im, target, epsilon) + def test_center_0(self): + im = hopper() + target = Image.open('Tests/images/hopper_45.png') + target_origin = target.size[1]/2 + target = target.crop((0, target_origin, 128, target_origin + 128)) + + im = im.rotate(45, center=(0,0), resample=Image.BICUBIC) + + self.assert_image_similar(im, target, 15) + + def test_center_14(self): + im = hopper() + target = Image.open('Tests/images/hopper_45.png') + target_origin = target.size[1] / 2 - 14 + target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) + + im = im.rotate(45, center=(14,14), resample=Image.BICUBIC) + + self.assert_image_similar(im, target, 10) + + def test_translate(self): + im = hopper() + target = Image.open('Tests/images/hopper_45.png') + target_origin = (target.size[1] / 2 - 64) - 5 + target = target.crop((target_origin, target_origin, + target_origin + 128, target_origin + 128)) + + im = im.rotate(45, translate=(5,5), resample=Image.BICUBIC) + + self.assert_image_similar(im, target, 1) + + def test_fastpath_center(self): + # if the center is -1,-1 and we rotate by 90<=x<=270 the + # resulting image should be black + for angle in (90, 180, 270): + im = hopper().rotate(angle, center=(-1,-1)) + self.assert_image_equal(im, Image.new('RGB', im.size, 'black')) + + def test_fastpath_translate(self): + # if we post-translate by -128 + # resulting image should be black + for angle in (0, 90, 180, 270): + im = hopper().rotate(angle, translate=(-128,-128)) + self.assert_image_equal(im, Image.new('RGB', im.size, 'black')) + def test_center(self): im = hopper() self.rotate(im, im.mode, 45, center=(0, 0)) From f286d8bb3404109cd711b71a0a3fdcfdb0fcdb59 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 11:10:39 +0000 Subject: [PATCH 210/267] Documentation param order to match actual order --- PIL/Image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 7e02c49bb..b86fae814 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1569,14 +1569,14 @@ class Image(object): (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`. - :param center: Optional center of rotation (a 2-tuple). Origin is - the upper left corner. Default is the center of the image. - :param translate: An optional final translation. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the input image. Note that the expand flag assumes rotation around the center and no translation. + :param center: Optional center of rotation (a 2-tuple). Origin is + the upper left corner. Default is the center of the image. + :param translate: An optional post-rotate translation (a 2-tuple). :returns: An :py:class:`~PIL.Image.Image` object. """ From fed4b52171c865ec45916699f5f13ac3a2c3c2cb Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 11:11:10 +0000 Subject: [PATCH 211/267] Disable fastpath when using center or translate --- PIL/Image.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index b86fae814..f49834e5b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1582,15 +1582,17 @@ class Image(object): angle = angle % 360.0 - # Fast paths regardless of filter - if angle == 0: - return self.copy() - if angle == 180: - return self.transpose(ROTATE_180) - if angle == 90 and expand: - return self.transpose(ROTATE_90) - if angle == 270 and expand: - return self.transpose(ROTATE_270) + # Fast paths regardless of filter, as long as we're not + # translating or changing the center. + if not (center or translate): + if angle == 0: + return self.copy() + if angle == 180: + return self.transpose(ROTATE_180) + if angle == 90 and expand: + return self.transpose(ROTATE_90) + if angle == 270 and expand: + return self.transpose(ROTATE_270) # Calculate the affine matrix. Note that this is the reverse # transformation (from destination image to source) because we From f78f1adf4f0888950f8cd2a8aefe8a3e05f72b15 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 11:28:00 +0000 Subject: [PATCH 212/267] Date Range --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 22a4445f9..5bb70fea4 100644 --- a/LICENSE +++ b/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2017 by Alex Clark and contributors + Copyright © 2010-2017 by Alex Clark and contributors Like PIL, Pillow is licensed under the MIT-like open source PIL Software License: From 962752f3fdc5ea1b2ef8f5d6f54a9db8ff6d4874 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 11:28:19 +0000 Subject: [PATCH 213/267] Date Range --- docs/COPYING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/COPYING b/docs/COPYING index ee8a7f807..d958dff46 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2017 by Alex Clark and contributors + Copyright © 2010-2017 by Alex Clark and contributors Like PIL, Pillow is licensed under the MIT-like open source PIL Software License: From 8dd19e6c3391557fddf00efed83dba87c144b229 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 12:04:59 +0000 Subject: [PATCH 214/267] refactor out postprocessing hack to load_end in PcdImageFile --- PIL/ImageFile.py | 6 ------ PIL/PcdImagePlugin.py | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index 52c2ed34c..a56795751 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -243,12 +243,6 @@ class ImageFile(Image.Image): # still raised if decoder fails to return anything raise_ioerror(err_code) - # post processing - if hasattr(self, "tile_post_rotate"): - # FIXME: This is a hack to handle rotated PCD's - self.im = self.im.rotate(self.tile_post_rotate) - self.size = self.im.size - self.load_end() return Image.Image.load(self) diff --git a/PIL/PcdImagePlugin.py b/PIL/PcdImagePlugin.py index b53635a99..0cf348e74 100644 --- a/PIL/PcdImagePlugin.py +++ b/PIL/PcdImagePlugin.py @@ -42,6 +42,7 @@ class PcdImageFile(ImageFile.ImageFile): raise SyntaxError("not a PCD file") orientation = i8(s[1538]) & 3 + self.tile_post_rotate = None if orientation == 1: self.tile_post_rotate = 90 # hack elif orientation == 3: @@ -51,6 +52,13 @@ class PcdImageFile(ImageFile.ImageFile): self.size = 768, 512 # FIXME: not correct for rotated images! self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)] + def load_end(self): + if self.tile_post_rotate: + # Handle rotated PCDs + self.im = self.im.rotate(self.tile_post_rotate) + self.size = self.im.size + + # # registry From 599ec6bcd6d8dbfbca0606bc0c9f0b2e8ce9526e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 12:59:48 +0000 Subject: [PATCH 215/267] Removed #hack --- PIL/PcdImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/PcdImagePlugin.py b/PIL/PcdImagePlugin.py index 0cf348e74..24186bcfc 100644 --- a/PIL/PcdImagePlugin.py +++ b/PIL/PcdImagePlugin.py @@ -44,7 +44,7 @@ class PcdImageFile(ImageFile.ImageFile): orientation = i8(s[1538]) & 3 self.tile_post_rotate = None if orientation == 1: - self.tile_post_rotate = 90 # hack + self.tile_post_rotate = 90 elif orientation == 3: self.tile_post_rotate = -90 From 1e22c6f1312822eafaeace70e1eac49ccc1f8c44 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 19:07:50 +0000 Subject: [PATCH 216/267] Update Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5b082c27b..bfe27fa78 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 3.5.0 (unreleased) ------------------ +- Refactor out postprocessing hack to load_end in PcdImageFile + [wiredfool] + +- Add center and translate option to Image.rotate. #2328 + [lambdafu] + - Test: Relax WMF test condition, fixes #2323 [wiredfool] From a021d4978dd96371557f21c7b68feff2056a6e9b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jan 2017 20:20:06 +0000 Subject: [PATCH 217/267] 4.0.0 version bump --- CHANGES.rst | 2 +- PIL/__init__.py | 2 +- _imaging.c | 2 +- appveyor.yml | 2 +- setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index bfe27fa78..094e39f87 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog (Pillow) ================== -3.5.0 (unreleased) +4.0.0 (2017-01-01) ------------------ - Refactor out postprocessing hack to load_end in PcdImageFile diff --git a/PIL/__init__.py b/PIL/__init__.py index be672e825..36c193cfc 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '3.5.0.dev0' # Pillow +PILLOW_VERSION = '4.0.0' # Pillow __version__ = PILLOW_VERSION diff --git a/_imaging.c b/_imaging.c index 83e159c56..c852f6d43 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "3.5.0.dev0" +#define PILLOW_VERSION "4.0.0" #include "Python.h" diff --git a/appveyor.yml b/appveyor.yml index 9ff661224..e03afa4c0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.5.pre.{build} +version: 4.0.0.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% diff --git a/setup.py b/setup.py index c35d4357c..65760926a 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '3.5.0.dev0' +PILLOW_VERSION = '4.4.0' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From 49fc3f175f44ad11440511a18075bb12b4f1f3b2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Jan 2017 14:32:08 +1100 Subject: [PATCH 218/267] Updated freetype to 2.7.1 --- winbuild/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 3cbba9d97..e6b8ac76b 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -37,10 +37,10 @@ libs = { 'dir': 'tiff-4.0.6', }, 'freetype': { - 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.7.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.7.tar.gz', - 'hash': 'md5:337139e5c7c5bd645fe130608e0fa8b5', - 'dir': 'freetype-2.7', + 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.7.1.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.7.1.tar.gz', + 'hash': 'md5:78701bee8d249578d83bb9a2f3aa3616', + 'dir': 'freetype-2.7.1', }, 'lcms': { 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', From 3f6db91ccc917e803227d245e38871169f08350f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 2 Jan 2017 03:45:39 -0800 Subject: [PATCH 219/267] Fixed typo in version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 65760926a..825d059b8 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '4.4.0' +PILLOW_VERSION = '4.0.0' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From 296fb5fe1da4e44a473862b8ba46669a98e5ec15 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 2 Jan 2017 04:06:48 -0800 Subject: [PATCH 220/267] 4.1.0.dev0 version bump --- PIL/__init__.py | 2 +- _imaging.c | 2 +- appveyor.yml | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 36c193cfc..7a32533d9 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '4.0.0' # Pillow +PILLOW_VERSION = '4.1.0.dev0' # Pillow __version__ = PILLOW_VERSION diff --git a/_imaging.c b/_imaging.c index c852f6d43..aa2e04778 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "4.0.0" +#define PILLOW_VERSION "4.1.0.dev0" #include "Python.h" diff --git a/appveyor.yml b/appveyor.yml index e03afa4c0..deb6d3b1a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 4.0.0.{build} +version: 4.1.pre.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% diff --git a/setup.py b/setup.py index 825d059b8..b0209a399 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '4.0.0' +PILLOW_VERSION = '4.1.0.dev0' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From c5265e2100f283b3ee884dce4157ed67aaed022c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 3 Jan 2017 13:30:09 +1100 Subject: [PATCH 221/267] Added test for crop operation with no argument --- Tests/test_image_crop.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index c887ab0c1..be50b46b7 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -7,9 +7,12 @@ class TestImageCrop(PillowTestCase): def test_crop(self): def crop(mode): - out = hopper(mode).crop((50, 50, 100, 100)) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, (50, 50)) + im = hopper(mode) + self.assert_image_equal(im.crop(), im) + + cropped = im.crop((50, 50, 100, 100)) + self.assertEqual(cropped.mode, mode) + self.assertEqual(cropped.size, (50, 50)) for mode in "1", "P", "L", "RGB", "I", "F": crop(mode) @@ -70,23 +73,23 @@ class TestImageCrop(PillowTestCase): #Image.crop crashes prepatch with an access violation #apparently a use after free on windows, see #https://github.com/python-pillow/Pillow/issues/1077 - + test_img = 'Tests/images/bmp/g/pal8-0.bmp' extents = (1,1,10,10) #works prepatch img = Image.open(test_img) img2 = img.crop(extents) img2.load() - + # fail prepatch img = Image.open(test_img) img = img.crop(extents) img.load() def test_crop_zero(self): - + im = Image.new('RGB', (0, 0), 'white') - + cropped = im.crop((0, 0, 0, 0)) self.assertEqual(cropped.size, (0, 0)) @@ -95,7 +98,7 @@ class TestImageCrop(PillowTestCase): self.assertEqual(cropped.getdata()[0], (0, 0, 0)) im = Image.new('RGB', (0, 0)) - + cropped = im.crop((10, 10, 20, 20)) self.assertEqual(cropped.size, (10, 10)) self.assertEqual(cropped.getdata()[2], (0, 0, 0)) From 1a1818f1eb19656b917d47d87c847aabdcb0a379 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 3 Jan 2017 09:41:29 +0200 Subject: [PATCH 222/267] [CI skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 094e39f87..b91e961e7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog (Pillow) ================== +4.1.0 (unreleased) +------------------ + +- Add test for crop operation with no argument #2333 + [radarhere] + 4.0.0 (2017-01-01) ------------------ From 2eba610b298bd7673d34ae072f58e06d3453790a Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 3 Jan 2017 15:13:45 +0200 Subject: [PATCH 223/267] Pillow 3.5.0 became 4.0.0 And some caps consistency [CI skip] --- docs/installation.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5eb12c69c..320c7e4ad 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,9 +15,9 @@ Notes .. note:: Pillow < 2.0.0 supports Python versions 2.4, 2.5, 2.6, 2.7. -.. note:: Pillow >= 2.0.0 < 3.5.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 +.. note:: Pillow >= 2.0.0 < 4.0.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 -.. note:: Pillow >= 3.5.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 +.. note:: Pillow >= 4.0.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 Basic Installation ------------------ @@ -193,7 +193,7 @@ build with newly installed external libraries. Build Options ^^^^^^^^^^^^^ -* Environment Variable: ``MAX_CONCURRENCY=n``. By default, Pillow will +* Environment variable: ``MAX_CONCURRENCY=n``. By default, Pillow will use multiprocessing to build the extension on all available CPUs, but not more than 4. Setting ``MAX_CONCURRENCY`` to 1 will disable parallel building. @@ -223,7 +223,7 @@ Build Options stdout. -Sample Usage:: +Sample usage:: $ MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install From 60bb13a89a8159ce52642a8f3f7448715f63e30d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 4 Jan 2017 11:44:36 +1100 Subject: [PATCH 224/267] Updated zlib to 1.2.10 --- winbuild/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 3cbba9d97..237b3a8b2 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -19,10 +19,10 @@ libs = { # 'version': '2.0' # }, 'zlib': { - 'url': 'http://zlib.net/zlib128.zip', - 'filename': PILLOW_DEPENDS_DIR + 'zlib128.zip', - 'hash': 'md5:126f8676442ffbd97884eb4d6f32afb4', - 'dir': 'zlib-1.2.8', + 'url': 'http://zlib.net/zlib1210.zip', + 'filename': PILLOW_DEPENDS_DIR + 'zlib1210.zip', + 'hash': 'md5:5327bdff96926cf9c479008bae983bc0', + 'dir': 'zlib-1.2.10', }, 'jpeg': { 'url': 'http://www.ijg.org/files/jpegsr9b.zip', From 3ac9ab6fa2cc3022369f4e260783f4d01eaad81d Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 6 Jan 2017 06:19:13 +0200 Subject: [PATCH 225/267] Remove an unnecessary import --- PIL/JpegImagePlugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 716cd7274..3976ff55a 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -38,7 +38,6 @@ import array import struct import io import warnings -from struct import unpack_from from PIL import Image, ImageFile, TiffImagePlugin, _binary from PIL.JpegPresets import presets from PIL._util import isStringType @@ -493,7 +492,7 @@ def _getmp(self): try: rawmpentries = mp[0xB002] for entrynum in range(0, quant): - unpackedentry = unpack_from( + unpackedentry = struct.unpack_from( '{}LLLHH'.format(endianness), rawmpentries, entrynum * 16) labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') From 1fb00976f5ac7f07930f8c701784f0aadf47cb8d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Jan 2017 10:14:49 +1100 Subject: [PATCH 226/267] Removed TODO resolved in #1121 --- PIL/_binary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PIL/_binary.py b/PIL/_binary.py index 9760b86cd..17ee67b11 100644 --- a/PIL/_binary.py +++ b/PIL/_binary.py @@ -28,7 +28,6 @@ else: # Input, le = little endian, be = big endian -# TODO: replace with more readable struct.unpack equivalent def i16le(c, o=0): """ Converts a 2-bytes (16 bits) string to an unsigned integer. From 67c1258cfcb9dc23b000c0fe572da59f0cf76217 Mon Sep 17 00:00:00 2001 From: Luis G Date: Sun, 19 Jun 2016 21:39:31 -0300 Subject: [PATCH 227/267] Expose registered file extensions This adds a new method in Image (registered_extensions) that exposes the internal EXTENSION dictionary to consumers of the library --- PIL/Image.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index f49834e5b..e368ef187 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2512,6 +2512,16 @@ def register_extension(id, extension): EXTENSION[extension.lower()] = id.upper() +def registered_extensions(): + """ + Returns a dictionary containing all file extensions belonging + to registered plugins + """ + if not bool(EXTENSION): + init() + return EXTENSION + + # -------------------------------------------------------------------- # Simple display support. User code may override this. From dad59715618926a405eff12c828b2c704053ff18 Mon Sep 17 00:00:00 2001 From: Luis G Date: Mon, 20 Jun 2016 12:36:26 -0300 Subject: [PATCH 228/267] Add unit tests Add unit tests for registered_extensions --- Tests/test_image.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index f1457a85b..85d1f3f5c 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -196,6 +196,24 @@ class TestImage(PillowTestCase): img_colors = sorted(img.getcolors()) self.assertEqual(img_colors, expected_colors) + def test_registered_extensions_uninitialized(self): + # Act + ext = Image.registered_extensions() + + # Assert + self.assertEqual(bool(ext), True) + + def test_registered_extensions(self): + # Arrange + # Open an image to trigger plugin registration + Image.open('Tests/images/rgb.jpg') + + # Act + ext = Image.registered_extensions() + + # Assert + self.assertEqual(bool(ext), True) + def test_effect_mandelbrot(self): # Arrange size = (512, 512) From 2a93cdb698fea78a3fec9b3bace5244178db1360 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Jan 2017 11:35:09 +1100 Subject: [PATCH 229/267] Simplified assertEqual checks to assertTrue --- Tests/test_image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 85d1f3f5c..a02596b20 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -201,7 +201,7 @@ class TestImage(PillowTestCase): ext = Image.registered_extensions() # Assert - self.assertEqual(bool(ext), True) + self.assertTrue(bool(ext)) def test_registered_extensions(self): # Arrange @@ -212,7 +212,7 @@ class TestImage(PillowTestCase): ext = Image.registered_extensions() # Assert - self.assertEqual(bool(ext), True) + self.assertTrue(bool(ext)) def test_effect_mandelbrot(self): # Arrange From 89742225bfade3df3f7cc2d755916c75ff7aad3c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Jan 2017 13:20:16 +1100 Subject: [PATCH 230/267] Fixed test coverage --- Tests/test_image.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index a02596b20..70bc63877 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -197,11 +197,20 @@ class TestImage(PillowTestCase): self.assertEqual(img_colors, expected_colors) def test_registered_extensions_uninitialized(self): + # Arrange + Image._initialized = 0 + extension = Image.EXTENSION + Image.EXTENSION = {} + # Act - ext = Image.registered_extensions() + Image.registered_extensions() # Assert - self.assertTrue(bool(ext)) + self.assertEqual(Image._initialized, 2) + + # Restore the original state and assert + Image.EXTENSION = extension + self.assertTrue(Image.EXTENSION) def test_registered_extensions(self): # Arrange @@ -209,10 +218,12 @@ class TestImage(PillowTestCase): Image.open('Tests/images/rgb.jpg') # Act - ext = Image.registered_extensions() + extensions = Image.registered_extensions() # Assert - self.assertTrue(bool(ext)) + self.assertTrue(bool(extensions)) + for ext in ['.cur', '.icns', '.tif', '.tiff']: + self.assertIn(ext, extensions) def test_effect_mandelbrot(self): # Arrange From c1905d3e26a88d31d03dd27d1aa33658d25e21e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Jan 2017 18:45:36 +1100 Subject: [PATCH 231/267] Added macOS Sierra tested Pillow version [ci skip] --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 320c7e4ad..eafefa976 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -338,6 +338,8 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Supported**|**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ +| macOS 10.12 Sierra |Yes | 3.4,3.5,3.6 | 4.0.0 |x86-64 | ++----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Mac OS X 10.11 El Capitan |Yes | 2.7,3.3,3.4,3.5 | 3.4.1 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Mac OS X 10.10 Yosemite |Yes | 2.7,3.3,3.4 | 3.0.0 |x86-64 | From da4ee8f90612568fcd55187fbf9b6a7cc367e200 Mon Sep 17 00:00:00 2001 From: David McInnis Date: Sat, 7 Jan 2017 04:47:13 -0800 Subject: [PATCH 232/267] added new version numbers for arch linux --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index eafefa976..42a1e28fe 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -369,7 +369,7 @@ current versions of Linux, macOS, and Windows. +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | Gentoo Linux |Yes | 2.7,3.2 | 2.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ -| Arch Linux |Yes | 2.7,3.5 | 3.4.1 |x86,x86-64 | +| Arch Linux |Yes | 2.7,3.6 | 4.0.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ | FreeBSD 10.2 |Yes | 2.7,3.4 | 3.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+--------------------------------+-----------------------+ From 071a5933ef5c9ed0261c7de37baa471ee7dac6da Mon Sep 17 00:00:00 2001 From: Alexander Karpinsky Date: Sun, 8 Jan 2017 22:32:57 +0300 Subject: [PATCH 233/267] Update concepts.rst --- docs/handbook/concepts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index 8c5de15d6..3023e1e67 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -93,7 +93,7 @@ Filters ------- For geometry operations that may map multiple input pixels to a single output -pixel, the Python Imaging Library provides four different resampling *filters*. +pixel, the Python Imaging Library provides different resampling *filters*. ``NEAREST`` Pick one nearest pixel from the input image. Ignore all other input pixels. From 3bdd15e55e58871181b16e5ede268a1f14e93178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarkko=20P=C3=B6yry?= Date: Tue, 10 Jan 2017 23:20:05 +0200 Subject: [PATCH 234/267] Make mode descriptor cache initialization thread-safe. Initializing mode descriptor cache in-place is racy and may cause a thread to observe a partially constructed cache if another thread is pre-empted while it's still constructing the cache. In this change, the mode descriptor cache is constructed into a local variable instead and then set globally in a single atomic operation, preventing any possibility of observing an incomplete cache. --- PIL/ImageMode.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/PIL/ImageMode.py b/PIL/ImageMode.py index f78a8df90..93dbeab41 100644 --- a/PIL/ImageMode.py +++ b/PIL/ImageMode.py @@ -14,7 +14,7 @@ # # mode descriptor cache -_modes = {} +_modes = None class ModeDescriptor(object): @@ -32,19 +32,23 @@ class ModeDescriptor(object): def getmode(mode): """Gets a mode descriptor for the given mode.""" + global _modes if not _modes: # initialize mode cache from PIL import Image + modes = {} # core modes for m, (basemode, basetype, bands) in Image._MODEINFO.items(): - _modes[m] = ModeDescriptor(m, bands, basemode, basetype) + modes[m] = ModeDescriptor(m, bands, basemode, basetype) # extra experimental modes - _modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") - _modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") - _modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") - _modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") + modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") + modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") + modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") + modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") # mapping modes - _modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") - _modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") - _modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") + modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") + modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") + modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") + # set global mode cache atomically + _modes = modes return _modes[mode] From bbb0ab9101165997df41f64c79b3a75d6f852136 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 12 Jan 2017 21:28:24 +0200 Subject: [PATCH 235/267] Fix invalid string escapes --- winbuild/build.py | 2 +- winbuild/build_dep.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/winbuild/build.py b/winbuild/build.py index 487977adb..1fbfa14a6 100644 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -16,7 +16,7 @@ def setup_vms(): for arch in ('', X64_EXT): ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" % (py, arch, VIRT_BASE, py, arch)) - ret.append("%s%s%s\Scripts\pip.exe install nose" % + ret.append(r"%s%s%s\Scripts\pip.exe install nose" % (VIRT_BASE, py, arch)) return "\n".join(ret) diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index 4c397236b..fb4d55d8c 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -13,6 +13,7 @@ def _relpath(*args): def _relbuild(*args): return _relpath('build', *args) + build_dir = _relpath('build') inc_dir = _relpath('depends') @@ -108,7 +109,7 @@ set MSBUILD=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe set CMAKE="cmake.exe" set INCLIB=%~dp0\depends set BUILD=%~dp0\build -""" + "\n".join('set %s=%%BUILD%%\%s' % (k.upper(), v['dir']) +""" + "\n".join(r'set %s=%%BUILD%%\%s' % (k.upper(), v['dir']) for (k, v) in libs.items() if v['dir']) From c5cd32dd4af58492910e1af1b2d7b7653a3907da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 14 Jan 2017 19:54:14 +1100 Subject: [PATCH 236/267] Changed from pngquant to libimagequant --- depends/install_imagequant.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 21386557b..4a8c0e6be 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,14 +1,14 @@ #!/bin/bash # install libimagequant -archive=pngquant-2.8.2 +archive=libimagequant-2.8.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz pushd $archive -make -C lib shared -sudo cp lib/libimagequant.so* /usr/lib/ -sudo cp lib/libimagequant.h /usr/include/ +make shared +sudo cp libimagequant.so* /usr/lib/ +sudo cp libimagequant.h /usr/include/ popd From 8aec14c405db16d275a61269055b49fa518e0751 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 16 Jan 2017 11:43:30 +0000 Subject: [PATCH 237/267] Update Changes.rst [ci skip] --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b91e961e7..128d7ca3e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,21 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- Expose registered file extensions in Image #2343 + [iggomez, radarhere] + +- Make mode descriptor cache initialization thread-safe. #2351 + [gunjambi] + +- Updated windows test dependencies: Freetype 2.7.1, zlib 1.2.10. #2331, #2332 + [radarhere] + +- Followed upstream pngquant packaging reorg to libimagquant #2354 + [radarhere] + +- Fix invalid string escapes #2352 + [hugovk] + - Add test for crop operation with no argument #2333 [radarhere] From a3d81e0677713ae9e0bac594679e13277d74ec4d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 17 Jan 2017 19:36:04 +1100 Subject: [PATCH 238/267] Updated zlib to 1.2.11 --- winbuild/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index efe36810b..6e927d2fb 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -19,10 +19,10 @@ libs = { # 'version': '2.0' # }, 'zlib': { - 'url': 'http://zlib.net/zlib1210.zip', - 'filename': PILLOW_DEPENDS_DIR + 'zlib1210.zip', - 'hash': 'md5:5327bdff96926cf9c479008bae983bc0', - 'dir': 'zlib-1.2.10', + 'url': 'http://zlib.net/zlib1211.zip', + 'filename': PILLOW_DEPENDS_DIR + 'zlib1211.zip', + 'hash': 'md5:16b41357b2cd81bca5e1947238e64465', + 'dir': 'zlib-1.2.11', }, 'jpeg': { 'url': 'http://www.ijg.org/files/jpegsr9b.zip', From 58b5c9187db8bb1c0cf49a35d8996a2179ac2e4c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Jan 2017 00:22:18 +1100 Subject: [PATCH 239/267] Replaced absolute PIL imports with relative imports (#2349) --- PIL/BdfFontFile.py | 3 +-- PIL/BmpImagePlugin.py | 11 +++------ PIL/BufrStubImagePlugin.py | 2 +- PIL/CurImagePlugin.py | 7 ++---- PIL/DcxImagePlugin.py | 7 +++--- PIL/DdsImagePlugin.py | 2 +- PIL/EpsImagePlugin.py | 6 ++--- PIL/FitsStubImagePlugin.py | 2 +- PIL/FliImagePlugin.py | 8 ++----- PIL/FontFile.py | 2 +- PIL/FpxImagePlugin.py | 6 ++--- PIL/FtexImagePlugin.py | 2 +- PIL/GbrImagePlugin.py | 5 ++--- PIL/GdImageFile.py | 7 +++--- PIL/GifImagePlugin.py | 10 +++------ PIL/GimpGradientFile.py | 2 +- PIL/GimpPaletteFile.py | 2 +- PIL/GribStubImagePlugin.py | 2 +- PIL/Hdf5StubImagePlugin.py | 2 +- PIL/IcnsImagePlugin.py | 5 ++--- PIL/IcoImagePlugin.py | 7 ++---- PIL/ImImagePlugin.py | 4 ++-- PIL/Image.py | 46 ++++++++++++++++++-------------------- PIL/ImageChops.py | 2 +- PIL/ImageColor.py | 2 +- PIL/ImageDraw.py | 10 ++++----- PIL/ImageDraw2.py | 2 +- PIL/ImageEnhance.py | 2 +- PIL/ImageFile.py | 4 ++-- PIL/ImageFont.py | 6 ++--- PIL/ImageGrab.py | 4 ++-- PIL/ImageMath.py | 3 +-- PIL/ImageMode.py | 3 ++- PIL/ImageMorph.py | 3 +-- PIL/ImageOps.py | 6 ++--- PIL/ImagePalette.py | 5 +---- PIL/ImagePath.py | 2 +- PIL/ImageQt.py | 4 ++-- PIL/ImageTk.py | 4 ++-- PIL/ImageTransform.py | 2 +- PIL/ImageWin.py | 2 +- PIL/ImtImagePlugin.py | 2 +- PIL/IptcImagePlugin.py | 10 +++------ PIL/Jpeg2KImagePlugin.py | 2 +- PIL/JpegImagePlugin.py | 12 ++++------ PIL/McIdasImagePlugin.py | 2 +- PIL/MicImagePlugin.py | 2 +- PIL/MpegImagePlugin.py | 4 ++-- PIL/MpoImagePlugin.py | 2 +- PIL/MspImagePlugin.py | 7 ++---- PIL/PSDraw.py | 2 +- PIL/PaletteFile.py | 2 +- PIL/PalmImagePlugin.py | 6 ++--- PIL/PcdImagePlugin.py | 5 ++--- PIL/PcfFontFile.py | 11 ++------- PIL/PcxImagePlugin.py | 9 ++------ PIL/PdfImagePlugin.py | 4 ++-- PIL/PixarImagePlugin.py | 6 ++--- PIL/PngImagePlugin.py | 11 ++------- PIL/PpmImagePlugin.py | 2 +- PIL/PsdImagePlugin.py | 10 ++------- PIL/SgiImagePlugin.py | 9 +++----- PIL/SunImagePlugin.py | 21 +++++++++-------- PIL/TarIO.py | 2 +- PIL/TgaImagePlugin.py | 10 ++------- PIL/TiffImagePlugin.py | 13 ++++------- PIL/WalImageFile.py | 5 ++--- PIL/WebPImagePlugin.py | 6 ++--- PIL/WmfImagePlugin.py | 12 ++++------ PIL/XVThumbImagePlugin.py | 5 ++--- PIL/XbmImagePlugin.py | 2 +- PIL/XpmImagePlugin.py | 4 ++-- PIL/features.py | 2 +- 73 files changed, 160 insertions(+), 258 deletions(-) diff --git a/PIL/BdfFontFile.py b/PIL/BdfFontFile.py index b02c44fd2..c8bc60461 100644 --- a/PIL/BdfFontFile.py +++ b/PIL/BdfFontFile.py @@ -19,8 +19,7 @@ from __future__ import print_function -from PIL import Image -from PIL import FontFile +from . import Image, FontFile # -------------------------------------------------------------------- diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index b04981af9..1afe303ab 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -24,18 +24,13 @@ # -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16le as i16, i32le as i32, \ + o8, o16le as o16, o32le as o32 import math __version__ = "0.7" -i8 = _binary.i8 -i16 = _binary.i16le -i32 = _binary.i32le -o8 = _binary.o8 -o16 = _binary.o16le -o32 = _binary.o32le - # # -------------------------------------------------------------------- # Read BMP file diff --git a/PIL/BufrStubImagePlugin.py b/PIL/BufrStubImagePlugin.py index 5184546e4..4c5da942f 100644 --- a/PIL/BufrStubImagePlugin.py +++ b/PIL/BufrStubImagePlugin.py @@ -9,7 +9,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile +from . import Image, ImageFile _handler = None diff --git a/PIL/CurImagePlugin.py b/PIL/CurImagePlugin.py index 3a9a10e01..e4257cd5a 100644 --- a/PIL/CurImagePlugin.py +++ b/PIL/CurImagePlugin.py @@ -18,17 +18,14 @@ from __future__ import print_function -from PIL import Image, BmpImagePlugin, _binary +from . import Image, BmpImagePlugin +from ._binary import i8, i16le as i16, i32le as i32 __version__ = "0.1" # # -------------------------------------------------------------------- -i8 = _binary.i8 -i16 = _binary.i16le -i32 = _binary.i32le - def _accept(prefix): return prefix[:4] == b"\0\0\2\0" diff --git a/PIL/DcxImagePlugin.py b/PIL/DcxImagePlugin.py index f9034d15c..5663dff5f 100644 --- a/PIL/DcxImagePlugin.py +++ b/PIL/DcxImagePlugin.py @@ -21,15 +21,14 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, _binary -from PIL.PcxImagePlugin import PcxImageFile +from . import Image +from ._binary import i32le as i32 +from .PcxImagePlugin import PcxImageFile __version__ = "0.2" MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? -i32 = _binary.i32le - def _accept(prefix): return len(prefix) >= 4 and i32(prefix) == MAGIC diff --git a/PIL/DdsImagePlugin.py b/PIL/DdsImagePlugin.py index b6228c2ad..9508e61c8 100644 --- a/PIL/DdsImagePlugin.py +++ b/PIL/DdsImagePlugin.py @@ -12,7 +12,7 @@ Full text of the CC0 license: import struct from io import BytesIO -from PIL import Image, ImageFile +from . import Image, ImageFile # Magic ("DDS ") diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 77a7e7e1c..8dd3e6857 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -23,16 +23,14 @@ import re import io import sys -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i32le as i32, o32le as o32 __version__ = "0.5" # # -------------------------------------------------------------------- -i32 = _binary.i32le -o32 = _binary.o32le - split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") diff --git a/PIL/FitsStubImagePlugin.py b/PIL/FitsStubImagePlugin.py index b6ea0e37d..e3a7eb4a6 100644 --- a/PIL/FitsStubImagePlugin.py +++ b/PIL/FitsStubImagePlugin.py @@ -9,7 +9,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile +from . import Image, ImageFile _handler = None diff --git a/PIL/FliImagePlugin.py b/PIL/FliImagePlugin.py index a07dc29b0..429b5e26f 100644 --- a/PIL/FliImagePlugin.py +++ b/PIL/FliImagePlugin.py @@ -16,15 +16,11 @@ # -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16le as i16, i32le as i32, o8 __version__ = "0.2" -i8 = _binary.i8 -i16 = _binary.i16le -i32 = _binary.i32le -o8 = _binary.o8 - # # decoder diff --git a/PIL/FontFile.py b/PIL/FontFile.py index 807984a8c..46e49bc4e 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -17,7 +17,7 @@ from __future__ import print_function import os -from PIL import Image, _binary +from . import Image, _binary WIDTH = 800 diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index 08e7f0da1..23f15f459 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -17,15 +17,13 @@ from __future__ import print_function -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i32le as i32, i8 import olefile __version__ = "0.1" -i32 = _binary.i32le -i8 = _binary.i8 - # we map from colour field tuples to (mode, rawmode) descriptors MODES = { # opacity diff --git a/PIL/FtexImagePlugin.py b/PIL/FtexImagePlugin.py index 4fa462f04..0d08f4cc4 100644 --- a/PIL/FtexImagePlugin.py +++ b/PIL/FtexImagePlugin.py @@ -42,7 +42,7 @@ Note: All data is stored in little-Endian (Intel) byte order. import struct from io import BytesIO -from PIL import Image, ImageFile +from . import Image, ImageFile MAGIC = b"FTEX" diff --git a/PIL/GbrImagePlugin.py b/PIL/GbrImagePlugin.py index d62981c28..b8b9f1a3c 100644 --- a/PIL/GbrImagePlugin.py +++ b/PIL/GbrImagePlugin.py @@ -24,9 +24,8 @@ # Version 3 files have a format specifier of 18 for 16bit floats in # the color depth field. This is currently unsupported by Pillow. -from PIL import Image, ImageFile, _binary - -i32 = _binary.i32be +from . import Image, ImageFile +from ._binary import i32be as i32 def _accept(prefix): diff --git a/PIL/GdImageFile.py b/PIL/GdImageFile.py index 5a07ee230..09ab5ec69 100644 --- a/PIL/GdImageFile.py +++ b/PIL/GdImageFile.py @@ -23,8 +23,9 @@ # purposes only. -from PIL import ImageFile, ImagePalette, _binary -from PIL._util import isPath +from . import ImageFile, ImagePalette +from ._binary import i16be as i16 +from ._util import isPath __version__ = "0.1" @@ -34,8 +35,6 @@ except ImportError: import __builtin__ builtins = __builtin__ -i16 = _binary.i16be - ## # Image plugin for the GD uncompressed format. Note that this format diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index a50af6c02..2e519c7ac 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -24,8 +24,9 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile, ImagePalette, \ - ImageChops, ImageSequence, _binary +from . import Image, ImageFile, ImagePalette, \ + ImageChops, ImageSequence +from ._binary import i8, i16le as i16, o8, o16le as o16 __version__ = "0.9" @@ -33,11 +34,6 @@ __version__ = "0.9" # -------------------------------------------------------------------- # Helpers -i8 = _binary.i8 -i16 = _binary.i16le -o8 = _binary.o8 -o16 = _binary.o16le - # -------------------------------------------------------------------- # Identify/read GIF files diff --git a/PIL/GimpGradientFile.py b/PIL/GimpGradientFile.py index 45af573bb..43cd72649 100644 --- a/PIL/GimpGradientFile.py +++ b/PIL/GimpGradientFile.py @@ -14,7 +14,7 @@ # from math import pi, log, sin, sqrt -from PIL._binary import o8 +from ._binary import o8 # -------------------------------------------------------------------- # Stuff to translate curve segments to palette values (derived from diff --git a/PIL/GimpPaletteFile.py b/PIL/GimpPaletteFile.py index e4b4641e5..6eef6a2dd 100644 --- a/PIL/GimpPaletteFile.py +++ b/PIL/GimpPaletteFile.py @@ -15,7 +15,7 @@ # import re -from PIL._binary import o8 +from ._binary import o8 ## diff --git a/PIL/GribStubImagePlugin.py b/PIL/GribStubImagePlugin.py index e880e5281..1fbfe61dc 100644 --- a/PIL/GribStubImagePlugin.py +++ b/PIL/GribStubImagePlugin.py @@ -9,7 +9,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile +from . import Image, ImageFile _handler = None diff --git a/PIL/Hdf5StubImagePlugin.py b/PIL/Hdf5StubImagePlugin.py index dc85084d8..a5d6b1bc1 100644 --- a/PIL/Hdf5StubImagePlugin.py +++ b/PIL/Hdf5StubImagePlugin.py @@ -9,7 +9,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile +from . import Image, ImageFile _handler = None diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py index 089fc2a44..cb215fe3e 100644 --- a/PIL/IcnsImagePlugin.py +++ b/PIL/IcnsImagePlugin.py @@ -15,7 +15,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile, PngImagePlugin, _binary +from PIL import Image, ImageFile, PngImagePlugin +from PIL._binary import i8 import io import os import shutil @@ -27,8 +28,6 @@ enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') if enable_jpeg2k: from PIL import Jpeg2KImagePlugin -i8 = _binary.i8 - HEADERSIZE = 8 diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 3436ae84f..e4db4e766 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -25,7 +25,8 @@ import struct from io import BytesIO -from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary +from . import Image, ImageFile, BmpImagePlugin, PngImagePlugin +from ._binary import i8, i16le as i16, i32le as i32 from math import log, ceil __version__ = "0.1" @@ -33,10 +34,6 @@ __version__ = "0.1" # # -------------------------------------------------------------------- -i8 = _binary.i8 -i16 = _binary.i16le -i32 = _binary.i32le - _MAGIC = b"\0\0\1\0" diff --git a/PIL/ImImagePlugin.py b/PIL/ImImagePlugin.py index dd4f82900..4638419fa 100644 --- a/PIL/ImImagePlugin.py +++ b/PIL/ImImagePlugin.py @@ -27,8 +27,8 @@ import re -from PIL import Image, ImageFile, ImagePalette -from PIL._binary import i8 +from . import Image, ImageFile, ImagePalette +from ._binary import i8 __version__ = "0.7" diff --git a/PIL/Image.py b/PIL/Image.py index e368ef187..dbe916df1 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -26,7 +26,7 @@ from __future__ import print_function -from PIL import VERSION, PILLOW_VERSION, _plugins +from . import VERSION, PILLOW_VERSION, _plugins import logging import warnings @@ -64,7 +64,7 @@ try: # import Image and use the Image.core variable instead. # Also note that Image.core is not a publicly documented interface, # and should be considered private and subject to change. - from PIL import _imaging as core + from . import _imaging as core if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): raise ImportError("The _imaging extension was built for another " "version of Pillow or PIL") @@ -109,11 +109,9 @@ except ImportError: import __builtin__ builtins = __builtin__ -from PIL import ImageMode -from PIL._binary import i8 -from PIL._util import isPath -from PIL._util import isStringType -from PIL._util import deferred_error +from . import ImageMode +from ._binary import i8 +from ._util import isPath, isStringType, deferred_error import os import sys @@ -355,23 +353,23 @@ def preinit(): return try: - from PIL import BmpImagePlugin + from . import BmpImagePlugin except ImportError: pass try: - from PIL import GifImagePlugin + from . import GifImagePlugin except ImportError: pass try: - from PIL import JpegImagePlugin + from . import JpegImagePlugin except ImportError: pass try: - from PIL import PpmImagePlugin + from . import PpmImagePlugin except ImportError: pass try: - from PIL import PngImagePlugin + from . import PngImagePlugin except ImportError: pass # try: @@ -525,7 +523,7 @@ class Image(object): if self.palette: new.palette = self.palette.copy() if im.mode == "P" and not new.palette: - from PIL import ImagePalette + from . import ImagePalette new.palette = ImagePalette.ImagePalette() new.info = self.info.copy() return new @@ -775,7 +773,7 @@ class Image(object): if HAS_CFFI and USE_CFFI_ACCESS: if self.pyaccess: return self.pyaccess - from PIL import PyAccess + from . import PyAccess self.pyaccess = PyAccess.new(self, self.readonly) if self.pyaccess: return self.pyaccess @@ -908,7 +906,7 @@ class Image(object): if mode == "P" and palette == ADAPTIVE: im = self.im.quantize(colors) new = self._new(im) - from PIL import ImagePalette + from . import ImagePalette new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) if delete_trns: # This could possibly happen if we requantize to fewer colors. @@ -1321,7 +1319,7 @@ class Image(object): box += (box[0]+size[0], box[1]+size[1]) if isStringType(im): - from PIL import ImageColor + from . import ImageColor im = ImageColor.getcolor(im, self.mode) elif isImageType(im): @@ -1468,7 +1466,7 @@ class Image(object): :param data: A palette sequence (either a list or a string). """ - from PIL import ImagePalette + from . import ImagePalette if self.mode not in ("L", "P"): raise ValueError("illegal image mode") @@ -1583,7 +1581,7 @@ class Image(object): angle = angle % 360.0 # Fast paths regardless of filter, as long as we're not - # translating or changing the center. + # translating or changing the center. if not (center or translate): if angle == 0: return self.copy() @@ -1977,14 +1975,14 @@ class Image(object): def toqimage(self): """Returns a QImage copy of this image""" - from PIL import ImageQt + from . import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.toqimage(self) def toqpixmap(self): """Returns a QPixmap copy of this image""" - from PIL import ImageQt + from . import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.toqpixmap(self) @@ -2057,7 +2055,7 @@ def new(mode, size, color=0): if isStringType(color): # css3-style specifier - from PIL import ImageColor + from . import ImageColor color = ImageColor.getcolor(color, mode) return Image()._new(core.fill(mode, size, color)) @@ -2219,7 +2217,7 @@ def fromarray(obj, mode=None): def fromqimage(im): """Creates an image instance from a QImage image""" - from PIL import ImageQt + from . import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.fromqimage(im) @@ -2227,7 +2225,7 @@ def fromqimage(im): def fromqpixmap(im): """Creates an image instance from a QPixmap image""" - from PIL import ImageQt + from . import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.fromqpixmap(im) @@ -2531,7 +2529,7 @@ def _show(image, **options): def _showxv(image, title=None, **options): - from PIL import ImageShow + from . import ImageShow ImageShow.show(image, title, **options) diff --git a/PIL/ImageChops.py b/PIL/ImageChops.py index ba5350e02..89016730e 100644 --- a/PIL/ImageChops.py +++ b/PIL/ImageChops.py @@ -15,7 +15,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image def constant(image, value): diff --git a/PIL/ImageColor.py b/PIL/ImageColor.py index 64eebfe9d..1c7bc31d5 100644 --- a/PIL/ImageColor.py +++ b/PIL/ImageColor.py @@ -17,7 +17,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image import re diff --git a/PIL/ImageDraw.py b/PIL/ImageDraw.py index 720403920..6b72fcab7 100644 --- a/PIL/ImageDraw.py +++ b/PIL/ImageDraw.py @@ -33,8 +33,8 @@ import numbers import warnings -from PIL import Image, ImageColor -from PIL._util import isStringType +from . import Image, ImageColor +from ._util import isStringType """ A simple 2D drawing interface for PIL images. @@ -105,7 +105,7 @@ class ImageDraw(object): """Get the current default font.""" if not self.font: # FIXME: should add a font repository - from PIL import ImageFont + from . import ImageFont self.font = ImageFont.load_default() return self.font @@ -319,11 +319,11 @@ def getdraw(im=None, hints=None): handler = None if not hints or "nicest" in hints: try: - from PIL import _imagingagg as handler + from . import _imagingagg as handler except ImportError: pass if handler is None: - from PIL import ImageDraw2 as handler + from . import ImageDraw2 as handler if im: im = handler.Draw(im) return im, handler diff --git a/PIL/ImageDraw2.py b/PIL/ImageDraw2.py index 62ee11630..a1763350d 100644 --- a/PIL/ImageDraw2.py +++ b/PIL/ImageDraw2.py @@ -16,7 +16,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath +from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath class Pen(object): diff --git a/PIL/ImageEnhance.py b/PIL/ImageEnhance.py index 56b5c0199..b38f406a3 100644 --- a/PIL/ImageEnhance.py +++ b/PIL/ImageEnhance.py @@ -18,7 +18,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFilter, ImageStat +from . import Image, ImageFilter, ImageStat class _Enhance(object): diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index a56795751..8f3ee524c 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -27,8 +27,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL._util import isPath +from . import Image +from ._util import isPath import io import os import sys diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 49494b33f..c74d00138 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -25,8 +25,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL._util import isDirectory, isPath +from . import Image +from ._util import isDirectory, isPath import os import sys @@ -37,7 +37,7 @@ class _imagingft_not_installed(object): raise ImportError("The _imagingft C module is not installed") try: - from PIL import _imagingft as core + from . import _imagingft as core except ImportError: core = _imagingft_not_installed() diff --git a/PIL/ImageGrab.py b/PIL/ImageGrab.py index 03283f3d6..938d0e994 100644 --- a/PIL/ImageGrab.py +++ b/PIL/ImageGrab.py @@ -15,7 +15,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image import sys if sys.platform not in ["win32", "darwin"]: @@ -75,7 +75,7 @@ def grabclipboard(): debug = 0 # temporary interface data = Image.core.grabclipboard(debug) if isinstance(data, bytes): - from PIL import BmpImagePlugin + from . import BmpImagePlugin import io return BmpImagePlugin.DibImageFile(io.BytesIO(data)) return data diff --git a/PIL/ImageMath.py b/PIL/ImageMath.py index 897f0aeb1..2ccd1891b 100644 --- a/PIL/ImageMath.py +++ b/PIL/ImageMath.py @@ -15,8 +15,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL import _imagingmath +from . import Image, _imagingmath try: import builtins diff --git a/PIL/ImageMode.py b/PIL/ImageMode.py index 93dbeab41..b227f2127 100644 --- a/PIL/ImageMode.py +++ b/PIL/ImageMode.py @@ -35,7 +35,8 @@ def getmode(mode): global _modes if not _modes: # initialize mode cache - from PIL import Image + + from . import Image modes = {} # core modes for m, (basemode, basetype, bands) in Image._MODEINFO.items(): diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 3ad708291..8382f7ce0 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -7,8 +7,7 @@ from __future__ import print_function -from PIL import Image -from PIL import _imagingmorph +from . import Image, _imagingmorph import re LUT_SIZE = 1 << 9 diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 182f3c3d2..3681109c1 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -17,8 +17,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL._util import isStringType +from . import Image +from ._util import isStringType import operator import functools @@ -39,7 +39,7 @@ def _border(border): def _color(color, mode): if isStringType(color): - from PIL import ImageColor + from . import ImageColor color = ImageColor.getcolor(color, mode) return color diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 8bf09385c..cecc64583 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -17,10 +17,7 @@ # import array -from PIL import ImageColor -from PIL import GimpPaletteFile -from PIL import GimpGradientFile -from PIL import PaletteFile +from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile class ImagePalette(object): diff --git a/PIL/ImagePath.py b/PIL/ImagePath.py index 3abfba031..1543508e4 100644 --- a/PIL/ImagePath.py +++ b/PIL/ImagePath.py @@ -14,7 +14,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image # the Python class below is overridden by the C implementation. diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 2ce89e7ad..36b4e1ebc 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -16,8 +16,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL._util import isPath +from . import Image +from ._util import isPath from io import BytesIO qt_is_installed = True diff --git a/PIL/ImageTk.py b/PIL/ImageTk.py index d3957b6d1..25c4534ce 100644 --- a/PIL/ImageTk.py +++ b/PIL/ImageTk.py @@ -32,7 +32,7 @@ except ImportError: tkinter = Tkinter del Tkinter -from PIL import Image +from . import Image from io import BytesIO @@ -182,7 +182,7 @@ class PhotoImage(object): except tkinter.TclError: # activate Tkinter hook try: - from PIL import _imagingtk + from . import _imagingtk try: _imagingtk.tkinit(tk.interpaddr(), 1) except AttributeError: diff --git a/PIL/ImageTransform.py b/PIL/ImageTransform.py index 92cc2c7e0..dcfcdfae8 100644 --- a/PIL/ImageTransform.py +++ b/PIL/ImageTransform.py @@ -13,7 +13,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image class Transform(Image.ImageTransformHandler): diff --git a/PIL/ImageWin.py b/PIL/ImageWin.py index 514fc4ff8..cc4dced97 100644 --- a/PIL/ImageWin.py +++ b/PIL/ImageWin.py @@ -17,7 +17,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +from . import Image class HDC(object): diff --git a/PIL/ImtImagePlugin.py b/PIL/ImtImagePlugin.py index e95f7aeba..05e8cd31a 100644 --- a/PIL/ImtImagePlugin.py +++ b/PIL/ImtImagePlugin.py @@ -17,7 +17,7 @@ import re -from PIL import Image, ImageFile +from . import Image, ImageFile __version__ = "0.2" diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 319e6abfe..8941643bb 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -17,17 +17,13 @@ from __future__ import print_function -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i8, i16be as i16, i32be as i32, o8 import os import tempfile __version__ = "0.3" -i8 = _binary.i8 -i16 = _binary.i16be -i32 = _binary.i32be -o8 = _binary.o8 - COMPRESSION = { 1: "raw", 5: "jpeg" @@ -191,7 +187,7 @@ def getiptcinfo(im): :returns: A dictionary containing IPTC information, or None if no IPTC information block was found. """ - from PIL import TiffImagePlugin, JpegImagePlugin + from . import TiffImagePlugin, JpegImagePlugin import io data = None diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 66de34bfa..101f55f76 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -12,7 +12,7 @@ # # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile +from . import Image, ImageFile import struct import os import io diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 3976ff55a..f01885b60 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -38,14 +38,10 @@ import array import struct import io import warnings -from PIL import Image, ImageFile, TiffImagePlugin, _binary -from PIL.JpegPresets import presets -from PIL._util import isStringType - -i8 = _binary.i8 -o8 = _binary.o8 -i16 = _binary.i16be -i32 = _binary.i32be +from . import Image, ImageFile, TiffImagePlugin +from ._binary import i8, o8, i16be as i16, i32be as i32 +from .JpegPresets import presets +from ._util import isStringType __version__ = "0.6" diff --git a/PIL/McIdasImagePlugin.py b/PIL/McIdasImagePlugin.py index b75360353..08eeec39f 100644 --- a/PIL/McIdasImagePlugin.py +++ b/PIL/McIdasImagePlugin.py @@ -17,7 +17,7 @@ # import struct -from PIL import Image, ImageFile +from . import Image, ImageFile __version__ = "0.2" diff --git a/PIL/MicImagePlugin.py b/PIL/MicImagePlugin.py index 125e297ac..a70838b07 100644 --- a/PIL/MicImagePlugin.py +++ b/PIL/MicImagePlugin.py @@ -17,7 +17,7 @@ # -from PIL import Image, TiffImagePlugin +from . import Image, TiffImagePlugin import olefile diff --git a/PIL/MpegImagePlugin.py b/PIL/MpegImagePlugin.py index 6671b8691..bdc5e3689 100644 --- a/PIL/MpegImagePlugin.py +++ b/PIL/MpegImagePlugin.py @@ -14,8 +14,8 @@ # -from PIL import Image, ImageFile -from PIL._binary import i8 +from . import Image, ImageFile +from ._binary import i8 __version__ = "0.1" diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 1d26021d8..9341c530c 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -18,7 +18,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, JpegImagePlugin +from . import Image, JpegImagePlugin __version__ = "0.1" diff --git a/PIL/MspImagePlugin.py b/PIL/MspImagePlugin.py index 85f8e764b..b60a21d69 100644 --- a/PIL/MspImagePlugin.py +++ b/PIL/MspImagePlugin.py @@ -17,7 +17,8 @@ # -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i16le as i16, o16le as o16 __version__ = "0.1" @@ -25,8 +26,6 @@ __version__ = "0.1" # # read MSP files -i16 = _binary.i16le - def _accept(prefix): return prefix[:4] in [b"DanM", b"LinS"] @@ -66,8 +65,6 @@ class MspImageFile(ImageFile.ImageFile): # # write MSP files (uncompressed only) -o16 = _binary.o16le - def _save(im, fp, filename): diff --git a/PIL/PSDraw.py b/PIL/PSDraw.py index d4e7b18cc..fe0823860 100644 --- a/PIL/PSDraw.py +++ b/PIL/PSDraw.py @@ -15,7 +15,7 @@ # See the README file for information on usage and redistribution. # -from PIL import EpsImagePlugin +from . import EpsImagePlugin import sys ## diff --git a/PIL/PaletteFile.py b/PIL/PaletteFile.py index ef50feefd..9ed69d687 100644 --- a/PIL/PaletteFile.py +++ b/PIL/PaletteFile.py @@ -13,7 +13,7 @@ # See the README file for information on usage and redistribution. # -from PIL._binary import o8 +from ._binary import o8 ## diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py index d02839bdf..cb4e491c0 100644 --- a/PIL/PalmImagePlugin.py +++ b/PIL/PalmImagePlugin.py @@ -7,7 +7,8 @@ # Image plugin for Palm pixmap images (output only). ## -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import o8, o16be as o16b __version__ = "1.0" @@ -108,9 +109,6 @@ _COMPRESSION_TYPES = { "scanline": 0x00, } -o8 = _binary.o8 -o16b = _binary.o16be - # # -------------------------------------------------------------------- diff --git a/PIL/PcdImagePlugin.py b/PIL/PcdImagePlugin.py index 24186bcfc..fa95b5008 100644 --- a/PIL/PcdImagePlugin.py +++ b/PIL/PcdImagePlugin.py @@ -15,12 +15,11 @@ # -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i8 __version__ = "0.1" -i8 = _binary.i8 - ## # Image plugin for PhotoCD images. This plugin only reads the 768x512 diff --git a/PIL/PcfFontFile.py b/PIL/PcfFontFile.py index c2006905e..eba85feb0 100644 --- a/PIL/PcfFontFile.py +++ b/PIL/PcfFontFile.py @@ -16,9 +16,8 @@ # See the README file for information on usage and redistribution. # -from PIL import Image -from PIL import FontFile -from PIL import _binary +from . import Image, FontFile +from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32 # -------------------------------------------------------------------- # declarations @@ -42,12 +41,6 @@ BYTES_PER_ROW = [ lambda bits: ((bits+63) >> 3) & ~7, ] -i8 = _binary.i8 -l16 = _binary.i16le -l32 = _binary.i32le -b16 = _binary.i16be -b32 = _binary.i32be - def sz(s, o): return s[o:s.index(b"\0", o)] diff --git a/PIL/PcxImagePlugin.py b/PIL/PcxImagePlugin.py index 9440d5362..e3c008f4f 100644 --- a/PIL/PcxImagePlugin.py +++ b/PIL/PcxImagePlugin.py @@ -28,14 +28,11 @@ from __future__ import print_function import logging -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16le as i16, o8, o16le as o16 logger = logging.getLogger(__name__) -i8 = _binary.i8 -i16 = _binary.i16le -o8 = _binary.o8 - __version__ = "0.6" @@ -123,8 +120,6 @@ SAVE = { "RGB": (5, 8, 3, "RGB;L"), } -o16 = _binary.o16le - def _save(im, fp, filename, check=0): diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py index 7decf0ee5..b615fe1e0 100644 --- a/PIL/PdfImagePlugin.py +++ b/PIL/PdfImagePlugin.py @@ -20,8 +20,8 @@ # Image plugin for PDF images (output only). ## -from PIL import Image, ImageFile -from PIL._binary import i8 +from . import Image, ImageFile +from ._binary import i8 import io __version__ = "0.4" diff --git a/PIL/PixarImagePlugin.py b/PIL/PixarImagePlugin.py index fd002d9cf..732d8c692 100644 --- a/PIL/PixarImagePlugin.py +++ b/PIL/PixarImagePlugin.py @@ -19,16 +19,14 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i16le as i16 __version__ = "0.1" # # helpers -i16 = _binary.i16le - - def _accept(prefix): return prefix[:4] == b"\200\350\000\000" diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 4d6f9d7ff..dca8a456e 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -38,16 +38,13 @@ import re import zlib import struct -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16be as i16, i32be as i32, o8, o16be as o16, o32be as o32 __version__ = "0.9" logger = logging.getLogger(__name__) -i8 = _binary.i8 -i16 = _binary.i16be -i32 = _binary.i32be - is_cid = re.compile(br"\w\w\w\w").match @@ -621,10 +618,6 @@ class PngImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- # PNG writer -o8 = _binary.o8 -o16 = _binary.o16be -o32 = _binary.o32be - _OUTMODES = { # supported PIL modes, and corresponding rawmodes/bits/color combinations "1": ("1", b'\x01\x00'), diff --git a/PIL/PpmImagePlugin.py b/PIL/PpmImagePlugin.py index adaf8384c..b91f9912b 100644 --- a/PIL/PpmImagePlugin.py +++ b/PIL/PpmImagePlugin.py @@ -17,7 +17,7 @@ import string -from PIL import Image, ImageFile +from . import Image, ImageFile __version__ = "0.2" diff --git a/PIL/PsdImagePlugin.py b/PIL/PsdImagePlugin.py index d06e320b0..1e4051c29 100644 --- a/PIL/PsdImagePlugin.py +++ b/PIL/PsdImagePlugin.py @@ -18,7 +18,8 @@ __version__ = "0.4" -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16be as i16, i32be as i32 MODES = { # (photoshop mode, bits) -> (pil mode, required channels) @@ -33,13 +34,6 @@ MODES = { (9, 8): ("LAB", 3) } -# -# helpers - -i8 = _binary.i8 -i16 = _binary.i16be -i32 = _binary.i32be - # --------------------------------------------------------------------. # read PSD images diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py index d0e293368..973c68567 100644 --- a/PIL/SgiImagePlugin.py +++ b/PIL/SgiImagePlugin.py @@ -21,16 +21,13 @@ # -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i8, o8, i16be as i16 import struct import os __version__ = "0.3" -i8 = _binary.i8 -o8 = _binary.o8 -i16 = _binary.i16be - def _accept(prefix): return len(prefix) >= 2 and i16(prefix) == 474 @@ -134,7 +131,7 @@ def _save(im, fp, filename): fp.write(struct.pack('404s', b'')) # dummy - #assert we've got the right number of bands. + #assert we've got the right number of bands. if len(im.getbands()) != z: raise ValueError("incorrect number of bands in SGI write: %s vs %s" % (z, len(im.getbands()))) diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py index c3e2bc402..876fb73fa 100644 --- a/PIL/SunImagePlugin.py +++ b/PIL/SunImagePlugin.py @@ -17,12 +17,11 @@ # -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i32be as i32 __version__ = "0.3" -i32 = _binary.i32be - def _accept(prefix): return len(prefix) >= 4 and i32(prefix) == 0x59a66a95 @@ -63,11 +62,11 @@ class SunImageFile(ImageFile.ImageFile): self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) - data_length = i32(s[16:20]) # unreliable, ignore. + data_length = i32(s[16:20]) # unreliable, ignore. file_type = i32(s[20:24]) palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary palette_length = i32(s[28:32]) - + if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 4: @@ -85,23 +84,23 @@ class SunImageFile(ImageFile.ImageFile): else: self.mode, rawmode = 'RGB', 'BGRX' else: - raise SyntaxError("Unsupported Mode/Bit Depth") - + raise SyntaxError("Unsupported Mode/Bit Depth") + if palette_length: if palette_length > 1024: raise SyntaxError("Unsupported Color Palette Length") if palette_type != 1: raise SyntaxError("Unsupported Palette Type") - + offset = offset + palette_length self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": self.mode = "P" rawmode = rawmode.replace('L', 'P') - + # 16 bit boundaries on stride - stride = ((self.size[0] * depth + 15) // 16) * 2 + stride = ((self.size[0] * depth + 15) // 16) * 2 # file type: Type is the version (or flavor) of the bitmap # file. The following values are typically found in the Type @@ -127,7 +126,7 @@ class SunImageFile(ImageFile.ImageFile): self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] else: raise SyntaxError('Unsupported Sun Raster file type') - + # # registry diff --git a/PIL/TarIO.py b/PIL/TarIO.py index 4f3182848..0e949ff88 100644 --- a/PIL/TarIO.py +++ b/PIL/TarIO.py @@ -14,7 +14,7 @@ # See the README file for information on usage and redistribution. # -from PIL import ContainerIO +from . import ContainerIO ## diff --git a/PIL/TgaImagePlugin.py b/PIL/TgaImagePlugin.py index a75ce2986..de2844339 100644 --- a/PIL/TgaImagePlugin.py +++ b/PIL/TgaImagePlugin.py @@ -17,7 +17,8 @@ # -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import i8, i16le as i16, o8, o16le as o16, o32le as o32 __version__ = "0.3" @@ -26,9 +27,6 @@ __version__ = "0.3" # -------------------------------------------------------------------- # Read RGA file -i8 = _binary.i8 -i16 = _binary.i16le - MODES = { # map imagetype/depth to rawmode @@ -132,10 +130,6 @@ class TgaImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- # Write TGA file -o8 = _binary.o8 -o16 = _binary.o16le -o32 = _binary.o32le - SAVE = { "1": ("1", 1, 0, 3), "L": ("L", 8, 0, 3), diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index ae98831d2..e5009d0f1 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -41,10 +41,8 @@ from __future__ import division, print_function -from PIL import Image, ImageFile -from PIL import ImagePalette -from PIL import _binary -from PIL import TiffTags +from . import Image, ImageFile, ImagePalette, TiffTags +from ._binary import i8, o8 import collections from fractions import Fraction @@ -71,9 +69,6 @@ IFD_LEGACY_API = True II = b"II" # little-endian (Intel style) MM = b"MM" # big-endian (Motorola style) -i8 = _binary.i8 -o8 = _binary.o8 - # # -------------------------------------------------------------------- # Read TIFF files @@ -569,7 +564,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): def _register_loader(idx, size): def decorator(func): - from PIL.TiffTags import TYPES + from .TiffTags import TYPES if func.__name__.startswith("load_"): TYPES[idx] = func.__name__[5:].replace("_", " ") _load_dispatch[idx] = size, func @@ -583,7 +578,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): return decorator def _register_basic(idx_fmt_name): - from PIL.TiffTags import TYPES + from .TiffTags import TYPES idx, fmt, name = idx_fmt_name TYPES[idx] = name size = struct.calcsize("=" + fmt) diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py index 1167fa739..a17238a5a 100644 --- a/PIL/WalImageFile.py +++ b/PIL/WalImageFile.py @@ -23,7 +23,8 @@ from __future__ import print_function -from PIL import Image, _binary +from . import Image +from ._binary import i32le as i32 try: import builtins @@ -31,8 +32,6 @@ except ImportError: import __builtin__ builtins = __builtin__ -i32 = _binary.i32le - def open(filename): """ diff --git a/PIL/WebPImagePlugin.py b/PIL/WebPImagePlugin.py index 6837b53be..b93e0d3e7 100644 --- a/PIL/WebPImagePlugin.py +++ b/PIL/WebPImagePlugin.py @@ -1,7 +1,5 @@ -from PIL import Image -from PIL import ImageFile +from . import Image, ImageFile, _webp from io import BytesIO -from PIL import _webp _VALID_WEBP_MODES = { @@ -43,7 +41,7 @@ class WebPImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0) + self.size, 0, self.mode)] def _getexif(self): - from PIL.JpegImagePlugin import _getexif + from .JpegImagePlugin import _getexif return _getexif(self) diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py index 99e498226..f7076c0d9 100644 --- a/PIL/WmfImagePlugin.py +++ b/PIL/WmfImagePlugin.py @@ -21,7 +21,10 @@ from __future__ import print_function -from PIL import Image, ImageFile, _binary +from . import Image, ImageFile +from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long + + __version__ = "0.2" @@ -59,13 +62,6 @@ if hasattr(Image.core, "drawwmf"): register_handler(WmfHandler()) -# -------------------------------------------------------------------- - -word = _binary.i16le -short = _binary.si16le -dword = _binary.i32le -_long = _binary.si32le - # # -------------------------------------------------------------------- # Read WMF file diff --git a/PIL/XVThumbImagePlugin.py b/PIL/XVThumbImagePlugin.py index 0034ff7d0..6929e8b82 100644 --- a/PIL/XVThumbImagePlugin.py +++ b/PIL/XVThumbImagePlugin.py @@ -17,12 +17,11 @@ # FIXME: make save work (this requires quantization support) # -from PIL import Image, ImageFile, ImagePalette, _binary +from . import Image, ImageFile, ImagePalette +from ._binary import o8 __version__ = "0.1" -o8 = _binary.o8 - _MAGIC = b"P7 332" # standard color palette for thumbnails (RGB332) diff --git a/PIL/XbmImagePlugin.py b/PIL/XbmImagePlugin.py index d0b0e47ab..b43fbef50 100644 --- a/PIL/XbmImagePlugin.py +++ b/PIL/XbmImagePlugin.py @@ -20,7 +20,7 @@ # import re -from PIL import Image, ImageFile +from . import Image, ImageFile __version__ = "0.6" diff --git a/PIL/XpmImagePlugin.py b/PIL/XpmImagePlugin.py index 556adb8f7..87736aff9 100644 --- a/PIL/XpmImagePlugin.py +++ b/PIL/XpmImagePlugin.py @@ -16,8 +16,8 @@ import re -from PIL import Image, ImageFile, ImagePalette -from PIL._binary import i8, o8 +from . import Image, ImageFile, ImagePalette +from ._binary import i8, o8 __version__ = "0.2" diff --git a/PIL/features.py b/PIL/features.py index 134d85abf..fb8e4371b 100644 --- a/PIL/features.py +++ b/PIL/features.py @@ -1,4 +1,4 @@ -from PIL import Image +from . import Image modules = { "pil": "PIL._imaging", From fbb92e99bb6426f1ee9a248db30d19328aeb8531 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 17 Jan 2017 13:23:30 +0000 Subject: [PATCH 240/267] Update Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 128d7ca3e..102686d2c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- Replaced absolute PIL imports with relative imports #2349 + [radarhere] + +- Added context managers for file handling #2307 + [radarhere] + - Expose registered file extensions in Image #2343 [iggomez, radarhere] From c13676e97556991f6aa5744eec1dd0d3d740e032 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 17 Jan 2017 06:21:47 -0800 Subject: [PATCH 241/267] Removed PIL 1.0 era readme that concerns Windows 95/NT --- Tk/README.rst | 285 -------------------------------------------------- 1 file changed, 285 deletions(-) delete mode 100644 Tk/README.rst diff --git a/Tk/README.rst b/Tk/README.rst deleted file mode 100644 index 61385effb..000000000 --- a/Tk/README.rst +++ /dev/null @@ -1,285 +0,0 @@ -Using PIL With Tkinter -==================================================================== - -Starting with 1.0 final (release candidate 2 and later, to be -precise), PIL can attach itself to Tkinter in flight. As a result, -you no longer need to rebuild the Tkinter extension to be able to -use PIL. - -However, if you cannot get the this to work on your platform, you -can do it in the old way: - -Adding Tkinter support ----------------------- - -1. Compile Python's _tkinter.c with the WITH_APPINIT and WITH_PIL - flags set, and link it with tkImaging.c and tkappinit.c. To - do this, copy the former to the Modules directory, and edit - the _tkinter line in Setup (or Setup.in) according to the - instructions in that file. - - NOTE: if you have an old Python version, the tkappinit.c - file is not included by default. If this is the case, you - will have to add the following lines to tkappinit.c, after - the MOREBUTTONS stuff:: - - { - extern void TkImaging_Init(Tcl_Interp* interp); - TkImaging_Init(interp); - } - - This registers a Tcl command called "PyImagingPhoto", which is - use to communicate between PIL and Tk's PhotoImage handler. - - You must also change the _tkinter line in Setup (or Setup.in) - to something like:: - - _tkinter _tkinter.c tkImaging.c tkappinit.c -DWITH_APPINIT - -I/usr/local/include -L/usr/local/lib -ltk8.0 -ltcl8.0 -lX11 - -The Photoimage Booster Patch (for Windows 95/NT) -==================================================================== - -This patch kit boosts performance for 16/24-bit displays. The -first patch is required on Tk 4.2 (where it fixes the problems for -16-bit displays) and later versions. By installing both patches, -Tk's PhotoImage handling becomes much faster on both 16-bit and -24-bit displays. The patch has been tested with Tk 4.2 and 8.0. - -Here's a benchmark, made with a sample program which loads two -512x512 greyscale PGM's, and two 512x512 colour PPM's, and displays -each of them in a separate toplevel windows. Tcl/Tk was compiled -with Visual C 4.0, and run on a P100 under Win95. Image load times -are not included in the timings: - -+----------------------+------------+-------------+----------------+ -| | **8-bit** | **16-bit** | **24-bit** | -+----------------------+------------+-------------+----------------+ -| 1. original 4.2 code | 5.52 s | 8.57 s | 3.79 s | -+----------------------+------------+-------------+----------------+ -| 2. booster patch | 5.49 s | 1.87 s | 1.82 s | -+----------------------+------------+-------------+----------------+ -| speedup | None | 4.6x | 2.1x | -+----------------------+------------+-------------+----------------+ - -Here's the patches: - -1. For portability and speed, the best thing under Windows is to -treat 16-bit displays as if they were 24-bit. The Windows device -drivers take care of the rest. - -.. Note:: - - If you have Tk 4.1 or Tk 8.0b1, you don't have to apply this - patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. - -In ``win/tkWinImage.c``, change the following line in ``XCreateImage``:: - - imagePtr->bits_per_pixel = depth; - -to:: - - /* ==================================================================== */ - /* The tk photo image booster patch -- patch section 1 */ - /* ==================================================================== */ - - if (visual->class == TrueColor) - /* true colour is stored as 3 bytes: (blue, green, red) */ - imagePtr->bits_per_pixel = 24; - else - imagePtr->bits_per_pixel = depth; - - /* ==================================================================== */ - - -2. The DitherInstance implementation is not good. It's especially -bad on highend truecolour displays. IMO, it should be rewritten from -scratch (some other day...). - -Anyway, the following band-aid makes the situation a little bit -better under Windows. This hack trades some marginal quality (no -dithering on 16-bit displays) for a dramatic performance boost. -Requires patch 1, unless you're using Tk 4.1 or Tk 8.0b1. - -In generic/tkImgPhoto.c, add the #ifdef section to the DitherInstance -function:: - - /* ==================================================================== */ - - for (; height > 0; height -= nLines) { - if (nLines > height) { - nLines = height; - } - dstLinePtr = (unsigned char *) imagePtr->data; - yEnd = yStart + nLines; - - /* ==================================================================== */ - /* The tk photo image booster patch -- patch section 2 */ - /* ==================================================================== */ - - #ifdef __WIN32__ - if (colorPtr->visualInfo.class == TrueColor - && instancePtr->gamma == 1.0) { - /* Windows hicolor/truecolor booster */ - for (y = yStart; y < yEnd; ++y) { - destBytePtr = dstLinePtr; - srcPtr = srcLinePtr; - for (x = xStart; x < xEnd; ++x) { - destBytePtr[0] = srcPtr[2]; - destBytePtr[1] = srcPtr[1]; - destBytePtr[2] = srcPtr[0]; - destBytePtr += 3; srcPtr += 3; - } - srcLinePtr += lineLength; - dstLinePtr += bytesPerLine; - } - } else - #endif - - /* ==================================================================== */ - - for (y = yStart; y < yEnd; ++y) { - srcPtr = srcLinePtr; - errPtr = errLinePtr; - destBytePtr = dstLinePtr; - -The PIL Bitmap Booster Patch -==================================================================== - -The pilbitmap booster patch greatly improves performance of the -ImageTk.BitmapImage constructor. Unfortunately, the design of Tk -doesn't allow us to do this from the tkImaging interface module, so -you have to patch the Tk sources. - -Once installed, the ImageTk module will automatically detect this -patch. - -(Note: this patch has been tested with Tk 8.0 on Win32 only, but it -should work just fine on other platforms as well). - -1. To the beginning of TkGetBitmapData (in generic/tkImgBmap.c), add - the following stuff:: - - /* ==================================================================== */ - - int width, height, numBytes, hotX, hotY; - char *p, *end, *expandedFileName; - ParseInfo pi; - char *data = NULL; - Tcl_DString buffer; - - /* ==================================================================== */ - /* The pilbitmap booster patch -- patch section */ - /* ==================================================================== */ - - char *PILGetBitmapData(); - - if (string) { - /* Is this a PIL bitmap reference? */ - data = PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr); - if (data) - return data; - } - - /* ==================================================================== */ - - pi.string = string; - if (string == NULL) { - if (Tcl_IsSafe(interp)) { - -2. Append the following to the same file (you may wish to include -Imaging.h instead of copying the struct declaration...):: - - /* ==================================================================== */ - /* The pilbitmap booster patch -- code section */ - /* ==================================================================== */ - - /* Imaging declaration boldly copied from Imaging.h (!) */ - - typedef struct ImagingInstance *Imaging; /* a.k.a. ImagingImage :-) */ - - typedef unsigned char UINT8; - typedef int INT32; - - struct ImagingInstance { - - /* Format */ - char mode[4+1]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK") */ - int type; /* Always 0 in this version */ - int depth; /* Always 8 in this version */ - int bands; /* Number of bands (1, 3, or 4) */ - int xsize; /* Image dimension. */ - int ysize; - - /* Colour palette (for "P" images only) */ - void* palette; - - /* Data pointers */ - UINT8 **image8; /* Set for 8-bit image (pixelsize=1). */ - INT32 **image32; /* Set for 32-bit image (pixelsize=4). */ - - /* Internals */ - char **image; /* Actual raster data. */ - char *block; /* Set if data is allocated in a single block. */ - - int pixelsize; /* Size of a pixel, in bytes (1 or 4) */ - int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ - - /* Virtual methods */ - void (*im_delete)(Imaging *); - - }; - - /* The pilbitmap booster patch allows you to pass PIL images to the - Tk bitmap decoder. Passing images this way is much more efficient - than using the "tobitmap" method. */ - - char * - PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr) - char *string; - int *widthPtr, *heightPtr; - int *hotXPtr, *hotYPtr; - { - char* data; - char* p; - int y; - Imaging im; - - if (strncmp(string, "PIL:", 4) != 0) - return NULL; - - im = (Imaging) atol(string + 4); - - if (strcmp(im->mode, "1") != 0 && strcmp(im->mode, "L") != 0) - return NULL; - - data = p = (char *) ckalloc((unsigned) ((im->xsize+7)/8) * im->ysize); - - for (y = 0; y < im->ysize; y++) { - char* in = im->image8[y]; - int i, m, b; - b = 0; m = 1; - for (i = 0; i < im->xsize; i++) { - if (in[i] != 0) - b |= m; - m <<= 1; - if (m == 256){ - *p++ = b; - b = 0; m = 1; - } - } - if (m != 1) - *p++ = b; - } - - *widthPtr = im->xsize; - *heightPtr = im->ysize; - *hotXPtr = -1; - *hotYPtr = -1; - - return data; - } - - /* ==================================================================== */ - -3. Recompile Tk and relink the _tkinter module (where necessary). \ No newline at end of file From a46ce022e72e4b93e7ed4a7ce3d169bbe9e3cfda Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 19 Jan 2017 19:45:49 +1100 Subject: [PATCH 242/267] Removed warning about zlib 1.2.2 security vulnerability --- setup.py | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/setup.py b/setup.py index b0209a399..b967926eb 100755 --- a/setup.py +++ b/setup.py @@ -631,16 +631,11 @@ class pil_build_ext(build_ext): build_ext.build_extensions(self) # - # sanity and security checks + # sanity checks - unsafe_zlib = None + self.summary_report(feature) - if feature.zlib: - unsafe_zlib = self.check_zlib_version(self.compiler.include_dirs) - - self.summary_report(feature, unsafe_zlib) - - def summary_report(self, feature, unsafe_zlib): + def summary_report(self, feature): print("-" * 68) print("PIL SETUP SUMMARY") @@ -676,16 +671,6 @@ class pil_build_ext(build_ext): print("*** %s support not available" % option[1]) all = 0 - if feature.zlib and unsafe_zlib: - print("") - print("*** Warning: zlib", unsafe_zlib) - print("may contain a security vulnerability.") - print("*** Consider upgrading to zlib 1.2.3 or newer.") - print("*** See: http://www.kb.cert.org/vuls/id/238678") - print(" http://www.kb.cert.org/vuls/id/680620") - print(" http://www.gzip.org/zlib/advisory-2002-03-11.txt") - print("") - print("-" * 68) if not all: @@ -697,21 +682,6 @@ class pil_build_ext(build_ext): print("To check the build, run the selftest.py script.") print("") - def check_zlib_version(self, include_dirs): - # look for unsafe versions of zlib - for subdir in include_dirs: - zlibfile = os.path.join(subdir, "zlib.h") - if os.path.isfile(zlibfile): - break - else: - return - for line in open(zlibfile).readlines(): - m = re.match(r'#define\s+ZLIB_VERSION\s+"([^"]*)"', line) - if not m: - continue - if m.group(1) < "1.2.3": - return m.group(1) - # https://hg.python.org/users/barry/rev/7e8deab93d5a def add_multiarch_paths(self): # Debian/Ubuntu multiarch support. From 8a9bd2cdcdd7fc9ecd89fa79323463f4d24f53b8 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Thu, 19 Jan 2017 17:24:28 +0100 Subject: [PATCH 243/267] Default to inch-interpretation for missing ResolutionUnit in TiffImagePlugin. --- PIL/TiffImagePlugin.py | 6 +++++- Tests/test_file_tiff.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index e5009d0f1..505025bb8 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1163,11 +1163,15 @@ class TiffImageFile(ImageFile.ImageFile): yres = self.tag_v2.get(Y_RESOLUTION, 1) if xres and yres: - resunit = self.tag_v2.get(RESOLUTION_UNIT, 1) + resunit = self.tag_v2.get(RESOLUTION_UNIT) if resunit == 2: # dots per inch self.info["dpi"] = xres, yres elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 + elif resunit == None: # used to default to 1, but now 2) + self.info["dpi"] = xres, yres + # For backward compatibility, we also preserve the old behavior. + self.info["resolution"] = xres, yres else: # No absolute unit of measurement self.info["resolution"] = xres, yres diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index bf19947a1..1fe3ad45e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -93,6 +93,24 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.info['dpi'], (72., 72.)) + def test_xyres_fallback_tiff(self): + from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION, RESOLUTION_UNIT + filename = "Tests/images/compression.tif" + im = Image.open(filename) + + # v2 api + self.assertIsInstance(im.tag_v2[X_RESOLUTION], + TiffImagePlugin.IFDRational) + self.assertIsInstance(im.tag_v2[Y_RESOLUTION], + TiffImagePlugin.IFDRational) + self.assertRaises(KeyError, + lambda: im.tag_v2[RESOLUTION_UNIT]) + + # Legacy. + self.assertEqual(im.info['resolution'], (100., 100.)) + # Fallback "inch". + self.assertEqual(im.info['dpi'], (100., 100.)) + def test_int_resolution(self): from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION filename = "Tests/images/pil168.tif" From 9099798ff279d00c80d3e336de2f8f82fa95b50e Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 19 Jan 2017 20:53:31 +0200 Subject: [PATCH 244/267] [CI ski[ --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 102686d2c..873326be7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,7 +16,7 @@ Changelog (Pillow) - Make mode descriptor cache initialization thread-safe. #2351 [gunjambi] -- Updated windows test dependencies: Freetype 2.7.1, zlib 1.2.10. #2331, #2332 +- Updated Windows test dependencies: Freetype 2.7.1, zlib 1.2.11 #2331, #2332, #2357 [radarhere] - Followed upstream pngquant packaging reorg to libimagquant #2354 From bceee54895f851028b76e75eca3149f41fa4c44d Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 20 Jan 2017 21:10:02 +0200 Subject: [PATCH 245/267] Prevent nose -v printing docstrings (#2369) --- Tests/helper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/helper.py b/Tests/helper.py index f4b6b52cf..f8eed3e2d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -149,6 +149,10 @@ class PillowTestCase(unittest.TestCase): if skip: self.skipTest(msg or "Known Bad Test") + def shortDescription(self): + # Prevents `nose -v` printing docstrings + return None + def tempfile(self, template): assert template[:5] in ("temp.", "temp_") fd, path = tempfile.mkstemp(template[4:], template[:4]) From da8f2737a8a325ed5bb1d24a777a0b4d3ddaa7d8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jan 2017 19:12:44 +0000 Subject: [PATCH 246/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 873326be7..0816eabcc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360 + [wiredfool] + +- Prevent `nose -v` printing docstrings #2369 + [hugovk] + - Replaced absolute PIL imports with relative imports #2349 [radarhere] From 3c7798bc7bedc7c868e88ddcf605c8f128f61a90 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 19 Jan 2017 07:54:54 -0800 Subject: [PATCH 247/267] Add prebuilt docker files to travis --- .travis.yml | 129 +++++++++++---------------------------- .travis/after_success.sh | 44 +++++++++++++ .travis/install.sh | 34 +++++++++++ .travis/script.sh | 14 +++++ 4 files changed, 126 insertions(+), 95 deletions(-) create mode 100755 .travis/after_success.sh create mode 100755 .travis/install.sh create mode 100755 .travis/script.sh diff --git a/.travis.yml b/.travis.yml index 3478d0aa0..2259fd5ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,49 +6,37 @@ notifications: # Run slow PyPy* first, to give them a headstart and reduce waiting time. # Run latest 3.x and 2.x next, to get quick compatibility results. # Then run the remainder. -python: - - "pypy" - - "pypy3" - - 3.6 - - 2.7 - - "2.7_with_system_site_packages" # For PyQt4 - - 3.5 - - 3.4 - - 3.3 - - nightly +matrix: + fast_finish: false + allow_failures: + - python: nightly + include: + - python: "pypy" + - python: "pypy3" + - python: '3.6' + - python: '2.7' + - env: DOCKER="alpine" + - env: DOCKER="ubuntu-trusty-x86" + - env: DOCKER="ubuntu-xenial-amd64" + - env: DOCKER="ubuntu-precise-amd64" + - python: "2.7_with_system_site_packages" # For PyQt4 + - python: '3.5' + - python: '3.4' + - python: '3.3' + dist: trusty +sudo: required + +services: + - docker + install: - - "travis_retry sudo apt-get update" - - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" - - "travis_retry pip install cffi" - - "travis_retry pip install nose" - - "travis_retry pip install check-manifest" - - "travis_retry pip install olefile" - # Pyroma tests sometimes hang on PyPy; skip - - if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi - - - "travis_retry pip install coverage" - - # docs only on python 2.7 - - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then travis_retry pip install -r requirements.txt ; fi - - # clean checkout for manifest - - mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest - - # webp - - pushd depends && ./install_webp.sh && popd - - # openjpeg - - pushd depends && ./install_openjpeg.sh && popd - - # libimagequant - - pushd depends && ./install_imagequant.sh && popd - - # extra test images - - pushd depends && ./install_extra_test_images.sh && popd + - if [ "$DOCKER" == "" ]; then .travis/install.sh; fi +before_install: + - if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER; fi before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install @@ -56,60 +44,16 @@ before_script: - "sh -e /etc/init.d/xvfb start" script: - - coverage erase - - python setup.py clean - - CFLAGS="-coverage" python setup.py build_ext --inplace - - - coverage run --append --include=PIL/* selftest.py - - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py - - pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd - - # Docs - - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi + - | + if [ "$DOCKER" == "" ]; then + .travis/script.sh + else + docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER + fi after_success: - # gather the coverage data - - travis_retry sudo apt-get -qq install lcov - - lcov --capture --directory . -b . --output-file coverage.info - # filter to remove system headers - - lcov --remove coverage.info '/usr/*' -o coverage.filtered.info - # convert to json - - travis_retry gem install coveralls-lcov - - coveralls-lcov -v -n coverage.filtered.info > coverage.c.json - - - coverage report - - travis_retry pip install coveralls-merge - - coveralls-merge coverage.c.json - - - travis_retry pip install pep8 pyflakes - - pep8 --statistics --count PIL/*.py - - pep8 --statistics --count Tests/*.py - - pyflakes *.py | tee >(wc -l) - - pyflakes PIL/*.py | tee >(wc -l) - - pyflakes Tests/*.py | tee >(wc -l) - - # Coverage and quality reports on just the latest diff. - # (Installation is very slow on Py3, so just do it for Py2.) - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi - - # after_all - - | - if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py - python travis_after_all.py - export $(cat .to_export_back) - if [ "$BUILD_LEADER" = "YES" ]; then - if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then - echo "All jobs succeded! Triggering macOS build..." - # Trigger a macOS build at the pillow-wheels repo - ./build_children.sh - else - echo "Some jobs failed" - fi - fi - fi - + - .travis/after_success.sh + after_failure: - | if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then @@ -131,11 +75,6 @@ after_script: echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS fi -matrix: - fast_finish: true - allow_failures: - - python: nightly - env: global: # travis encrypt AUTH_TOKEN= diff --git a/.travis/after_success.sh b/.travis/after_success.sh new file mode 100755 index 000000000..64f35c9c9 --- /dev/null +++ b/.travis/after_success.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# gather the coverage data +sudo apt-get -qq install lcov +lcov --capture --directory . -b . --output-file coverage.info +# filter to remove system headers +lcov --remove coverage.info '/usr/*' -o coverage.filtered.info +# convert to json +gem install coveralls-lcov +coveralls-lcov -v -n coverage.filtered.info > coverage.c.json + +coverage report +pip install coveralls-merge +coveralls-merge coverage.c.json + +if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then + pip install pep8 pyflakes + pep8 --statistics --count PIL/*.py + pep8 --statistics --count Tests/*.py + pyflakes *.py | tee >(wc -l) + pyflakes PIL/*.py | tee >(wc -l) + pyflakes Tests/*.py | tee >(wc -l) +fi + +# Coverage and quality reports on just the latest diff. +# (Installation is very slow on Py3, so just do it for Py2.) +if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi +if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi + +# after_all + +if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py + python travis_after_all.py + export $(cat .to_export_back) + if [ "$BUILD_LEADER" = "YES" ]; then + if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then + echo "All jobs succeded! Triggering macOS build..." + # Trigger a macOS build at the pillow-wheels repo + ./build_children.sh + else + echo "Some jobs failed" + fi + fi diff --git a/.travis/install.sh b/.travis/install.sh new file mode 100755 index 000000000..4b7503bed --- /dev/null +++ b/.travis/install.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +sudo apt-get update +sudo apt-get -qq install libfreetype6-dev liblcms2-dev\ + python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick +pip install cffi +pip install nose +pip install check-manifest +pip install olefile +# Pyroma tests sometimes hang on PyPy; skip +if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then pip install pyroma; fi + +pip install coverage + +# docs only on python 2.7 +if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi + +# clean checkout for manifest +mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest + +# webp +pushd depends && ./install_webp.sh && popd + +# openjpeg +pushd depends && ./install_openjpeg.sh && popd + +# libimagequant +pushd depends && ./install_imagequant.sh && popd + +# extra test images +pushd depends && ./install_extra_test_images.sh && popd + diff --git a/.travis/script.sh b/.travis/script.sh new file mode 100755 index 000000000..e1d522122 --- /dev/null +++ b/.travis/script.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +coverage erase +python setup.py clean +CFLAGS="-coverage" python setup.py build_ext --inplace + +coverage run --append --include=PIL/* selftest.py +coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py +pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd + +# Docs +if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi From 94cc72a2ba879592e437ec8506a2fe50f9f8777e Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 20 Jan 2017 07:50:51 -0800 Subject: [PATCH 248/267] disable tests broken on old system versions of numpy/scipy --- Tests/test_numpy.py | 7 ++++++- Tests/test_scipy.py | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 02ae5e50b..8a530ce1b 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -135,7 +135,12 @@ class TestNumpy(PillowTestCase): img = Image.fromarray(arr * 255).convert('1') self.assertEqual(img.mode, '1') arr_back = numpy.array(img) - numpy.testing.assert_array_equal(arr, arr_back) + # numpy 1.8 and earlier return this as a boolean. (trusty/precise) + if arr_back.dtype == numpy.bool: + arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], 'bool') + numpy.testing.assert_array_equal(arr_bool, arr_back) + else: + numpy.testing.assert_array_equal(arr, arr_back) def test_save_tiff_uint16(self): # Tests that we're getting the pixel value in the right byte order. diff --git a/Tests/test_scipy.py b/Tests/test_scipy.py index 8be16c518..1f4f016d1 100644 --- a/Tests/test_scipy.py +++ b/Tests/test_scipy.py @@ -1,10 +1,11 @@ from helper import unittest, PillowTestCase - +from distutils.version import StrictVersion try: import numpy as np from numpy.testing import assert_equal from scipy import misc + import scipy HAS_SCIPY = True except ImportError: HAS_SCIPY = False @@ -27,6 +28,11 @@ class Test_scipy_resize(PillowTestCase): im1 = misc.imresize(im, T(1.101)) self.assertEqual(im1.shape, (11, 22)) + # this test fails prior to scipy 0.14.0b1 + # https://github.com/scipy/scipy/commit/855ff1fff805fb91840cf36b7082d18565fc8352 + @unittest.skipIf(HAS_SCIPY and + (StrictVersion(scipy.__version__) < StrictVersion('0.14.0')), + "Test fails on scipy < 0.14.0") def test_imresize4(self): im = np.array([[1, 2], [3, 4]]) From 426f8d68b15dacf134881b8276b564b803c35bd8 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 20 Jan 2017 11:34:13 -0800 Subject: [PATCH 249/267] reenable nightly, move diffcover to non-docker 2.7 build --- .travis.yml | 3 ++- .travis/after_success.sh | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2259fd5ea..34f37b611 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ notifications: # Then run the remainder. matrix: - fast_finish: false + fast_finish: true allow_failures: - python: nightly include: @@ -24,6 +24,7 @@ matrix: - python: '3.5' - python: '3.4' - python: '3.3' + - python: 'nightly' dist: trusty diff --git a/.travis/after_success.sh b/.travis/after_success.sh index 64f35c9c9..415224f48 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -13,19 +13,19 @@ coverage report pip install coveralls-merge coveralls-merge coverage.c.json -if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then +if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then pip install pep8 pyflakes pep8 --statistics --count PIL/*.py pep8 --statistics --count Tests/*.py pyflakes *.py | tee >(wc -l) pyflakes PIL/*.py | tee >(wc -l) pyflakes Tests/*.py | tee >(wc -l) -fi -# Coverage and quality reports on just the latest diff. -# (Installation is very slow on Py3, so just do it for Py2.) -if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi -if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi + # Coverage and quality reports on just the latest diff. + # (Installation is very slow on Py3, so just do it for Py2.) + depends/diffcover-install.sh + depends/diffcover-run.sh +fi # after_all From 361f579579ca9cfed40a9bb4fc322e0ea99d60e4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 15 Jan 2017 17:36:59 +1100 Subject: [PATCH 250/267] Moved iCCP chunk before PLTE chunk when saving as PNG --- PIL/PngImagePlugin.py | 24 ++++++++++++------------ Tests/test_file_png.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index dca8a456e..486162a38 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -715,6 +715,18 @@ def _save(im, fp, filename, chunk=putchunk, check=0): b'\0', # 11: filter category b'\0') # 12: interlace flag + icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + # ICC profile + # according to PNG spec, the iCCP chunk contains: + # Profile name 1-79 bytes (character string) + # Null separator 1 byte (null character) + # Compression method 1 byte (0) + # Compressed profile n bytes (zlib with deflate compression) + name = b"ICC Profile" + data = name + b"\0\0" + zlib.compress(icc) + chunk(fp, b"iCCP", data) + if im.mode == "P": palette_byte_number = (2 ** bits) * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] @@ -764,18 +776,6 @@ def _save(im, fp, filename, chunk=putchunk, check=0): for cid, data in info.chunks: chunk(fp, cid, data) - icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) - if icc: - # ICC profile - # according to PNG spec, the iCCP chunk contains: - # Profile name 1-79 bytes (character string) - # Null separator 1 byte (null character) - # Compression method 1 byte (0) - # Compressed profile n bytes (zlib with deflate compression) - name = b"ICC Profile" - data = name + b"\0\0" + zlib.compress(icc) - chunk(fp, b"iCCP", data) - ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0)+im.size, 0, rawmode)]) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index cab0e01fa..fc6b07699 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -497,6 +497,25 @@ class TestFilePng(PillowTestCase): self.assertEqual(repr_png.format, 'PNG') self.assert_image_equal(im, repr_png) + def test_chunk_order(self): + im = Image.open("Tests/images/icc_profile.png") + test_file = self.tempfile("temp.png") + im.convert("P").save(test_file) + + chunks = [] + fp = open(test_file, "rb") + fp.read(8) + png = PngImagePlugin.PngStream(fp) + while True: + cid, pos, length = png.read() + chunks.append(cid) + try: + s = png.call(cid, pos, length) + except EOFError: + break + png.crc(cid, s) + self.assertLess(chunks.index(b"iCCP"), chunks.index(b"PLTE")) + if __name__ == '__main__': unittest.main() From ded14572a180142dd3c75fd55daf6d873255daf0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Jan 2017 14:47:59 +1100 Subject: [PATCH 251/267] Added more tests for PNG chunk ordering --- Tests/test_file_png.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index fc6b07699..8af5afe2f 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -500,7 +500,7 @@ class TestFilePng(PillowTestCase): def test_chunk_order(self): im = Image.open("Tests/images/icc_profile.png") test_file = self.tempfile("temp.png") - im.convert("P").save(test_file) + im.convert("P").save(test_file, dpi=(100, 100)) chunks = [] fp = open(test_file, "rb") @@ -514,7 +514,20 @@ class TestFilePng(PillowTestCase): except EOFError: break png.crc(cid, s) + + # https://www.w3.org/TR/PNG/#5ChunkOrdering + # IHDR - shall be first + self.assertEqual(chunks.index(b"IHDR"), 0) + # PLTE - before first IDAT + self.assertLess(chunks.index(b"PLTE"), chunks.index(b"IDAT")) + # iCCP - before PLTE and IDAT self.assertLess(chunks.index(b"iCCP"), chunks.index(b"PLTE")) + self.assertLess(chunks.index(b"iCCP"), chunks.index(b"IDAT")) + # tRNS - after PLTE, before IDAT + self.assertGreater(chunks.index(b"tRNS"), chunks.index(b"PLTE")) + self.assertLess(chunks.index(b"tRNS"), chunks.index(b"IDAT")) + # pHYs - before IDAT + self.assertLess(chunks.index(b"pHYs"), chunks.index(b"IDAT")) if __name__ == '__main__': From f0480de118c36024094801fd98de9513c6ec4785 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Jan 2017 16:57:03 +1100 Subject: [PATCH 252/267] Restricted PNG encoderinfo chunks to valid values when saving --- PIL/PngImagePlugin.py | 19 ++++++++++++++++++- Tests/test_file_png.py | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 486162a38..a633d4e54 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -715,6 +715,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0): b'\0', # 11: filter category b'\0') # 12: interlace flag + chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"] + icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) if icc: # ICC profile @@ -726,6 +728,18 @@ def _save(im, fp, filename, chunk=putchunk, check=0): name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(icc) chunk(fp, b"iCCP", data) + else: + chunks.remove(b"sRGB") + + info = im.encoderinfo.get("pnginfo") + if info: + chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"] + for cid, data in info.chunks: + if cid in chunks: + chunks.remove(cid) + chunk(fp, cid, data) + elif cid in chunks_multiple_allowed: + chunk(fp, cid, data) if im.mode == "P": palette_byte_number = (2 ** bits) * 3 @@ -773,8 +787,11 @@ def _save(im, fp, filename, chunk=putchunk, check=0): info = im.encoderinfo.get("pnginfo") if info: + chunks = [b"bKGD", b"hIST"] for cid, data in info.chunks: - chunk(fp, cid, data) + if cid in chunks: + chunks.remove(cid) + chunk(fp, cid, data) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0)+im.size, 0, rawmode)]) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 8af5afe2f..32d6a3acd 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -394,8 +394,8 @@ class TestFilePng(PillowTestCase): self.assertIsInstance(im.info["Text"], str) def test_unicode_text(self): - # Check preservation of non-ASCII characters on Python3 - # This cannot really be meaningfully tested on Python2, + # Check preservation of non-ASCII characters on Python 3 + # This cannot really be meaningfully tested on Python 2, # since it didn't preserve charsets to begin with. def rt_text(value): From 02e077aa480504977a5554e7181a13547c14938f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 21 Jan 2017 02:21:58 -0800 Subject: [PATCH 253/267] run pep8/pyflakes on all non-docker builds --- .travis/after_success.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis/after_success.sh b/.travis/after_success.sh index 415224f48..136dbdc8e 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -13,14 +13,16 @@ coverage report pip install coveralls-merge coveralls-merge coverage.c.json -if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then +if [ "$DOCKER" == "" ]; then pip install pep8 pyflakes pep8 --statistics --count PIL/*.py pep8 --statistics --count Tests/*.py pyflakes *.py | tee >(wc -l) pyflakes PIL/*.py | tee >(wc -l) pyflakes Tests/*.py | tee >(wc -l) +fi +if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then # Coverage and quality reports on just the latest diff. # (Installation is very slow on Py3, so just do it for Py2.) depends/diffcover-install.sh From ca3f6a25f40f6c51d69c71f1206e337684a36ef8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 17 Jan 2017 06:20:39 -0800 Subject: [PATCH 254/267] Applied patch: https://github.com/matplotlib/matplotlib/commit/a91559b82cf23ee407cd57580653015fc7dc35f0 to fix issue #1902 --- _imagingtk.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_imagingtk.c b/_imagingtk.c index 87de36a04..d0295f317 100644 --- a/_imagingtk.c +++ b/_imagingtk.c @@ -36,18 +36,18 @@ _tkinit(PyObject* self, PyObject* args) { Tcl_Interp* interp; - Py_ssize_t arg; + PyObject* arg; int is_interp; - if (!PyArg_ParseTuple(args, "ni", &arg, &is_interp)) + if (!PyArg_ParseTuple(args, "Oi", &arg, &is_interp)) return NULL; if (is_interp) - interp = (Tcl_Interp*) arg; + interp = (Tcl_Interp*)PyLong_AsVoidPtr(arg); else { TkappObject* app; /* Do it the hard way. This will break if the TkappObject layout changes */ - app = (TkappObject*) arg; + app = (TkappObject*)PyLong_AsVoidPtr(arg); interp = app->interp; } From e66271d46477cedca2ffb18a1206e3436f7710d8 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Tue, 24 Jan 2017 05:43:58 -0800 Subject: [PATCH 255/267] added tests for functionality in ImageTk --- Tests/test_imagetk.py | 63 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index f56333a59..1d5a281a6 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,22 +1,29 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper from PIL import Image + try: from PIL import ImageTk + import Tkinter as tk dir(ImageTk) + HAS_TK = True except (OSError, ImportError) as v: # Skipped via setUp() - pass - + HAS_TK = False + +TK_MODES = ('1', 'L', 'P', 'RGB', 'RGBA') class TestImageTk(PillowTestCase): def setUp(self): + if not HAS_TK: + self.skipTest("Tk not installed") try: - from PIL import ImageTk - dir(ImageTk) - except (OSError, ImportError) as v: - self.skipTest(v) + # setup tk + app = tk.Frame() + #root = tk.Tk() + except (tk.TclError) as v: + self.skipTest("TCL Error: %s" % v) def test_kw(self): TEST_JPG = "Tests/images/hopper.jpg" @@ -40,5 +47,47 @@ class TestImageTk(PillowTestCase): self.assertEqual(im, None) + def test_photoimage(self): + for mode in TK_MODES: + # test as image: + im = hopper(mode) + + # this should not crash + im_tk = ImageTk.PhotoImage(im) + + self.assertEqual(im_tk.width(), im.width) + self.assertEqual(im_tk.height(), im.height) + + # _tkinter.TclError: this function is not yet supported + #reloaded = ImageTk.getimage(im_tk) + #self.assert_image_equal(reloaded, im) + + + + def test_photoimage_blank(self): + # test a image using mode/size: + for mode in TK_MODES: + im_tk = ImageTk.PhotoImage(mode, (100,100)) + + self.assertEqual(im_tk.width(), 100) + self.assertEqual(im_tk.height(), 100) + + #reloaded = ImageTk.getimage(im_tk) + #self.assert_image_equal(reloaded, im) + + def test_bitmapimage(self): + im = hopper('1') + + # this should not crash + im_tk = ImageTk.BitmapImage(im) + + self.assertEqual(im_tk.width(), im.width) + self.assertEqual(im_tk.height(), im.height) + + #reloaded = ImageTk.getimage(im_tk) + #self.assert_image_equal(reloaded, im) + + + if __name__ == '__main__': unittest.main() From a69c37738ab5bf370e007f762272710a86a9bd5a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 05:30:49 -0800 Subject: [PATCH 256/267] Look in a different location for the tk intepreter on pypy fixes #2376 --- PIL/ImageTk.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/PIL/ImageTk.py b/PIL/ImageTk.py index 25c4534ce..d0d6fa8fd 100644 --- a/PIL/ImageTk.py +++ b/PIL/ImageTk.py @@ -32,6 +32,13 @@ except ImportError: tkinter = Tkinter del Tkinter +# required for pypy, which always has cffi installed +try: + from cffi import FFI + ffi = FFI() +except ImportError: + pass + from . import Image from io import BytesIO @@ -184,7 +191,13 @@ class PhotoImage(object): try: from . import _imagingtk try: - _imagingtk.tkinit(tk.interpaddr(), 1) + if hasattr(tk, 'interp'): + # Pypy is using a ffi cdata element + # (Pdb) self.tk.interp + # + _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) + else: + _imagingtk.tkinit(tk.interpaddr(), 1) except AttributeError: _imagingtk.tkinit(id(tk), 0) tk.call("PyImagingPhoto", self.__photo, block.id) From b4fbd36adb718488f4bfd7618d012c5db8f83565 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 13:42:07 +0000 Subject: [PATCH 257/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0816eabcc..7bd2adc97 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- Tests: Added docker images for testing alternate platforms. See also https://github.com/python-pillow/docker-images. #2368 + [wiredfool] + - Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360 [wiredfool] From a71ba1a1c63678c701524203788bf2d3c733d1e5 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 05:55:18 -0800 Subject: [PATCH 258/267] comments --- PIL/ImageTk.py | 2 ++ Tk/tkImaging.c | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/PIL/ImageTk.py b/PIL/ImageTk.py index d0d6fa8fd..8c6d1a9c2 100644 --- a/PIL/ImageTk.py +++ b/PIL/ImageTk.py @@ -277,6 +277,8 @@ class BitmapImage(object): def getimage(photo): + """ This function is unimplemented """ + """Copies the contents of a PhotoImage to a PIL image memory.""" photo.tk.call("PyImagingPhotoGet", photo) diff --git a/Tk/tkImaging.c b/Tk/tkImaging.c index 6c612cfe9..f448be166 100644 --- a/Tk/tkImaging.c +++ b/Tk/tkImaging.c @@ -24,10 +24,7 @@ * This registers a Tcl command called "PyImagingPhoto", which is used * to communicate between PIL and Tk's PhotoImage handler. * - * Compile and link tkImaging.c with tkappinit.c and _tkinter (see the - * Setup file for details on how to use tkappinit.c). Note that - * _tkinter.c must be compiled with WITH_APPINIT. - * + * History: * 1995-09-12 fl Created * 1996-04-08 fl Ready for release @@ -169,7 +166,7 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, return TCL_OK; } - +/* Warning -- this does not work at all */ static int PyImagingPhotoGet(ClientData clientdata, Tcl_Interp* interp, int argc, const char **argv) From 74a29602e8ce00af086732f9ffd8dd5cb1181d29 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 14:12:58 +0000 Subject: [PATCH 259/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7bd2adc97..014aabd44 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- Fixed segfault when using ImagingTk on pypy Issue #2376, #2359. + [wiredfool] + +- Fixed Integer overflow using ImagingTk on 32 bit platforms #2359 + [wiredfool, QuLogic] + - Tests: Added docker images for testing alternate platforms. See also https://github.com/python-pillow/docker-images. #2368 [wiredfool] From 54eedd81199f637fb29930b3f3947a13656644a3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 14:23:19 +0000 Subject: [PATCH 260/267] Updated Changes.rst [ci skip] --- CHANGES.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 014aabd44..d5d05a954 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,10 +4,13 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ -- Fixed segfault when using ImagingTk on pypy Issue #2376, #2359. +- Default to inch-interpretation for missing ResolutionUnit in TiffImagePlugin #2365 + [lambdafu] + +- Bug: Fixed segfault when using ImagingTk on pypy Issue #2376, #2359. [wiredfool] -- Fixed Integer overflow using ImagingTk on 32 bit platforms #2359 +- Bug: Fixed Integer overflow using ImagingTk on 32 bit platforms #2359 [wiredfool, QuLogic] - Tests: Added docker images for testing alternate platforms. See also https://github.com/python-pillow/docker-images. #2368 From 851aa210df1956f7e55dec5d161be042bb9c26c6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 26 Jan 2017 14:31:40 +0000 Subject: [PATCH 261/267] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d5d05a954..8c5025758 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 4.1.0 (unreleased) ------------------ +- PNG: Moved iCCP chunk before PLTE chunk when saving as PNG, restricted chunks known value/ordering #2347 + [radarhere] + - Default to inch-interpretation for missing ResolutionUnit in TiffImagePlugin #2365 [lambdafu] From 581fef2ebd1bdc0ff09951a3d9e6250ac08420a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 13:06:28 +1100 Subject: [PATCH 262/267] Added test for Image offset NotImplementedError --- Tests/test_image.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index c79930e76..ae8dd7bf4 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -305,6 +305,13 @@ class TestImage(PillowTestCase): with self.assertRaises(ValueError): Image.core.fill('RGB', (2,-2), (0,0,0)) + def test_offset_not_implemented(self): + # Arrange + im = hopper() + + # Act / Assert + self.assertRaises(NotImplementedError, lambda: im.offset(None)) + if __name__ == '__main__': unittest.main() From e8495e59179f487a46c9aebfd0c40c05e907e471 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 13:50:44 +1100 Subject: [PATCH 263/267] Added test for abbreviated Image paste mask syntax --- Tests/test_image_paste.py | 41 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 4450fd028..1b76c6609 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -28,6 +28,15 @@ class TestImagingPaste(PillowTestCase): ] self.assertEqual(actual, expected) + def assert_9points_paste(self, im, im2, mask, expected): + im3 = im.copy() + im3.paste(im2, (0, 0), mask) + self.assert_9points_image(im3, expected) + + # Abbreviated syntax + im.paste(im2, mask) + self.assert_9points_image(im, expected) + @cached_property def mask_1(self): mask = Image.new('1', (self.size, self.size)) @@ -91,9 +100,7 @@ class TestImagingPaste(PillowTestCase): im = Image.new(mode, (200, 200), 'white') im2 = getattr(self, 'gradient_' + mode) - im.paste(im2, (0, 0), self.mask_1) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, im2, self.mask_1, [ (255, 255, 255, 255), (255, 255, 255, 255), (127, 254, 127, 0), @@ -110,9 +117,7 @@ class TestImagingPaste(PillowTestCase): im = Image.new(mode, (200, 200), 'white') im2 = getattr(self, 'gradient_' + mode) - im.paste(im2, (0, 0), self.mask_L) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, im2, self.mask_L, [ (128, 191, 255, 191), (208, 239, 239, 208), (255, 255, 255, 255), @@ -129,9 +134,7 @@ class TestImagingPaste(PillowTestCase): im = Image.new(mode, (200, 200), 'white') im2 = getattr(self, 'gradient_' + mode) - im.paste(im2, (0, 0), self.gradient_RGBA) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, im2, self.gradient_RGBA, [ (128, 191, 255, 191), (208, 239, 239, 208), (255, 255, 255, 255), @@ -148,9 +151,7 @@ class TestImagingPaste(PillowTestCase): im = Image.new(mode, (200, 200), 'white') im2 = getattr(self, 'gradient_' + mode) - im.paste(im2, (0, 0), self.gradient_RGBa) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, im2, self.gradient_RGBa, [ (128, 255, 126, 255), (0, 127, 126, 255), (126, 253, 126, 255), @@ -180,9 +181,7 @@ class TestImagingPaste(PillowTestCase): im = Image.new(mode, (200, 200), (50, 60, 70, 80)[:len(mode)]) color = (10, 20, 30, 40)[:len(mode)] - im.paste(color, (0, 0), self.mask_1) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, color, self.mask_1, [ (50, 60, 70, 80), (50, 60, 70, 80), (10, 20, 30, 40), @@ -199,9 +198,7 @@ class TestImagingPaste(PillowTestCase): im = getattr(self, 'gradient_' + mode).copy() color = 'white' - im.paste(color, (0, 0), self.mask_L) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, color, self.mask_L, [ (127, 191, 254, 191), (111, 207, 206, 110), (127, 254, 127, 0), @@ -218,9 +215,7 @@ class TestImagingPaste(PillowTestCase): im = getattr(self, 'gradient_' + mode).copy() color = 'white' - im.paste(color, (0, 0), self.gradient_RGBA) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, color, self.gradient_RGBA, [ (127, 191, 254, 191), (111, 207, 206, 110), (127, 254, 127, 0), @@ -237,9 +232,7 @@ class TestImagingPaste(PillowTestCase): im = getattr(self, 'gradient_' + mode).copy() color = 'white' - im.paste(color, (0, 0), self.gradient_RGBa) - - self.assert_9points_image(im, [ + self.assert_9points_paste(im, color, self.gradient_RGBa, [ (255, 63, 126, 63), (47, 143, 142, 46), (126, 253, 126, 255), From 01cb6590a49c250f31912320921d957e7553b522 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 14:02:22 +1100 Subject: [PATCH 264/267] Added test for point operation on F mode image --- Tests/test_image_point.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index dd33b3632..a6d20daa4 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -35,6 +35,9 @@ class TestImagePoint(PillowTestCase): int_lut = [x//2 for x in range(256)] self.assert_image_equal(out.convert('L'), im.point(int_lut, 'L')) + def test_f_mode(self): + self.assertRaises(ValueError, lambda: hopper('F').point(None)) + if __name__ == '__main__': unittest.main() From 2039d43d854b0ec4e71b64cfc476004fac015479 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 14:09:28 +1100 Subject: [PATCH 265/267] Added test for unknown filter in Image resize --- Tests/test_image_resize.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 38a60564c..de7b6abc3 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -97,6 +97,9 @@ class TestImagingCoreResize(PillowTestCase): self.assertEqual(r.size, (212, 195)) self.assertEqual(r.getdata()[0], (0,0,0)) + def test_unknown_filter(self): + self.assertRaises(ValueError, self.resize, hopper(), (10, 10), 9) + class TestImageResize(PillowTestCase): From cd114cef3f71ad3279f3ef947cda09268efe5225 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 14:16:39 +1100 Subject: [PATCH 266/267] Added test for Image fromstring NotImplementedError --- Tests/test_image.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index ae8dd7bf4..afeacb0a0 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -312,6 +312,9 @@ class TestImage(PillowTestCase): # Act / Assert self.assertRaises(NotImplementedError, lambda: im.offset(None)) + def test_fromstring(self): + self.assertRaises(NotImplementedError, Image.fromstring) + if __name__ == '__main__': unittest.main() From e67ee44ea816458ab16136809d88a5a9c7b5e179 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jan 2017 14:21:41 +1100 Subject: [PATCH 267/267] Added test for missing method data in Image transform --- Tests/test_image_transform.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 16e2e4850..da254eaf6 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -140,6 +140,10 @@ class TestImageTransform(PillowTestCase): self.test_mesh() + def test_missing_method_data(self): + self.assertRaises(ValueError, lambda: + hopper().transform((100, 100), None)) + class TestImageTransformAffine(PillowTestCase): transform = Image.AFFINE